Skip to content

Commit

Permalink
feat(map): update to generic v5 map.jinja
Browse files Browse the repository at this point in the history
The `cert-formula` requires a dedicated `map.jinja` configuration to
skip values from `salt["config.get"](cert)` for compatibility.

BREAKING CHANGE: `map.jinja` now export a generic `mapdata` variable

BREAKING CHANGE: the parameters per grains are now under `cert/parameters/`
  • Loading branch information
baby-gnu committed Apr 8, 2021
1 parent b7f90ca commit 93c89e1
Show file tree
Hide file tree
Showing 15 changed files with 1,292 additions and 58 deletions.
4 changes: 2 additions & 2 deletions cert/_mapdata/init.sls
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
---
{#- Get the `tplroot` from `tpldir` #}
{%- set tplroot = tpldir.split("/")[0] %}
{%- from tplroot ~ "/map.jinja" import map with context %}
{%- from tplroot ~ "/map.jinja" import mapdata with context %}
{%- set _mapdata = {
"values": map,
"values": mapdata,
} %}
{%- do salt["log.debug"]("### MAP.JINJA DUMP ###\n" ~ _mapdata | yaml(False)) %}
Expand Down
28 changes: 14 additions & 14 deletions cert/init.sls
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# This is the main state file for deploying certificates

{% from "cert/map.jinja" import map with context %}
{% from "cert/map.jinja" import mapdata with context %}
# Install required packages
cert_packages:
pkg.installed:
- pkgs:
{% for pkg in map.pkgs %}
{% for pkg in mapdata.pkgs %}
- {{ pkg }}
{% endfor %}
Expand All @@ -22,17 +22,17 @@ cert_packages:
{% set cert = data.get('cert', False) %}
{% set key = data.get('key', False) %}
{% set cert_ext = data.get('cert_ext', map.cert_ext) %}
{% set key_ext = data.get('key_ext', map.key_ext) %}
{% set cert_user = data.get('cert_user', map.cert_user) %}
{% set key_user = data.get('key_user', map.key_user) %}
{% set cert_group = data.get('cert_group', map.cert_group) %}
{% set key_group = data.get('key_group', map.key_group) %}
{% set cert_mode = data.get('cert_mode', map.cert_mode) %}
{% set key_mode = data.get('key_mode', map.key_mode) %}
{% set cert_dir = data.get('cert_dir', map.cert_dir) %}
{% set key_dir = data.get('key_dir', map.key_dir) %}
{% set remove = data.get('remove', map.remove) %}
{% set cert_ext = data.get('cert_ext', mapdata.cert_ext) %}
{% set key_ext = data.get('key_ext', mapdata.key_ext) %}
{% set cert_user = data.get('cert_user', mapdata.cert_user) %}
{% set key_user = data.get('key_user', mapdata.key_user) %}
{% set cert_group = data.get('cert_group', mapdata.cert_group) %}
{% set key_group = data.get('key_group', mapdata.key_group) %}
{% set cert_mode = data.get('cert_mode', mapdata.cert_mode) %}
{% set key_mode = data.get('key_mode', mapdata.key_mode) %}
{% set cert_dir = data.get('cert_dir', mapdata.cert_dir) %}
{% set key_dir = data.get('key_dir', mapdata.key_dir) %}
{% set remove = data.get('remove', mapdata.remove) %}
{{ cert_dir }}/{{ name }}{{ cert_ext }}:
{% if remove %}
Expand All @@ -45,7 +45,7 @@ cert_packages:
- contents: |
{{ cert|indent(8, True) }}
{% else %}
- source: {{ map.cert_source_dir }}{{ name }}{{ cert_ext }}
- source: {{ mapdata.cert_source_dir }}{{ name }}{{ cert_ext }}
{% endif %}
- makedirs: True
- user: {{ cert_user }}
Expand Down
310 changes: 310 additions & 0 deletions cert/libmapstack.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
{#- -*- coding: utf-8 -*- #}
{#- vim: ft=jinja #}

{#- Get the `tplroot` from `tpldir` #}
{%- set tplroot = tpldir.split("/")[0] %}
{%- from tplroot ~ "/libmatchers.jinja" import parse_matchers, query_map %}

{%- set _default_config_dirs = [
"parameters/",
tplroot ~ "/parameters"
] %}

{%- macro mapstack(
matchers,
defaults=None,
dirs=_default_config_dirs,
log_prefix="libmapstack: "
) %}
{#-
Load configuration in the order of `matchers` and merge
successively the values with `defaults`.
The `matchers` are processed using `libmatchers.jinja` to select
the configuration sources from where the values are loaded.
Parameters:
- `matchers`: list of matchers in the form
`[<TYPE>[:<OPTION>[:<DELIMITER>]]@]<QUERY>`
- `defaults`: dictionary of default values to start the merging,
they are considered built-ins. It must conform to the same
layout as the YAML files: a mandatory `values` key and two
optional `strategy` and `merge_lists` keys.
- `dirs`: list of directory where to look-up the configuration
file matching the matchers, by default a global `salt://parameters/`
and a per formula `salt://<tplroot>/parameters`
- `log_prefix`: prefix used in the log outputs, by default it is
`libmapstack: `
Example: On a Debian system with `roles=["nginx/server", "telegraf"]`
{%- set settings = mapstack(
matchers=[
"Y:G@os_family",
"I@" ~ tplroot,
"Y:C@roles",
],
dirs=["defaults", tplroot ~ "/parameters"],
)
| load_yaml %}
This will merge the values:
- starting with the default empty dictionary `{}` (no
`defaults` parameter)
- from the YAML files
- `salt://defaults/os_family/Debian.yaml`
- `salt://{{ tplroot }}/parameters/os_family/Debian.yaml`
- from the pillar `salt["pillar.get"](tplroot)`
- from the `nginx/server` YAML files:
- `salt://defaults/roles/nginx/server.yaml`
- `salt://{{ tplroot }}/parameters/roles/nginx/server.yaml`
- from the `telegraf` YAML files:
- `salt://defaults/roles/telegraf.yaml`
- `salt://{{ tplroot }}/parameters/roles/telegraf.yaml`
Each YAML file and the `defaults` parameters must conform to the
following layout:
- a mandatory `values` key to store the configuration values
- two optional keys to configure the use of `salt.slsutil.merge`
- an optional `strategy` key to configure the merging
strategy, for example `strategy: 'recurse'`, the default is
`smart`
- an optional `merge_lists` key to configure if lists should
be merged or overridden for the `recurse` and `overwrite`
strategies, for example `merge_lists: 'true'`
#}
{%- set stack = defaults | default({"values": {} }, boolean=True) %}

{#- Build configuration file names based on matchers #}
{%- set config_get_strategy = salt["config.get"](tplroot ~ ":strategy", None) %}
{%- set matchers = parse_matchers(
matchers,
config_get_strategy=config_get_strategy,
log_prefix=log_prefix
)
| load_yaml %}

{%- do salt["log.debug"](
log_prefix
~ "built-in configuration:\n"
~ {"values": defaults | traverse("values")}
| yaml(False)
) %}

{%- for param_dir in dirs %}
{%- for matcher in matchers %}
{#- `slsutil.merge` options from #}
{#- 1. the `value` #}
{#- 2. the `defaults` #}
{#- 3. the built-in #}
{%- set strategy = matcher.value
| traverse(
"strategy",
defaults
| traverse(
"strategy",
"smart"
)
) %}
{%- set merge_lists = matcher.value
| traverse(
"merge_lists",
defaults
| traverse(
"merge_lists",
False
)
)
| to_bool %}

{%- if matcher.type in query_map.keys() %}
{#- No value is an empty list, must be a dict for `stack.update` #}
{%- set normalized_value = matcher.value | default({}, boolean=True) %}

{#- Merge in `mapdata.<query>` instead of directly in `mapdata` #}
{%- set is_sub_key = matcher.option | default(False) == "SUB" %}
{%- if is_sub_key %}
{#- Merge values with `mapdata.<key>`, `<key>` and `<key>:lookup` are merged together #}
{%- set value = { matcher.query | regex_replace(":lookup$", ""): normalized_value } %}
{%- else %}
{%- set value = normalized_value %}
{%- endif %}

{%- do salt["log.debug"](
log_prefix
~ "merge "
~ "sub key " * is_sub_key
~ "'"
~ matcher.query
~ "' retrieved with '"
~ matcher.query_method
~ "', merge: strategy='"
~ strategy
~ "', lists='"
~ merge_lists
~ "':\n"
~ value
| yaml(False)
) %}

{%- do stack.update(
{
"values": salt["slsutil.merge"](
stack["values"],
value,
strategy=strategy,
merge_lists=merge_lists,
)
}
) %}

{%- else %}
{#- Load YAML file matching the grain/pillar/... #}
{#- Fallback to use the source name as a direct filename #}

{%- if matcher.value | length == 0 %}
{#- Mangle `matcher.value` to use it as literal path #}
{%- set query_parts = matcher.query.split("/") %}
{%- set yaml_dirname = query_parts[0:-1] | join("/") %}
{%- set yaml_names = query_parts[-1] %}
{%- else %}
{%- set yaml_dirname = matcher.query %}
{%- set yaml_names = matcher.value %}
{%- endif %}

{#- Some configuration return list #}
{%- if yaml_names is string %}
{%- set yaml_names = [yaml_names] %}
{%- endif %}

{#- Try to load a `.yaml.jinja` file for each `.yaml` file #}
{%- set all_yaml_names = [] %}
{%- for name in yaml_names %}
{%- set extension = name.rpartition(".")[2] %}
{%- if extension not in ["yaml", "jinja"] %}
{%- do all_yaml_names.extend([name ~ ".yaml", name ~ ".yaml.jinja"]) %}
{%- elif extension == "yaml" %}
{%- do all_yaml_names.extend([name, name ~ ".jinja"]) %}
{%- else %}
{%- do all_yaml_names.append(name) %}
{%- endif %}
{%- endfor %}

{#- `yaml_dirname` can be an empty string with literal path like `myconf.yaml` #}
{%- set yaml_dir = [
param_dir,
yaml_dirname
]
| select
| join("/") %}

{%- for yaml_name in all_yaml_names %}
{%- set yaml_filename = [
yaml_dir.rstrip("/"),
yaml_name
]
| select
| join("/") %}

{%- do salt["log.debug"](
log_prefix
~ "load configuration values from "
~ yaml_filename
) %}
{%- load_yaml as yaml_values %}
{%- include yaml_filename ignore missing %}
{%- endload %}

{%- if yaml_values %}
{%- do salt["log.debug"](
log_prefix
~ "loaded configuration values from "
~ yaml_filename
~ ":\n"
~ yaml_values
| yaml(False)
) %}

{#- `slsutil.merge` options from #}
{#- 1. the `value` #}
{#- 2. the `defaults` #}
{#- 3. the built-in #}
{%- set strategy = yaml_values
| traverse(
"strategy",
defaults
| traverse(
"strategy",
"smart"
)
) %}
{%- set merge_lists = yaml_values
| traverse(
"merge_lists",
defaults
| traverse(
"merge_lists",
False
)
)
| to_bool %}
{%- do stack.update(
{
"values": salt["slsutil.merge"](
stack["values"],
yaml_values
| traverse("values", {}),
strategy=strategy,
merge_lists=merge_lists,
)
}
) %}
{%- do salt["log.debug"](
log_prefix
~ "merged configuration values from "
~ yaml_filename
~ ", merge: strategy='"
~ strategy
~ "', merge_lists='"
~ merge_lists
~ "':\n"
~ {"values": stack["values"]}
| yaml(False)
) %}
{%- endif %}
{%- endfor %}
{%- endif %}
{%- endfor %}
{%- endfor %}

{%- do salt["log.debug"](
log_prefix
~ "final configuration values:\n"
~ {"values": stack["values"]}
| yaml(False)
) %}

{#- Output stack as YAML, caller should use with something like #}
{#- `{%- set config = mapstack(matchers=["foo"]) | load_yaml %}` #}
{{ stack | yaml }}

{%- endmacro %}
Loading

0 comments on commit 93c89e1

Please sign in to comment.