summaryrefslogtreecommitdiff
path: root/buildscripts/evergreen_expansions2bash.py
blob: f89b245129fe734c99cc19c1f83593e679f07a6e (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
"""Convert Evergreen's expansions.yml to an eval-able shell script."""
import sys
import platform
from shlex import quote
from typing import Any


def _error(msg: str) -> None:
    print(f"___expansions_error={quote(msg)}")
    sys.exit(1)


try:
    import yaml
    import click
except ModuleNotFoundError:
    _error("ERROR: Failed to import a dependency. This is almost certainly because "
           "the task did not initialize the venv immediately after cloning the repository.")


def _load_defaults(defaults_file: str) -> dict:
    with open(defaults_file) as fh:
        defaults = yaml.safe_load(fh)
        if not isinstance(defaults, dict):
            _error("ERROR: expected to read a dictionary. expansions.defaults.yml"
                   "must be a dictionary. Check the indentation.")

        # expansions MUST be strings. Reject any that are not
        bad_expansions = set()
        for key, value in defaults.items():
            if not isinstance(value, str):
                bad_expansions.add(key)

        if bad_expansions:
            _error("ERROR: all default expansions must be strings. You can "
                   " fix this error by quoting the values in expansions.defaults.yml. "
                   "Integers, floating points, 'true', 'false', and 'null' "
                   "must be quoted. The following keys were interpreted as "
                   f"other types: {bad_expansions}")

        # These values show up if 1. Python's str is used to naively convert
        # a boolean to str, 2. A human manually entered one of those strings.
        # Either way, our shell scripts expect 'true' or 'false' (leading
        # lowercase), and we reject them as errors. This will probably save
        # someone a lot of time, but if this assumption proves wrong, start
        # a conversation in #server-testing.
        risky_boolean_keys = set()
        for key, value in defaults.items():
            if value in ("True", "False"):
                risky_boolean_keys.add(key)

        if risky_boolean_keys:
            _error("ERROR: Found keys which had 'True' or 'False' as values. "
                   "Shell scripts assume that booleans are represented as 'true'"
                   " or 'false' (leading lowercase). If you added a new boolean, "
                   "ensure that it's represented in lowercase. If not, please report this in "
                   f"#server-testing. Keys with bad values: {risky_boolean_keys}")

        return defaults


def _load_expansions(expansions_file) -> dict:
    with open(expansions_file) as fh:
        expansions = yaml.safe_load(fh)

        if not isinstance(expansions, dict):
            _error("ERROR: expected to read a dictionary. Has the output format "
                   "of expansions.write changed?")

        if not expansions:
            _error("ERROR: found 0 expansions. This is almost certainly wrong.")

        return expansions


@click.command()
@click.argument("expansions_file", type=str)
@click.argument("defaults_file", type=str)
def _main(expansions_file: str, defaults_file: str):
    try:
        defaults = _load_defaults(defaults_file)
        expansions = _load_expansions(expansions_file)

        # inject defaults into expansions
        for key, value in defaults.items():
            if key not in expansions:
                expansions[key] = value

        for key, value in expansions.items():
            print(f"{key}={quote(value)}; ", end="")

    except Exception as ex:  # pylint: disable=broad-except
        _error(ex)


if __name__ == "__main__":
    _main()  # pylint: disable=no-value-for-parameter