Salt documentation, getting started, and best practises:
https://docs.saltstack.com/en/latest/
https://docs.saltstack.com/en/getstarted/
https://docs.saltstack.com/en/latest/topics/best_practices.html
- Installation
- Remote command execution
- Targetting
- Copying files
- Grains
- States
- Pillars
- Jinja temlates
- Salt formulas
- Storing sensitive data (passwords)
- Salt ssh
- Salt cloud
Table of contents generated with markdown-toc
Examples below are on Ubuntu 18.04.
Everything must (?) be run as root.
sudo -sroot@saltmaster:~$ apt-get install salt-masterWARNING Note that the salt-master install will create the /srv directory (with the root:root owner it seems) so chown it to salt:salt otherwise you will get silent errors when applying states!
root@saltmaster:~$ chown -R salt:salt /srvA minion is a machine you want to control.
root@mediacenter:~$ apt-get install salt-minionMinion configuration file is /etc/salt/minion You need to make 2 changes in it:
- set the hostname of the salt master
- and set id of the minion
If the name of your master machine is salt, then this is the default and you don't need to do step 1.
master: saltmaster
id: mediacenterHaving a DNS is certainly a good idea instead of having IP adresses hardcoded into files...
See https://docs.saltstack.com/en/latest/ref/configuration/index.html
Get the public key of the master:
root@saltmaster:~$ salt-key -F masterIn /etc/salt/minion, add the public key of the master (master.pub in the output of command above):
master_finger: 'b1:d4:c2:a1:82:c7:04:08:56:14:a1:dd:cc:2e:4a:d0:f2:9b:7c:9a:36:5b:56:7c:f8:07:85:0d:7f:93:ae:d3'Restart the minion:
root@mediacenter:~$ service salt-minion restartDisplay the public key of the minion:
root@mediacenter:~$ salt-call --local key.fingerShow all unaccepted keys on the master:
root@saltmaster:~$ salt-key -Laccept the minion key (if equal to the actual minion key, see above):
root@saltmaster:~$ salt-key -a mediacenteror to accept all in one:
root@saltmaster:~$ salt-key -Atest the connection (probably not testing the secure connection though):
salt '*' test.pingSee https://docs.saltstack.com/en/getstarted/fundamentals/remotex.html
Run commands on all machines:
root@saltmaster:~$ salt '*' cmd.run 'ls /tmp'show disk usage of all machines:
root@saltmaster:~$ salt '*' disk.usageinstall a package on all machines:
root@saltmaster:~$ salt '*' pkg.install gitshow network interfaces of all machines:
root@saltmaster:~$ salt '*' network.interfacesSee https://docs.saltstack.com/en/getstarted/fundamentals/targeting.html
targetting specific machines:
root@saltmaster:~$ salt 'media*' disk.usagetargetting using grains (grains are kind of properties of a salt host, either got from their own nature like their OS, or manually added):
root@saltmaster:~$ salt -G 'os:Ubuntu' test.pingtargetting using regex:
root@saltmaster:~$ salt -E 'mediacenter[0-9]*' test.pingtargetting using a list:
root@saltmaster:~$ salt -L 'mediacenter,masterminion' test.pingtargetting using a combination:
root@saltmaster:~$ salt -C 'G@os:Ubuntu and *minion or [email protected].*' test.pingcopying a ~/files.zip file (can large with --chunked) in the /tmp/ directory of minions:
root@saltmaster:~$ salt-cp --chunked '*' ~/files.zip /tmp/See https://docs.saltstack.com/en/latest/topics/grains/
Grains are properties either got by Salt from the env, or manually set.
Listing grains:
root@saltmaster:~$ salt '*' grains.ls
root@saltmaster:~$ salt '*' grains.itemsSalt state files are in YAML, so you probably want to have a look at this getting started:
http://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html
And you probably want to install a YAML validator:
root@saltmaster:~$ apt-get install yamllintStates describe what is to be done or, more specifically, the expected state of the minion.
See https://docs.saltstack.com/en/latest/topics/tutorials/starting_states.html
create a file /srv/salt/nettools.sls like this:
---
install_network_packages:
pkg.installed:
- pkgs:
- rsync
- curl
...Let's describe that file a little bit:
---
# In theory (not mandatory though) a YAML file is starting with ---
# This 'install_network_packages' is the ID for a set of data, and it is called the ID Declaration.
# (It seems that it has to be unique if you have many of them).
# Sometimes that ID can be used by the module as a parameter.
install_network_packages:
# Here we are using the 'pkg' state:
# (see https://docs.saltstack.com/en/latest/ref/states/all/salt.states.pkg.html)
# It is implemented by one of the pkg modules:
# (see https://docs.saltstack.com/en/latest/ref/modules/all/salt.modules.pkg.html)
# Here you want packages to be installed (pkg.installed), but
# you could specify pkg.removed for instance.
# You can also use the pkg state to add a custom repository.
pkg.installed:
# Then you have the list of "parameters" of the module
# Here there is only a single item: the pkgs parameter
- pkgs:
# That parameter is accepting a list of packages to be installed
- rsync
- curl
# In theory (not mandatory though) a YAML file is ending with ...
...validate it with:
root@saltmaster:~$ yamllint nettools.slsApply it on a minion:
root@saltmaster:~$ salt 'mediacenter' state.apply nettoolsCalling state.apply with no arguments starts a highstate (global state application?). Either on all minions:
root@saltmaster:~$ salt '*' state.applyOr on specific ones:
root@saltmaster:~$ salt 'mediacenter' state.applyIn Salt, the file which contains a mapping between groups of machines on a network and the configuration roles that should be applied to them is called a top file.
See https://docs.saltstack.com/en/latest/ref/states/top.html
There is one top file per environment (base, dev, prod) for instance. The 'base' environment is defined in Salt's default configuration. Those environments are either independent, or merged (see the /etc/salt/master config).
The top.sls file has to be located by default in /srv/salt/top.sls
The top file is used to define which state has to be applied on which minions:
---
base:
'*':
- nettools
...Let's describe that file a little bit:
---
# This is the top file of the 'base' environment
base:
# On all minions
'*':
# We want to apply the configuration defined in the nettools.sls file
- nettools
...You can locally run your state (if the config files are on the minion, which is generally not the case).
Let's assume with have a minion of the saltmaster machine too. It is very convenient to test the configuration. You can show the state of a minion (--local is a debug feature that makes the minion read state files locally instead of asking them to the master):
root@saltmaster:~$ salt-call --local state.show_highstateYou can report what would be done if the state "executed" on the minion (dry run):
root@saltmaster:~$ salt-call --local state.highstate test=TrueOf course on other hosts, the minion would not be able to access new files locally, so:
root@saltminion:~# salt-call state.highstate test=TrueThat test on the mediacenter host can also be run from the salt master (where you're developing):
salt 'saltminion' state.apply test=TrueAsk the local minion its "vision" of top:
root@saltmaster:~$ salt 'minion' state.show_topGet a report of dependency versions:
root@saltmaster:~$ salt-call --versions-reportSee https://docs.saltstack.com/en/latest/topics/pillar/
Pillar is an interface for Salt designed to offer global values that can be distributed to minions.
Pillars are not containing states, and they are not containing a description of what is to be done. Pillars are only containing data that will be used as an input for writing states.
A pillar file is also an sls file written in YAML, and its schema (its structure) is up to you.
By default, for the 'base' environment they are stored in the /srv/pillar directory.
For example here is a /srv/pillar/users.sls pillar file describing users:
---
users:
bob:
fullname: Bob
password: $6$axWX[...]/fjtG1
groups:
- users
ssh_auth:
- ssh-rsa AAAAB3NzaC1yc2EA[...]bcgnkzrKn/WkgfJfViYLw==
user_files:
enabled: true
alice:
fullname: Alice
password: $6$xr65i[...]6o7JWes1
groups:
- sudo
- users
user_files:
enabled: true
...The structure of this file is really up to you.
Then you must have a top.sls file to reference it. For instance here is a /srv/pillar/top.sls file that will state that this user configuration is for all machines:
---
base:
'*':
- users
...See http://jinja.pocoo.org/docs/2.10/templates/
Jinja is a template engine that is able to generate any text file using variables, expressions and directives.
A Jinja template contains variables and/or expressions, which get replaced with values when a template is rendered; and tags, which control the logic of the template. The template syntax is heavily inspired by Django and Python.
Within Salt, the Jinja engine will operate on grain and/or pillar data.
Here is a /srv/pillar/pkgs.sls pillar that is defining the name of packages depending on the grains data:
---
pkgs:
{% if grains['os'] == 'RedHat' %}
apache: httpd
git: git
{% elif grains['os'] == 'Debian' %}
apache: apache2
git: git-core
{% endif %}
...That's an illustration of Jinja if statement:
http://jinja.pocoo.org/docs/2.10/templates/#if
WARNING: please note that pillar Jinja can only work from grain data. AFAIK you cannot write a a pillar Jinja (let's say samba user configuration) based on another pillar (listing your users). This limitation is described here:
It seems that Saltclass can overcome this limitation:
There is some example data here:
https://git.mauras.ch/salt/saltclass/src/branch/master/examples
Validating a jinja file can be done using the slsutil.renderer module:
salt 'masterminion' slsutil.renderer /srv/salt/users.sls 'jinja'or:
salt-call --local slsutil.renderer /srv/salt/users.sls 'jinja'Note that those command do not tell you if the execution of your file will succeed on minions, they will just tell you if there is a Jinja syntax error.
WARNING: pay a great attention to Jinja Whitespace Control (described in http://jinja.pocoo.org/docs/2.10/templates/).
The following Jinja will generate an empty line after the users, and YAML will complain:
---
users:
{% for name, user in pillar.get('users', {}).items() %}This one (notice the - character after {%) will be ok since no empty line will be generated:
---
users:
{%- for name, user in pillar.get('users', {}).items() -%}Maybe this one would work too (notice indentation before {%) but I've not tested:
---
users:
{% for name, user in pillar.get('users', {}).items() %}Let's start with a simple /srv/salt/pkgs.sls state that is based on the /srv/pillar/pkgs.sls pillar.
This example is an illustration of Jinja variables: http://jinja.pocoo.org/docs/2.10/templates/#variables
---
apache:
pkg.installed:
- name: {{ pillar['pkgs']['apache'] }}
...You can also create more complex states like this /srv/salt/users.sls state based on the /srv/pillar/users.sls we defined above.
This example is an illustration of Jinja for control structure: http://jinja.pocoo.org/docs/2.10/templates/#for
Let's start with a non-commented version, then we'll add comments:
---
{% for name, user in pillar.get('users', {}).items() if user.absent is not defined or not user.absent %}
{%- if user == None -%}
{%- set user = {} -%}
{%- endif -%}
{%- set user_files = salt['pillar.get'](('users:' ~ name ~ ':user_files'), {'enabled': False}) -%}
{%- set home = user.get('home', "/home/%s" %name) -%}
{%- set user_group = name -%}
{% for group in user.get('groups', []) %}
users_{{name}}_{{group}}_group:
group:
- name: {{group}}
- present
{% endfor %}
users_{{name}}_user:
group.present:
- name: {{ user_group }}
- gid: {{ user['uid'] }}
user.present:
- name: {{ name }}
- home: {{ home }}
- uid: {{ user['uid'] }}
- password: {{ user['password'] }}
- fullname: {{ user['fullname'] }}
- groups:
- {{ user_group }}
{% for group in user.get('groups', []) %}
- {{ group }}
{% endfor %}
{% if 'ssh_auth' in user %}
{% for auth in user['ssh_auth'] %}
users_ssh_auth_{{name}}_{{loop.index0 }}:
ssh_auth.present:
- user: {{ name }}
- name: {{ auth }}
{% endfor %}
{% endif %}
{% if user_files.enabled %}
vimrc_{{name}}:
file.managed:
- name: {{home}}/.vimrc
- source: salt://files/.vimrc
{% endif %}
{% endfor %}
...Here is the commented out version:
---
# This is a Jinja template file (see http://jinja.pocoo.org/docs/2.10/templates/)
# loop on dictionary that is below the 'users' key of the pillar
# and use an empty dictionary {} if no user specified below users
# then put the key of the current entry in the 'name' variable,
# and put the value of the current entry (a dictionary) in the 'user' variable.
# maybe that code also allows to disable a user with the user.absent stuff?
{% for name, user in pillar.get('users', {}).items() if user.absent is not defined or not user.absent %}
# if the user dictionary is not defined, use an empty dictionary
# the minus - sign means that this template line will not generate an empty line
# (see "Whitespace Control" in http://jinja.pocoo.org/docs/2.10/templates/)
{%- if user == None -%}
{%- set user = {} -%}
{%- endif -%}
# here we are doing several dictionary lookups (users -> name -> user_files) to get the value of that
# user_files dictionary entry. If it is not defined, we will be using the {'enabled': False} dictionary.
{%- set user_files = salt['pillar.get'](('users:' ~ name ~ ':user_files'), {'enabled': False}) -%}
# here we are getting the value of the home key in the user dictionary, or building it from the
# user name if it does not exist
{%- set home = user.get('home', "/home/%s" %name) -%}
# set the user_group variable to be the name of the user
{%- set user_group = name -%}
# at this point, we have only read pillar data, and set Jinja variables
# now we will write salt states
# first we loop on groups of the users (defined in the pillar)
{% for group in user.get('groups', []) %}
# and we are creating a salt state using the group module of salt
# https://docs.saltstack.com/en/2017.7/ref/modules/all/salt.modules.groupadd.html#module-salt.modules.groupadd
# it looks like salt state name has to be unique, and we make sure it is
# by using the user name and the group and a bit of context around
# so here we are declaring that each group of the user (declared in the pillar) must be present on the machine
users_{{name}}_{{group}}_group:
group:
- name: {{group}}
- present
{% endfor %}
# then create the user
users_{{name}}_user:
# maybe this is a trick to make sure the group is present before creating the user?
# actually I don't know the difference between "group - present" above and "group.present" below:
group.present:
- name: {{ user_group }}
- gid: {{ user['uid'] }}
# now we are making sure the user is present
# otherwise it will be created with the following properties
user.present:
- name: {{ name }}
- home: {{ home }}
- uid: {{ user['uid'] }}
- password: {{ user['password'] }}
- fullname: {{ user['fullname'] }}
- groups:
- {{ user_group }}
# again a loop on users groups
{% for group in user.get('groups', []) %}
- {{ group }}
{% endfor %}
# now setup our public ssh keys
{% if 'ssh_auth' in user %}
{% for auth in user['ssh_auth'] %}
# again, here we are trying hard to have unique names for salt states by using the loop index:
users_ssh_auth_{{name}}_{{loop.index0 }}:
# here we are using the salt ssh_auth module
# (see https://docs.saltstack.com/en/latest/ref/states/all/salt.states.ssh_auth.html)
# to add our public keys to the users
ssh_auth.present:
- user: {{ name }}
- name: {{ auth }}
{% endfor %}
{% endif %}
# now we are going to copy some files in the home directory
{% if user_files.enabled %}
vimrc_{{name}}:
# we will do so using the file module of salt
# https://docs.saltstack.com/en/develop/ref/modules/all/salt.modules.file.html
file.managed:
- name: {{home}}/.vimrc
- source: salt://files/.vimrc
{% endif %}
# end loop on users
{% endfor %}
...There are many salt states already written by the salt community, serving many purposes. Those are called salt formulas (see https://docs.saltstack.com/en/latest/topics/development/conventions/formulas.html)
You can find plenty of them on this github (apache, php, samba...): https://github.com/saltstack-formulas
Altough you can clone or download formula files by yourself, salt can do that for you through its GitFS fileserver backend. You can directly use formulas stored in the saltstack-formulas Github account, or you can fork them on your own account for more stability (your version won't be updated).
In /etc/salt/master add those lines (they are commented-out somewhere in the file) to use the samba formula:
fileserver_backend:
- roots
- gitfs
gitfs_remotes:
- git://github.com/bfreuden/samba-formula.gitThen restart the salt master:
root@saltmaster:~$ service salt-master restartEach Github formula repository has a directory containing state files: for instance the samba directory for the samba-formula repository (https://github.com/bfreuden/samba-formula/tree/master/samba)
When formulas are configurable, they also have an pillar.example file next to it (https://github.com/bfreuden/samba-formula/blob/master/pillar.example)
To use the formula in your state files, you have to mention this directory name in your /srv/salt/top.sls file:
---
base:
'mediacenter':
- samba
- samba.config
...To override the default configuration, we must now define a /srv/pillar/samba.sls based on the pillar.example file. Something like this should be enough:
samba:
conf:
render:
section_order: ['global', 'user1share']
sections:
global:
workgroup: workgroup
user1share:
comment: "user1 samba share"
path: /home/user1
valid users: user1
create mode: '0660'
directory mode: '0770'
public: no
writable: yes
printable: no
users:
user1:
password: user1sambapassword
user2:
password: user2sambapasswordI am generally not editing my salt files on place and editing file in my home directory, then copying them into /srv (for many reasons, but mostly because of the salt:salt owner of the files).
So let's rsync files from my home directory to /srv and change the owner:
root@saltmaster:~/saltdev/srv# rsync -avz --exclude '.git' /home/bruno/saltdev/srv/ /srv/ ; chown -R salt:salt /srvNow let's test that before actually deploying it:
root@saltmaster:~/saltdev/srv/pillar# salt 'mediacenter' state.apply test=TrueIf you have used Jinja you can validate your pillar with:
root@saltmaster:~/saltdev/srv/pillar# salt-call --local slsutil.renderer /srv/pillar/samba.sls 'jinja'Salt can leverage GPG encryption inside sls files (https://docs.saltstack.com/en/latest/ref/renderers/all/salt.renderers.gpg.html).
This guy has been incredibly helpful: https://gist.github.com/vrillusions/5484422
This is how to set this up (creates a saltmaster key):
root@saltmaster:~/saltdev/srv/pillar#
mkdir -p /etc/salt/gpgkeys
chmod 0700 /etc/salt/gpgkeys
cat <<EOT >> /tmp/genkey-batch
%no-protection
Key-Type: default
Subkey-Type: default
Name-Real: saltmaster
Name-Email: [email protected]
Expire-Date: 0
EOT
export GNUPGHOME=/etc/salt/gpgkeys
gpg --batch --gen-key /tmp/genkey-batchThen export the saltmaster public key:
root@saltmaster:~/saltdev/srv/pillar#
gpg --homedir /etc/salt/gpgkeys --armor --export saltmaster > /tmp/saltmaster_pubkey.gpgThen import the saltmaster public key into your own keyring:
me@saltmaster:~/saltdev$ gpg --import /tmp/saltmaster_pubkey.gpgNow you can use the saltmaster public key to encrypt data:
me@saltmaster:~/saltdev$ echo -n "supersecret" | gpg --armor --batch --trust-model always --encrypt -r saltmasterThe encrypted data can be put into your yaml files. Just make sure the file is starting with #!yaml|gpg and use the following syntax (pay attention to the indentation! the file CANNOT start with --- like the others!):
#!yaml|gpg
users:
bruno:
fullname: Me
samba:
password: |
-----BEGIN PGP MESSAGE-----
hQGMAzAL2NXA7O5UAQv/XKcFQHs7AovUR8T/c4AwQBjHVNMrPGIHQ7N3ZN22m2kP
zqbJnn13SdOUlxW9u76UvP/g2pf5W/hUWyri3gPGCF65s92rHLsRcuAvVNugWQNH
IOywRt2G5PhUdSFC6xr6OnVivOglCnReCmjJgWimLGBfetnePUzpLF4CtdTZGF2g
lcGv/GyaWCk86s/cF15rMr2s+9tMKcD/2YbdoOEgUTgt0gxuAspz8yvb1TMH4a92
V5s/Ve68KjHxNCutNgdBnH33NXlEymL+hdD4mLKDHxNQCrwuAazrj65QqFkmiF/P
ZdvRfwfhYwuAIUDQArwI4s4QIPJ4iQ4Opo1J8f0OlykHp4klRApQa3NzCOuLecFG
1B1HBM/hD2hedOuDpjt6XWLhzqTMOdMF3BNyrNlYK6OLFis70kjdWEKcGMIqNiTA
FjAFq7c0tv+XjYhNcT9re2IIoBWSM3yTWbncA2kcHfmcun6h3X76OXoZez+g+65g
Wf0utBVEZHQJd/kF5yYu0kMBOcAwEOhpndv9iW5cax+mT1UGo9d/jxI+sURhX0t+
9/LZp5enwIy1khfXxs9vv4EmBkyADkT6/8eNqzhEXjA8uRQz
=kzuB
-----END PGP MESSAGE-----
...Salt can be used in agentless mode, without minion.
https://docs.saltstack.com/en/latest/topics/ssh/
One probably has to think twice before using salt-ssh because Ansible might be a best contender here...
Salt is an agent-based solution by design: salt master is a server, salt minions are servers. When minions are applying a formula requiring files, they can simply download them from the server.
With salt-ssh when a file is required by a formula, it must be sent in along with the formula. Analyzing formulas to find files is apparently a difficult task when Jinja is involved. So salt-ssh has an --extra-filerefs option to manually specify a file to be included. But doing so places the burder on the user of the formula (who doesn't know it!).
On top of that --extra-filerefs doesn't seem to work well when targeting a file on gitfs. So there are times you can't simply use gitfs. So you have to clone the formula and declare it file roots of your salt environment.
see: saltstack/salt#19564
The following setup is a bit complex but it seems it is required to be able to run salt-ssh in non root.
First install salt-ssh:
sudo apt-get install salt-sshThen create salt_setup (the name is up to you) in your home directory:
cd
mkdir salt_setup
cd salt_setup
mkdir -p {config,salt/{files,templates,states,pillar,formulas,pki/master,logs}}
mkdir cache
touch ssh.log
cdThen copy the content of /etc/salt here:
cp -rp /etc/salt/* ~/salt_setup/
chown $USER. -R ~/salt_setupFinally create master and Saltfile files:
cat <<EOT >> ~/salt_setup/master
root_dir: "/home/$USER/salt_setup"
pki_dir: "pki"
cachedir: "cache"
log_file: "salt-ssh.log"
file_roots:
base:
- /home/$USER/salt_setup/salt
fileserver_backend:
- roots
- gitfs
EOT
cat <<EOT >> ~/salt_setup/Saltfile
salt-ssh:
config_dir: "/home/$USER/salt_setup/"
log_file: "/home/$USER/salt_setup/ssh.log"
pki_dir: "/home/$USER/salt_setup/pki"
cachedir: "/home/$USER/salt_setup/cache"
roster_file: "/home/$USER/salt_setup/roster"
# ssh_wipe: True
EOTDeclare servers in roster file:
for server in server1 server2 server3; do \
echo "$server:" >> ~/salt_setup/roster ; \
echo " host: $server" >> ~/salt_setup/roster ; \
echo " user: $USER" >> ~/salt_setup/roster ; \
echo " sudo: true" >> ~/salt_setup/roster ; \
echo "" >> ~/salt_setup/roster ; \
doneIt must give something like this: /etc/salt/roster
server1:
host: server1
user: me
sudo: true
server2:
host: server2
user: me
sudo: true
server3:
host: server3
user: me
sudo: trueThen enable sudo NOPASSWD for your user on all (ubuntu) machines:
for server in server1 server2 server3; do \
ssh -t $USER@$server "echo '$USER ALL=(ALL) NOPASSWD: ALL' | sudo tee -a /etc/sudoers" ; \
donePlease note that the procedure below is not the recommended way of installing keys since salt-ssh can do that for you during the first salt-ssh invocation (see https://youtu.be/qWG5pI8Glbs). Salt-ssh can also keeping prompting for your password if you prefer not deploying your key. Installing a key with Salt can be done like this although it is not clear to which public key is actually installed (looks like salt has its own key pair):
me@laptop:~/salt_setup$ salt-ssh -i server1 pkg.install cowsay
Permission denied for host server1, do you want to deploy the salt-ssh key? (password required):
[Y/n] Y
Password for me@server1: That's why I prefer the method below.
Deploying SSH key on your freshly installed machines:
for server in server1 server2 server3; do \
ssh $USER@$server "mkdir .ssh ; chmod 700 .ssh ; echo \"`cat ~/.ssh/id_rsa.pub`\" >> .ssh/authorized_keys" ; \
doneIt would probably better with (but not tested):
for server in server1 server2 server3; do \
ssh-copy-id -i ~/.ssh/id_rsa.pub $USER@$server ; \
doneNote: to run salt-ssh as non-root you must be in the ~/salt_setup directory.
cd ~/salt_setupFinally run your first salt-ssh command:
salt-ssh -i server2 disk.usageNow let's install the cowsay package on all systems:
salt-ssh -i "*" pkg.install cowsayAnd finally run a command on one of the machine:
salt-ssh -i server1 -r '/usr/games/cowsay "Hello me lad!"'If you don't want to leave any trace on the system you can use the -W wipe option:
salt-ssh -i server1 -W -r '/usr/games/cowsay "Hello me lad!"'Since salt-ssh 2017 delivered with Ubuntu 18.04 is too old and has a bug with gitfs, the following procedure has been done by installing a more recent version of salt-ssh using pip:
conda create -n salt
conda activate salt
conda install python=3.7
pip install salt-ssh
pip install pygit2Here we'll install apache on all machines.
First clone the formula (you should have your own fork, see Salt Formulas above) since extra filerefs don't work on gitfs:
cd ~/salt_setup/salt/formulas
git clone https://github.com/saltstack-formulas/apache-formula.git
rm -rf ~/salt_setup/salt/formulas/apache-formula/.git
EOTDeclare the formula and required filerefs in your master configuration (~/salt_setup/master):
file_roots:
base:
- /home/bruno/salt_setup/salt
- /home/bruno/salt_setup/salt/formulas/apache-formula
extra_filerefs:
- salt://apache/map.jinjaDownload the example apache pillar:
curl -o ~/salt_setup/salt/pillar/apache.sls https://raw.githubusercontent.com/saltstack-formulas/apache-formula/master/pillar.exampleCreate the top.sls:
cat <<EOT >> ~/salt_setup/salt/top.sls
---
base:
'*':
- apache
EOTThen go into your salt directory:
cd ~/salt_setupInstall apache on all machines:
salt-ssh '*' state.apply Test the installation of your apache:
curl server1/index.htmlTo uninstall apache, modify the top.sls:
---
base:
'*':
- apache.uninstallThen apply the state on all machines:
salt-ssh '*' state.apply Salt can be used to manage (provisioning, starting, stopping) cloud instances (Amazon, Azure...)
(TBD)