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