Skip to content

Instantly share code, notes, and snippets.

@ppmathis
Last active January 25, 2022 12:23
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ppmathis/a78d9000a4ba90e7f6571ce06f6bc209 to your computer and use it in GitHub Desktop.
Save ppmathis/a78d9000a4ba90e7f6571ce06f6bc209 to your computer and use it in GitHub Desktop.
SaltStack: Custom state and execution module for ansible-like 'assemble' function
import os
import salt.loader
import salt.utils
import salt.utils.jinja
import salt.utils.files
from salt.exceptions import SaltInvocationError, CommandExecutionError
def get_assemble_fragments(fragments_path, include_pattern=None, exclude_pattern=None):
# Verify given fragments path
if not os.path.isabs(fragments_path):
raise SaltInvocationError('Fragments path must be absolute: {0}'.format(fragments_path))
if not os.path.isdir(fragments_path):
raise SaltInvocationError('Fragments path must exist and be a directory: {0}'.format(fragments_path))
# Get list of all fragment files
fragment_file_paths = set()
try:
# List all directory entries without using recursion
for entry_name in os.listdir(fragments_path):
entry_path = os.path.join(fragments_path, entry_name)
# Skip entry if path is not a file
if not os.path.isfile(entry_path):
continue
# Skip entry if name does not match include/exclude_pat
if not salt.utils.check_include_exclude(entry_path, include_pattern, exclude_pattern):
continue
# Append entry to set of source files
fragment_file_paths.add(entry_path)
except Exception as exc:
raise CommandExecutionError('Could not build list of fragment files: {0}'.format(exc))
# Collect content of all fragment files, sorted by name/path
fragments = []
try:
for fragment_file_path in sorted(fragment_file_paths):
with open(fragment_file_path, 'r') as fragment_file:
fragments.append({'source': fragment_file_path, 'content': fragment_file.read()})
except Exception as exc:
raise CommandExecutionError('Unable to collect content of fragment files: {0}'.format(exc))
# Return collected fragments
return fragments
# noinspection PyUnresolvedReferences
from salt.states.file import *
def _error(ret, err_msg):
ret['result'] = False
ret['comment'] = err_msg
return ret
def assemble(name,
fragments,
include_pat=None,
exclude_pat=None,
template_src=None,
**kwargs):
ret = {
'name': name,
'changes': {},
'pchanges': {},
'result': True,
'comment': ''
}
# Fetch all fragments by executing ssx.get_assemble_fragments
try:
fragments = __salt__['ssx.get_assemble_fragments'](fragments_path=fragments, include_pattern=include_pat,
exclude_pattern=exclude_pat)
except Exception as exc:
return _error(ret, exc)
# Remove specific, dangerous keys before passing all kwargs to file.managed
for key in ('source', 'source_hash', 'contents', 'contents_grains', 'contents_pillar'):
kwargs.pop(key, None)
# Override kwargs for contents/source for fragments rendering
if not template_src:
fragment_contents = ([fragment['content'] for fragment in fragments])
kwargs.update({
'name': name,
'contents': fragment_contents
})
else:
context = kwargs.get('context', {})
context.update({'__fragments__': fragments})
kwargs.update({
'name': name,
'source': template_src,
'context': context
})
# Execute file.managed state with modified kwargs, merge the results and return
ret.update(__states__['file.managed'](**kwargs))
return ret
THIS IS HEADA!
{%- for fragment in __fragments__ %}
# {{ fragment.source }} {{ '{{{' }}
{{ fragment.content -}}
# {{ '}}}' }}
{%- endfor %}
THIS IS FOOTA!
/tmp/development/salt/assemble1.cfg:
ssx.assemble:
- fragments: /tmp/development/salt/assemble1-fragments
/tmp/development/salt/assemble2.cfg:
ssx.assemble:
- fragments: /tmp/development/salt/assemble2-fragments
- include_pat: '*.conf'
- template: jinja
- template_src: salt://assemble.j2
- check_cmd: /bin/true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment