input: add references and origin

Currently all options for inputs are totally opaque to osbuild
itself. This is neat from a seperation of concerns point of view
but has one major downside: osbuild can not verify the integrity
of the pipeline graph, i.e. if all inputs that need pipelines or
sources do indeed exists. Therefore intrdouce two generic fields
for inputs: `origin` and `references`. The former can either be
a source or a pipeline. The latter is an array of identifiers or
a dictionary where the keys are the identifiers and the values
are additional options for that id. The identifiers then refer
to either resources obtained via a source or a pipeline that has
already been built.
This commit is contained in:
Christian Kellner 2021-01-26 14:13:11 +00:00
parent f450338809
commit eb1d17d8ac
3 changed files with 61 additions and 24 deletions

View file

@ -2,8 +2,10 @@
""" """
Tree inputs Tree inputs
Resolve the given pipeline `id` to a path and return that. If Open the tree produced by the pipeline supplied via the
`id` is `null` or the empty string it returns an empty tree. first and only entry in `references`. The tree is opened
in read only mode. If the id is `null` or the empty
string it returns an empty tree.
""" """
@ -15,30 +17,53 @@ from osbuild.objectstore import StoreClient
SCHEMA = """ SCHEMA = """
"additionalProperties": false, "additionalProperties": false,
"required": ["pipeline"], "required": ["origin", "references"],
"properties": { "properties": {
"pipeline": { "origin": {
"description": "The Pipeline that built the desired tree", "description": "The origin of the input (must be 'org.osbuild.pipeline')",
"type": "object", "type": "string",
"required": ["id"], "enum": ["org.osbuild.pipeline"]
"additionalProperties": false, },
"properties": { "references": {
"id": { "description": "Exactly one pipeline identifier to ues as tree input",
"description": "Identifier for the pipeline", "oneOf": [{
"type": "array",
"additionalItems": false,
"items": [{
"type": "string" "type": "string"
} }]
} }, {
"type": "object",
"additionalProperties": false,
"patternProperties": {
".*": {
"type": "object",
"additionalProperties": false
}
},
"minProperties": 1,
"maxProperties": 1
}]
} }
} }
""" """
def error(msg):
json.dump({"error": msg}, sys.stdout)
sys.exit(1)
def main(): def main():
args = json.load(sys.stdin) args = json.load(sys.stdin)
options = args["options"] refs = args["refs"]
# input verification *must* have been done via schema
# verification. It is expected that origin is a pipeline
# and we have exactly one reference, i.e. a pipeline id
pid, _ = refs.popitem()
store = StoreClient(connect_to=args["api"]["store"]) store = StoreClient(connect_to=args["api"]["store"])
pid = options["pipeline"]["id"]
if not pid: if not pid:
path = store.mkdtemp(prefix="empty") path = store.mkdtemp(prefix="empty")
@ -46,8 +71,7 @@ def main():
path = store.read_tree(pid) path = store.read_tree(pid)
if not path: if not path:
json.dump({"error": "Could find target"}, sys.stdout) error(f"Could not find pipeline with id '{pid}'")
return 1
json.dump({"path": path}, sys.stdout) json.dump({"path": path}, sys.stdout)
return 0 return 0

View file

@ -64,10 +64,9 @@ def load_assembler(description: Dict, index: Index, manifest: Manifest):
stage = pipeline.add_stage(info, options, {}) stage = pipeline.add_stage(info, options, {})
info = index.get_module_info("Input", "org.osbuild.tree") info = index.get_module_info("Input", "org.osbuild.tree")
stage.inputs = { ip = Input(info, "org.osbuild.pipeline", {})
"tree": Input(info, {"pipeline": {"id": base}}) ip.add_reference(base)
} stage.inputs = {"tree": ip}
return pipeline return pipeline

View file

@ -23,7 +23,7 @@ import json
import os import os
import subprocess import subprocess
from typing import Dict, Tuple from typing import Dict, Optional, Tuple
from .meta import ModuleInfo from .meta import ModuleInfo
from .objectstore import StoreServer from .objectstore import StoreServer
@ -34,14 +34,22 @@ class Input:
A single input with its corresponding options. A single input with its corresponding options.
""" """
def __init__(self, info: ModuleInfo, options: Dict): def __init__(self, info: ModuleInfo, origin: str, options: Dict):
self.info = info self.info = info
self.origin = origin
self.refs = {}
self.options = options or {} self.options = options or {}
self.id = self.calc_id() self.id = self.calc_id()
def add_reference(self, ref, options: Optional[Dict] = None):
self.refs[ref] = options or {}
self.id = self.calc_id()
def calc_id(self): def calc_id(self):
m = hashlib.sha256() m = hashlib.sha256()
m.update(json.dumps(self.name, sort_keys=True).encode()) m.update(json.dumps(self.name, sort_keys=True).encode())
m.update(json.dumps(self.origin, sort_keys=True).encode())
m.update(json.dumps(self.refs, sort_keys=True).encode())
m.update(json.dumps(self.options, sort_keys=True).encode()) m.update(json.dumps(self.options, sort_keys=True).encode())
return m.hexdigest() return m.hexdigest()
@ -52,8 +60,14 @@ class Input:
def run(self, storeapi: StoreServer) -> Tuple[str, Dict]: def run(self, storeapi: StoreServer) -> Tuple[str, Dict]:
name = self.info.name name = self.info.name
msg = { msg = {
# mandatory bits
"origin": self.origin,
"refs": self.refs,
# global options
"options": self.options, "options": self.options,
"origin": name,
# API endpoints
"api": { "api": {
"store": storeapi.socket_address "store": storeapi.socket_address
} }