Ansible WHEN with variable interpolation - ansible

From what I was able o find online, Ansible doesn't support varibale intrepolation very well when it comes to jinja templates.
However I'm sure that someone more advanced in Ansible has found a workaround for my problem below.
I would like to "interpolate" a variable to WHEN statment.
i.e. when: Disabled in (smart_link_status.results[item[0]].stdout)
This is my play:
- name: "Get Smart Link status"
shell: "{{ssh_command}} show network {{network_name}}_{{item}} | grep 'Smart Link'"
register: "smart_link_status"
with_items:
- "{{uplink_id}}"
- name: "enable SmartLink for the network"
shell: "{{ssh_command}} set network {{network_name}}_{{item[1]}} SmartLink={{smart_link}}"
when: Disabled in (smart_link_status.results[item[0]].stdout)
with_indexed_items:
- "{{uplink_id}}"
How can I achieve this? Seem's that I can do it easly with normal modules i.e. debug but not with the WHEN statement.
This works fine:
- debug:
msg: "{{ls_bin.results[item[0]].stdout}}"
with_indexed_items:
- "{{bob}}"
Any help or pointers will be appreciated.

The here were two issues:
One of the as correctly pointed by #Konstantin Suvorov was that I did not use the quotation.
The other problem was that I was running the ansible-playbook with --start-at-task and thus skipping the step where smart_link_status was created.

Try this:
when: smart_link_status is defined and smart_link_status.results[item[0]].stdout.find('Disabled') != -1
Also, in the shell task, I would use check_mode: no so that smart_link_status will be undefined when running in check mode. Otherwise, you'll get errors on the subsequent access to smart_link_status.results.

Related

Ansible loop over registered results [duplicate]

i have a simple playbook that is supposed to display my services status. and i want to view the output from the machine to see if the status is active or not. so i used a debug print, like so:
- name: name_of_services
shell: systemctl status {{item}}
with_items:
- service1
- service2
register: out
- debug: var=item.stdout_lines
with_items: out.results
when i execute this i get a lot of info i don't want plus the item.stdout_lines info that i do want in the end of it.
how is it possible to view the output of my command better?
For modules, including debug, called in a loop (ie with_items), the value of item at each iteration will be shown. I don't know of a way to turn this off. If you want you reduce your output you can try switching to using the msg parameter to the debug module which takes a jinja templated string. You could do something like this obviously adjusting the regex to match systemctl output.
- name: show values
debug: msg="{{ item.stdout_lines | replace_regex('^(.*).service.*Active: (.*).$', \\\1 \\\2) }}"
with_items: out.results
If you don't want to use the replace_regex function you can consider writing your own filter plugin to format the data the way you like it.
In general ansible playbooks aren't a great place to display status information gathered through register vars, facts, etc. The playbook output is more geared toward task status.

Execute role in play based on a failure [duplicate]

I'm trying to spin up an AWS deployment environment in Ansible, and I want to make it so that if something fails along the way, Ansible tears down everything on AWS that has been spun up so far. I can't figure out how to get Ansible to throw an error within the role
For example:
<main.yml>
- hosts: localhost
connection: local
roles:
- make_ec2_role
- make_rds_role
- make_s3_role
2. Then I want it to run some code based on that error here.
<make_rds_role>
- name: "Make it"
- rds:
params: etc <-- 1. Let's say it fails in the middle here
I've tried:
- name: this command prints FAILED when it fails
command: /usr/bin/example-command -x -y -z
register: command_result
failed_when: "'FAILED' in command_result.stderr"
As well as other things on within the documentation, but what I really want is just a way to use something like the "block" and "rescue" commands , but as far as I can tell that only works within the same book and on plays, not roles. Does anyone have a good way to do this?
Wrap tasks inside your roles into block/rescue thing.
Make sure that rescue block has at least one task – this way Ansible will not mark the host as failed.
Like this:
- block:
- name: task 1
... # something bad may happen here
- name: task N
rescue:
- assert: # we need a dummy task here to prevent our host from being failed
that: ansible_failed_task is defined
Recent versions of Ansible register ansible_failed_task and ansible_failed_result when hit rescue block.
So you can do some post_tasks in your main.yml playbook like this:
post_tasks:
- debug:
msg: "Failed task: {{ ansible_failed_task }}, failed result: {{ ansible_failed_result }}"
when: ansible_failed_task is defined
But be warned that this trick will NOT prevent other roles from executing.
So in your example if make_rds_role fails ansible will apply make_s3_role and run your post_tasks afterwards.
If you need to prevent it, add some checking for ansible_failed_task fact in the beginning of each role or something.

