aboutsummaryrefslogtreecommitdiff
path: root/releng/environment.py
blob: 2b6554eded4e8ba10316815a5c14edec790b559d (plain)
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
from typing import Callable
import urllib.parse
import re
import functools
import subprocess
import dataclasses

S3_HOST = 's3.lix.systems'
S3_ENDPOINT = 'https://s3.lix.systems'

DEFAULT_STORE_URI_BITS = {
    'region': 'garage',
    'endpoint': 's3.lix.systems',
    'want-mass-query': 'true',
    'write-nar-listing': 'true',
    'ls-compression': 'zstd',
    'narinfo-compression': 'zstd',
    'compression': 'zstd',
    'parallel-compression': 'true',
}


@dataclasses.dataclass
class DockerTarget:
    registry_path: str
    """Registry path without the tag, e.g. ghcr.io/lix-project/lix"""

    tags: list[str]
    """List of tags this image should take. There must be at least one."""

    @staticmethod
    def resolve(item: str, version: str, major: str) -> str:
        """
        Applies templates:
        - version: the Lix version e.g. 2.90.0
        - major: the major Lix version e.g. 2.90
        """
        return item.format(version=version, major=major)

    def registry_name(self) -> str:
        [a, _, _] = self.registry_path.partition('/')
        return a


@dataclasses.dataclass
class RelengEnvironment:
    name: str
    colour: Callable[[str], str]

    cache_store_overlay: dict[str, str]
    cache_bucket: str
    releases_bucket: str
    docs_bucket: str
    git_repo: str
    git_repo_is_gerrit: bool

    docker_targets: list[DockerTarget]

    def cache_store_uri(self):
        qs = DEFAULT_STORE_URI_BITS.copy()
        qs.update(self.cache_store_overlay)
        return self.cache_bucket + "?" + urllib.parse.urlencode(qs)


SGR = '\x1b['
RED = '31;1m'
GREEN = '32;1m'
RESET = '0m'


def sgr(colour: str, text: str) -> str:
    return f'{SGR}{colour}{text}{SGR}{RESET}'


STAGING = RelengEnvironment(
    name='staging',
    colour=functools.partial(sgr, GREEN),
    docs_bucket='s3://staging-docs',
    cache_bucket='s3://staging-cache',
    cache_store_overlay={'secret-key': 'staging.key'},
    releases_bucket='s3://staging-releases',
    git_repo='ssh://git@git.lix.systems/lix-project/lix-releng-staging',
    git_repo_is_gerrit=False,
    docker_targets=[
        # latest will be auto tagged if appropriate
        DockerTarget('git.lix.systems/lix-project/lix-releng-staging',
                     tags=['{version}', '{major}']),
        DockerTarget('ghcr.io/lix-project/lix-releng-staging',
                     tags=['{version}', '{major}']),
    ],
)

GERRIT_REMOTE_RE = re.compile(r'^ssh://(\w+@)?gerrit.lix.systems:2022/lix$')


def guess_gerrit_remote():
    """
    Deals with people having unknown gerrit username.
    """
    out = [
        x.split()[1] for x in subprocess.check_output(
            ['git', 'remote', '-v']).decode().splitlines()
    ]
    return next(x for x in out if GERRIT_REMOTE_RE.match(x))


PROD = RelengEnvironment(
    name='production',
    colour=functools.partial(sgr, RED),
    docs_bucket='s3://docs',
    cache_bucket='s3://cache',
    # FIXME: we should decrypt this with age into a tempdir in the future, but
    # the issue is how to deal with the recipients file. For now, we should
    # just delete it after doing a release.
    cache_store_overlay={'secret-key': 'prod.key'},
    releases_bucket='s3://releases',
    git_repo=guess_gerrit_remote(),
    git_repo_is_gerrit=True,
    docker_targets=[
        # latest will be auto tagged if appropriate
        DockerTarget('git.lix.systems/lix-project/lix',
                     tags=['{version}', '{major}']),
        DockerTarget('ghcr.io/lix-project/lix', tags=['{version}', '{major}']),
    ],
)

ENVIRONMENTS = {
    'staging': STAGING,
    'production': PROD,
}


@dataclasses.dataclass
class S3Credentials:
    name: str
    id: str
    secret_key: str