I am trying to using Salt state files to configure network devices. I will briefly describe my current setup:
I have pillar ntp.sls file saved as /etc/salt/pillar/ntp.sls and it looks like this:
ntp.servers:
- 11.1.1.1
- 2.2.2.2
Then I have Jinja template saved as /etc/salt/states/ntp/templates/ntp.jinja and looks like this:
{%- for server in servers %}
ntp {{ server }}
{%- endfor %}
Finally I have state file saved as /etc/salt/states/ntp/init.sls as this:
ntp_example:
netconfig.managed:
- template_name: salt://ntp/templates/ntp.jinja
- debug: true
- servers: {{ salt.pillar.get('ntp.servers') }}
I am getting the following error while trying to run the command: sudo salt sw state.sls ntp, where sw is the proxy minion, so here is the error:
sw:
Data failed to compile:
ID ntp.servers in SLS ntp is not a dictionary
Command to get data from pillar is working, command: sudo salt sw pillar.get ntp.servers
Output:
sw:
- 11.1.1.1
- 2.2.2.2
Any suggetions what could be wrong and how to fix it?
Thanks
I think you should declare in /etc/salt/pillar/ntp.sls something like:
ntp-servers:
- 11.1.1.1
- 2.2.2.2
and than load these values with:
- servers: {{ salt.pillar.get('ntp-servers') }}
The . is a directory separator in SaltStack.
I hope someone can help me 🙂.
I have stored ssh keys in vault as a secret. With saltstack config I am reading the secret with a pillar. Like this
docker:
{% set sshkey = salt['vault'].read_secret('super/secret/sshinfo', 'slackbot') %}
sshkey: |
{{ sshkey | indent(4) }}
And init.sls file looks like this:
{% set docker_config = pillar['docker'] %}
git-key:
file.managed:
- name: /root/.ssh/slack-bot
- contents: {{ docker_config.sshkey }}
- mode: 400
- user: root
Which works great and I get the ssh key by typing salt-call pillar.items
However, when I run salt-call state.apply I get the following error message
local:
Data failed to compile:
----------
Rendering SLS 'base:docker' failed: could not find expected ':'
Which is driving me nuts!
I read Mulitline string on Github that in order to write mulitline with salt you need to have | indent(4) (as an example).
Does someone have any ideas? Would be much appreciated 🙂
So, after a while I tried to change indent(4) to indent(5), but it didn't gave up the error. So I also tried to change content to be
- contents: |
{{ docker_config.sshkey | indent(5) }}
Which fixed the problem and is now working.
Hope anyone who has similar problems find this useful.
Cheers!
I have a Salt state that I only want to be executed when the target operating system is not RedHat; if the OS is RedHat, then I'd like to return just an error message.
In order to do that, I've been adding this at the top of the .sls file:
{% if grains['os'] == RedHat %}
RedHat not supported
{% endif %}
The above works because the message I've inserted is not a valid entry and then it failed to compile when the target operating system is RedHat, but I feel this is just a hack; I'd like to know if there's a more elegant solution to this problem, any ideas?
With the code below RedHat servers will only run the test.succeed_without_changes state.
The state: test.succeed_without_changes will ensure you that the minion has executed this job and that it has no changes which is useful in your logging.
Only servers with another OS are executing the real states in you statefile.
Code:
{% if grains['os'] == RedHat %}
RedHat-server-logging-state:
test.succeed_without_changes:
- name: RedHat OS detected
{% else %}
Execution-state-1:
test.succeed_with_changes:
- name: State 1 executed on non RedHat server
Execution-state-2:
test.succeed_with_changes:
- name: State 2 executed on non RedHat server
{% endif %}
I successfully configured salt master (with reactor) and minion (with beacon). On minion I have nginx and beacon configuration that watch the process:
beacons:
service:
nginx:
onchangeonly: True
uncleanshutdown: /run/nginx.pid
Event is send and reactor get that event. I try to restart nginx:
{% set nginx_running = data['data']['nginx']['running'] %}
{% if not nginx_running %}
restart_nginx:
local.cmd.run:
- tgt: {{ data['data']['id'] }}
- arg:
- 'pkill nginx'
- 'systemctl restart nginx'
{% endif %}
Problem:
is that the right way to do it?
I want to send pkill because if nginx is killed - only root process, worker process are still working
I got information "ERROR: Specified cmd 'pkill nginx' either not absolute or does not exist
This may seem at first to be pretty simple. But I can tell you I've been wracking my brains for a couple days on this. I've read a lot of docs, sat on IRC with folks, and spoken to colleagues and at this point I don't have an answer I really think holds up.
I've looked into a few possible approaches
reactor
orchestration runner
I don't like these two because of the top down execution necessity... they seem tailored to orchestrating multiple node states, not workflows in a single node.
custom states
This is kind of something I would REALLY like to avoid as this is a repeated workflow, and I don't want to build customizations like this. There's too much room for non legibility if I go down this path with my team mates.
requires / watches
These don't have a concept ( that I am aware of ) of applying a state repeatedly, or in a logical order / workflow.
And a few others I won't mention.
Without further discussion, here's my dilemma.
Goals:
Jenkins Master gets Deployed
We can unit.test the deployment as it proceeds
We only restart tomcat when necessary
We can update plugins on a per package basis
A big emphasis on good clean intuitively clear salt configs
Jenkins deployment is pretty straight forward. We drop in the packages, and the configs, and we're set.
Unit testing is harder. As an example I've got this state file.
actions/version.sls:
# Hit's the jenkins CLI interface to check for version info
# This can be used to verify that jenkins is active and the version we want
# Import some info
{%- from 'jenkins/init.sls' import jenkins_home with context %}
# Install plugins in jenkins_plugins list
jenkins_version:
cmd.run:
- name: java -jar jenkins-cli.jar -s "http://127.0.0.1:8080" version
- cwd: /var/lib/tomcat/webapps/ROOT/WEB-INF/
- user: jenkins
actions.version basically verifies that jenkins is running and queryable. we want to be sure of this during the build at several points.
example... tomcat takes time to spin up. we had to add a delay to that restart operation. If you check out start.sls below you can see that operation occurring. Note the bug open on init_delay: .
actions/start.sls:
# Starts the tomcat service
tomcat_start:
service.running:
- name: tomcat
- enable: True
- full_restart: True
# Not functional atm see --> https://github.com/saltstack/salt/issues/20631
# - init_delay: 120
# initiate a 120 second delay after any service start to let tomcat come up.
tomcat_wait:
module.run:
- name: test.sleep
- length: 60
include:
- jenkins.actions.version
Now we have this restart capability by doing an actions.stop and an actions.start. We have this actions.version state that we can use to verify that the system is ready to proceed with jenkins specific state workflows.
I want to do something kinda like this...
Install Jenkins --> Grab yaml of plugins --> install plugins that need it
Pretty straight forward.
Except, to loop through the yaml of plugins I am using Jinja.
And now I have no way to call and be sure that the start.sls and version.sls states can be repeatedly applied.
I am looking for, a good way to do that.
This would be something akin to a jenkins.sls
{% set repo_username = "foo" -%}
{% set repo_password = "bar" -%}
include:
- jenkins.actions.version
- jenkins.actions.stop
- jenkins.actions.start
# Install Jenkins
jenkins:
pkg:
- installed
# Import Jenkins Plugins as List, and Working Path
{%- from 'jenkins/init.sls' import jenkins_home with context %}
{%- import_yaml "jenkins/plugins.sls" as jenkins_plugins %}
{%- import_yaml "jenkins/custom-plugins.sls" as custom_plugins %}
# Grab updated package list
jenkins-contact-update-server:
cmd.run:
- name: curl -L http://updates.jenkins-ci.org/update-center.json | sed '1d;$d' > {{ jenkins_home }}/updates/default.json
- unless: test -d {{ jenkins_home }}/updates/default.json
- require:
- pkg: jenkins
- service: tomcat
# Install plugins in jenkins_plugins list
{% for plugin in jenkins_plugins %}
jenkins-plugin-{{ plugin }}:
cmd.run:
- name: java -jar jenkins-cli.jar -s "http://127.0.0.1:8080" install-plugin "{{ plugin }}"
- unless: java -jar jenkins-cli.jar -s "http://127.0.0.1:8080" list-plugins | grep "{{ plugin }}"
- cwd: /var/lib/tomcat/webapps/ROOT/WEB-INF/
- user: jenkins
- require:
- pkg: jenkins
- service: tomcat
Here is where I am stuck. require won't do this. and lists
of actions don't seem to schedule linearly in salt. I need to
be able to just verify that jenkins is up and ready. I need
to be able to restart tomcat after a single plugin in the
iteration is added. I need to be able to do this to satisfy
dependencies in the plugin order.
- sls: jenkins.actions.version
- sls: jenkins.actions.stop
- sls: jenkins.actions.start
# This can't work for several reasons
# - watch_in:
# - sls: jenkins-safe-restart
{% endfor %}
# Install custom plugins in the custom_plugins list
{% for cust_plugin,cust_plugin_url in custom_plugins.iteritems() %}
# manually downloading the plugin, because jenkins-cli.jar doesn't seem to work direct to artifactory URLs.
download-plugin-{{ cust_plugin }}:
cmd.run:
- name: curl -o {{ cust_plugin }}.jpi -O "https://{{ repo_username }}:{{ repo_password }}#{{ cust_plugin_url }}"
- unless: java -jar jenkins-cli.jar -s "http://127.0.0.1:8080" list-plugins | grep "{{ cust_plugin }}"
- cwd: /tmp
- user: jenkins
- require:
- pkg: jenkins
- service: tomcat
# installing the plugin ( REQUIRES TOMCAT RESTART AFTER )
custom-plugin-{{ cust_plugin }}:
cmd.run:
- name: java -jar jenkins-cli.jar -s "http://127.0.0.1:8080" install-plugin /tmp/{{ cust_plugin }}.jpi
- unless: java -jar jenkins-cli.jar -s "http://127.0.0.1:8080" list-plugins | grep "{{ cust_plugin }}"
- cwd: /var/lib/tomcat/webapps/ROOT/WEB-INF/
- user: jenkins
- require:
- pkg: jenkins
- service: tomcat
{% endfor %}
You won't be able to achieve this without using reactors, beacons and especially not without writing your own python execution modules.
Jenkins Master gets Deployed
Write a jenkins execution module in python with a function install(...):. In that function you would manage any dependencies by either calling existing execution modules or by writing them yourself.
We can unit.test the deployment as it proceeds
Inside the install function of the jenkins module you would fire specific events depending on the results of the install.
if not _run_deployment_phase(...):
__salt__['event.send']('jenkins/install/error', {
'finished': False,
'message': "Something failed during the deployment!",
})
You would map that event to reactor sls files and handle it.
We only restart tomcat when necessary
Write a tomcat module. Add an _is_up(...) function where you would check if tomcat is up by parsing the tomcat logs for the result. Call the function inside a state module and add a mod_watch function.
def mod_watch():
# required dict to return
return_dict = {
"name": "Tomcat install",
"changes": {},
"result": False,
"comment": "",
}
if __salt__["tomcat._is_up"]():
return_dict["result"] = True
return_dict["comment"] = "Tomcat is up."
if __opts__["test"]:
return_dict["result"] = None
return_dict["comment"] = "comment here about what will change"
return return_dict
# execute changes now
return return_dict
Use your state module inside a state file.
install tomcat:
tomcat.install:
- name: ...
- user: ...
...
wait until tomcat is up:
cmd.run:
- name: ...
- watch:
- tomcat: install tomcat
We can update plugins on a per package basis
Add a function to your jenkins execution module named install_plugin. View pkg.install code to replicate interface.
A big emphasis on good clean intuitively clear salt configs
Write python execution modules for easy and maintainable configuration logic. Use that execution module inside your own state modules. Inside state files call your own state modules and supply individual configuration with any state renderer you like.
States only execute once, by design. If you need the same action to occur multiple times, you need multiple states. Also, includes are only included a single time.
Rather than all of this include/require stuff you're doing, you should just put all of the code into a single sls file, and generate states through jinja iteration.
If what you're trying to do is add a bunch of plugins, add config files, then at the end do restarts, then you should really just execute everything in order, don't use require, and use listen or listen_in, rather than watch or watch_in.
listen/listen_in cause triggered actions to happen at the end of a state run. They are similar to the concept of handlers in Ansible.
This is a pretty old question, but If you change your Jenkins/tomcat start/stop procedure to be a standard init/systemd/windows service (as all well behaved services should be), you could have a service.running for the Jenkins service and add this to each of your custom-plugin-{{ cust_plugin }} states.
require_in:
- svc: jenkins
watch_in:
- svc: jenkins
You could continue to use the cmd.run module with onchanges. You'd have to add onchanges_in: to each of the custom-plugin-{{ cust_plugin }} states, but you need to have at least one item in the on changes list or the command will fire every time the state runs.
If you use require you cause salt to re-order your states. If you want your states to run in order, just write them in the order you want them to run in.
Watch/watch_in will also re-order your states. If you use listen/listen_in instead, it'll queue the triggered actions to run in the order they were triggered at the end of the state run.
See:
http://ryandlane.com/blog/2014/07/14/truly-ordered-execution-using-saltstack/
http://ryandlane.com/blog/2015/01/06/truly-ordered-execution-using-saltstack-part-2/