Bug 1492664 - add {artifact-reference: ..}; r=tomprince

This provides an easy way to encode an artifact URL in static data such as
taskcluster/ci/nightly-l10n/kind.yml, without knowing in advance the format of
the URL.

--HG--
extra : rebase_source : 5b5229d96aad2916b1b3c8e72045c1d461fc1c02
extra : source : 9c35dd209c6b407bc3a45ce7b4c27272ef1bb486
This commit is contained in:
Dustin J. Mitchell 2018-09-25 20:26:55 +00:00
parent 1516eb0435
commit 536dc55522
23 changed files with 133 additions and 117 deletions

View File

@ -70,6 +70,6 @@ label. At this phase, the edges in the task graph diverge from the
``task.dependencies`` attributes, as the latter may contain dependencies
outside of the taskgraph (for replacement tasks).
As a side-effect, this phase also expands all ``{"task-reference": ".."}``
objects within the task definitions.
As a side-effect, this phase also expands all ``{"task-reference": ".."}`` and
``{"artifact-reference": ".."}`` objects within the task definitions.

View File

@ -176,6 +176,11 @@ using simple parameterized values, as follows:
Multiple labels may be substituted in a single string, and ``<<>`` can be
used to escape a literal ``<``.
``{"artifact-reference": "..<dep-name/artifact/name>.."}``
Similar to a ``task-reference``, but this substitutes a URL to the queue's
``getLatestArtifact`` API method (for which a GET will redirect to the
artifact itself).
.. _taskgraph-graph-config:
Graph Configuration

View File

@ -202,9 +202,10 @@ this common functionality. They expect a "signing description", and produce a
task definition. The schema for a signing description is defined at the top of
``signing.py``, with copious comments.
In particular you define a set of upstream artifact urls (that point at the dependent
task) and can optionally provide a dependent name (defaults to build) for use in
task-reference. You also need to provide the signing formats to use.
In particular you define a set of upstream artifact urls (that point at the
dependent task) and can optionally provide a dependent name (defaults to build)
for use in ``task-reference``/``artifact-reference``. You also need to provide
the signing formats to use.
More Detail
-----------

View File

