How to use gather facts inside role? - ansible

I want to check if a package is installed but I get an error. Why the facts are not available inside the role?
TASK [docker : Check if listed package is installed or not on Debian Linux family] *****************************************************************************************************************************************************************
fatal: [10.100.0.52]: FAILED! => {"changed": false, "msg": "Could not detect which package manager to use. Try gathering facts or setting the \"use\" option."}
My playbook
- hosts: "{{ host }}"
gather_facts: yes
sudo: yes
roles:
- { role: "docker"}
and part of my role
- name: Check if listed package is installed or not on Debian Linux family
package:
name: ferm
state: present
check_mode: true
register: package_check
- name: Print execution results
debug:
msg: "Package is installed"
when: package_check is succeeded

Related

Ansible Cisco Config Terminal

I have a problem with Cisco Routers. I can't open a config terminal because it's saying always invalid input. I done everything what are docs saying.
Inventory file:
all:
vars:
ansible_connection: network_cli
ansible_network_os: ios
ansible_user: user
ansible_ssh_pass: pass
ansible_become: yes
ansible_become_method: enable
ansible_become_password: pass
ansible_python_interpreter: python
accept_hostkey: yes
ansible_host_key_checking: false
hosts:
testcisco:
ansible_host: ip
Playbook:
---
- hosts: all
ignore_errors: yes
gather_facts: no
tasks:
- name: Config
cisco.ios.ios_command:
commands: "configure terminal"
register: output
- debug:
msg: "{{ output }}"
Output:
fatal: [testcisco]: FAILED! => {"changed": false, "msg": "configure terminal\r\nconfigure terminal\r\n ^\r\n% Invalid input detected at '^' marker.\r\n\r\n------#"}
No matter what i type config terminal, conf t etc.
It's always saying invalid input
For some reason, Ansible is deciding to run two instances of conf t, which is strange. However, you don't nececarily need to be going into global config mode inside of Ansible, see this documentation: https://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_config_module.html
and specifically this part
- name: configure top level configuration
cisco.ios.ios_config:
lines: hostname {{ inventory_hostname }}
- name: configure interface settings
cisco.ios.ios_config:
lines:
- description test interface
- ip address 172.31.1.1 255.255.255.0
parents: interface Ethernet1
You can see you can do most/all configuration changes you need inside of the playbook itself.
you can also just type ios_config and leave out the word cisco

Use ansible package module to work with apt and homebrew

