forked from jgraichen/salt-tower
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add filter renderer returning partial datasets
The new `filter` renderer only returns matching data from a loaded template (e.g. YAML). This top-level dict is matched again a customizable grain or pillar key, only the value of the first matching key is returned.
- Loading branch information
Showing
4 changed files
with
187 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,4 +14,5 @@ good-names=f,k,v | |
additional-builtins= | ||
__salt__, | ||
__opts__, | ||
__grains__ | ||
__grains__, | ||
__pillar__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
# -*- coding: utf-8 -*- | ||
# pylint: disable=missing-docstring | ||
""" | ||
The ``filter`` renderers takes a dataset and only returns the first matching | ||
value. It can match globs on grains and pillar values. | ||
The input must be a parsed dictionary, for example from the YAML renderer. The | ||
first key is uses as a pattern to match the value from grains or pillar. Shell | ||
like globs are supported as the ``fnmatch`` function is used to check the | ||
patterns. | ||
The ``default`` option can provide a string used as the value if the grain or | ||
pillar key does not exist. | ||
Example: Default matching uses ``minion_id``: | ||
.. code-block:: yaml | ||
#!yaml | filter | ||
minion-1: | ||
some: | ||
data: for minion-1 | ||
other-minion: | ||
some: | ||
data: for other minion | ||
minions*: | ||
some: | ||
data: for mulitple minions matching key | ||
Example: Matching using the ``os_family`` grain | ||
.. code-block:: yaml | ||
#!yaml | filter grain=os_family default='default value' | ||
Debian: | ||
package_name: docker.io | ||
default value: | ||
package_name: docker-ce | ||
""" | ||
|
||
import logging | ||
import shlex | ||
|
||
from fnmatch import fnmatch | ||
|
||
from salt.exceptions import TemplateError | ||
|
||
try: | ||
from salt.utils.data import traverse_dict_and_list | ||
except ImportError: | ||
from salt.utils import traverse_dict_and_list | ||
|
||
|
||
VALID_SELECTORS = ("grain", "pillar") | ||
LOG = logging.getLogger(__name__) | ||
|
||
|
||
def render(source, _saltenv, _sls, argline=None, **kwargs): | ||
if not isinstance(source, dict): | ||
raise TypeError(f"Source must be a dict, not {type(source)}") | ||
|
||
selector = "grain" | ||
default = None | ||
key = "id" | ||
|
||
if argline: | ||
for arg in shlex.split(argline): | ||
try: | ||
(option, value) = arg.split("=", 2) | ||
except ValueError: | ||
option, value = arg, None | ||
|
||
if option in VALID_SELECTORS: | ||
if not value: | ||
raise TemplateError(f"Selector {option!r} needs a value") | ||
selector = option | ||
key = value | ||
|
||
elif option == "default": | ||
if not value: | ||
raise TemplateError(f"Option {option!r} needs a value") | ||
default = value | ||
|
||
else: | ||
raise TemplateError(f"Unknown option {option!r}") | ||
|
||
if selector == "grain": | ||
value = traverse_dict_and_list(__grains__, key, default) | ||
|
||
elif selector == "pillar": | ||
context = kwargs.get("context", {}) | ||
if "pillar" in context: | ||
value = traverse_dict_and_list(context["pillar"], key, default) | ||
else: | ||
value = traverse_dict_and_list(__pillar__, key, default) | ||
|
||
if not value: | ||
LOG.debug("Skipping blank filter value: %r", value) | ||
return {} | ||
|
||
# Matching only works on strings | ||
value = str(value) | ||
|
||
for pattern in source: | ||
if fnmatch(value, pattern): | ||
return source[pattern] | ||
|
||
LOG.debug("No pattern matched value: %r", value) | ||
|
||
return {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# -*- coding: utf-8 -*- | ||
# pylint: disable=missing-docstring | ||
# pylint: disable=redefined-outer-name | ||
|
||
|
||
def test_render(render): | ||
template = """ | ||
#!yaml | filter grain=id | ||
test_*: | ||
key: 1 | ||
test_master: | ||
key: 2 | ||
something else: | ||
key: 3 | ||
""" | ||
|
||
assert render(template) == {"key": 1} | ||
|
||
|
||
def test_render_default_minion_id(render): | ||
template = """ | ||
#!yaml | filter | ||
test_master: | ||
key: 1 | ||
something else: | ||
key: 2 | ||
""" | ||
|
||
assert render(template) == {"key": 1} | ||
|
||
|
||
def test_render_grain(render): | ||
template = """ | ||
#!yaml | filter grain=os_family | ||
Debian: | ||
key: 1 | ||
something else: | ||
key: 2 | ||
""" | ||
|
||
assert render(template) == {"key": 1} | ||
|
||
|
||
def test_render_pillar(render): | ||
template = """ | ||
#!yaml | filter pillar=some:key | ||
value: | ||
match: True | ||
""" | ||
|
||
context = {"pillar": {"some": {"key": "value"}}} | ||
|
||
assert render(template, context=context) == {"match": True} | ||
|
||
|
||
def test_render_default(render): | ||
template = """ | ||
#!yaml | filter pillar=some:key default='matching value' | ||
'*value': | ||
match: True | ||
""" | ||
|
||
assert render(template) == {"match": True} |