summaryrefslogtreecommitdiffstats
path: root/third_party/python/taskcluster_taskgraph/taskgraph/transforms/task_context.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/python/taskcluster_taskgraph/taskgraph/transforms/task_context.py')
-rw-r--r--third_party/python/taskcluster_taskgraph/taskgraph/transforms/task_context.py121
1 files changed, 121 insertions, 0 deletions
diff --git a/third_party/python/taskcluster_taskgraph/taskgraph/transforms/task_context.py b/third_party/python/taskcluster_taskgraph/taskgraph/transforms/task_context.py
new file mode 100644
index 0000000000..5c7ed6af80
--- /dev/null
+++ b/third_party/python/taskcluster_taskgraph/taskgraph/transforms/task_context.py
@@ -0,0 +1,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