One more step to autodeploy

saltstack

For maintenance of our servers we use Satl Stack. Few days ago we installed packages from states and define versions in pillars. So we had for every cluster some states (e.g. workers.sls, launchers.sls, masters.sls). When we wanted add some package we had to edit state and pillar. Additionally, if we had to adjust config for this package we had edit more states. One day we said we have to find better way for install package. The way where deployers can edit (via merge request to our gitlab - where we have our salt states with pillars) configs and versions that are in productions servers.

We had states and pillars in this format.

state-launcher.sls

clusterlauncher:
  pkg.installed:
    - name:
      - szn-email-job: {{ pillar['szn-email-job'] }}
      - szn-email-activity: {{ pillar['szn-email-activity'] }}
      - szn-fulltext-generator: {{ pillar['szn-fulltext-generator'] }}
      - szn-fulltext-dump: {{ pillar['szn-fulltext-dump'] }}
    - refresh: True

pillar-cluster.sls

szn-email-job: 0.1.0
szn-email-activity: 0.0.17
szn-fulltext-generator: 0.1.21
szn-fulltext-dump: 0.0.3

We made adjustments to our states and pillars. What we want know about package? We want to know where to install package, his version and config file. So we chose this format for our new pillars.

new-pillar-cluster.sls

#there is two ways for define package for install
# 1)
#   package-name: 0.0.1
#
# 2)
#   package-name:
#     version: 0.0.1
#    [conf-src: salt://path/to/conf]
#    [conf-dst: /path/to/conf/on/server]
pkgs:
  - clusterlauncher\d+\.ng\.seznam\.cz:
    szn-email-job: 0.1.0
    szn-email-activity:
      version: 0.0.17
      conf-src: salt://conf/szn-email-activity/
      conf-dst: /www/szn-email-activity/conf/
    szn-fulltext-generator: 0.1.21
    szn-fulltext-dump:
      version: 0.0.3
      conf-src: salt://conf/szn-fulltext-dump/config.xml
      conf-dst: /www/szn-fulltext-dump/conf/config.xml

You will se that is optionally if we use conf or not. Plus we can copy one file or directory (if conf-dst last char is "/"). After this we must deploy state for installation packages and copy configs. But there was problem. Jinja can`t work with regex. Really! So we had to add workaround via salt module.

my_re.py

import re

def match(pattern, st, flags=0):
  return re.match(pattern, st, flags)

Ok. After this we can finaly deploy states. We wrote two states:

pkg-regex-install.sls

{% if pillar['pkgs'] is defined %}

regex-install:
  pkg.installed:
    - pkgs:
#### we can`t have empty list of pkgs for install (when for loop is empty) ####
      - apt
{% for regex, pkgs in salt['pillar.get']('pkgs', {}).iteritems() %}
  {% if salt['my_re.match'](regex, grains['fqdn']) != None %}
    {% for pkg, metadata in pkgs.iteritems() %}
      {% if 'version' in metadata %}
      - {{ pkg }}: {{ metadata['version'] }}
      {% else %}
      - {{ pkg }}: {{ metadata }}
      {% endif %}
    {% endfor %}
  {% endif %}
{% endfor %}
    - refresh: True

{% endif %}

config-regex.sls

{% if pillar['pkgs'] is defined %}

{% for regex, pkgs in salt['pillar.get']('pkgs', {}).iteritems() %}
  {% if salt['my_re.match'](regex, grains['fqdn']) != None %}
    {% for pkg, metadata in pkgs.iteritems() %}
      {% if 'conf-dst' in metadata %} 
{{ metadata['conf-dst'] }}:
        {% if metadata['conf-dst'][-1] == "/" %}
  file.recurse:
        {% else %}
  file.managed:
        {% endif %}
    - source: {{ metadata['conf-src'] }}
    - template: jinja
      {% endif %}
    {% endfor %}
  {% endif %}
{% endfor %}

{% endif %}

Now when we want to add some package we edit only one file. Plus deployers can themself send merge request with his changes in config or version. Then sysadmin only check changes and run state.highstate on salt master to server.