Package: salt-tower
Salt Tower — A Flexible External Pillar ModuleSalt Tower is an advanced and flexibleext_pillarthat gives access to pillar values while processing and merging them, can render all usual salt file formats and include private and binary files for a minion.Salt Tower is inspired bypillarstackfor merging pillar files and giving access to them. It also has atop filelike salt itself and utilizes salt renderers to supports all formats such as YAML, Jinja, Python and any combination. Superchargedrenderers for plain text and YAMLare included too.Each tower data file is passed the current processed pillars. They can therefore access previously defined values. Data files can include other files that are all merged together.Salt Tower is designed to completely replace the usual pillar repository or can be utilized beside salts original pillar that e.g. can bootstrap a salt master with Salt Tower.Questions or Need Help?Seeexamples. They each have their own README further explaining the given example.There is agroupandmailing list. You can join the grouphereor by sending asubscribe-email.Feel free to ask for help, discuss solutions or ideas there. Otherwise, you can open anissue.InstallationGitFSYou can include this repository as a gitfs root and synchronize the extensions on the master:gitfs_remotes:-https://github.com/jgraichen/salt-tower.git:-base:v1.12.0Sync all modules:$ salt-run saltutil.sync_all
pillar:
    - pillar.tower
renderers:
    - renderers.filter
    - renderers.text
    - renderers.yamletPlease note thateverythingin this repository would be merged with your other roots.pippip install salt-towerManual installationInstall the extension files from thesalt_tower/{pillar,renderers}directories into theextension_modulesdirectory configured in salt.ConfigurationSalt Tower is configured as anext_pillar:ext_pillar:-tower:/path/to/tower.slsTop FileThetower.slsfile is similar to the usualtop.slswith some important differences.Ordered matchersPillar top items are ordered and processed in order of appearance. You can therefore define identical matchers multiple times.# tower.slsbase:-'*':-first-'*':-secondCommon includesYou do not need to define a matcher at all, the files will be included for all minions. Furthermore, you also can use globs to match multiple files, e.g. include all files fromcommon/.base:-common/*GrainsThe top file itself is rendered using the default renderer (yaml|jinja). Therefore, you can use e.g.grainsto include specific files.base:-common/*-dist/{{ grains['oscodename'] }}Embedded dataYou can directly include pillar data into the top file simply be defining adictitem.base:-'*.a.example.org':-site:id:aname:A SiteIterative pillar processingAll matchers are compound matchers by default. As items are processes in order of appearance, later items can patch on previously defined pillar values. The above example includesapplication.slsfor any minion matching*.a.example.orgsimply because it defines asitepillar value.base:-'*.a.example.org':-site:{id:a,name:A Site}-'I@site:*':-applicationsLate-bound variable replacementFile includes are preprocessed by a string formatter to late-bind pillar values.base:-'*.a.example.org':-site:{id:a,env:production}-'*.a-staging.example.org':-site:{id:a,env:staging}-'I@site:*':-site/default-site/{site.id}-site/{site.id}/{site.env}/*In the above example a minionnode0.a-staging.example.orgwill include the following files:site/default
