How to add meta information to ansible playbook tasks - ansible

I have to display the ansible output to the user after a play is ran programatically. Currently I log the std_out to a json file using the stdout_callback=json option and then parse it. But I don't need to show all tasks from the tasks to the user but only a few.
For example, in the following tasks I only want to show the installing supervisor task, not the second one.
tasks:
-
name: 'Install Supervisor'
apt: 'pkg=supervisor state=latest update_cache=true'
-
name: 'Ensure the Supervisor service is enabled'
service: 'name=supervisor state=started enabled=yes'
Is there any way to
either omit it from the stdout, so that it never comes in the json log file.
I add some kind of meta information to the task, so that I can see that information in the log file and avoid showing that to user.

Related

How can I avoid duplication of retry logic in Ansible tasks?

In my Ansible playbook I've been running into intermittent failures with apt tasks due to failures to obtain a lock.
I'm thinking of using some retry logic as described in this answer. e.g.
- name: install packages
apt:
name:
- nginx
- git
register: result
until: result is not failed
retries: 7
delay: 9
The playbook includes multiple apt tasks so I'm wondering if there's a way that I can avoid repeating the register, retries, until for each apt task i.e. some way to make these defaults, or to define my own apt-with-retries?
My playbook is for provisioning DigitalOcean droplets. I've got several apt tasks due to:
Breaking down the playbook into logic steps i.e. apt install a few things, followed by a few other steps, followed by apt installing another group of packages...
I've extracted a couple of roles into their own self-contained files e.g. a postgres_server role.
The goals here are the usual reasons for wanting to avoid duplication/repetition. e.g. avoid having to make changes in multiple places, avoid having to remember to copy and paste when adding another apt task in the future.
I looked into using module_defaults, but these properties are on the task itself rather than the apt module so I don't think they can be used for this scenario.
Newer version of apt module has support for retries at module level:
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/apt_module.html#parameter-lock_timeout
Default is 60.

Executing task only on the first run on each host

