mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 15:52:07 +00:00
Bug 1275409: move templates to taskgraph.util; r=wcosta
MozReview-Commit-ID: 3vdnm20W4OD --HG-- rename : taskcluster/taskgraph/test/test_util.py => taskcluster/taskgraph/test/test_util_docker.py rename : testing/taskcluster/tests/test_templates.py => taskcluster/taskgraph/test/test_util_templates.py rename : taskcluster/taskgraph/util.py => taskcluster/taskgraph/util/__init__.py rename : testing/taskcluster/taskcluster_graph/templates.py => taskcluster/taskgraph/util/templates.py extra : rebase_source : 6d098d87e715b82c0dcd5bf03beb7646bbd50fe2
This commit is contained in:
parent
eb7f0385b1
commit
c1ccda957f
@ -17,4 +17,5 @@ than you might suppose! This implementation supports:
|
|||||||
taskgraph
|
taskgraph
|
||||||
parameters
|
parameters
|
||||||
attributes
|
attributes
|
||||||
|
yaml-templates
|
||||||
old
|
old
|
||||||
|
45
taskcluster/docs/yaml-templates.rst
Normal file
45
taskcluster/docs/yaml-templates.rst
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
Task Definition YAML Templates
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Many kinds of tasks are described using YAML files. These files allow some
|
||||||
|
limited forms of inheritance and template substitution as well as the usual
|
||||||
|
YAML features, as described below.
|
||||||
|
|
||||||
|
Please use these features sparingly. In many cases, it is better to add a
|
||||||
|
feature to the implementation of a task kind rather than add complexity to the
|
||||||
|
YAML files.
|
||||||
|
|
||||||
|
Inheritance
|
||||||
|
-----------
|
||||||
|
|
||||||
|
One YAML file can "inherit" from another by including a top-level ``$inherits``
|
||||||
|
key. That key specifies the parent file in ``from``, and optionally a
|
||||||
|
collection of variables in ``variables``. For example:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
$inherits:
|
||||||
|
from: 'tasks/builds/base_linux32.yml'
|
||||||
|
variables:
|
||||||
|
build_name: 'linux32'
|
||||||
|
build_type: 'dbg'
|
||||||
|
|
||||||
|
Inheritance proceeds as follows: First, the child document has its template
|
||||||
|
substitutions performed and is parsed as YAML. Then, the parent document is
|
||||||
|
parsed, with substitutions specified by ``variables`` added to the template
|
||||||
|
substitutions. Finally, the child document is merged with the parent.
|
||||||
|
|
||||||
|
To merge two JSON objects (dictionaries), each value is merged individually.
|
||||||
|
Lists are merged by concatenating the lists from the parent and child
|
||||||
|
documents. Atomic values (strings, numbers, etc.) are merged by preferring the
|
||||||
|
child document's value.
|
||||||
|
|
||||||
|
Substitution
|
||||||
|
------------
|
||||||
|
|
||||||
|
Each document is expanded using the PyStache template engine before it is
|
||||||
|
parsed as YAML. The parameters for this expansion are specific to the task
|
||||||
|
kind.
|
||||||
|
|
||||||
|
Simple value substitution looks like ``{{variable}}``. Function calls look
|
||||||
|
like ``{{#function}}argument{{/function}}``.
|
@ -14,10 +14,10 @@ import time
|
|||||||
|
|
||||||
from . import base
|
from . import base
|
||||||
from ..types import Task
|
from ..types import Task
|
||||||
from taskgraph.util import docker_image
|
from taskgraph.util.docker import docker_image
|
||||||
import taskcluster_graph.transform.routes as routes_transform
|
import taskcluster_graph.transform.routes as routes_transform
|
||||||
import taskcluster_graph.transform.treeherder as treeherder_transform
|
import taskcluster_graph.transform.treeherder as treeherder_transform
|
||||||
from taskcluster_graph.templates import Templates
|
from taskgraph.util.templates import Templates
|
||||||
from taskcluster_graph.from_now import (
|
from taskcluster_graph.from_now import (
|
||||||
json_time_from_now,
|
json_time_from_now,
|
||||||
current_json_time,
|
current_json_time,
|
||||||
|
@ -32,9 +32,9 @@ from taskcluster_graph.from_now import (
|
|||||||
json_time_from_now,
|
json_time_from_now,
|
||||||
current_json_time,
|
current_json_time,
|
||||||
)
|
)
|
||||||
from taskcluster_graph.templates import Templates
|
from taskgraph.util.templates import Templates
|
||||||
import taskcluster_graph.build_task
|
import taskcluster_graph.build_task
|
||||||
from taskgraph.util import docker_image
|
from taskgraph.util.docker import docker_image
|
||||||
|
|
||||||
# TASKID_PLACEHOLDER is the "internal" form of a taskid; it is substituted with
|
# TASKID_PLACEHOLDER is the "internal" form of a taskid; it is substituted with
|
||||||
# actual taskIds at the very last minute, in get_task_definition
|
# actual taskIds at the very last minute, in get_task_definition
|
||||||
|
@ -6,7 +6,7 @@ from __future__ import absolute_import, print_function, unicode_literals
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from ..util import docker_image, DOCKER_ROOT
|
from ..util.docker import docker_image, DOCKER_ROOT
|
||||||
from mozunit import main, MockedOpen
|
from mozunit import main, MockedOpen
|
||||||
|
|
||||||
|
|
189
taskcluster/taskgraph/test/test_util_templates.py
Executable file
189
taskcluster/taskgraph/test/test_util_templates.py
Executable file
@ -0,0 +1,189 @@
|
|||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# 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 os
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import mozunit
|
||||||
|
import textwrap
|
||||||
|
from taskgraph.util.templates import (
|
||||||
|
Templates,
|
||||||
|
TemplatesException
|
||||||
|
)
|
||||||
|
|
||||||
|
files = {}
|
||||||
|
files['/fixtures/circular.yml'] = textwrap.dedent("""\
|
||||||
|
$inherits:
|
||||||
|
from: 'circular_ref.yml'
|
||||||
|
variables:
|
||||||
|
woot: 'inherit'
|
||||||
|
""")
|
||||||
|
|
||||||
|
files['/fixtures/inherit.yml'] = textwrap.dedent("""\
|
||||||
|
$inherits:
|
||||||
|
from: 'templates.yml'
|
||||||
|
variables:
|
||||||
|
woot: 'inherit'
|
||||||
|
""")
|
||||||
|
|
||||||
|
files['/fixtures/extend_child.yml'] = textwrap.dedent("""\
|
||||||
|
list: ['1', '2', '3']
|
||||||
|
was_list: ['1']
|
||||||
|
obj:
|
||||||
|
level: 1
|
||||||
|
deeper:
|
||||||
|
woot: 'bar'
|
||||||
|
list: ['baz']
|
||||||
|
""")
|
||||||
|
|
||||||
|
files['/fixtures/circular_ref.yml'] = textwrap.dedent("""\
|
||||||
|
$inherits:
|
||||||
|
from: 'circular.yml'
|
||||||
|
""")
|
||||||
|
|
||||||
|
files['/fixtures/child_pass.yml'] = textwrap.dedent("""\
|
||||||
|
values:
|
||||||
|
- {{a}}
|
||||||
|
- {{b}}
|
||||||
|
- {{c}}
|
||||||
|
""")
|
||||||
|
|
||||||
|
files['/fixtures/inherit_pass.yml'] = textwrap.dedent("""\
|
||||||
|
$inherits:
|
||||||
|
from: 'child_pass.yml'
|
||||||
|
variables:
|
||||||
|
a: 'a'
|
||||||
|
b: 'b'
|
||||||
|
c: 'c'
|
||||||
|
""")
|
||||||
|
|
||||||
|
files['/fixtures/deep/2.yml'] = textwrap.dedent("""\
|
||||||
|
$inherits:
|
||||||
|
from: deep/1.yml
|
||||||
|
|
||||||
|
""")
|
||||||
|
|
||||||
|
files['/fixtures/deep/3.yml'] = textwrap.dedent("""\
|
||||||
|
$inherits:
|
||||||
|
from: deep/2.yml
|
||||||
|
|
||||||
|
""")
|
||||||
|
|
||||||
|
files['/fixtures/deep/4.yml'] = textwrap.dedent("""\
|
||||||
|
$inherits:
|
||||||
|
from: deep/3.yml
|
||||||
|
""")
|
||||||
|
|
||||||
|
files['/fixtures/deep/1.yml'] = textwrap.dedent("""\
|
||||||
|
variable: {{value}}
|
||||||
|
""")
|
||||||
|
|
||||||
|
files['/fixtures/simple.yml'] = textwrap.dedent("""\
|
||||||
|
is_simple: true
|
||||||
|
""")
|
||||||
|
|
||||||
|
files['/fixtures/templates.yml'] = textwrap.dedent("""\
|
||||||
|
content: 'content'
|
||||||
|
variable: '{{woot}}'
|
||||||
|
""")
|
||||||
|
|
||||||
|
files['/fixtures/extend_parent.yml'] = textwrap.dedent("""\
|
||||||
|
$inherits:
|
||||||
|
from: 'extend_child.yml'
|
||||||
|
|
||||||
|
list: ['4']
|
||||||
|
was_list:
|
||||||
|
replaced: true
|
||||||
|
obj:
|
||||||
|
level: 2
|
||||||
|
from_parent: true
|
||||||
|
deeper:
|
||||||
|
list: ['bar']
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
class TemplatesTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.mocked_open = mozunit.MockedOpen(files)
|
||||||
|
self.mocked_open.__enter__()
|
||||||
|
self.subject = Templates('/fixtures')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.mocked_open.__exit__(None, None, None)
|
||||||
|
|
||||||
|
def test_invalid_path(self):
|
||||||
|
with self.assertRaisesRegexp(TemplatesException, 'must be a directory'):
|
||||||
|
Templates('/zomg/not/a/dir')
|
||||||
|
|
||||||
|
def test_no_templates(self):
|
||||||
|
content = self.subject.load('simple.yml', {})
|
||||||
|
self.assertEquals(content, {
|
||||||
|
'is_simple': True
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_with_templates(self):
|
||||||
|
content = self.subject.load('templates.yml', {
|
||||||
|
'woot': 'bar'
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEquals(content, {
|
||||||
|
'content': 'content',
|
||||||
|
'variable': 'bar'
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_inheritance(self):
|
||||||
|
'''
|
||||||
|
The simple single pass inheritance case.
|
||||||
|
'''
|
||||||
|
content = self.subject.load('inherit.yml', {})
|
||||||
|
self.assertEqual(content, {
|
||||||
|
'content': 'content',
|
||||||
|
'variable': 'inherit'
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_inheritance_implicat_pass(self):
|
||||||
|
'''
|
||||||
|
Implicitly pass parameters from the child to the ancestor.
|
||||||
|
'''
|
||||||
|
content = self.subject.load('inherit_pass.yml', {
|
||||||
|
'a': 'overriden'
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(content, { 'values': ['overriden', 'b', 'c'] });
|
||||||
|
|
||||||
|
|
||||||
|
def test_inheritance_circular(self):
|
||||||
|
'''
|
||||||
|
Circular reference handling.
|
||||||
|
'''
|
||||||
|
with self.assertRaisesRegexp(TemplatesException, 'circular'):
|
||||||
|
self.subject.load('circular.yml', {})
|
||||||
|
|
||||||
|
def test_deep_inheritance(self):
|
||||||
|
content = self.subject.load('deep/4.yml', {
|
||||||
|
'value': 'myvalue'
|
||||||
|
})
|
||||||
|
self.assertEqual(content, { 'variable': 'myvalue' })
|
||||||
|
|
||||||
|
def test_inheritance_with_simple_extensions(self):
|
||||||
|
content = self.subject.load('extend_parent.yml', {})
|
||||||
|
self.assertEquals(content, {
|
||||||
|
'list': ['1', '2', '3', '4'],
|
||||||
|
'obj': {
|
||||||
|
'from_parent': True,
|
||||||
|
'deeper': {
|
||||||
|
'woot': 'bar',
|
||||||
|
'list': ['baz', 'bar']
|
||||||
|
},
|
||||||
|
'level': 2,
|
||||||
|
},
|
||||||
|
'was_list': { 'replaced': True }
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
mozunit.main()
|
0
taskcluster/taskgraph/util/__init__.py
Normal file
0
taskcluster/taskgraph/util/__init__.py
Normal file
@ -6,12 +6,12 @@ from __future__ import absolute_import, print_function, unicode_literals
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
GECKO = os.path.realpath(os.path.join(__file__, '..', '..', '..'))
|
GECKO = os.path.realpath(os.path.join(__file__, '..', '..', '..', '..'))
|
||||||
DOCKER_ROOT = os.path.join(GECKO, 'testing', 'docker')
|
DOCKER_ROOT = os.path.join(GECKO, 'testing', 'docker')
|
||||||
|
|
||||||
def docker_image(name):
|
def docker_image(name):
|
||||||
''' Determine the docker image name, including repository and tag, from an
|
'''Determine the docker image name, including repository and tag, from an
|
||||||
in-tree docker file'''
|
in-tree docker file.'''
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(DOCKER_ROOT, name, 'REGISTRY')) as f:
|
with open(os.path.join(DOCKER_ROOT, name, 'REGISTRY')) as f:
|
||||||
registry = f.read().strip()
|
registry = f.read().strip()
|
||||||
@ -23,3 +23,4 @@ def docker_image(name):
|
|||||||
version = f.read().strip()
|
version = f.read().strip()
|
||||||
|
|
||||||
return '{}/{}:{}'.format(registry, name, version)
|
return '{}/{}:{}'.format(registry, name, version)
|
||||||
|
|
@ -8,7 +8,7 @@ import urllib2
|
|||||||
import taskcluster_graph.transform.routes as routes_transform
|
import taskcluster_graph.transform.routes as routes_transform
|
||||||
import taskcluster_graph.transform.treeherder as treeherder_transform
|
import taskcluster_graph.transform.treeherder as treeherder_transform
|
||||||
from slugid import nice as slugid
|
from slugid import nice as slugid
|
||||||
from taskcluster_graph.templates import Templates
|
from taskgraph.util.templates import Templates
|
||||||
|
|
||||||
TASKCLUSTER_ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..'))
|
TASKCLUSTER_ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..'))
|
||||||
IMAGE_BUILD_TASK = os.path.join(TASKCLUSTER_ROOT, 'tasks', 'image.yml')
|
IMAGE_BUILD_TASK = os.path.join(TASKCLUSTER_ROOT, 'tasks', 'image.yml')
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
values:
|
|
||||||
- {{a}}
|
|
||||||
- {{b}}
|
|
||||||
- {{c}}
|
|
@ -1,4 +0,0 @@
|
|||||||
$inherits:
|
|
||||||
from: 'circular_ref.yml'
|
|
||||||
variables:
|
|
||||||
woot: 'inherit'
|
|
@ -1,2 +0,0 @@
|
|||||||
$inherits:
|
|
||||||
from: 'circular.yml'
|
|
@ -1 +0,0 @@
|
|||||||
variable: {{value}}
|
|
@ -1,3 +0,0 @@
|
|||||||
$inherits:
|
|
||||||
from: deep/1.yml
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
|||||||
$inherits:
|
|
||||||
from: deep/2.yml
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
|||||||
$inherits:
|
|
||||||
from: deep/3.yml
|
|
@ -1,7 +0,0 @@
|
|||||||
list: ['1', '2', '3']
|
|
||||||
was_list: ['1']
|
|
||||||
obj:
|
|
||||||
level: 1
|
|
||||||
deeper:
|
|
||||||
woot: 'bar'
|
|
||||||
list: ['baz']
|
|
@ -1,11 +0,0 @@
|
|||||||
$inherits:
|
|
||||||
from: 'extend_child.yml'
|
|
||||||
|
|
||||||
list: ['4']
|
|
||||||
was_list:
|
|
||||||
replaced: true
|
|
||||||
obj:
|
|
||||||
level: 2
|
|
||||||
from_parent: true
|
|
||||||
deeper:
|
|
||||||
list: ['bar']
|
|
@ -1,4 +0,0 @@
|
|||||||
$inherits:
|
|
||||||
from: 'templates.yml'
|
|
||||||
variables:
|
|
||||||
woot: 'inherit'
|
|
@ -1,6 +0,0 @@
|
|||||||
$inherits:
|
|
||||||
from: 'child_pass.yml'
|
|
||||||
variables:
|
|
||||||
a: 'a'
|
|
||||||
b: 'b'
|
|
||||||
c: 'c'
|
|
@ -1 +0,0 @@
|
|||||||
is_simple: true
|
|
@ -1,2 +0,0 @@
|
|||||||
content: 'content'
|
|
||||||
variable: '{{woot}}'
|
|
@ -1,88 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
import mozunit
|
|
||||||
from taskcluster_graph.templates import (
|
|
||||||
Templates,
|
|
||||||
TemplatesException
|
|
||||||
)
|
|
||||||
|
|
||||||
class TemplatesTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
abs_path = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
self.subject = Templates(os.path.join(abs_path, 'fixtures'))
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_path(self):
|
|
||||||
with self.assertRaisesRegexp(TemplatesException, 'must be a directory'):
|
|
||||||
Templates('/zomg/not/a/dir')
|
|
||||||
|
|
||||||
def test_no_templates(self):
|
|
||||||
content = self.subject.load('simple.yml', {})
|
|
||||||
self.assertEquals(content, {
|
|
||||||
'is_simple': True
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_with_templates(self):
|
|
||||||
content = self.subject.load('templates.yml', {
|
|
||||||
'woot': 'bar'
|
|
||||||
})
|
|
||||||
|
|
||||||
self.assertEquals(content, {
|
|
||||||
'content': 'content',
|
|
||||||
'variable': 'bar'
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_inheritance(self):
|
|
||||||
'''
|
|
||||||
The simple single pass inheritance case.
|
|
||||||
'''
|
|
||||||
content = self.subject.load('inherit.yml', {})
|
|
||||||
self.assertEqual(content, {
|
|
||||||
'content': 'content',
|
|
||||||
'variable': 'inherit'
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_inheritance_implicat_pass(self):
|
|
||||||
'''
|
|
||||||
Implicitly pass parameters from the child to the ancestor.
|
|
||||||
'''
|
|
||||||
content = self.subject.load('inherit_pass.yml', {
|
|
||||||
'a': 'overriden'
|
|
||||||
})
|
|
||||||
|
|
||||||
self.assertEqual(content, { 'values': ['overriden', 'b', 'c'] });
|
|
||||||
|
|
||||||
|
|
||||||
def test_inheritance_circular(self):
|
|
||||||
'''
|
|
||||||
Circular reference handling.
|
|
||||||
'''
|
|
||||||
with self.assertRaisesRegexp(TemplatesException, 'circular'):
|
|
||||||
self.subject.load('circular.yml', {})
|
|
||||||
|
|
||||||
def test_deep_inheritance(self):
|
|
||||||
content = self.subject.load('deep/4.yml', {
|
|
||||||
'value': 'myvalue'
|
|
||||||
})
|
|
||||||
self.assertEqual(content, { 'variable': 'myvalue' })
|
|
||||||
|
|
||||||
def test_inheritance_with_simple_extensions(self):
|
|
||||||
content = self.subject.load('extend_parent.yml', {})
|
|
||||||
self.assertEquals(content, {
|
|
||||||
'list': ['1', '2', '3', '4'],
|
|
||||||
'obj': {
|
|
||||||
'from_parent': True,
|
|
||||||
'deeper': {
|
|
||||||
'woot': 'bar',
|
|
||||||
'list': ['baz', 'bar']
|
|
||||||
},
|
|
||||||
'level': 2,
|
|
||||||
},
|
|
||||||
'was_list': { 'replaced': True }
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
mozunit.main()
|
|
Loading…
Reference in New Issue
Block a user