Unify variable and tags expansion inside jobs and macros

Expand variables inside macros without parameters and jobs
the same way as they are expanded inside macros with parameters
and job templates.
Make tags behave inside macros without parameters and jobs
the same way as they are expanded inside macros with parameters
and job templates.
Update or fix affected tests.

Story: 2010588
Story: 2010963
Story: 2010535
Task: 47394
Task: 49069
Task: 47151

Change-Id: Ie05ae6aa386c62ebbf68dd3e2c7001a4e444a47a
This commit is contained in:
Vsevolod Fedorov 2023-11-13 12:28:57 +03:00
parent 081fcaa0d3
commit 18efe5066b
34 changed files with 133 additions and 205 deletions

View File

@ -362,9 +362,6 @@ specific ones without having to duplicate code::
# Generic macro call with a parameter # Generic macro call with a parameter
- add: - add:
number: "ZERO" number: "ZERO"
# Generic macro called without a parameter. Never do this!
# See below for the resulting wrong output :(
- add
Then ``<builders />`` section of the generated job show up as:: Then ``<builders />`` section of the generated job show up as::
@ -375,9 +372,6 @@ Then ``<builders />`` section of the generated job show up as::
<hudson.tasks.Shell> <hudson.tasks.Shell>
<command>echo Adding ZERO</command> <command>echo Adding ZERO</command>
</hudson.tasks.Shell> </hudson.tasks.Shell>
<hudson.tasks.Shell>
<command>echo Adding {number}</command>
</hudson.tasks.Shell>
</builders> </builders>
As you can see, the specialized macro ``addtwo`` reused the definition from As you can see, the specialized macro ``addtwo`` reused the definition from
@ -386,31 +380,10 @@ the generic macro ``add``.
Macro Notes Macro Notes
~~~~~~~~~~~ ~~~~~~~~~~~
If a macro is not passed any parameters it will not have any expansion
performed on it. Thus if you forget to provide `any` parameters to a
macro that expects some, the parameter-templates (``{foo}``) will be
left as is in the resulting output; this is almost certainly not what
you want. Note if you provide an invalid parameter, the expansion
will fail; the expansion will only be skipped if you provide `no`
parameters at all.
Macros are expanded using Python string substitution rules. This can Macros are expanded using Python string substitution rules. This can
especially cause confusion with shell snippets that use ``{`` as part especially cause confusion with shell snippets that use ``{`` and ``}`` as part
of their syntax. As described, if a macro has `no` parameters, no of their syntax. You must escape curly braces you wish to make it through
expansion will be performed and thus it is correct to write the script to the output, e.g.::
with no escaping, e.g.::
- builder:
name: a_builder
builders:
- shell: |
VARIABLE=${VARIABLE:-bar}
function foo {
echo "my shell function"
}
However, if the macro `has` parameters, you must escape the ``{`` you
wish to make it through to the output, e.g.::
- builder: - builder:
name: a_builder name: a_builder
@ -422,10 +395,11 @@ wish to make it through to the output, e.g.::
echo "my shell function" echo "my shell function"
}} }}
Note that a ``job-template`` will have parameters by definition (at The same apply for ``job`` and ``job-template``. Thus embedded-shell within
least a ``name``). Thus embedded-shell within a ``job-template`` should a ``job`` or ``job-template`` should always use ``{{`` to achieve a literal
always use ``{{`` to achieve a literal ``{``. A generic builder will need ``{``.
to consider the correct quoting based on its use of parameters.
You can also use ``!j2:`` tag instead - Jinja2 uses double quotes for variables.
.. _folders: .. _folders:

View File

@ -176,7 +176,7 @@ class Defaults:
contents: dict # Values that go to job contents. contents: dict # Values that go to job contents.
@classmethod @classmethod
def add(cls, config, roots, expander, params_expander, data, pos): def add(cls, config, roots, data, pos):
d = data.copy() d = data.copy()
name = d.pop_required_loc_string("name") name = d.pop_required_loc_string("name")
contents, params = split_contents_params( contents, params = split_contents_params(

View File

@ -15,7 +15,7 @@ from collections import namedtuple
from .errors import Context, JenkinsJobsException from .errors import Context, JenkinsJobsException
from .loc_loader import LocList, LocDict from .loc_loader import LocList, LocDict
from jenkins_jobs.expander import Expander from jenkins_jobs.expander import YamlObjectsExpander
Dimension = namedtuple("Dimension", "axis params") Dimension = namedtuple("Dimension", "axis params")
@ -48,7 +48,7 @@ def _decode_axis_value(axis, value, key_pos, value_pos):
def enum_dimensions_params(axes, params, defaults): def enum_dimensions_params(axes, params, defaults):
expander = Expander() expander = YamlObjectsExpander()
if not axes: if not axes:
# No axes - instantiate one job/view. # No axes - instantiate one job/view.
yield {} yield {}
@ -60,9 +60,10 @@ def enum_dimensions_params(axes, params, defaults):
except KeyError: except KeyError:
try: try:
value = defaults[axis] value = defaults[axis]
key_pos = value_pos = None
except KeyError: except KeyError:
continue # May be, value would be received from an another axis values. continue # May be, value would be received from an another axis values.
expanded_value = expander.expand(value, params) expanded_value = expander.expand(value, params, key_pos, value_pos)
value = [ value = [
Dimension(axis, params) Dimension(axis, params)
for params in _decode_axis_value(axis, expanded_value, key_pos, value_pos) for params in _decode_axis_value(axis, expanded_value, key_pos, value_pos)

View File

@ -55,9 +55,8 @@ def expand_tuple(expander, obj, params, key_pos, value_pos):
class StrExpander: class StrExpander:
def __init__(self, config): def __init__(self, allow_empty_variables):
allow_empty = config.yamlparser["allow_empty_variables"] self._formatter = CustomFormatter(allow_empty_variables)
self._formatter = CustomFormatter(allow_empty)
def __call__(self, obj, params, key_pos, value_pos): def __call__(self, obj, params, key_pos, value_pos):
try: try:
@ -79,14 +78,14 @@ def call_expand(expander, obj, params, key_pos, value_pos):
return obj.expand(expander, params) return obj.expand(expander, params)
def call_subst(expander, obj, params, key_pos, value_pos):
return obj.subst(expander, params)
def dont_expand(obj, params, key_pos, value_pos): def dont_expand(obj, params, key_pos, value_pos):
return obj return obj
def dont_expand_yaml_object(expander, obj, params, key_pos, value_pos):
return obj
yaml_classes_list = [ yaml_classes_list = [
J2String, J2String,
J2Yaml, J2Yaml,
@ -104,9 +103,13 @@ deprecated_yaml_tags = [
] ]
# Does not expand string formats. Used in jobs and macros without parameters. # Expand strings and yaml objects.
class Expander: class Expander:
def __init__(self, config=None): def __init__(self, config=None):
if config:
allow_empty_variables = config.yamlparser["allow_empty_variables"]
else:
allow_empty_variables = False
_yaml_object_expanders = { _yaml_object_expanders = {
cls: partial(call_expand, self) for cls in yaml_classes_list cls: partial(call_expand, self) for cls in yaml_classes_list
} }
@ -116,8 +119,8 @@ class Expander:
list: partial(expand_list, self), list: partial(expand_list, self),
LocList: partial(expand_list, self), LocList: partial(expand_list, self),
tuple: partial(expand_tuple, self), tuple: partial(expand_tuple, self),
str: dont_expand, str: StrExpander(allow_empty_variables),
LocString: dont_expand, LocString: StrExpander(allow_empty_variables),
bool: dont_expand, bool: dont_expand,
int: dont_expand, int: dont_expand,
float: dont_expand, float: dont_expand,
@ -136,20 +139,26 @@ class Expander:
return expander(obj, params, key_pos, value_pos) return expander(obj, params, key_pos, value_pos)
# Expands string formats also. Used in jobs templates and macros with parameters. # Expand only yaml objects.
class ParamsExpander(Expander): class YamlObjectsExpander(Expander):
def __init__(self):
super().__init__()
self.expanders.update(
{
str: dont_expand,
LocString: dont_expand,
}
)
# Expand only string parameters.
class StringsOnlyExpander(Expander):
def __init__(self, config): def __init__(self, config):
super().__init__(config) super().__init__(config)
_yaml_object_expanders = { _yaml_object_expanders = {
cls: partial(call_subst, self) for cls in yaml_classes_list cls: partial(dont_expand_yaml_object, self) for cls in yaml_classes_list
} }
self.expanders.update( self.expanders.update(_yaml_object_expanders)
{
str: StrExpander(config),
LocString: StrExpander(config),
**_yaml_object_expanders,
}
)
def call_required_params(obj, pos): def call_required_params(obj, pos):

View File

@ -13,6 +13,7 @@
from dataclasses import dataclass from dataclasses import dataclass
from .errors import JenkinsJobsException from .errors import JenkinsJobsException
from .expander import Expander
from .root_base import RootBase, NonTemplateRootMixin, TemplateRootMixin, Group from .root_base import RootBase, NonTemplateRootMixin, TemplateRootMixin, Group
from .defaults import split_contents_params, job_contents_keys from .defaults import split_contents_params, job_contents_keys
@ -23,7 +24,7 @@ class JobBase(RootBase):
folder: str folder: str
@classmethod @classmethod
def from_dict(cls, config, roots, expander, data, pos): def from_dict(cls, config, roots, data, pos):
keep_descriptions = config.yamlparser["keep_descriptions"] keep_descriptions = config.yamlparser["keep_descriptions"]
d = data.copy() d = data.copy()
name = d.pop_required_loc_string("name") name = d.pop_required_loc_string("name")
@ -35,7 +36,7 @@ class JobBase(RootBase):
contents, params = split_contents_params(d, job_contents_keys) contents, params = split_contents_params(d, job_contents_keys)
return cls( return cls(
roots.defaults, roots.defaults,
expander, Expander(config),
keep_descriptions, keep_descriptions,
id, id,
name, name,
@ -72,8 +73,8 @@ class JobBase(RootBase):
class Job(JobBase, NonTemplateRootMixin): class Job(JobBase, NonTemplateRootMixin):
@classmethod @classmethod
def add(cls, config, roots, expander, param_expander, data, pos): def add(cls, config, roots, data, pos):
job = cls.from_dict(config, roots, expander, data, pos) job = cls.from_dict(config, roots, data, pos)
roots.assign(roots.jobs, job.id, job, "job") roots.assign(roots.jobs, job.id, job, "job")
def __str__(self): def __str__(self):
@ -82,8 +83,8 @@ class Job(JobBase, NonTemplateRootMixin):
class JobTemplate(JobBase, TemplateRootMixin): class JobTemplate(JobBase, TemplateRootMixin):
@classmethod @classmethod
def add(cls, config, roots, expander, params_expander, data, pos): def add(cls, config, roots, data, pos):
template = cls.from_dict(config, roots, params_expander, data, pos) template = cls.from_dict(config, roots, data, pos)
roots.assign(roots.job_templates, template.id, template, "job template") roots.assign(roots.job_templates, template.id, template, "job template")
def __str__(self): def __str__(self):
@ -96,7 +97,7 @@ class JobGroup(Group):
_job_templates: dict _job_templates: dict
@classmethod @classmethod
def add(cls, config, roots, expander, params_expander, data, pos): def add(cls, config, roots, data, pos):
d = data.copy() d = data.copy()
name = d.pop_required_loc_string("name") name = d.pop_required_loc_string("name")
try: try:

View File

@ -17,7 +17,7 @@ from functools import partial
from .errors import JenkinsJobsException from .errors import JenkinsJobsException
from .loc_loader import LocLoader from .loc_loader import LocLoader
from .yaml_objects import BaseYamlObject from .yaml_objects import BaseYamlObject
from .expander import Expander, ParamsExpander, deprecated_yaml_tags, yaml_classes_list from .expander import Expander, deprecated_yaml_tags, yaml_classes_list
from .roots import root_adders from .roots import root_adders
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -115,7 +115,6 @@ def enum_expanded_paths(path_list):
def load_files(config, roots, path_list): def load_files(config, roots, path_list):
expander = Expander(config) expander = Expander(config)
params_expander = ParamsExpander(config)
loader = Loader.empty(config) loader = Loader.empty(config)
for path in enum_expanded_paths(path_list): for path in enum_expanded_paths(path_list):
if is_stdin(path): if is_stdin(path):
@ -155,4 +154,4 @@ def load_files(config, roots, path_list):
f" known are: {','.join(root_adders)}.", f" known are: {','.join(root_adders)}.",
pos=item.pos, pos=item.pos,
) )
adder(config, roots, expander, params_expander, contents, item.pos) adder(config, roots, contents, item.pos)

View File

@ -45,8 +45,6 @@ class Macro:
elements_name, elements_name,
config, config,
roots, roots,
expander,
params_expander,
data, data,
pos, pos,
): ):

View File

@ -33,7 +33,7 @@ class Project(GroupBase):
params: dict params: dict
@classmethod @classmethod
def add(cls, config, roots, expander, params_expander, data, pos): def add(cls, config, roots, data, pos):
d = data.copy() d = data.copy()
name = d.pop_required_loc_string("name") name = d.pop_required_loc_string("name")
defaults = d.pop_loc_string("defaults", None) defaults = d.pop_loc_string("defaults", None)

View File

@ -27,7 +27,7 @@ from six import PY2
from jenkins.plugins import PluginVersion from jenkins.plugins import PluginVersion
from jenkins_jobs.errors import JenkinsJobsException from jenkins_jobs.errors import JenkinsJobsException
from jenkins_jobs.expander import Expander, ParamsExpander from jenkins_jobs.expander import Expander, StringsOnlyExpander
from jenkins_jobs.yaml_objects import BaseYamlObject from jenkins_jobs.yaml_objects import BaseYamlObject
__all__ = ["ModuleRegistry"] __all__ = ["ModuleRegistry"]
@ -49,7 +49,7 @@ class ModuleRegistry(object):
self.masked_warned = {} self.masked_warned = {}
self._macros = {} self._macros = {}
self._expander = Expander(jjb_config) self._expander = Expander(jjb_config)
self._params_expander = ParamsExpander(jjb_config) self._str_expander = StringsOnlyExpander(jjb_config)
if plugins_list is None: if plugins_list is None:
self._plugin_version = {} self._plugin_version = {}
@ -290,20 +290,17 @@ class ModuleRegistry(object):
"component type that is masking an inbuilt " "component type that is masking an inbuilt "
"definition" % (name, component_type) "definition" % (name, component_type)
) )
# Expand macro strings only if at least one macro parameter is provided. if component_data is None:
if component_data: component_data = {}
expander = self._params_expander
else:
expander = self._expander
component_data = {} # It may be None.
expander_params = {**component_data, **(job_data or {})} expander_params = {**component_data, **(job_data or {})}
elements = macro.elements elements = macro.elements
if isinstance(elements, BaseYamlObject): if isinstance(elements, BaseYamlObject):
# Expand !j2-yaml element right below macro body. # Expand !j2-yaml tag if it is right below macro body.
elements = elements.expand(expander, expander_params) # But do not expand yaml tags inside it - they will be expanded later.
elements = elements.expand(self._str_expander, expander_params)
for b in elements: for b in elements:
try: try:
element = expander.expand(b, expander_params) element = self._expander.expand(b, expander_params)
except JenkinsJobsException as x: except JenkinsJobsException as x:
raise x.with_context( raise x.with_context(
f"While expanding macro {name!r}", f"While expanding macro {name!r}",

View File

@ -103,12 +103,16 @@ class NonTemplateRootMixin:
def top_level_generate_items(self): def top_level_generate_items(self):
try: try:
defaults = self._pick_defaults(self.defaults_name, merge_global=False) defaults = self._pick_defaults(self.defaults_name, merge_global=False)
item_params = LocDict.merge(
defaults.params,
self.params,
)
contents = LocDict.merge( contents = LocDict.merge(
defaults.contents, defaults.contents,
self.contents, self.contents,
pos=self.pos, pos=self.pos,
) )
expanded_contents = self._expand_contents(contents, self.params) expanded_contents = self._expand_contents(contents, item_params)
context = [Context(f"In {self}", self.pos)] context = [Context(f"In {self}", self.pos)]
yield JobViewData(expanded_contents, context) yield JobViewData(expanded_contents, context)
except JenkinsJobsException as x: except JenkinsJobsException as x:

View File

@ -14,6 +14,7 @@ from dataclasses import dataclass
from .errors import JenkinsJobsException from .errors import JenkinsJobsException
from .loc_loader import LocDict from .loc_loader import LocDict
from .expander import Expander
from .root_base import RootBase, NonTemplateRootMixin, TemplateRootMixin, Group from .root_base import RootBase, NonTemplateRootMixin, TemplateRootMixin, Group
from .defaults import split_contents_params, view_contents_keys from .defaults import split_contents_params, view_contents_keys
@ -23,7 +24,7 @@ class ViewBase(RootBase):
view_type: str view_type: str
@classmethod @classmethod
def from_dict(cls, config, roots, expander, data, pos): def from_dict(cls, config, roots, data, pos):
keep_descriptions = config.yamlparser["keep_descriptions"] keep_descriptions = config.yamlparser["keep_descriptions"]
d = data.copy() d = data.copy()
name = d.pop_required_loc_string("name") name = d.pop_required_loc_string("name")
@ -34,7 +35,7 @@ class ViewBase(RootBase):
contents, params = split_contents_params(d, view_contents_keys) contents, params = split_contents_params(d, view_contents_keys)
return cls( return cls(
roots.defaults, roots.defaults,
expander, Expander(config),
keep_descriptions, keep_descriptions,
id, id,
name, name,
@ -59,8 +60,8 @@ class ViewBase(RootBase):
class View(ViewBase, NonTemplateRootMixin): class View(ViewBase, NonTemplateRootMixin):
@classmethod @classmethod
def add(cls, config, roots, expander, param_expander, data, pos): def add(cls, config, roots, data, pos):
view = cls.from_dict(config, roots, expander, data, pos) view = cls.from_dict(config, roots, data, pos)
roots.assign(roots.views, view.id, view, "view") roots.assign(roots.views, view.id, view, "view")
def __str__(self): def __str__(self):
@ -69,8 +70,8 @@ class View(ViewBase, NonTemplateRootMixin):
class ViewTemplate(ViewBase, TemplateRootMixin): class ViewTemplate(ViewBase, TemplateRootMixin):
@classmethod @classmethod
def add(cls, config, roots, expander, params_expander, data, pos): def add(cls, config, roots, data, pos):
template = cls.from_dict(config, roots, params_expander, data, pos) template = cls.from_dict(config, roots, data, pos)
roots.assign(roots.view_templates, template.id, template, "view template") roots.assign(roots.view_templates, template.id, template, "view template")
def __str__(self): def __str__(self):
@ -83,7 +84,7 @@ class ViewGroup(Group):
_view_templates: dict _view_templates: dict
@classmethod @classmethod
def add(cls, config, roots, expander, params_expander, data, pos): def add(cls, config, roots, data, pos):
d = data.copy() d = data.copy()
name = d.pop_required_loc_string("name") name = d.pop_required_loc_string("name")
try: try:

View File

@ -83,40 +83,9 @@ filenames to be opened as one or more data blob, which should be read into
the calling yaml construct without any further parsing. Any data in a file the calling yaml construct without any further parsing. Any data in a file
included through this tag, will be treated as string data. included through this tag, will be treated as string data.
Examples: It will expand variables inside the file. If your file contains curly braces,
you should double them. Or, you can use tag ``!include-raw-escape``, which
.. literalinclude:: /../../tests/loader/fixtures/include-raw001-job.yaml does not substitute variables.
contents of include-raw001-hello-world.sh:
.. literalinclude::
/../../tests/loader/fixtures/include-raw001-hello-world.sh
contents of include-raw001-vars.sh:
.. literalinclude::
/../../tests/loader/fixtures/include-raw001-vars.sh
using a list of files:
.. literalinclude::
/../../tests/loader/fixtures/include-raw-multi001.yaml
The tag ``!include-raw-escape:`` treats the given string or list of strings as
filenames to be opened as one or more data blobs, which should be escaped
before being read in as string data. This allows job-templates to use this tag
to include scripts from files without needing to escape braces in the original
file.
.. warning::
When used as a macro ``!include-raw-escape:`` should only be used if
parameters are passed into the escaped file and you would like to escape
those parameters. If the file does not have any jjb parameters passed into
it then ``!include-raw:`` should be used instead otherwise you will run
into an interesting issue where ``include-raw-escape:`` actually adds
additional curly braces around existing curly braces. For example
${PROJECT} becomes ${{PROJECT}} which may break bash scripts.
Examples: Examples:
@ -259,12 +228,8 @@ class BaseYamlObject(metaclass=abc.ABCMeta):
@abc.abstractmethod @abc.abstractmethod
def expand(self, expander, params): def expand(self, expander, params):
"""Expand object but do not substitute template parameters"""
pass
def subst(self, expander, params):
"""Expand object and substitute template parameters""" """Expand object and substitute template parameters"""
return self.expand(expander, params) pass
def _find_file(self, rel_path, pos): def _find_file(self, rel_path, pos):
search_path = self._search_path search_path = self._search_path
@ -286,10 +251,6 @@ class BaseYamlObject(metaclass=abc.ABCMeta):
for idx, path in enumerate(path_list): for idx, path in enumerate(path_list):
yield self._expand_path(path, path_list.value_pos[idx], *args) yield self._expand_path(path, path_list.value_pos[idx], *args)
def _subst_path_list(self, path_list, *args):
for idx, path in enumerate(path_list):
yield self._subst_path(path, path_list.value_pos[idx], *args)
class J2BaseYamlObject(BaseYamlObject): class J2BaseYamlObject(BaseYamlObject):
def __init__(self, jjb_config, loader, pos): def __init__(self, jjb_config, loader, pos):
@ -438,19 +399,11 @@ class IncludeRawBase(IncludeBaseObject):
def expand(self, expander, params): def expand(self, expander, params):
return "\n".join(self._expand_path_list(self._path_list, params)) return "\n".join(self._expand_path_list(self._path_list, params))
def subst(self, expander, params):
return "\n".join(self._subst_path_list(self._path_list, params))
class IncludeRaw(IncludeRawBase): class IncludeRaw(IncludeRawBase):
yaml_tag = "!include-raw:" yaml_tag = "!include-raw:"
def _expand_path(self, rel_path_template, pos, params): def _expand_path(self, rel_path_template, pos, params):
rel_path = self._formatter.format(rel_path_template, **params)
full_path = self._find_file(rel_path, pos)
return full_path.read_text()
def _subst_path(self, rel_path_template, pos, params):
rel_path = self._formatter.format(rel_path_template, **params) rel_path = self._formatter.format(rel_path_template, **params)
full_path = self._find_file(rel_path, pos) full_path = self._find_file(rel_path, pos)
template = full_path.read_text() template = full_path.read_text()
@ -464,14 +417,6 @@ class IncludeRawEscape(IncludeRawBase):
yaml_tag = "!include-raw-escape:" yaml_tag = "!include-raw-escape:"
def _expand_path(self, rel_path_template, pos, params): def _expand_path(self, rel_path_template, pos, params):
rel_path = self._formatter.format(rel_path_template, **params)
full_path = self._find_file(rel_path, pos)
text = full_path.read_text()
# Backward compatibility:
# if used inside job or macro without parameters, curly braces are duplicated.
return text.replace("{", "{{").replace("}", "}}")
def _subst_path(self, rel_path_template, pos, params):
rel_path = self._formatter.format(rel_path_template, **params) rel_path = self._formatter.format(rel_path_template, **params)
full_path = self._find_file(rel_path, pos) full_path = self._find_file(rel_path, pos)
return full_path.read_text() return full_path.read_text()

View File

@ -2,6 +2,6 @@
name: test-job-include-raw-1 name: test-job-include-raw-1
builders: builders:
- shell: - shell:
!include-raw include-raw001-hello-world.sh !include-raw-escape include-raw001-hello-world.sh
- shell: - shell:
!include-raw include-raw001-vars.sh !include-raw-escape include-raw001-vars.sh

View File

@ -5,7 +5,7 @@
"shell": "#!/bin/bash\n#\n# Sample script showing how the yaml include-raw tag can be used\n# to inline scripts that are maintained outside of the jenkins\n# job yaml configuration.\n\necho \"hello world\"\n\nexit 0\n" "shell": "#!/bin/bash\n#\n# Sample script showing how the yaml include-raw tag can be used\n# to inline scripts that are maintained outside of the jenkins\n# job yaml configuration.\n\necho \"hello world\"\n\nexit 0\n"
}, },
{ {
"shell": "#!/bin/bash\n#\n# sample script to check that brackets aren't escaped\n# when using the include-raw application yaml tag\n\nVAR1=\"hello\"\nVAR2=\"world\"\nVAR3=\"${{VAR1}} ${{VAR2}}\"\n\n[[ -n \"${{VAR3}}\" ]] && {{\n # this next section is executed as one\n echo \"${{VAR3}}\"\n exit 0\n}}\n" "shell": "#!/bin/bash\n#\n# sample script to check that brackets aren't escaped\n# when using the include-raw application yaml tag\n\nVAR1=\"hello\"\nVAR2=\"world\"\nVAR3=\"${VAR1} ${VAR2}\"\n\n[[ -n \"${VAR3}\" ]] && {\n # this next section is executed as one\n echo \"${VAR3}\"\n exit 0\n}\n"
} }
], ],
"description": "<!-- Managed by Jenkins Job Builder -->", "description": "<!-- Managed by Jenkins Job Builder -->",

View File

@ -30,13 +30,13 @@ exit 0
VAR1=&quot;hello&quot; VAR1=&quot;hello&quot;
VAR2=&quot;world&quot; VAR2=&quot;world&quot;
VAR3=&quot;${{VAR1}} ${{VAR2}}&quot; VAR3=&quot;${VAR1} ${VAR2}&quot;
[[ -n &quot;${{VAR3}}&quot; ]] &amp;&amp; {{ [[ -n &quot;${VAR3}&quot; ]] &amp;&amp; {
# this next section is executed as one # this next section is executed as one
echo &quot;${{VAR3}}&quot; echo &quot;${VAR3}&quot;
exit 0 exit 0
}} }
</command> </command>
</hudson.tasks.Shell> </hudson.tasks.Shell>
</builders> </builders>

View File

@ -1,4 +1,4 @@
# Using include-raw-excape inside job cause double braces in included file, like: {{VAR1}}. # Using include-raw-escape inside job works the same way as inside job template.
- job: - job:
name: test-job-include-raw name: test-job-include-raw
builders: builders:

View File

@ -2,6 +2,6 @@
name: test-job-include-raw-1 name: test-job-include-raw-1
builders: builders:
- shell: - shell:
!include-raw: !include-raw-escape:
- include-raw001-hello-world.sh - include-raw001-hello-world.sh
- include-raw001-vars.sh - include-raw001-vars.sh

View File

@ -2,6 +2,6 @@
name: test-job-include-raw-1 name: test-job-include-raw-1
builders: builders:
- shell: - shell:
!include-raw: include-raw001-hello-world.sh !include-raw-escape: include-raw001-hello-world.sh
- shell: - shell:
!include-raw: include-raw001-vars.sh !include-raw-escape: include-raw001-vars.sh

View File

@ -11,7 +11,7 @@
- xenial-{bdate} - xenial-{bdate}
jobs: jobs:
- 'template-requiring-param-{os}': - 'template-requiring-param-{os}':
os: 'ubuntu-{flavour}' os: 'ubuntu-{flavor}'
- job-template: - job-template:
name: 'template-requiring-param-{os}' name: 'template-requiring-param-{os}'

View File

@ -0,0 +1,18 @@
macro_with_null_params.yaml:14:3: In project 'sample-project'
- project:
^
macro_with_null_params.yaml:17:9: Defined here
- sample-job
^
macro_with_null_params.yaml:8:3: In job template 'sample-job'
- job-template:
^
macro_with_null_params.yaml:12:9: While expanding builder macro call 'sample-macro'
- sample-macro:
^
macro_with_null_params.yaml:1:3: While expanding macro 'sample-macro'
- builder:
^
macro_with_null_params.yaml:6:17: While formatting string 'echo {hello}': Missing parameter: 'hello'
- shell: 'echo {hello}'
^

View File

@ -2,13 +2,13 @@
name: sample-macro name: sample-macro
builders: builders:
# Add parameter to check if macro behaves the same way as if no params were provided. # Add parameter to check if macro behaves the same way as if no params were provided.
# That is, it should not be expanded. # That is, it should try to expand the parameter.
- shell: 'echo {hello}' - shell: 'echo {hello}'
- job-template: - job-template:
name: sample-job name: sample-job
builders: builders:
# Place colon but define no parameters. # Place colon but define no parameters (parameters are null instead of dict).
- sample-macro: - sample-macro:
- project: - project:

View File

@ -5,13 +5,13 @@ recursive_parameter.yaml:7:3: In job template 'sample-job-{param_1}'
- job-template: - job-template:
^ ^
recursive_parameter.yaml:5:9: Used by param_1 recursive_parameter.yaml:5:9: Used by param_1
- '{param_2}-at-project' - '{param_2}-at-globals'
^ ^
recursive_parameter.yaml:9:14: Used by param_2 recursive_parameter.yaml:9:14: Used by param_2
param_2: '{param_3}-at-template' param_2: '{param_3}-at-template'
^ ^
recursive_parameter.yaml:13:14: Used by param_3 recursive_parameter.yaml:13:14: Used by param_3
param_3: '{param_4}-at-globals' param_3: '{param_4}-at-project'
^ ^
recursive_parameter.yaml:16:20: Used by param_4 recursive_parameter.yaml:16:20: Used by param_4
param_4: '{param_1}-at-job-spec' param_4: '{param_1}-at-job-spec'
@ -20,5 +20,5 @@ recursive_parameter.yaml:3:5: While expanding 'param_1'
param_1: param_1:
^ ^
recursive_parameter.yaml:5:9: Recursive parameters usage: param_1 <- param_2 <- param_3 <- param_4 recursive_parameter.yaml:5:9: Recursive parameters usage: param_1 <- param_2 <- param_3 <- param_4
- '{param_2}-at-project' - '{param_2}-at-globals'
^ ^

View File

@ -2,7 +2,7 @@
name: global name: global
param_1: param_1:
- param_1_value_1 - param_1_value_1
- '{param_2}-at-project' - '{param_2}-at-globals'
- job-template: - job-template:
name: 'sample-job-{param_1}' name: 'sample-job-{param_1}'
@ -10,7 +10,7 @@
- project: - project:
name: sample-project name: sample-project
param_3: '{param_4}-at-globals' param_3: '{param_4}-at-project'
jobs: jobs:
- 'sample-job-{param_1}': - 'sample-job-{param_1}':
param_4: '{param_1}-at-job-spec' param_4: '{param_1}-at-job-spec'

View File

@ -9,8 +9,8 @@
name: builder-without-params name: builder-without-params
builders: builders:
- shell: | - shell: |
echo Should not be expanded: {param} echo Should not be expanded: {{param}}
- shell: !include-raw: job-and-macro-expansions.yaml.no-expand.inc - shell: !include-raw-escape: job-and-macro-expansions.yaml.no-expand.inc
- builder: - builder:
name: builder-with-params name: builder-with-params
@ -27,8 +27,8 @@
display-name: sample-job display-name: sample-job
builders: builders:
- shell: | - shell: |
echo Should not be expanded: {param} echo Should not be expanded: {{param}}
- shell: !include-raw: job-and-macro-expansions.yaml.no-expand.inc - shell: !include-raw-escape: job-and-macro-expansions.yaml.no-expand.inc
- job-template: - job-template:
name: sample-job-template name: sample-job-template

View File

@ -11,7 +11,7 @@
<scm class="hudson.scm.NullSCM"/> <scm class="hudson.scm.NullSCM"/>
<builders> <builders>
<hudson.tasks.Shell> <hudson.tasks.Shell>
<command>echo Build arch {arch}.</command> <command>echo Build arch i386.</command>
</hudson.tasks.Shell> </hudson.tasks.Shell>
</builders> </builders>
<publishers/> <publishers/>

View File

@ -1,4 +1,4 @@
# Variables in jobs are not expanded. # Variables in jobs are expanded using defaults.
- defaults: - defaults:
name: global name: global

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<project>
<actions/>
<description>&lt;!-- Managed by Jenkins Job Builder --&gt;</description>
<keepDependencies>false</keepDependencies>
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
<concurrentBuild>false</concurrentBuild>
<canRoam>true</canRoam>
<properties/>
<scm class="hudson.scm.NullSCM"/>
<builders>
<hudson.tasks.Shell>
<command>echo {hello}</command>
</hudson.tasks.Shell>
</builders>
<publishers/>
<buildWrappers/>
</project>

View File

@ -4,6 +4,6 @@
dsl: | dsl: |
build("job1") build("job1")
parallel ( parallel (
{ build("job2a") }, {{ build("job2a") }},
{ build("job2b") } {{ build("job2b") }}
) )

View File

@ -5,7 +5,7 @@
build job: "job1" build job: "job1"
parallel [ parallel [
2a: build job: "job2a", 2a: build job: "job2a",
2b: node "dummynode" { 2b: node "dummynode" {{
sh "echo I'm alive!" sh "echo I'm alive!"
} }}
] ]

View File

@ -5,7 +5,7 @@
build job: "job1" build job: "job1"
parallel [ parallel [
2a: build job: "job2a", 2a: build job: "job2a",
2b: node "dummynode" { 2b: node "dummynode" {{
sh "echo I'm alive!" sh "echo I'm alive!"
} }}
] ]

View File

@ -25,7 +25,7 @@
- timed: "H 14 * * *" - timed: "H 14 * * *"
builders: builders:
- shell: !include-raw: regression-2006254.inc - shell: !include-raw-escape: regression-2006254.inc
parameters: parameters:
- bool: - bool:

View File

@ -13,7 +13,7 @@
<hudson.model.StringParameterDefinition> <hudson.model.StringParameterDefinition>
<name>PARAM_1</name> <name>PARAM_1</name>
<description/> <description/>
<defaultValue>{default|my_default}</defaultValue> <defaultValue>my_default</defaultValue>
<trim>false</trim> <trim>false</trim>
</hudson.model.StringParameterDefinition> </hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition> <hudson.model.StringParameterDefinition>

View File

@ -1,5 +1,5 @@
# https://storyboard.openstack.org/#!/story/2010535 # https://storyboard.openstack.org/#!/story/2010535
# Bug: JJB doesn't expand macro in case of usage without arguments # Fixed: Bug: JJB doesn't expand macro in case of usage without arguments
# String templates in macro calls without parameters are NOT expanded. # String templates in macro calls without parameters are NOT expanded.
# Jinja2 templates in macro calls without parameters ARE expanded. # Jinja2 templates in macro calls without parameters ARE expanded.

View File

@ -12,7 +12,7 @@
publishers: publishers:
- trigger-parameterized-builds: - trigger-parameterized-builds:
- project: first_job - project: first_job
predefined-parameters: BUILD_NUM=${BUILD_NUMBER} predefined-parameters: BUILD_NUM=${{BUILD_NUMBER}}
property-file: default_version.prop property-file: default_version.prop
current-parameters: true current-parameters: true
- project: second_job - project: second_job
@ -25,7 +25,7 @@
publishers: publishers:
- trigger-parameterized-builds: - trigger-parameterized-builds:
- project: 1.2_first_job - project: 1.2_first_job
predefined-parameters: BUILD_NUM=${BUILD_NUMBER} predefined-parameters: BUILD_NUM=${{BUILD_NUMBER}}
current-parameters: true current-parameters: true
property-file: version.prop property-file: version.prop
- project: 1.2_second_job - project: 1.2_second_job