jenkins-job-builder/jenkins_jobs/loader.py
Vsevolod Fedorov 18efe5066b 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
2024-01-19 12:31:42 +03:00

158 lines
5.2 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import io
import logging
from functools import partial
from .errors import JenkinsJobsException
from .loc_loader import LocLoader
from .yaml_objects import BaseYamlObject
from .expander import Expander, deprecated_yaml_tags, yaml_classes_list
from .roots import root_adders
logger = logging.getLogger(__name__)
class Loader(LocLoader):
@classmethod
def empty(cls, jjb_config):
return cls(io.StringIO(), jjb_config)
def __init__(
self, stream, jjb_config, source_path=None, source_dir=None, anchors=None
):
super().__init__(stream, source_path)
self.jjb_config = jjb_config
self.source_path = source_path
self.source_dir = source_dir
self._retain_anchors = jjb_config.yamlparser["retain_anchors"]
if anchors:
# Override default set by super class.
self.anchors = 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
def _with_stream(self, stream, source_path, source_dir):
return Loader(stream, self.jjb_config, source_path, source_dir, self.anchors)
def load_fp(self, fp):
return self.load(fp)
def load_path(self, path):
return self.load(path.read_text(), source_path=path, source_dir=path.parent)
def load(self, stream, source_path=None, source_dir=None):
loader = self._with_stream(stream, source_path, source_dir)
try:
return loader.get_single_data()
finally:
loader.dispose()
if self._retain_anchors:
self.anchors.update(loader.anchors)
def load_deprecated_yaml(tag, cls, loader, node):
logger.warning("Tag %r is deprecated, switch to using %r", tag, cls.yaml_tag)
return cls.from_yaml(loader, node)
for cls in yaml_classes_list:
Loader.add_constructor(cls.yaml_tag, cls.from_yaml)
for tag, cls in deprecated_yaml_tags:
Loader.add_constructor(tag, partial(load_deprecated_yaml, tag, cls))
def is_stdin(path):
return hasattr(path, "read")
def enum_expanded_paths(path_list):
visited_set = set()
def real(path):
real_path = path.resolve()
if real_path in visited_set:
logger.warning(
"File '%s' is already added as '%s'; ignoring reference to avoid"
" duplicating YAML definitions.",
path,
real_path,
)
else:
yield real_path
visited_set.add(real_path)
for path in path_list:
if is_stdin(path):
yield path
elif path.is_dir():
for p in path.iterdir():
if p.suffix in {".yml", ".yaml"}:
yield from real(p)
else:
yield from real(path)
def load_files(config, roots, path_list):
expander = Expander(config)
loader = Loader.empty(config)
for path in enum_expanded_paths(path_list):
if is_stdin(path):
data = loader.load_fp(path)
else:
data = loader.load_path(path)
if data is None:
continue
if not isinstance(data, list):
raise JenkinsJobsException(
f"The topmost collection must be a list, but is: {data}",
pos=data.pos,
)
for idx, item in enumerate(data):
if not isinstance(item, dict):
raise JenkinsJobsException(
f"Topmost list should contain single-item dict,"
f" not a {type(item)}. Missing indent?",
pos=data.value_pos[idx],
)
if len(item) != 1:
raise JenkinsJobsException(
f"Topmost dict should be single-item,"
f" but have keys {list(item.keys())}. Missing indent?",
pos=item.pos,
)
kind, contents = next(iter(item.items()))
if kind.startswith("_"):
continue
if isinstance(contents, BaseYamlObject):
contents = contents.expand(expander, params={})
try:
adder = root_adders[kind]
except KeyError:
raise JenkinsJobsException(
f"Unknown topmost element type : {kind!r};"
f" known are: {','.join(root_adders)}.",
pos=item.pos,
)
adder(config, roots, contents, item.pos)