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",
|
||||
"!Index scalar",
|
||||
"!KeyOf scalar",
|
||||
"!Value scalar"
|
||||
"!Value scalar",
|
||||
"!AtIndex scalar"
|
||||
],
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"typescript.preferences.importModuleSpecifierEnding": "index",
|
||||
|
@ -146,6 +146,10 @@ entries:
|
||||
]
|
||||
]
|
||||
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:
|
||||
name: test
|
||||
conditions:
|
||||
|
@ -215,6 +215,10 @@ class TestBlueprintsV1(TransactionTestCase):
|
||||
},
|
||||
"nested_context": "context-nested-value",
|
||||
"env_null": None,
|
||||
"at_index_sequence": "foo",
|
||||
"at_index_sequence_default": "non existent",
|
||||
"at_index_mapping": 2,
|
||||
"at_index_mapping_default": "non existent",
|
||||
}
|
||||
).exists()
|
||||
)
|
||||
|
@ -24,6 +24,10 @@ from authentik.lib.sentry import SentryIgnoredException
|
||||
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]:
|
||||
"""Get object's attributes via their serializer, and convert it to a normal dict"""
|
||||
serializer: Serializer = obj.serializer(obj)
|
||||
@ -556,6 +560,53 @@ class Value(EnumeratedItem):
|
||||
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):
|
||||
"""Dump dataclasses to yaml"""
|
||||
|
||||
@ -606,6 +657,7 @@ class BlueprintLoader(SafeLoader):
|
||||
self.add_constructor("!Enumerate", Enumerate)
|
||||
self.add_constructor("!Value", Value)
|
||||
self.add_constructor("!Index", Index)
|
||||
self.add_constructor("!AtIndex", AtIndex)
|
||||
|
||||
|
||||
class EntryInvalidError(SentryIgnoredException):
|
||||
|
@ -16,7 +16,8 @@ For VS Code, for example, add these entries to your `settings.json`:
|
||||
"!If sequence",
|
||||
"!Index 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: 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