Skip to content

Commit 2968032

Browse files
author
sammy.smati
committed
feat(map): update to v5 map.jinja
1 parent ce35c1c commit 2968032

38 files changed

+929
-31
lines changed

CODEOWNERS

+3
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
/docs/CHANGELOG.rst @saltstack-formulas/ssf
2121
/docs/TOFS_pattern.rst @saltstack-formulas/ssf
2222
/*/_mapdata/ @saltstack-formulas/ssf
23+
/*/libmapstack.jinja @saltstack-formulas/ssf
24+
/*/libmatchers.jinja @saltstack-formulas/ssf
2325
/*/libsaltcli.jinja @saltstack-formulas/ssf
2426
/*/libtofs.jinja @saltstack-formulas/ssf
27+
/*/map.jinja @saltstack-formulas/ssf
2528
/test/integration/**/_mapdata.rb @saltstack-formulas/ssf
2629
/test/integration/**/libraries/system.rb @saltstack-formulas/ssf
2730
/test/integration/**/inspec.yml @saltstack-formulas/ssf

consul/_mapdata/init.sls

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
---
44
{#- Get the `tplroot` from `tpldir` #}
55
{%- set tplroot = tpldir.split("/")[0] %}
6-
{%- from tplroot ~ "/map.jinja" import consul with context %}
6+
{%- from tplroot ~ "/map.jinja" import mapdata with context %}
77
88
{%- set _mapdata = {
9-
"values": consul,
9+
"values": mapdata,
1010
} %}
1111
{%- do salt["log.debug"]("### MAP.JINJA DUMP ###\n" ~ _mapdata | yaml(False)) %}
1212

consul/config.sls

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
# -*- coding: utf-8 -*-
2+
# vim: ft=sls
3+
4+
{#- Get the `tplroot` from `tpldir` #}
15
{%- set tplroot = tpldir.split('/')[0] %}
2-
{%- from tplroot + '/map.jinja' import consul with context -%}
6+
{%- from tplroot ~ "/map.jinja" import mapdata as consul with context %}
37
48
consul-config:
59
file.serialize:

consul/init.sls

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
# -*- coding: utf-8 -*-
2+
# vim: ft=sls
3+
4+
{#- Get the `tplroot` from `tpldir` #}
15
{%- set tplroot = tpldir.split('/')[0] %}
2-
{%- if pillar.get('consul', {}).get('enabled', True) %}
3-
{% from tplroot+"/map.jinja" import consul with context %}
6+
{%- from tplroot ~ "/map.jinja" import mapdata as consul with context %}
47
8+
{%- if consul.get('enabled', True) %}
59
include:
610
- {{ tplroot }}.install
711
- {{ tplroot }}.config
812
- {{ tplroot }}.service
9-
1013
{%- endif %}

consul/install.sls

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
# -*- coding: utf-8 -*-
2+
# vim: ft=sls
3+
4+
{#- Get the `tplroot` from `tpldir` #}
15
{%- set tplroot = tpldir.split('/')[0] %}
2-
{%- from tplroot + '/map.jinja' import consul with context -%}
6+
{%- from tplroot ~ "/map.jinja" import mapdata as consul with context %}
37
48
consul-dep-unzip:
59
pkg.installed:

consul/libmapstack.jinja

+315
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
{#- -*- coding: utf-8 -*- #}
2+
{#- vim: ft=jinja #}
3+
4+
{#- Get the `tplroot` from `tpldir` #}
5+
{%- set tplroot = tpldir.split("/")[0] %}
6+
{%- from tplroot ~ "/libmatchers.jinja" import parse_matchers, query_map with context %}
7+
8+
{%- set _default_config_dirs = [
9+
"parameters/",
10+
tplroot ~ "/parameters"
11+
] %}
12+
13+
{%- macro mapstack(
14+
matchers,
15+
defaults=None,
16+
dirs=_default_config_dirs,
17+
log_prefix="libmapstack: "
18+
) %}
19+
{#-
20+
Load configuration in the order of `matchers` and merge
21+
successively the values with `defaults`.
22+
23+
The `matchers` are processed using `libmatchers.jinja` to select
24+
the configuration sources from where the values are loaded.
25+
26+
Parameters:
27+
28+
- `matchers`: list of matchers in the form
29+
`[<TYPE>[:<OPTION>[:<DELIMITER>]]@]<QUERY>`
30+
31+
- `defaults`: dictionary of default values to start the merging,
32+
they are considered built-ins. It must conform to the same
33+
layout as the YAML files: a mandatory `values` key and two
34+
optional `strategy` and `merge_lists` keys.
35+
36+
- `dirs`: list of directory where to look-up the configuration
37+
file matching the matchers, by default a global `salt://parameters/`
38+
and a per formula `salt://<tplroot>/parameters`
39+
40+
- `log_prefix`: prefix used in the log outputs, by default it is
41+
`libmapstack: `
42+
43+
Example: On a Debian system with `roles=["nginx/server", "telegraf"]`
44+
45+
{%- set settings = mapstack(
46+
matchers=[
47+
"Y:G@os_family",
48+
"I@" ~ tplroot,
49+
"Y:C@roles",
50+
],
51+
dirs=["defaults", tplroot ~ "/parameters"],
52+
)
53+
| load_yaml %}
54+
55+
This will merge the values:
56+
57+
- starting with the default empty dictionary `{}` (no
58+
`defaults` parameter)
59+
60+
- from the YAML files
61+
62+
- `salt://defaults/os_family/Debian.yaml`
63+
64+
- `salt://{{ tplroot }}/parameters/os_family/Debian.yaml`
65+
66+
- from the pillar `salt["pillar.get"](tplroot)`
67+
68+
- from the `nginx/server` YAML files:
69+
70+
- `salt://defaults/roles/nginx/server.yaml`
71+
72+
- `salt://{{ tplroot }}/parameters/roles/nginx/server.yaml`
73+
74+
- from the `telegraf` YAML files:
75+
76+
- `salt://defaults/roles/telegraf.yaml`
77+
78+
- `salt://{{ tplroot }}/parameters/roles/telegraf.yaml`
79+
80+
Each YAML file and the `defaults` parameters must conform to the
81+
following layout:
82+
83+
- a mandatory `values` key to store the configuration values
84+
85+
- two optional keys to configure the use of `salt.slsutil.merge`
86+
87+
- an optional `strategy` key to configure the merging
88+
strategy, for example `strategy: 'recurse'`, the default is
89+
`smart`
90+
91+
- an optional `merge_lists` key to configure if lists should
92+
be merged or overridden for the `recurse` and `overwrite`
93+
strategies, for example `merge_lists: 'true'`
94+
#}
95+
{%- set stack = defaults | default({"values": {} }, boolean=True) %}
96+
97+
{#- Build configuration file names based on matchers #}
98+
{%- set config_get_strategy = salt["config.get"](tplroot ~ ":strategy", None) %}
99+
{%- set matchers = parse_matchers(
100+
matchers,
101+
config_get_strategy=config_get_strategy,
102+
log_prefix=log_prefix
103+
)
104+
| load_yaml %}
105+
106+
{%- do salt["log.debug"](
107+
log_prefix
108+
~ "built-in configuration:\n"
109+
~ {"values": defaults | traverse("values")}
110+
| yaml(False)
111+
) %}
112+
113+
{%- for param_dir in dirs %}
114+
{%- for matcher in matchers %}
115+
{#- `slsutil.merge` options from #}
116+
{#- 1. the `value` #}
117+
{#- 2. the `defaults` #}
118+
{#- 3. the built-in #}
119+
{%- set strategy = matcher.value
120+
| traverse(
121+
"strategy",
122+
defaults
123+
| traverse(
124+
"strategy",
125+
"smart"
126+
)
127+
) %}
128+
{%- set merge_lists = matcher.value
129+
| traverse(
130+
"merge_lists",
131+
defaults
132+
| traverse(
133+
"merge_lists",
134+
False
135+
)
136+
)
137+
| to_bool %}
138+
139+
{%- if matcher.type in query_map.keys() %}
140+
{#- No value is an empty list, must be a dict for `stack.update` #}
141+
{%- set normalized_value = matcher.value | default({}, boolean=True) %}
142+
143+
{#- Merge in `mapdata.<query>` instead of directly in `mapdata` #}
144+
{%- set is_sub_key = matcher.option | default(False) == "SUB" %}
145+
{%- if is_sub_key %}
146+
{#- Merge values with `mapdata.<key>`, `<key>` and `<key>:lookup` are merged together #}
147+
{%- set value = { matcher.query | regex_replace(":lookup$", ""): normalized_value } %}
148+
{%- else %}
149+
{%- set value = normalized_value %}
150+
{%- endif %}
151+
152+
{%- do salt["log.debug"](
153+
log_prefix
154+
~ "merge "
155+
~ "sub key " * is_sub_key
156+
~ "'"
157+
~ matcher.query
158+
~ "' retrieved with '"
159+
~ matcher.query_method
160+
~ "', merge: strategy='"
161+
~ strategy
162+
~ "', lists='"
163+
~ merge_lists
164+
~ "':\n"
165+
~ value
166+
| yaml(False)
167+
) %}
168+
169+
{%- do stack.update(
170+
{
171+
"values": salt["slsutil.merge"](
172+
stack["values"],
173+
value,
174+
strategy=strategy,
175+
merge_lists=merge_lists,
176+
)
177+
}
178+
) %}
179+
180+
{%- else %}
181+
{#- Load YAML file matching the grain/pillar/... #}
182+
{#- Fallback to use the source name as a direct filename #}
183+
184+
{%- if matcher.value is sequence and matcher.value | length == 0 %}
185+
{#- Mangle `matcher.value` to use it as literal path #}
186+
{%- set query_parts = matcher.query.split("/") %}
187+
{%- set yaml_dirname = query_parts[0:-1] | join("/") %}
188+
{%- set yaml_names = query_parts[-1] %}
189+
{%- else %}
190+
{%- set yaml_dirname = matcher.query %}
191+
{%- set yaml_names = matcher.value %}
192+
{%- endif %}
193+
194+
{#- Some configuration return list #}
195+
{%- if yaml_names is string %}
196+
{%- set yaml_names = [yaml_names] %}
197+
{%- elif yaml_names is sequence %}
198+
{#- Convert to strings if it's a sequence of numbers #}
199+
{%- set yaml_names = yaml_names | map("string") | list %}
200+
{%- else %}
201+
{%- set yaml_names = [yaml_names | string] %}
202+
{%- endif %}
203+
204+
{#- Try to load a `.yaml.jinja` file for each `.yaml` file #}
205+
{%- set all_yaml_names = [] %}
206+
{%- for name in yaml_names %}
207+
{%- set extension = name.rpartition(".")[2] %}
208+
{%- if extension not in ["yaml", "jinja"] %}
209+
{%- do all_yaml_names.extend([name ~ ".yaml", name ~ ".yaml.jinja"]) %}
210+
{%- elif extension == "yaml" %}
211+
{%- do all_yaml_names.extend([name, name ~ ".jinja"]) %}
212+
{%- else %}
213+
{%- do all_yaml_names.append(name) %}
214+
{%- endif %}
215+
{%- endfor %}
216+
217+
{#- `yaml_dirname` can be an empty string with literal path like `myconf.yaml` #}
218+
{%- set yaml_dir = [
219+
param_dir,
220+
yaml_dirname
221+
]
222+
| select
223+
| join("/") %}
224+
225+
{%- for yaml_name in all_yaml_names %}
226+
{%- set yaml_filename = [
227+
yaml_dir.rstrip("/"),
228+
yaml_name
229+
]
230+
| select
231+
| join("/") %}
232+
233+
{%- do salt["log.debug"](
234+
log_prefix
235+
~ "load configuration values from "
236+
~ yaml_filename
237+
) %}
238+
{%- load_yaml as yaml_values %}
239+
{%- include yaml_filename ignore missing %}
240+
{%- endload %}
241+
242+
{%- if yaml_values %}
243+
{%- do salt["log.debug"](
244+
log_prefix
245+
~ "loaded configuration values from "
246+
~ yaml_filename
247+
~ ":\n"
248+
~ yaml_values
249+
| yaml(False)
250+
) %}
251+
252+
{#- `slsutil.merge` options from #}
253+
{#- 1. the `value` #}
254+
{#- 2. the `defaults` #}
255+
{#- 3. the built-in #}
256+
{%- set strategy = yaml_values
257+
| traverse(
258+
"strategy",
259+
defaults
260+
| traverse(
261+
"strategy",
262+
"smart"
263+
)
264+
) %}
265+
{%- set merge_lists = yaml_values
266+
| traverse(
267+
"merge_lists",
268+
defaults
269+
| traverse(
270+
"merge_lists",
271+
False
272+
)
273+
)
274+
| to_bool %}
275+
{%- do stack.update(
276+
{
277+
"values": salt["slsutil.merge"](
278+
stack["values"],
279+
yaml_values
280+
| traverse("values", {}),
281+
strategy=strategy,
282+
merge_lists=merge_lists,
283+
)
284+
}
285+
) %}
286+
{%- do salt["log.debug"](
287+
log_prefix
288+
~ "merged configuration values from "
289+
~ yaml_filename
290+
~ ", merge: strategy='"
291+
~ strategy
292+
~ "', merge_lists='"
293+
~ merge_lists
294+
~ "':\n"
295+
~ {"values": stack["values"]}
296+
| yaml(False)
297+
) %}
298+
{%- endif %}
299+
{%- endfor %}
300+
{%- endif %}
301+
{%- endfor %}
302+
{%- endfor %}
303+
304+
{%- do salt["log.debug"](
305+
log_prefix
306+
~ "final configuration values:\n"
307+
~ {"values": stack["values"]}
308+
| yaml(False)
309+
) %}
310+
311+
{#- Output stack as YAML, caller should use with something like #}
312+
{#- `{%- set config = mapstack(matchers=["foo"]) | load_yaml %}` #}
313+
{{ stack | yaml }}
314+
315+
{%- endmacro %}

0 commit comments

Comments
 (0)