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

@ -1,21 +1,21 @@
version: 1 version: 1
entries: entries:
- identifiers: - identifiers:
name: "%(id1)s" name: "%(id1)s"
slug: "%(id1)s" slug: "%(id1)s"
model: authentik_flows.flow model: authentik_flows.flow
conditions: conditions:
- true - true
attrs: attrs:
designation: stage_configuration designation: stage_configuration
title: foo title: foo
- identifiers: - identifiers:
name: "%(id2)s" name: "%(id2)s"
slug: "%(id2)s" slug: "%(id2)s"
model: authentik_flows.flow model: authentik_flows.flow
conditions: conditions:
- true - true
- true - true
attrs: attrs:
designation: stage_configuration designation: stage_configuration
title: foo title: foo

View File

@ -1,21 +1,21 @@
version: 1 version: 1
entries: entries:
- identifiers: - identifiers:
name: "%(id1)s" name: "%(id1)s"
slug: "%(id1)s" slug: "%(id1)s"
model: authentik_flows.flow model: authentik_flows.flow
conditions: conditions:
- false - false
attrs: attrs:
designation: stage_configuration designation: stage_configuration
title: foo title: foo
- identifiers: - identifiers:
name: "%(id2)s" name: "%(id2)s"
slug: "%(id2)s" slug: "%(id2)s"
model: authentik_flows.flow model: authentik_flows.flow
conditions: conditions:
- true - true
- false - false
attrs: attrs:
designation: stage_configuration designation: stage_configuration
title: foo title: foo

View File

@ -1,7 +1,7 @@
version: 1 version: 1
entries: entries:
- identifiers: - identifiers:
name: "%(id)s" name: "%(id)s"
slug: "%(id)s" slug: "%(id)s"
model: authentik_flows.flow model: authentik_flows.flow
state: absent state: absent

View File

@ -1,10 +1,10 @@
version: 1 version: 1
entries: entries:
- identifiers: - identifiers:
name: "%(id)s" name: "%(id)s"
slug: "%(id)s" slug: "%(id)s"
model: authentik_flows.flow model: authentik_flows.flow
state: created state: created
attrs: attrs:
designation: stage_configuration designation: stage_configuration
title: foo title: foo

View File

@ -1,10 +1,10 @@
version: 1 version: 1
entries: entries:
- identifiers: - identifiers:
name: "%(id)s" name: "%(id)s"
slug: "%(id)s" slug: "%(id)s"
model: authentik_flows.flow model: authentik_flows.flow
state: present state: present
attrs: attrs:
designation: stage_configuration designation: stage_configuration
title: foo title: foo

View File

@ -1,12 +1,12 @@
version: 1 version: 1
entries: entries:
- identifiers: - identifiers:
pk: cb954fd4-65a5-4ad9-b1ee-180ee9559cf4 pk: cb954fd4-65a5-4ad9-b1ee-180ee9559cf4
model: authentik_stages_prompt.prompt model: authentik_stages_prompt.prompt
attrs: attrs:
field_key: username field_key: username
label: Username label: Username
type: username type: username
required: true required: true
placeholder: Username placeholder: Username
order: 0 order: 0

View File

@ -4,37 +4,97 @@ context:
policy_property: name policy_property: name
policy_property_value: foo-bar-baz-qux policy_property_value: foo-bar-baz-qux
entries: entries:
- model: authentik_sources_oauth.oauthsource - model: authentik_sources_oauth.oauthsource
identifiers: identifiers:
slug: test slug: test
attrs: attrs:
name: test name: test
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 [
- attrs: authentik_flows.Flow,
expression: return True [slug, default-source-authentication],
identifiers: ]
name: !Format [foo-%s-%s-%s, !Context foo, !Context bar, qux] enrollment_flow:
id: policy !Find [authentik_flows.Flow, [slug, default-source-enrollment]]
model: authentik_policies_expression.expressionpolicy - attrs:
- attrs: expression: return True
attributes: identifiers:
policy_pk1: !Format ["%s-%s", !Find [authentik_policies_expression.expressionpolicy, [!Context policy_property, !Context policy_property_value], [expression, return True]], suffix] name: !Format [foo-%s-%s-%s, !Context foo, !Context bar, qux]
policy_pk2: !Format ["%s-%s", !KeyOf policy, suffix] id: policy
boolAnd: !Condition [AND, !Context foo, !Format ["%s", "a_string"], 1] model: authentik_policies_expression.expressionpolicy
boolNand: !Condition [NAND, !Context foo, !Format ["%s", "a_string"], 1] - attrs:
boolOr: !Condition [OR, !Context foo, !Format ["%s", "a_string"], null] attributes:
boolNor: !Condition [NOR, !Context foo, !Format ["%s", "a_string"], null] policy_pk1:
boolXor: !Condition [XOR, !Context foo, !Format ["%s", "a_string"], 1] !Format [
boolXnor: !Condition [XNOR, !Context foo, !Format ["%s", "a_string"], 1] "%s-%s",
boolComplex: !Condition [XNOR, !Condition [AND, !Context non_existing], !Condition [NOR, a string], !Condition [XOR, null]] !Find [
identifiers: authentik_policies_expression.expressionpolicy,
name: test [
conditions: !Context policy_property,
- !Condition [AND, true, true, text] !Context policy_property_value,
- true ],
- text [expression, return True],
model: authentik_core.group ],
suffix,
]
policy_pk2: !Format ["%s-%s", !KeyOf policy, suffix]
boolAnd:
!Condition [AND, !Context foo, !Format ["%s", "a_string"], 1]
boolNand:
!Condition [NAND, !Context foo, !Format ["%s", "a_string"], 1]
boolOr:
!Condition [
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:
name: test
conditions:
- !Condition [AND, true, true, text]
- true
- text
model: authentik_core.group

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.