I want to install nginx with particular version. There are servers with different OS, so I want to avoid using similar typed "dnf", "apt", "yum" commands.
By now it looks like this
- name: Install ngnix
dnf:
name: nginx <= "{{ version_needed }}"
state: latest
when: (ansible_os_family == "RedHat")
...
# same commands with "yum" and "apt"
So I want to exclude "when" and change "dnf" with "package" but "dnf", "apt", "yum" accept using "<=" and "package" does not. Any suggestions or tricks?
P.S. The body
name: nginx <= "{{ version_needed }}"
state: latest
must stay due to problems with compatibility on some machines.
Found solution myself.
The nginx <= "{{ version_needed }}" is still a case for me, so I ended up on using "use".
So now, it looks like this:
- name: Install nginx
package:
name: nginx <= "{{ version_needed }}"
state: present
use: "{{ item }}"
with_items:
- dnf
- yum
- apt
ignore_errors: yes
Now it works perfectly fine for me. Only workaround is "ignore_errors" because without this statement it stops on installing.
Related
I'm using an ansible playbook (ansible ver. 2.9) to install WordPress using wp-cli tool.
Here's the playbook:
- name: Create WordPress database
mysql_db: name="{{ db_name }}"
state=present
login_user=root
login_password="{{ mysql_root_password }}"
- name: Create WordPress DB user and grant permissions to WordPress DB
mysql_user: name="{{ db_user }}"
password="{{ db_pwd }}"
priv="{{ db_name }}.*:ALL"
state=present
login_user="root"
login_password="{{ mysql_root_password }}"
- name: Is WordPress downloaded?
stat: path="/var/www/{{ domain_name }}/html/index.php"
register: wp_dir
- name: Download WordPress
command: wp core download
args:
chdir: "/var/www/{{ domain_name }}/html/"
remote_user: "{{ web_user }}"
when: wp_dir.stat.isdir is not defined
- name: Configure WordPress
command: wp core config
--path="/var/www/{{ domain_name }}/html"
--dbname="{{ db_name }}"
--dbuser="{{ db_user }}"
--dbpass="{{ db_pwd }}"
--dbprefix="{{ db_prefix }}"
remote_user: "{{ web_user }}"
when: wp_dir.stat.isdir is not defined
- name: Is WordPress installed?
command: wp core is-installed
args:
chdir: "/var/www/{{ domain_name }}/html/"
register: wordpress_is_installed
ignore_errors: True
remote_user: "{{ web_user }}"
- name: Install WordPress tables
command: wp core install
--url="{{ wp_home_url }}"
--title="{{ wp_site_title }}"
--admin_user="{{ wp_admin_user }}"
--admin_password="{{ wp_admin_pwd }}"
--admin_email="{{ wp_admin_email }}"
args:
chdir: "/var/www/{{ domain_name }}/html/"
when: wordpress_is_installed|failed
remote_user: "{{ web_user }}"
At the "Download WordPress" task, a fatal error shows up:
"Error: YIKES! It looks like you're running this as root. You probably meant to run this as the user that your WordPress installation exists under."
I run the playbook as a sudo user ("ansible_user" in hosts file). And I have setup an additional user to manage WordPress setup (remote_user: "{{ web_user }}").
Any help would be much appreciated!
In the tasks you need to use become and become_user instead remote_user as below
- name: Download WordPress
command: wp core download
args:
chdir: "/var/www/{{ domain_name }}/html/"
become: yes
become_user: "{{ web_user }}"
when: wp_dir.stat.isdir is not defined
Now a different error is showing up when running the same code:
FAILED! => {"msg": "Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user (rc: 1, err: chown: changing ownership of '/var/tmp/ansible-tmp-1613648876.307028-8235-221563540981220/': Operation not permitted\nchown: changing ownership of '/var/tmp/ansible-tmp-1613648876.307028-8235-221563540981220/AnsiballZ_command.py': Operation not permitted\n}). For information on working around this, see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user"}
I updated Ansible to the last version available (2.10).
The only solution I've found so far is adding allow_world_readable_tmpfiles = Yes to ansible.cfg file...
Any ideas?
Thanks
[☺ first time posting here, I have huge problems with formating so sorry, I really dont understand how to get that code to the grey boxes, sorry!)
Hello, so I am supposed to set up a server using Ansible for a high school graduation project. All I have to do is basicaly install a few programs like htop, httpd ..... and finally set up a wordpress server. I am folowing this guide.
Problem is that this code:
---
# tasks file for wp-dependencies
- name: Update packages (this is equivalent to yum update -y)
yum: name=* state=latest
- name: Install dependencies for WordPress
yum:
name:
- php
- php-mysql
- MySQL-python
state: present
- name: Ensure MariaDB is running (and enable it at boot)
service: name=mariadb state=started enabled=yes
- name: Copy ~/.my.cnf to nodes
copy: src=.my.cnf dest=/root/.my.cnf
- name: Create MariaDB database
mysql_db: name={{ wp_mysql_db }} state=present
- name: Create MariaDB username and password
mysql_user: login_user=root login_password=root name = {{ wp_mysql_user }} password = {{ wp_mysql_password }}
priv=*.*:ALL`
Results in this error:
TASK [wp-dependencies : Create MariaDB username and password] ******************************************
fatal: [192.168.56.101]: FAILED! => {"changed": false, "msg": "missing required arguments: user"}
to retry, use: --limit #/home/Admin/wordpress.retry
COuld you tell whats the problem?
Your task is this:
- name: Create MariaDB username and password
mysql_user: login_user=root login_password=root name = {{ wp_mysql_user }} password = {{ wp_mysql_password }}
priv=*.*:ALL`
You have spaces between name and password and the values they are to take. And for safe variable handling you should also place quotation marks (") around the variables.
Try this:
- name: Create MariaDB username and password
mysql_user: login_user=root login_password=root name="{{ wp_mysql_user }}" password="{{ wp_mysql_password }}" priv=*.*:ALL
I am running Ansible version 2.7 on Centos7 using the network_cli connection method.
I have a playbook that:
Instructs a networking device to pull in a new firmware image via TFTP
Instructs the networking device to calculate the md5 hash value
Stores the output of the calculation in .stdout
Has a conditional When: statment that checks for a given md5 value in the .stdout before proceeding with the task block.
No matter what md5 value I give, it always runs the task block.
The conditional statement is:
when: '"new_ios_md5" | string in md5_result.stdout'
Here is the full playbook:
- name: UPGRADE SUP8L-E SWITCH FIRMWARE
hosts: switches
connection: network_cli
gather_facts: no
vars_prompt:
- name: "compliant_ios_version"
prompt: "What is the compliant IOS version?"
private: no
- name: "new_ios_bin"
prompt: "What is the name of the new IOS file?"
private: no
- name: "new_ios_md5"
prompt: "What is the MD5 value of the new IOS file?"
private: no
- name: "should_reboot"
prompt: "Do you want Ansible to reboot the hosts? (YES or NO)"
private: no
tasks:
- name: GATHER SWITCH FACTS
ios_facts:
- name: UPGRADE IOS IMAGE IF NOT COMPLIANT
block:
- name: COPY OVER IOS IMAGE
ios_command:
commands:
- command: "copy tftp://X.X.X.X/45-SUP8L-E/{{ new_ios_bin }} bootflash:"
prompt: '[{{ new_ios_bin }}]'
answer: "\r"
vars:
ansible_command_timeout: 1800
- name: CHECK MD5 HASH
ios_command:
commands:
- command: "verify /md5 bootflash:{{ new_ios_bin }}"
register: md5_result
vars:
ansible_command_timeout: 300
- name: CONTINUE UPGRADE IF MD5 HASH MATCHES
block:
- name: SETTING BOOT IMAGE
ios_config:
lines:
- no boot system
- boot system flash bootflash:{{ new_ios_bin }}
match: none
save_when: always
- name: REBOOT SWITCH IF INSTRUCTED
block:
- name: REBOOT SWITCH
ios_command:
commands:
- command: "reload"
prompt: '[confirm]'
answer: "\r"
vars:
ansible_command_timeout: 30
- name: WAIT FOR SWITCH TO RETURN
wait_for:
host: "{{inventory_hostname}}"
port: 22
delay: 60
timeout: 600
delegate_to: localhost
- name: GATHER ROUTER FACTS FOR VERIFICATION
ios_facts:
- name: ASSERT THAT THE IOS VERSION IS CORRECT
assert:
that:
- compliant_ios_version == ansible_net_version
msg: "New IOS version matches compliant version. Upgrade successful."
when: should_reboot == "YES"
when: '"new_ios_md5" | string in md5_result.stdout'
when: ansible_net_version != compliant_ios_version
...
The other two conditionals in the playbook work as expected. I cannot figure out how to get ansible to fail the when: '"new_ios_md5" | string in md5_result.stdout' conditional and stop the play if the md5 value is wrong.
When you run the play with debug output the value of stdout is:
"stdout": [
".............................................................................................................................................Done!",
"verify /md5 (bootflash:cat4500es8-universalk9.SPA.03.10.02.E.152-6.E2.bin) = c1af921dc94080b5e0172dbef42dc6ba"
]
You can clearly see the calculated md5 in the string but my conditional doesn't seem to care either way.
Does anyone have any advice?
When you write:
when: '"new_ios_md5" | string in md5_result.stdout'
You are looking for the literal string "new_ios_md5" inside the variable md5_result.stdout. Since you actually want to refer to the new new_ios_md5 variable, you ened to remove the quotes around it:
when: 'new_ios_md5 | string in md5_result.stdout'
Credit goes to zoredache on reddit for the final solution:
BTW, you know that for most of the various networking commands ios_command the results come back as a list right? So you need to index into the list relative to the command you run.
Say you had this. task
ios_command:
commands:
- command: "verify /md5 bootflash:{{ new_ios_bin }}"
- command: show version
- command: show config
register: results
You would have output in the list like this.
# results.stdout[0] = verify
# results.stdout[1] = show version
# results.stdout[2] = show config
So the correct conditional statement would be:
when: 'new_ios_md5 in md5_result.stdout[0]'
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/
So looks like this feature has been deprecated, i really don't understand why, Ansible CTO's says that we should use instead with_nested but honestly i have no idea how to do it,
Here's my playboook:
- hosts: all
user: root
vars:
- sites:
- site: site1.com
repo: ssh://hg#bitbucket.org/orgname/reponame
nginx_ssl: true;
copy_init:
- path1/file1.txt
- path2/file2.php
- path2/file3.php
- site: site2.net
repo: ssh://hg#bitbucket.org/orgname/reposite2
- site: site4.com
repo: ssh://hg#bitbucket.org/orgname/reposite3
copy_init:
- path2/file2.php
tasks:
- name: Bootstrap Sites
include: bootstrap_site.yml site={{item}}
And the error message when trying to execute this in Ansible 1.6.6:
ERROR: [DEPRECATED]: include + with_items is a removed deprecated feature. Please update your playbooks.
How can i convert this playbook to something that works with this ansible version?
There's no drop-in replacement, unfortunately. Some things you can do:
Pass the list to your included file and iterate there. In your playbook:
vars:
sites:
- site1
- site2
tasks:
- include: bootstrap_site.yml sites={{sites}}
And in bootstrap_site.yml:
- some_Task: ...
with_items: sites
- another_task: ...
with_items: sites
...
Rewrite bootstrap_site as a module (in python, bash, whatever), put it in a library dir next to your playbook. Then you could do:
- bootstrap_site: site={{item}}
with_items: sites
Update: Ansible V2 is out and brings back the include + with_items combo loop!
I found an answer to circumvent the blahblah-deprecated message... as asked in the original post.
I added a file vars/filenames.yml:
filenames:
- file1
- file2
- file3
Next I read these names at the beginning of the playbook:
- name: read filenames
include_vars: vars/filenames.yml
Then, I can use them later:
- name: Copy files 1
copy: src=/filesrc1/{{ item }} dest=/filedest1/{{ item }} owner=me group=we
with_items: filenames
and so on....
Regards,
Tom