blueprints: add AtIndex tag (#12386)
This commit is contained in:

committed by
GitHub

parent
22b0a1bd23
commit
98b5b75f29
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -33,7 +33,8 @@
|
|||||||
"!If sequence",
|
"!If sequence",
|
||||||
"!Index scalar",
|
"!Index scalar",
|
||||||
"!KeyOf scalar",
|
"!KeyOf scalar",
|
||||||
"!Value scalar"
|
"!Value scalar",
|
||||||
|
"!AtIndex scalar"
|
||||||
],
|
],
|
||||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||||
"typescript.preferences.importModuleSpecifierEnding": "index",
|
"typescript.preferences.importModuleSpecifierEnding": "index",
|
||||||
|
@ -146,6 +146,10 @@ entries:
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
nested_context: !Context context2
|
nested_context: !Context context2
|
||||||
|
at_index_sequence: !AtIndex [!Context sequence, 0]
|
||||||
|
at_index_sequence_default: !AtIndex [!Context sequence, 100, "non existent"]
|
||||||
|
at_index_mapping: !AtIndex [!Context mapping, "key2"]
|
||||||
|
at_index_mapping_default: !AtIndex [!Context mapping, "invalid", "non existent"]
|
||||||
identifiers:
|
identifiers:
|
||||||
name: test
|
name: test
|
||||||
conditions:
|
conditions:
|
||||||
|
@ -215,6 +215,10 @@ class TestBlueprintsV1(TransactionTestCase):
|
|||||||
},
|
},
|
||||||
"nested_context": "context-nested-value",
|
"nested_context": "context-nested-value",
|
||||||
"env_null": None,
|
"env_null": None,
|
||||||
|
"at_index_sequence": "foo",
|
||||||
|
"at_index_sequence_default": "non existent",
|
||||||
|
"at_index_mapping": 2,
|
||||||
|
"at_index_mapping_default": "non existent",
|
||||||
}
|
}
|
||||||
).exists()
|
).exists()
|
||||||
)
|
)
|
||||||
|
@ -24,6 +24,10 @@ from authentik.lib.sentry import SentryIgnoredException
|
|||||||
from authentik.policies.models import PolicyBindingModel
|
from authentik.policies.models import PolicyBindingModel
|
||||||
|
|
||||||
|
|
||||||
|
class UNSET:
|
||||||
|
"""Used to test whether a key has not been set."""
|
||||||
|
|
||||||
|
|
||||||
def get_attrs(obj: SerializerModel) -> dict[str, Any]:
|
def get_attrs(obj: SerializerModel) -> dict[str, Any]:
|
||||||
"""Get object's attributes via their serializer, and convert it to a normal dict"""
|
"""Get object's attributes via their serializer, and convert it to a normal dict"""
|
||||||
serializer: Serializer = obj.serializer(obj)
|
serializer: Serializer = obj.serializer(obj)
|
||||||
@ -556,6 +560,53 @@ class Value(EnumeratedItem):
|
|||||||
raise EntryInvalidError.from_entry(f"Empty/invalid context: {context}", entry) from exc
|
raise EntryInvalidError.from_entry(f"Empty/invalid context: {context}", entry) from exc
|
||||||
|
|
||||||
|
|
||||||
|
class AtIndex(YAMLTag):
|
||||||
|
"""Get value at index of a sequence or mapping"""
|
||||||
|
|
||||||
|
obj: YAMLTag | dict | list | tuple
|
||||||
|
attribute: int | str | YAMLTag
|
||||||
|
default: Any | UNSET
|
||||||
|
|
||||||
|
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.obj = loader.construct_object(node.value[0])
|
||||||
|
self.attribute = loader.construct_object(node.value[1])
|
||||||
|
if len(node.value) == 2: # noqa: PLR2004
|
||||||
|
self.default = UNSET
|
||||||
|
else:
|
||||||
|
self.default = loader.construct_object(node.value[2])
|
||||||
|
|
||||||
|
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
|
||||||
|
if isinstance(self.obj, YAMLTag):
|
||||||
|
obj = self.obj.resolve(entry, blueprint)
|
||||||
|
else:
|
||||||
|
obj = self.obj
|
||||||
|
if isinstance(self.attribute, YAMLTag):
|
||||||
|
attribute = self.attribute.resolve(entry, blueprint)
|
||||||
|
else:
|
||||||
|
attribute = self.attribute
|
||||||
|
|
||||||
|
if isinstance(obj, list | tuple):
|
||||||
|
try:
|
||||||
|
return obj[attribute]
|
||||||
|
except TypeError as exc:
|
||||||
|
raise EntryInvalidError.from_entry(
|
||||||
|
f"Invalid index for list: {attribute}", entry
|
||||||
|
) from exc
|
||||||
|
except IndexError as exc:
|
||||||
|
if self.default is UNSET:
|
||||||
|
raise EntryInvalidError.from_entry(
|
||||||
|
f"Index out of range: {attribute}", entry
|
||||||
|
) from exc
|
||||||
|
return self.default
|
||||||
|
if attribute in obj:
|
||||||
|
return obj[attribute]
|
||||||
|
else:
|
||||||
|
if self.default is UNSET:
|
||||||
|
raise EntryInvalidError.from_entry(f"Key does not exist: {attribute}", entry)
|
||||||
|
return self.default
|
||||||
|
|
||||||
|
|
||||||
class BlueprintDumper(SafeDumper):
|
class BlueprintDumper(SafeDumper):
|
||||||
"""Dump dataclasses to yaml"""
|
"""Dump dataclasses to yaml"""
|
||||||
|
|
||||||
@ -606,6 +657,7 @@ class BlueprintLoader(SafeLoader):
|
|||||||
self.add_constructor("!Enumerate", Enumerate)
|
self.add_constructor("!Enumerate", Enumerate)
|
||||||
self.add_constructor("!Value", Value)
|
self.add_constructor("!Value", Value)
|
||||||
self.add_constructor("!Index", Index)
|
self.add_constructor("!Index", Index)
|
||||||
|
self.add_constructor("!AtIndex", AtIndex)
|
||||||
|
|
||||||
|
|
||||||
class EntryInvalidError(SentryIgnoredException):
|
class EntryInvalidError(SentryIgnoredException):
|
||||||
|
@ -16,7 +16,8 @@ For VS Code, for example, add these entries to your `settings.json`:
|
|||||||
"!If sequence",
|
"!If sequence",
|
||||||
"!Index scalar",
|
"!Index scalar",
|
||||||
"!KeyOf scalar",
|
"!KeyOf scalar",
|
||||||
"!Value scalar"
|
"!Value scalar",
|
||||||
|
"!AtIndex scalar"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -299,3 +300,23 @@ The above example will resolve to something like this:
|
|||||||
- "bar: (index: 1, letter: a)"
|
- "bar: (index: 1, letter: a)"
|
||||||
- "bar: (index: 2, letter: r)"
|
- "bar: (index: 2, letter: r)"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `!AtIndex` <span class="badge badge--version">authentik 2024.12+</span>
|
||||||
|
|
||||||
|
Minimal example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
value_in_sequence: !AtIndex [["first", "second"], 0] # Resolves to "first"
|
||||||
|
value_in_mapping: !AtIndex [{ "foo": "bar", "other": "value" }, "foo"] # Resolves to "bar"
|
||||||
|
```
|
||||||
|
|
||||||
|
Example with default value:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
default_value: !AtIndex [["first"], 100, "default"] # Resolves to "default"
|
||||||
|
default_value: !AtIndex [["first"], 100] # Throws an error
|
||||||
|
```
|
||||||
|
|
||||||
|
Resolves to the value at a specific index in a sequence or a mapping.
|
||||||
|
|
||||||
|
If no value can be found at the specified index and no default is provided, an error is raised and the blueprint is invalid.
|
||||||
|
Reference in New Issue
Block a user