osbuild-mpp: Allow use of mpp-* operations for stages

This mergest the handling of process_stages() and process_format() into
just one process_format(), which incrementally tracks the call stack
of the formating, which allows it to detect when it is hitting a stage
and can call _process_stage().

This means it is possible to mix things like mpp-if and mpp-join with
stages.

For example, you can do complex combinations like:

pipelines:
- name: rootfs
  stages:
    mpp-join:
      - - type: org.ostree.foo
        - mpp-if: use_bar
          then:
            type: org.osbuild.bar
      - mpp-if: use_extra_stages
        then:
          mpp-eval: extra_stages

This is particularly useful if you included something and you want
to mpp-join something that was set in a variable.
This commit is contained in:
Alexander Larsson 2022-08-04 17:55:31 +02:00 committed by Christian Kellner
parent ed99aa4bfa
commit fc2697927a

View file

@ -959,6 +959,7 @@ class ManifestFile:
self.version = version
self.sources = element_enter(self.root, "sources", {})
self.source_urls = {}
self.format_stack = []
self.solver_factory = None
self.vars = default_vars.copy()
@ -1098,6 +1099,18 @@ class ManifestFile:
else:
return fakeroot[0], False
def _format_dict_node(self, node, stack):
if len(stack) > 0:
parent_node = stack[-1][0]
parent_key = stack[-1][1]
else:
parent_node = None
parent_key = None
if parent_key == "stages":
pipeline_name = self.get_pipeline_name(parent_node)
self._process_stage(node, pipeline_name)
def _process_format(self, node):
def _is_format(node):
if not isinstance(node, dict):
@ -1162,7 +1175,11 @@ class ManifestFile:
return res, False
if isinstance(node, dict):
self._format_dict_node(node, self.format_stack)
for key in list(node.keys()):
self.format_stack.append( (node, key) )
value = node[key]
if _is_format(value):
val, remove = _eval_format(value, self.get_vars())
@ -1172,6 +1189,8 @@ class ManifestFile:
node[key] = val
else:
self._process_format(value)
self.format_stack.pop()
if isinstance(node, list):
to_remove = []
for i, value in enumerate(node):
@ -1200,6 +1219,9 @@ class ManifestFile:
name = desc.get("id", "image")
self.vars[name] = Image.from_dict(desc)
def get_pipeline_name(self, node):
return node.get("name", "unknown")
def _process_stage(self, stage, pipeline_name):
self._process_depsolve(stage, pipeline_name)
self._process_embed_files(stage)
@ -1270,24 +1292,22 @@ class ManifestFileV1(ManifestFile):
packages += checksums
def process_stages(self, pipeline=None, depth=0):
if pipeline is None:
pipeline = self.pipeline
def get_pipeline_name(self, node):
if self.pipeline == node:
return "stages"
if depth == 0:
pipeline_name = "stages"
elif depth == 1:
pipeline_name = "build"
else:
pipeline_name = "build" + str(depth)
build = self.pipeline.get("build", {}).get("pipeline")
if build == node:
return "build"
stages = element_enter(pipeline, "stages", [])
for stage in stages:
self._process_stage(stage, pipeline_name)
build = pipeline.get("build")
if build:
if "pipeline" in build:
self.process_stages(build["pipeline"], depth+1)
depth = 1
while build:
build = build.get("build", {}).get("pipeline")
depth = depth + 1
if build == node:
return "build" + str(depth)
return "unknown"
def _process_embed_files(self, stage):
"Embedding files is not supported for v1 manifests"
@ -1374,13 +1394,6 @@ class ManifestFileV2(ManifestFile):
for checksum in checksums:
refs[checksum] = {}
def process_stages(self):
for pipeline in self.pipelines:
name = pipeline.get("name", "")
stages = element_enter(pipeline, "stages", [])
for stage in stages:
self._process_stage(stage, name)
def _process_embed_files(self, stage):
class Embedded(collections.namedtuple("Embedded", ["id", "checksum"])):
@ -1547,11 +1560,9 @@ def main():
with tempfile.TemporaryDirectory() as persistdir:
m.solver_factory = DepSolverFactory(args.cachedir, persistdir)
m.process_stages()
m.process_format()
m.solver_factory = None
m.process_format()
with sys.stdout if args.dst == "-" else open(args.dst, "w") as f:
m.write(f, args.sort_keys)