blueprints: add `!If` tag (#4264)

* Added \!If tag

* Fix typo

* Removed trailing whitespace

Signed-off-by: sdimovv <36302090+sdimovv@users.noreply.github.com>

* format blueprint fixtures

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

Signed-off-by: sdimovv <36302090+sdimovv@users.noreply.github.com>
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
sdimovv 2022-12-26 15:20:22 +00:00 committed by GitHub
parent 3eecc76717
commit 8f3579ba45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 239 additions and 104 deletions

View File

@ -12,8 +12,13 @@ entries:
provider_type: github provider_type: github
consumer_key: !Env foo consumer_key: !Env foo
consumer_secret: !Env [bar, baz] consumer_secret: !Env [bar, baz]
authentication_flow: !Find [authentik_flows.Flow, [slug, default-source-authentication]] authentication_flow:
enrollment_flow: !Find [authentik_flows.Flow, [slug, default-source-enrollment]] !Find [
authentik_flows.Flow,
[slug, default-source-authentication],
]
enrollment_flow:
!Find [authentik_flows.Flow, [slug, default-source-enrollment]]
- attrs: - attrs:
expression: return True expression: return True
identifiers: identifiers:
@ -22,15 +27,70 @@ entries:
model: authentik_policies_expression.expressionpolicy model: authentik_policies_expression.expressionpolicy
- attrs: - attrs:
attributes: attributes:
policy_pk1: !Format ["%s-%s", !Find [authentik_policies_expression.expressionpolicy, [!Context policy_property, !Context policy_property_value], [expression, return True]], suffix] policy_pk1:
!Format [
"%s-%s",
!Find [
authentik_policies_expression.expressionpolicy,
[
!Context policy_property,
!Context policy_property_value,
],
[expression, return True],
],
suffix,
]
policy_pk2: !Format ["%s-%s", !KeyOf policy, suffix] policy_pk2: !Format ["%s-%s", !KeyOf policy, suffix]
boolAnd: !Condition [AND, !Context foo, !Format ["%s", "a_string"], 1] boolAnd:
boolNand: !Condition [NAND, !Context foo, !Format ["%s", "a_string"], 1] !Condition [AND, !Context foo, !Format ["%s", "a_string"], 1]
boolOr: !Condition [OR, !Context foo, !Format ["%s", "a_string"], null] boolNand:
boolNor: !Condition [NOR, !Context foo, !Format ["%s", "a_string"], null] !Condition [NAND, !Context foo, !Format ["%s", "a_string"], 1]
boolXor: !Condition [XOR, !Context foo, !Format ["%s", "a_string"], 1] boolOr:
boolXnor: !Condition [XNOR, !Context foo, !Format ["%s", "a_string"], 1] !Condition [
boolComplex: !Condition [XNOR, !Condition [AND, !Context non_existing], !Condition [NOR, a string], !Condition [XOR, null]] OR,
!Context foo,
!Format ["%s", "a_string"],
null,
]
boolNor:
!Condition [
NOR,
!Context foo,
!Format ["%s", "a_string"],
null,
]
boolXor:
!Condition [XOR, !Context foo, !Format ["%s", "a_string"], 1]
boolXnor:
!Condition [XNOR, !Context foo, !Format ["%s", "a_string"], 1]
boolComplex:
!Condition [
XNOR,
!Condition [AND, !Context non_existing],
!Condition [NOR, a string],
!Condition [XOR, null],
]
if_true_complex:
!If [
true,
{
dictionary:
{
with: { keys: "and_values" },
and_nested_custom_tags:
!Format ["foo-%s", !Context foo],
},
},
null,
]
if_false_complex:
!If [
!Condition [AND, false],
null,
[list, with, items, !Format ["foo-%s", !Context foo]],
]
if_true_simple: !If [!Context foo, true, text]
if_false_simple: !If [null, false, 2]
identifiers: identifiers:
name: test name: test
conditions: conditions:

View File

@ -153,6 +153,15 @@ class TestBlueprintsV1(TransactionTestCase):
"boolXor": True, "boolXor": True,
"boolXnor": False, "boolXnor": False,
"boolComplex": True, "boolComplex": True,
"if_true_complex": {
"dictionary": {
"with": {"keys": "and_values"},
"and_nested_custom_tags": "foo-bar",
}
},
"if_false_complex": ["list", "with", "items", "foo-bar"],
"if_true_simple": True,
"if_false_simple": 2,
} }
) )
) )

