summaryrefslogtreecommitdiffstats
path: root/third_party/python/taskcluster_taskgraph/taskgraph/transforms/task_context.py
blob: 5c7ed6af805d42f34bce17b8e8c967bf46887355 (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
from textwrap import dedent

from voluptuous import ALLOW_EXTRA, Any, Optional, Required

from taskgraph.transforms.base import TransformSequence
from taskgraph.util.schema import Schema
from taskgraph.util.templates import deep_get, substitute
from taskgraph.util.yaml import load_yaml

SCHEMA = Schema(
    {
        Required(
            "task-context",
            description=dedent(
                """
            `task-context` can be used to substitute values into any field in a
            task with data that is not known until `taskgraph` runs.

            This data can be provided via `from-parameters` or `from-file`,
            which can pull in values from parameters and a defined yml file
            respectively.

            Data may also be provided directly in the `from-object` section of
            `task-context`. This can be useful in `kinds` that define most of
            their contents in `task-defaults`, but have some values that may
            differ for various concrete `tasks` in the `kind`.

            If the same key is found in multiple places the order of precedence
            is as follows:
              - Parameters
              - `from-object` keys
              - File

            That is to say: parameters will always override anything else.

            """.lstrip(),
            ),
        ): {
            Optional(
                "from-parameters",
                description=dedent(
                    """
                Retrieve task context values from parameters. A single
                parameter may be provided or a list of parameters in
                priority order. The latter can be useful in implementing a
                "default" value if some other parameter is not provided.
                """.lstrip()
                ),
            ): {str: Any([str], str)},
            Optional(
                "from-file",
                description=dedent(
                    """
                Retrieve task context values from a yaml file. The provided
                file should usually only contain top level keys and values
                (eg: nested objects will not be interpolated - they will be
                substituted as text representations of the object).
                """.lstrip()
                ),
            ): str,
            Optional(
                "from-object",
                description="Key/value pairs to be used as task context",
            ): object,
            Required(
                "substitution-fields",
                description=dedent(
                    """
                A list of fields in the task to substitute the provided values
                into.
                """.lstrip()
                ),
            ): [str],
        },
    },
    extra=ALLOW_EXTRA,
)

transforms = TransformSequence()
transforms.add_validate(SCHEMA)


@transforms.add
def render_task(config, jobs):
    for job in jobs:
        sub_config = job.pop("task-context")
        params_context = {}
        for var, path in sub_config.pop("from-parameters", {}).items():
            if isinstance(path, str):
                params_context[var] = deep_get(config.params, path)
            else:
                for choice in path:
                    value = deep_get(config.params, choice)
                    if value is not None:
                        params_context[var] = value
                        break

        file_context = {}
        from_file = sub_config.pop("from-file", None)
        if from_file:
            file_context = load_yaml(from_file)

        fields = sub_config.pop("substitution-fields")

        subs = {}
        subs.update(file_context)
        # We've popped away the configuration; everything left in `sub_config` is
        # substitution key/value pairs.
        subs.update(sub_config.pop("from-object", {}))
        subs.update(params_context)

        # Now that we have our combined context, we can substitute.
        for field in fields:
            container, subfield = job, field
            while "." in subfield:
                f, subfield = subfield.split(".", 1)
                container = container[f]

            container[subfield] = substitute(container[subfield], **subs)

        yield job