-
Notifications
You must be signed in to change notification settings - Fork 4
/
bucket.py
171 lines (124 loc) · 5.11 KB
/
bucket.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import xml.dom.minidom as xml_dom
import urllib.request
import urllib.error
import os
import subprocess
class Bucket:
"""Provides an easy way to download and extract helm charts"""
url_stable = "https://kubernetes-charts.storage.googleapis.com/"
url_incubator = "http://storage.googleapis.com/kubernetes-charts-incubator"
path_charts = "_charts"
path_descriptors = "_descriptors"
def __init__(self, url: str = url_stable, path: str = path_charts, desc: str = path_descriptors):
"""
:param url: points to the xml document that hosts the charts
:param path: the path where the charts will be stored
:param desc: the path where the charts will be extracted
"""
self.url = url
self.path = path
self.descriptor = desc
self.releases = {}
def download(self):
"""Downloads helm charts from the specified url"""
if os.path.isdir(self.path):
print("read index (cached)")
return
next_marker = None
while True:
next_marker = self.download_partial(next_marker)
if not next_marker:
break
self.write()
def download_partial(self, marker=None):
"""
Downloads the partial xml document and returns a pointer to the next document (if any).
:param marker: pointer to the rest of the charts
:return: next_marker if there's any, or None.
"""
print(f'read index... [{marker}]')
murl = self.url
if marker:
murl += f'?marker={marker}'
with urllib.request.urlopen(murl) as req:
res = req.read().decode("utf-8")
dom = xml_dom.parseString(res)
keys, next_marker = self.scan(dom)
releases = self.get_releases(keys)
self.releases = {**self.releases, **releases}
os.makedirs(self.path, exist_ok=True)
return next_marker
def write(self):
"""
Downloads each chart specified in the releases dictionary and stores them
in the specified path.
"""
for release in self.releases:
rel = release + '-' + self.releases[release]
dllink = self.url + rel
print(f"-fetch", dllink, "...")
with urllib.request.urlopen(dllink) as req:
res = req.read()
with open(os.path.join(self.path, rel), "wb") as f:
f.write(res)
def extract(self):
"""Extracts chart templates from packaged charts"""
# if os.path.isdir(self.descriptor):
# print("render templates (cached)")
# return
print("render templates...")
charts = os.listdir(self.path)
os.makedirs(self.descriptor, exist_ok=True)
for chart in charts:
if chart.endswith(".tgz"):
chart_name = os.path.join(self.path, chart)
p = subprocess.run(f"helm template {chart_name}", shell=True, stdout=subprocess.PIPE)
yaml = os.path.join(self.descriptor, chart.replace(".tgz", ".yaml"))
with open(yaml, "wb") as f:
f.write(p.stdout)
@staticmethod
def get_releases(keys):
"""
Takes in the keys list and returns a dictionary [str: str].
The chart's name is the key, and the version is the value.
If multiple versions of the same chart are included,
only the latest version is added to the dictionary.
:param keys: List of charts from the chart repository
:return: releases dict
"""
releases = {}
for key in keys:
parts = key.split("-")
basename = "-".join(parts[:-1])
version = parts[-1]
if not basename:
continue
if basename not in releases:
releases[basename] = version
elif version > releases[basename]:
releases[basename] = version
return releases
@staticmethod
def scan(dom):
"""
Takes the dom of the xml and scans for the "Key" and "NextMarker" elements.
- Key contains the chart's name and version.
- NextMarker points to the next document.
We need NextMarker to retrieve the remaining charts because the xml document
is truncated.
The value of Key is appended to the keys list and returned when the dom is consumed.
The value of NextMarker is returned as well, so the rest of the charts can be retrieved.
:param dom: dom parsed from the url containing charts
:return: keys list, next_marker.
"""
keys = []
next_marker = None
for element in dom.childNodes:
for element2 in element.childNodes:
if element2.nodeName == "Contents":
for element3 in element2.childNodes:
if element3.nodeName == "Key":
keys.append(element3.childNodes[0].nodeValue)
elif element2.nodeName == "NextMarker":
next_marker = element2.childNodes[0].nodeValue
return keys, next_marker