I have two problems with creating playbook that works on linux and on macOS.
I have many steps like this one in my playbooks:
- name: install something
package:
name: [something_1, something_2, ...]
state: present
become: yes
It works nice for apt and yum, but when I tried to run this on macOS, homebrew complains:
Running Homebrew as root is extremely dangerous and no longer supported.
I couldn't find elegant way of fixing that in many places. Copy all tasks and use when clause seems overwhelming to me. Probably I could use become_user variable set to root/local_user dependent on distribution, but that's a lot changes too.
Second problem is with head-only formula (homebrew packages that can be installed only with --head flag). What if something_2 need to be installed with this flag? Again I could copy task and change package module to homebrew but that's a lot boilerplate.
Any help?
If you want a single set of tasks that flexible enough for for multiple Linux
package managers and macOS brew, the choice is either more logic or more
duplication.
These three patterns should help. They still have repetition and
boilerplate code, but that's the territory we're in with Ansible plays
for cross-platform.
Declare become: yes (root) globally for Linux only
Address packages that need platform-specific treatment as-needed with when
This might be --head for brew, or setting up a PPA for apt, etc
Map package name discrepancies with variables
For example: brew install ncurses, apt install libncurses5-dev, and dnf install ncurses-devel are all the same library.
1) Declare become: yes (root) globally for Linux only
For Linux hosts, switching to root for installation is the intended behavior.
For macOS a la Homebrew, installing as root is not good. So, we need become: no (false) when using brew, and become: yes (true) otherwise (for
Linux).
In your example the become directive is nested inside each task ("step"). To
prevent duplication, invoke become at a higher lexical scope, before the tasks
start. The subsequent tasks will then inherit the state of become, which is
set based on a conditional expression.
Unfortunately a variable for become at the root playbook scope will be
undefined and throw an error before the first task is run:
# playbook.yml
- name: Demo
hosts: localhost
connection: local
# This works
become: True
# This doesn't - the variable is undefined
become: "{{ False if ansible_pkg_mgr == 'brew' else True }}"
# Nor does this - also undefined
become: "{{ False if ansible_os_family == 'Darwin' else True }}"
tasks:
# ...
To fix this, we can store the tasks in another file and import them, or
wrap the tasks in a block. Either of these patterns will provide
a chance to declare become with our custom variable value in time for the
tasks to pick it up:
# playbook.yml
---
- name: Demo
hosts: localhost
connection: local
vars:
# This variable gives us a boolean for deciding whether or not to become
# root. It cascades down to any subsequent tasks unless overwritten.
should_be_root: "{{ true if ansible_pkg_mgr != 'brew' else false }}"
# It could also be based on the OS type, but since brew is the main cause
# it's probably better this way.
# should_be_root: "{{ False if ansible_os_family == 'Darwin' else True }}"
tasks:
# Import the tasks from another file, which gives us a chance to pass along
# a `become` context with our variable:
- import_tasks: test_tasks.yml
become: "{{ should_be_root }}"
# Wrapping the tasks in a block will also work:
- block:
- name: ncurses is present
package:
name: [libncurses5-dev, libncursesw5-dev]
state: present
- name: cmatrix is present
package:
name: cmatrix
state: present
become: "{{ should_be_root }}"
Now there is a single logic check for brew and a single before directive
(depending on which task pattern above is used). All tasks will be executed as
the root user, unless the package manager in use is brew.
2) Address packages that need platform-specific treatment as-needed with when
The Package Module is a great convenience but it's quite limited. By
itself it only works for ideal scenarios; meaning, a package that doesn't
require any special treatment or flags from the underlying package manager. All
it can do is pass the literal string of the package to install, the state, and
an optional parameter to force use of a specific package manager executable.
Here's an example that installs wget with a nice short task and only becomes
verbose to handle ffmpeg's special case when installed with brew:
# playbook.yml
# ...
tasks:
# wget is the same among package managers, nothing to see here
- name: wget is present
when: ansible_pkg_mgr != 'brew'
package:
name: wget
state: present
# This will only run on hosts that do not use `brew`, like linux
- name: ffmpeg is present
when: ansible_pkg_mgr != 'brew'
package:
name: ffmpeg
state: present
# This will only run on hosts that use `brew`, i.e. macOS
- name: ffmpeg is present (brew)
when: ansible_pkg_mgr == 'brew'
homebrew:
name: ffmpeg
# head flag
state: head
# --with-chromaprint --with-fdk-aac --with-etc-etc
install_options: with-chromaprint, with-fdk-aac, with-etc-etc
The play above would produce this output for ffmpeg against a Linux box:
TASK [youtube-dl : ffmpeg is present] ******************************************
ok: [localhost]
TASK [youtube-dl : ffmpeg is present (brew)] ***********************************
skipping: [localhost]
3) Map package name discrepancies with variables
This isn't specifically part of your question but it's likely to come up next.
The Package Module docs also mention:
Package names also vary with package manager; this module will not "translate"
them per distro. For example libyaml-dev, libyaml-devel.
So, we're on our own to handle cases where the same software uses different
names between package manager platforms. This is quite common.
There are multiple patterns for this, such as:
Use separate variable files for each OS/distro and import them
conditionally
Use a role with its own variables
Use the same package manager across platforms, such as Homebrew or
Conda
Compile everything from source via git
None of them are very pleasant. Here is an approach using a role. Roles do
involve more boilerplate and directory juggling, but in exchange they provide
modularity and a local variable environment. When a set of tasks in a role
requires more finagling to get right, it doesn't end up polluting other task
sets.
# playbook.yml
---
- name: Demo
hosts: localhost
connection: local
roles:
- cmatrix
# roles/cmatrix/defaults/main.yml
---
ncurses:
default:
- ncurses
# Important: these keys need to exactly match the name of package managers for
# our logic to hold up
apt:
- libncurses5-dev
- libncursesw5-dev
brew:
- pkg-config
- ncurses
# roles/cmatrix/tasks/main.yml
---
- name: cmatix and its dependencies are present
become: "{{ should_be_root }}"
block:
- name: ncurses is present
package:
name: '{{ item }}'
state: latest
loop: "{{ ncurses[ansible_pkg_mgr] | default(ncurses['default']) }}"
- name: cmatrix is present
when: ansible_pkg_mgr != 'brew'
package:
name: cmatrix
state: present
The task for ncurses looks for an array of items to loop through keyed by the
corresponding package manager. If the package manager being used is not defined
in the variable object, a Jinja default filter is employed to reference the
default value we set.
With this pattern, adding support for another package manager or additional
dependencies simply involves updating the variable object:
# roles/cmatrix/defaults/main.yml
---
ncurses:
default:
- ncurses
apt:
- libncurses5-dev
- libncursesw5-dev
# add a new dependency for Debian
- imaginarycurses-dep
brew:
- pkg-config
- ncurses
# add support for Fedora
dnf:
- ncurses-devel
Combining everything into a real play
Here's a full example covering all three aspects. The playbook has two roles
that each use the correct become value based on a single variable. It also
incorporates an special cases for cmatrix and ffmpeg when installed with
brew, and handles alternate names for ncurses between package managers.
# playbook.yml
---
- name: Demo
hosts: localhost
connection: local
vars:
should_be_root: "{{ true if ansible_pkg_mgr != 'brew' else false }}"
roles:
- cmatrix
- youtube-dl
# roles/cmatrix/defaults/main.yml
ncurses:
default:
- ncurses
apt:
- libncurses5-dev
- libncursesw5-dev
brew:
- pkg-config
- ncurses
dnf:
- ncurses-devel
# roles/cmatrix/tasks/main.yml
---
- name: cmatrix and dependencies are present
# A var from above, in the playbook
become: "{{ should_be_root }}"
block:
- name: ncurses is present
package:
name: '{{ item }}'
state: latest
# Get an array of the correct package names to install from the map in our
# default variables file
loop: "{{ ncurses[ansible_pkg_mgr] | default(ncurses['default']) }}"
# Install as usual if this is not a brew system
- name: cmatrix is present
when: ansible_pkg_mgr != 'brew'
package:
name: cmatrix
state: present
# If it is a brew system, use this instead
- name: cmatrix is present (brew)
when: ansible_pkg_mgr == 'brew'
homebrew:
name: cmatrix
state: head
install_options: with-some-option
# roles/youtube-dl/tasks/main.yml
---
- name: youtube-dl and dependencies are present
become: "{{ should_be_root }}"
block:
- name: ffmpeg is present
when: ansible_pkg_mgr != 'brew'
package:
name: ffmpeg
state: latest
- name: ffmpeg is present (brew)
when: ansible_pkg_mgr == 'brew'
homebrew:
name: ffmpeg
state: head
install_options: with-chromaprint, with-fdk-aac, with-etc-etc
- name: atomicparsley is present
package:
name: atomicparsley
state: latest
- name: youtube-dl is present
package:
name: youtube-dl
state: latest
The result for Ubuntu:
$ ansible-playbook demo.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that
the implicit localhost does not match 'all'
PLAY [Demo] ********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [cmatrix : ncurses is present] ********************************************
ok: [localhost] => (item=libncurses5-dev)
ok: [localhost] => (item=libncursesw5-dev)
TASK [cmatrix : cmatrix is present] ********************************************
ok: [localhost]
TASK [cmatrix : cmatrix is present (brew)] *************************************
skipping: [localhost]
TASK [youtube-dl : ffmpeg is present] ******************************************
ok: [localhost]
TASK [youtube-dl : ffmpeg is present (brew)] ***********************************
skipping: [localhost]
TASK [youtube-dl : atomicparsley is present] ***********************************
ok: [localhost]
TASK [youtube-dl : youtube-dl is present] **************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=6 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0

