Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(map.jinja): load configuration values from configurable sources #186

Conversation

baby-gnu
Copy link
Contributor

@baby-gnu baby-gnu commented Feb 13, 2020

PR progress checklist (to be filled in by reviewers)

  • Changes to documentation are appropriate (or tick if not required)
  • Changes to tests are appropriate (or tick if not required)
  • Reviews completed

What type of PR is this?

Primary type

  • [build] Changes related to the build system
  • [chore] Changes to the build process or auxiliary tools and libraries such as documentation generation
  • [ci] Changes to the continuous integration configuration
  • [feat] A new feature
  • [fix] A bug fix
  • [perf] A code change that improves performance
  • [refactor] A code change that neither fixes a bug nor adds a feature
  • [revert] A change used to revert a previous commit
  • [style] Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc.)

Secondary type

  • [docs] Documentation changes
  • [test] Adding missing or correcting existing tests

Does this PR introduce a BREAKING CHANGE?

No.

Users see no difference.

Related issues and/or pull requests

#116

Describe the changes you're proposing

I propose to split all the os*map.yaml under parameters/<config>/<configvalue>.yaml.

  • this is an answer to the common complain about using too heavily pillars in formula
  • each osarch, os_family, os, osfinger, … is split in a separate file instead of a single YAML file per configuration
    • a user can override parameters for Debian os_family without the need to maintain the others
    • a use can easily add support for a new os_family (or os, …) by providing a single file in its file_roots
  • avoid using non secret values by modifying the map:sources (or the formula specific <tplroot>:map:sources), for example:
    • add some value sources: ['osarch', 'os_family', 'os', 'osfinger', 'domain', 'roles', 'id']
    • add the proper YAML files: domain/example.net.yaml, domain/example.org.yaml, roles/web.yaml, roles/ssh.yaml, id/minion1.example.org.yaml
  • make great use of gitfs

Pillar / config required to test the proposed changes

Debug log showing how the proposed changes work

