Ansible Version Forcing - ansible

We have multiple versions of ansible(2.3,2.3.0,2.8.6) we want to use a particular version '2.8.6'.
But whenever ansible execution starts, it uses a different version 2.4.2.0. If we check ansible --version it is ansible-playbook 2.4.2.0.
Is ansible and ansible-playbook versions are same?
How to force a version in ansible execution?
Please give your inputs.

Ansible is well-known to subtly break compatibility around variables, roles, includes and delegation. Therefore, it's considered to be the best practice to assert an Ansible version in a playbook before doing anything.
There are few things to assure:
Those checks should survive --tags option.
Those checks should survive --limit
Those checks should be relatively fast and cause as less verbosity as possible.
My current solution is:
Create playbook version_check.yaml
- import_playbook: version_check.yaml at the beginning of each playbook. (each, this is important, trust me).
version_check.yaml content (ansible 2.5+):
- hosts: all, localhost
gather_facts: false
run_once: true
tasks:
- name: Check ansible version
assert:
that:
- "ansible_version.full is version('2.8.0', '>=')"
- "ansible_version.full is version('2.9.0', '<')"
msg: >
Use Ansible to 2.8 to run this playbook
delegate_to: localhost
tags:
- always
As you can see, there are a lot here.
run_once do this once per Ansible run.
gather_facts: false speed it up a bit.
It is assigned to 'all, localhost', basically guaranee that it's in Ansible runlist regardless of any --limit
It has tag always almost guarantee to run in the case of use --tags. The single uncovered hole here is to use --skip always (which is insane).
There is an older answer with older syntax for versions (below 2.5):
- hosts: all, localhost
gather_facts: false
run_once: true
tasks:
- name: Check ansible version
assert:
that:
- "ansible_version.full | version_compare('2.8.0', '>=')"
- "ansible_version.full | version_compare('2.9.0', '<')"
msg: >
Use Ansible to 2.8 to run this playbook
delegate_to: localhost
tags:
- always
It's hard to say which version to use. If you are afraid of very old ansible, version_compare is better than version, but if you are sure it's at least 2.5+, you can use newer version syntax.

Related

Ansible pre-check before run playbook

Is it possible to add a condition before to run a playbook which check if there is a title, a description, the environment and the versions on the playbook ?
For example my test.yml playbook:
---
#Apache servers
#Linux
#Ubuntu
#version 2.1.1
#Testing for secure the webserver
task:
xxxxxx
xxxxxx
And I would like to check if all the comment before are present before to run this task !
I tried to test this solution :
name: run Apachetest playbook
include: test.yml
when: "{{ lookup('file', 'test.yml').split('\n')[1] == '#Apache servers' }}"
But still not working...
BS
Comments are, well, comments. They do not impact the execution and are just ignored. So there is no way, and actually no real reason, to check if comments are present or not. You would need to implement that yourself.
To check playbooks, roles, etc. there is ansible-lint which will verify the syntax and some best practices (e.g. if you use a command or shell for something there is a module for) but this does not verify comments (again, checking for comments does not make sense from a execution perspective, as they are ignored).
You want some information to be present in your playbook, that is what I understand. If I was you, I would either create a git hook, that verifies if the information is present before letting you push that code to your repository or establish a proper review-process, where the reviewer only accepts a merge/pull request, if the information is present.
Otherwise, here is the code, that will do what you are trying to do:
---
#Apache server
- hosts: all
tasks:
- name: set fact
set_fact:
pb: "{{ lookup('file', 'test.yml').split('\n')[1] }}"
- name: check if we found it
debug:
msg: 'found'
when: "'#Apache server' in pb"
You could use the apache role for apache installed like that
---
- hosts: apache
sudo: yes
tasks:
- name: install apache2
apt: name=apache2 update_cache=yes state=latest
have a look here how-to-install-apache-on-ansible

Is it safe to use Ansible shell module and gather_facts: no?

I was writing an Ansible playbook to update Linux headers and build essentials for Debian operating system. But the playbook hang in gathering facts step. So after lots of search on internet I introduced gather_facts: no and then it ran successfully. But I want to know:
Is is OK to use gather_facts: no. Please give some understanding of gather_facts and what it does internally?
To get the kernel version I used the shell module. Is there any other Ansible command to get the kernel version of host machine?
- hosts: DEV1
become: yes
gather_facts: no
tasks:
- name: "Getting the debian kernal version"
shell:
cmd: uname -r
register: kernal_version_output
- name: "Debug content for kernal version"
debug:
msg: "kernal version is => {{ kernal_version_output.stdout }}"
- name: "Update apt-get repo and cache"
apt:
name:
- libreadline-gplv2-dev
- libreadline-dev
- linux-headers-{{ kernal_version_output.stdout }}
- build-essential
Regarding your questions
Please give some understanding of gather_facts
you may have a look into Playbook Vars Facts "Discovering variables: facts and magic variables" and the setup_module.
Quote: "With Ansible you can retrieve or discover certain variables containing information about your remote systems or about Ansible itself. Variables related to remote systems are called facts."
... and what it does internally?
For Ansible ad-hoc commands and the setup_module the source code of setup.py is a good starting point to research how it works internally. In summary, it is just executing some scripts to collect certain system information.
Is is OK to use gather_facts: no
Yes, of course. If you don't need specific information about the remote system to perform tasks and if commands and playbook do not depend on gathered information, you can leave it switched off.
To get the kernel version I used the shell module. Is there any other Ansible command to get the kernel version of host machine?
Even if that is an anti-pattern with Ansible, without facts there seems to be no other possibilities.

