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
|