Salt Cheat Sheet

From UVOO Tech Wiki
Revision as of 12:31, 21 July 2020 by Busk (talk | contribs) (Created page with "- Shameless rip from - https://sites.google.com/site/mrxpalmeiras/saltstack/salt-cheat-sheet ``` Contents 1 BOOTSTRAP 2 SERVICES 3 TARGETING 4 KEY MANAGEMENT 5 SERVER DIAGN...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
Contents

1 BOOTSTRAP
2 SERVICES
3 TARGETING
4 KEY MANAGEMENT
5 SERVER DIAGNOSTICS
6 GRAINS
7 MINE
8 PACKAGES AND INSTALLATION
9 JOBS AND PROCESS CONTROL
10 REACTOR
11 DEBUG
12 FILE OPERATIONS
13 USER AND GROUP MGMT
14 STATES
15 SSH Management (Agentless)
16 PILLAR
17 PORTS & NETWORK
18 JINJA
19 ETC
DOCS & FILES
Modules List
https://docs.saltstack.com/en/latest/salt-modindex.html
State List
https://docs.saltstack.com/en/latest/ref/states/all/index.html#all-salt-states

Salt source files
/usr/lib/python<version>/site-packages/salt


BOOTSTRAP
Bootstrap Install
https://repo.saltstack.com/#bootstrap

wget -O bootstrap_salt.sh https://bootstrap.saltstack.com 

install Master
sh bootstrap_salt.sh -M  

install specific Salt version
sh bootstrap_salt.sh git v2015.8.8
SERVICES

Start / Stop / Restart service on Minion
salt 'target' service.start "service name"  (start/stop/restart)

Restart Minion on Win target
salt 'target' cmd.run 'start powershell "Restart-Service -Name salt-minion"'

Restart Minion on Linux target
salt 'target' cmd.run 'service salt-minion restart"'

Execute a script remotely
salt target cmd.exec_code python 'import sys; print sys.version'
2.7.8 GCC 4.9.1

salt target cmd.exec_code sh 'echo $PATH'
/usr/local/bin:/usr/local/sbin

Check service on minion
salt target service.status httpd

Check if service is available
salt target service.available httpd

get all services
salt target service.get_all

reload a service config (avoids restart)
salt target service.reload httpd

start | stop | restart a service
salt target service.start httpd


TARGETING
by OS grain
salt -G os:Windows cmd.run "net stop Firewall"   

by other grains
salt –G 'server_type:app and env:prod' state.highstate

target EC2 instances only
salt -G uuid:ec2\* test.ping

compound match
salt –C 'server_type:web and clo*' state.sls nginx

list based match
salt –L 'hostname1,hostname2,hostname3' state.sls ntp

Nodegroup match
salt –N ny_db_servers cmd.run 'ps –ef | grep mysql'

regex OR
salt -E "(nyweb|db5)" test.ping

by pillar value
salt -I 'role:webserver' test.ping
KEY MANAGEMENT

Add Minions to Master
salt-key -L (show pending to be accepted)
salt-key -A (accept all pending)
salt-key -a target (accept by hostname)

Remove inactive minions from Salt
salt-run manage.down removekeys=True

Remove minions by name
salt-key -D targetName


SERVER DIAGNOSTICS

Test Connection
salt 'target' test.ping

Diagnostics
salt target status.all_status // gets all info
status.cpu_info
status.cpustats
status.uptime
status.diskusage  // or disk.usage
status.loadavg
status.meminfo
status.netdev  // network device
status.netstats //network stats
status.procs
status.version //system version
status.vmstats //virtual mem stats
status.w  //who is logged in

Show Minions by State (Up/Down)
 salt-run manage.up
 salt-run manage.down
 salt-run manage.status  (show all by status)


Compliance and Audit

to get a compliance result, run a State check with test=True
salt \* state.highstate test=True

This will return any differences from existing configuration to whats in
the Top file


Show Salt Master version
salt --versions-report

Show Salt Minion version
salt-call --versions-report


Start Minion in Debug mode
salt-master --log-level=debug

Restart everything on Master:
pkill salt-minion  //Kill minion
pkill salt-syndic  // Kill Syndic
salt-run cache.clear_all   //Clear all cache
salt '*' saltutil.sync_grains   //Sync grains
salt-master -d  //Start master daemon
salt-minion -d  //Start minion daemon
salt-syndic -d  //Start syndic daemon

Agent Env Info

show all information about a minion (lots of data)
salt minion status.all_status

show memory
salt minion status.meminfo

show disk usage
salt minion status.diskusage

show who is logged in
salt minion status.w

GRAINS

Show Grain data
salt '*' grains.ls   
salt '*' grains.items

get specific Grain 
salt cent7 grains.get selinux
cent7:
    ----------
    enabled:
        True
    enforced:
        Enforcing

can also do the same with 
salt cent7 grains.item selinux

set a Grain data on a node
salt cent7 grains.set 'apps:Myapp:port' 2500

salt cent7 grains.item apps
cent7:
    ----------
    apps:
        ----------
        Myapp:
            ----------
            port:
                2500


All grain data is stored on the minion in /etc/salt/grains file
if adding more data manually, refresh Grains on the Master to pick up changes
salt target saltutil.refresh_modules

Use grain in a state file
apache:
  pkg.installed:
    {% if grains['os'] == 'RedHat' %}
    - name: httpd


show JSON output
salt target grains.item ipv4 --out=json
{
    "target": {
        "ipv4": [
            "10.0.2.15", 
            "127.0.0.1", 
            "192.168.56.102"
        ]
    }
}


Use grain as a variable
{% set nodename = grains['nodename'] %}

base:
  '*':
    - common
    - packages
    - users
    - servers.{{ nodename }}









MINE

show mine data
salt \* mine.get \* x509.get_pem_entries







PACKAGES AND INSTALLATION

Verbose output (timeout 300 sec)
salt 'target' state.hightstate -t 300 -v

Show package version
salt 'target' pkg.version apache

install package on minions
salt 'target' pkg.install apache

Uninstall pkg
salt 'target' pkg.remove 'npp'
salt 'target' pkg.purge 'npp'

Show Installed Packages or Software
salt 'target' pkg.list_pkgs

show all packages that need updates
salt target pkg.list_upgrades

upgrade all packages
salt target pkg.upgrade


Windows (Chocolatey)
install chocolatey
salt wintarget chocolatey.bootstrap  

install pkg using choco
salt mrxwin7 chocolatey.install 7zip 



JOBS AND PROCESS CONTROL

Show all Salt jobs run history
salt-run jobs.list_jobs

Show active Salt jobs
salt-run jobs.active    // returns a Job ID

Show currently running processes on a minion
salt '*' saltutil.running

Kill active job
salt 'target' saltutil.kill_job $JOB_ID
salt '*' saltutil.term_job <job id>

Clear Job cache
salt '*' saltutil.clear_cache


REACTOR

examples of reactor matching

/etc/salt/master.d/reactor.conf
reactor:
  - 'sayhello':
    - /srv/reactor/test.sls

/srv/reactor/test.sls
{% if data['id'].startswith('web') %}
sayhello:
  local.state.apply:
    - tgt: {{ data['id'] }}
    - arg:
      - say-hello

  local.cmd.run:
    - tgt: minion1
    - arg: 
      - "echo 'hello' > /tmp hello"

{% endif %}


DEBUG

Run highstate in debug
salt-call -l debug state.highstate

Run specific state in debug
salt-call -l debug state.sls elasticsearch

show highstate process (debug YAML syntax errors)
salt-call state.show_highstate

show specific State details
salt 'target' state.show_sls apache 

show only Changed and Failed during run
modify /etc/salt/master  and /etc/salt/minion, restart Master after change
state_verbose: True
state_output: mixed

start minion in debug, see connection errors
salt-minion -l debug

https://docs.saltstack.com/en/latest/topics/troubleshooting/minion.html

if Master not seeing Minion key requests, add IPTables rules to Master,
root@master# iptables -I INPUT -s 172.31.23.0/24 -p tcp -m multiport --dports 4505,4506 -j ACCEPT
root@master# iptables -I INPUT -s 172.31.25.0/24 -p tcp -m multiport --dports 4505,4506 -j ACCEPT

# reject everything else,
root@master# iptables -A INPUT -p tcp -m multiport --dports 4505,4506 -j REJECT

Log Jinja variables to Minion
{% do salt.log.error('testing jinja logging') -%}

show Options passed to a State (ie, test=true)
{% do salt.log.error(opts['test']) -%}

Output variables from State file,
show_var:
    - test.show_notification:
        - text:  This is my var {{ var }}



Exit w failure message

fail_run:
  test.fail_without_changes:
    - name: your message here




FILE OPERATIONS
Check if file contains a string (true/false)
salt '*' file.contains /etc/ssh/sshd_config 'Port'

Check if a file on the Salt minions contains a certain regex (search file on minions):
salt "*" file.contains_regex /etc/resolv.conf "timeout.4"

check if file is a file or directory
salt target file.stats /etc/hosts

Find a file
salt '*' file.find /etc name=host\*.\*
result
- /etc/host.conf
- /etc/hosts.allow
- /etc/hosts.deny'
copy small file ( > 100kb)  from Master to minion
salt-cp 'target' /opt/file (source)  /opt (destination)

copy dir from Master /srv/salt area to minion
salt 'target' cp.get_dir salt://myDir /target/dir

copy large file from Master /srv/salt/distribution folder to minion
salt 'target' cp.get_file salt://distribution/myFile.tar   /tmp/myFile.tar


copy file from one minion to another using MinionFS (only works on Salt 2016.3.2)
https://docs.saltstack.com/en/latest/topics/tutorials/minionfs.html

add MinionFS to master conf file
vi /etc/salt/master

add this lines: 
minionfs_mountpoint: salt://minionfs
file_recv: True

restart Master

get the file from the minion and store it in MinionFS
salt 'target' cp.push /path/to/file/or/dir/on/minion

files are stored on Master in here:
/var/cache/salt/master/minions/<minion>


copy file from Minion to Master
on Master, set "file_recv: True"  in /etc/salt/master, restart Master

to copy file, 

salt \* cp.push /path/to/file/on/minion

all files are stored on Master /var/cache/salt/master/minions/<minion>/files

add host entry to a minion
salt target hosts.add_host 192.168.55.100 hostname

Replace contents of file with new value
salt '*' file.sed /etc/ssh/sshd_config 'Port 22' 'Port 2201'

Create folder
salt '*' file.makedirs /tmp/testFolder/ (for windows use native Win syntax, ie C:/temp/dir)

Delete folder
salt '*' file.remove /tmp/testFolder

Create new file
salt '*' file.touch /tmp/testFolder/emptyFile

manage file content in State file
/etc/fstab:
  file.line: 
    - content: "proc    /proc"
    - mode: insert
    - after

replace contents of file
update_modprobe:
  file.replace:
    - name: /etc/modprobe.d/salt_cis.conf
    - pattern: "^install {{ fs }} /bin/true"
    - repl: install {{ fs }} /bin/true
    - append_if_not_found: True

create symlink
symlink:
  file.symlink:
    - name: /path/to/A
    - target: /symlink/path/A
create directory

/home/qb/q3:
  file.directory:
    - user: qb
    - group: qb
    - dir_mode: 755
    - file_mode: 755
    - require:
        - user: qb

replace file contents using Augeas
sshd_config:
  augeas.change:
    - context: /files/etc/ssh/sshd_config
    - changes:
      - set Port 8888
      - set PasswordAuthentication yes
      - set PubkeyAuthentication yes

copy directory from Master to minion

app_ta_nix_dir:
    file.recurse:
        - name: /opt/splunkforwarder/etc/apps/Splunk_TA_nix
        - source: salt://{{ slspath }}/files/apps/Splunk_TA_nix
        - makedirs: True
        - user: splunk
        - group: splunk
        - file_mode: 0755

File Managed

try several files if 1st one doesnt exist

monit_config:
    file.managed:
        - name: /etc/monit/monit.conf
        - source: 
            - salt://{{ slspath }}/files/configs/host/{{ grains.id }}.j2
            - salt://{{ slspath }}/files/configs/profile/{{ salt['pillar.get']('profile') }}.j2
            - salt://{{ slspath }}/files/configs/default.j2
        - template: jinja
        - makedirs: True
        - mode: 0600
        - user: monit
        - group: monit


USER AND GROUP MGMT

Set user's password to 123123
salt '*' shadow.set_password user02
'$6$EYk3o52W$DaSUIfHpYMBkSShFYXdODyrHbmQlCNKFghNl9FZzZshUn240GCOn5szQ3piyBMtt/x4m.'

Generate a password
salt 'target' shadow.gen_password myP@ssword
$6$nTul6WP1$EJ6THWEYKgOuGjqSEhnv8ZcYET6z/sDsSB.YBoyImRWEoDjguvcUahnY3UuNtNpECVhwxsjWI6ucvCc1

Additional ways to generate you own password:
python -c "import crypt, getpass, pwd; print crypt.crypt('yourpassword', '\$6\$SALTsalt\$')"
openssl passwd -1

Add User
salt target user.add Joe

Remove User
salt '*' user.delete Joe remove=True force=True

Show all users on a target
salt target user.list_users

Info on all users on a target
salt target user.getent

Info on specific user
salt target user.info Joe

Add User to Group
salt target user.chgroups Joe Administrator, LocalAdmin True
// or
salt target group.adduser admins Joe

Remove User from Group
salt target group.deluser admins Joe

Show users Groups
salt target user.list_groups Joe

Change Users Shell
salt '*' user.chshell user02 /bin/bash

get info on all groups
salt target group.getent

get info o a particular group
salt target group.info splunk

Delete group
salt target group.delete splunk

STATES
Highstate 
salt '*' state.highstate

Deploy specific state
salt '*' state.apply webserver
Run multiple state executions on same minion at once (by design Salt limits 1 state per minion at once)
salt target state.sls yourState concurrent=True


Requisites 

https://docs.saltstack.com/en/latest/ref/states/requisites.html


unless
vim:
  pkg.installed:
    - unless:
      - rpm -q vim-enhanced
      - ls /usr/bin/vim

onlyif
set_RTC:
  cmd.run:
    - name: "/usr/bin/timedatectl set-local-rtc 0"
    - onlyif: "/usr/bin/timedatectl  | grep "RTC in local TZ" | grep yes"


require
bar:
  pkg.installed:
    - require:
      - sls: foo

onchanges
extract_package:
  archive.extracted:
    - name: /usr/local/share/myapp
    - source: /usr/local/share/myapp.tar.xz
    - archive_format: tar
    - onchanges:
      - file: Deploy server package

watch
ntpd:
  service.running:
    - watch:
      - file: /etc/ntp.conf

prereq
prereq allows for actions to be taken based on the expected results of a state that has not yet been executed. The state containing the prereq requisite is defined as the pre-requiring state. The state specified in the prereq statement is defined as the pre-required state.

graceful-down:
  cmd.run:
    - name: service apache graceful
    - prereq:
      - file: site-code

use
The use requisite is used to inherit the arguments passed in another id declaration. This is useful when many files need to have the same defaults.
/etc/foo.conf:
  file.managed:
    - source: salt://foo.conf
    - template: jinja
    - mkdirs: True
    - user: apache
    - group: apache
    - mode: 755

/etc/bar.conf:
  file.managed:
    - source: salt://bar.conf
    - use:
      - file: /etc/foo.conf

require_in
vim:
  pkg.installed:
    - require_in:
      - file: /etc/vimrc

import YAML data into a state (same with import_json, import_text)

{% import_yaml 'formula/facl/files/configs/test.yaml' as myconfig %}

show_config:
  - test.show_notification:
    - text: {{ myconfig }}




SSH Management (Agentless)

edit the Roster file, include node's IP address and user to use (do this on Master)

vi /etc/salt/roster

# Sample salt-ssh config file
mrxcloud1:
   host: 104.131.102.230      # The IP addr or DNS hostname
   user: fred     # Remote executions will be executed as user fred
   passwd: foobarbaz      # The password to use for login, if omitted, keys are used
   sudo: True      # Whether to sudo to root, not enabled by default

Run command on node (salt-ssh -i)
salt-ssh -i mrxcloud1 cmd.run "uname -a"


To run SSH as another user (non-root)

copy /etc/salt directory to /home/user and change perms to user:group
should look like,
/home/user/salt/master
/home/user/salt/minion
/home/user/salt/minion.d
/home/user/salt/minion_uid
/home/user/salt/pki
/home/user/salt/var
/home/user/salt/roster

configure Roster file: /home/joe.shmo/salt/roster

edit /home/joe.shmo/salt/master

change the following paths
pidfile: /home/user/salt/var/run/salt-master.pid
log_file: /home/user/salt/var/log/salt/master
pki_dir: /home/user/salt/pki/master
cachedir: /home/user/salt/var/cache/salt/master

Add to the Roster file, the path to the user's private key
machine1:
  host: machine1.company.com
  user: joe.shmo
  sudo: True
  priv: /home/joe.shmo/.ssh/id_rsa

run command with -c option
salt-ssh -c ~/salt -i machine1 cmd.run "/sbin/service jira status"

apply a state file
salt-ssh '*' state.apply network


Salt Roster File - Ansible-syntax (configure Roster to have variables and Groups)


PILLAR
Sample Pillar structure to create system users pillar structure

Salt top file calls the Users state

/srv/salt/top.sls

base:
  '*':
    - common
    - users
Pillar top file tells what pillars to load for what nodes

/srv/pillar/top.sls

base:
  '*':
    - users



Users Pillar contains actual data for users
/srv/pillar/users.sls
users:
  spiderman:
    uid: 1280
    fullname: 'spider man'
    shell: /bin/bash
    ssh-keys:
      - ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAt1IFQP9xxx

  black.hood:
    uid: 1281
    fullname: black hood
    shell: '/bin/bash'
    ssh-keys:
      - ssh-rsa AADRN34zf12fdfd343434wAAAQEAwAAAQEA

  supergirl:
    uid: 1282
    fullname: super girl!
    shell: '/bin/bash'
    ssh-keys:
      - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNWRiUmFXjxrp4V
      - ssh-rsa ABCDEF134343434343dfdfdf343111dgfdfdfdf


Salt Users state parses array and creates users,

/srv/salt/users.sls
{% for user, args in pillar.get('users',{}).iteritems() %}

{{ user }}:

  group.present:
    - gid: {{ args['uid'] }}

  user.present:
    - fullname: {{ args['fullname'] }}
    - uid: {{ args['uid'] }}
    - gid: {{ args['uid'] }}
    - shell: {{ args['shell'] }}
    - home: /home/{{ user }}

{% endfor %}


Refresh pillars on all nodes
salt \* saltutil.refresh_pillar


Look at pillar data
salt \* pillar.items

get a Pillar value in a state file or Jinja file  (and pass a default value if no pillar is found)
{{ salt['pillar.get']('role:name', 'default') }}

get Pillar value by passing a variable
{% for rt in salt['pillar.get']('network:routes:{0}:networks'.format(interface)) -%}
get nested pillar value (use semi colon to get to specific key)
salt nycweb01 pillar.get users:joe
PORTS & NETWORK
Master - Agent ports: 4505 (master to agent), 4506 (agent to master)

Get IP of a Minion
salt target network.ip_addrs

Ping from Minion
salt target network.ping someHostname

get all active TCP connections on a minion
salt target network.active_tcp

get ARP table 
salt target network.arp

test port connectivity for certain port
salt target network.connect www.google.com 80

get hardware address for a MAC
salt target network.hw_addr eth0

get intet address for interface
salt target network.interface eth0

get all interfaces
salt target network.interfaces





JINJA
 For Loop
{% for usr in 'moe','larry','curly' %}
{{ usr }}:
  group:
    - present
  user:
    - present
    - gid_from_name: True
    - require:
      - group: {{ usr }}
{% endfor %}
If Conditional

{% if var == 2 %}
    Var is 2
{% elif var == 5 %}
    var is 5
{% else %}
    var is not 2
{% endif %}
 While loop

{% range number from 3 to 6 %}
      {{ number }}
      (...)
  {% endrange %}

Comparisons

{% if 'Watermelon' ends with 'n' %}
 It ends with N
{% endif %}

{% if varA == varB %}
{% if varA != varB %}


get shell command value from inside Jinja
{% set procs = salt['cmd.run']('ps aux') %}

disable tab space in a for loop
{% for server in servers %}
{{ server }}
{% endfor %}

results in: 
   server1
   server2

to disable this, add 

#jinja2: lstrip_blocks: True

to top of the template

Run a state only if a file doesnt exist,

{% if not salt['file.directory_exists']('/opt/q') %}

deploy_kdb:
    file.managed:
        - name: /opt/q.tar.gz
        - source: salt://repo/q.tar.gz

extract_kdb:
    archive.extracted:
        - name: /opt/
        - source: /opt/q.tar.gz
        - user: kdb
        - group: kdb

{% endif %}

Test for File

{% if not salt['file.file_exists']('/opt/file') %}

render parameter
{{ var_name }}

set a parameter
{% set fruit = 'apple' %}

Iterate dictionary (For Loop)
{% for name, app in applications.iteritems() %}
   {{ name }}
   {{ app['version'] }}
{% endfor %}

sort a list
{% for vm in vcenter['vm_list']|sort %}
    {{ vm }}
{% endfor %}

or by attribute or reverse
{% for vm in vcenter['vm_list']|sort(attribute='osname', reverse = True) %}
get total # of elements in list
{{ myList|length }}

If statement with AND & OR
{% if var is None and var2 == 'blah' % or val3 == 'shmaa' %}

convert variable uppercase / lowercase
{{ somevar | upper }}

set a default value if value doesnt exist
{{ somevar or 'default message here' }}

match by regex
{% if grains.id | regex_match('nyc(.*)', ignorecase=True) %}

will match any grain in regex pattern


Jinja Tricks

difference
{{ [1, 2, 3] | difference([2, 3, 4]) | join(', ') }}
>> 1

avg, min, max, is_list, 
{{ [1, 2, 3] | avg }}

generate random UID
{{ 'random' | uuid }}

date format
{{ 1457456400 | date_format }}
{{ 1457456400 | date_format('%d.%m.%Y %H:%M') }}
2017-03-08
08.03.2017 17:00
string to number
{{ '5' | to_num }}

run Salt execution module
{{ salt.cmd.run('whoami') }}
{{ salt.group.add('newgroup1') }}
regex match
{{ 'abcd' | regex_match('BC(.*)', ignorecase=True) }}

regex search
{{ 'muppet baby' | regex_search('pet(.*)', ignorecase=True) }}
>> baby

compare_lists, compare_dicts
{{ [1,2,3] | compare_lists([1,2,4]) }}
>> {'new': 4, 'old': 3}

list files in a directory
{{ '/etc/salt/' | list_files | join('\n') }}

escape Jinja syntax
{% raw %}
    some text that contains jinja {% characters that need to be escaped
{% endraw %}

iterate a dictionary
parent_dict = [{'A':'val1','B':'val2'}]
{% for item in parent_dict %}
  {% for key,val in item.items() %}
     {{ key }} {{ val }}


ETC

generate random password hash
python -c "import crypt; print(crypt.crypt('password', crypt.mksalt(crypt.METHOD_SHA512)))"