Here is the debug output (the new map.jinja debug output is prefixed by map.jinja: :

       [DEBUG   ] In saltenv 'base', ** considering ** path '/tmp/kitchen/var/cache/salt/minion/files/base/TEMPLATE/map.jinja' to resolve 'salt://TEMPLATE/map.jinja'
       [DEBUG   ] LazyLoaded config.get
       [DEBUG   ] key: map:sources, ret: _|-
       [DEBUG   ] key: TEMPLATE:map:sources, ret: _|-
       [DEBUG   ] map.jinja: load parameters with sources from ['osarch', 'os_family', 'os', 'osfinger']
       [DEBUG   ] map.jinja: initialise parameters from TEMPLATE/parameters/defaults.yaml
       [DEBUG   ] In saltenv 'base', looking at rel_path 'TEMPLATE/parameters/defaults.yaml' to resolve 'salt://TEMPLATE/parameters/defaults.yaml'
       [DEBUG   ] In saltenv 'base', ** considering ** path '/tmp/kitchen/var/cache/salt/minion/files/base/TEMPLATE/parameters/defaults.yaml' to resolve 'salt://TEMPLATE/parameters/defaults.yaml'
       [DEBUG   ] map.jinja: load parameters from file TEMPLATE/parameters/osarch/amd64.yaml
       [DEBUG   ] In saltenv 'base', looking at rel_path 'TEMPLATE/parameters/osarch/amd64.yaml' to resolve 'salt://TEMPLATE/parameters/osarch/amd64.yaml'
       [DEBUG   ] In saltenv 'base', ** considering ** path '/tmp/kitchen/var/cache/salt/minion/files/base/TEMPLATE/parameters/osarch/amd64.yaml' to resolve 'salt://TEMPLATE/parameters/osarch/amd64.yaml'
       [DEBUG   ] map.jinja: merge parameters from TEMPLATE/parameters/osarch/amd64.yaml
       [DEBUG   ] map.jinja: load parameters from file TEMPLATE/parameters/os_family/Debian.yaml
       [DEBUG   ] Could not find file 'salt://TEMPLATE/parameters/os_family/Debian.yaml' in saltenv 'base'
       [DEBUG   ] map.jinja: load parameters from file TEMPLATE/parameters/os/Debian.yaml
       [DEBUG   ] Could not find file 'salt://TEMPLATE/parameters/os/Debian.yaml' in saltenv 'base'
       [DEBUG   ] map.jinja: load parameters from file TEMPLATE/parameters/osfinger/Debian-9.yaml
       [DEBUG   ] In saltenv 'base', looking at rel_path 'TEMPLATE/parameters/osfinger/Debian-9.yaml' to resolve 'salt://TEMPLATE/parameters/osfinger/Debian-9.yaml'
       [DEBUG   ] In saltenv 'base', ** considering ** path '/tmp/kitchen/var/cache/salt/minion/files/base/TEMPLATE/parameters/osfinger/Debian-9.yaml' to resolve 'salt://TEMPLATE/parameters/osfinger/Debian-9.yaml'
       [DEBUG   ] map.jinja: merge parameters from TEMPLATE/parameters/osfinger/Debian-9.yaml
       [DEBUG   ] map.jinja: save parameters in variable "TEMPLATE"

Documentation checklist

  • Updated the README (e.g. Available states).
  • Updated pillar.example.

Testing checklist

  • Included in Kitchen (i.e. under state_top).
  • Covered by new/existing tests (e.g. InSpec, Serverspec, etc.).
  • Updated the relevant test pillar.

Additional context

@baby-gnu
Copy link
Contributor Author

I have an issue where the tofs:files_switch list has an empty last element. I'll see that later.

@baby-gnu
Copy link
Contributor Author

I have an issue where the tofs:files_switch list has an empty last element. I'll see that later.

It looks to be a side effect when using files_switch from libtofs.jinja after import of map.jinja.

I'm doing some debugging right now.

@baby-gnu
Copy link
Contributor Author

I have an issue where the tofs:files_switch list has an empty last element. I'll see that later.

It looks to be a side effect when using files_switch from libtofs.jinja after import of map.jinja.

I'm doing some debugging right now.

I found the issue in libtofs.jinja, a simple copy of a list solve the problem:

diff --git a/TEMPLATE/libtofs.jinja b/TEMPLATE/libtofs.jinja
index 900e62b..23c5c60 100644
--- a/TEMPLATE/libtofs.jinja
+++ b/TEMPLATE/libtofs.jinja
@@ -78,7 +78,7 @@
     {#- Use the default, new method otherwise #}
     {%- set fsl = salt['config.get'](
         tplroot ~ path_prefix_ext|replace('/', ':') ~ ':files_switch',
-        files_switch_list
+        files_switch_list.copy()
     ) %}
     {#- Append an empty value to evaluate as `default` in the loop below #}
     {%- if '' not in fsl %}

Without this copy(), the {%- do fsl.append('') %} modify the structure globally.

I'm doing a dedicated PR to fix this issue.

Regards.

@baby-gnu
Copy link
Contributor Author

We must merge #187 before continuing this.

@baby-gnu
Copy link
Contributor Author

If someone have some comments about variable naming and the understandability of the code I'll take them.

Regards.

Copy link
Member

@javierbertoli javierbertoli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a suggestion. Awesome work, btw! Kudos!

TEMPLATE/parameters/defaults.yaml Outdated Show resolved Hide resolved
@baby-gnu baby-gnu changed the title Feature/configurable map.jinja feat(map.jinja): configurable map.jinja Feb 15, 2020
@baby-gnu baby-gnu force-pushed the feature/configurable-map.jinja branch from 76a7029 to baa44d9 Compare February 15, 2020 17:54
@baby-gnu baby-gnu changed the title feat(map.jinja): configurable map.jinja feat(map.jinja): load a configurable list of YAML files Feb 15, 2020
@baby-gnu
Copy link
Contributor Author

baby-gnu commented Feb 16, 2020

I did a little python3 script which split defaults.yaml and all the os*map.yaml under parameters/

#!/usr/bin/python3

import glob
import os
import re
from string import Template
import sys

from ruamel.yaml import YAML

if not os.path.isfile('defaults.yaml'):
    raise Exception('You must run this script in the same directory as defaults.yaml')


yaml = YAML(typ='rt')
yaml.indent(offset=2)
yaml.preserve_quotes = True
yaml.default_flow_style = False
yaml.explicit_start = True
yaml.explicit_end = True

def load_yaml(filename):
    with open(filename, 'r') as yaml_file:
        yaml_str = yaml.load(yaml_file)
        return yaml_str

if not os.path.isdir('parameters'):
    os.makedirs('parameters')

yaml_files = glob.glob('os*.yaml')
config_name_map = {'osfamily': 'os_family'}

## defaults.yaml
print("Move 'defaults.yaml' to 'parameters/defaults.yaml'")
defaults = load_yaml('defaults.yaml')
defaults_keys = defaults.keys()

if len(defaults_keys) != 1:
    raise Exception(f'Wrong number of top level keys in defaults.yaml, found: {", ".join(defaults.keys())}')

values_items = defaults.popitem()
values = values_items[1]

defaults_header = """# -*- coding: utf-8 -*-
# vim: ft=yaml
"""

with open('parameters/defaults.yaml', 'w') as defaults_fh:
    defaults_fh.write(defaults_header)
    yaml.dump(values, defaults_fh)

with open('parameters/defaults.yaml', 'r+') as defaults_fh:
    # Strip 2 spaces before misaligned comments
    lines = []
    for line in defaults_fh.readlines():
        line = re.sub(r'^(\s*)  #',r'\1#', line)
        lines.append(line)
    defaults_fh.seek(0)
    defaults_fh.truncate(0)
    defaults_fh.write(''.join(lines))


## os*map.yaml
config_header = Template("""# -*- coding: utf-8 -*-
# vim: ft=yaml
#
# Setup variables specific to salt['config.get']('${config_name}') == ${config_value}.
# You just need to add the key:values for this `${config_name}` that differ
# from ${config_differ}.
#
# If you do not need to provide defaults via the `${config_name}` config,
# you can remove this file or provide at least an empty dict, e.g.
# values: {}
#
# This file will be merged with `salt.slsutil.merge`:
#
# You can select the merging strategy by defining the `strategy` dict
# with one of: `aggregate`, `list`, `overwrite`, `recurse`, `smart`, e.g.
# strategy: 'recurse'
#
# If you use the `recurse` or `overwrite` strategy, you can aggregate
# the lists by defining the `merge_lists` dict with a boolean, e.g.
# merge_lists: 'true'
""")

config_differ_map = {'osarch': '`defaults.yaml`',
                     'os': '`defaults.yaml` + `<osarch>.yaml` + `<os_family>.yaml`',
                     'os_family': '`defaults.yaml` + `<osarch>.yaml`',
                     'os_finger': '`defaults.yaml` + `<osarch>.yaml` + `<os_family>.yaml` + `<osmap>.yaml`'}

for yaml_file in yaml_files:
    config_name = yaml_file.replace('map.yaml', '')
    # Some YAML files do not have the proper config name
    config_name = config_name_map.get(config_name, config_name)

    config_dir = os.path.join('parameters', config_name)
    if not os.path.isdir(config_dir):
        os.makedirs(config_dir)

    print(f"Split '{yaml_file}' to 'parameters/{config_name}/'")
    yaml_content = load_yaml(yaml_file)
    for item in yaml_content:
        print(f"-> write file 'parameters/{config_name}/{item}.yaml'")
        with open(os.path.join(config_dir, f'{item}.yaml'), 'w') as config_fh:
            config_differ = config_differ_map.get(config_name, '`defaults.yaml`')
            config_fh.write(config_header.substitute({'config_name': config_name,
                                                      'config_value': item,
                                                      'config_differ': config_differ}))
            values = {'values': yaml_content[item]}
            yaml.dump(values, config_fh)

Regards.

@baby-gnu baby-gnu force-pushed the feature/configurable-map.jinja branch from dfdf86d to f59c484 Compare February 16, 2020 20:04
@baby-gnu
Copy link
Contributor Author

only the old 2017 is failing due to the use of traverse.

@myii
Copy link
Member

myii commented Feb 18, 2020

@alxwr I've already conducted a fair bit of testing on this new map.jinja, including salt-ssh. But since you had so much input into this before, it would be great to tap into your salt-ssh experience.

@baby-gnu
Copy link
Contributor Author

@alxwr I've already conducted a fair bit of testing on this new map.jinja, including salt-ssh. But since you had so much input into this before, it would be great to tap into your salt-ssh experience.

I have troubles executing salt-ssh testmachine state.apply TEMPLATE with my salt-ssh configuration

testmachine:
----------
          ID: TEMPLATE-package-install-pkg-installed
    Function: pkg.installed
        Name: TEMPLATE-debian
      Result: False
     Comment: Problem encountered installing package(s). Additional info follows:
              
              errors:
                  - Running scope as unit: run-r4ba9663081004f61b5536ddd22b031ae.scope
                    E: Unable to locate package TEMPLATE-debian
     Started: 14:27:23.773381
    Duration: 7109.832 ms
     Changes:   
----------
          ID: TEMPLATE-subcomponent-config-file-file-managed
    Function: file.managed
        Name: /etc/TEMPLATE-subcomponent-formula.conf
      Result: True
     Comment: File /etc/TEMPLATE-subcomponent-formula.conf updated
     Started: 14:27:30.937966
    Duration: 135.167 ms
     Changes:   
              ----------
              diff:
                  New file
              mode:
                  0644
----------
          ID: TEMPLATE-config-file-file-managed
    Function: file.managed
        Name: /etc/TEMPLATE.d/custom.conf
      Result: False
     Comment: One or more requisite failed: TEMPLATE.package.install.TEMPLATE-package-install-pkg-installed
     Started: 14:27:31.073460
    Duration: 0.008 ms
     Changes:   
----------
          ID: TEMPLATE-service-running-service-running
    Function: service.running
        Name: TEMPLATE
      Result: False
     Comment: One or more requisite failed: TEMPLATE.config.file.TEMPLATE-config-file-file-managed
     Started: 14:27:31.079633
    Duration: 0.006 ms
     Changes:   

Summary for testmachine
------------
Succeeded: 1 (changed=1)
Failed:    3
------------
Total states run:     4
Total run time:   7.245 s

@myii
Copy link
Member

myii commented Feb 18, 2020

@baby-gnu Use the appropriate pillars and you should be fine:

pillars:
top.sls:
base:
'*':
- TEMPLATE
- define_roles
pillars_from_files:
TEMPLATE.sls: pillar.example
define_roles.sls: test/salt/pillar/define_roles.sls

@baby-gnu
Copy link
Contributor Author

@baby-gnu Use the appropriate pillars and you should be fine:

Much better, thanks ;-)

Succeeded: 4 (changed=2)
Failed:    0
------------
Total states run:     4
Total run time: 167.007 ms

With salt-ssh, we have very usefull debug output for salt.slsutil.merge:

log.debug "map.jinja: load parameters from file TEMPLATE/parameters/osarch/amd64.yaml"
[…]
log.debug "map.jinja: merge parameters from TEMPLATE/parameters/osarch/amd64.yaml"
[…]
slsutil.merge {"pkg": {"name": "TEMPLATE"}, "rootgroup": "root", "config": "/etc/TEMPLATE", "service": {"name": "TEMPLATE"}, "subcomponent": {"config": "/etc/TEMPLATE-subcomponent-formula.conf"}, "added_in_defaults": "defaults_value", "winner": "defaults", "map.jinja": {"sources": ["osarch", "os_family", "os", "osfinger", "roles", "id", "any/path/can/be/used/here.yaml"]}} {"arch": "amd64"} strategy="smart" merge_lists=false

[…]

log.debug "map.jinja: load parameters from file TEMPLATE/parameters/os_family/Debian.yaml"
[…]
log.debug "map.jinja: merge parameters from TEMPLATE/parameters/os_family/Debian.yaml"
[…]
slsutil.merge {"pkg": {"name": "TEMPLATE"}, "rootgroup": "root", "config": "/etc/TEMPLATE", "service": {"name": "TEMPLATE"}, "subcomponent": {"config": "/etc/TEMPLATE-subcomponent-formula.conf"}, "added_in_defaults": "defaults_value", "winner": "defaults", "map.jinja": {"sources": ["osarch", "os_family", "os", "osfinger", "roles", "id", "any/path/can/be/used/here.yaml"]}, "arch": "amd64"} {"pkg": {"name": "TEMPLATE-debian"}, "config": "/etc/TEMPLATE.d/custom.conf"} strategy="smart" merge_lists=false

@myii
Copy link
Member

myii commented Feb 18, 2020

Much better, thanks ;-)

@baby-gnu You're welcome.

With salt-ssh, we have very usefull debug output for salt.slsutil.merge:

You can get this with salt-minion running with -l debug in one of your terminal tabs -- something I actually do all of the time when working with formulas, it's indispensable.

@baby-gnu
Copy link
Contributor Author

I'll apply few changes before this being able to merge:

  • change map.jinja:sources to map_jinja:sources to avoid problem with Jinja2 dot notation
  • remove empty parameters/<config>/*yaml files
  • remove half the comment in each parameter file describing the use of options
  • add a docs/map.jinja.rst explaining how to use it and how it works.

Regards.

@baby-gnu baby-gnu force-pushed the feature/configurable-map.jinja branch 2 times, most recently from e59ee58 to 65cc037 Compare February 24, 2020 19:37
@baby-gnu
Copy link
Contributor Author

By the way, the merge strategy of config.get wasn't working for salt-ssh before:

It's not working with 3000 either, it start to became quite complicated ;-)

@baby-gnu
Copy link
Contributor Author

I just push my first implementation of the configuration of merge strategy for config.get.

It's not working for salt-ssh yet, so now we can discussion about the way to do it.

@baby-gnu
Copy link
Contributor Author

Hello @myii.

The following patch is working with salt-ssh but this will introduce a different behaviour over SSH if the user wants to use merge, so I added an error log:

diff --git a/TEMPLATE/map.jinja b/TEMPLATE/map.jinja
index 5b2edf6..0be483a 100644
--- a/TEMPLATE/map.jinja
+++ b/TEMPLATE/map.jinja
@@ -4,6 +4,23 @@
 {#- Get the `tplroot` from `tpldir` #}
 {%- set tplroot = tpldir.split('/')[0] %}
 
+{#- Determine the type of command being run
+    * min: standard call via. master
+    * cll: `salt-call`
+    * ssh: `salt-ssh`
+    * unk: unknown call #}
+{%- if salt['config.get']('__cli') == 'salt-minion' %}
+  {%- set cli = 'min' %}
+{%- elif salt['config.get']('__cli') == 'salt-call' %}
+  {%- if salt['config.get']('root_dir') == '/' %}
+    {%- set cli = 'cll' %}
+  {%- else %}
+    {%- set cli = 'ssh' %}
+  {%- endif %}
+{%- else %}
+  {%- set cli = 'unk' %}
+{%- endif %}
+
 {#- Where to lookup parameters source files #}
 {%- set map_sources_dir = tplroot | path_join('parameters') %}
 
@@ -45,10 +62,22 @@
     {%- set _merge_lists = {'config_get': _config['config_merge_lists'],
                             'config_get_lookup': _config['lookup_merge_lists']}.get(map_source) %}
 
+    {%- if cli == 'ssh' %}
+      {%- if _strategy %}
+        {%- do salt['log.error']("map.jinja: the 'merge' option of 'config.get' is skipped with 'salt-ssh'") %}
+      {%- endif %}
+      {%- set merge_opt = {} %}
+      {%- set merge_msg = '' %}
+    {%- else %}
+      {%- set merge_opt = {'merge': _strategy } %}
+      {%- set merge_msg = ", merge: strategy='" ~ _strategy ~ "'" %}
+    {%- endif %}
+
     {%- do salt['log.debug']("map.jinja: retrieve formula " ~ _config_type
-                             ~ " with 'config.get', "
-                             ~ "merge: strategy='" ~ _strategy ~ "'") %}
-    {%- set _config_get = salt['config.get'](_config_key, merge=_strategy, default={}) %}
+                             ~ " with 'config.get'"
+                             ~ merge_msg
+                             ) %}
+    {%- set _config_get = salt['config.get'](_config_key, default={}, **merge_opt) %}
 
     {%- do salt['log.debug']("map.jinja: merge formula " ~ _config_type
                              ~ " retrieved with 'config.get'"

Which gives the following logs:

log.error "map.jinja: the 'merge' option of 'config.get' is skipped with 'salt-ssh'"
2020-03-24 16:22:01,690 [salt.client.ssh.shell:342 ][DEBUG   ][26754] Executed SHIM command. Command logged to TRACE
2020-03-24 16:22:01,694 [salt.utils.vt    :224 ][DEBUG   ][26754] Child Forked! PID: 26835  STDOUT_FD: 11  STDERR_FD: 13
2020-03-24 16:22:01,694 [salt.utils.vt    :230 ][DEBUG   ][26754] VT: Salt-SSH SHIM Terminal Command executed. Logged to TRACE
2020-03-24 16:22:03,377 [salt.client.ssh  :1306][DEBUG   ][26754] RETCODE 192.168.0.102: 0
2020-03-24 16:22:03,379 [salt.utils.lazy  :107 ][DEBUG   ][26754] Could not LazyLoad log.debug: 'log.debug' is not available.
2020-03-24 16:22:03,384 [salt.client.ssh  :1297][DEBUG   ][26754] Performing shimmed, blocking command as follows:
log.debug "map.jinja: retrieve formula configuration with 'config.get'"
2020-03-24 16:22:03,414 [salt.client.ssh.shell:342 ][DEBUG   ][26754] Executed SHIM command. Command logged to TRACE
2020-03-24 16:22:03,417 [salt.utils.vt    :224 ][DEBUG   ][26754] Child Forked! PID: 26838  STDOUT_FD: 11  STDERR_FD: 13
2020-03-24 16:22:03,418 [salt.utils.vt    :230 ][DEBUG   ][26754] VT: Salt-SSH SHIM Terminal Command executed. Logged to TRACE
2020-03-24 16:22:04,965 [salt.client.ssh  :1306][DEBUG   ][26754] RETCODE 192.168.0.102: 0
2020-03-24 16:22:04,968 [salt.utils.lazy  :107 ][DEBUG   ][26754] Could not LazyLoad log.debug: 'log.debug' is not available.
2020-03-24 16:22:04,972 [salt.client.ssh  :1297][DEBUG   ][26754] Performing shimmed, blocking command as follows:
log.debug "map.jinja: merge formula configuration retrieved with 'config.get', merge: strategy='recurse', lists='False'"

What do you think about this?

@myii
Copy link
Member

myii commented Mar 24, 2020

@baby-gnu Personally, I like it. I'm just concerned we're going to make map.jinja more and more inaccessible to other users. Maybe we should keep some of the logic out of it, such as using libsaltcli.jinja, as I've proposed in #131. We could add the 'merge' option of 'config.get' block to it as well, to handle it out of the way. Any thoughts?

@baby-gnu
Copy link
Contributor Author

The problem with using a libsaltcli.jinja is that it will cause a problem to import the library with salt-ssh.

@myii
Copy link
Member

myii commented Mar 24, 2020

The problem with using a libsaltcli.jinja is that it will cause a problem to import the library with salt-ssh.

@baby-gnu That's a shame, we seem to be held back further and further due to salt-ssh bugs. Let's keep track of the implementation that you've provided and get some feedback from the others. I'd like to come to a conclusion about this PR as soon as possible, so that we can start using it in other formulas. If this feature is going to delay it any further, we should come back to it in a subsequent PR. I believe it's very useful to have, though, and I'd like to use it to resolve the issue in the zabbix-formula as well.


@javierbertoli @dafyddj @daks @aboe76 Can we arrange a rough timeline and strategy for approving (or rejecting) this PR? I hate to trouble @baby-gnu with more and more features, only for a potential rejection upon review. How about the idea I've suggested below?

@baby-gnu How about an alternative idea to help the review here. You've already tested this with the mysql-formula, which was really helpful. How about we make the first actual merged implementation in the libvirt-formula, since you're the main contributor there? That way, we can get these ideas up and running, so that we can refer to a live implementation in our discussions. Perhaps there's a concern about polluting the template-formula with untested methods. A bit like how TOFS was working in other formulas before it finally got merged into the template-formula, to become an adopted standard for our whole organisation.

myii added a commit to myii/template-formula that referenced this pull request Mar 25, 2020
* To distinguish between:
  - `salt-minion`
  - `salt-call`
  - `salt-ssh`
* Invoked like `map.jinja`:
  - `{%- from tplroot ~ "/libsaltcli.jinja" import cli with context %}`
* Based upon work done in PRs: saltstack-formulas#102, saltstack-formulas#114 & saltstack-formulas#115
* Finalised from saltstack-formulas/libvirt-formula#71
* Required by saltstack-formulas#186
@myii
Copy link
Member

myii commented Mar 25, 2020

This has been implemented in the libvirt-formula:

- the test file must include the `map_jinja:sources`

- the `bar` role provide two parameters but the lookup override the
  `winner`

- adapt rubocop since `config_spec.rb` is more that 36 lines
@baby-gnu baby-gnu changed the title feat(map.jinja): load a configurable list of YAML files feat(map.jinja): load configuration values from configurable sources Mar 25, 2020
Lookup the configuration for `map.jinja` from:

1. builtin default

   1. `osarch` grain
   2. `os_family` grain
   3. `os` grain
   4. `osfinger` grain
   5. `lookup` table retrived by `config.get`
   6. configuration retrived by `config.get`
   7. `id` grain

2. `defaults.yaml`: optionally define a formula specific
   `map_jinja:sources`

3. global configuration lookup `map_jinja:sources`

4. formula specific `<tplroot>:map_jinja:sources`

The hard coded default is backward compatible and add the minion id at
the end of the list.

If an entry does not match a `salt['config.get']` parameter, it's used
as a literal file path (with `.yaml` added if it's missing).

Each YAML file are formatted with the following top level keys:

- `values`: contains the the parameter values

- `strategy` (optional): define the merge strategy for the function
  `salt.slsutils.merge` (aggregate, list, overwrite, recurse,
  smart). The default is `smart`.

- `merge_lists` (optional): boolean to merge lists or overwrite them

It's possile to configure `config.get` when looking up the formula
configuration. You need to define a subkey `strategy` with the
following differences from YAML files:

- `strategy` can only be one of `None`, `overwrite` and `recurse`.
  The default is `None`.

- if you use `salt-ssh`, this merge strategy is skipped and and error
  log message is emitted.

The parameters values are merged in the following order:

1. initialize default values from `defaults.yaml`

2. merge the values from each source defined by the ordered list
   `map_jinja:sources`
* TEMPLATE/parameters/defaults.yaml: remove the top level key
  `TEMPLATE` and add a `map.jinja:sources` configuration for tests.
@baby-gnu baby-gnu force-pushed the feature/configurable-map.jinja branch from 9428b8b to 30961a7 Compare March 25, 2020 18:08
@baby-gnu
Copy link
Contributor Author

I updated the map.jinja.rst I think it could be improved to add a quick start at the top for formula users, something like:

  • set minion specific values
    1. clone the formula
    2. copy parameters/defaults.yaml to parameters/id/<minion ID>.yaml
    3. edit parameters/id/<minion ID>.yaml
  • group values per role
    1. copy parameters/defaults.yaml to parameters/roles/<role name>.yaml
    2. edit parameters/roles/<role name>.yaml
    3. set roles for the minions
    4. overwrite map_jinja:sources
  • group values per DNS domain
    1. copy parameters/defaults.yaml to parameters/domain/<role name>.yaml
    2. edit parameters/domain/<role name>.yaml
    3. overwrite map_jinja:sources to add the domain grain at the proper place in the list

@claudekenni
Copy link

claudekenni commented Apr 5, 2020

Looks like this has them same problem that I ran into while solving the Pillar problematic in https://github.com/claudekenni/stdlib-formula

YAML Data from cached files which have since been deleted on the Master are still being used within the Formula.

This is the reason why in my centralized Map I do the part where I compare the Master and Minion files and then delete cached files that don't exist on the master anymore.
https://github.com/claudekenni/stdlib-formula/blob/c1b99eecc56ae0874e94701c51975791c3b661be/stdlib/map.sls#L8-L17

Here's a rundown on how to duplicate this:

First run of the Formula fails because of dummy values

root@ubuntu18:/vagrant/code/formulas# salt-call --local state.apply TEMPLATE
local:
----------
          ID: TEMPLATE-package-install-pkg-installed
    Function: pkg.installed
        Name: TEMPLATE-ubuntu
      Result: False
     Comment: Problem encountered installing package(s). Additional info follows:

              errors:
                  - Running scope as unit: run-r21c755ede09f4914aa261087122dbcf9.scope
                    E: Unable to locate package TEMPLATE-ubuntu
     Started: 01:18:56.946006
    Duration: 11522.07 ms
     Changes:
----------
          ID: TEMPLATE-subcomponent-config-file-file-managed
    Function: file.managed
        Name: /etc/TEMPLATE-subcomponent-formula.conf
      Result: True
     Comment: File /etc/TEMPLATE-subcomponent-formula.conf is in the correct state
     Started: 01:19:08.486862
    Duration: 686.674 ms
     Changes:
----------
          ID: TEMPLATE-config-file-file-managed
    Function: file.managed
        Name: /etc/TEMPLATE.d/custom-ubuntu-18.04.conf
      Result: False
     Comment: One or more requisite failed: TEMPLATE.package.install.TEMPLATE-package-install-pkg-installed
     Started: 01:19:09.174023
    Duration: 0.016 ms
     Changes:
----------
          ID: TEMPLATE-service-running-service-running
    Function: service.running
        Name: TEMPLATE
      Result: False
     Comment: One or more requisite failed: TEMPLATE.config.file.TEMPLATE-config-file-file-managed
     Started: 01:19:09.178048
    Duration: 0.032 ms
     Changes:

Summary for local
------------
Succeeded: 1
Failed:    3
------------
Total states run:     4
Total run time:  12.209 s

Add Role to the Node

root@ubuntu18:/vagrant/code/formulas# salt-call --local grains.set roles ['baseos']
local:
    ----------
    changes:
        ----------
        roles:
            - baseos
    comment:
    result:
        True

root@ubuntu18:/vagrant/code/formulas# salt-call --local config.get roles
local:
    - baseos

Add a yaml file for the role with correct values for package and service

root@ubuntu18:/vagrant/code/formulas# cat template-formula/TEMPLATE/parameters/roles/baseos.yaml
strategy: 'overwrite'
values:
  pkg:
    name: vim
  service:
    name: ssh

Successfully apply the state

root@ubuntu18:/vagrant/code/formulas# salt-call --local state.apply TEMPLATE
local:
----------
          ID: TEMPLATE-package-install-pkg-installed
    Function: pkg.installed
        Name: vim
      Result: True
     Comment: All specified packages are already installed
     Started: 01:26:08.441502
    Duration: 214.429 ms
     Changes:
----------
          ID: TEMPLATE-subcomponent-config-file-file-managed
    Function: file.managed
        Name: /etc/TEMPLATE-subcomponent-formula.conf
      Result: True
     Comment: File /etc/TEMPLATE-subcomponent-formula.conf is in the correct state
     Started: 01:26:08.663481
    Duration: 65.527 ms
     Changes:
----------
          ID: TEMPLATE-config-file-file-managed
    Function: file.managed
        Name: /etc/TEMPLATE.d/custom-ubuntu-18.04.conf
      Result: True
     Comment: File /etc/TEMPLATE.d/custom-ubuntu-18.04.conf is in the correct state
     Started: 01:26:08.729439
    Duration: 23.62 ms
     Changes:
----------
          ID: TEMPLATE-service-running-service-running
    Function: service.running
        Name: ssh
      Result: True
     Comment: The service ssh is already running
     Started: 01:26:08.756156
    Duration: 73.28 ms
     Changes:

Summary for local
------------
Succeeded: 4
Failed:    0
------------
Total states run:     4
Total run time: 376.856 ms
root@ubuntu18:/vagrant/code/formulas#

Remove the yaml file for this role and run the state again successfully even though we'd expect it to fail.

root@ubuntu18:/vagrant/code/formulas# rm template-formula/TEMPLATE/parameters/roles/baseos.yaml
root@ubuntu18:/vagrant/code/formulas# salt-call --local state.apply TEMPLATE
local:
----------
          ID: TEMPLATE-package-install-pkg-installed
    Function: pkg.installed
        Name: vim
      Result: True
     Comment: All specified packages are already installed
     Started: 01:27:03.203670
    Duration: 270.446 ms
     Changes:
----------
          ID: TEMPLATE-subcomponent-config-file-file-managed
    Function: file.managed
        Name: /etc/TEMPLATE-subcomponent-formula.conf
      Result: True
     Comment: File /etc/TEMPLATE-subcomponent-formula.conf is in the correct state
     Started: 01:27:03.486575
    Duration: 115.931 ms
     Changes:
----------
          ID: TEMPLATE-config-file-file-managed
    Function: file.managed
        Name: /etc/TEMPLATE.d/custom-ubuntu-18.04.conf
      Result: True
     Comment: File /etc/TEMPLATE.d/custom-ubuntu-18.04.conf is in the correct state
     Started: 01:27:03.602863
    Duration: 42.773 ms
     Changes:
----------
          ID: TEMPLATE-service-running-service-running
    Function: service.running
        Name: ssh
      Result: True
     Comment: The service ssh is already running
     Started: 01:27:03.646921
    Duration: 136.065 ms
     Changes:

Summary for local
------------
Succeeded: 4
Failed:    0
------------
Total states run:     4
Total run time: 565.215 ms
root@ubuntu18:/vagrant/code/formulas#

Remove the cached yaml file for the role and run the state again, this time failing as expected

root@ubuntu18:/vagrant/code/formulas# rm /var/cache/salt/minion/files/base/TEMPLATE/parameters/roles/baseos.yaml
root@ubuntu18:/vagrant/code/formulas# salt-call --local state.apply TEMPLATE
local:
----------
          ID: TEMPLATE-package-install-pkg-installed
    Function: pkg.installed
        Name: TEMPLATE-ubuntu
      Result: False
     Comment: Problem encountered installing package(s). Additional info follows:

              errors:
                  - Running scope as unit: run-r9b3b33d3b0f2481cbce677c10d3524a2.scope
                    E: Unable to locate package TEMPLATE-ubuntu
     Started: 01:28:57.036956
    Duration: 13473.615 ms
     Changes:
----------
          ID: TEMPLATE-subcomponent-config-file-file-managed
    Function: file.managed
        Name: /etc/TEMPLATE-subcomponent-formula.conf
      Result: True
     Comment: File /etc/TEMPLATE-subcomponent-formula.conf is in the correct state
     Started: 01:29:10.521963
    Duration: 138.125 ms
     Changes:
----------
          ID: TEMPLATE-config-file-file-managed
    Function: file.managed
        Name: /etc/TEMPLATE.d/custom-ubuntu-18.04.conf
      Result: False
     Comment: One or more requisite failed: TEMPLATE.package.install.TEMPLATE-package-install-pkg-installed
     Started: 01:29:10.660648
    Duration: 0.019 ms
     Changes:
----------
          ID: TEMPLATE-service-running-service-running
    Function: service.running
        Name: TEMPLATE
      Result: False
     Comment: One or more requisite failed: TEMPLATE.config.file.TEMPLATE-config-file-file-managed
     Started: 01:29:10.662750
    Duration: 0.023 ms
     Changes:

Summary for local
------------
Succeeded: 1
Failed:    3
------------
Total states run:     4
Total run time:  13.612 s
root@ubuntu18:/vagrant/code/formulas#

Here is the Debug output from when the cached yaml file still exists

[DEBUG   ] In saltenv 'base', looking at rel_path 'TEMPLATE/subcomponent/config/file.sls' to resolve 'salt://TEMPLATE/subcomponent/config/file.sls'
[DEBUG   ] In saltenv 'base', ** considering ** path '/var/cache/salt/minion/files/base/TEMPLATE/subcomponent/config/file.sls' to resolve 'salt://TEMPLATE/subcomponent/config/file.sls'
[DEBUG   ] compile template: /var/cache/salt/minion/files/base/TEMPLATE/subcomponent/config/file.sls
[DEBUG   ] Jinja search path: [u'/var/cache/salt/minion/files/base']
[DEBUG   ] In saltenv 'base', looking at rel_path 'TEMPLATE/map.jinja' to resolve 'salt://TEMPLATE/map.jinja'
[DEBUG   ] In saltenv 'base', ** considering ** path '/var/cache/salt/minion/files/base/TEMPLATE/map.jinja' to resolve 'salt://TEMPLATE/map.jinja'
[DEBUG   ] In saltenv 'base', looking at rel_path 'TEMPLATE/libsaltcli.jinja' to resolve 'salt://TEMPLATE/libsaltcli.jinja'
[DEBUG   ] In saltenv 'base', ** considering ** path '/var/cache/salt/minion/files/base/TEMPLATE/libsaltcli.jinja' to resolve 'salt://TEMPLATE/libsaltcli.jinja'
[DEBUG   ] [libsaltcli] the salt command type has been identified to be: local
[DEBUG   ] map.jinja: initialise parameters from TEMPLATE/parameters/defaults.yaml
[DEBUG   ] In saltenv 'base', looking at rel_path 'TEMPLATE/parameters/defaults.yaml' to resolve 'salt://TEMPLATE/parameters/defaults.yaml'
[DEBUG   ] In saltenv 'base', ** considering ** path '/var/cache/salt/minion/files/base/TEMPLATE/parameters/defaults.yaml' to resolve 'salt://TEMPLATE/parameters/defaults.yaml'
[DEBUG   ] map.jinja: lookup 'map_jinja' configuration sources
[DEBUG   ] key: map_jinja:sources, ret: _|-
[DEBUG   ] key: TEMPLATE:map_jinja:sources, ret: _|-
[DEBUG   ] map.jinja: load parameters with sources from [u'osarch', u'os_family', u'os', u'osfinger', u'roles', u'config_get_lookup', u'config_get', u'id', u'any/path/can/be/used/here.yaml']
[DEBUG   ] key: TEMPLATE:strategy, ret: _|-
[DEBUG   ] key: TEMPLATE:merge_lists, ret: _|-
[DEBUG   ] map.jinja: load parameters from file TEMPLATE/parameters/osarch/amd64.yaml
[DEBUG   ] In saltenv 'base', looking at rel_path 'TEMPLATE/parameters/osarch/amd64.yaml' to resolve 'salt://TEMPLATE/parameters/osarch/amd64.yaml'
[DEBUG   ] In saltenv 'base', ** considering ** path '/var/cache/salt/minion/files/base/TEMPLATE/parameters/osarch/amd64.yaml' to resolve 'salt://TEMPLATE/parameters/osarch/amd64.yaml'
[DEBUG   ] map.jinja: merge parameters from TEMPLATE/parameters/osarch/amd64.yaml
[DEBUG   ] map.jinja: load parameters from file TEMPLATE/parameters/os_family/Debian.yaml
[DEBUG   ] In saltenv 'base', looking at rel_path 'TEMPLATE/parameters/os_family/Debian.yaml' to resolve 'salt://TEMPLATE/parameters/os_family/Debian.yaml'
[DEBUG   ] In saltenv 'base', ** considering ** path '/var/cache/salt/minion/files/base/TEMPLATE/parameters/os_family/Debian.yaml' to resolve 'salt://TEMPLATE/parameters/os_family/Debian.yaml'
[DEBUG   ] map.jinja: merge parameters from TEMPLATE/parameters/os_family/Debian.yaml
[DEBUG   ] map.jinja: load parameters from file TEMPLATE/parameters/os/Ubuntu.yaml
[DEBUG   ] In saltenv 'base', looking at rel_path 'TEMPLATE/parameters/os/Ubuntu.yaml' to resolve 'salt://TEMPLATE/parameters/os/Ubuntu.yaml'
[DEBUG   ] In saltenv 'base', ** considering ** path '/var/cache/salt/minion/files/base/TEMPLATE/parameters/os/Ubuntu.yaml' to resolve 'salt://TEMPLATE/parameters/os/Ubuntu.yaml'
[DEBUG   ] map.jinja: merge parameters from TEMPLATE/parameters/os/Ubuntu.yaml
[DEBUG   ] map.jinja: load parameters from file TEMPLATE/parameters/osfinger/Ubuntu-18.04.yaml
[DEBUG   ] In saltenv 'base', looking at rel_path 'TEMPLATE/parameters/osfinger/Ubuntu-18.04.yaml' to resolve 'salt://TEMPLATE/parameters/osfinger/Ubuntu-18.04.yaml'
[DEBUG   ] In saltenv 'base', ** considering ** path '/var/cache/salt/minion/files/base/TEMPLATE/parameters/osfinger/Ubuntu-18.04.yaml' to resolve 'salt://TEMPLATE/parameters/osfinger/Ubuntu-18.04.yaml'
[DEBUG   ] map.jinja: merge parameters from TEMPLATE/parameters/osfinger/Ubuntu-18.04.yaml
[DEBUG   ] map.jinja: load parameters from file TEMPLATE/parameters/roles/baseos.yaml
[DEBUG   ] Could not find file 'salt://TEMPLATE/parameters/roles/baseos.yaml' in saltenv 'base'
<===================== This is where the cached file gets merged
[DEBUG   ] map.jinja: merge parameters from TEMPLATE/parameters/roles/baseos.yaml   
=====================>
[DEBUG   ] map.jinja: retrieve formula lookup with 'config.get', merge: strategy='None'
[DEBUG   ] key: TEMPLATE:lookup, ret: _|-

@baby-gnu
Copy link
Contributor Author

This pull request will be archived and replace by a update to v5 map.jinja based on recent work (saltstack-formulas/openssh-formula#191 and saltstack-formulas/openvpn-formula#134).

@baby-gnu baby-gnu closed this Feb 15, 2021
@baby-gnu baby-gnu deleted the feature/configurable-map.jinja branch February 15, 2021 13:37
@baby-gnu baby-gnu mentioned this pull request Feb 15, 2021
19 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants