How to execute multiple scripts with Salt cmd.script? - salt-stack

I'm trying to apply salt state that consist of multiple scripts. But some reason only script4 is running.
Here's my state file.
#!jinja|yaml|gpg
Distro_Specific_Scripts:
cmd.script:
- name: Run Scripts.sh
{% if grains['os_family'] == 'RedHat' %}
- source: salt://scripts/files/scripts/script_for_redhat.sh
{% elif grains['os_family'] == 'Debian' %}
- source: salt://scripts/files/scripts/script_for_ubuntu.sh
{% endif %}
- source: salt://scripts/files/scripts/script2.sh
- source: salt://scripts/files/scripts/script3.sh
- source: salt://scripts/files/scripts/script4.sh
Would you share your experience about running multiple scripts? or Would you tell me where i went wrong.

There are multiple ways to write this.
OPTION 1:
This will be the simplest method. If you have scripts corresponding to the grains['os_family'], i.e.
script_for_debian.sh for Debian
script_for_ubuntu.sh for Ubuntu
script_for_redhat.sh for RedHat
run_my_scripts:
cmd.script:
- names:
- salt://scripts/files/scripts/script_for_{{ grains['os_family'] | lower }}.sh
- salt://scripts/files/scripts/script2.sh
- salt://scripts/files/scripts/script3.sh
- salt://scripts/files/scripts/script4.sh
OPTION 2:
If not, then we can set the script name conditionally and outside the state declaration:
{% set os_specific_script = salt['grains.filter_by']({
'Debian': 'script_for_ubuntu.sh',
'RedHat': 'script_for_redhat.sh'
}) %}
run_my_scripts:
cmd.script:
- names:
- salt://scripts/files/scripts/{{ os_specific_script }}
- salt://scripts/files/scripts/script2.sh
- salt://scripts/files/scripts/script3.sh
- salt://scripts/files/scripts/script4.sh
If you have control over the name of scripts, OPTION 1 will be a better choice.

Actually I found a way to achieve my goal. But if there's a smartest way to do it any help would be appreciated :)
#!jinja|yaml|gpg
Distro_Specific_Scripts:
cmd.script:
- name: Distro Specific Scripts
{% if grains['os_family'] == 'RedHat' %}
- source: salt://scripts/files/scripts/script_for_redhat.sh
{% elif grains['os_family'] == 'Debian' %}
- source: salt://scripts/files/scripts/script_for_debian.sh
{% endif %}
script2:
cmd.script:
- name: script2.sh
- source: salt://scripts/files/scripts/script2.sh
script3:
cmd.script:
- name: script3
- source: salt://scripts/files/scripts/script2.sh
script4:
cmd.script:
- name: script4.sh
- source: salt://scripts/files/scripts/script2.sh

Related

Cannot write data to db in Flask

I am working on a small reading tracking app in Flask. I cannot seem to write data from one of my routes, below:
#app.route('/add_book', methods=['GET', 'POST'])
#login_required
def add_book():
form=BookForm()
if request.method=='POST':
if form.validate_on_submit():
book=Book(
title=form.title.data,
author=form.author.data,
# category=form.category.data,
# added_on=form.added_on.data,
# done=form.done.data,
user_id=current_user
)
db.session.add(book)
db.session.commit()
flash('Book added')
return redirect(url_for('books'))
else:
flash('ERROR. The book not added.')
return render_template('add_book.html', form=form)
This is the corresponding HTML:
{% extends "layout.html" %}
{% block content %}
{% if form %}
<form action="{{ url_for('add_book') }}" method="post">
{{ form.hidden_tag() }}
{{ form.title.label }}<br>
{{ form.title() }}<br>
{{ form.author.label }}<br>
{{ form.author(cols=32, rows=4) }}<br>
<!-- {{ form.category.label }}<br>
{{ form.category() }}<br> -->
{{ form.submit() }}
</form>
{% endif %}
{% endblock %}
When the page renders, the label and forms for the book and author appear, however on clicking Submit, the data does not get saved.
The code section is similar to that for registering a user and I am stuck on what to do because I cannot see any errors. I am using SQLite as a database.
Here is the book model:
class Book(db.Model):
__tablename__='books'
id=db.Column(db.Integer, primary_key=True)
title=db.Column(db.String(100))
author=db.Column(db.String(50))
# category=db.Column(db.String(50))
added_on=db.Column(db.DateTime, index=True, default=datetime.utcnow)
done=db.Column(db.Boolean, default=False)
user_id=db.Column(db.Integer, db.ForeignKey('users.id'))
def __init__(self, title, author, added_on, done, user_id):
self.title=title
self.author=author
self.added_on=added_on
self.done=done
self.user_id=user_id
def __repr__(self):
# return '<Book: Title - {0}, Author - {1}, Category - {2}>'.format(self.title, self.author, self.category)
return '<Book: Title - {0}, Author - {1}>'.format(self.title, self.author)
The current_user is a user proxy giving access to all user attributes or use something like this How to track the current user in flask-login?
a quick solution would be to change this line
user_id = current_user
into this
user_id = current_user.id
Update:
ok, I got it working. You need the following fixes, each of them leads to trouble with form validation and or committing to database:
- use current_user.id in your book object as I said earlier.
- removed the init method in books model. I'm not sure what the added value is at the moment, I'm getting error messages about fields added on and done which are not on the form. I haven't taken the time to look into it further.
- just go for if request.method=='POST' as you don't need both. The form will be checked for validation anyways.
tip: don't forget to create a requirements file (pip freeze --> requirements.txt), that makes it a lot easier to reinstall in a new virtual environment.

how to set a random key once using saltstack

I install a configuration for rails that looks like this (simplified):
production:
secret_key_base: 800afb35d5086b2c60ebd35c01b2bd2b522c2492
db_username: ...
db_password: ...
and so it gets installed from a template file
{{ role }}:
secret_key_base: {{ secret_key }}
db_username: {{ db_user }}
db_password: {{ db_pass }}
And the role and db user/pass get pulled from pillar and installed in that file. The secret_key it would make sense to generate randomly, e.g., {{ salt['random.get_str'](length=80) }}. But I want to generate it once, not every time the template is rendered. (Changing the secret key invalidates cookies, not something to do on each salt run.)
The only solution I've found is two-phase: I have a template.in file
{{ role }}:
secret_key_base: ||secret_key_base||
db_username: {{ db_user }}
db_password: {{ db_pass }}
that I sed into my template file on any given minion:
/srv/salt/rails/secrets.yml:
cmd.run:
# Fill in the secret key base (used for cookies). We can't use
# jinja2 for this, since jinja would complain about the other
# variables that it doesn't know how to replace. We want our
# output to be a jinja template.
- name: |
cat /srv/salt/rails/secrets.yml.in | \
sed -e 's/||secret_key_base||/{{ salt['random.get_str'](length=80) }}/;' | \
cat > /srv/salt/rails/secrets.yml
chmod 400 /srv/salt/rails/secrets.yml
- creates: /srv/salt/rails/secrets.yml
- runas: root
/var/railroad/{{host_role}}/shared/config/secrets.yml:
file.managed:
- source: salt://rails/secrets.yml
- mode: 400
- user: railroad-{{host_role}}
- group: railroad-{{host_role}}
- template: jinja
- defaults:
role: host_role
db_username: m_u
db_password: m_p
This works but has the disadvantage that a change to secrets.yml.in would not be propagated on to secrets.yml. (Suppose we add another key to the secrets file.) It also feels clunkier than necessary.
Is there a better way?
A better way
As noted in the comments, a better way is to just generate the secret by hand (after all, it's only done at host setup) and store it in pillar, where we anyway have to say a few things about each host.
Here is what the working code eventually looked like, unsimplified for those who might want to see something more complex. Much of the complexity is my host_credentials pillar data, which tries to characterise all that we need to know about each host.
{% set fqdn = grains.get('fqdn', 'unknown-host-fqdn') %}
{% set host_role = pillar['host_credentials']
[grains.get('fqdn')]
['role'] %}
{% set examplecom_web_app = pillar['host_credentials']
[grains.get('fqdn')]
['examplecom-web-app'] %}
{% set mysql_server_host = examplecom_web_app.mysql.host %}
{% set mysql_server_database = examplecom_web_app.mysql.database %}
{% set mysql_server_role = examplecom_web_app.mysql.role %}
{% set mysql_server_spec = pillar['host_credentials']
[mysql_server_host]
['mysql'] %}
{% set mongodb_server_host = examplecom_web_app.mongodb.host %}
{% set mongodb_server_spec = pillar['host_credentials']
[mongodb_server_host]
['mongodb'] %}
/var/examplecom/railroad/{{host_role}}/shared/config/secrets.yml:
file.managed:
- source: salt://rails/secrets.yml
- mode: 400
- user: railroad-{{host_role}}
- group: railroad-{{host_role}}
- template: jinja
- defaults:
role: {{ host_role }}
secret_key_base: {{ examplecom_web_app.secret_key_base }}
mysql_hostname: {{ mysql_server_host }}
mysql_username: {{ mysql_server_spec[mysql_server_database]
[mysql_server_role]
['username'] }}
mysql_password: {{ mysql_server_spec[mysql_server_database]
[mysql_server_role]
['password'] }}
mongodb_hostname: {{ mongodb_server_host }}
mongodb_username: {{ mongodb_server_spec.username }}
mongodb_password: {{ mongodb_server_spec.password }}
As an aside, I was happy to discover that jinja2 is white-space agnostic, which helps enormously with readability on such lookups.
I recommend putting the secret in a pillar and generate the value once (manually) on the master. That way you can avoid doing the stateful on-the-fly-magic in your SLS file.
jma updated his question to include an example solution.
Do I understand you correctly:
- this is salt-masterless run?
- you generate some template file on masterless node (to have some static parts generated once)
- you apply file.managed state to previously generated file?
Assuming my understandings:
First of all it will be propagated to another states if you set proper watch/require statements but in your case this would be harder to achieve as you've used cmd.run for template parsing (you must add stateful argument to express that there is some potential state changes underlying)
Remarks:
- did you see file.blockreplace? it seems that you can use this to replace first cmd.run and get file change detection "for free"
- as for one time password generation, there is nice trick to do this simply by using grains.get_or_set_hash
As the generated password is not changing for given minion (you master-less node in your case) the file.blockreplace won't report any changes unless you add changes to the template
Having said this, I think we can go further and your state actually can be as simple as this (template changes will be propagated always, key will be generated once):
/var/railroad/{{host_role}}/shared/config/secrets.yml:
file.managed:
- source: salt://rails/secrets.yml
- mode: 400
- user: railroad-{{host_role}}
- group: railroad-{{host_role}}
- template: jinja
- defaults:
role: host_role
db_username: m_u
db_password: {{ salt['grains.get_or_set_hash']('some:place:secret_key_base') }}

Passing variables with include in salt-stack

I have several states that are almost the same. All of them deploy project, create virtualenv and configure supervisor. Difference is only in repo, project name and some additional actions.
A lot of code is duplicated. Is it possible to put the same parts into file and include it with additional variables?
In Ansible it can be done this way:
tasks:
- include: wordpress.yml
vars:
wp_user: timmy
ssh_keys:
- keys/one.txt
- keys/two.txt
This question looks similar to this one
If I understood your question correctly - I believe the best way to achieve what you want is to use Salt Macros.
With this most of your state will go to macros with placeholders being parameters like:
# lib.sls
{% macro create_user(user, password) %}
{{user}}:
user.present:
- home: /home/{{user}}
- password: {{password}}
{% endmacro %}
Then your state will look like:
# john.sls
{% from 'lib.sls' import create_user with context %}
{{ create_user('john', '<password hash>') }}
and:
# jane.sls
{% from 'lib.sls' import create_user with context %}
{{ create_user('john', '<password hash>') }}
As I found out there is another way to archive it without messing with templates (more Ansible way). Create an abstract state "python-project". Create concrete roles then and provide different pillars to these roles:
salt/top.sls:
base:
'roles:python-project-1':
- match: grain
- python-project
'roles:python-project-2':
- match: grain
- python-project
pillar/top.sls:
base:
'roles:python-project-1':
- match: grain
- common-pillars
- pillars-for-the-first
'roles:python-project-2':
- match: grain
- common-pillars
- pillars-for-the-second
Structure:
pillar/top.sls
pillar/common-pillars/init.sls
pillar/pillars-for-the-first/init.sls
pillar/pillars-for-the-second/init.sls
salt/top.sls
salt/python-project/init.sls
You can use Jinja imports to do that:
top.sls
{% set user = 'john' %}
{% include 'config.sls' %}
config.sls
file.managed:
- name: '/Users/{{ user }}/.config
- user: {{ user }}

need help in salt-stack pillar data

I want to read the data from one pillar file into another. for example:
top.sls
- one
- two
one.sls
env: test
two.sls
dir: {{salt'pillar.get'}}_value
But it is not getting the value of pillar 'env' from one.sls in two.sls. Any idea how we can do that?
Thanks,
Sanjiv
This currently not available out of the box. There's an issue, but at this rate it probably wont make it in any of the 2016.x releases.
The cleanest workaround I've found is import:
one.sls:
{% set foo = 'bar' %}
baz: {{ foo }}
two.sls:
{% from 'one.sls' import foo %}
baz2: {{ foo }}

Defining states depending on existance of a file/directory

How is it possible to get something like the following running:
{% if not exist('/tmp/dummy/') then %}
dummy:
file.touch:
- name: /tmp/dummy/tmp.txt
...
{% endif %}
I need it for installing software from a ZIP-file. I want to unzip on the minions, but there I don't want to have any remnants of licence files, which I only need for installation, left.
You can use unless for this.
dummy:
file.touch:
- name: /tmp/dummy/tmp.txt
- unless: test -d /tmp/dummy/
{% if 1 == salt['cmd.retcode']('test -f /tmp/woo.test') %}
ack:
file.touch:
- name: /tmp/woo.test
{% endif %}
You could use the pure python renderer for salt. Here is how it would look like.
#!py
import os
def run():
# defines the hash config
config = {}
if (not os.path.isfile("/tmp/dummy")):
config["dummy"] = {
"file.touch": [
{'name': '/tmp/dummy'},
],
}
return config

Resources