View File

@ -312,6 +312,35 @@ class Condition(YAMLTag):
raise EntryInvalidError(exc) raise EntryInvalidError(exc)
class If(YAMLTag):
"""Select YAML to use based on condition"""
condition: Any
when_true: Any
when_false: Any
# pylint: disable=unused-argument
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
super().__init__()
self.condition = loader.construct_object(node.value[0])
self.when_true = loader.construct_object(node.value[1])
self.when_false = loader.construct_object(node.value[2])
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
if isinstance(self.condition, YAMLTag):
condition = self.condition.resolve(entry, blueprint)
else:
condition = self.condition
try:
return entry.tag_resolver(
self.when_true if condition else self.when_false,
blueprint,
)
except TypeError as exc:
raise EntryInvalidError(exc)
class BlueprintDumper(SafeDumper): class BlueprintDumper(SafeDumper):
"""Dump dataclasses to yaml""" """Dump dataclasses to yaml"""
@ -353,6 +382,7 @@ class BlueprintLoader(SafeLoader):
self.add_constructor("!Context", Context) self.add_constructor("!Context", Context)
self.add_constructor("!Format", Format) self.add_constructor("!Format", Format)
self.add_constructor("!Condition", Condition) self.add_constructor("!Condition", Condition)
self.add_constructor("!If", If)
self.add_constructor("!Env", Env) self.add_constructor("!Env", Env)

View File

@ -45,6 +45,42 @@ Example: `name: !Format [my-policy-%s, !Context instance_name]`
Format a string using python's % formatting. First argument is the format string, any remaining arguments are used for formatting. Format a string using python's % formatting. First argument is the format string, any remaining arguments are used for formatting.
#### `!If`
Minimal example:
`required: !If [true, true, false] # !If [<condition>, <when true>, <when false>`
Full example:
```
attributes: !If [
!Condition [...], # Or any valid YAML or custom tag. Evaluated as boolean in Python
{ # When condition evaluates to true
dictionary:
{
with:
{
keys: "and_values"
},
and_nested_custom_tags: !Format ["foo-%s", !Context foo]
}
},
[ # When condition evaluates to false
list,
with,
items,
!Format ["foo-%s", !Context foo]
]
]
```
Conditionally add YAML to a blueprint.
Similar to a one-line if, the first argument is the condition, which can be any valid yaml or custom tag. It will be evaluted as boolean in python. However, keep in mind that dictionaries and lists will always evaluate to `true`, unless they are empty.
The second argument is used when the condition is `true`, and the third - when `false`. The YAML inside both arguments will be fully resolved, thus it is possible to use custom YAML tags and even nest them inside dictionaries and lists.
#### `!Condition` #### `!Condition`
Minimal example: Minimal example:
@ -68,4 +104,4 @@ Requires at least one argument after the mode selection.
If only a single argument is provided, its boolean representation will be returned for all normal modes and its negated boolean representation will be returned for all negated modes. If only a single argument is provided, its boolean representation will be returned for all normal modes and its negated boolean representation will be returned for all negated modes.
Normally, it should be used to define complex conditions for the `conditions` attribute of a blueprint entry (see [the blueprint file structure](./structure.md)). However, this is essentially just a boolean evaluator so it can be used everywhere a boolean representation is required. Normally, it should be used to define complex conditions for use with an `!If` tag or for the `conditions` attribute of a blueprint entry (see [the blueprint file structure](./structure.md)). However, this is essentially just a boolean evaluator so it can be used everywhere a boolean representation is required.