How to run some tasks locally in Ansible

I have a playbook with some roles/tasks to be executed on the remote host. There is a scenario where I want some tasks to execute locally like downloading artifacts from svn/nexus to local server.
Here is my main playbook where I am passing the target_env from the command line and dynamically loading the variables using group_vars directory
---
- name: Starting Deployment of Application to tomcat nodes
hosts: '{{ target_env }}'
become: yes
become_user: tomcat
become_method: sudo
gather_facts: yes
roles:
- role: repodownload
tags:
- repodownload
- role: stoptomcat
tags:
- stoptomcat
The first role repodownload actually download the artifacts from svn/nexus to the local server/controller. Here is the main.yml of this role -
- name: Downloading MyVM Artifacts on the local server
delegate_to: localhost
get_url: url="http://nexus.com/myrelease.war" dest=/tmp/releasename/
- name: Checkout latest application configuration templates from SVN repo to local server
delegate_to: localhost
subversion:
repo: svn://12.57.98.90/release-management/config
dest: ../templates
in_place: yes
But it's not working. Could it be because in my main yml file I am becoming the user using which I want to execute the commands on remote host.
Let me know if someone can help. It will be appreciated.
ERROR -
"changed": false,
"module_stderr": "sudo: a password is required\n",
"module_stdout": "",
"msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
"rc": 1
}
Given the scenario, you can do it multiple ways. One could be adding another play for repodownload role to your main playbook that runs only on localhost. Then remove delegate_to: localhost from the role tasks and move the variables accordingly.
---
- name: Download repo
hosts: localhost
gather_facts: yes
roles:
- role: repodownload
tags:
- repodownload
- name: Starting Deployment of Application to tomcat nodes
hosts: '{{ target_env }}'
become: yes
become_user: tomcat
become_method: sudo
gather_facts: yes
roles:
- role: stoptomcat
tags:
- stoptomcat
Another way could be removing become from play level and add to role stoptomcat. Something like below should work.
---
- name: Starting Deployment of Application to tomcat nodes
hosts: '{{ target_env }}'
gather_facts: yes
roles:
- role: repodownload
tags:
- repodownload
- role: stoptomcat
become: yes
become_user: tomcat
become_method: sudo
tags:
- stoptomcat
Haven't tested the code so apologies if any formatting issues.