With Ansible what is the best way of remembering a command has executed successfully previously?

With the command module, if the command creates a file, you can check to see if that file exists. If it does it prevents the command executing again.
- command: touch ~/myfile
args:
creates: ~/myfile
However, if the command does not create a file then on a re-run it executes again.
To avoid a second execution, I create some random file on a change (notify) as follows:
- command: dothisonceonly # this does not create a file
args:
creates: ~/somefile
notify: done
then the handler:
- name: done
command: touch ~/somefile
This approach works but is a bit ugly. Can anyone shed let on best practice? Maybe setting some fact? Maybe a whole new approach?
It is a fact (in common language) that a command was run successfully on a specific target host, so the most appropriate would be to use local facts (in Ansible vernacular).
In your handler save the state as a JSON file under /etc/ansible/facts.d with copy module and content parameter.
It will be retrieved and accessible whenever you run a play against the host with a regular fact-gathering process.
Then you can control the tasks with when condition (you need to include the default filter for the situation when no fact exists yet).
Ideally with Ansible, check for the state that was changed by the first command rather than using a file as a proxy. The reason being that checking the actual state of something provides better immutability since it is tested on every pass.
If there is a reason not to use that approach. Then register the result of the command and use that instead of a notifier to trigger creation of the file.
- command: dothisonceonly # this does not create a file
creates: ~/somefile
register: result
- file:
path: ~/somefile
state: touch
when: result|succeeded
If you are curious to see what is happening here, add:
- debug: var=result
Be aware with notifiers that they are run at the end of the play. This means that if a notifier is triggered by a task but then then play fails to complete, the notifier will not be run. Conversely there are Ansible options which cause notifiers to run even when not triggered by tasks.
This is my actual implementation based on the answer by #techraf
- command: echo 'phew'
register: itworks
notify: Done itworks
when: ansible_local.itworks | d(0) == 0
and handler:
- name: Done itworks
copy:
content: '{{ itworks }}'
dest: /etc/ansible/facts.d/itworks.fact
Docs:
http://docs.ansible.com/ansible/playbooks_variables.html#local-facts-facts-d
Thanks #techraf this works great and persists facts.
EDIT
Applied default value logic in #techraf's comment.

Ansible - Check if string exists in file

I'm very new to Ansible
Is it possible to check if a string exists in a file using Ansible.
I want to check is a user has access to a server.
this can be done on the server using cat /etc/passwd | grep username
but I want Ansible to stop if the user is not there.
I have tried to use the lineinfile but can't seem to get it to return.
code
- name: find
lineinfile: dest=/etc/passwd
regexp=[user]
state=present
line="user"
The code above adds user to the file if he is not there. All i want to do is check. I don't want to modify the file in any way, is this possible
Thanks.
It's a tricky one. the lineinfile module is specifically intended for modifying the content of a file, but you can use it for a validation check as well.
- name: find
lineinfile:
dest: /etc/passwd
line: "user"
check_mode: yes
register: presence
failed_when: presence.changed
check_mode ensures it never updates the file.
register saves the variable as noted.
failed_when allows you to set the failure condition i.e. by adding the user because it was not found in the file.
There are multiple iterations of this that you can use based on what you want the behavior to be. lineinfile docs particular related to state and regexp should allow you to determine whether or not presence or absence is failure etc, or you can do the not presence.changed etc.
I'd probably register and evaluate a variable.
The following simple playbook works for me:
- hosts: localhost
tasks:
- name: read the passwd file
shell: cat /etc/passwd
register: user_accts
- name: a task that only happens if the user exists
when: user_accts.stdout.find('hillsy') != -1
debug: msg="user hillsy exists"
If you want to fail if there is no user:
tasks:
- shell: grep username /etc/passwd
changed_when: false
By default shell module will fail if command exit code is non zero.
So it will give you ok if username is there and fails otherwise.
I use changed_when: false to prevent changed state when grepping.
I am using the following approach, using only a grep -q and a registered variable.
Upside is that it's simple, downside is that you have a FAILED line in your output. YMMV.
- name: Check whether foobar is defined in /bar/baz
command:
cmd: 'grep -q foobar /bar/baz'
register: foobar_in_barbaz
changed_when: false
ignore_errors: true
- when: not foobar_in_barbaz.failed
name: Do something when foobar is in /bar/baz
....
- when: foobar_in_barbaz.failed
pause:
seconds: 1
content: |
You do not seem to have a foobar line in /bar/baz
If you add it, then magic stuff will happen!

