<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tryggvi Björgvinsson</title>
    <description>The latest articles on DEV Community by Tryggvi Björgvinsson (@trickvi).</description>
    <link>https://dev.to/trickvi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F8037%2F3489241.png</url>
      <title>DEV Community: Tryggvi Björgvinsson</title>
      <link>https://dev.to/trickvi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/trickvi"/>
    <language>en</language>
    <item>
      <title>How do you manage multi-project documentation?</title>
      <dc:creator>Tryggvi Björgvinsson</dc:creator>
      <pubDate>Sun, 09 Feb 2020 12:07:50 +0000</pubDate>
      <link>https://dev.to/trickvi/how-do-you-manage-multi-project-documentation-1j46</link>
      <guid>https://dev.to/trickvi/how-do-you-manage-multi-project-documentation-1j46</guid>
      <description>&lt;p&gt;At work we have lots and lots of software projects and we believe having good documentation makes us more efficient and the code more maintainable.&lt;/p&gt;

&lt;p&gt;The problem is how to manage this documentation. We used to have a central wiki system where we would document projects but because it was disconnected from the code, the docs got outdated.&lt;/p&gt;

&lt;p&gt;We run our own Gitlab instance so we decided to move documentation into the Gitlab projects where we'd write quick intros and &lt;em&gt;get-the-project-running&lt;/em&gt; docs in a README file and more detailed docs in the project wiki. That allowed the docs to be closer to the code and at least the READMEs checked via merge requests.&lt;/p&gt;

&lt;p&gt;But non-code docs (processes etc.) are then more difficult to manage. We have some documentation repos to get around it, and a central doc repo with links to all the other repos and their docs.&lt;/p&gt;

&lt;p&gt;How do you manage documentation for multiple projects?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@viktortalashuk"&gt;Viktor Talashuk&lt;/a&gt; on &lt;a href="https://unsplash.com/"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Jinja templates in Salt</title>
      <dc:creator>Tryggvi Björgvinsson</dc:creator>
      <pubDate>Sun, 26 Jan 2020 23:58:59 +0000</pubDate>
      <link>https://dev.to/trickvi/jinja-templates-in-salt-457o</link>
      <guid>https://dev.to/trickvi/jinja-templates-in-salt-457o</guid>
      <description>&lt;p&gt;We ended the &lt;a href="https://dev.to/trickvi/grains-and-pillars-in-salt-15aa"&gt;second article&lt;/a&gt; in this series about the configuration management tool Salt, with a problem. Throughout that post we defined roles in grains and configured what packages we wanted to install in the pillar. But at the end of the post, we were still installing packages by targeting the grains in the state.&lt;/p&gt;

&lt;p&gt;To make the states really dynamic, we don't want to hard-code roles and packages in them. We want the state logic to be flexible. We want to configure our environment in salt's own key value store (the grains and the pillar). To do that, we need to go deeper into templating.&lt;/p&gt;

&lt;h1&gt;
  
  
  Renderers in Salt
&lt;/h1&gt;