Why does my Ansible task hang?

I have the following ansible playbook:
- hosts: node1
sudo: yes
gather_facts: no
tasks:
- name: update apt
apt: update_cache=yes
- name: install python-setuptools
apt: name=python-setuptools update_cache=yes
- name: easy_install pexpect module
easy_install: name=pexpect state=latest
- name: add geonode repo
apt_repository: repo='ppa:geonode/stable' state=present
- name: update apt
apt: update_cache=yes
- name: install geonode
apt: name=geonode update_cache=yes
- expect:
command: geonode createsuperuser
responses:
(?i)username: 'test'
(?i)email: 'test#test.com'
When I run it I get:
PLAY [node1] *******************************************************************
TASK [update apt] **************************************************************
ok: [node1]
TASK [install python-setuptools] ***********************************************
changed: [node1]
TASK [easy_install pexpect module] *********************************************
changed: [node1]
TASK [add geonode repo] ********************************************************
changed: [node1]
TASK [update apt] **************************************************************
ok: [node1]
TASK [install geonode] *********************************************************
Then it hangs indefinitely.
In the remote node (node1), I checked the dir
/home/vagrant/.ansible/tmp/ansible-tmp-1470059145.13-122191240803512/
run the file inside to see why my task is hanging
vagrant#node1:~/.ansible/tmp/ansible-tmp-1470059145.13-122191240803512$ python apt
and get:
{"msg": "Failed to lock apt for exclusive operation", "failed": true, "invocation": {"module_args": {"dpkg_options": "force-confdef,force-confold", "autoremove": false, "force": false, "name": "geonode", "install_recommends": null, "package": ["geonode"], "purge": false, "allow_unauthenticated": false, "state": "present", "upgrade": null, "update_cache": true, "default_release": null, "only_upgrade": false, "deb": null, "cache_valid_time": null}}}
Do you have any insights?
EDIT 1:
It is all day I'm launching this script and never got it working. As I posted this question, obviously, the script successfully executed till the end in 15 minutes. I launched it before lunch today and after 1 hour it was still hanging. Why do I get such a different behaviour? Is there a way in which I can control it?
This issue might be caused by an empty /var/lib/apt folder.
Vagrant might take a while to populate these folders which could cause the apt lock.
Also the playbook is inefficient as update_cache is used multiple times. I would recommend to use something like this:
- hosts: node1
sudo: yes
gather_facts: no
tasks:
# Pause for 5 minutes to make sure vagrant does not hold apt lock.
- pause:
minutes: 5
- name: add geonode repo
apt_repository:
repo: 'ppa:geonode/stable'
state: present
- name: Install apt packages.
apt:
name: "{{ item }}"
state: present
update_cache: true
with_items:
- python-setuptools
- geonode
- name: Create geonode superuser.
expect:
command: geonode createsuperuser
responses:
(?i)username: 'test'
(?i)email: 'test#test.com'
This way Ansible won't update the repositories multiple times during the play.
Since the last thing you see is TASK [install geonode], that's where it's getting stuck.
You are asking it to run geonode createsuperuser which you expect to cause a prompt for username and password.
But what is likely happening is that there is an error being produced from that command, and the expect task is not handling the error, instead just hanging.
You can login to the server you are running this against and run the geonode createsuperuser command manually to see what error is being produced.
In my case, this was happening as a result of the username already being taken since I successfully ran the command on this machine already.
Error: That username is already taken.
Even with the echo: yes argument, ansible doesn't seem to pass on the responses to make it obvious what's happening. And it doesn't accept ignore_errors, so there seems to be no way to handle errors with the expect module.
To work around this, I added another task after the createsuperuser task which places a file in the project indicating the user was created once, and then added creates: {{ path }}/superuser_exists.txt to the createsuperuser task so that it won't run if that file is already there.
It's a hack, but an easy one, and until the module gets better error handling, it will work well enough.
- name: Create the django superuser
expect:
command: "{{ virtualenv_path }}/bin/python3 {{ project_path }}/{{ api_app_name }}/manage.py createsuperuser"
creates: "{{ project_path }}/{{ api_app_name }}/superuser_exists.txt"
responses:
(?i)username: "{{ superuser_username }}"
(?i)email: "{{ superuser_email }}"
(?i)password: "{{ superuser_password }}"
(?i)again: "{{ superuser_password }}"
- name: Create a file to indicate that the superuser was already created
file: path="{{ project_path }}/{{ api_app_name }}/superuser_exists.txt" state=touch

