From 1191dcfccf6a37ebd6d986b8146f11de96508622 Mon Sep 17 00:00:00 2001 From: Victor Seva Date: Fri, 20 Feb 2015 12:37:29 +0100 Subject: [PATCH] Allow use of aliases defined previously inside included files Anchors and aliases were expanded internally within JJB's yaml loading calls so they were limited to individual documents. Now, included files will have access to aliases of anchors already defined at previously processed files. Example: - default: name: default-timeout-wrapper timeout: &timeout fail: true elastic-percentage: 150 elastic-default-timeout: 90 type: elastic - wrapper: !include include002_1.yaml.inc Previously was not possible to use '*timeout' alias inside include002_1.yaml.inc file Closes-Story: 2000173 Change-Id: Ic031ddbb0310bd11748183fbde9502735c3b7169 --- doc/source/definition.rst | 5 ++-- jenkins_jobs/local_yaml.py | 27 ++++++++++++++++++- tests/localyaml/test_localyaml.py | 5 ++-- tests/yamlparser/fixtures/include002.xml | 24 +++++++++++++++++ tests/yamlparser/fixtures/include002.yaml | 16 +++++++++++ tests/yamlparser/fixtures/include002.yaml.inc | 1 + .../yamlparser/fixtures/include002_1.yaml.inc | 3 +++ 7 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 tests/yamlparser/fixtures/include002.xml create mode 100644 tests/yamlparser/fixtures/include002.yaml create mode 100644 tests/yamlparser/fixtures/include002.yaml.inc create mode 100644 tests/yamlparser/fixtures/include002_1.yaml.inc diff --git a/doc/source/definition.rst b/doc/source/definition.rst index ce24ad258..a57b248da 100644 --- a/doc/source/definition.rst +++ b/doc/source/definition.rst @@ -319,9 +319,8 @@ For example: The `anchors and aliases`_ are expanded internally within JJB's yaml loading -calls, and are limited to individual documents. That means you use the same -anchor name in separate files without collisions, but also means that you must -define the anchor in the same file that you intend to reference it. +calls and are not limited to individual documents. That means you can't use +the same anchor name in included files without collisions. A simple example can be seen in the specs `full length example`_ with the following being more representative of usage within JJB: diff --git a/jenkins_jobs/local_yaml.py b/jenkins_jobs/local_yaml.py index 689ae06ec..b179d196e 100644 --- a/jenkins_jobs/local_yaml.py +++ b/jenkins_jobs/local_yaml.py @@ -129,7 +129,31 @@ class OrderedConstructor(BaseConstructor): data.update(mapping) -class LocalLoader(OrderedConstructor, yaml.Loader): +class LocalAnchorLoader(yaml.Loader): + """Subclass for yaml.Loader which keeps Alias between calls""" + anchors = {} + + def __init__(self, *args, **kwargs): + super(LocalAnchorLoader, self).__init__(*args, **kwargs) + self.anchors = LocalAnchorLoader.anchors + + @classmethod + def reset_anchors(cls): + cls.anchors = {} + + # override the default composer to skip resetting the anchors at the + # end of the current document + def compose_document(self): + # Drop the DOCUMENT-START event. + self.get_event() + # Compose the root node. + node = self.compose_node(None, None) + # Drop the DOCUMENT-END event. + self.get_event() + return node + + +class LocalLoader(OrderedConstructor, LocalAnchorLoader): """Subclass for yaml.Loader which handles the local tags 'include', 'include-raw' and 'include-raw-escaped' to specify a file to include data from and whether to parse it as additional yaml, treat it as a data blob @@ -228,4 +252,5 @@ class LocalLoader(OrderedConstructor, yaml.Loader): def load(stream, **kwargs): + LocalAnchorLoader.reset_anchors() return yaml.load(stream, functools.partial(LocalLoader, **kwargs)) diff --git a/tests/localyaml/test_localyaml.py b/tests/localyaml/test_localyaml.py index d1827037b..7699db71f 100644 --- a/tests/localyaml/test_localyaml.py +++ b/tests/localyaml/test_localyaml.py @@ -16,9 +16,9 @@ import os from testtools import ExpectedException -from testtools.matchers import MismatchError from testtools import TestCase from testscenarios.testcase import TestWithScenarios +from yaml.composer import ComposerError from jenkins_jobs import builder from tests.base import get_scenarios, JsonTestCase, YamlTestCase @@ -40,7 +40,8 @@ class TestCaseLocalYamlInclude(TestWithScenarios, TestCase, JsonTestCase): def test_yaml_snippet(self): if os.path.basename(self.in_filename).startswith("exception_"): - with ExpectedException(MismatchError): + with ExpectedException(ComposerError, + "^found duplicate anchor .*"): super(TestCaseLocalYamlInclude, self).test_yaml_snippet() else: super(TestCaseLocalYamlInclude, self).test_yaml_snippet() diff --git a/tests/yamlparser/fixtures/include002.xml b/tests/yamlparser/fixtures/include002.xml new file mode 100644 index 000000000..2274117ee --- /dev/null +++ b/tests/yamlparser/fixtures/include002.xml @@ -0,0 +1,24 @@ + + + + <!-- Managed by Jenkins Job Builder --> + false + false + false + false + true + + + + + + + 3 + true + false + 150 + 90 + elastic + + + diff --git a/tests/yamlparser/fixtures/include002.yaml b/tests/yamlparser/fixtures/include002.yaml new file mode 100644 index 000000000..13a2f1219 --- /dev/null +++ b/tests/yamlparser/fixtures/include002.yaml @@ -0,0 +1,16 @@ +# vim: sw=4 ts=4 et +- default: + name: default-timeout-wrapper + timeout: &timeout + fail: true + elastic-percentage: 150 + elastic-default-timeout: 90 + type: elastic + +# include that uses timeout alias +- wrapper: !include include002_1.yaml.inc + +- job: + name: test-job-1 + wrappers: + !include include002.yaml.inc diff --git a/tests/yamlparser/fixtures/include002.yaml.inc b/tests/yamlparser/fixtures/include002.yaml.inc new file mode 100644 index 000000000..e41c6be6c --- /dev/null +++ b/tests/yamlparser/fixtures/include002.yaml.inc @@ -0,0 +1 @@ +- timeout-wrapper diff --git a/tests/yamlparser/fixtures/include002_1.yaml.inc b/tests/yamlparser/fixtures/include002_1.yaml.inc new file mode 100644 index 000000000..b127f07b7 --- /dev/null +++ b/tests/yamlparser/fixtures/include002_1.yaml.inc @@ -0,0 +1,3 @@ +name: timeout-wrapper +wrappers: + - timeout: *timeout