debian-forge/osbuild/formats/v1.py
Christian Kellner 945914b195 osbuild: introduce Manifest class
The 'Manifest' class represents what to build and the necessary
sources to do so. For now thus it is just a combination of the
pipeline the source options.
2021-01-09 18:09:47 +01:00

127 lines
3.9 KiB
Python

# Version 1 of the manifest description
from typing import Dict
from osbuild.meta import Index, ValidationResult
from ..pipeline import Manifest, Pipeline, detect_host_runner
def describe(pipeline: Pipeline, *, with_id=False) -> Dict:
"""Create the manifest description for the pipeline"""
def describe_stage(stage):
description = {"name": stage.name}
if stage.options:
description["options"] = stage.options
if with_id:
description["id"] = stage.id
return description
description = {}
if pipeline.build:
build = pipeline.build
description["build"] = {
"pipeline": describe(build, with_id=with_id),
"runner": pipeline.runner
}
if pipeline.stages:
stages = [describe_stage(s) for s in pipeline.stages]
description["stages"] = stages
if pipeline.assembler:
assembler = describe_stage(pipeline.assembler)
description["assembler"] = assembler
return description
def load_build(description: Dict, sources_options: Dict):
pipeline = description.get("pipeline")
if pipeline:
build_pipeline = load_pipeline(pipeline, sources_options)
else:
build_pipeline = None
return build_pipeline, description["runner"]
def load_pipeline(description: Dict, sources_options: Dict) -> Pipeline:
build = description.get("build")
if build:
build_pipeline, runner = load_build(build, sources_options)
else:
build_pipeline, runner = None, detect_host_runner()
pipeline = Pipeline(runner, build_pipeline)
for s in description.get("stages", []):
pipeline.add_stage(s["name"], sources_options, s.get("options", {}))
a = description.get("assembler")
if a:
pipeline.set_assembler(a["name"], a.get("options", {}))
return pipeline
def load(description: Dict) -> Manifest:
"""Load a manifest description"""
pipeline = description.get("pipeline", {})
sources = description.get("sources", {})
pipeline = load_pipeline(pipeline, sources)
manifest = Manifest(pipeline, sources)
return manifest
def validate(manifest: Dict, index: Index) -> ValidationResult:
"""Validate a OSBuild manifest
This function will validate a OSBuild manifest, including
all its stages and assembler and build manifests. It will
try to validate as much as possible and not stop on errors.
The result is a `ValidationResult` object that can be used
to check the overall validation status and iterate all the
individual validation errors.
"""
schema = index.get_schema("Manifest")
result = schema.validate(manifest)
# main pipeline
pipeline = manifest.get("pipeline", {})
# recursively validate the build pipeline as a "normal"
# pipeline in order to validate its stages and assembler
# options; for this it is being re-parented in a new plain
# {"pipeline": ...} dictionary. NB: Any nested structural
# errors might be detected twice, but de-duplicated by the
# `ValidationResult.merge` call
build = pipeline.get("build", {}).get("pipeline")
if build:
res = validate({"pipeline": build}, index=index)
result.merge(res, path=["pipeline", "build"])
stages = pipeline.get("stages", [])
for i, stage in enumerate(stages):
name = stage["name"]
schema = index.get_schema("Stage", name)
res = schema.validate(stage)
result.merge(res, path=["pipeline", "stages", i])
asm = pipeline.get("assembler", {})
if asm:
name = asm["name"]
schema = index.get_schema("Assembler", name)
res = schema.validate(asm)
result.merge(res, path=["pipeline", "assembler"])
# sources
sources = manifest.get("sources", {})
for name, source in sources.items():
schema = index.get_schema("Source", name)
res = schema.validate(source)
result.merge(res, path=["sources", name])
return result