Ansible include task only if file exists

I'm trying to include a file only if it exists. This allows for custom "tasks/roles" between existing "tasks/roles" if needed by the user of my role. I found this:
- include: ...
when: condition
But the Ansible docs state that:
"All the tasks get evaluated, but the conditional is applied to each and every task" - http://docs.ansible.com/playbooks_conditionals.html#applying-when-to-roles-and-includes
So
- stat: path=/home/user/optional/file.yml
register: optional_file
- include: /home/user/optional/file.yml
when: optional_file.stat.exists
Will fail if the file being included doesn't exist. I guess there might be another mechanism for allowing a user to add tasks to an existing recipe. I can't let the user to add a role after mine, because they wouldn't have control of the order: their role will be executed after mine.
The with_first_found conditional can accomplish this without a stat or local_action. This conditional will go through a list of local files and execute the task with item set to the path of the first file that exists.
Including skip: true on the with_first_found options will prevent it from failing if the file does not exist.
Example:
- hosts: localhost
connection: local
gather_facts: false
tasks:
- include: "{{ item }}"
with_first_found:
- files:
- /home/user/optional/file.yml
skip: true
Thanks all for your help! I'm aswering my own question after finally trying all responses and my own question's code back in today's Ansible: ansible 2.0.1.0
My original code seems to work now, except the optional file I was looking was in my local machine, so I had to run stat through local_action and set become: no for that particular tasks, so ansible wouldn't attempt to do sudo in my local machine and error with: "sudo: a password is required\n"
- local_action: stat path=/home/user/optional/file.yml
register: optional_file
become: no
- include: /home/user/optional/file.yml
when: optional_file.stat.exists
In Ansible 2.5 and above, it can be done using tests like this:
- include: /home/user/optional/file.yml
when: "'/home/user/optional/file.yml' is file"
More details: https://docs.ansible.com/ansible/latest/user_guide/playbooks_tests.html#testing-paths
I using something similar but for the file module and what did the trick for me is to check for the variable definition, try something like:
when: optional_file.stat.exists is defined and optional_file.stat.exists
the task will run only when the variable exists.
If I am not wrong, you want to continue the playbook even the when statement false?
If so, please add this line after when:
ignore_errors: True
So your tasks will be look like this:
- stat: path=/home/user/optional/file.yml
register: optional_file
- include: /home/user/optional/file.yml
when: optional_file.stat.exists
ignore_errors: True
Please let me know, if I understand your question correctly, or can help further. Thanks
I could spend time here to bash ansible's error handling provisions, but in short, you are right and you can't use stat module for this purpose due to stated reasons.
Simplest solution for most ansible problems is to do it outside ansible. E.g.
- shell: test -f /home/user/optional/file.yml # or use -r if you're too particular.
register: optional_file
failed_when: False
- include: /home/user/optional/file.yml
when: optional_file.rc == 0
- debug: msg="/home/user/optional/file.yml did not exist and was not included"
when: optional_file.rc != 0
* failed_when added to avoid host getting excluded from further tasks when the file doesn't exist.
Using ansible-2.1.0, I'm able to use snippets like this in my playbook:
- hosts: all
gather_facts: false
tasks:
- name: Determine if local playbook exists
local_action: stat path=local.yml
register: st
- include: local.yml
when: st.stat.exists
I get no errors/failures when local.yml does not exist, and the playbook is executed (as a playbook, meaning it starts with the hosts: line, etc.)
You can do the same at the task level instead with similar code.
Using stat appears to work correctly.
There's also the option to use a Jinja2 filter for that:
- set_fact: optional_file="/home/user/optional/file.yml"
- include: ....
when: optional_file|exists
The best option I have come up with so far is this:
- include: "{{ hook_variable | default(lookup('pipe', 'pwd') ~ '/hooks/empty.yml') }}"
It's not exactly an "if-exists", but it gives users of your role the same result. Create a few variables within your role and a default empty file. The Jinja filters "default" and "lookup" take care of falling back on the empty file in case the variable was not set.
For convenience, a user could use the {{ playbook_dir }} variable for setting the paths:
hook_variable: "{{ playbook_dir }}/hooks/tasks-file.yml"

Resources