I have set up pillar data for websites, e.g. web_root, virtualhost and mysql:
web_root:
config_file: salt://some/path.conf
key: some data
directory_name: directoryA
virtualhost:
config_file: salt://some/path.conf
name: websiteA
mysql:
database:
- websiteA_db
These map to states for web_root, virtualhost and mysql (using formula).
I'd like to use have a minion run these states multiple times, using separate pillar data, e.g.
include:
- apache
- php
{% for instance in [instanceA, instanceB] -%}
{% load pillar data /pillar/{{ instance }} -%}
- web_root #run the state
- virtualhost #run the state
- mysql #run the state
{% endfor -%}
Is this possible? I know I can set up pillar data like so:
web_root:
instanceA:
config_file: salt://some/pathA.conf
key: some data
directory_name: directoryA
instanceB:
config_file: salt://some/pathB.conf
key: some data
directory_name: directoryB
virtualhost:
instanceA:
config_file: salt://some/pathA.conf
name: websiteA
instanceB:
config_file: salt://some/pathB.conf
name: websiteB
mysql:
database:
- websiteA_db
- websiteB_db
But it means I have to add loops to each state file, making it less readable as well as use different syntax, e.g. for mysql which is a formula with set syntax requirements.
You'll want to do something like this:
Pillar Data
web_root:
instances:
A:
- name: A
- key: key_A_data
B:
- name: B
- key: key_B_data
State file
{% set names = salt['pillar.get']('web_root:instances') %}
apache:
pkg.installed: []
{% for name in names %}
instance{{ name }}:
- config_file: salt://some/path{{ name }}.conf
- key: {{ key }}
- directory_name: directory{{ name }}
{% endfor %}
Then just do the same thing for the rest of your objects. This way you don't have to change your state file when you add objects to the pillar.
Related
I'm trying to access listed grain values from state file, need help on this.
State file is as below
{% set list = grains['selinux'] %}
echo {{ list }}:
cmd.run
But when i run the state file got the error.
# salt '*' state.sls list_grains
client1:
Data failed to compile:
----------
Rendering SLS 'base:list_grains' failed: mapping values are not allowed in this context
ERROR: Minions returned with non-zero exit code
[root#server ~]# vim /srv/salt/list_grains.sls
grain values accessed are as below
# salt '*' grains.item selinux
client1:
----------
selinux:
----------
enabled:
True
enforced:
Permissive
The selinux grain is a dictionary/map like:
selinux:
enabled: True
enforced: Permissive
So in a state ID you cannot have dictionary/map. You can pick the required dictionary key like list.enabled or list.enforced.
For example, the below state ID will output Permissive:
{% set list = grains['selinux'] %}
echo {{ list.enforced }}:
cmd.run
If you want to get the complete dict as output, you can use a module like test.echo:
{% set list = grains['selinux'] %}
show-selinux-grains:
module.run:
- name: test.echo
- text: "{{ list }}"
How do I use requisites in reactor files?
I only want to run the second state if the first completes successfully.
{% if ('id' in data) and ('act' in data) and ('pub' in data) and
((data['act'] == 'pend') and (data['pub'] != '')) %}
check_minion_domain_joined:
local.cmd.run:
- tgt: 'MINIONNAME'
- arg:
- powershell.exe -ExecutionPolicy Bypass -File "C:/salt/auth-minion.ps1" {{ data['id'] }}
accept_key_domain_joined:
wheel.key.accept:
- match: {{ data['id'] }}
- require:
- cmd: check_minion_domain_joined
{% endif %}
I can see that the first state is being run by the master via the output of salt-run state.event, but "accept_key_domain_joined" never runs.
As per the official documentation, reactor SLS files do not support requisites.
Reactor SLS files, by design, do not support requisites, ordering, onlyif/unless conditionals and most other powerful constructs from Salt's State system.
The better option is to trigger orchestration from the reactor. So instead of having the two actions performed from reactor, just call for an orchestration run:
Example reactor/file.sls:
create-file-with-content:
runner.state.orch:
- args:
- mods: orch.create_file
- pillar:
min_id: {{ data['id'] }}
min_act: {{ data['act'] }}
Then in orch/create_file.sls, we can use the pillar data and accomplish the tasks.
Example:
{% set minion_id = salt.pillar.get('min_id') %}
{% set minion_act = salt.pillar.get('min_act') %}
touch-a-tmp-file:
salt.function:
- name: file.touch
- tgt: {{ minion_id }}
- arg:
- /tmp/dummy.file
update-tmp-file:
salt.function:
- name: file.append
tgt: {{ minion_id }}
arg:
- /tmp/dummy.file
- minion_act is {{ minion_act }}
- require:
- salt: touch-a-tmp-file
There is some more information on troubleshooting pillar information in reactor-orchestration states here.
I am trying to write an if statement based on a nested grain. I have tried this statement in multiple different ways:
System Services Needed:
module.run:
- name: service.systemctl_reload
- onchanges:
- file: /lib/systemd/system/salt-minion.service
{% if salt['grains.get']('Project:DeviceTypeID') == '2' %}
- file: /etc/rc.local
- file: /opt/interfaces_init.sh
{% endif %}
Returns:
Rendering SLS 'Development:System' failed: Jinja variable 'dict object' has no attribute 'Project:DeviceTypeID'
System Services Needed:
module.run:
- name: service.systemctl_reload
- onchanges:
- file: /lib/systemd/system/salt-minion.service
{% if grains['Project']['DeviceTypeID'] == '2' %}
- file: /etc/rc.local
- file: /opt/interfaces_init.sh
{% endif %}
System Services Needed:
module.run:
- name: service.systemctl_reload
- onchanges:
- file: /lib/systemd/system/salt-minion.service
{% if grains['Project:DeviceTypeID'] == '2' %}
- file: /etc/rc.local
- file: /opt/interfaces_init.sh
{% endif %}
As you can tell from the example their are multiple device type IDs. In this example DeviceTypeID = 2 I need to worry about rc.local and a shell script. I can not seem to get this work for the life of me. I know the grain exists as I can run the following:
sudo salt 'Dev-Box' grains.get Project
and I will get:
Dev-Box:
DeviceTypeID:
1
IsActive:
True
SoftwareEnvironmentName:
Production
SoftwareVersion:
Foo
This is either a bug or I am missing something (significantly more likely I am missing something). Any help would be much appreciated.
Edit 1:
Added ['grains.get']('Project:DeviceTypeID') example
in salt grains.get return a dictionary in the following format:
{'minion-id': value}
I believe if you change your code into something like bellow, it should works.
{% if salt['grains.get']('Project:DeviceTypeID')[minion-id] == '2' %}
If you can't do:
salt 'Dev-Box' grains.get 'Project:DeviceTypeID'
Then you don't actually have the proper grain set.
Try the following:
salt 'Dev-Box' grains.setval Project '{"DeviceTypeID": 2, "IsActive": True, "SoftwareEnvironmentName": "Production", "SoftwareVersion": "Foo"}'
Then the following state:
Do the {{ salt['grains.get']('Project:DeviceTypeID') }} things:
test.succeed_with_changes:
- some: thing
You should get:
ID: Do the 2 things
Function: test.succeed_with_changes
Result: True
Comment: Success!
Started: 17:10:42.739240
Duration: 0.491 ms
Changes:
----------
testing:
----------
new:
Something pretended to change
old:
Unchanged
Given what you wrote elsewhere
salt Dev-Box grains.setval BETTI "{'DeviceTypeID': 2, 'IsActive': True SoftwareEnvironmentName': 'Production', 'SoftwareVersion': 'Foo'}"
Your problem is that you have ' and " confused.
Wrapping the value with " makes it a string. Wrapping it with ' and providing valid JSON makes it a dictionary value.
I'm trying to register my minion in a central hosts file and then push this hosts file to all my connected minions.
Here is what I have in mind:
Minion send an event 'register' with its ip and hostname to be registered on the master's central hosts file
Master listening to the event 'register' and react with the reactor /srv/reactor/register.sls
Reactor calls the state /srv/salt/register.sls on the minion installed on the master's host to modify the central file and send an event 'hosts_modified' after the modification is complete
Master listening to the event 'hosts_modified' and react with the reactor /srv/reactor/deploy_hosts.sls which applies the state /srv/salt/hosts.sls on all connected minions to push the new modified hosts file
The first 3 steps are working fine but the master is not reacting to the last event 'hosts_modified'.
Command to initiate the register on the minion:
salt-call event.send register minion_host=somehostname minion_ip=1.1.1.1
Master reactor config (/etc/salt/master.d/reactor.conf):
reactor:
- salt/beacon/*/inotify//etc/hosts:
- /srv/reactor/revert.sls
- 'deployment':
- /srv/reactor/deployment.sls
- 'register':
- /srv/reactor/register.sls
- 'hosts_modified':
- /srv/reactor/deploy_hosts.sls
/srv/reactor/register.sls
{% set forwarded_data = data.data %}
test:
local.state.sls:
- tgt: 'master'
- args:
- mods: register
- pillar:
forwarded_data: {{ forwarded_data | json() }}
/srv/salt/register.sls
{% set data = salt.pillar.get('forwarded_data') %}
add_host:
cmd.run:
- name: /srv/scripts/hosts-manage.sh {{ data.minion_ip }} {{ data.minion_host }}
event_host_modified:
event.send:
- name: hosts_modified
- require:
- cmd: add_host
/srv/reactor/deploy_hosts.sls
deploy_hosts:
local.state.sls:
- tgt: '*'
- name: hosts
/srv/salt/hosts.sls
# Hosts file management
/etc/hosts:
file.managed:
- source: salt://repo/conf/hosts
Am I doing it wrong?
Is it not possible to handle events sent while applying states?
EDIT
I finally did it with an Orchestrate Runner.
/srv/reactor/register.sls:
{% set forwarded_data = data.data %}
register:
runner.state.orch:
- args:
- mods: orch.register
- pillar:
forwarded_data: {{ forwarded_data | json() }}
/srv/salt/orch/register.sls:
{% set data = salt.pillar.get('forwarded_data') %}
add_host:
cmd.run:
- name: /srv/scripts/hosts-manage.sh {{ data.minion_ip }} {{ data.minion_host }}
- stateful: True
refresh hosts on minions:
salt.state:
- tgt: '*'
- sls: hosts
- watch:
- cmd: add_host
/srv/salt/hosts.sls:
# Hosts file management
/etc/hosts:
file.managed:
- source: salt://repo/conf/hosts
It seems to be working this way.
I finally did it with an Orchestrate Runner.
/srv/reactor/register.sls:
{% set forwarded_data = data.data %}
register:
runner.state.orch:
- args:
- mods: orch.register
- pillar:
forwarded_data: {{ forwarded_data | json() }}
/srv/salt/orch/register.sls:
{% set data = salt.pillar.get('forwarded_data') %}
add_host:
cmd.run:
- name: /srv/scripts/hosts-manage.sh {{ data.minion_ip }} {{ data.minion_host }}
- stateful: True
refresh hosts on minions:
salt.state:
- tgt: '*'
- sls: hosts
- watch:
- cmd: add_host
/srv/salt/hosts.sls:
# Hosts file management
/etc/hosts:
file.managed:
- source: salt://repo/conf/hosts
It seems to be working this way.
I am trying to pull results from a sqlite3 database (set up as an external salt pillar) and use jinja templating to set grains data.
Here is the relevant section of my salt master file:
sqlite3:
database: '/var/lib/salt/stations.db'
timeout: 5.0
ext_pillar:
- sqlite3:
fromdb:
query: 'SELECT * FROM table;'
And here is the relevant part of the init.sls file I am using to create the grains file:
{% set station_id = salt['grains.filter_by']({
{% for row in query_result %}
{% hostname = station_id %}
}, default="UNKNOWN", grain="host") %}
I confirmed that the external pillar produces results by running
salt '*' sqlite3.fetch /var/lib/salt/stations.db 'SELECT * FROM test;'
But I can't figure out how to get results into the jinja file.
I want something like
'SELECT * FROM table WHERE hostname=station_id LIMIT 1;'
and use the result to set the grain environmental variable called 'hostname'.
But am not sure how to get there from here.
Any help is greatly appreciated.
Thanks to the good folks in Saltstack IRC this problem is solved.
Master:
- sqlite3:
station_map:
query: 'SELECT hostname, id
FROM stations
WHERE hostname like ?'
init.sls:
{% set station_id = salt['grains.filter_by']({
{% for row in station_map %}
{{ hostname }} : {{ station_id }}
}, default="UNKNOWN", grain="host") %}