forked from jupyterhub/zero-to-jupyterhub-k8s
-
Notifications
You must be signed in to change notification settings - Fork 0
/
build.py
executable file
·193 lines (156 loc) · 5.88 KB
/
build.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#!/usr/bin/env python3
import argparse
import os
import subprocess
import shutil
from tempfile import TemporaryDirectory
from ruamel.yaml import YAML
# use safe roundtrip yaml loader
yaml = YAML(typ='rt')
yaml.indent(mapping=2, offset=2, sequence=4)
def last_modified_commit(*paths, **kwargs):
return subprocess.check_output([
'git',
'log',
'-n', '1',
'--pretty=format:%h',
*paths
], **kwargs).decode('utf-8')
def last_modified_date(*paths, **kwargs):
return subprocess.check_output([
'git',
'log',
'-n', '1',
'--pretty=format:%cd',
'--date=iso',
*paths
], **kwargs).decode('utf-8')
def path_touched(*paths, commit_range):
return subprocess.check_output([
'git', 'diff', '--name-only', commit_range, *paths
]).decode('utf-8').strip() != ''
def render_build_args(options, ns):
"""Get docker build args dict, rendering any templated args."""
build_args = options.get('buildArgs', {})
for key, value in build_args.items():
build_args[key] = value.format(**ns)
return build_args
def build_image(image_path, image_spec, build_args):
cmd = ['docker', 'build', '-t', image_spec, image_path]
for k, v in build_args.items():
cmd += ['--build-arg', '{}={}'.format(k, v)]
subprocess.check_call(cmd)
def build_images(prefix, images, tag=None, commit_range=None, push=False):
value_modifications = {}
for name, options in images.items():
image_path = os.path.join('images', name)
paths = options.get('paths', []) + [image_path]
last_commit = last_modified_commit(*paths)
if tag is None:
tag = last_commit
image_name = prefix + name
image_spec = '{}:{}'.format(image_name, tag)
value_modifications[options['valuesPath']] = {
'name': image_name,
'tag': tag
}
if commit_range and not path_touched(*paths, commit_range=commit_range):
print(f"Skipping {name}, not touched in {commit_range}")
continue
template_namespace = {
'LAST_COMMIT': last_commit,
'TAG': tag,
}
build_args = render_build_args(options, template_namespace)
build_image(image_path, image_spec, build_args)
if push:
subprocess.check_call([
'docker', 'push', image_spec
])
return value_modifications
def build_values(name, values_mods):
"""Update name/values.yaml with modifications"""
values_file = os.path.join(name, 'values.yaml')
with open(values_file) as f:
values = yaml.load(f)
for key, value in values_mods.items():
parts = key.split('.')
mod_obj = values
for p in parts:
mod_obj = mod_obj[p]
mod_obj.update(value)
with open(values_file, 'w') as f:
yaml.dump(values, f)
def build_chart(name, version=None, paths=None):
"""Update chart with specified version or last-modified commit in path(s)"""
chart_file = os.path.join(name, 'Chart.yaml')
with open(chart_file) as f:
chart = yaml.load(f)
if version is None:
if paths is None:
paths = ['.']
commit = last_modified_commit(*paths)
version = chart['version'].split('-')[0] + '-' + commit
chart['version'] = version
with open(chart_file, 'w') as f:
yaml.dump(chart, f)
def publish_pages(name, paths, git_repo, published_repo):
"""publish helm chart index to github pages"""
version = last_modified_commit(*paths)
checkout_dir = '{}-{}'.format(name, version)
subprocess.check_call([
'git', 'clone', '--no-checkout',
'git@github.com:{}'.format(git_repo), checkout_dir],
)
subprocess.check_call(['git', 'checkout', 'gh-pages'], cwd=checkout_dir)
# package the latest version into a temporary directory
# and run helm repo index with --merge to update index.yaml
# without refreshing all of the timestamps
with TemporaryDirectory() as td:
subprocess.check_call([
'helm', 'package', name,
'--destination', td + '/',
])
subprocess.check_call([
'helm', 'repo', 'index', td,
'--url', published_repo,
'--merge', os.path.join(checkout_dir, 'index.yaml'),
])
# equivalent to `cp td/* checkout/`
# copies new helm chart and updated index.yaml
for f in os.listdir(td):
shutil.copy2(
os.path.join(td, f),
os.path.join(checkout_dir, f)
)
subprocess.check_call(['git', 'add', '.'], cwd=checkout_dir)
subprocess.check_call([
'git',
'commit',
'-m', '[{}] Automatic update for commit {}'.format(name, version)
], cwd=checkout_dir)
subprocess.check_call(
['git', 'push', 'origin', 'gh-pages'],
cwd=checkout_dir,
)
def main():
with open('chartpress.yaml') as f:
config = yaml.load(f)
argparser = argparse.ArgumentParser()
argparser.add_argument('--commit-range', help='Range of commits to consider when building images')
argparser.add_argument('--push', action='store_true')
argparser.add_argument('--publish-chart', action='store_true')
argparser.add_argument('--tag', default=None, help='Use this tag for images & charts')
args = argparser.parse_args()
for chart in config['charts']:
value_mods = build_images(chart['imagePrefix'], chart['images'], args.tag, args.commit_range, args.push)
build_values(chart['name'], value_mods)
chart_paths = ['.'] + chart.get('paths', [])
build_chart(chart['name'], paths=chart_paths, version=args.tag)
if args.publish_chart:
publish_pages(chart['name'],
paths=chart_paths,
git_repo=chart['repo']['git'],
published_repo=chart['repo']['published'],
)
main()