1#!/hint/python3
2
3import json
4import os
5import os.path
6import sys
7from contextlib import contextmanager
8from typing import Dict, Generator, Optional, Tuple, cast
9from urllib.error import HTTPError
10from urllib.request import urlopen
11import fileinput
12import subprocess
13
14from . import ansiterm, assert_eq, build_version, get_is_private
15from .uiutil import Checker, CheckResult, run, run_bincapture, run_txtcapture
16from . import mirror_artifacts
17
18
19def docker_pull(tag: str) -> str:
20 """`docker pull` and then return the image ID"""
21 run(['docker', 'pull', tag])
22 return run_txtcapture(['docker', 'image', 'inspect', tag, '--format={{.Id}}'])
23
24
25def s3_login() -> None:
26 cred_str = run_txtcapture(
27 ['keybase', 'fs', 'read', '/keybase/team/datawireio/secrets/aws.datawire-release-bot.access-key-id'])
28 for line in cred_str.split("\n"):
29 k, v = line.split(':')
30 os.environ[f"AWS_{k.strip()}"] = v.strip()
31
32
33def s3_cat(url: str) -> bytes:
34 return run_bincapture(['aws', 's3', 'cp', url, '-'])
35
36
37def http_cat(url: str) -> bytes:
38 with urlopen(url) as fh:
39 return cast(bytes, fh.read()) # docs say .read() returns 'bytes', typeshed says it returns 'Any'?
40
41
42@contextmanager
43def do_check_s3(checker: Checker,
44 name: str,
45 bucket: str = 'datawire-static-files',
46 private: bool = False) -> Generator[Tuple[CheckResult, Optional[bytes]], None, None]:
47 prefix: Dict[bool, str] = {
48 True: f's3://{bucket}/',
49 False: f'https://s3.amazonaws.com/{bucket}/',
50 }
51 url = prefix[private] + name
52 with checker.check(name=url) as out:
53 try:
54 if private:
55 publicly_readable = True
56 try:
57 http_cat(prefix[False] + name)
58 except HTTPError:
59 publicly_readable = False
60 if publicly_readable:
61 raise Exception('Should be private, but is publicly readable')
62 body = s3_cat(url)
63 else:
64 body = http_cat(url)
65 except Exception as err:
66 yield (out, None)
67 raise
68 else:
69 yield (out, body)
70
71
72def main(ga_ver: str, chart_ver: str, include_docker: bool = True,
73 release_channel: str = "", source_registry: str ="docker.io/datawire",
74 image_append: str = "", image_name: str = "emissary",
75 s3_bucket: str = "datawire-static-files") -> int:
76 warning = """
77 ==> Warning: FIXME: While this script is handy in the things that it
78 does check, there's still quite a bit more that it could check. Don't
79 be fooled into thinking that this script is complete.
80"""
81 print(f"{ansiterm.sgr.fg_red}{warning}{ansiterm.sgr}")
82
83
84 is_private = get_is_private()
85
86 def do_check_docker(checker: Checker, name: str) -> None:
87 with checker.check(name=f'Docker image: {name}', clear_on_success=False) as check:
88 iids = []
89 if release_channel != '':
90 tags = [f"{ga_ver}-{release_channel}"]
91 else:
92 tags = [ga_ver]
93
94 for tag in tags:
95 repos = mirror_artifacts.default_repos
96 if is_private:
97 repos = {'quay.io/datawire-private/emissary'}
98 images = mirror_artifacts.enumerate_images(repos=repos, tag=tag)
99 for image in images:
100 with check.subcheck(name=image) as subcheck:
101 iid = docker_pull(image)
102 iids += [iid]
103 subcheck.result = iid[len('sha256:'):len('sha256:') + 12]
104 with check.subcheck(name='All images match') as subcheck:
105 if len(iids) == 0:
106 return
107 a = iids[0]
108 for b in iids[1:]:
109 if b != a:
110 subcheck.ok = False
111
112 s3_login()
113
114 checker = Checker()
115
116 if include_docker:
117 do_check_docker(checker, 'ambassador')
118 with checker.check('Ambassador S3 files', clear_on_success=False) as checker:
119 with do_check_s3(checker, name=f'emissary-ingress/{release_channel}stable.txt', bucket=s3_bucket) as (subcheck, body):
120 if body is not None:
121 subcheck.result = body.decode('UTF-8').strip()
122 if is_private:
123 assert subcheck.result != ga_ver
124 else:
125 assert_eq(subcheck.result, ga_ver)
126 with do_check_s3(checker, name=f'emissary-ingress/{release_channel}app.json', bucket='scout-datawire-io',
127 private=True) as (subcheck, body):
128 if body is not None:
129 subcheck.result = json.loads(body.decode('UTF-8')).get('latest_version', '')
130 if is_private:
131 assert subcheck.result != ga_ver
132 else:
133 assert_eq(subcheck.result, ga_ver)
134
135 with checker.check(name='Website YAML') as check:
136 yaml_str = http_cat('https://app.getambassador.io/yaml/emissary/latest/emissary-emissaryns.yaml').decode('utf-8')
137 images = [
138 line.strip()[len('image:'):].strip() for line in yaml_str.split("\n")
139 if line.strip().startswith('image:')
140 ]
141 assert_eq(len(images), 2) # One for Ambassador, one for the Agent.
142
143 check_tag = ga_ver
144 if release_channel != '':
145 check_tag = f"{check_tag}-{release_channel}"
146 for image in images:
147 assert '/emissary:' in image
148 check.result = image.split(':', 1)[1]
149 assert_eq(check.result, check_tag)
150
151 with checker.check(name="Updating helm repo"):
152 subprocess.run(['helm', 'repo', 'rm', 'emissary'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
153 subprocess.run(['helm', 'repo', 'add', 'emissary',
154 'https://s3.amazonaws.com/{}/charts'.format(s3_bucket)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
155
156 run(['helm', 'repo', 'update'])
157
158 with checker.check(name="Check Helm Chart"):
159 yaml_str = run_txtcapture(['helm', 'show', 'chart', '--version', chart_ver, 'emissary/emissary-ingress'])
160
161 versions = [
162 line[len('appVersion:'):].strip() for line in yaml_str.split("\n") if line.startswith('appVersion:')
163 ]
164 assert_eq(len(versions), 1)
165 check.result = versions[0]
166 check_tag = ga_ver
167 if release_channel != '':
168 check_tag = f"{check_tag}-{release_channel}"
169 assert_eq(check.result, check_tag)
170
171 # The existence of a GitHub release implies the existence of its tag, and we check to
172 # make sure that the tag matches what we expect. Therefore we don't do a separate check
173 # for the tag. (It's true that you can delete the tag after the release; we're just not
174 # going to worry about that.)
175 with checker.check(name='ambassador.git GitHub release for chart (implies GitHub tag, too)') as check:
176 tag = run_txtcapture([
177 "gh", "release", "view",
178 "--json=tagName",
179 "--jq=.tagName",
180 "--repo=emissary-ingress/emissary",
181 f"chart/v{chart_ver}"])
182 assert_eq(tag.strip(), f"chart/v{chart_ver}")
183
184 # See above re tags.
185 with checker.check(name='ambassador.git GitHub release for code (implies GitHub tag, too)') as check:
186 tag = run_txtcapture([
187 "gh", "release", "view",
188 "--json=tagName",
189 "--jq=.tagName",
190 "--repo=emissary-ingress/emissary",
191 f"v{ga_ver}"])
192 assert_eq(tag.strip(), f"v{ga_ver}")
193
194 if not checker.ok:
195 return 1
196 return 0
View as plain text