1.. _conventions-formula:
2
3=============
4Salt Formulas
5=============
6
7Formulas are pre-written Salt States. They are as open-ended as Salt States
8themselves and can be used for tasks such as installing a package, configuring,
9and starting a service, setting up users or permissions, and many other common
10tasks.
11
12All official Salt Formulas are found as separate Git repositories in the
13"saltstack-formulas" organization on GitHub:
14
15https://github.com/saltstack-formulas
16
17As a simple example, to install the popular Apache web server (using the normal
18defaults for the underlying distro) simply include the
19:formula_url:`apache-formula` from a top file:
20
21.. code-block:: yaml
22
23    base:
24      'web*':
25        - apache
26
27Installation
28============
29
30Each Salt Formula is an individual Git repository designed as a drop-in
31addition to an existing Salt State tree. Formulas can be installed in the
32following ways.
33
34Adding a Formula as a GitFS remote
35----------------------------------
36
37One design goal of Salt's GitFS fileserver backend was to facilitate reusable
38States. GitFS is a quick and natural way to use Formulas.
39
401.  :ref:`Install any necessary dependencies and configure GitFS
41    <tutorial-gitfs>`.
42
432.  Add one or more Formula repository URLs as remotes in the
44    :conf_master:`gitfs_remotes` list in the Salt Master configuration file:
45
46    .. code-block:: yaml
47
48        gitfs_remotes:
49          - https://github.com/saltstack-formulas/apache-formula
50          - https://github.com/saltstack-formulas/memcached-formula
51
52    **We strongly recommend forking a formula repository** into your own GitHub
53    account to avoid unexpected changes to your infrastructure.
54
55    Many Salt Formulas are highly active repositories so pull new changes with
56    care. Plus any additions you make to your fork can be easily sent back
57    upstream with a quick pull request!
58
593.  Restart the Salt master.
60
61Beginning with the 2018.3.0 release, using formulas with GitFS is now much more
62convenient for deployments which use many different fileserver environments
63(i.e. saltenvs). Using the :ref:`all_saltenvs <gitfs-global-remotes>`
64parameter, files from a single git branch/tag will appear in all environments.
65See :ref:`here <gitfs-global-remotes>` for more information on this feature.
66
67
68Adding a Formula directory manually
69-----------------------------------
70
71Formulas are simply directories that can be copied onto the local file system
72by using Git to clone the repository or by downloading and expanding a tarball
73or zip file of the repository. The directory structure is designed to work with
74:conf_master:`file_roots` in the Salt master configuration.
75
761.  Clone or download the repository into a directory:
77
78    .. code-block:: bash
79
80        mkdir -p /srv/formulas
81        cd /srv/formulas
82        git clone https://github.com/saltstack-formulas/apache-formula.git
83
84        # or
85
86        mkdir -p /srv/formulas
87        cd /srv/formulas
88        wget -O apache-formula-master.tar.gz https://github.com/saltstack-formulas/apache-formula/archive/master.tar.gz
89        tar xf apache-formula-master.tar.gz
90
912.  Add the new directory to :conf_master:`file_roots`:
92
93    .. code-block:: yaml
94
95        file_roots:
96          base:
97            - /srv/salt
98            - /srv/formulas/apache-formula
99
1003.  Restart the Salt Master.
101
102
103Usage
104=====
105
106Each Formula is intended to be immediately usable with sane defaults without
107any additional configuration. Many formulas are also configurable by including
108data in Pillar; see the :file:`pillar.example` file in each Formula repository
109for available options.
110
111Including a Formula in an existing State tree
112---------------------------------------------
113
114Formula may be included in an existing ``sls`` file. This is often useful when
115a state you are writing needs to ``require`` or ``extend`` a state defined in
116the formula.
117
118Here is an example of a state that uses the :formula_url:`epel-formula` in a
119``require`` declaration which directs Salt to not install the ``python26``
120package until after the EPEL repository has also been installed:
121
122.. code-block:: yaml
123
124    include:
125      - epel
126
127    python26:
128      pkg.installed:
129        - require:
130          - pkg: epel
131
132Including a Formula from a Top File
133-----------------------------------
134
135Some Formula perform completely standalone installations that are not
136referenced from other state files. It is usually cleanest to include these
137Formula directly from a Top File.
138
139For example the easiest way to set up an OpenStack deployment on a single
140machine is to include the :formula_url:`openstack-standalone-formula` directly from
141a :file:`top.sls` file:
142
143.. code-block:: yaml
144
145    base:
146      'myopenstackmaster':
147        - openstack
148
149Quickly deploying OpenStack across several dedicated machines could also be
150done directly from a Top File and may look something like this:
151
152.. code-block:: yaml
153
154    base:
155      'controller':
156        - openstack.horizon
157        - openstack.keystone
158      'hyper-*':
159        - openstack.nova
160        - openstack.glance
161      'storage-*':
162        - openstack.swift
163
164Configuring Formula using Pillar
165--------------------------------
166
167Salt Formulas are designed to work out of the box with no additional
168configuration. However, many Formula support additional configuration and
169customization through :ref:`Pillar <pillar>`. Examples of available options can
170be found in a file named :file:`pillar.example` in the root directory of each
171Formula repository.
172
173.. _extending-formulas:
174
175Using Formula with your own states
176----------------------------------
177
178Remember that Formula are regular Salt States and can be used with all Salt's
179normal state mechanisms. Formula can be required from other States with
180:ref:`requisites-require` declarations, they can be modified using ``extend``,
181they can made to watch other states with :ref:`requisites-watch-in`.
182
183The following example uses the stock :formula_url:`apache-formula` alongside a
184custom state to create a vhost on a Debian/Ubuntu system and to reload the
185Apache service whenever the vhost is changed.
186
187.. code-block:: yaml
188
189    # Include the stock, upstream apache formula.
190    include:
191      - apache
192
193    # Use the watch_in requisite to cause the apache service state to reload
194    # apache whenever the my-example-com-vhost state changes.
195    my-example-com-vhost:
196      file:
197        - managed
198        - name: /etc/apache2/sites-available/my-example-com
199        - watch_in:
200          - service: apache
201
202Don't be shy to read through the source for each Formula!
203
204Reporting problems & making additions
205-------------------------------------
206
207Each Formula is a separate repository on GitHub. If you encounter a bug with a
208Formula please file an issue in the respective repository! Send fixes and
209additions as a pull request. Add tips and tricks to the repository wiki.
210
211Writing Formulas
212================
213
214Each Formula is a separate repository in the `saltstack-formulas`_ organization
215on GitHub.
216
217Get involved creating new Formulas
218----------------------------------
219
220The best way to create new Formula repositories for now is to create a
221repository in your own account on GitHub and notify a SaltStack employee when
222it is ready. We will add you to the Contributors team on the
223`saltstack-formulas`_ organization and help you transfer the repository over.
224Ping a SaltStack employee on IRC (`#salt`_ on LiberaChat), join the
225``#formulas`` channel on the `salt-slack`_ (bridged to ``#saltstack-formulas``
226on LiberaChat) or send an email to the `salt-users`_ mailing list.  Note that
227IRC logs are available at https://freenode.logbot.info/salt,
228https://freenode.logbot.info/saltstack-formulas, and
229http://ngxbot.nginx.org/logs/%23salt/.
230
231There are a lot of repositories in that organization! Team members can manage
232which repositories they are subscribed to on GitHub's watching page:
233https://github.com/watching.
234
235Members of the Contributors team are welcome to participate in reviewing pull
236requests across the Organization. Some repositories will have regular
237contributors and some repositories will not. As you get involved in a
238repository be sure to communicate with any other contributors there on pull
239requests that are large or have breaking changes.
240
241In general it is best to have another Contributor review and merge any pull
242requests that you open. Feel free to `at-mention`_ other regular contributors
243to a repository and request a review. However, there are a lot of formula
244repositories so if a repository does not yet have regular contributors or if
245your pull request has stayed open for more than a couple days feel free to
246"selfie-merge" your own pull request.
247
248.. _`at-mention`: https://help.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax#mentioning-people-and-teams
249.. _`#salt`: https://web.libera.chat/#salt
250
251Style
252-----
253
254Maintainability, readability, and reusability are all marks of a good Salt sls
255file. This section contains several suggestions and examples.
256
257.. code-block:: jinja
258
259    # Deploy the stable master branch unless version overridden by passing
260    # Pillar at the CLI or via the Reactor.
261
262    deploy_myapp:
263      git.latest:
264        - name: git@github.com/myco/myapp.git
265        - version: {{ salt.pillar.get('myapp:version', 'master') }}
266
267Use a descriptive State ID
268``````````````````````````
269
270The ID of a state is used as a unique identifier that may be referenced via
271other states in :ref:`requisites <requisites>`. It must be unique across the
272whole state tree (:ref:`it is a key in a dictionary <id-declaration>`, after
273all).
274
275In addition a state ID should be descriptive and serve as a high-level hint of
276what it will do, or manage, or change. For example, ``deploy_webapp``, or
277``apache``, or ``reload_firewall``.
278
279Use ``module.function`` notation
280````````````````````````````````
281
282So-called "short-declaration" notation is preferred for referencing state
283modules and state functions. It provides a consistent pattern of
284``module.function`` shared between Salt States, the Reactor, Salt
285Mine, the Scheduler, as well as with the CLI.
286
287.. code-block:: yaml
288
289    # Do
290    apache:
291      pkg.installed:
292        - name: httpd
293
294    # Don't
295    apache:
296      pkg:
297        - installed
298        - name: httpd
299
300Salt's state compiler will transform "short-decs" into the longer format
301:ref:`when compiling the human-friendly highstate structure into the
302machine-friendly lowstate structure <state-layers>`.
303
304Specify the ``name`` parameter
305``````````````````````````````
306
307Use a unique and permanent identifier for the state ID and reserve ``name`` for
308data with variability.
309
310The :ref:`name declaration <name-declaration>` is a required parameter for all
311state functions. The state ID will implicitly be used as ``name`` if it is not
312explicitly set in the state.
313
314In many state functions the ``name`` parameter is used for data that varies
315such as OS-specific package names, OS-specific file system paths, repository
316addresses, etc. Any time the ID of a state changes all references to that ID
317must also be changed. Use a permanent ID when writing a state the first time to
318future-proof that state and allow for easier refactors down the road.
319
320Comment state files
321```````````````````
322
323YAML allows comments at varying indentation levels. It is a good practice to
324comment state files. Use vertical whitespace to visually separate different
325concepts or actions.
326
327.. code-block:: yaml
328
329    # Start with a high-level description of the current sls file.
330    # Explain the scope of what it will do or manage.
331
332    # Comment individual states as necessary.
333    update_a_config_file:
334      # Provide details on why an unusual choice was made. For example:
335      #
336      # This template is fetched from a third-party and does not fit our
337      # company norm of using Jinja. This must be processed using Mako.
338      file.managed:
339        - name: /path/to/file.cfg
340        - source: salt://path/to/file.cfg.template
341        - template: mako
342
343      # Provide a description or explanation that did not fit within the state
344      # ID. For example:
345      #
346      # Update the application's last-deployed timestamp.
347      # This is a workaround until Bob configures Jenkins to automate RPM
348      # builds of the app.
349      cmd.run:
350        # FIXME: Joe needs this to run on Windows by next quarter. Switch these
351        # from shell commands to Salt's file.managed and file.replace state
352        # modules.
353        - name: |
354            touch /path/to/file_last_updated
355            sed -e 's/foo/bar/g' /path/to/file_environment
356        - onchanges:
357          - file: a_config_file
358
359Be careful to use Jinja comments for commenting Jinja code and YAML comments
360for commenting YAML code.
361
362.. code-block:: jinja
363
364    # BAD EXAMPLE
365    # The Jinja in this YAML comment is still executed!
366    # {% set apache_is_installed = 'apache' in salt.pkg.list_pkgs() %}
367
368    # GOOD EXAMPLE
369    # The Jinja in this Jinja comment will not be executed.
370    {# {% set apache_is_installed = 'apache' in salt.pkg.list_pkgs() %} #}
371
372Easy on the Jinja!
373------------------
374
375Jinja templating provides vast flexibility and power when building Salt sls
376files. It can also create an unmaintainable tangle of logic and data. Speaking
377broadly, Jinja is best used when kept apart from the states (as much as is
378possible).
379
380Below are guidelines and examples of how Jinja can be used effectively.
381
382Know the evaluation and execution order
383```````````````````````````````````````
384
385High-level knowledge of how Salt states are compiled and run is useful when
386writing states.
387
388The default :conf_minion:`renderer` setting in Salt is Jinja piped to YAML.
389Each is a separate step. Each step is not aware of the previous or following
390step. Jinja is not YAML aware, YAML is not Jinja aware; they cannot share
391variables or interact.
392
393* Whatever the Jinja step produces must be valid YAML.
394* Whatever the YAML step produces must be a valid :ref:`highstate data
395  structure <states-highstate-example>`. (This is also true of the final step
396  for :ref:`any of the alternate renderers <all-salt.renderers>` in Salt.)
397* Highstate can be thought of as a human-friendly data structure; easy to write
398  and easy to read.
399* Salt's state compiler validates the :ref:`highstate <running-highstate>` and
400  compiles it to low state.
401* Low state can be thought of as a machine-friendly data structure. It is a
402  list of dictionaries that each map directly to a function call.
403* Salt's state system finally starts and executes on each "chunk" in the low
404  state. Remember that requisites are evaluated at runtime.
405* The return for each function call is added to the "running" dictionary which
406  is the final output at the end of the state run.
407
408The full evaluation and execution order::
409
410    Jinja -> YAML -> Highstate -> low state -> execution
411
412Avoid changing the underlying system with Jinja
413```````````````````````````````````````````````
414
415Avoid calling commands from Jinja that change the underlying system. Commands
416run via Jinja do not respect Salt's dry-run mode (``test=True``)! This is
417usually in conflict with the idempotent nature of Salt states unless the
418command being run is also idempotent.
419
420Inspect the local system
421````````````````````````
422
423A common use for Jinja in Salt states is to gather information about the
424underlying system. The ``grains`` dictionary available in the Jinja context is
425a great example of common data points that Salt itself has already gathered.
426Less common values are often found by running commands. For example:
427
428.. code-block:: jinja
429
430    {% set is_selinux_enabled = salt.cmd.run('sestatus') == '1' %}
431
432This is usually best done with a variable assignment in order to separate the
433data from the state that will make use of the data.
434
435Gather external data
436````````````````````
437
438One of the most common uses for Jinja is to pull external data into the state
439file. External data can come from anywhere like API calls or database queries,
440but it most commonly comes from flat files on the file system or Pillar data
441from the Salt Master. For example:
442
443.. code-block:: jinja
444
445    {% set some_data = salt.pillar.get('some_data', {'sane default': True}) %}
446
447    {# or #}
448
449    {% import_yaml 'path/to/file.yaml' as some_data %}
450
451    {# or #}
452
453    {% import_json 'path/to/file.json' as some_data %}
454
455    {# or #}
456
457    {% import_text 'path/to/ssh_key.pub' as ssh_pub_key %}
458
459    {# or #}
460
461    {% from 'path/to/other_file.jinja' import some_data with context %}
462
463This is usually best done with a variable assignment in order to separate the
464data from the state that will make use of the data.
465
466Light conditionals and looping
467``````````````````````````````
468
469Jinja is extremely powerful for programmatically generating Salt states. It is
470also easy to overuse. As a rule of thumb, if it is hard to read it will be hard
471to maintain!
472
473Separate Jinja control-flow statements from the states as much as is possible
474to create readable states. Limit Jinja within states to simple variable
475lookups.
476
477Below is a simple example of a readable loop:
478
479.. code-block:: jinja
480
481    {% for user in salt.pillar.get('list_of_users', []) %}
482
483    {# Ensure unique state IDs when looping. #}
484    {{ user.name }}-{{ loop.index }}:
485      user.present:
486        - name: {{ user.name }}
487        - shell: {{ user.shell }}
488
489    {% endfor %}
490
491Avoid putting a Jinja conditionals within Salt states where possible.
492Readability suffers and the correct YAML indentation is difficult to see in the
493surrounding visual noise. Parametrization (discussed below) and variables are
494both useful techniques to avoid this. For example:
495
496.. code-block:: jinja
497
498    {# ---- Bad example ---- #}
499
500    apache:
501      pkg.installed:
502        {% if grains.os_family == 'RedHat' %}
503        - name: httpd
504        {% elif grains.os_family == 'Debian' %}
505        - name: apache2
506        {% endif %}
507
508    {# ---- Better example ---- #}
509
510    {% if grains.os_family == 'RedHat' %}
511    {% set name = 'httpd' %}
512    {% elif grains.os_family == 'Debian' %}
513    {% set name = 'apache2' %}
514    {% endif %}
515
516     apache:
517      pkg.installed:
518        - name: {{ name }}
519
520    {# ---- Good example ---- #}
521
522    {% set name = {
523        'RedHat': 'httpd',
524        'Debian': 'apache2',
525    }.get(grains.os_family) %}
526
527     apache:
528      pkg.installed:
529        - name: {{ name }}
530
531Dictionaries are useful to effectively "namespace" a collection of variables.
532This is useful with parametrization (discussed below). Dictionaries are also
533easily combined and merged. And they can be directly serialized into YAML which
534is often easier than trying to create valid YAML through templating. For
535example:
536
537.. code-block:: jinja
538
539    {# ---- Bad example ---- #}
540
541    haproxy_conf:
542      file.managed:
543        - name: /etc/haproxy/haproxy.cfg
544        - template: jinja
545        {% if 'external_loadbalancer' in grains.roles %}
546        - source: salt://haproxy/external_haproxy.cfg
547        {% elif 'internal_loadbalancer' in grains.roles %}
548        - source: salt://haproxy/internal_haproxy.cfg
549        {% endif %}
550        - context:
551            {% if 'external_loadbalancer' in grains.roles %}
552            ssl_termination: True
553            {% elif 'internal_loadbalancer' in grains.roles %}
554            ssl_termination: False
555            {% endif %}
556
557    {# ---- Better example ---- #}
558
559    {% load_yaml as haproxy_defaults %}
560    common_settings:
561      bind_port: 80
562
563    internal_loadbalancer:
564      source: salt://haproxy/internal_haproxy.cfg
565      settings:
566        bind_port: 8080
567        ssl_termination: False
568
569    external_loadbalancer:
570      source: salt://haproxy/external_haproxy.cfg
571      settings:
572        ssl_termination: True
573    {% endload %}
574
575    {% if 'external_loadbalancer' in grains.roles %}
576    {% set haproxy = haproxy_defaults['external_loadbalancer'] %}
577    {% elif 'internal_loadbalancer' in grains.roles %}
578    {% set haproxy = haproxy_defaults['internal_loadbalancer'] %}
579    {% endif %}
580
581    {% do haproxy.settings.update(haproxy_defaults.common_settings) %}
582
583    haproxy_conf:
584      file.managed:
585        - name: /etc/haproxy/haproxy.cfg
586        - template: jinja
587        - source: {{ haproxy.source }}
588        - context: {{ haproxy.settings | yaml() }}
589
590There is still room for improvement in the above example. For example,
591extracting into an external file or replacing the if-elif conditional with a
592function call to filter the correct data more succinctly. However, the state
593itself is simple and legible, the data is separate and also simple and legible.
594And those suggested improvements can be made at some future date without
595altering the state at all!
596
597Avoid heavy logic and programming
598`````````````````````````````````
599
600Jinja is not Python. It was made by Python programmers and shares many
601semantics and some syntax but it does not allow for abitrary Python function
602calls or Python imports. Jinja is a fast and efficient templating language but
603the syntax can be verbose and visually noisy.
604
605Once Jinja use within an sls file becomes slightly complicated -- long chains
606of if-elif-elif-else statements, nested conditionals, complicated dictionary
607merges, wanting to use sets -- instead consider using a different Salt
608renderer, such as the Python renderer. As a rule of thumb, if it is hard to
609read it will be hard to maintain -- switch to a format that is easier to read.
610
611Using alternate renderers is very simple to do using Salt's "she-bang" syntax
612at the top of the file. The Python renderer must simply return the correct
613:ref:`highstate data structure <states-highstate-example>`. The following
614example is a state tree of two sls files, one simple and one complicated.
615
616``/srv/salt/top.sls``:
617
618.. code-block:: yaml
619
620    base:
621      '*':
622        - common_configuration
623        - roles_configuration
624
625``/srv/salt/common_configuration.sls``:
626
627.. code-block:: yaml
628
629    common_users:
630      user.present:
631        - names:
632          - larry
633          - curly
634          - moe
635
636``/srv/salt/roles_configuration``:
637
638.. code-block:: python
639
640    #!py
641    def run():
642        list_of_roles = set()
643
644        # This example has the minion id in the form 'web-03-dev'.
645        # Easily access the grains dictionary:
646        try:
647            app, instance_number, environment = __grains__["id"].split("-")
648            instance_number = int(instance_number)
649        except ValueError:
650            app, instance_number, environment = ["Unknown", 0, "dev"]
651
652        list_of_roles.add(app)
653
654        if app == "web" and environment == "dev":
655            list_of_roles.add("primary")
656            list_of_roles.add("secondary")
657        elif app == "web" and environment == "staging":
658            if instance_number == 0:
659                list_of_roles.add("primary")
660            else:
661                list_of_roles.add("secondary")
662
663        # Easily cross-call Salt execution modules:
664        if __salt__["myutils.query_valid_ec2_instance"]():
665            list_of_roles.add("is_ec2_instance")
666
667        return {
668            "set_roles_grains": {
669                "grains.present": [{"name": "roles"}, {"value": list(list_of_roles)}],
670            },
671        }
672
673Jinja Macros
674````````````
675
676In Salt sls files Jinja macros are useful for one thing and one thing only:
677creating mini templates that can be reused and rendered on demand. Do not fall
678into the trap of thinking of macros as functions; Jinja is not Python (see
679above).
680
681Macros are useful for creating reusable, parameterized states. For example:
682
683.. code-block:: jinja
684
685    {% macro user_state(state_id, user_name, shell='/bin/bash', groups=[]) %}
686    {{ state_id }}:
687      user.present:
688        - name: {{ user_name }}
689        - shell: {{ shell }}
690        - groups: {{ groups | json() }}
691    {% endmacro %}
692
693    {% for user_info in salt.pillar.get('my_users', []) %}
694    {{ user_state('user_number_' ~ loop.index, **user_info) }}
695    {% endfor %}
696
697Macros are also useful for creating one-off "serializers" that can accept a
698data structure and write that out as a domain-specific configuration file. For
699example, the following macro could be used to write a php.ini config file:
700
701``/srv/salt/php.sls``:
702
703.. code-block:: jinja
704
705    php_ini:
706      file.managed:
707        - name: /etc/php.ini
708        - source: salt://php.ini.tmpl
709        - template: jinja
710        - context:
711            php_ini_settings: {{ salt.pillar.get('php_ini', {}) | json() }}
712
713``/srv/pillar/php.sls``:
714
715.. code-block:: yaml
716
717    php_ini:
718      PHP:
719        engine: 'On'
720        short_open_tag: 'Off'
721        error_reporting: 'E_ALL & ~E_DEPRECATED & ~E_STRICT'
722
723``/srv/salt/php.ini.tmpl``:
724
725.. code-block:: jinja
726
727    {% macro php_ini_serializer(data) %}
728    {% for section_name, name_val_pairs in data.items() %}
729    [{{ section_name }}]
730    {% for name, val in name_val_pairs.items() -%}
731    {{ name }} = "{{ val }}"
732    {% endfor %}
733    {% endfor %}
734    {% endmacro %}
735
736    ; File managed by Salt at <{{ source }}>.
737    ; Your changes will be overwritten.
738
739    {{ php_ini_serializer(php_ini_settings) }}
740
741Abstracting static defaults into a lookup table
742-----------------------------------------------
743
744Separate data that a state uses from the state itself to increases the
745flexibility and reusability of a state.
746
747An obvious and common example of this is platform-specific package names and
748file system paths. Another example is sane defaults for an application, or
749common settings within a company or organization. Organizing such data as a
750dictionary (aka hash map, lookup table, associative array) often provides a
751lightweight namespacing and allows for quick and easy lookups. In addition,
752using a dictionary allows for easily merging and overriding static values
753within a lookup table with dynamic values fetched from Pillar.
754
755A strong convention in Salt Formulas is to place platform-specific data, such
756as package names and file system paths, into a file named :file:`map.jinja`
757that is placed alongside the state files.
758
759The following is an example from the MySQL Formula.
760The :py:func:`grains.filter_by <salt.modules.grains.filter_by>` function
761performs a lookup on that table using the ``os_family`` grain (by default).
762
763The result is that the ``mysql`` variable is assigned to a *subset* of
764the lookup table for the current platform. This allows states to reference, for
765example, the name of a package without worrying about the underlying OS. The
766syntax for referencing a value is a normal dictionary lookup in Jinja, such as
767``{{ mysql['service'] }}`` or the shorthand ``{{ mysql.service }}``.
768
769:file:`map.jinja`:
770
771.. code-block:: jinja
772
773    {% set mysql = salt['grains.filter_by']({
774        'Debian': {
775            'server': 'mysql-server',
776            'client': 'mysql-client',
777            'service': 'mysql',
778            'config': '/etc/mysql/my.cnf',
779            'python': 'python-mysqldb',
780        },
781        'RedHat': {
782            'server': 'mysql-server',
783            'client': 'mysql',
784            'service': 'mysqld',
785            'config': '/etc/my.cnf',
786            'python': 'MySQL-python',
787        },
788        'Gentoo': {
789            'server': 'dev-db/mysql',
790            'client': 'dev-db/mysql',
791            'service': 'mysql',
792            'config': '/etc/mysql/my.cnf',
793            'python': 'dev-python/mysql-python',
794        },
795    }, merge=salt['pillar.get']('mysql:lookup')) %}
796
797Values defined in the map file can be fetched for the current platform in any
798state file using the following syntax:
799
800.. code-block:: jinja
801
802    {% from "mysql/map.jinja" import mysql with context %}
803
804    mysql-server:
805      pkg.installed:
806        - name: {{ mysql.server }}
807      service.running:
808        - name: {{ mysql.service }}
809
810Organizing Pillar data
811``````````````````````
812
813It is considered a best practice to make formulas expect **all**
814formula-related parameters to be placed under second-level ``lookup`` key,
815within a main namespace designated for holding data for particular
816service/software/etc, managed by the formula:
817
818.. code-block:: yaml
819
820    mysql:
821      lookup:
822        version: 5.7.11
823
824Collecting common values
825````````````````````````
826
827Common values can be collected into a *base* dictionary.  This
828minimizes repetition of identical values in each of the
829``lookup_dict`` sub-dictionaries.  Now only the values that are
830different from the base must be specified by the alternates:
831
832:file:`map.jinja`:
833
834.. code-block:: jinja
835
836    {% set mysql = salt['grains.filter_by']({
837        'default': {
838            'server': 'mysql-server',
839            'client': 'mysql-client',
840            'service': 'mysql',
841            'config': '/etc/mysql/my.cnf',
842            'python': 'python-mysqldb',
843        },
844        'Debian': {
845        },
846        'RedHat': {
847            'client': 'mysql',
848            'service': 'mysqld',
849            'config': '/etc/my.cnf',
850            'python': 'MySQL-python',
851        },
852        'Gentoo': {
853            'server': 'dev-db/mysql',
854            'client': 'dev-db/mysql',
855            'python': 'dev-python/mysql-python',
856        },
857    },
858    merge=salt['pillar.get']('mysql:lookup'), base='default') %}
859
860
861Overriding values in the lookup table
862`````````````````````````````````````
863
864Allow static values within lookup tables to be overridden. This is a simple
865pattern which once again increases flexibility and reusability for state files.
866
867The ``merge`` argument in :py:func:`filter_by <salt.modules.grains.filter_by>`
868specifies the location of a dictionary in Pillar that can be used to override
869values returned from the lookup table. If the value exists in Pillar it will
870take precedence.
871
872This is useful when software or configuration files is installed to
873non-standard locations or on unsupported platforms. For example, the following
874Pillar would replace the ``config`` value from the call above.
875
876.. code-block:: yaml
877
878    mysql:
879      lookup:
880        config: /usr/local/etc/mysql/my.cnf
881
882.. note:: Protecting Expansion of Content with Special Characters
883
884  When templating keep in mind that YAML does have special characters for
885  quoting, flows, and other special structure and content.  When a Jinja
886  substitution may have special characters that will be incorrectly parsed by
887  YAML care must be taken.  It is a good policy to use the ``yaml_encode`` or
888  the ``yaml_dquote`` Jinja filters:
889
890  .. code-block:: jinja
891
892      {%- set foo = 7.7 %}
893      {%- set bar = none %}
894      {%- set baz = true %}
895      {%- set zap = 'The word of the day is "salty".' %}
896      {%- set zip = '"The quick brown fox . . ."' %}
897
898      foo: {{ foo|yaml_encode }}
899      bar: {{ bar|yaml_encode }}
900      baz: {{ baz|yaml_encode }}
901      zap: {{ zap|yaml_encode }}
902      zip: {{ zip|yaml_dquote }}
903
904  The above will be rendered as below:
905
906  .. code-block:: yaml
907
908      foo: 7.7
909      bar: null
910      baz: true
911      zap: "The word of the day is \"salty\"."
912      zip: "\"The quick brown fox . . .\""
913
914The :py:func:`filter_by <salt.modules.grains.filter_by>` function performs a
915simple dictionary lookup but also allows for fetching data from Pillar and
916overriding data stored in the lookup table. That same workflow can be easily
917performed without using ``filter_by``; other dictionaries besides data from
918Pillar can also be used.
919
920.. code-block:: jinja
921
922    {% set lookup_table = {...} %}
923    {% do lookup_table.update(salt.pillar.get('my:custom:data')) %}
924
925When to use lookup tables
926`````````````````````````
927
928The ``map.jinja`` file is only a convention within Salt Formulas. This greater
929pattern is useful for a wide variety of data in a wide variety of workflows.
930This pattern is not limited to pulling data from a single file or data source.
931This pattern is useful in States, Pillar and the Reactor, for example.
932
933Working with a data structure instead of, say, a config file allows the data to
934be cobbled together from multiple sources (local files, remote Pillar, database
935queries, etc), combined, overridden, and searched.
936
937Below are a few examples of what lookup tables may be useful for and how they
938may be used and represented.
939
940Platform-specific information
941.............................
942
943An obvious pattern and one used heavily in Salt Formulas is extracting
944platform-specific information such as package names and file system paths in
945a file named ``map.jinja``. The pattern is explained in detail above.
946
947Sane defaults
948.............
949
950Application settings can be a good fit for this pattern. Store default
951settings along with the states themselves and keep overrides and sensitive
952settings in Pillar. Combine both into a single dictionary and then write the
953application config or settings file.
954
955The example below stores most of the Apache Tomcat ``server.xml`` file
956alongside the Tomcat states and then allows values to be updated or augmented
957via Pillar. (This example uses the BadgerFish format for transforming JSON to
958XML.)
959
960``/srv/salt/tomcat/defaults.yaml``:
961
962.. code-block:: yaml
963
964    Server:
965      '@port': '8005'
966      '@shutdown': SHUTDOWN
967      GlobalNamingResources:
968        Resource:
969          '@auth': Container
970          '@description': User database that can be updated and saved
971          '@factory': org.apache.catalina.users.MemoryUserDatabaseFactory
972          '@name': UserDatabase
973          '@pathname': conf/tomcat-users.xml
974          '@type': org.apache.catalina.UserDatabase
975      # <...snip...>
976
977``/srv/pillar/tomcat.sls``:
978
979.. code-block:: yaml
980
981    appX:
982      server_xml_overrides:
983        Server:
984          Service:
985            '@name': Catalina
986            Connector:
987              '@port': '8009'
988              '@protocol': AJP/1.3
989              '@redirectPort': '8443'
990              # <...snip...>
991
992``/srv/salt/tomcat/server_xml.sls``:
993
994.. code-block:: jinja
995
996    {% import_yaml 'tomcat/defaults.yaml' as server_xml_defaults %}
997    {% set server_xml_final_values = salt.pillar.get(
998        'appX:server_xml_overrides',
999        default=server_xml_defaults,
1000        merge=True)
1001    %}
1002
1003    appX_server_xml:
1004      file.serialize:
1005        - name: /etc/tomcat/server.xml
1006        - dataset: {{ server_xml_final_values | json() }}
1007        - formatter: xml_badgerfish
1008
1009The :py:func:`file.serialize <salt.states.file.serialize>` state can provide a
1010shorthand for creating some files from data structures. There are also many
1011examples within Salt Formulas of creating one-off "serializers" (often as Jinja
1012macros) that reformat a data structure to a specific config file format. For
1013example, look at the`Nginx vhosts`_ states or the `php.ini`_ file template.
1014
1015.. _`Nginx vhosts`: https://github.com/saltstack-formulas/nginx-formula/blob/5cad4512/nginx/ng/vhosts_config.sls
1016.. _`php.ini`: https://github.com/saltstack-formulas/php-formula/blob/82e2cd3a/php/ng/files/php.ini
1017
1018Environment specific information
1019................................
1020
1021A single state can be reused when it is parameterized as described in the
1022section below, by separating the data the state will use from the state that
1023performs the work. This can be the difference between deploying *Application X*
1024and *Application Y*, or the difference between production and development. For
1025example:
1026
1027``/srv/salt/app/deploy.sls``:
1028
1029.. code-block:: jinja
1030
1031    {# Load the map file. #}
1032    {% import_yaml 'app/defaults.yaml' as app_defaults %}
1033
1034    {# Extract the relevant subset for the app configured on the current
1035       machine (configured via a grain in this example). #}
1036    {% app = app_defaults.get(salt.grains.get('role')) %}
1037
1038    {# Allow values from Pillar to (optionally) update values from the lookup
1039       table. #}
1040    {% do app_defaults.update(salt.pillar.get('myapp', {})) %}
1041
1042    deploy_application:
1043      git.latest:
1044        - name: {{ app.repo_url }}
1045        - version: {{ app.version }}
1046        - target: {{ app.deploy_dir }}
1047
1048    myco/myapp/deployed:
1049      event.send:
1050        - data:
1051            version: {{ app.version }}
1052        - onchanges:
1053          - git: deploy_application
1054
1055``/srv/salt/app/defaults.yaml``:
1056
1057.. code-block:: yaml
1058
1059    appX:
1060      repo_url: git@github.com/myco/appX.git
1061      target: /var/www/appX
1062      version: master
1063    appY:
1064      repo_url: git@github.com/myco/appY.git
1065      target: /var/www/appY
1066      version: v1.2.3.4
1067
1068Single-purpose SLS files
1069------------------------
1070
1071Each sls file in a Formula should strive to do a single thing. This increases
1072the reusability of this file by keeping unrelated tasks from getting coupled
1073together.
1074
1075As an  example, the base Apache formula should only install the Apache httpd
1076server and start the httpd service. This is the basic, expected behavior when
1077installing Apache. It should not perform additional changes such as set the
1078Apache configuration file or create vhosts.
1079
1080If a formula is single-purpose as in the example above, other formulas, and
1081also other states can ``include`` and use that formula with :ref:`requisites`
1082without also including undesirable or unintended side-effects.
1083
1084The following is a best-practice example for a reusable Apache formula. (This
1085skips platform-specific options for brevity. See the full
1086:formula_url:`apache-formula` for more.)
1087
1088.. code-block:: text
1089
1090    # apache/init.sls
1091    apache:
1092      pkg.installed:
1093        [...]
1094      service.running:
1095        [...]
1096
1097    # apache/mod_wsgi.sls
1098    include:
1099      - apache
1100
1101    mod_wsgi:
1102      pkg.installed:
1103        [...]
1104        - require:
1105          - pkg: apache
1106
1107    # apache/conf.sls
1108    include:
1109      - apache
1110
1111    apache_conf:
1112      file.managed:
1113        [...]
1114        - watch_in:
1115          - service: apache
1116
1117To illustrate a bad example, say the above Apache formula installed Apache and
1118also created a default vhost. The mod_wsgi state would not be able to include
1119the Apache formula to create that dependency tree without also installing the
1120unneeded default vhost.
1121
1122:ref:`Formulas should be reusable <extending-formulas>`. Avoid coupling
1123unrelated actions together.
1124
1125.. _conventions-formula-parameterization:
1126
1127Parameterization
1128----------------
1129
1130*Parameterization is a key feature of Salt Formulas* and also for Salt
1131States. Parameterization allows a single Formula to be reused across many
1132operating systems; to be reused across production, development, or staging
1133environments; and to be reused by many people all with varying goals.
1134
1135Writing states, specifying ordering and dependencies is the part that takes the
1136longest to write and to test. Filling those states out with data such as users
1137or package names or file locations is the easy part. How many users, what those
1138users are named, or where the files live are all implementation details that
1139**should be parameterized**. This separation between a state and the data that
1140populates a state creates a reusable formula.
1141
1142In the example below the data that populates the state can come from anywhere
1143-- it can be hard-coded at the top of the state, it can come from an external
1144file, it can come from Pillar, it can come from an execution function call, or
1145it can come from a database query. The state itself doesn't change regardless
1146of where the data comes from. Production data will vary from development data
1147will vary from data from one company to another, however the state itself stays
1148the same.
1149
1150.. code-block:: jinja
1151
1152    {% set user_list = [
1153        {'name': 'larry', 'shell': 'bash'},
1154        {'name': 'curly', 'shell': 'bash'},
1155        {'name': 'moe', 'shell': 'zsh'},
1156    ] %}
1157
1158    {# or #}
1159
1160    {% set user_list = salt['pillar.get']('user_list') %}
1161
1162    {# or #}
1163
1164    {% load_json "default_users.json" as user_list %}
1165
1166    {# or #}
1167
1168    {% set user_list = salt['acme_utils.get_user_list']() %}
1169
1170    {% for user in list_list %}
1171    {{ user.name }}:
1172      user.present:
1173        - name: {{ user.name }}
1174        - shell: {{ user.shell }}
1175    {% endfor %}
1176
1177Configuration
1178-------------
1179
1180Formulas should strive to use the defaults of the underlying platform, followed
1181by defaults from the upstream project, followed by sane defaults for the
1182formula itself.
1183
1184As an example, a formula to install Apache **should not** change the default
1185Apache configuration file installed by the OS package. However, the Apache
1186formula **should** include a state to change or override the default
1187configuration file.
1188
1189Pillar overrides
1190----------------
1191
1192Pillar lookups must use the safe :py:func:`~salt.modules.pillar.get`
1193and must provide a default value. Create local variables using the Jinja
1194``set`` construct to increase readability and to avoid potentially hundreds or
1195thousands of function calls across a large state tree.
1196
1197.. code-block:: jinja
1198
1199    {% from "apache/map.jinja" import apache with context %}
1200    {% set settings = salt['pillar.get']('apache', {}) %}
1201
1202    mod_status:
1203      file.managed:
1204        - name: {{ apache.conf_dir }}
1205        - source: {{ settings.get('mod_status_conf', 'salt://apache/mod_status.conf') }}
1206        - template: {{ settings.get('template_engine', 'jinja') }}
1207
1208Any default values used in the Formula must also be documented in the
1209:file:`pillar.example` file in the root of the repository. Comments should be
1210used liberally to explain the intent of each configuration value. In addition,
1211users should be able copy-and-paste the contents of this file into their own
1212Pillar to make any desired changes.
1213
1214Scripting
1215---------
1216
1217Remember that both State files and Pillar files can easily call out to Salt
1218:ref:`execution modules <all-salt.modules>` and have access to all the system
1219grains as well.
1220
1221.. code-block:: jinja
1222
1223    {% if '/storage' in salt['mount.active']() %}
1224    /usr/local/etc/myfile.conf:
1225      file:
1226        - symlink
1227        - target: /storage/myfile.conf
1228    {% endif %}
1229
1230Jinja macros to encapsulate logic or conditionals are discouraged in favor of
1231:ref:`writing custom execution modules  <writing-execution-modules>` in Python.
1232
1233Repository structure
1234====================
1235
1236A basic Formula repository should have the following layout:
1237
1238.. code-block:: text
1239
1240    foo-formula
1241    |-- foo/
1242    |   |-- map.jinja
1243    |   |-- init.sls
1244    |   `-- bar.sls
1245    |-- CHANGELOG.rst
1246    |-- LICENSE
1247    |-- pillar.example
1248    |-- README.rst
1249    `-- VERSION
1250
1251.. seealso:: :formula_url:`template-formula`
1252
1253    The :formula_url:`template-formula` repository has a pre-built layout that
1254    serves as the basic structure for a new formula repository. Just copy the
1255    files from there and edit them.
1256
1257``README.rst``
1258--------------
1259
1260The README should detail each available ``.sls`` file by explaining what it
1261does, whether it has any dependencies on other formulas, whether it has a
1262target platform, and any other installation or usage instructions or tips.
1263
1264A sample skeleton for the ``README.rst`` file:
1265
1266.. code-block:: restructuredtext
1267
1268    ===
1269    foo
1270    ===
1271
1272    Install and configure the FOO service.
1273
1274    **NOTE**
1275
1276    See the full `Salt Formulas installation and usage instructions
1277    <https://docs.saltproject.io/en/latest/topics/development/conventions/formulas.html>`_.
1278
1279    Available states
1280    ================
1281
1282    .. contents::
1283        :local:
1284
1285    ``foo``
1286    -------
1287
1288    Install the ``foo`` package and enable the service.
1289
1290    ``foo.bar``
1291    -----------
1292
1293    Install the ``bar`` package.
1294
1295``CHANGELOG.rst``
1296-----------------
1297
1298The ``CHANGELOG.rst`` file should detail the individual versions, their
1299release date and a set of bullet points for each version highlighting the
1300overall changes in a given version of the formula.
1301
1302A sample skeleton for the `CHANGELOG.rst` file:
1303
1304:file:`CHANGELOG.rst`:
1305
1306.. code-block:: restructuredtext
1307
1308    foo formula
1309    ===========
1310
1311    0.0.2 (2013-01-01)
1312
1313    - Re-organized formula file layout
1314    - Fixed filename used for upstart logger template
1315    - Allow for pillar message to have default if none specified
1316
1317Versioning
1318----------
1319
1320Formula are versioned according to Semantic Versioning, https://semver.org/.
1321
1322.. note::
1323
1324    Given a version number MAJOR.MINOR.PATCH, increment the:
1325
1326    #. MAJOR version when you make incompatible API changes,
1327    #. MINOR version when you add functionality in a backwards-compatible manner, and
1328    #. PATCH version when you make backwards-compatible bug fixes.
1329
1330    Additional labels for pre-release and build metadata are available as extensions
1331    to the MAJOR.MINOR.PATCH format.
1332
1333Formula versions are tracked using Git tags as well as the ``VERSION`` file
1334in the formula repository. The ``VERSION`` file should contain the currently
1335released version of the particular formula.
1336
1337Testing Formulas
1338================
1339
1340A smoke-test for invalid Jinja, invalid YAML, or an invalid Salt state
1341structure can be performed by with the :py:func:`state.show_sls
1342<salt.modules.state.show_sls>` function:
1343
1344.. code-block:: bash
1345
1346    salt '*' state.show_sls apache
1347
1348Salt Formulas can then be tested by running each ``.sls`` file via
1349:py:func:`state.apply <salt.modules.state.apply_>` and checking the output for
1350the success or failure of each state in the Formula. This should be done for
1351each supported platform.
1352
1353.. ............................................................................
1354
1355.. _`saltstack-formulas`: https://github.com/saltstack-formulas
1356