&lt;p&gt;By default, every salt state file is passed through a renderer before being processed by salt. The default renderer in salt is a Python templating engine called &lt;a href="https://jinja.palletsprojects.com/"&gt;Jinja&lt;/a&gt;. The output of the Jinja process is then passed to a YAML processor which parses the data in the files into a data structure usable by salt. This rendering pipeline is commonly represented as &lt;code&gt;jinja|yaml&lt;/code&gt; (if you know Unix pipes, you'll understand why).&lt;/p&gt;

&lt;p&gt;These represent the two types of renderers in salt. Jinja is a text renderer, that takes in some text and returns some text. YAML is a data renderer, that takes in some text and returns a data structure.&lt;/p&gt;

&lt;p&gt;There are other text renderers available such as genshi and mako, both of them templating engines. There are also other data renderers, such as json.&lt;/p&gt;

&lt;p&gt;In this post we'll stick to the default &lt;code&gt;jinja|yaml&lt;/code&gt; and focus on the text renderer, namely Jinja.&lt;/p&gt;

&lt;h1&gt;
  
  
  Jinja
&lt;/h1&gt;

&lt;p&gt;As a templating language, Jinja allows us to generate output text files by processing a bit of logic placed in a source text file, a template file as it is called. Jinja even allows us to use a bit of Python for the logic.&lt;/p&gt;

&lt;p&gt;For example let's say we would want to turn a Python list into a Markdown list we could have a template like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight jinja"&gt;&lt;code&gt;Here is a markdown list for you:
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'apple'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'grapple'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wroom'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
* &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;word&lt;/span&gt; &lt;span class="cp"&gt;}}{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This would produce the following output when we run it through the Jinja renderer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here is a markdown list for you:

* apple
* grapple
* wroom
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's break this example down a bit. As you can see, Jinja's templating language is based on curly brackets &lt;code&gt;{}&lt;/code&gt; accompanied by another sign denoting statements, expressions, or even comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Statements
&lt;/h2&gt;

&lt;p&gt;Statements are wrapped in curly brackets accompanied by &lt;code&gt;%&lt;/code&gt;. The for loop statement in the example above is written as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% for word in ['apple', 'grapple', 'wroom'] %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is resembles (and is) the Python way of defining a for loop (excluding the colon needed in Python). An &lt;code&gt;if&lt;/code&gt; statement follows the same curly percentage format, so an &lt;code&gt;if&lt;/code&gt; statement to check if the word contains the letter a, would be written as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% if 'a' in word %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Python uses indents to close of blocks for statements like these. That would raise problems in templates because it wouldn't be clear when you want the template to output white space or when you're just indenting a statement. To get around this problem, Jinja closes blocks with a Jinja-specific statement.&lt;/p&gt;

&lt;p&gt;To close off a &lt;code&gt;for&lt;/code&gt; loop statement you would use &lt;code&gt;{% endfor %}&lt;/code&gt; while closing an &lt;code&gt;if&lt;/code&gt; statement requires &lt;code&gt;{% endif %}&lt;/code&gt;. So when you find a &lt;code&gt;for&lt;/code&gt; loop in a Jinja template you will see where the block ends by looking for the &lt;code&gt;endfor&lt;/code&gt; statement.&lt;/p&gt;

&lt;p&gt;Closing statements like this is only required for code blocks like loops or conditionals. There are statements that do not require it, such as the &lt;code&gt;set&lt;/code&gt; statement which assigns a value to a variable. If we would want to assign the string &lt;code&gt;'snow'&lt;/code&gt; to a variable called &lt;code&gt;weather&lt;/code&gt; we'd write that as a statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% set weather = 'snow' %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We do not close a statement like this with a &lt;code&gt;endset&lt;/code&gt; because it isn't a code block. It's just a single line statement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expressions
&lt;/h2&gt;

&lt;p&gt;Expressions to print out something like a variable are expressed with double curly brackets &lt;code&gt;{{ }}&lt;/code&gt;. In the example we want to print out in each line in our list an asterisk followed by the variable &lt;code&gt;word&lt;/code&gt; so we write that as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* {{ word }}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The output will be an asterisk followed by white space, because Jinja prints out all non-Jinja symbols as is. Thereafter it would print out the variable word (the Jinja expression). If the value of the variable &lt;code&gt;word&lt;/code&gt; is apple it would output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* apple
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  White space removal
&lt;/h2&gt;

&lt;p&gt;Jinja prints out all symbols as is. That means white space becomes slightly problematic, because white space and line breaks (the invisible &lt;code&gt;\n&lt;/code&gt; in our templates) all get printed out too. The example with the apple, grapple, wroom may be difficult to parse as it is. If we would instead write it as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight jinja"&gt;&lt;code&gt;Here is a markdown list for you:

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'apple'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'grapple'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wroom'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
  * &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;word&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We'd end up with an output containing a lot more white space:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here is a markdown list for you:


  * apple

  * grapple

  * wroom

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Jinja gives us a nice way to remove white space. If instead of opening the statement with a &lt;code&gt;{%&lt;/code&gt; you open it with a &lt;code&gt;{%-&lt;/code&gt; (that is, with an appended minus sign), you remove all white space in front of the statement. If you prepend the minus sign to the closing bracket &lt;code&gt;%}&lt;/code&gt; to get &lt;code&gt;-%}&lt;/code&gt; you remove all white space after the statement.&lt;/p&gt;

&lt;p&gt;So to make our example more readable, we could add a single minus sign when we close our &lt;code&gt;for&lt;/code&gt; statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight jinja"&gt;&lt;code&gt;Here is a markdown list for you:

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'apple'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'grapple'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wroom'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
* &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;word&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This would give us the output we want, but in a more readable way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other nice features
&lt;/h2&gt;

&lt;p&gt;There are plenty of other features in Jinja that I won't go into but I do recommend you read the &lt;a href="https://jinja.palletsprojects.com/en/2.10.x/templates/"&gt;official Jinja documentation&lt;/a&gt;. There are two honorable mentions that I recommend you to check out.&lt;/p&gt;

&lt;p&gt;One is filters. Filters are functions you can use to modify variables. Filters are applied using the pipe and can be chained (similar to Unix pipes), i.e. &lt;code&gt;variable|filter|filter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we would for example want to print out a random word from our list we could use the built-in &lt;code&gt;random&lt;/code&gt; filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight jinja"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;wordlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'apple'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'grapple'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wroom'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
Word of the day is: &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;wordlist&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This would print out a string with one of the strings randomly chosen, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Word of the day is: apple
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I recommend going through the &lt;a href="https://jinja.palletsprojects.com/en/2.10.x/templates/#builtin-filters"&gt;list of built-in filters in Jinja&lt;/a&gt; and get to know most of the filters.&lt;/p&gt;

&lt;p&gt;Another feature which is nice is tests for variables, mostly because of syntactic sugar that makes the templates easier to read. Tests are done by adding an &lt;code&gt;is&lt;/code&gt; behind the variable followed by a function. For example we can combine a lot of what we've discussed into our very own Jinja &lt;a href="https://en.wikipedia.org/wiki/Fizz_buzz"&gt;fizzbuzz&lt;/a&gt; generator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight jinja"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nb"&gt;number&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;22&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
    &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;modulo3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="nb"&gt;number&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="nb"&gt;divisibleby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
    &lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;modulo3&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;fizz&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

    &lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;number&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="nb"&gt;divisibleby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;buzz
    &lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;elif&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nv"&gt;modulo3&lt;/span&gt; &lt;span class="cp"&gt;%}{{&lt;/span&gt; &lt;span class="nv"&gt;number&lt;/span&gt; &lt;span class="cp"&gt;}}{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will output the fizzbuzz counter for a few numbers. In this we use a &lt;code&gt;for&lt;/code&gt; loops, &lt;code&gt;if&lt;/code&gt; and &lt;code&gt;set&lt;/code&gt; statements. We print out variables and we use a built-in test called &lt;code&gt;divisibleby&lt;/code&gt; that checks if the variable is divisible by a provided number. There are a few &lt;a href="https://jinja.palletsprojects.com/en/2.10.x/templates/#builtin-tests"&gt;built-in tests&lt;/a&gt; in Jinja that can make the code easier to read.&lt;/p&gt;

&lt;p&gt;But let's get back to the series. We're here to learn about Salt!&lt;/p&gt;

&lt;h1&gt;
  
  
  Using Jinja in Salt
&lt;/h1&gt;

&lt;p&gt;In our last post we had defined what packages to install based on roles in our pillar. However, we were still hard coding these packages in our states. We should aim to keep configurations in our pillar and the state logic, not the configuration, in our states.&lt;/p&gt;

&lt;p&gt;So to remind ourselves, the pillar has a variable called &lt;code&gt;packages&lt;/code&gt; which contains a dictionary of package names as keys, and a description as the value. For example, this was the pillar for our cow host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cow-e9e01577bbd8:
    ----------
    packages:
        ----------
        cowsay:
            A program with a cow
        fortune:
            A program for a fortune teller
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's create a new state file and call it &lt;code&gt;roles.sls&lt;/code&gt;. This file is going to have the logic we need to install the packages based on the pillar with the help of Jinja.&lt;/p&gt;

&lt;p&gt;We still have the same problem as before, where Ubuntu installs in a different location than Alpine so we must create symbolic links. Now you however understand the logic a little bit better. This was the trick we did:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% if grains['os'] == 'Ubuntu' %}
make cowsay available on ubuntu:
  file.symlink:
    - name: /usr/bin/cowsay
    - target: /usr/games/cowsay
{% endif %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We used Jinja's &lt;code&gt;if&lt;/code&gt; statement to check if the operating system defined in the grains is Ubuntu. If it is, then we create this state. If not, then we don't do anything.&lt;/p&gt;

&lt;p&gt;Salt adds grains and the pillar to the global context of Jinja, and makes them available as variables. That's why we're able to write &lt;code&gt;grains['os']&lt;/code&gt;. Similarly we can use the Jinja global variable &lt;code&gt;pillar&lt;/code&gt; to access the pillar.&lt;/p&gt;

&lt;p&gt;Back to the &lt;code&gt;roles.sls&lt;/code&gt;. We'll put this Jinja/YAML logic into the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% if 'packages' in pillar %}
install role tools:
  pkg.installed:
    - pkgs: {% for package in pillar['packages'] %}
      - {{ package }}
      {% endfor %}

{% if grains['os'] == 'Ubuntu' %}
{% for package in pillar['packages'] %}
make {{ package }} available on ubuntu:
  file.symlink:
    - name: /usr/bin/{{ package }}
    - target: /usr/games/{{ package }}
{% endfor %}
{% endif %}

{% endif %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's go through this step by step. First we check if the pillar has some packages for us. If it does we define a state to install all role tools. This state calls the &lt;code&gt;installed&lt;/code&gt; function in the &lt;code&gt;pkg&lt;/code&gt; module and then supplies the list of all packages names by looping through the packages we get from the pillar.&lt;/p&gt;

&lt;p&gt;Thereafter we create a separate state with a symbolic link for every package (again accessing the pillar packages, but only if we are on an Ubuntu machine). Now this assumes that all packages we install will be installed in &lt;code&gt;/usr/games&lt;/code&gt; on Ubuntu. That may not be the case, but I'll leave it as an exercise for you to use the pillar value and some logic in the state to only do it if we explicitly tell salt to create this symbolic link.&lt;/p&gt;

&lt;p&gt;In the next post I'll show you one way to achieve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying our package logic
&lt;/h2&gt;

&lt;p&gt;We can now safely drop the rest of the sls files we created for each of the roles. Go ahead and remove &lt;code&gt;cow.sls&lt;/code&gt;, &lt;code&gt;fortune.sls&lt;/code&gt;, and &lt;code&gt;locomotive.sls&lt;/code&gt; from the state directory.&lt;/p&gt;

&lt;p&gt;Then we need to update our &lt;code&gt;top.sls&lt;/code&gt; state file to use the new &lt;code&gt;roles.sls&lt;/code&gt; instead of the hard-coded state files we just deleted. Thankfully this is easy because we define packages in the pillar and only worry about state logic.&lt;/p&gt;

&lt;p&gt;We could just apply this to all salt minions, because we do check if there are packages in the pillar before we apply them. However, let's just limit ourselves to those minions that have a role defined in the grains (so we can use this opportunity to remind ourselves about how to target with grains). Our &lt;code&gt;top.sls&lt;/code&gt; file will then look like this now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;base:
  'roles:*':
    - match: grain
    - roles
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's try it out. Again, we should kill all the machines and start with a clean slate. Type in these commands and answer yes when prompted.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose stop
docker-compose rm
docker-compose up -d
docker-compose exec salt bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once we're attached to the salt master, we accept the salt keys for the new minions (press enter to accept them when prompted):&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt-key -A
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then we simply apply the state to all machines:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt '*' state.apply
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You should see all states succeed. That's super.&lt;/p&gt;

&lt;p&gt;So now we're at a place where, if we want to define new packages for a new role, we just define it in the pillar with some simple configurations and the rest is automatic.&lt;/p&gt;

&lt;p&gt;Now we're starting to see the true potential of using Salt to manage IT infrastructure.&lt;/p&gt;




&lt;p&gt;&lt;a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"&gt;&lt;img alt="Creative Commons License" src="https://res.cloudinary.com/practicaldev/image/fetch/s--AAgNuxfG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.creativecommons.org/l/by-sa/4.0/80x15.png"&gt;&lt;/a&gt; This work is licensed under a &lt;a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"&gt;Creative Commons Attribution-ShareAlike 4.0 International License&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover image by Motokoka: &lt;a href="https://commons.wikimedia.org/wiki/File:Awashima_jinja_shrine_4.jpg"&gt;Awashima jinja shrine&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>salt</category>
      <category>saltstack</category>
      <category>jinja</category>
      <category>devops</category>
    </item>
    <item>
      <title>Grains and pillars in Salt</title>
      <dc:creator>Tryggvi Björgvinsson</dc:creator>
      <pubDate>Thu, 16 Jan 2020 23:13:14 +0000</pubDate>
      <link>https://dev.to/trickvi/grains-and-pillars-in-salt-15aa</link>
      <guid>https://dev.to/trickvi/grains-and-pillars-in-salt-15aa</guid>
      <description>&lt;p&gt;The &lt;a href="https://dev.to/trickvi/let-s-get-started-with-salt-55hb"&gt;first article in this series&lt;/a&gt; went over an example of how to set up the &lt;a href="https://saltstack.com" rel="noopener noreferrer"&gt;Salt&lt;/a&gt; configuration management tool to automate a computer infrastructure.&lt;/p&gt;

&lt;p&gt;Salt is very powerful and in this next section of the series, we'll take a look at two important components, &lt;em&gt;grains&lt;/em&gt; and the &lt;em&gt;pillar&lt;/em&gt;. At a first glance grains and the pillar seem to serve the same purpose but there is an important distinction between them.&lt;/p&gt;

&lt;h1&gt;
  
  
  What are grains and the pillar?
&lt;/h1&gt;

&lt;p&gt;Grains and the pillar are used by Salt to store data about the minion it is controlling. They are both in the end a key value store. In fact they are what is called in Python a &lt;em&gt;dictionary&lt;/em&gt; (if you're into Perl or Ruby, they're known as a &lt;em&gt;hash&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;For example, the following is an example of some grains on the alpine minion we created in the first article in the series:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

ipv4:
    - 127.0.0.1
    - 172.25.0.3
ipv6:
kernel:
    Linux
kernelrelease:
    4.15.0-65-generic


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you want to check out all of the grains, you can execute the following command in the salt master docker container (to get a shell, type this in the same directory as you keep your docker compose file: &lt;code&gt;docker-compose exec salt bash&lt;/code&gt;):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt 'locomotive-*' grains.items
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The pillar data looks the same except the keys and values are different. However if you run the command to list the pillar items on your salt master, you won't get a similar result now:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt 'locomotive-*' pillar.items
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You'll get nothing as a result:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

locomotive-03f0f2574592:
    ----------


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That's because while many grains are automatically generated when the salt minion starts up, the pillar data isn't. In fact the salt minion doesn't ever generate the pillar data. That's the big distinction between grains and the pillar.&lt;/p&gt;

&lt;p&gt;Grains are generated and/or stored on the minions themselves. Pillar data are generated and/or stored on the salt master.&lt;/p&gt;

&lt;p&gt;This distinction allows the pillar data to contain sensitive data you don't want lying around on the minion. It could be minion configurations or variables you want to manage in a central place, well or just any other data.&lt;/p&gt;

&lt;p&gt;Grains on the other hand are useful for information you won't necessarily know when you configure the minion like IP addresses, operating system, CPU information etc. It can also be arbitrary data, like a role you want to configure and store on the minion itself.&lt;/p&gt;

&lt;p&gt;For example you could on the machine create a grain called &lt;code&gt;role&lt;/code&gt; with value &lt;code&gt;webserver&lt;/code&gt;. Then the salt master could pick up that grain and from there decide how to configure it, without changing anything in the salt master configuration files. The pillar data for that machine may then include information such as software licenses you need for a particular application, users allowed to log into web servers etc.&lt;/p&gt;

&lt;p&gt;One good thing to keep in mind is that grains are fairly static, they change infrequently because they're just information about the system (either automatically generated or custom data). The pillar on the other hand is better suited for data that will change more frequently.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configuring grains
&lt;/h1&gt;

&lt;p&gt;Most of the grains you normally need are automatically generated. However, if you ever find yourself in a position where you want to configure them you can store them in the minion configuration file on the machine. Location depends on operating system, for example on Unix systems you find it at &lt;code&gt;/etc/salt/minion&lt;/code&gt; except in FreeBSD where it's stored at &lt;code&gt;/usr/local/etc/salt/minion&lt;/code&gt;. On Windows machines the configuration is found at &lt;code&gt;C:\salt\conf\minion&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The configuration file is a YAML file and you can add &lt;code&gt;grains&lt;/code&gt; as a root element in the configuration file with the grains you want to store. For example you could add this to the minion config:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;grains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;webserver&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This would make the grain &lt;code&gt;roles&lt;/code&gt; available with a list as its value, containing the webserver role (it doesn't have to be a list of strings but I try to choose a data structure applicable to the context and a server could have multiple roles so that's why I'd use a list for the roles).&lt;/p&gt;

&lt;p&gt;Another place where you can store grains is in &lt;code&gt;/etc/salt/grains&lt;/code&gt;. I like explicit so I like this location more than the minion config. It's a YAML file but if you want to use &lt;code&gt;/etc/salt/grains&lt;/code&gt; you don't need the root &lt;code&gt;grains&lt;/code&gt; element. So the equivalent &lt;code&gt;/etc/salt/grains&lt;/code&gt; file to the one above is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;webserver&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's adapt the example from the first post in this series and configure grains for the two salt minions.&lt;/p&gt;

&lt;p&gt;Let's create two grains files, one for our alpine container and another for the Ubuntu container, defining roles for the machines. Create a file called &lt;code&gt;alpine.grains&lt;/code&gt; in the same location as your &lt;code&gt;docker-compose.yml&lt;/code&gt; file (we're going to mount it later on). Here are the contents of the &lt;code&gt;alpine.grains&lt;/code&gt; file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;locomotive&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fortuneteller&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Create a similar file for the Ubuntu container called &lt;code&gt;ubuntu.grains&lt;/code&gt; with these contents:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cow&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fortuneteller&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now let's update our &lt;code&gt;docker-compose.yml&lt;/code&gt; We just have to mount the two files we created at &lt;code&gt;/etc/salt/grains&lt;/code&gt; on both machines so the &lt;code&gt;docker-compose.yml&lt;/code&gt; file should look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;salt-master&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./master.config:/etc/salt/master&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./states:/srv/salt&lt;/span&gt;
  &lt;span class="na"&gt;alpine-minion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine-minion&lt;/span&gt;
      &lt;span class="s"&gt;- ./alpine.grains:/etc/salt/grains&lt;/span&gt;
  &lt;span class="na"&gt;ubuntu-minion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-minion&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./ubuntu.grains:/etc/salt/grains&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let' get all of them up and running using these new grains and therefore their new roles. Run this command from the same directory:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Still running first post containers?
&lt;/h2&gt;

&lt;p&gt;If you shut down and removed the containers you created in the first post, you'll just have to accept keys like you did in the first post.&lt;/p&gt;

&lt;p&gt;If you are still running the same containers, you should see the minions being updated (recreated), but not the salt master (it's up to date). &lt;/p&gt;

&lt;p&gt;If you try to check things out with salt master now, you'll notice that it doesn't manage your salt minions any longer. The reason is that because we recreated the docker containers they now have a new minion id (it's a new machine basically). So if you're not following this guide adamantly and want to play around, you'll have to accept the keys as shown in the first blog post in the series.&lt;/p&gt;

&lt;p&gt;Now we're ready to use those newly defined roles somehow.&lt;/p&gt;
&lt;h1&gt;
  
  
  Usefulness of grains
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Targeting minions
&lt;/h2&gt;

&lt;p&gt;Grains are very useful for targeting minions. Remember in the first article how we targeted the machines using the minion id? We can also target them using grains.&lt;/p&gt;

&lt;p&gt;Let's update the configuration file for the salt master to target based on grains instead of minion ids. First let's create a special state file for the &lt;code&gt;fortune&lt;/code&gt; program. Remove the line in both &lt;code&gt;cow.sls&lt;/code&gt; and &lt;code&gt;locomotive.sls&lt;/code&gt; where we tell salt to install the &lt;code&gt;fortune&lt;/code&gt; package. Then create a similar state file called &lt;code&gt;fortune.sls&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;install fortuneteller tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pkg.installed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pkgs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fortune&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Alright. Now we can change the &lt;code&gt;top.sls&lt;/code&gt; file to target based on grains instead of minion ids. The pattern for it is to say &lt;code&gt;key:value&lt;/code&gt;. Then you can, before you list what state files to include, begin by telling salt what you want to match with your pattern (in our case the grain).&lt;/p&gt;

&lt;p&gt;So to apply the &lt;code&gt;apache.sls&lt;/code&gt; file to machines with the role &lt;code&gt;webserver&lt;/code&gt; you would write something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;roles:webserver'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grain&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apache&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Notice the &lt;code&gt;'&lt;/code&gt; around &lt;code&gt;roles:webserver&lt;/code&gt;. This is so that the YAML file gets processed correctly. There is a shorthand version to achieve the same. You could instead write this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;G@roles:webserver'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apache&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's modify the top file so we target each role specifically and apply the appropriate state files. The top file could look like this then (I choose the more explicit targeting pattern):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;roles:cow'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grain&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cow&lt;/span&gt;
  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;roles:fortuneteller'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grain&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fortune&lt;/span&gt;
  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;roles:locomotive'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grain&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;locomotive&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Logic in state files
&lt;/h2&gt;

&lt;p&gt;Grains can also be useful in the state files themselves. For example, we can check the operating system and do different things based on the operating system. You'll see more on this when we go through templating in the Salt files but to give you a taste, let's solve the problem we had in the first article of the series, where the Ubuntu machine installed the packages in &lt;code&gt;/usr/games/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To solve that problem we'll put an if statement that checks the &lt;code&gt;os&lt;/code&gt; grain to see if it is an Ubuntu operating system. If it is, then we make a symbolic link to &lt;code&gt;/usr/bin&lt;/code&gt;. We'll need to change all state files because Ubuntu does this with all packages.&lt;/p&gt;

&lt;p&gt;Before we change the files, let's first see how we look up the grain value in the state file. The grain we're after to check the operating system is the &lt;code&gt;os&lt;/code&gt; grain. It becomes available as &lt;code&gt;grains['os']&lt;/code&gt; (if you know Python, you'll understand that this is just a dictionary lookup).&lt;/p&gt;

&lt;p&gt;So the trick is to add a Python &lt;code&gt;if&lt;/code&gt; statement inside &lt;code&gt;{% %}&lt;/code&gt; then include the state configurations you want before you end with a &lt;code&gt;{% endif %}&lt;/code&gt;. I'll cover this better in a later post but to start &lt;code&gt;cow.sls&lt;/code&gt; should have this content now:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

install cow tools:
  pkg.installed:
    - pkgs:
      - cowsay

{% if grains['os'] == 'Ubuntu' %}
make cowsay available on Ubuntu:
  file.symlink:
    - name: /usr/bin/cowsay
    - target: /usr/games/cowsay
{% endif %}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To dissect this a little bit, first you see the state we defined in the first post of the series and modified a few minutes ago (to remove &lt;code&gt;fortune&lt;/code&gt;). Then we create our if statement which checks if the &lt;code&gt;os&lt;/code&gt; grain is equal to &lt;code&gt;Ubuntu&lt;/code&gt;. If it is, then we create a state called &lt;em&gt;make cowsay available on Ubuntu&lt;/em&gt;. This states uses the file state module to create a symlink to &lt;code&gt;/usr/games/cowsay&lt;/code&gt; (the target) at &lt;code&gt;/usr/bin/cowsay&lt;/code&gt; (defined in name of the symlink). Then we close the if statement.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fortune.sls&lt;/code&gt; is going to be similar&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

install fortuneteller tools:
  pkg.installed:
    - pkgs:
      - fortune

{% if grains['os'] == 'Ubuntu' %}
make fortune available on Ubuntu:
  file.symlink:
    - name: /usr/bin/fortune
    - target: /usr/games/fortune
{% endif %}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So will &lt;code&gt;locomotive.sls&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

install locomotive tools:
  pkg.installed:
    - pkgs:
      - sl

{% if grains['os'] == 'Ubuntu' %}
make sl available on Ubuntu:
  file.symlink:
    - name: /usr/bin/sl
    - target: /usr/games/sl
{% endif %}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Trying out the grain based configuration
&lt;/h2&gt;

&lt;p&gt;Let's test our new grain based configuration. First let's stop and remove all of our docker containers because we want a fresh slate (this way we'll also get around the new minion ids discussed earlier and easily remove the older containers.&lt;/p&gt;

&lt;p&gt;Type in these docker-compose commands to get stop containers, remove them, start up new ones and finally enter bash shell on the salt master (answering yes when prompted):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose stop
docker-compose rm
docker-compose up -d
docker-compose exec salt bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once you've entered the bash shell on the new salt master, accept the minion keys with this command (pressing Enter for yes when prompted):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt-key -A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we're ready to test our grain based targeting in the &lt;code&gt;top.sls&lt;/code&gt;. Let's apply the states to all machines (targeting them with the glob &lt;code&gt;*&lt;/code&gt;):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt '*' state.apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will install all the programs, based on the roles defined in the grains and create the symlinks on the Ubuntu machines thanks to the if statement in the states. If you look at the output report, you'll see 2 states succeeded in the Alpine container while four changed in the Ubuntu container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Targeting in the command line
&lt;/h2&gt;

&lt;p&gt;Another place you can use to target based on grains is on the command line. Instead of using the glob or minion id when you run the &lt;code&gt;salt&lt;/code&gt; command on the salt master, you can target based on grain by using the &lt;code&gt;-G&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;As an example, let's run the fortune command on all fortuneteller minions (both Ubuntu and Alpine containers). To run a command via the &lt;code&gt;salt&lt;/code&gt; command on the salt master you type &lt;code&gt;cmd.run&lt;/code&gt; instead of the &lt;code&gt;state.apply&lt;/code&gt; we've used earlier and then you tell salt which command to run (in our case &lt;code&gt;fortune&lt;/code&gt;). Go ahead and type this in your salt master's bash shell:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt -G roles:fortuneteller cmd.run fortune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see, we give salt the &lt;code&gt;-G&lt;/code&gt; option followed by the grain key and value we want to target (much like we targeted them in the &lt;code&gt;top.sls&lt;/code&gt; file. Then we tell salt to run the &lt;code&gt;fortune&lt;/code&gt; command with the &lt;code&gt;cmd.run fortune&lt;/code&gt;. The output should be some nice words of wisdom:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

cow-e54f99c12817:
    Don't feed the bats tonight.
locomotive-68af4abb8a98:
    Put no trust in cryptic comments.


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We can also only ask Alpine minions to deliver fortunes:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt -G os:Alpine cmd.run fortune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This returns only a single wisdom because the grain only matched a single minion:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

locomotive-68af4abb8a98:
    The meek shall inherit the earth -- they are too weak to refuse.


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;OK. That's enough about grains for now, let's quickly turn to the pillar.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configuring the pillar
&lt;/h1&gt;

&lt;p&gt;As I said previously. The pillar offers the same data structure as grains but instead of being generated and hosted on the salt minions, the pillar data are generated on the salt master and handed to the minion.&lt;/p&gt;

&lt;p&gt;That means that one big difference between the pillar and grains is that the pillar needs targeting, so that the pillar data can be handed to the right minion. Besides that, they're just the same.&lt;/p&gt;

&lt;p&gt;Let's move the exact programs we need to install for each role into the pillar. That makes it more flexible because we don't have to hard code the programs to install on each minion based on the role. It's going to simplify maintenance and our state code.&lt;/p&gt;

&lt;p&gt;Configuring the pillar is a mix between states and grains. We have a &lt;code&gt;top.sls&lt;/code&gt; file for the pillar where we do the targeting. There we include files, which should return a data structure similar to grains.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pillar configuration files
&lt;/h2&gt;

&lt;p&gt;We begin by creating a folder called &lt;code&gt;pillar&lt;/code&gt;. This folder we're going to mount on our salt master. This is the folder that will hold our pillar data. Let's first create files for each role. The root element in each file will be &lt;code&gt;packages&lt;/code&gt; (could be anything we want but we want it to be explicit right?). Followed by the name of the program as a key with a value which is just a description of the package we install. Something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;package-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;package description&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We won't really use the package description, we're only interested in the package name. However, salt will automatically merge dictionaries in a smart way (which is useful and cool) but unfortunately not append to lists. That's fine though because this gives us a nice place to put a description or comment with an Easter egg.&lt;/p&gt;

&lt;p&gt;So first create a file called &lt;code&gt;cow.sls&lt;/code&gt; with this content:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cowsay&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A program with a cow&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The second file called &lt;code&gt;fortuneteller.sls&lt;/code&gt; with this content:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;fortune&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A program for a fortune teller&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Lastly create the third file called &lt;code&gt;locomotive.sls&lt;/code&gt; with this content:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tech Model Railroad Club&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally we need our &lt;code&gt;top.sls&lt;/code&gt; file. It's going to be exactly the same as our state file (you can copy it if you want). First we define the environment we want it to be used in, then we target the roles and include the sls files we just created. So go ahead and create the &lt;code&gt;top.sls&lt;/code&gt; with this content:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;roles:cow'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grain&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cow&lt;/span&gt;
  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;roles:fortuneteller'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grain&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fortune&lt;/span&gt;
  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;roles:locomotive'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grain&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;locomotive&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see we can match grains when targeting our pillar. That's pretty cool. We use the &lt;code&gt;roles&lt;/code&gt; grain to match the roles.&lt;/p&gt;

&lt;p&gt;Be careful, because matching grains in the pillar comes with a caveat. When you use grains to match because they can be configured on the minion itself which means a minion can send a grain to get data it shouldn't get. In our case we're just declaring packages to install, so that's not so harmful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the master
&lt;/h2&gt;

&lt;p&gt;Next we'll reconfigure our salt master to read from our newly created pillar directory. For that we have to mount the pillar directory with docker compose and configure the master to read the pillar files from that directory. We begin by changing the master configuration.&lt;/p&gt;

&lt;p&gt;We only need to add a new configuration variable called &lt;code&gt;pillar_roots&lt;/code&gt; which is similar to the preexisting &lt;code&gt;file_roots&lt;/code&gt;. It tells in what folder we keep the pillar files for a particular environment (in our case &lt;code&gt;base&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;We will mount the directory with the pillar data at &lt;code&gt;/srv/pillar&lt;/code&gt;. Update the &lt;code&gt;master.config&lt;/code&gt; file and add the &lt;code&gt;pillar_root&lt;/code&gt; and point the base environment to &lt;code&gt;/srv/pillar&lt;/code&gt;, so your &lt;code&gt;master.config&lt;/code&gt; file should look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;file_roots&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/srv/salt/&lt;/span&gt;
&lt;span class="na"&gt;pillar_roots&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/srv/pillar&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then we need to change our &lt;code&gt;docker-compose.yml&lt;/code&gt; file to mount our new &lt;code&gt;pillar&lt;/code&gt; directory at &lt;code&gt;/srv/pillar&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;salt-master&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./master.config:/etc/salt/master&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./states:/srv/salt&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./pillar:/srv/pillar&lt;/span&gt;
  &lt;span class="na"&gt;alpine-minion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine-minion&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./alpine.grains:/etc/salt/grains&lt;/span&gt;
  &lt;span class="na"&gt;ubuntu-minion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-minion&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./ubuntu.grains:/etc/salt/grains&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That's it. That's all you need.&lt;/p&gt;

&lt;h1&gt;
  
  
  Do we have pillar data?
&lt;/h1&gt;

&lt;p&gt;Let's have a look at the pillar data we've created. Again, we want to start with a clean slate so we stop our docker containers and remove them (press y when prompted if we're sure we want to remove them). Then we spin them back up again and attach to the bash shell of salt master:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose stop
docker-compose rm
docker-compose up -d
docker-compose exec salt bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once we're attached to the salt master, we accept the salt keys for the new minions (press enter to accept them when prompted):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt-key -A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To have a look at the pillar data we just created type the following command&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt '*' pillar.items
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should now see the packages listed based on the roles of the machines. Something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

cow-e9e01577bbd8:
    ----------
    packages:
        ----------
        cowsay:
            A program with a cow
        fortune:
            A program for a fortune teller
locomotive-023378ead2d9:
    ----------
    packages:
        ----------
        fortune:
            A program for a fortune teller
        sl:
            Tech Model Railroad Club


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We still aren't using this pillar data. To do that properly, we'll have to dive into the next part of this series: &lt;em&gt;templating&lt;/em&gt; (I've teased it a little bit in this article though).&lt;/p&gt;

&lt;p&gt;Little by little we'll get a better understanding of many of Salt's features.&lt;/p&gt;




&lt;p&gt;&lt;a rel="license noopener noreferrer" href="http://creativecommons.org/licenses/by-sa/4.0/"&gt;&lt;img alt="Creative Commons License" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.creativecommons.org%2Fl%2Fby-sa%2F4.0%2F80x15.png"&gt;&lt;/a&gt; This work is licensed under a &lt;a rel="license noopener noreferrer" href="http://creativecommons.org/licenses/by-sa/4.0/"&gt;Creative Commons Attribution-ShareAlike 4.0 International License&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover image by Yair Aronshtam: &lt;a href="https://commons.wikimedia.org/wiki/File:Salt_pillar_at_the_Dead_Sea,_Israel_(35253925685).jpg" rel="noopener noreferrer"&gt;Salt pillar at the Dead Sea, Israel&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>salt</category>
      <category>saltstack</category>
      <category>devops</category>
      <category>iac</category>
    </item>
    <item>
      <title>Let's get started with Salt</title>
      <dc:creator>Tryggvi Björgvinsson</dc:creator>
      <pubDate>Tue, 07 Jan 2020 23:12:54 +0000</pubDate>
      <link>https://dev.to/trickvi/let-s-get-started-with-salt-55hb</link>
      <guid>https://dev.to/trickvi/let-s-get-started-with-salt-55hb</guid>
      <description>&lt;p&gt;Configuration management is an essential piece of any computer system consisting of multiple components. If implemented correctly, it can make maintenance a lot easier and predictable for anyone involved. &lt;/p&gt;

&lt;p&gt;This becomes especially powerful when the system is managed with machine-readable configuration files instead of using various interactive tools. Spinning up and configuring each component should be automatic and consistent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.saltstack.com/"&gt;SaltStack&lt;/a&gt; (also known by the less searchable name Salt) is one such configuration management system. It doesn't only support configuration management, but also remote execution and event driven automation but let's focus on getting started with Salt configuration management.&lt;/p&gt;

&lt;h1&gt;
  
  
  Simple architectural overview
&lt;/h1&gt;

&lt;p&gt;Before we dive into a small example, let's take a look at a very brief and simplistic overview of how Salt works so you'll know what it's all about.&lt;/p&gt;

&lt;p&gt;At its core Salt operates by setting up a small agent, called a &lt;em&gt;minion&lt;/em&gt;, on all the system components you want to configure or operate (e.g. server). This minion talks to a Salt &lt;em&gt;master&lt;/em&gt; which knows how the minion should be configured based on some configuration files written in YAML.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F7vAD-TM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/gnbeney3rlfxowwwrgf9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F7vAD-TM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/gnbeney3rlfxowwwrgf9.png" alt="Simple overview of the Salt architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the configuration management corner of Salt, these YAML configuration files define the &lt;em&gt;state&lt;/em&gt; the component should find itself in. The minion itself then makes sure it is in the desired state by doing some Python-fu.&lt;/p&gt;

&lt;p&gt;So basically to get Salt working, all you need to do is to set up a Salt master and define the configuration in a YAML file. Then you just install the minion on the system components you want the Salt master to control.&lt;/p&gt;

&lt;h1&gt;
  
  
  Example
&lt;/h1&gt;

&lt;p&gt;To see it all in action let's set up a small Docker-based example. I'm fully aware that it's kind of weird to use Salt to configure Docker images because the &lt;em&gt;Dockerfile&lt;/em&gt; is essentially a configuration file providing consistency, but Docker just makes it simpler to showcase the simplicity and for you to try out Salt.&lt;/p&gt;

&lt;p&gt;We're going to create one Debian image with Salt master and create configuration files to control one Ubuntu image and one Alpine image.&lt;/p&gt;

&lt;p&gt;First, let's create our two minions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alpine minion
&lt;/h2&gt;

&lt;p&gt;The only thing you have to do really, is to install the Salt minion agent, it will automatically be configured with sane defaults although we'll change them slightly.&lt;/p&gt;

&lt;p&gt;Installing Salt minion on Alpine is as easy as just running:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apk add salt-minion
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(in Docker we'll add the &lt;code&gt;--no-cache&lt;/code&gt; flag because we want to clean up after ourselves)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We have to make one small change to make the configuration files for the states easier. We're going to change something called the &lt;em&gt;minion id&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Normally when the salt minion agent starts up it will by default take the hostname and set it as a &lt;em&gt;minion id&lt;/em&gt;. The id is how the Salt master will recognize the different minions.&lt;/p&gt;

&lt;p&gt;In the configuration management setup we need to &lt;em&gt;target&lt;/em&gt; our minions which will allow us to define different states for different machines. There are many ways to &lt;em&gt;target&lt;/em&gt; minions but we're going to target ours using the minion ids.&lt;/p&gt;

&lt;p&gt;In Docker the hostnames are going to be a random string (e.g. &lt;code&gt;71665beda883&lt;/code&gt;) which makes predefined targeting difficult. What we're going to do is prefix the minion id with a string that makes it easier for us to target the machine.&lt;/p&gt;

&lt;p&gt;For the Alpine example, we're going to use the prefix &lt;code&gt;locomotive&lt;/code&gt;. To do that we'll create a docker entrypoint file which echoes &lt;code&gt;"locomotive-$HOSTNAME"&lt;/code&gt; into the file where the salt minion stores its id, &lt;code&gt;/etc/salt/minion_id&lt;/code&gt;. Afterwards we'll start up the salt minion.&lt;/p&gt;

&lt;p&gt;So this is our docker-entrypoint.sh for alpine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"locomotive-&lt;/span&gt;&lt;span class="nv"&gt;$HOSTNAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/salt/minion_id
salt-minion
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now all we have to do to create the Alpine Salt minion, is install the Salt minion, copy our docker entrypoint file into the image, and set the command to run the docker entrypoint file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; salt-minion

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; docker-entrypoint.sh /&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["sh", "/docker-entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's build the Docker image as &lt;code&gt;alpine-minion&lt;/code&gt; (assuming we're in a folder where we have the alpine Dockerfile):&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t alpine-minion .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alright, onwards to the next minion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ubuntu minion
&lt;/h2&gt;

&lt;p&gt;The Ubuntu minion is going to be almost exactly the same. The difference is going to be that we use apt to install the salt minion instead of apk, and we're going to use the prefix &lt;code&gt;cow&lt;/code&gt; instead of &lt;code&gt;locomotive&lt;/code&gt; (because we want to target the minions differently).&lt;/p&gt;

&lt;p&gt;So the docker-entrypoint.sh is going to be like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"cow-&lt;/span&gt;&lt;span class="nv"&gt;$HOSTNAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/salt/minion_id
salt-minion
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The Dockerfile is going to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; salt-minion

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; docker-entrypoint.sh /&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["sh", "/docker-entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's build this one as &lt;code&gt;ubuntu-minion&lt;/code&gt; (again assuming we're now in the ubuntu minion's directory):&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t ubuntu-minion .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's enough of the minions, let's move on to the states for our minions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Salt states
&lt;/h2&gt;

&lt;p&gt;You may be wondering why I chose the &lt;code&gt;locomotive&lt;/code&gt; and &lt;code&gt;cow&lt;/code&gt; prefix for the alpine and ubuntu images, respectively. That's because we're going to install two different software packages based on the prefix.&lt;/p&gt;

&lt;p&gt;As I said before, we're going to target the minions (based on their minion id) and install the different packages based on the prefix. When compiling the state for a specific minion, Salt always starts looking in a file called &lt;code&gt;top.sls&lt;/code&gt;. The &lt;code&gt;sls&lt;/code&gt; file extension stands for SaLt State file, but don't let that fool you, it's just a YAML file. All state definition files should have the &lt;code&gt;sls&lt;/code&gt; file extension.&lt;/p&gt;

&lt;p&gt;We're going to create three state files. One to install two software packages: &lt;code&gt;cowsay&lt;/code&gt; and &lt;code&gt;fortune&lt;/code&gt;. One to install software packages &lt;code&gt;sl&lt;/code&gt; and &lt;code&gt;fortune&lt;/code&gt; (we install fortune in both this way for the example). Lastly we create the &lt;code&gt;top.sls&lt;/code&gt; file where Salt starts its journey in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make sure a package is installed
&lt;/h3&gt;

&lt;p&gt;Let's start by making sure the packages &lt;code&gt;cowsay&lt;/code&gt; and &lt;code&gt;fortune&lt;/code&gt; are installed. A Salt state file can contain multiple states, all defined by state names as the root elements of the state file. We're just going to have one in a file called &lt;code&gt;cow.sls&lt;/code&gt; to make sure we install &lt;code&gt;cowsay&lt;/code&gt; and &lt;code&gt;fortune&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can call the state what we want, but to be explicit let's call it &lt;em&gt;install fun cow tools&lt;/em&gt;. The only thing we have to make sure when giving these names is that the for any given minion, no two states can have the same name.&lt;/p&gt;

&lt;p&gt;The state element in the YAML file is a dictionary of the states we want. Salt comes with a lot of built in state modules we can use (that's the beauty of Salt) but the Salt state module we want to use is called &lt;code&gt;pkg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We use the &lt;code&gt;pkg&lt;/code&gt; state module to define that the minions where this state applies need to have packages &lt;code&gt;installed&lt;/code&gt; (a function in the &lt;code&gt;pkg&lt;/code&gt; module called &lt;code&gt;installed&lt;/code&gt;). The &lt;code&gt;pkg.installed&lt;/code&gt; function can take in a keyword argument &lt;code&gt;pkgs&lt;/code&gt; which for its value is a list of packages to install, in our case: &lt;code&gt;fortune&lt;/code&gt; and &lt;code&gt;cowsay&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;cow.sls&lt;/code&gt; file will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;install cow tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;pkg.installed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pkgs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fortune&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cowsay&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The locomotive file is going to be similar, but instead of installing cowsay, we're going to install &lt;code&gt;sl&lt;/code&gt; in addition to &lt;code&gt;fortune&lt;/code&gt;. We'll also use the name &lt;code&gt;install locomotive tools&lt;/code&gt; for the state. So the file &lt;code&gt;locomotive.sls&lt;/code&gt; will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;install locomotive tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;pkg.installed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pkgs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fortune&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see, the files use the same salt modules and functions even if we know that behind the scenes one will be executed on an alpine machine and the other on an ubuntu machine. Salt will use the appropriate package manager.&lt;/p&gt;

&lt;h3&gt;
  
  
  top.sls
&lt;/h3&gt;

&lt;p&gt;Let's tie this all together in the &lt;code&gt;top.sls&lt;/code&gt; and target the machines based on their prefix. Salt allows use of an asterisk/glob when targeting so it's fairly easy to target the minion ids with these prefixes.&lt;/p&gt;

&lt;p&gt;First we must define an environment we use. We'll use the default Salt environment called &lt;code&gt;base&lt;/code&gt;. That's going to be the root element and the value is a dictionary. The key elements of that dictionary are the targeting patterns. Their values are what state files it should apply. So something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;&amp;lt;environment&amp;gt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;&amp;lt;target pattern&amp;gt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;state file&amp;gt;&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;state file&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The state files are given as the name without the file extension. The state files can be in subdirectories but then we have to use the Python convention where directories and files are separated by a period. So a file &lt;code&gt;cow.sls&lt;/code&gt; in the subdirectory &lt;code&gt;farm&lt;/code&gt; is included as &lt;code&gt;farm.cow&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now this may seem complicated but it's really not. It's just hard to describe. If we assume the files are in the same directory as the top.sls file, our top.sls will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;locomotive-*&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;locomotive&lt;/span&gt;
  &lt;span class="s"&gt;cow-*&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This means any minion id that starts with &lt;code&gt;locomotive-&lt;/code&gt; is going to apply the states found in the &lt;code&gt;locomotive.sls&lt;/code&gt; file in the same folder as the &lt;code&gt;top.sls&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;This is the gist of how we build up and define the state of a system with Salt, using YAML files. Now we all we need is the master that can tell the minions what to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Salt master
&lt;/h2&gt;

&lt;p&gt;Let's use Debian for the Salt mater. Just like with the minions, The only thing we have to do to get Salt up and running with sane defaults, is to install the salt-master package. So it's going to be a simple Docker image we need (we'll do the magic that we need to make our Salt system operational, when we mount configuration files):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; debian:10-slim&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; salt-master

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; salt-master&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's build this image as &lt;code&gt;salt-master&lt;/code&gt; (assuming we're in the same directory as the Salt master Dockerfile):&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t salt-master .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now with all images we need let's make it operational!&lt;/p&gt;

&lt;h2&gt;
  
  
  Salt master configuration
&lt;/h2&gt;

&lt;p&gt;First off, we're going to need a basic configuration file that will tell the Salt master where our state files are.&lt;/p&gt;

&lt;p&gt;Thanks to Salt's sane defaults, the configuration file is super-simple. We only have to define a file root where our state environment can be found. It is possible to manage a lot of different Salt environments with different states using the same Salt master and configuration file but we're going to stick to the default &lt;code&gt;base&lt;/code&gt; environment.&lt;/p&gt;

&lt;p&gt;The configuration file just needs to point to the directory with the root of our state files for a &lt;code&gt;base&lt;/code&gt; Salt environment. We'll create a file called &lt;code&gt;master.config&lt;/code&gt; with only three lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;file_roots&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/srv/salt/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Our state files (those we created earlier) need to be made available at &lt;code&gt;/srv/salt&lt;/code&gt; and we need to mount our &lt;code&gt;master.config&lt;/code&gt; in our Salt master as the file &lt;code&gt;/etc/salt/master&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Docker compose environment
&lt;/h2&gt;

&lt;p&gt;To make all of this easier we'll use Docker compose to set up all of our services. Before we create the Docker compose file let's first have a look at how our local file system should look when we've created our Docker compose file (assuming the Dockerfiles we used earlier are stored elsewhere):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt-example/
  |
  |- docker-compose.yml
  |- master.config
  `- states/
       |
       |- cow.sls
       |- locomotive.sls
       `- top.sls
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we can create our Docker compose file.&lt;/p&gt;

&lt;p&gt;There are a few things we have to make sure we do. Two I have already mentioned: one is to mount the &lt;code&gt;master.config&lt;/code&gt; as &lt;code&gt;/etc/salt/master&lt;/code&gt; in the Salt master service, the other to mount our &lt;code&gt;states/&lt;/code&gt; directory as &lt;code&gt;/srv/salt/&lt;/code&gt; in the Salt master service.&lt;/p&gt;

&lt;p&gt;The third thing I haven't mentioned, but it's very important. By default minions will look for the Salt master at the domain name &lt;code&gt;salt&lt;/code&gt;. This is configurable but we are using defaults, so we have to make sure the Salt master service in the Docker compose file is called &lt;code&gt;salt&lt;/code&gt; (which makes it available as &lt;code&gt;salt&lt;/code&gt; to our minions who share the docker compose network).&lt;/p&gt;

&lt;p&gt;So the &lt;code&gt;docker-compose.yml&lt;/code&gt; file will define three services, salt (with files and directories mounted properly), our alpine minion and the ubuntu minion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;salt-master&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./master.config:/etc/salt/master&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./states:/srv/salt&lt;/span&gt;
  &lt;span class="na"&gt;alpine-minion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine-minion&lt;/span&gt;
  &lt;span class="na"&gt;ubuntu-minion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-minion&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That is all. We're ready to start everything up. Just type:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Congratulations! If there haven't been any typos, you now have your first Salt configuration management environment up and running. But it actually doesn't do anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accepting minions
&lt;/h2&gt;

&lt;p&gt;If Salt would just start handing out states to any machine that contacts the Salt master and fits into a specific minion id pattern, we'd have a problem on our hands. To avoid this we need to accept what minions we want the Salt master to manage.&lt;/p&gt;

&lt;p&gt;When the minion contacts the Salt master it sends its public key. If we haven't accepted the minion before, we need to accept it (that is to say, if we trust it) using a tool installed on the Salt master called &lt;code&gt;salt-key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before we continue, let's open up a shell on the Salt master docker container:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose exec salt bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now in the Salt master bash shell you can list all minion keys that are accepted, denied, rejected and unaccepted, by running &lt;code&gt;salt-key&lt;/code&gt; with the &lt;code&gt;-l all&lt;/code&gt; option:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt-key -l all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You should see an output similar to this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Accepted Keys:
Denied Keys:
Unaccepted Keys:
cow-7d7f4faf9802
locomotive-1fbb4f5c409d
Rejected Keys:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see there should be two unaccepted keys (one with a cow prefix and another with a locomotive prefix).&lt;/p&gt;

&lt;p&gt;If we do not accept them we will not be able to manage their configurations so let's go ahead and accept all the keys we've gotten using the &lt;code&gt;-A&lt;/code&gt; flag:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt-key -A
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You'll be asked to confirm if you want to accept the two listed keys. Just enter &lt;code&gt;Y&lt;/code&gt; if all looks good (or simply press Enter because accepting is the default). Now you should see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Key for minion cow-7d7f4faf9802 accepted.
Key for minion locomotive-1fbb4f5c409d accepted.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we can control our minions. They may have shutdown because it took some time to accept their keys (they don't want to wait forever). Let's just be sure they're up by exiting the Salt master:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then we run docker compose up again:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then we can attach to the Salt master again:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose exec salt bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you want to be sure we've accepted our keys and they're still there just type:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt-key -l all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You should see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Accepted Keys:
cow-7d7f4faf9802
locomotive-1fbb4f5c409d
Denied Keys:
Unaccepted Keys:
Rejected Keys:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Applying state
&lt;/h2&gt;

&lt;p&gt;The states still haven't been applied to our minions. They don't know what packages they should install so they haven't installed anything. To trigger the configuration we will have to execute a &lt;code&gt;state.apply&lt;/code&gt; on the minions we want to apply the state.&lt;/p&gt;

&lt;p&gt;To do this we target our minions and we can safely just target all of our minions using the glob (asterisk) and tell it to apply the states. To do that we type the following command:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salt '*' state.apply
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notice the quotes around the glob. This is needed because we're passing the string with the targeting pattern.&lt;/p&gt;

&lt;p&gt;You should see a lot of stuff on your screen but near the end of it all, you'll see a summary like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Summary for locomotive-1fbb4f5c409d
------------
Succeeded: 1 (changed=1)
Failed:    0
------------
Total states run:     1
Total run time:   5.633 s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This tells you all states were run successfully and one change was made (it installed the packages) on the locomotive machine (Alpine). You'll see a similar report for the cow machine (Ubuntu)&lt;/p&gt;

&lt;p&gt;You can run the &lt;code&gt;state.apply&lt;/code&gt; function as often as you like. Because it is a state, it defines &lt;em&gt;how&lt;/em&gt; your minion should look so what it actually does before installing packages in our case, is to check if all the packages are installed. If they aren't or some are missing, it goes ahead and installs the missing ones.&lt;/p&gt;

&lt;p&gt;The state apply does the magic of making sure that the state is fulfilled. Running &lt;code&gt;state.apply&lt;/code&gt; again should result in a report for (one for the Alpine machine and another for the Ubuntu machine) like this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;locomotive-1fbb4f5c409d:
----------
          ID: install locomotive tools
    Function: pkg.installed
      Result: True
     Comment: All specified packages are already installed
     Started: 23:53:42.277146
    Duration: 723.556 ms
     Changes:   

Summary for locomotive-03f0f2574592
------------
Succeeded: 1
Failed:    0
------------
Total states run:     1
Total run time: 723.556 ms
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is awesome. Salt tells us everything is great, but is it? Let's check. First exit the Salt master&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alright now we're ready to check if Salt did what we wanted it to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it work?
&lt;/h3&gt;

&lt;p&gt;The cowsay package in Ubuntu is installed in &lt;code&gt;/usr/games/cowsay&lt;/code&gt; so to see it in action we'll run this:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose exec ubuntu-minion /usr/games/cowsay I think I need some Salt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You'll see a bovine ascii art telling you it needs some Salt. Let's make see if we have fortune installed (also in /usr/games/):&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose exec ubuntu-minion /usr/games/fortune
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Enjoy the wisdom!&lt;/p&gt;

&lt;p&gt;Alpine should also have some packages (in this case we don't have to access it at &lt;code&gt;/usr/games/&lt;/code&gt; because it is already in the path):&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose exec alpine-minion fortune
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Woah! More wisdom! Wait, maybe we installed the cow on Alpine as well:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose exec alpine-minion cowsay Salt told me not to
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nope. You should get an error telling you that cowsay isn't found in the path. But sl is:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose exec alpine-minion sl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here's a joke because you got this far: What sound does a sick train make? -- &lt;em&gt;Ah-Ah-Ah Choo choo!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So these were the basics of Salt. In future posts I want to dive deeper into Salt but I needed to get the basics out of the way.&lt;/p&gt;




&lt;p&gt;&lt;a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"&gt;&lt;img alt="Creative Commons License" src="https://res.cloudinary.com/practicaldev/image/fetch/s--AAgNuxfG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.creativecommons.org/l/by-sa/4.0/80x15.png"&gt;&lt;/a&gt; This work is licensed under a &lt;a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"&gt;Creative Commons Attribution-ShareAlike 4.0 International License&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover image by Jorge Royan: &lt;a href="https://commons.wikimedia.org/wiki/File:Saleros_-_5394.jpg"&gt;Four salt shakers on a table with salt spilled from one of them&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>salt</category>
      <category>saltstack</category>
      <category>devops</category>
      <category>iac</category>
    </item>
    <item>
      <title>I wrote a book</title>
      <dc:creator>Tryggvi Björgvinsson</dc:creator>
      <pubDate>Wed, 06 Jun 2018 00:15:02 +0000</pubDate>
      <link>https://dev.to/trickvi/i-wrote-a-book-lfg</link>
      <guid>https://dev.to/trickvi/i-wrote-a-book-lfg</guid>
      <description>&lt;p&gt;Yes. I wrote a &lt;a href="https://www.manning.com/books/the-art-of-data-usability"&gt;book&lt;/a&gt;. It hasn't been printed yet but now that I have sent it to my publisher I have time to kill. So I thought: Why not reflect on the last one and a half year I spent writing the book and share my experience. If you want to write a book you maybe you can get an idea of what the journey is like.&lt;/p&gt;

&lt;p&gt;Here's the backstory; One day in September 2016 I got an email from someone claiming to be an assistant acquisitions editor at &lt;a href="https://www.manning.com/"&gt;Manning publications&lt;/a&gt;. According to the email they were interested in publishing a book on data quality and wanted to know if I'd be interested in talking to them. My first thought was: &lt;em&gt;"email scam"&lt;/em&gt;, then after some thought I decided to reply which 1.5 year later ended in a complete book.&lt;/p&gt;

&lt;h1&gt;
  
  
  User oriented book writing
&lt;/h1&gt;

&lt;p&gt;Manning publications has a really great approach to book writing. I can't compare it to other publisher because this is my first book and first foray in book writing (except for my Ph.D. thesis). Manning's approach is very user oriented and the whole process is designed to output a book that suits all kind of readers.&lt;/p&gt;

&lt;p&gt;First thing I had to do after the initial call with Manning with was define my intended audience (data scientists) and what I wanted to teach that audience in my book. I scoped the book, weighed the difficulty of each chapter to see if the book really matched my audience. It was a kind of a requirement analysis for my book. As I worked on the book I fleshed out the lesson in each chapter, reweighed the chapters, and constantly aimed all my writing at the intended audience.&lt;/p&gt;

&lt;p&gt;During the writing process the book was regularly reviewed by a focus group consisting of my intended audience. A group that gave me really valuable feedback and allowed me to better focus my book on what they found helpful and avoid any confusion or boring bits. This was often frustrating because I had to rethink, restructure and rewrite all of my chapters at least twice before I found the best approach to teach data quality without being limited by preconceptions of the intended audience.&lt;/p&gt;

&lt;p&gt;Then once I had sent Manning a majority of the chapters my book went into something called &lt;em&gt;Manning early access program&lt;/em&gt; or MEAP for short. This program publishes an early release of the book and allows the general public to provide feedback and help me further improve the book for all possible readers. As part of the MEAP Manning put up a book forum where I could converse with readers and adapt the book accordingly but I found private conversations with readers outside the forum most helpful.&lt;/p&gt;

&lt;p&gt;Manning provided me with a development editor and a technical editor who helped me frame the book and keep me on track. My development editor was awesome and patient while I internalized Manning's learning system -- the way they have found out helps people learn best. That way I adapted my style to how my readers would read my book. I think this shift in how I write and structure my content was the most difficult thing to overcome. I had written a lot of text before, but unknowingly I wrote only for me and people like me. Obviously, I'm not everybody and Manning had a way to structure book to fit a wider audience.&lt;/p&gt;

&lt;p&gt;For example I didn't like repeating explanations in each chapter but what I didn't realize is that while I read books from page 1 to the last page. Others &lt;em&gt;fly&lt;/em&gt; into chapters in random order, based on what interests them. If I had never repeated things I said in previous chapters those readers would miss context and not get the same experience. So the trick was to write so that the book was approachable to both groups.&lt;/p&gt;

&lt;p&gt;Given my experience in data quality and usability, which is all about understanding what users need in addition to software development which is also about understanding user requirements, it's almost embarrassing to admit how hard it was for me to realize this.&lt;/p&gt;

&lt;h1&gt;
  
  
  Balancing work, life, and writing
&lt;/h1&gt;

&lt;p&gt;The second most difficult part of the book writing process was fitting the book writing into my life. I'm a father of two daughters. When I started writing the book my two daughters were 2 and 4 years old. My wife was finishing her Ph.D. and had recently started a full-time job. I had a full-time job as the head of IT and dissemination at Statistics Iceland. We had also planned our summer vacation trip in 2017. That plus all of my other interests had to be balanced somehow in addition to all the crazy things I decided to do also, like buying a new apartment and moving, facing a hard disk breakdown and restoring from backup to a new laptop, participating in the parent association of my daughters' preschool, undertaking a major strategy shift at work and whatnot. In retrospect I don't know what I was thinking.&lt;/p&gt;

&lt;p&gt;So how did I write the book? First thing: You know how authors tend to thank their family and spouses for the support. I see that in a new light. It's not something they do out of courtesy to their spouses. If you have a family you need to have a supporting spouse. We had to plan how to split the weekends, who got to work in the morning and who got to work in the afternoon (as I said my wife was finishing her Ph.D. on the side). Our family life suffered. We often discussed how awful it was not to see each others for days on end. If one of us was picking up the daughters from preschool, cooking, reading to them, and getting them to bed; the other one was sitting in a coffee shop typing on a laptop.&lt;/p&gt;

&lt;p&gt;Coffee shops were great. They became home away from home. A place where I could focus. If I was at home I was easily disturbed which caused me to lose focus. In coffee shops I was rarely disturbed and kept focus for hours. I especially like one coffee shop that was opened until late so I was dose up on coffee during the day and shift into beer in the evening. I wrote the book in a lot of different places that kept my me constantly interested while revising the book.&lt;/p&gt;

&lt;p&gt;The writing rhythm I found most suitable was to spend a week or two to research further the chapter I was going to write; find interesting stories, dig into literature, material, create example programs for the chapter and other preparation work. Then take a weekend to bash out surrounding words. I then sent the first rough draft to my awesome development editor who read it and helped me finalize the chapter for user review with lots of back and forth editing and comments. This rhythm didn't always work -- some chapters were more difficult to plan and write than others so they needed more preparation -- but that's the setup that worked best for me and my family.&lt;/p&gt;

&lt;h1&gt;
  
  
  Git statistics
&lt;/h1&gt;

&lt;p&gt;I wrote all of my book in &lt;a href="http://asciidoc.org/"&gt;Asciidoc&lt;/a&gt;, a typesetting text-based format to write books. I used Git to version control the book and pushed it to a Gitlab server hosted by Manning. Looking at the Git statistics and Gitlab charts reveals how I wrote the book.&lt;/p&gt;

&lt;p&gt;Looking at the whole git repository I changed 1208 files, inserted 113137 lines and deleted 42037 lines (a line is is no more than 80 characters). To put those insertions and deletions into into context, the main chapters of the book are in total 9347 lines (70077 words). Some of the insertions and deletions are things like svg diagrams I created for the book and kept in Git, but it's still pretty clear that a lot of the work was rewriting, deleting, and adding text.&lt;/p&gt;

&lt;p&gt;The setup of writing most of the content during the weekend is clear if we look at commits broken up by weekday. Majority of the 157 commits (in total) I made on Sundays (almost 40). Those were the big text blocks I hammered out after preparing for a week or longer. The Thursday commits are made up of two types of commits, when I finalized code for the chapters before the weekends or the chapter structure and when I made changes after getting edits from my editor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--61eIoFvQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/sxrfkp1pc2x8w81q3rye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--61eIoFvQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/sxrfkp1pc2x8w81q3rye.png" alt="Commits per weekday"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking at the commit hours it's also pretty clear that I'm a family man working a full-time job (my timezone is UTC+0 so those are my working hours in the graph below). I seem to work best in the evenings. I start committing work around 4PM and continue until 2AM in the night. Then I sleep for a few hours and head into work. The small amount of commits around noon are from when I took the &lt;em&gt;morning shift&lt;/em&gt; on weekends.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WipuoA4G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/c9lmi31syn7q3mlvru4u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WipuoA4G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/c9lmi31syn7q3mlvru4u.png" alt="Commits per day hour"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Was it worth it?
&lt;/h1&gt;

&lt;p&gt;Oh yes it was. Even if I worked late, after my family went to sleep, sacrificing extra curricular activities I learned so lot, not only about how to teach and write, but also about my area of expertise. I had to come up with creative solutions to make my examples simple enough to be useful in a book.&lt;/p&gt;

&lt;p&gt;I'm very happy with the outcome. The book is still in MEAP (now when I write these words) so it hasn't been printed yet but all the chapters have been published and it's going through the last review. If you're interested, you can buy it -- &lt;a href="https://www.manning.com/books/the-art-of-data-usability"&gt;The Art of Data Usability&lt;/a&gt; on Manning's website (ebook available now, printed book/pbook once it's been published).&lt;/p&gt;

&lt;p&gt;If I was able to do this with everything that was going on in my life at the same time, you can too! So if you have an idea for a book somewhere in your stomach there's nothing stopping you from just starting (although I do recommend that you have fewer things going on in your life than I had -- you're more likely to keep your sanity that way).&lt;/p&gt;

&lt;p&gt;I hope this post is inspiring, even if it was mostly about me. I'm more than happy to answer any questions you may have in the comments to help you and encourage you to get your book written.&lt;/p&gt;

</description>
      <category>book</category>
      <category>writing</category>
      <category>experience</category>
      <category>reflections</category>
    </item>
  </channel>
</rss>