how to set different python interpreters for local and remote hosts

Use-Case:
Playbook 1
when we first connect to a remote host/s, the remote host will already have some python version installed - the auto-discovery feature will find it
now we install ansible-docker on the remote host
from this time on: the ansible-docker docs suggest to use ansible_python_interpreter=/usr/bin/env python-docker
Playbook 2
We connect to the same host/s again, but now we must use the /usr/bin/env python-docker python interpreter
What is the best way to do this?
Currently we set ansible_python_interpreter on the playbook level of Playbook 2:
---
- name: DaqMon app
vars:
- ansible_python_interpreter: "{{ '/usr/bin/env python-docker' }}"
This works, but this will also change the python interpreter of the local actions. And thus the local actions will fail, because (python-docker does not exist locally).
the current workaround is to explicitly specify the ansible_python_interpreter on every local-action which is tedious and error-prone
Questions:
the ideal solution is, if we could add '/usr/bin/env python-docker' as fallback to interpreter-python-fallback - but I think this is not possible
is there a way to set the python interpreter only for the remote hosts - and keep the default for the localhost?
or is it possible to explicitly override the python interpreter for the local host?
You should set the ansible_python_interpreter on the host level.
So yes, it's possible to explicitly set the interpreter for localhost in your inventory.
localhost ansible_connection=local ansible_python_interpreter=/usr/bin/python
And I assume that you could also use set_fact on hostvars[<host>].ansible_python_interpreter on your localhost or docker host.
There is a brillant article about set_fact on hostvars ! ;-P
Thanks to the other useful answers I found an easy solution:
on the playbook level we set the python interpreter to /usr/bin/env python-docker
then we use a set_fact task to override the interpreter for localhost only
we must also delegate the facts
we can use the magic ansible_playbook_python variable, which refers to the python interpreter that was used on the (local) Ansible host to start the playbook: see Ansible docs
Here are the important parts at the start of Playbook 2:
---
- name: Playbook 2
vars:
- ansible_python_interpreter: "{{ '/usr/bin/env python-docker' }}"
...
tasks:
- set_fact:
ansible_python_interpreter: '{{ ansible_playbook_python }}'
delegate_to: localhost
delegate_facts: true
Try to use set_fact for ansible_python_interpreter at host level in the first playbook.
Globally, use the interpreter_python key in the [defaults] section of the ansible.cfg file.
interpreter_python = auto_silent

Iterate over inventory facts [duplicate]

