This started off as a longer rant about things that frustrate me about salt, but for now I’ll limit myself to one thing: the syntax.

Salt is driven by YAML config files, where you describe your desired configuration as series of named ‘states’.

For example:

root_gitignore:
  file.managed:
    - name: /root/.gitignore_global
    - source: salt://git/.gitignore_global

In this we’ve got a label for the state: root_gitignore; the name of the state module and function to run: file.managed; and finally, a list of arguments to that function.

However, the salt documentation immediately starts you off with something like this:

/root/.gitignore_global:
  file.managed:
    - source: salt://git/.gitignore_global

In this, the name of the file to manage has been used as the state ID, because that’s used as the name parameter if not explicitly defined.

This very quickly falls down as a syntactic short-cut because:

  • Not everything salt manages is a file. In fact, the first thing you’d probably try to manage would be packages, which have a bunch of exceptions where the name isn’t used.

  • Defining dependencies requires duplication of that ID, e.g.

    other_state:
      do.something:
        - 
        - watch:
          - /root/.gitignore_global
    
  • You can’t use the same name twice, on account of YAML being a dictionary/list syntax that doesn’t support duplicate keys. This means you end up defining redundant names for everything, e.g. adding a plugin to dokku:

    dokku_plugin_letsencrypt:
      cmd.run:
        - name: 'dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git'
        - unless: 'dokku plugin:list | grep letsencrypt'
    
    dokku_config_letsencrypt:
      cmd.run:
        - name: 'dokku config:set --global --no-restart DOKKU_LETSENCRYPT_EMAIL={{ letsencrypt_email }}'
        - unless: 'dokku config:get --global DOKKU_LETSENCRYPT_EMAIL | grep {{ letsencrypt_email }}'
    
    dokku_cron_letsencrypt:
      cron.present:
        - name: '/var/lib/dokku/plugins/available/letsencrypt/cron-job'
        - user: dokku
        - special: '@daily'
    

How could this be improved? One possibility would be to support lists of commands for each ID:

dokku_letsencrypt:
  - cmd.run:
      name: 'dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git'
      unless: 'dokku plugin:list | grep letsencrypt'
  - cmd.run:
      name: 'dokku config:set --global --no-restart DOKKU_LETSENCRYPT_EMAIL={{ letsencrypt_email }}'
      unless: 'dokku config:get --global DOKKU_LETSENCRYPT_EMAIL | grep {{ letsencrypt_email }}'
  - cron.present:
      name: '/var/lib/dokku/plugins/available/letsencrypt/cron-job'
      user: dokku
      special: '@daily'

To implement this change in a backwards-compatible fashion it might be possible to pre-process states so they work with the existing dependency resolution and output formatters.

E.g. The dokku_letsencrypt list could be flattened into multiple IDs:

dokku_letsencrypt_0:
  cmd.run:
    - name: 
    - unless: 

dokku_letsencrypt_1:
  cmd.run:
    - name: 
    - unless: 

Dependencies that refer to the parent could be translated in the same way:

other_state:
  do.something:
    - 
    - watch:
      - dokku_letsencrypt_0
      - dokku_letsencrypt_1
      - dokku_letsencrypt_2