I'm new to ansible and I would like to run some tasks only once. Not once per every run but only once and then never. In puppet I used file placeholders for that, similar to ansible "creates".
What is the best practice for my use case?
If I should use the "creates" method, which folder should be safe to use?
For example I would like to install letsencrypt on host where the first step is to update snap. I don't want to refresh snap every run so I would like to run it only before letsencrypt install.
- name: Update snap
command: snap install core && snap refresh core
args:
creates: /usr/local/ansible/placeholders/.update-snap
become: true
Most of the ansible modules are made to be idempotent, which means that they will perform action only when needed.
shell, script and command are the exception as ansible cannot know if action needs to be performed, and that's why there is the creates parameter to add idempotency.
That's why before writing a shell task you should check if there is not already an ansible module that could perform your task. There are almost 3000 base modules and with new versions of ansible you can get new community-provided modules through collections in Ansible Galaxy.
In your specific case, the snap module (https://docs.ansible.com/ansible/2.10/collections/community/general/snap_module.html) may be enough:
- name: Update snap
snap:
name: core
become: true
It will install the core snap if it's not already installed, and do nothing otherwise.
If it's not enough, you can implement yourself idempotency by creating a task querying snap to check the presence of core with it's version (add a changed_when: False as it's a task not doing any changes) and then launching the install/update depending on the query result (with a when condition).
If there is no way to do that, then you can fallback to using the creates. If the command executed already creates a specific file that presence can be used to know if it was already executed, better to reference this one. Else you need to create your own marker file in the shell. As it's a last option solution, there is no convention of where this marker file should be, so up to you to create your own (the one you propose looks good, even if the file name could be more specific and include the snap name in case you should use the same trick for other snaps).

How can I ensure that an Ansible shell command executes only once per host?

I want to be able to guarantee that after a long running shell command is executed successfully on a given host, it is not executed in subsequent playbook runs on that host.
I was happy to learn of the creates option to the Ansible shell task (see http://docs.ansible.com/ansible/shell_module.html); using this I can create a file after a shell command succeeds and not have that command execute in future runs:
- name: Install Jupiter theme
shell: wp theme install ~/downloads/jupiter-theme.zip --activate
&& touch ~/ansible-flags/install-jupiter-theme
args:
creates: ~/ansible-flags/install-jupiter-theme
As you can see, I have a directory ansible-flags for these control files in the home directory of my active user (deploy).
However, this is quite a kludge. Is there a better way to do this?
Also, the situation gets a lot more complicated when I need to run a step as a different user; in that case, I don't have access to the deploy home directory and its subdirectories. I could grant access, of course, but that's adding even more complexity. How would you recommend I deal with this?
Your playbook should be idempotent!
So either task should be safe to be executed multiple times (e.g. echo ok) or your should check whether you should execute it at all.
Your task may look like this:
- name: Install Jupiter theme
shell: wp theme is-installed jupiter || wp theme install ~/downloads/jupiter-theme.zip --activate
It will check if the jupiter theme is installed and will run wp theme install only if theme is not installed.
But this will mark the result of task as changed in any case.
To make it nice, you can do this:
- name: Install Jupiter theme
shell: wp theme is-installed jupiter && echo ThemeAlreadyInstalled || wp theme install ~/downloads/jupiter-theme.zip --activate
register: cmd_result
changed_when: cmd_result.stdout != "ThemeAlreadyInstalled"
First, it will check if the jupiter theme is already installed.
If this is the case, it will output ThemeAlreadyInstalled and exit.
Otherwise it will call wp theme install.
The task's result is registered into cmd_result.
We user changed_when parameter to state the fact that if ThemeAlreadyInstalled ansible shouldn't consider the task as changed, so it remains green in your playbook output.
If you do some preparation tasks before wp theme install (e.g. download theme), you may want to run the test as separate task to register the result and use when clauses in subsequent tasks:
- name: Check Jupiter theme is installed
shell: wp theme is-installed jupiter && echo Present || echo Absent
register: wp_theme
changed_when: false
- name: Download theme
get_url: url=...
when: wp_theme.stdout == "Absent"
- name: Install Jupiter theme
shell: wp theme install ~/downloads/jupiter-theme.zip --activate
when: wp_theme.stdout == "Absent"
Ansible itself is stateless, so whatever technique you use needs to be implement by yourself. There is no easier way like telling Ansible to only ever run a task once.
I think what you have already is pretty straight forward. Maybe use another path to check. I'm pretty sure the wp theme install will actually extract that zip to some location, which you then can use together with the creates option.
There are alternatives but nothing that makes it easier:
Create a script in any language you like, that checks for the theme and if it is not installed, install it. Instead of the shell task then you would execute the script with the script module.
Install a local fact in /etc/ansible/facts.d which checks if the theme in already installed. Then you could simply apply a condition based on that fact to the shell task.
Set up fact caching, then register the result of your shell task and store it as a fact. With fact caching enabled you can access the stored result on later playbook runs.
It only gets more complicated. The creates option is perfect for this scenario and if you use the location where the wp command extracted the zip to, it also is pretty clean.
Of course you need to grant access to that file, if you have a need to access it from another user account. Storing stuff in a users home directory with the permissions set to only be readable by the owner indeed is no ideal solution.

Can I install (or remove) Ansible role from Galaxy using ansible-pull?

I'm working with Ansible using ansible-pull (runs on cron).
Can I install Ansible role from Ansible Galaxy without login in to all computers (just by adding a command to my Ansible playbook)?
If I understand you correctly, you're trying to download and install roles from Ansible Galaxy from the command line, in a hands-off manner, possibly repeatedly (via cron). If this is the case, here's how you can do it.
# download the roles
ansible-galaxy install --ignore-errors f500.elasticsearch groover.packerio
# run ansible-playbook to install the roles downloaded from Ansible Galaxy
ansible-playbook -i localhost, -c local <(echo -e '- hosts: localhost\n roles:\n - { role: f500.elasticsearch, elasticsearch_cluster_name: "my elasticsearch cluster" }\n - { role: groover.packerio, packerio_version: 0.6.1 }\n')
Explanation / FYI:
To download roles from Ansible Galaxy, use ansible-galaxy, not ansible-pull. For details, see the manual. You can download multiple roles at once.
If the role had been downloaded previously, repeated attempts at downloading using ansible-galaxy install will result in an error. If you wish to call this command repeatedly (e.g. from cron), use --ignore-errors (skip the role and move on to the next item) or --force (force overwrite) to work around this.
When running ansible-playbook, we can avoid having to create an inventory file using -i localhost, (the comma at the end signals that we're providing a list, not a file).
-c local (same as --connection=local) means that we won't be connecting remotely but will execute commands on the localhost.
<() functionality is process substitution. The output of the command appears as a file, so we can feed a "virtual playbook file" into the ansible-playbook command without saving the playbook to the disk first (e.g., playbookname.yml).
As shown, it's possible to embed role variables, such as packerio_version: 0.6.1 and apply multiple roles in a single command.
Note that whitespace is significant in playbooks (they are YAML files). Just as in Python code, be careful about indentation. It's easy to make typos in long lines with echo -e and \n (newlines).
You can run updates of roles from Ansible Galaxy and ansible-playbook separately.
With a bit of magic, you don't have to create inventory files or playbooks (this can be useful sometimes). The solution to install Galaxy roles remotely via push is less hacky / cleaner but if you prefer to use cron and pulling then this can help.
I usually add roles from galaxy as submodules in my own repository; that way I have control over when I update them, and ansible-pull will automatically fetch them - removing the need to run ansible-galaxy.
E.g.:
mkdir roles
git submodule add https://github.com/groover/ansible-role-packerio roles/groover.packerio
Yes you can.
# install Ansible Galaxy requirements via the pull playbook itself
- hosts: localhost
tasks:
- command: ansible-galaxy install -r requirements.yml

(no such process) while deploying app via Ansible

I am trying to follow the Ansible Tutorial by Matt Wright. I have forked it and updated with latest Ansible modules here.
But I'm getting
msg: hello_flask: ERROR (no such process)
while running deploy.yml at -name: start app. I have a open issue here on github.
Why I am getting this error?
So you are seeing the error because supervisor is not finding the hello_flask application.
This is probably because you have a newer configuration for supervisor that doesn't include ini files.
If you look at one of the latest /etc/supervisor/supervisor.conf it actually includes *.conf files not *.ini files.
[include]
files = /etc/supervisor/conf.d/*.conf
Also, if you look at this Ansible task:
- name: create supervisor program config
action: template src=templates/supervisor.ini dest=/etc/supervisor/${app_name}.ini
notify:
- restart app
You can see that the configuration for hello_flash is being put under /etc/supervisor/hello_flash.ini
So make sure either that your supervisor.conf includes *.ini files. Or simply change this step to this:
- name: create supervisor program config
action: template src=templates/supervisor.ini dest=/etc/supervisor/conf.d/${app_name}.conf
notify:
- restart app
Hope it helps.

Resources