@ -11,10 +11,11 @@ locally, so they should be limited to changes that do not modify the meaning of
the graph.
"""
# Note that the translation of `{'task-reference': '..'}` is handled in the
# optimization phase (since optimization involves dealing with taskIds
# directly). Similarly, `{'relative-datestamp': '..'}` is handled at the last
# possible moment during task creation.
# Note that the translation of `{'task-reference': '..'}` and
# `artifact-reference` are handled in the optimization phase (since
# optimization involves dealing with taskIds directly). Similarly,
# `{'relative-datestamp': '..'}` is handled at the last possible moment during
# task creation.
from __future__ import absolute_import, print_function, unicode_literals

View File

@ -6,6 +6,8 @@ from __future__ import absolute_import, print_function, unicode_literals
import unittest
import datetime
import mock
import os
from mozunit import main
from taskgraph.util.parameterization import (
@ -88,5 +90,43 @@ class TestTaskRefs(unittest.TestCase):
)
class TestArtifactRefs(unittest.TestCase):
def do(self, input, output):
taskid_for_edge_name = {'edge%d' % n: 'tid%d' % n for n in range(1, 4)}
with mock.patch.dict(os.environ, {'TASKCLUSTER_ROOT_URL': 'https://tc-tests.localhost'}):
self.assertEqual(resolve_task_references('subject', input, taskid_for_edge_name),
output)
def test_in_list(self):
"resolve_task_references resolves artifact references in a list"
self.do(
{'in-a-list': [
'stuff', {'artifact-reference': '<edge1/foo/bar>'}]},
{'in-a-list': [
'stuff', 'https://tc-tests.localhost/api/queue/v1/task/tid1/artifacts/foo/bar']})
def test_in_dict(self):
"resolve_task_references resolves artifact references in a dict"
self.do(
{'in-a-dict':
{'stuff': {'artifact-reference': '<edge2/bar/foo>'}}},
{'in-a-dict':
{'stuff': 'https://tc-tests.localhost/api/queue/v1/task/tid2/artifacts/bar/foo'}})
def test_in_string(self):
"resolve_task_references resolves artifact references embedded in a string"
self.do(
{'stuff': {'artifact-reference': '<edge1/filename> and <edge2/bar/foo>'}},
{'stuff': 'https://tc-tests.localhost/api/queue/v1/task/tid1/artifacts/filename and '
'https://tc-tests.localhost/api/queue/v1/task/tid2/artifacts/bar/foo'})
def test_invalid(self):
"resolve_task_references ignores badly-formatted artifact references"
for inv in ['<edge1>', 'edge1/foo>', '<edge1>/foo', '<edge1>foo']:
resolved = resolve_task_references('subject', {'artifact-reference': inv}, {})
self.assertEqual(resolved, inv)
if __name__ == '__main__':
main()

View File

@ -17,18 +17,13 @@ from taskgraph.util.scriptworker import (
get_balrog_server_scope, get_worker_type_for_scope
)
from taskgraph.transforms.task import task_description_schema
from voluptuous import Any, Required, Optional
from voluptuous import Optional
# Voluptuous uses marker objects as dictionary *keys*, but they are not
# comparable, so we cast all of the keys back to regular strings
task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
# shortcut for a string where task references are allowed
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
balrog_description_schema = schema.extend({
# unique label to describe this balrog task, defaults to balrog-{dep.label}
Optional('label'): basestring,

View File

@ -7,7 +7,7 @@ Transform the beetmover task into an actual task description.
from __future__ import absolute_import, print_function, unicode_literals
from voluptuous import Any, Optional, Required
from voluptuous import Optional, Required
from taskgraph.loader.single_dep import schema
from taskgraph.transforms.base import TransformSequence
@ -119,11 +119,6 @@ task_description_schema = {str(k): v for k, v in task_description_schema.schema.
transforms = TransformSequence()
# shortcut for a string where task references are allowed
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
beetmover_description_schema = schema.extend({
# depname is used in taskref's to identify the taskID of the unsigned things
Required('depname', default='build'): basestring,

View File

@ -18,17 +18,13 @@ from taskgraph.util.scriptworker import (generate_beetmover_artifact_map,
get_beetmover_bucket_scope,
get_worker_type_for_scope,
should_use_artifact_map)
from voluptuous import Any, Optional, Required
from voluptuous import Optional, Required
from taskgraph.transforms.task import task_description_schema
# Voluptuous uses marker objects as dictionary *keys*, but they are not
# comparable, so we cast all of the keys back to regular strings
task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
beetmover_checksums_description_schema = schema.extend({
Required('depname', default='build'): basestring,
Required('attributes'): {basestring: object},

View File

@ -12,16 +12,12 @@ from taskgraph.transforms.base import TransformSequence
from taskgraph.transforms.beetmover import craft_release_properties
from taskgraph.util.attributes import copy_attributes_from_dependent_job
from taskgraph.transforms.task import task_description_schema
from voluptuous import Any, Required, Optional
from voluptuous import Required, Optional
# Voluptuous uses marker objects as dictionary *keys*, but they are not
# comparable, so we cast all of the keys back to regular strings
task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
beetmover_checksums_description_schema = schema.extend({
Required('depname', default='build'): basestring,
Optional('label'): basestring,

View File

@ -15,16 +15,12 @@ from taskgraph.util.scriptworker import (get_beetmover_bucket_scope,
get_beetmover_action_scope,
get_worker_type_for_scope)
from taskgraph.transforms.task import task_description_schema
from voluptuous import Any, Required, Optional
from voluptuous import Required, Optional
# Voluptuous uses marker objects as dictionary *keys*, but they are not
# comparable, so we cast all of the keys back to regular strings
task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
beetmover_checksums_description_schema = schema.extend({
Required('depname', default='build'): basestring,
Optional('label'): basestring,

View File

@ -8,14 +8,17 @@ Transform the beetmover-push-to-release task into a task description.
from __future__ import absolute_import, print_function, unicode_literals
from taskgraph.transforms.base import TransformSequence
from taskgraph.util.schema import Schema
from taskgraph.util.schema import (
Schema,
taskref_or_string,
)
from taskgraph.util.scriptworker import (
get_beetmover_bucket_scope, add_scope_prefix,
get_worker_type_for_scope,
)
from taskgraph.transforms.job import job_description_schema
from taskgraph.transforms.task import task_description_schema
from voluptuous import Any, Required, Optional
from voluptuous import Required, Optional
# Voluptuous uses marker objects as dictionary *keys*, but they are not
# comparable, so we cast all of the keys back to regular strings
@ -23,10 +26,6 @@ task_description_schema = {str(k): v for k, v in task_description_schema.schema.
job_description_schema = {str(k): v for k, v in job_description_schema.schema.iteritems()}
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
beetmover_push_to_release_description_schema = Schema({
Required('name'): basestring,
Required('product'): basestring,

View File

@ -19,7 +19,7 @@ from taskgraph.util.scriptworker import (get_beetmover_bucket_scope,
get_worker_type_for_scope)
from taskgraph.util.taskcluster import get_artifact_prefix
from taskgraph.transforms.task import task_description_schema
from voluptuous import Any, Required, Optional
from voluptuous import Required, Optional
import logging
import re
@ -139,11 +139,6 @@ UPSTREAM_ARTIFACT_SIGNED_MAR_PATHS = [
# comparable, so we cast all of the keys back to regular strings
task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
# shortcut for a string where task references are allowed
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
beetmover_description_schema = schema.extend({
# depname is used in taskref's to identify the taskID of the unsigned things
Required('depname', default='build'): basestring,

View File

@ -39,11 +39,6 @@ logger = logging.getLogger(__name__)
# comparable, so we cast all of the keys back to regular strings
task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
# shortcut for a string where task references are allowed
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
beetmover_description_schema = schema.extend({
# depname is used in taskref's to identify the taskID of the unsigned things
Required('depname', default='build'): basestring,

View File

@ -18,16 +18,12 @@ from taskgraph.util.scriptworker import (generate_beetmover_artifact_map,
get_worker_type_for_scope,
should_use_artifact_map)
from taskgraph.transforms.task import task_description_schema
from voluptuous import Any, Required, Optional
from voluptuous import Required, Optional
# Voluptuous uses marker objects as dictionary *keys*, but they are not
# comparable, so we cast all of the keys back to regular strings
task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
beetmover_checksums_description_schema = schema.extend({
Required('depname', default='build'): basestring,
Optional('label'): basestring,

View File

@ -16,16 +16,12 @@ from taskgraph.util.scriptworker import (
add_scope_prefix,
)
from taskgraph.transforms.task import task_description_schema
from voluptuous import Any, Required, Optional
from voluptuous import Required, Optional
# Voluptuous uses marker objects as dictionary *keys*, but they are not
# comparable, so we cast all of the keys back to regular strings
task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
checksums_signing_description_schema = schema.extend({
Required('depname', default='beetmover'): basestring,
Optional('label'): basestring,

View File

@ -18,6 +18,7 @@ from taskgraph.transforms.base import (
from taskgraph.util.schema import (
optionally_keyed_by,
resolve_keyed_by,
taskref_or_string,
)
from taskgraph.util.attributes import copy_attributes_from_dependent_job
from taskgraph.util.taskcluster import get_artifact_prefix
@ -35,11 +36,6 @@ def _by_platform(arg):
return optionally_keyed_by('build-platform', arg)
# shortcut for a string where task references are allowed
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
# Voluptuous uses marker objects as dictionary *keys*, but they are not
# comparable, so we cast all of the keys back to regular strings
job_description_schema = {str(k): v for k, v in job_description_schema.schema.iteritems()}

View File

@ -20,18 +20,13 @@ from taskgraph.util.taskcluster import get_artifact_prefix
from taskgraph.util.platforms import archive_format, executable_extension, architecture
from taskgraph.util.workertypes import worker_type_implementation
from taskgraph.transforms.job import job_description_schema
from voluptuous import Any, Required, Optional
from voluptuous import Required, Optional
# Voluptuous uses marker objects as dictionary *keys*, but they are not
# comparable, so we cast all of the keys back to regular strings
job_description_schema = {str(k): v for k, v in job_description_schema.schema.iteritems()}
# shortcut for a string where task references are allowed
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
packaging_description_schema = schema.extend({
# depname is used in taskref's to identify the taskID of the signed things
Required('depname', default='build'): basestring,

View File

@ -22,7 +22,7 @@ from taskgraph.util.platforms import archive_format, executable_extension
from taskgraph.util.workertypes import worker_type_implementation
from taskgraph.transforms.task import task_description_schema
from taskgraph.transforms.repackage import PACKAGE_FORMATS
from voluptuous import Any, Required, Optional
from voluptuous import Required, Optional
# Voluptuous uses marker objects as dictionary *keys*, but they are not
# comparable, so we cast all of the keys back to regular strings
@ -33,11 +33,6 @@ def _by_platform(arg):
return optionally_keyed_by('build-platform', arg)
# shortcut for a string where task references are allowed
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
packaging_description_schema = schema.extend({
# depname is used in taskref's to identify the taskID of the signed things
Required('depname', default='build'): basestring,

View File

@ -10,13 +10,14 @@ from __future__ import absolute_import, print_function, unicode_literals
from taskgraph.loader.single_dep import schema
from taskgraph.transforms.base import TransformSequence
from taskgraph.util.attributes import copy_attributes_from_dependent_job
from taskgraph.util.schema import taskref_or_string
from taskgraph.util.scriptworker import (
add_scope_prefix,
get_signing_cert_scope_per_platform,
get_worker_type_for_scope,
)
from taskgraph.transforms.task import task_description_schema
from voluptuous import Any, Required, Optional
from voluptuous import Required, Optional
# Voluptuous uses marker objects as dictionary *keys*, but they are not
@ -25,11 +26,6 @@ task_description_schema = {str(k): v for k, v in task_description_schema.schema.
transforms = TransformSequence()
# shortcut for a string where task references are allowed
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
signing_description_schema = schema.extend({
# Artifacts from dep task to sign - Sync with taskgraph/transforms/task.py
# because this is passed directly into the signingscript worker

View File

@ -16,16 +16,12 @@ from taskgraph.util.scriptworker import (
add_scope_prefix,
)
from taskgraph.transforms.task import task_description_schema
from voluptuous import Any, Required, Optional
from voluptuous import Required, Optional
# Voluptuous uses marker objects as dictionary *keys*, but they are not
# comparable, so we cast all of the keys back to regular strings
task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
checksums_signing_description_schema = schema.extend({
Required('depname', default='beetmover'): basestring,
Optional('label'): basestring,

View File

@ -28,6 +28,7 @@ from taskgraph.util.schema import (
optionally_keyed_by,
resolve_keyed_by,
OptimizationSchema,
taskref_or_string,
)
from taskgraph.util.scriptworker import (
BALROG_ACTIONS,
@ -48,12 +49,6 @@ def _run_task_suffix():
return hash_path(RUN_TASK)[0:20]
# shortcut for a string where task references are allowed
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring},
)
# A task description is a general description of a TaskCluster task
task_description_schema = Schema({
# the label for this task

View File

@ -4,24 +4,27 @@
from __future__ import absolute_import, print_function, unicode_literals
import os
import re
import taskcluster_urls
from taskgraph.util.time import json_time_from_now
from taskgraph.util.taskcluster import get_root_url
TASK_REFERENCE_PATTERN = re.compile('<([^>]+)>')
ARTIFACT_REFERENCE_PATTERN = re.compile('<([^/]+)/([^>]+)>')
def _recurse(val, param_name, param_fn):
param_keys = [param_name]
def _recurse(val, param_fns):
def recurse(val):
if isinstance(val, list):
return [recurse(v) for v in val]
elif isinstance(val, dict):
if val.keys() == param_keys:
return param_fn(val[param_name])
else:
return {k: recurse(v) for k, v in val.iteritems()}
if len(val) == 1:
for param_key, param_fn in param_fns.items():
if val.keys() == [param_key]:
return param_fn(val[param_key])
return {k: recurse(v) for k, v in val.iteritems()}
else:
return val
return recurse(val)
@ -29,20 +32,47 @@ def _recurse(val, param_name, param_fn):
def resolve_timestamps(now, task_def):
"""Resolve all instances of `{'relative-datestamp': '..'}` in the given task definition"""
return _recurse(task_def, 'relative-datestamp', lambda v: json_time_from_now(v, now))
return _recurse(task_def, {
'relative-datestamp': lambda v: json_time_from_now(v, now),
})
def resolve_task_references(label, task_def, dependencies):
"""Resolve all instances of `{'task-reference': '..<..>..'}` in the given task
definition, using the given dependencies"""
def repl(match):
key = match.group(1)
try:
return dependencies[key]
except KeyError:
# handle escaping '<'
if key == '<':
return key
raise KeyError("task '{}' has no dependency named '{}'".format(label, key))
"""Resolve all instances of
{'task-reference': '..<..>..'}
and
{'artifact-reference`: '..<dependency/artifact/path>..'}
in the given task definition, using the given dependencies"""
return _recurse(task_def, 'task-reference', lambda v: TASK_REFERENCE_PATTERN.sub(repl, v))
def task_reference(val):
def repl(match):
key = match.group(1)
try:
return dependencies[key]
except KeyError:
# handle escaping '<'
if key == '<':
return key
raise KeyError("task '{}' has no dependency named '{}'".format(label, key))
return TASK_REFERENCE_PATTERN.sub(repl, val)
def artifact_reference(val):
def repl(match):
dependency, artifact_name = match.group(1, 2)
try:
dependency_task_id = dependencies[dependency]
except KeyError:
raise KeyError("task '{}' has no dependency named '{}'".format(label, dependency))
return taskcluster_urls.api(
get_root_url(), 'queue', 'v1',
'task/{}/artifacts/{}'.format(dependency_task_id, artifact_name))
return ARTIFACT_REFERENCE_PATTERN.sub(repl, val)
return _recurse(task_def, {
'task-reference': task_reference,
'artifact-reference': artifact_reference,
})

View File

@ -231,3 +231,10 @@ OptimizationSchema = voluptuous.Any(
# skip if SETA or skip-unless-schedules says to
{'skip-unless-schedules-or-seta': list(schedules.ALL_COMPONENTS)},
)
# shortcut for a string where task references are allowed
taskref_or_string = voluptuous.Any(
basestring,
{voluptuous.Required('task-reference'): basestring},
{voluptuous.Required('artifact-reference'): basestring},
)