Skip to main content

Salt State

Guangzhou, China

Setting up your Minions

Installing Apache

We can set the state of an Minion inside a YAML file that contains instructions for Salt. Start by creating a folder /srv/salt and run git init to version your minion state. To make sure that our minion has Apache installed we will create an apache.sls file inside the directory on your MASTER:

install_apache:
  pkg.installed:
   - name: apache2

You can now execute this set of instructions on your minion server by running:

sudo salt ubuntuAsus state.sls apache

ubuntuAsus:
----------
          ID: install_apache
    Function: pkg.installed
        Name: apache2
      Result: True
     Comment: The following packages were installed/updated: apache2
     Started: 15:15:20.619100
    Duration: 28624.3 ms
     Changes:   
              ----------
              apache2:
                  ----------
                  new:
                      2.4.41-4ubuntu3
                  old:
              apache2-bin:
                  ----------
                  new:
                      2.4.41-4ubuntu3
                  old:
              apache2-data:

Summary for ubuntuAsus
------------
Succeeded: 1 (changed=1)
Failed:    0
------------
Total states run:     1
Total run time:  28.624 s

This step had our Minion download a copy of apache.sls file from our master and run it's instructions. The master does not need to know how the minion is going to download and install the required software - the OS on our minion makes that decision. Since we have Ubuntu installed on our minion it will run apt-get update && apt-get install apache2.

Configuring Apache

We can now also make sure that Apache will be enabled and activated as a service by adding the following line to our instruction file:

install_apache:
  pkg.installed:
   - name: apache2

enable_apache:
  service.running:
    - name: apache2
    - enable: True

We can also configure Apache to display a landing page on Port 80:

install_apache:
  pkg.installed:
   - name: apache2

enable_apache:
  service.running:
    - name: apache2
    - enable: True

add_landing_page:
  file.managed:
    - name: /var/www/html/index.html
    - contents: |
        <!doctype html>
        <body><h1>Salty Dayze, Sailor!</h1></body>

Now re-run state.sls and the default Apache Landing Page will be overwritten by our Hello World:

sudo salt ubuntuAsus state.sls apache test=true

sudo salt ubuntuAsus state.sls apache

You can visit the website on your minions IP address and port 80. You can get the IP address by running the following command:

sudo salt ubuntuAsus network.ip_addrs

ubuntuAsus:
    - 10.1.88.0
    - 172.17.0.1
    - 192.168.2.111
curl 192.168.2.111

<!doctype html>
<body><h1>Salty Dayze, Sailor!</h1></body>

Jinja Scripts

Conditionals

The Apache install script in this form will only work for Debian-based operating system - the Apache package is called apache2 on Ubuntu but httpd on CentOS. We can use the Jinja script syntax to make our script more robust:

install_apache:
  pkg.installed:
   {% if salt.grains.get('os_family') == 'Debian' %}
   - name: apache2
   {% elif salt.grains.get('os_family') == 'RedHat' %}
   - name: httpd
   {% endif %}

enable_apache:
  service.running:
    {% if salt.grains.get('os_family') == 'Debian' %}
    - name: apache2
    {% elif salt.grains.get('os_family') == 'RedHat' %}
    - name: httpd
    {% endif %}
    - enable: True

add_landing_page:
  file.managed:
    - name: /var/www/html/index.html
    - contents: |
        <!doctype html>
        <body><h1>Salty Dayze, Sailor!</h1></body>

We can test this script with the state.show_sls command:

sudo salt '*' state.show_sls apache | less

ubuntuAsus:
    ----------
    enable_apache:
        ----------
        __env__:
            base
        __sls__:
            apache
        service:
            |_
              ----------
              name:
                  apache2
            |_
              ----------
              enable:
                  True
            - running
            |_
              ----------
              order:
                  10001

We can see that our Ubuntu Minion correctly resolved the apache2 package and not httpd.

Looping

To configure Apache we can now create configuration files in the conf-available directory and copy them over into the conf-enabled directory to start using them:

mod_status:
  file.managed:
    - name: /etc/apache2/conf-available/mod_status.conf
    - contents: |
      <Location '/status'>
        SetHandler server-status
      </Location>
  cmd.run:
    - name: a2enmod status && a2enconf mod_status
    - creates: /etc/apache2/conf-enabled/mod_status.conf

mod_info:
  file.managed:
    - name: /etc/apache2/conf-available/mod_info.conf
    - contents: |
      <Location '/info'>
        SetHandler server-info
      </Location>
  cmd.run:
    - name: a2enmod info && a2enconf mod_info
    - creates: /etc/apache2/conf-enabled/mod_info.conf

This configuration script can be compacted by writing a for-loop:

{% for conf in ['status', 'info'] %}

mod_{{ conf }}:
  file.managed:
    - name: /etc/apache2/conf-available/mod_{{ conf }}.conf
    - contents: |
        <Location '/{{ conf }}'>
            SetHandler server-{{ conf }}
        </Location>

  {% if salt.grains.get('os_family') == 'Debian' %}
  cmd.run:
    - name: a2enmod {{ conf }} && a2enconf mod_{{ conf }}
    - creates: /etc/apache2/conf-enabled/mod_{{ conf }}.conf
  {% endif %}

