blueprints: add !Context to lookup things from instance context
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
0227ba90bb
commit
dcbf106daa
|
@ -30,6 +30,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"context": {
|
||||||
|
"$id": "#/properties/context",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
"entries": {
|
"entries": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
|
|
@ -100,6 +100,7 @@ class Blueprint:
|
||||||
entries: list[BlueprintEntry] = field(default_factory=list)
|
entries: list[BlueprintEntry] = field(default_factory=list)
|
||||||
|
|
||||||
metadata: Optional[BlueprintMetadata] = field(default=None)
|
metadata: Optional[BlueprintMetadata] = field(default=None)
|
||||||
|
context: Optional[dict] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
class YAMLTag:
|
class YAMLTag:
|
||||||
|
@ -136,6 +137,29 @@ class KeyOf(YAMLTag):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Context(YAMLTag):
|
||||||
|
"""Lookup key from instance context"""
|
||||||
|
|
||||||
|
key: str
|
||||||
|
default: Optional[Any]
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def __init__(self, loader: "BlueprintLoader", node: ScalarNode | SequenceNode) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.default = None
|
||||||
|
if isinstance(node, ScalarNode):
|
||||||
|
self.key = node.value
|
||||||
|
if isinstance(node, SequenceNode):
|
||||||
|
self.key = node.value[0].value
|
||||||
|
self.default = node.value[1].value
|
||||||
|
|
||||||
|
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
|
||||||
|
value = self.default
|
||||||
|
if self.key in blueprint.context:
|
||||||
|
value = blueprint.context[self.key]
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class Find(YAMLTag):
|
class Find(YAMLTag):
|
||||||
"""Find any object"""
|
"""Find any object"""
|
||||||
|
|
||||||
|
@ -189,6 +213,7 @@ class BlueprintLoader(SafeLoader):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.add_constructor("!KeyOf", KeyOf)
|
self.add_constructor("!KeyOf", KeyOf)
|
||||||
self.add_constructor("!Find", Find)
|
self.add_constructor("!Find", Find)
|
||||||
|
self.add_constructor("!Context", Context)
|
||||||
|
|
||||||
|
|
||||||
class EntryInvalidError(SentryIgnoredException):
|
class EntryInvalidError(SentryIgnoredException):
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
"""Blueprint importer"""
|
"""Blueprint importer"""
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
|
|
||||||
from dacite import from_dict
|
from dacite import from_dict
|
||||||
from dacite.exceptions import DaciteError
|
from dacite.exceptions import DaciteError
|
||||||
|
from deepmerge import always_merger
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
|
@ -75,7 +76,7 @@ class Importer:
|
||||||
|
|
||||||
logger: BoundLogger
|
logger: BoundLogger
|
||||||
|
|
||||||
def __init__(self, yaml_input: str):
|
def __init__(self, yaml_input: str, context: Optional[dict] = None):
|
||||||
self.__pk_map: dict[Any, Model] = {}
|
self.__pk_map: dict[Any, Model] = {}
|
||||||
self.logger = get_logger()
|
self.logger = get_logger()
|
||||||
import_dict = load(yaml_input, BlueprintLoader)
|
import_dict = load(yaml_input, BlueprintLoader)
|
||||||
|
@ -83,6 +84,11 @@ class Importer:
|
||||||
self.__import = from_dict(Blueprint, import_dict)
|
self.__import = from_dict(Blueprint, import_dict)
|
||||||
except DaciteError as exc:
|
except DaciteError as exc:
|
||||||
raise EntryInvalidError from exc
|
raise EntryInvalidError from exc
|
||||||
|
context = {}
|
||||||
|
always_merger.merge(context, self.__import.context)
|
||||||
|
if context:
|
||||||
|
always_merger.merge(context, context)
|
||||||
|
self.__import.context = context
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def blueprint(self) -> Blueprint:
|
def blueprint(self) -> Blueprint:
|
||||||
|
|
|
@ -121,7 +121,7 @@ def apply_blueprint(self: MonitoredTask, instance_pk: str):
|
||||||
full_path = Path(CONFIG.y("blueprints_dir")).joinpath(Path(instance.path))
|
full_path = Path(CONFIG.y("blueprints_dir")).joinpath(Path(instance.path))
|
||||||
file_hash = sha512(full_path.read_bytes()).hexdigest()
|
file_hash = sha512(full_path.read_bytes()).hexdigest()
|
||||||
with open(full_path, "r", encoding="utf-8") as blueprint_file:
|
with open(full_path, "r", encoding="utf-8") as blueprint_file:
|
||||||
importer = Importer(blueprint_file.read())
|
importer = Importer(blueprint_file.read(), instance.context)
|
||||||
valid, logs = importer.validate()
|
valid, logs = importer.validate()
|
||||||
if not valid:
|
if not valid:
|
||||||
instance.status = BlueprintInstanceStatus.ERROR
|
instance.status = BlueprintInstanceStatus.ERROR
|
||||||
|
|
|
@ -30,6 +30,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"context": {
|
||||||
|
"$id": "#/properties/context",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
"entries": {
|
"entries": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
|
|
@ -21,6 +21,12 @@ Example: `configure_flow: !Find [authentik_flows.flow, [slug, default-password-c
|
||||||
Looks up any model and resolves to the the matches' primary key.
|
Looks up any model and resolves to the the matches' primary key.
|
||||||
First argument is the model to be queried, remaining arguments are expected to be pairs of key=value pairs to query for.
|
First argument is the model to be queried, remaining arguments are expected to be pairs of key=value pairs to query for.
|
||||||
|
|
||||||
|
#### `!Context`
|
||||||
|
|
||||||
|
Example: `configure_flow: !Context foo`
|
||||||
|
|
||||||
|
Find values from the context. Can optionally be called with a default like `!Context [foo, default-value]`.
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -32,6 +38,9 @@ metadata:
|
||||||
labels:
|
labels:
|
||||||
foo: bar
|
foo: bar
|
||||||
name: example-blueprint
|
name: example-blueprint
|
||||||
|
# Optional default context, instance context is merged over this.
|
||||||
|
context:
|
||||||
|
foo: bar
|
||||||
# List of entries (required)
|
# List of entries (required)
|
||||||
entries:
|
entries:
|
||||||
- # Model in app.model notation, possibilities are listed in the schema (required)
|
- # Model in app.model notation, possibilities are listed in the schema (required)
|
||||||
|
|
Reference in a new issue