site/a
site/a/staging/*File lookupFile names will be matches to files and directories, e.g. when includingpath/to/filethe first existing match will be used:path/to/file
path/to/file.sls
path/to/file/init.slsTower Data FileA data file is processed like a usual pillar file. Rendering uses salts template engines therefore all usual features should be available.The injectedpillarobjects can be used to access previously defined values. The additional.getmethod allows to traverse the pillar tree.application:title:Site of {{ pillar.get('tenant:name') }}Note:Usingsalt['pillar.get']()willnotwork.Tower data files can beany supported template formatincluding python files:#!pydefrun():ret={'databases':[]}forappin__pillar__['application']:ret['databases'].append({'name':'{0}-{1}'.format(app['name'],app['env'])})returnretIncludesPillar data files can include other pillar files similar to how states can be included:include:-another/pillardata:moreIncluded files cannot be used in the pillar data file template itself but are merged in the pillar before the new pillar data. Includes can be relative to the current file by prefixing a dot:include:-file/from/pillar/root.sls-./adjacent_file.sls-../parent_file.slsYamlet rendererThe Yamlet renderer is an improved YAML renderer that supports loading other files and rendering templates:ssh_private_key:!readid_rsassh_public_key:!readid_rsa.pubThis reads a file from the pillar directory in plain text or binary and embeds it into the pillar to e.g. ease shipping private file blobs to minions.Using the!includetag files can be pushed through salts rendering pipeline on the server:nginx:sites:my-app:!include../files/site.conf#!jinja | text stripserver {listen{{pillar.get('my-app:ip')}}:80;root /var/www/my-app;}The pillar will return the following:nginx:sites:my-app:|server {listen 127.0.0.1:80;root /var/www/my-app;}This can greatly simplify states as they only need to drop pillar values into config files and restart services:nginx:pkg.installed:[]service.running:[]{%fornameinpillar.get('nginx:sites',{})%}/etc/nginx/sites-enabled/{{name}}:file.managed:-contents_pillar: nginx:sites:{{name}}-makedirs:True-watch_in:-service:nginx{%endfor%}The yamlet renderer!includemacro does accept context variables too:nginx:sites:my-app:!includesource:../files/site.confcontext:listen_ip:127.0.0.1#!jinja | text stripserver {listen{{listen_ip}}:80;root /var/www/my-app;}Text rendererThe text renderer (used above) renders a file as plain text. It stripes the shebang and can optionally strip whitespace from the beginning and end.#!text strip

Hello WorldThis will return:Hello WorldThe text renderer is mostly used for embedding rendered configuration files into a Yamlet file.Filter rendererThe filter renderer returns only a subset of data that matches a given grain or pillar key value:#!yamlet | filter grain=os_family default='Unknown OS'Debian:package_source:aptRedHat:package_source:rpmUnknown OS:package_source:unknownWhen this file is rendered, only the data from the matching top level key is returned. The renderer supports glob matches and uses the minion ID by default:#!yamlet | filterminion-1:monitoring:type:pingaddress:10.0.0.1webserver-*:monitoring:type:httpaddress:http://example.orgAdvanced usage (very dangerous)The pillar object passed to the python template engine is the actual mutable dict reference used to process and merge the data. It is possible to modify this dict e.g. in a python template without returning anything:#!pyimportcopydefrun():databases=__pillar__['databases']default=databases.pop('default')# Deletes from actual pillarforname,configindatabases.items():databases[name]=dict(default,**config)return{}Note 1:Do not returnNone. Otherwise,Salt will render the template twiceand all side effects will be applied twice.Note 2:The__pillar__object in Python templates is different to other template engines. It is a dict and does not allow traversing usingget.#!pydefrun():return{'wrong':__pilar__.get('tenant:name'),'python':__pillar__['tenant']['name'],'alternative':tower.get('tenant:name')}The above example demonstrates different usages. The first example will only work if the pillar contains an actualtenant:nametop-level key. The second example is idiomatic-python but will raise an error if the keys do not exist. The third example uses the additionaltowerhelper module to traverse the pillar data.Thetowerpillar object is available in all rendering engines and can be used for low-level interaction with the ext_pillar engine. Some available functions are:tower.get(key, default=None, require=False)Get a pillar value by given traverse path:tower.get('my:pillar:key')Ifrequire=Trueis set,defaultwill be ignored and a KeyError will be raised if the pillar key is not found.tower.update(dict)Merges given dictionary into the pillar data.tower.update({'my':{'pillar':'data'}})asserttower.get('my:pillar')=='data'tower.merge(tgt, *objects)Merges given dictionaries or lists into the first one.Note: The first given dictionary or list ismutatedand returned.tgt={}ret=tower.merge(tgt,{'a':1})assertretistgtasserttgt['a']==1tower.format(obj, *args, **kwargs)Performs recursive late-bind string formatting using tower pillar and given arguments ad keywords for resolving. Usesstring.Formatterinternally.tower.update({'database':{'password':'secret'}})ret=tower.format('postgres://user@{database.password}/db')assertret=='postgres://user@secret/db'Format accept dictionaries and list as well and can therefore be used to format full or partial pillar data, this can be used to e.g. format defaults with extra variables:#!pydefrun():returns={}defaults=__pillar__['default_app_config']# e.g. {#        'database': 'sqlite:///opt/{name}.sqlite'#        'listen': '0.0.0.0:{app.port}'# }forname,confin__pillar__['applications'].items():# Merge defaults with conf into new dictionaryconf=tower.merge({},defaults,conf)# Format late-bind defaults with application configconf=tower.format(conf,name=name,app=conf)returns[name]=confreturn{'applications':returns}