{% endfor %}

The command step is only necessary on Debian systems and can be wrapped into a conditional. We can again test our script:

sudo salt '*' state.show_sls mods | less

ubuntuAsus:
    ----------
    mod_info:
        ----------
        __env__:
            base
        __sls__:
            mods
    ...

Scripts CleanUP

Commenting your scripts and separating logic from state:

apache.sls

# Install vanilla Apache on Debian/RedHat

{% if salt.grains.get('os_family') == 'Debian' %}
{% set apache_pkg = 'apache2' %}
{% elif salt.grains.get('os_family') == 'RedHat' %}
{% set apache_pkg = 'httpd' %}
{% endif %}

install_apache:
  pkg.installed:
   - name: {{ apache_pkg }}

enable_apache:
  service.running:
    - name: {{ apache_pkg }}

    # Will be enabled automatically on Debian but has to be enabled manually on RedHat
    - enable: True

# Adding a blank front page
add_landing_page:
  file.managed:
    - name: /var/www/html/index.html
    - contents: |
        <!doctype html>
        <body><h1>Salty Dayze, Sailor!</h1></body>

Working with Dictionaries

Create a lookup dictionary to assign the correct package and service name for each minion:

apache.sls

# Install vanilla Apache on Debian/RedHat

{% set lookup = {
    'Debian': {
        'pkg': 'apache2',
        'srv': 'apache2'
    },
    'RedHat': {
        'pkg': 'httpd',
        'srv': 'httpd'
    }
} %}

{% set apache = lookup[salt.grains.get('os_family')] %}

install_apache:
  pkg.installed:
   - name: {{ apache.pkg }}

enable_apache:
  service.running:
    - name: {{ apache.srv }}

    # Will be enabled automatically on Debian but has to be enabled manually on RedHat
    - enable: True

# Adding a blank front page
add_landing_page:
  file.managed:
    - name: /var/www/html/index.html
    - contents: |
        <!doctype html>
        <body><h1>Salty Dayze, Sailor!</h1></body>

making this is a little bit more compact by using grains.filter_by:

sudo salt '*' grains.filter_by '{Debian: apache2, RedHat: httpd}'

ubuntuAsus:
    apache2

apache.sls

# Install vanilla Apache on Debian/RedHat

{% set apache = salt.grains.filter_by({
    'Debian': {
        'pkg': 'apache2',
        'srv': 'apache2'
    },
    'RedHat': {
        'pkg': 'httpd',
        'srv': 'httpd'
    }
}) %}

install_apache:
  pkg.installed:
   - name: {{ apache.pkg }}

enable_apache:
  service.running:
    - name: {{ apache.srv }}

    # Will be enabled automatically on Debian but has to be enabled manually on RedHat
    - enable: True

# Adding a blank front page
add_landing_page:
  file.managed:
    - name: /var/www/html/index.html
    - contents: |
        <!doctype html>
        <body><h1>Salty Dayze, Sailor!</h1></body>

Again, you can test your script with sudo salt '*' state.show_sls apache | less.

Splitting up our Files

We can now break up our configuration file so that every SLS file only does one thing - if possible. We will collect the resulting files inside a subdirectory /srv/salt/apache:

welcome.sls

# Adding a blank front page
add_landing_page:
  file.managed:
    - name: /var/www/html/index.html
    - contents: |
        <!doctype html>
        <body><h1>Salty Dayze, Sailor!</h1></body>

map.sls

# Get package/service name per OS version
{% set apache = salt.grains.filter_by({
    'Debian': {
        'pkg': 'apache2',
        'srv': 'apache2'
    },
    'RedHat': {
        'pkg': 'httpd',
        'srv': 'httpd'
    }
}) %}

init.sls

# Install vanilla Apache on Debian/RedHat

{% from 'apache/map.sls' import apache with context %}

install_apache:
  pkg.installed:
   - name: {{ apache.pkg }}

enable_apache:
  service.running:
    - name: {{ apache.srv }}

    # Will be enabled automatically on Debian but has to be enabled manually on RedHat
    - enable: True

mods.sls

{% for conf in ['status', 'info'] %}

mod_{{ conf }}:
  file.managed:
    - name: /etc/apache2/conf-available/mod_{{ conf }}.conf
    - contents: |
        <Location '/{{ conf }}'>
            SetHandler server-{{ conf }}
        </Location>

  {% if salt.grains.get('os_family') == 'Debian' %}
  cmd.run:
    - name: a2enmod {{ conf }} && a2enconf mod_{{ conf }}
    - creates: /etc/apache2/conf-enabled/mod_{{ conf }}.conf
  {% endif %}

{% endfor %}

Again, you can test your script with sudo salt '*' state.show_sls apache | less

Using Custom Python Scripts

You can write your own Python modules and execute them with Salt. Start by creating a folder /srv/salt/_modules and add your Python scripts - myUtils.py:

def getDate():
    return __salt__['cmd.run']('date')
sudo salt '*' saltutil.sync_modules  //sync script with all minions
sudo salt '*' myUtils.getDate

ubuntuAsus:
    Tue Aug  4 09:19:49 UTC 2020