...

Text file src/github.com/emissary-ingress/emissary/v3/releng/lib/check_artifacts.py

Documentation: github.com/emissary-ingress/emissary/v3/releng/lib

     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