--- a/taskcluster/taskgraph/test/test_util_schema.py
+++ b/taskcluster/taskgraph/test/test_util_schema.py
@@ -4,18 +4,19 @@
from __future__ import absolute_import, print_function, unicode_literals
import unittest
from mozunit import main
from taskgraph.util.schema import (
validate_schema,
resolve_keyed_by,
+ check_schema,
)
-from voluptuous import Schema
+from voluptuous import Schema, Optional, Any
schema = Schema({
'x': int,
'y': basestring,
})
class TestValidateSchema(unittest.TestCase):
@@ -68,17 +69,18 @@ class TestResolveKeyedBy(unittest.TestCa
resolve_keyed_by(
{'f': 'shoes', 'x': {'y': {'by-f': {'shoes': 'feet', 'gloves': 'hands'}}}},
'x.y', 'n'),
{'f': 'shoes', 'x': {'y': 'feet'}})
def test_match_regexp(self):
self.assertEqual(
resolve_keyed_by(
- {'f': 'shoes', 'x': {'by-f': {'s?[hH]oes?': 'feet', 'gloves': 'hands'}}},
+ {'f': 'shoes', 'x': {
+ 'by-f': {'s?[hH]oes?': 'feet', 'gloves': 'hands'}}},
'x', 'n'),
{'f': 'shoes', 'x': 'feet'})
def test_match_partial_regexp(self):
self.assertEqual(
resolve_keyed_by(
{'f': 'shoes', 'x': {'by-f': {'sh': 'feet', 'default': 'hands'}}},
'x', 'n'),
@@ -105,10 +107,37 @@ class TestResolveKeyedBy(unittest.TestCa
{'f': 'shoes', 'x': {'by-f': {'hat': 'head'}}}, 'x', 'n')
def test_multiple_matches(self):
self.assertRaises(
Exception, resolve_keyed_by,
{'f': 'hats', 'x': {'by-f': {'hat.*': 'head', 'ha.*': 'hair'}}}, 'x', 'n')
+class TestCheckSchema(unittest.TestCase):
+
+ def test_intercaps_key(self):
+ self.assertRaises(RuntimeError, lambda:
+ check_schema(Schema({'interCaps': int})))
+
+ def test_underscore_key(self):
+ self.assertRaises(RuntimeError, lambda:
+ check_schema(Schema({'under_scores': int})))
+
+ def test_optional_bad_key(self):
+ self.assertRaises(RuntimeError, lambda:
+ check_schema(Schema({Optional('under_scores'): int})))
+
+ def test_nested_bad_key(self):
+ self.assertRaises(RuntimeError, lambda:
+ check_schema(Schema({'x': {'under_scores': int}})))
+
+ def test_list_bad_key(self):
+ self.assertRaises(RuntimeError, lambda:
+ check_schema(Schema([{'under_scores': int}])))
+
+ def test_any_bad_key(self):
+ self.assertRaises(RuntimeError, lambda:
+ check_schema(Schema(Any({'under_scores': int}, int))))
+
+
if __name__ == '__main__':
main()
--- a/taskcluster/taskgraph/transforms/balrog.py
+++ b/taskcluster/taskgraph/transforms/balrog.py
@@ -3,19 +3,19 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""
Transform the beetmover task into an actual task description.
"""
from __future__ import absolute_import, print_function, unicode_literals
from taskgraph.transforms.base import TransformSequence
-from taskgraph.util.schema import validate_schema
+from taskgraph.util.schema import validate_schema, Schema
from taskgraph.transforms.task import task_description_schema
-from voluptuous import Schema, Any, Required, Optional
+from voluptuous import Any, 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()}
transforms = TransformSequence()
@@ -78,18 +78,18 @@ def make_task_description(config, jobs):
if dep_job.attributes.get('locale'):
treeherder['symbol'] = 'tc-Up({})'.format(dep_job.attributes.get('locale'))
attributes['locale'] = dep_job.attributes.get('locale')
label = job.get('label', "balrog-{}".format(dep_job.label))
upstream_artifacts = [{
- "taskId": {"task-reference": "<beetmover>"},
- "taskType": "beetmover",
+ "task-id": {"task-reference": "<beetmover>"},
+ "task-type": "beetmover",
"paths": [
"public/manifest.json"
],
}]
task = {
'label': label,
'description': "{} Balrog".format(
--- a/taskcluster/taskgraph/transforms/beetmover.py
+++ b/taskcluster/taskgraph/transforms/beetmover.py
@@ -3,19 +3,19 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""
Transform the beetmover task into an actual task description.
"""
from __future__ import absolute_import, print_function, unicode_literals
from taskgraph.transforms.base import TransformSequence
-from taskgraph.util.schema import validate_schema
+from taskgraph.util.schema import validate_schema, Schema
from taskgraph.transforms.task import task_description_schema
-from voluptuous import Schema, Any, Required, Optional
+from voluptuous import Any, Required, Optional
_DESKTOP_UPSTREAM_ARTIFACTS_UNSIGNED_EN_US = [
"balrog_props.json",
"target.common.tests.zip",
"target.cppunittest.tests.zip",
"target.crashreporter-symbols.zip",
"target.json",
@@ -228,27 +228,27 @@ def generate_upstream_artifacts(taskid_t
mapping = UPSTREAM_ARTIFACT_SIGNED_PATHS
artifact_prefix = 'public/build'
if locale:
artifact_prefix = 'public/build/{}'.format(locale)
platform = "{}-l10n".format(platform)
upstream_artifacts = [{
- "taskId": {"task-reference": taskid_to_beetmove},
- "taskType": task_type,
+ "task-id": {"task-reference": taskid_to_beetmove},
+ "task-type": task_type,
"paths": ["{}/{}".format(artifact_prefix, p) for p in mapping[platform]],
"locale": locale or "en-US",
}]
if not locale and "android" in platform:
# edge case to support 'multi' locale paths
multi_platform = "{}-multi".format(platform)
upstream_artifacts.append({
- "taskId": {"task-reference": taskid_to_beetmove},
- "taskType": task_type,
+ "task-id": {"task-reference": taskid_to_beetmove},
+ "task-type": task_type,
"paths": ["{}/{}".format(artifact_prefix, p) for p in mapping[multi_platform]],
"locale": "multi",
})
return upstream_artifacts
def generate_signing_upstream_artifacts(taskid_to_beetmove, taskid_of_manifest, platform,
@@ -256,18 +256,18 @@ def generate_signing_upstream_artifacts(
upstream_artifacts = generate_upstream_artifacts(taskid_to_beetmove, platform, locale,
signing=True)
if locale:
artifact_prefix = 'public/build/{}'.format(locale)
else:
artifact_prefix = 'public/build'
manifest_path = "{}/balrog_props.json".format(artifact_prefix)
upstream_artifacts.append({
- "taskId": {"task-reference": taskid_of_manifest},
- "taskType": "build",
+ "task-id": {"task-reference": taskid_of_manifest},
+ "task-type": "build",
"paths": [manifest_path],
"locale": locale or "en-US",
})
return upstream_artifacts
def generate_build_upstream_artifacts(taskid_to_beetmove, platform, locale=None):
@@ -308,17 +308,17 @@ def make_task_worker(config, jobs):
else:
taskid_to_beetmove = "<" + str(build_kind) + ">"
update_manifest = False
upstream_artifacts = generate_build_upstream_artifacts(
taskid_to_beetmove, platform, locale
)
worker = {'implementation': 'beetmover',
- 'update_manifest': update_manifest,
+ 'update-manifest': update_manifest,
'upstream-artifacts': upstream_artifacts}
if locale:
worker["locale"] = locale
job["worker"] = worker
yield job
--- a/taskcluster/taskgraph/transforms/build_signing.py
+++ b/taskcluster/taskgraph/transforms/build_signing.py
@@ -35,18 +35,18 @@ def make_signing_description(config, job
'artifacts': ['public/build/update/target.complete.mar'],
'format': 'mar',
}
]
upstream_artifacts = []
for spec in job_specs:
fmt = spec["format"]
upstream_artifacts.append({
- "taskId": {"task-reference": "<build>"},
- "taskType": "build",
+ "task-id": {"task-reference": "<build>"},
+ "task-type": "build",
"paths": spec["artifacts"],
"formats": [fmt]
})
job['upstream-artifacts'] = upstream_artifacts
label = dep_job.label.replace("build-", "signing-")
job['label'] = label
--- a/taskcluster/taskgraph/transforms/job/__init__.py
+++ b/taskcluster/taskgraph/transforms/job/__init__.py
@@ -14,24 +14,24 @@ from __future__ import absolute_import,
import copy
import logging
import os
from taskgraph.transforms.base import TransformSequence
from taskgraph.util.schema import (
validate_schema,
resolve_keyed_by,
+ Schema,
)
from taskgraph.transforms.task import task_description_schema
from voluptuous import (
Any,
Extra,
Optional,
Required,
- Schema,
)
logger = logging.getLogger(__name__)
# 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()}
--- a/taskcluster/taskgraph/transforms/job/hazard.py
+++ b/taskcluster/taskgraph/transforms/job/hazard.py
@@ -2,17 +2,18 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""
Support for running hazard jobs via dedicated scripts
"""
from __future__ import absolute_import, print_function, unicode_literals
-from voluptuous import Schema, Required, Optional, Any
+from taskgraph.util.schema import Schema
+from voluptuous import Required, Optional, Any
from taskgraph.transforms.job import run_job_using
from taskgraph.transforms.job.common import (
docker_worker_add_workspace_cache,
docker_worker_setup_secrets,
docker_worker_add_public_artifacts,
docker_worker_support_vcs_checkout,
)
--- a/taskcluster/taskgraph/transforms/job/mach.py
+++ b/taskcluster/taskgraph/transforms/job/mach.py
@@ -4,17 +4,18 @@
"""
Support for running mach tasks (via run-task)
"""
from __future__ import absolute_import, print_function, unicode_literals
from taskgraph.transforms.job import run_job_using
from taskgraph.transforms.job.run_task import docker_worker_run_task
-from voluptuous import Schema, Required
+from taskgraph.util.schema import Schema
+from voluptuous import Required
mach_schema = Schema({
Required('using'): 'mach',
# The mach command (omitting `./mach`) to run
Required('mach'): basestring,
})
--- a/taskcluster/taskgraph/transforms/job/mozharness.py
+++ b/taskcluster/taskgraph/transforms/job/mozharness.py
@@ -5,17 +5,18 @@
Support for running jobs via mozharness. Ideally, most stuff gets run this
way, and certainly anything using mozharness should use this approach.
"""
from __future__ import absolute_import, print_function, unicode_literals
-from voluptuous import Schema, Required, Optional, Any
+from taskgraph.util.schema import Schema
+from voluptuous import Required, Optional, Any
from taskgraph.transforms.job import run_job_using
from taskgraph.transforms.job.common import (
docker_worker_add_workspace_cache,
docker_worker_add_gecko_vcs_env_vars,
docker_worker_setup_secrets,
docker_worker_add_public_artifacts,
docker_worker_support_vcs_checkout,
--- a/taskcluster/taskgraph/transforms/job/run_task.py
+++ b/taskcluster/taskgraph/transforms/job/run_task.py
@@ -5,20 +5,21 @@
Support for running jobs that are invoked via the `run-task` script.
"""
from __future__ import absolute_import, print_function, unicode_literals
import copy
from taskgraph.transforms.job import run_job_using
+from taskgraph.util.schema import Schema
from taskgraph.transforms.job.common import (
docker_worker_support_vcs_checkout,
)
-from voluptuous import Schema, Required, Any
+from voluptuous import Required, Any
run_task_schema = Schema({
Required('using'): 'run-task',
# if true, add a cache at ~worker/.cache, which is where things like pip
# tend to hide their caches. This cache is never added for level-1 jobs.
Required('cache-dotcache', default=False): bool,
--- a/taskcluster/taskgraph/transforms/job/spidermonkey.py
+++ b/taskcluster/taskgraph/transforms/job/spidermonkey.py
@@ -2,17 +2,18 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""
Support for running spidermonkey jobs via dedicated scripts
"""
from __future__ import absolute_import, print_function, unicode_literals
-from voluptuous import Schema, Required, Optional, Any
+from taskgraph.util.schema import Schema
+from voluptuous import Required, Optional, Any
from taskgraph.transforms.job import run_job_using
from taskgraph.transforms.job.common import (
docker_worker_add_public_artifacts,
docker_worker_support_vcs_checkout,
)
sm_run_schema = Schema({
--- a/taskcluster/taskgraph/transforms/job/toolchain.py
+++ b/taskcluster/taskgraph/transforms/job/toolchain.py
@@ -2,17 +2,18 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""
Support for running toolchain-building jobs via dedicated scripts
"""
from __future__ import absolute_import, print_function, unicode_literals
-from voluptuous import Schema, Required, Any
+from taskgraph.util.schema import Schema
+from voluptuous import Required, Any
from taskgraph.transforms.job import run_job_using
from taskgraph.transforms.job.common import (
docker_worker_add_tc_vcs_cache,
docker_worker_add_gecko_vcs_env_vars
)
toolchain_run_schema = Schema({
--- a/taskcluster/taskgraph/transforms/l10n.py
+++ b/taskcluster/taskgraph/transforms/l10n.py
@@ -12,24 +12,24 @@ import copy
from mozbuild.chunkify import chunkify
from taskgraph.transforms.base import (
TransformSequence,
)
from taskgraph.util.schema import (
validate_schema,
optionally_keyed_by,
resolve_keyed_by,
+ Schema,
)
from taskgraph.util.treeherder import split_symbol, join_symbol
from voluptuous import (
Any,
Extra,
Optional,
Required,
- Schema,
)
def _by_platform(arg):
return optionally_keyed_by('build-platform', arg)
# shortcut for a string where task references are allowed
taskref_or_string = Any(
@@ -42,17 +42,17 @@ l10n_description_schema = Schema({
# build-platform, inferred from dependent job before validation
Required('build-platform'): basestring,
# max run time of the task
Required('run-time'): _by_platform(int),
# Data used by chain of trust (see `chain_of_trust` in this file)
- Optional('chainOfTrust'): {Extra: object},
+ Optional('chain-of-trust'): {Extra: object},
# All l10n jobs use mozharness
Required('mozharness'): {
# Script to invoke for mozharness
Required('script'): _by_platform(basestring),
# Config files passed to the mozharness script
Required('config'): _by_platform([basestring]),
@@ -95,27 +95,17 @@ l10n_description_schema = Schema({
Required('platform'): _by_platform(basestring),
# Symbol to use
Required('symbol'): basestring,
# Tier this task is
Required('tier'): _by_platform(int),
},
- Required('attributes'): {
- # Is this a nightly task, inferred from dependent job before validation
- Optional('nightly'): bool,
-
- # build_platform of this task, inferred from dependent job before validation
- Required('build_platform'): basestring,
-
- # build_type for this task, inferred from dependent job before validation
- Required('build_type'): basestring,
- Extra: object,
- },
+ Required('attributes'): dict,
# Extra environment values to pass to the worker
Optional('env'): _by_platform({basestring: taskref_or_string}),
# Number of chunks to split the locale repacks up into
Optional('chunks'): _by_platform(int),
# Task deps to chain this task with, added in transforms from dependent-task
@@ -303,19 +293,19 @@ def mh_options_replace_project(config, j
job['mozharness']['options']
)
yield job
@transforms.add
def chain_of_trust(config, jobs):
for job in jobs:
- job.setdefault('chainOfTrust', {})
- job['chainOfTrust'].setdefault('inputs', {})
- job['chainOfTrust']['inputs']['docker-image'] = {
+ job.setdefault('chain-of-trust', {})
+ job['chain-of-trust'].setdefault('inputs', {})
+ job['chain-of-trust']['inputs']['docker-image'] = {
"task-reference": "<docker-image>"
}
yield job
@transforms.add
def validate_again(config, jobs):
for job in jobs:
@@ -330,17 +320,17 @@ def make_job_description(config, jobs):
'name': job['name'],
'worker': {
'implementation': 'docker-worker',
'docker-image': {'in-tree': 'desktop-build'},
'max-run-time': job['run-time'],
'chain-of-trust': True,
},
'extra': {
- 'chainOfTrust': job['chainOfTrust'],
+ 'chainOfTrust': job['chain-of-trust'],
},
'worker-type': job['worker-type'],
'description': job['description'],
'run': {
'using': 'mozharness',
'job-script': 'taskcluster/scripts/builder/build-l10n.sh',
'config': job['mozharness']['config'],
'script': job['mozharness']['script'],
--- a/taskcluster/taskgraph/transforms/nightly_l10n_signing.py
+++ b/taskcluster/taskgraph/transforms/nightly_l10n_signing.py
@@ -39,18 +39,18 @@ def make_signing_description(config, job
'artifacts': ['public/build/{locale}/target.complete.mar'],
'format': 'mar',
}
]
upstream_artifacts = []
for spec in job_specs:
fmt = spec['format']
upstream_artifacts.append({
- "taskId": {"task-reference": "<unsigned-repack>"},
- "taskType": "l10n",
+ "task-id": {"task-reference": "<unsigned-repack>"},
+ "task-type": "l10n",
# Set paths based on artifacts in the specs (above) one per
# locale present in the chunk this is signing stuff for.
"paths": [f.format(locale=l)
for l in dep_job.attributes.get('chunk_locales', [])
for f in spec['artifacts']],
"formats": [fmt]
})
--- a/taskcluster/taskgraph/transforms/signing.py
+++ b/taskcluster/taskgraph/transforms/signing.py
@@ -3,19 +3,19 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""
Transform the signing task into an actual task description.
"""
from __future__ import absolute_import, print_function, unicode_literals
from taskgraph.transforms.base import TransformSequence
-from taskgraph.util.schema import validate_schema
+from taskgraph.util.schema import validate_schema, Schema
from taskgraph.transforms.task import task_description_schema
-from voluptuous import Schema, Any, Required, Optional
+from voluptuous import Any, Required, Optional
ARTIFACT_URL = 'https://queue.taskcluster.net/v1/task/<{}>/artifacts/{}'
# 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()}
@@ -30,20 +30,20 @@ taskref_or_string = Any(
signing_description_schema = Schema({
# the dependant task (object) for this signing job, used to inform signing.
Required('dependent-task'): object,
# Artifacts from dep task to sign - Sync with taskgraph/transforms/task.py
# because this is passed directly into the signingscript worker
Required('upstream-artifacts'): [{
# taskId of the task with the artifact
- Required('taskId'): taskref_or_string,
+ Required('task-id'): taskref_or_string,
# type of signing task (for CoT)
- Required('taskType'): basestring,
+ Required('task-type'): basestring,
# Paths to the artifacts to sign
Required('paths'): [basestring],
# Signing formats to use on each of the paths
Required('formats'): [basestring],
}],
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -10,18 +10,18 @@ complexities of worker implementations,
from __future__ import absolute_import, print_function, unicode_literals
import json
import time
from taskgraph.util.treeherder import split_symbol
from taskgraph.transforms.base import TransformSequence
-from taskgraph.util.schema import validate_schema
-from voluptuous import Schema, Any, Required, Optional, Extra
+from taskgraph.util.schema import validate_schema, Schema
+from voluptuous import Any, Required, Optional, Extra
from .gecko_v2_whitelist import JOB_NAME_WHITELIST, JOB_NAME_WHITELIST_ERROR
# shortcut for a string where task references are allowed
taskref_or_string = Any(
basestring,
{Required('task-reference'): basestring})
@@ -274,64 +274,64 @@ task_description_schema = Schema({
Required('implementation'): 'scriptworker-signing',
# the maximum time to spend signing, in seconds
Required('max-run-time', default=600): int,
# list of artifact URLs for the artifacts that should be signed
Required('upstream-artifacts'): [{
# taskId of the task with the artifact
- Required('taskId'): taskref_or_string,
+ Required('task-id'): taskref_or_string,
# type of signing task (for CoT)
- Required('taskType'): basestring,
+ Required('task-type'): basestring,
# Paths to the artifacts to sign
Required('paths'): [basestring],
# Signing formats to use on each of the paths
Required('formats'): [basestring],
}],
}, {
Required('implementation'): 'beetmover',
# the maximum time to spend signing, in seconds
Required('max-run-time', default=600): int,
# taskid of task with artifacts to beetmove
# beetmover template key
- Required('update_manifest'): bool,
+ Required('update-manifest'): bool,
# locale key, if this is a locale beetmover job
Optional('locale'): basestring,
# list of artifact URLs for the artifacts that should be beetmoved
Required('upstream-artifacts'): [{
# taskId of the task with the artifact
- Required('taskId'): taskref_or_string,
+ Required('task-id'): taskref_or_string,
# type of signing task (for CoT)
- Required('taskType'): basestring,
+ Required('task-type'): basestring,
# Paths to the artifacts to sign
Required('paths'): [basestring],
# locale is used to map upload path and allow for duplicate simple names
Required('locale'): basestring,
}],
}, {
Required('implementation'): 'balrog',
# list of artifact URLs for the artifacts that should be beetmoved
Required('upstream-artifacts'): [{
# taskId of the task with the artifact
- Required('taskId'): taskref_or_string,
+ Required('task-id'): taskref_or_string,
# type of signing task (for CoT)
- Required('taskType'): basestring,
+ Required('task-type'): basestring,
# Paths to the artifacts to sign
Required('paths'): [basestring],
}],
}),
# The "when" section contains descriptions of the circumstances
# under which this task can be "optimized", that is, left out of the
@@ -542,46 +542,57 @@ def build_generic_worker_payload(config,
}
# needs-sccache is handled in mozharness_on_windows
if 'retry-exit-status' in worker:
raise Exception("retry-exit-status not supported in generic-worker")
+def format_upstream_artifacts(upstream_artifacts):
+ """Change the case of the artifact definitions to match that expected by
+ scriptworker"""
+ return [{
+ 'taskId': ua['task-id'],
+ 'taskType': ua['task-type'],
+ 'paths': ua['paths'],
+ 'formats': ua['formats'],
+ } for ua in upstream_artifacts]
+
+
@payload_builder('scriptworker-signing')
def build_scriptworker_signing_payload(config, task, task_def):
worker = task['worker']
task_def['payload'] = {
'maxRunTime': worker['max-run-time'],
- 'upstreamArtifacts': worker['upstream-artifacts']
+ 'upstreamArtifacts': format_upstream_artifacts(worker['upstream-artifacts'])
}
@payload_builder('beetmover')
def build_beetmover_payload(config, task, task_def):
worker = task['worker']
task_def['payload'] = {
'maxRunTime': worker['max-run-time'],
'upload_date': config.params['build_date'],
- 'update_manifest': worker['update_manifest'],
- 'upstreamArtifacts': worker['upstream-artifacts']
+ 'update_manifest': worker['update-manifest'],
+ 'upstreamArtifacts': format_upstream_artifacts(worker['upstream-artifacts'])
}
if worker.get('locale'):
task_def['payload']['locale'] = worker['locale']
@payload_builder('balrog')
def build_balrog_payload(config, task, task_def):
worker = task['worker']
task_def['payload'] = {
- 'upstreamArtifacts': worker['upstream-artifacts']
+ 'upstreamArtifacts': format_upstream_artifacts(worker['upstream-artifacts'])
}
@payload_builder('native-engine')
def build_macosx_engine_payload(config, task, task_def):
worker = task['worker']
artifacts = map(lambda artifact: {
'name': artifact['name'],
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -23,22 +23,22 @@ from taskgraph.transforms.base import Tr
from taskgraph.util.schema import resolve_keyed_by
from taskgraph.util.treeherder import split_symbol, join_symbol
from taskgraph.transforms.job.common import (
docker_worker_support_vcs_checkout,
)
from taskgraph.util.schema import (
validate_schema,
optionally_keyed_by,
+ Schema,
)
from voluptuous import (
Any,
Optional,
Required,
- Schema,
)
import copy
import logging
import os.path
import re
ARTIFACT_URL = 'https://queue.taskcluster.net/v1/task/{}/artifacts/{}'
--- a/taskcluster/taskgraph/util/schema.py
+++ b/taskcluster/taskgraph/util/schema.py
@@ -2,16 +2,17 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import, print_function, unicode_literals
import re
import copy
import pprint
+import collections
import voluptuous
def validate_schema(schema, obj, msg_prefix):
"""
Validate that object satisfies schema. If not, generate a useful exception
beginning with msg_prefix.
"""
@@ -83,34 +84,69 @@ def resolve_keyed_by(item, field, item_n
if subfield not in container:
return item
value = container[subfield]
if not isinstance(value, dict) or len(value) != 1 or not value.keys()[0].startswith('by-'):
return item
keyed_by = value.keys()[0][3:] # strip off 'by-' prefix
- key = extra_values.get(keyed_by) if keyed_by in extra_values else item[keyed_by]
+ key = extra_values.get(
+ keyed_by) if keyed_by in extra_values else item[keyed_by]
alternatives = value.values()[0]
# exact match
if key in alternatives:
container[subfield] = alternatives[key]
return item
# regular expression match
- matches = [(k, v) for k, v in alternatives.iteritems() if re.match(k + '$', key)]
+ matches = [(k, v)
+ for k, v in alternatives.iteritems() if re.match(k + '$', key)]
if len(matches) > 1:
raise Exception(
"Multiple matching values for {} {!r} found while determining item {} in {}".format(
keyed_by, key, field, item_name))
elif matches:
container[subfield] = matches[0][1]
return item
# default
if 'default' in alternatives:
container[subfield] = alternatives['default']
return item
raise Exception(
"No {} matching {!r} nor 'default' found while determining item {} in {}".format(
keyed_by, key, field, item_name))
+
+
+def check_schema(schema):
+ identifier_re = re.compile('^[a-z][a-z0-9-]*$')
+
+ def iter(sch):
+ if isinstance(sch, collections.Mapping):
+ for k, v in sch.iteritems():
+ if isinstance(k, (voluptuous.Optional, voluptuous.Required)):
+ k = str(k)
+ if isinstance(k, basestring):
+ if not identifier_re.match(str(k)):
+ raise RuntimeError(
+ 'YAML schemas should use dashed lower-case identifiers, '
+ 'not {!r}'.format(k))
+ iter(v)
+ elif isinstance(sch, (list, tuple)):
+ for v in sch:
+ iter(v)
+ elif isinstance(sch, voluptuous.Any):
+ for v in sch.validators:
+ iter(v)
+ iter(schema.schema)
+
+
+def Schema(*args, **kwargs):
+ """
+ Operates identically to voluptuous.Schema, but applying some taskgraph-specific checks
+ in the process.
+ """
+ schema = voluptuous.Schema(*args, **kwargs)
+ check_schema(schema)
+ return schema