how to run a particular task on specific host in ansible

my inventory file's contents -
[webservers]
x.x.x.x ansible_ssh_user=ubuntu
[dbservers]
x.x.x.x ansible_ssh_user=ubuntu
in my tasks file which is in common role i.e. it will run on both hosts but I want to run a following task on host webservers not in dbservers which is defined in inventory file
- name: Install required packages
apt: name={{ item }} state=present
with_items:
- '{{ programs }}'
become: yes
tags: programs
is when module helpful or there is any other way? How could I do this ?
If you want to run your role on all hosts but only a single task limited to the webservers group, then - like you already suggested - when is your friend.
You could define a condition like:
when: inventory_hostname in groups['webservers']
Thank you, this helps me too.
hosts file:
[production]
host1.dns.name
[internal]
host2.dns.name
requirements.yml file:
- name: install the sphinx-search rpm from a remote repo on x86_64 - internal host
when: inventory_hostname in groups['internal']
yum:
name: http://sphinxsearch.com/files/sphinx-2.2.11-1.rhel7.x86_64.rpm
state: present
- name: install the sphinx-search rpm from a remote repo on i386 - Production
when: inventory_hostname in groups['production']
yum:
name: http://sphinxsearch.com/files/sphinx-2.2.11-2.rhel6.i386.rpm
state: present
An alternative to consider in some scenarios is -
delegate_to: hostname
There is also this example form the ansible docs, to loop over a group. https://docs.ansible.com/ansible/latest/user_guide/playbooks_delegation.html -
- hosts: app_servers
tasks:
- name: gather facts from db servers
setup:
delegate_to: "{{item}}"
delegate_facts: True
loop: "{{groups['dbservers']}}"

Resources