Skip to content

Commit

Permalink
feat: Support filter renderer to return nested dict
Browse files Browse the repository at this point in the history
  • Loading branch information
jgraichen committed Sep 3, 2022
1 parent e491537 commit 159ade5
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 15 deletions.
22 changes: 22 additions & 0 deletions .markdownlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# markdownlint config

# The CHANGELOG contains duplicated headers by design
MD024: false

# MD013/line-length: disable line length for all. We prefer lines as
# long as paragraph with in-editor line breaks.
MD013: false

# MD033/no-inline-html: allow often need tags
MD033:
allowed_elements:
- figure
- figcaption

# MD046/code-block-style: code block style conflicting with
# admonitions...
MD046: false

# MD048/code-fence-style: code fence style
MD048:
style: backtick
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- Add `key` argument to filter renderer returning result in a nested dictionary

## [1.9.0] - 2022-05-16

### Added
Expand Down
29 changes: 22 additions & 7 deletions docs/renderers/filter.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,8 @@ roles:
- app
```


### Match a grain


The renderer can use any grain to match top-level keys:

```yaml
Expand All @@ -58,7 +56,6 @@ RedHat:
repo_url: http://rpm.example.org
```


### Match a pillar key

The `filter` renderer can match on a previously set pillar key too.
Expand Down Expand Up @@ -113,7 +110,6 @@ site:
customer: customer_a
```


### Default value

If a grain or pillar doesn't exist, a default value can be given to match the top-level keys:
Expand All @@ -131,25 +127,44 @@ demo:
worker_count: 1
```

### Wrapping result in dictionary

The result can be wrapping to a nested dictionary before being returned. It will be easier to customize a nested value for every minion, such as a user password:

```yaml
#!yaml | filter grain=id key=users:root:password
minion-a: $2b$05$BvTnTGxuWrMJJwty0mk2D.MCRTnBz4P9M3hZAnkxr0Eo1V9y8CJJK
minion-b: $2b$05$4EaM70ZUcK4Y.oJe2ZC9FOuCz53WttNhD.NuipNUrxCodjDb6Cfg.
```

The returned data will look like this:

```yaml
users:
root:
password: $2b$05$BvTnTGxuWrMJJwty0mk2D.MCRTnBz4P9M3hZAnkxr0Eo1V9y8CJJK
```

## Technical details

### Arguments

The `filter` renderer can take values from grains or the pillar, the key has to be specified on the shebang:

```
```text
#!filter pillar=some:key
```

or

```
```text
#!filter grain=os_family
```

A default value can be specified for both, if the grain or pillar key does not exist, the default value will be used for matching the top-level keys. The default value must be specified with quotes if it contains spaces.

```
```text
#!filter pillar=some:key default=value
#!filter grain=id default='value with space'
```
Expand Down
28 changes: 20 additions & 8 deletions salt_tower/renderers/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ def render( # pylint: disable=too-many-branches

selector = "grain"
default = None
key = "id"
lookup_key = "id"
wrap_key = False

if argline:
for arg in shlex.split(argline):
Expand All @@ -84,25 +85,30 @@ def render( # pylint: disable=too-many-branches
if not value:
raise TemplateError(f"Selector {option!r} needs a value")
selector = option
key = value
lookup_key = value

elif option == "default":
if not value:
raise TemplateError(f"Option {option!r} needs a value")
default = value

elif option == "key":
if not value:
raise TemplateError(f"Option {option!r} needs a value")
wrap_key = value

else:
raise TemplateError(f"Unknown option {option!r}")

if selector == "grain":
value = traverse_dict_and_list(__grains__, key, default)
value = traverse_dict_and_list(__grains__, lookup_key, default)

elif selector == "pillar":
context = kwargs.get("context", {})
if "pillar" in context:
value = traverse_dict_and_list(context["pillar"], key, default)
value = traverse_dict_and_list(context["pillar"], lookup_key, default)
else:
value = traverse_dict_and_list(__pillar__, key, default)
value = traverse_dict_and_list(__pillar__, lookup_key, default)

if not value:
LOG.debug("Skipping blank filter value: %r", value)
Expand All @@ -111,10 +117,16 @@ def render( # pylint: disable=too-many-branches
# Matching only works on strings
value = str(value)

result = {}
for pattern in source:
if fnmatch(value, pattern):
return source[pattern]
result = source[pattern]
break
else:
LOG.debug("No pattern matched value: %r", value)

LOG.debug("No pattern matched value: %r", value)
if wrap_key:
for k in reversed(wrap_key.split(":")):
result = {k: result}

return {}
return result
10 changes: 10 additions & 0 deletions test/renderers/test_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,13 @@ def test_render_default(render):
"""

assert render(template) == {"match": True}


def test_render_key(render):
template = """
#!yaml | filter grain=id key=sub:path
'test_master':
key: 2
"""

assert render(template) == {"sub": {"path": {"key": 2}}}

0 comments on commit 159ade5

Please sign in to comment.