I'm sitting in front of a fairly complex Ansible project that we're using to set up our local development environments (multiple VMs) and there's one role that uses the facts gathered by Ansible to set up the /etc/hosts file on every VM. Unfortunately, when you want to run the playbook for one host only (using the -limit parameter) the facts from the other hosts are (obviously) missing.
Is there a way to force Ansible to gather facts on all hosts, even if you limit the playbook to one specific host?
We tried to add a play to the playbook to gather facts from all hosts, but of course that also gets limited to the one host given by the -limit parameter. If there'd be a way to force this play to run on all hosts before the other plays, that would be perfect.
I've googled a bit and found the solution with fact caching with redis, but since our playbook is used locally, I wanted to avoid the need for additional software. I know, it's not a big deal, but I was just looking for a "cleaner", Ansible-only solution and was wondering, if that would exist.
Ansible version 2 introduced a clean, official way to do this using delegated facts (see: http://docs.ansible.com/ansible/latest/playbooks_delegation.html#delegated-facts).
when: hostvars[item]['ansible_default_ipv4'] is not defined is a check to ensure you don't check for facts in a host you already know the facts about
---
# This play will still work as intended if called with --limit "<host>" or --tags "some_tag"
- name: Hostfile generation
hosts: all
become: true
pre_tasks:
- name: Gather facts from ALL hosts (regardless of limit or tags)
setup:
delegate_to: "{{ item }}"
delegate_facts: True
when: hostvars[item]['ansible_default_ipv4'] is not defined
with_items: "{{ groups['all'] }}"
tasks:
- template:
src: "templates/hosts.j2"
dest: "/etc/hosts"
tags:
- hostfile
...
In general the way to get facts for all hosts even when you don't want to run tasks on all hosts is to do something like this:
- hosts: all
tasks: [ ]
But as you mentioned, the --limit parameter will limit what hosts this would be applied to.
I don't think there's a way to simply tell Ansible to ignore the --limit parameter on any plays. However there may be another way to do what you want entirely within Ansible.
I haven't used it personally, but as of Ansible 1.8 fact caching is available. In a nutshell, with fact caching enabled Ansible will use a redis server to cache all the facts about hosts it encounters and you'll be able to reference them in subsequent playbooks:
With fact caching enabled, it is possible for machine in one group to reference variables about machines in the other group, despite the fact that they have not been communicated with in the current execution of /usr/bin/ansible-playbook.
This still seems to be an issue without a clean solution here in 2016, but newer versions of Ansible offer a "jsonfile" fact caching backend, which seems to be a decent compromise to installing Redis locally just to address this need. Now I just fire off an ansible all -m setup before running a playbook with the --limit option. Good enough for jazz!
http://docs.ansible.com/ansible/playbooks_variables.html#fact-caching
You could modify your playbook to:
...
- hosts: "{{ limit_hosts|default('default_group') }}"
tasks:
...
...
And when you run it, if some_var is not defined (normal state) then it will run on the default_group inventory group, BUT if you run it as:
ansible-playbook --extra-vars "limit_hosts=myHost" myplaybook.yml
Then it will only run on your myHost, but you could still have other sections with different hosts: .. declarations, for fact gathering, or anything else actually.

Ansible - vars are not correctly propagated to handlers when role run in loop

I am asking for help with a problem of deploying multiple versions (different variables) of the app with the same role run from one playbook.
We have an app with multiple product families, which are different code versions. Each version has separate uWSGI vassal config and Nginx virtualhost config(/api/v2, /api/v3, ...).
The desired state would be to run playbook and configure the server with all versions specified.
Sadly, ansible's import_role/import_tasks can't be used with with_items, so include_role/include_tasks must be used (pitty because they do not honor role tags).
The include_role method would not be the biggest problem, but we use handlers to notify uWSGI touch to reload - on a code change, link change, virtualenv change, app_config change, ...).
But when using loop (with_items), the variables passed from the loop does not correctly propagate to handlers.
I tried this scenarios
playbook.yml - with_items loop inside the playbook
PROBLEM: Handler is run only for the first iteration of the loop.
#!/usr/bin/env ansible-playbook
# HAndler is run only once, from first notifier
- hosts: localhost
gather_facts: no
vars:
app_root: "/tmp/test_ansible"
app_versions:
- app_product_family: 1
app_release: "v1.0.2"
- app_product_family: 3
app_release: "v4.0.7"
tasks:
- name: Deploy multiple versions of app
include_role:
name: app
with_items: "{{ app_versions }}"
loop_control:
loop_var: app_version
vars:
app_product_family: "{{ app_version.app_product_family }}"
app_release: "{{ app_version.app_release }}"
tags:
- app
- app_debug
playbook_v2.yml - with_items loop inside role task
PROBLEM: Handler is run with the default value from "Defaults"
#!/usr/bin/env ansible-playbook
- hosts: localhost
gather_facts: no
roles:
- app_v2
vars:
app_v2_root: "/tmp/test_ansible_v2"
app_v2_versions:
- app_v2_product_family: 1
app_v2_release: "v1.0.2"
- app_v2_product_family: 3
app_v2_release: "v4.0.7"
Tasks roles/app_v2/main.yml
---
# Workaround because import_tasks can't be run with_items
- include_tasks: deploy.yml
when: app_v2_versions
with_items: "{{ app_v2_versions }}"
loop_control:
loop_var: app_v2
vars:
app_v2_product_family: "{{ app_v2.app_v2_product_family }}"
app_v2_release: "{{ app_v2.app_v2_release }}"
tags:
- app_v2
- app_v2_deploy
...
One idea was about writing a separate role for each product family, but they share nginx and uWSGI, so it will be lots of copy-pasting and sharing tasks (so tags would not work properly).
For now, I solved it with shell script wrapper, but this is not an ideal solution and does not work from Ansible tower.
Sample repo with tasks to reproduce problem (tested with ansible 2.4, 2.5, 2.6)
Any ideas & recommendations are very welcome.
The order of overrides for variables is broken for includes in Ansible. F.e. even set_fact in the included role will be shadowed by role defaults.
See this bug: https://github.com/ansible/ansible/issues/22025
It's closed but not fixed. My advice: use include and variables really carefully.
In practice I never use role includes with loop. If you need loop, include a tasklist in this loop (and that tasklist, in turn, may import_role).
Ok, It is a bug as #George Shuklin posted.
I will use my shell wrapper, which reads group_vars yaml and then runs the playbook multiple times according to the variable list length.
Sadly I hit multiple annoying bugs in ansible in last few weeks, kinda losing my trust in it ):
And probably everybody is using microservices and kubernetes, so need to speed up our migration (:

Resources