Ansible logical AND with conditions not working [closed] - ansible

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 4 months ago.
Improve this question
I try to run task depending on two conditions using logical "and". For the first condition I created the following task:
- name: Check if configuration already exists, then skip next task
stat:
path: "{{openldap_config}}/cn=config"
register: is_configured
Then I crated the following task, which is working fine:
- name: add new basic-configuration
shell: /opt/symas/sbin/slapadd -n 0 -F {{openldap_config}} -l /home/{{ansible_user}}/config.ldif
args:
executable: /bin/bash
when: not is_configured.stat.exists
If a configuration file exists the task will be skipped. Then I have a few task where I use "group_names" like this on:
- name: generating deltasyncrepl LDIF for main DB
template:
src: main_db_repl.j2
dest: /home/{{ansible_user}}/main-db-repl.ldif
owner: "{{ansible_user}}"
group: "{{ansible_group}}"
mode: '660'
when: "'ldap_provider' in group_names"
That's also working. BUT now I would like to have a logical "and" for both conditions:
- name: add replication of cn=config to all provider
shell: /opt/symas/bin/ldapmodify -Y EXTERNAL -H ldapi:/// -f /home/{{ansible_user}}/repl_config.ldif
args:
executable: /bin/bash
when: not is_configured.stat.exists
and "'ldap_provider' in group_names"
This is not working. I tried different quoting but I could not find a a working solution. I try it with brackets too, also not working. Some how it must be possible to have a logical AND with a int- and a string- variable.
Thanks for any help

While you can manually stat the file and that works just fine, this is actually exactly the case that the creates parameter to the shell and command modules is designed to handle. You should also migrate to the cmd parameter (supported since Ansible 2.0) instead of using args, so that people reading your code don't have to understand that rarely-used syntax:
- name: Basic configuration for OpenLDAP
shell:
cmd: /opt/symas/sbin/slapadd -n 0 -F {{ openldap_config }} -l /home/{{ ansible_user }}/config.ldif
executable: /bin/bash
creates: "{{ openldap_config }}/cn=config"
register: openldap_config_result
While you didn't include your other attempts at solving this problem, generally the best way to apply multiple conditions to a task is to use a list, which will implicitly apply and between each condition in the list:
- name: Add replication of cn=config to all providers
shell:
cmd: /opt/symas/bin/ldapmodify -Y EXTERNAL -H ldapi:/// -f /home/{{ ansible_user }}/repl_config.ldif
executable: /bin/bash
when:
- openldap_config_result is changed
- "'ldap_provider' in group_names"
You can also do it as a single string, though you have to make sure your quoting is correct (which it isn't in your question):
when: openldap_config_result is changed
and 'ldap_provider' in group_names

Related

Ansible lineinfile not performing idempotency [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 1 year ago.
Improve this question
I have an ansible task that writes 2 lines into journald.conf, however it doesn't perform idempotency when running again.
I have seen the following questions that didn't work for me:
Ansible lineinfile duplicates line
Idempotency in ansible playbook
https://github.com/ansible/ansible/issues/4531
My regex seems to be okay, you can see below my task:
- name: set cpu affinity settings in systemd
lineinfile:
dest: /etc/systemd/journald.conf
line: "{{ item.key }}={{ item.value }}"
regexp: "^#?{{ item.value }}"
state: present
with_dict:
RateLimitIntervalSec: 0
RateLimitBurst: 0
tags: journald
notify: restart journald
The expected behavior should be: keep the commented line and add new ones at the end of the file with the items in the list, unless the uncommented lines already exists.
My file journald.conf file is like this:
[Journal]
#Storage=auto
#Compress=yes
#Seal=yes
#SplitMode=uid
#SyncIntervalSec=5m
#RateLimitIntervalSec=30s
#RateLimitBurst=1000
#SystemMaxUse=
#SystemKeepFree=
#SystemMaxFileSize=
#SystemMaxFiles=100
#RuntimeMaxUse=
#RuntimeKeepFree=
#RuntimeMaxFileSize=
#RuntimeMaxFiles=100
#MaxRetentionSec=
#MaxFileSec=1month
#ForwardToSyslog=yes
#ForwardToKMsg=no
#ForwardToConsole=no
#ForwardToWall=yes
#TTYPath=/dev/console
#MaxLevelStore=debug
#MaxLevelSyslog=debug
#MaxLevelKMsg=notice
#MaxLevelConsole=info
#MaxLevelWall=emerg
#LineMax=48K
RateLimitIntervalSec=0
RateLimitBurst=0
RateLimitIntervalSec=0
RateLimitBurst=0
RateLimitIntervalSec=0
RateLimitBurst=0
RateLimitIntervalSec=0
RateLimitBurst=0
I tried to use the parameter backrefs: yes as suggested in above mentioned articles, but it performs idempotency every time, even when there is no any uncommented line.
Do you guys have any suggestions?
I'm using ansible 2.9.0
I'd suggest an alternative approach - use the ini_file module, as settings in journald.conf are INI style key=value (with a section as well). This will simplify the task required and be idempotent as well.
Example:
- name: set cpu affinity settings in systemd
ini_file:
path: /etc/systemd/journald.conf
section: Journal
option: "{{ item.key }}"
value: "{{ item.value }}"
no_extra_spaces: yes
with_dict:
RateLimitIntervalSec: 0
RateLimitBurst: 0
Note: If you want a reference of settings prior to change, add backup: yes to the task.

Add Conditionals to Ansible Role for Idempotent Tasks Runs

I've been searching for quite some time and tried many variants and similar answers without success. Hopefully this is something simple I am missing.
Ansible 2.9.6
I am creating many playbooks that all share a large set of custom Roles for my clients. I want to keep all logic out of the playbooks, and place that logic in the roles themselves to allow maximum re-usability. Basically, I just want to add boilerplate playbooks for simple "Role" runs via tags, with some vars overrides here and there.
My problem is that I can't seem to make some roles idempotent by using conditions - the conditionals don't work. The error I get is:
fatal: [localhost]: FAILED! => {"msg": "The conditional check 'homebrew_base.rc != 0' failed.
The error was: error while evaluating conditional (homebrew_bash.rc != 0): 'homebrew_bash' is undefined
The error appears to be in '/Users/eric/code/client1/provisioning/roles/bash/tasks/main.yaml': line 12, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- name: Change shell (chsh) for macOS to homebrew managed version
^ here
"}
Below is the boilerplate code for my playbooks:
# ./playbook.yaml
---
- name: Provisioning
hosts: localhost
connection: local
pre_tasks:
- include_vars: "{{ item }}"
with_fileglob:
- "{{ playbook_dir }}/vars/global.yaml"
tags: always
tasks:
- name: Use homebrew bash binary
include_role:
name: bash
tags: bash
The above is truncated quite a bit, but only thing missing are additional var files and a whole bunch of include_roles.
Below is my role file in the entirely though. They are largely untested because of the error I keep getting.
# ./roles/bash/tasks/main.yaml
---
- name: Check /etc/shells contains "/usr/local/bin/bash"
command: grep -Fxq "/usr/local/bin/bash" /etc/shells
register: homebrew_bash
ignore_errors: True
changed_when:
- homebrew_bash.rc != 0
- name: Check that homebrew installed /usr/local/bin/bash
stat:
path: /usr/local/bin/bash
register: homebrew_bash_binary
- name: Change shell (chsh) for macOS to homebrew managed versoin
tags: bash, chsh
shell: chsh -s /usr/local/bin/bash
become: yes
when:
- homebrew_bash.rc != 0
- homebrew_bash_binary.stat.exists = True
Ps: I do plan on abstracting those hardcoded paths into roles/bash/defaults/. But I need it working first before that.
Ps2: If there is a better way to use a contains filter (instead of the grep hack), I'm all ears.
I've tried:
making separate tasks/chsh.yaml, and using the include: command to call that task within the role. When I do this, I get an odd error telling me the variable is undefined - in the tasks/chsh.yaml - even though I am checking for the variable in tasks/main.yaml! That doesn't seem right.
using quotes in various places in the conditions
commenting out each condition: both give the same error, just differenet names.
Again, I am trying to keep this logic in the roles only - not in the playbook.
Thanks!
Figured it out. I was missing the "tags" on the conditionals!
# ./roles/bash/tasks/main.yaml
---
- name: Check /etc/shells contains "/usr/local/bin/bash"
tags: bash, chsh
command: grep -Fxq "/usr/local/bin/bash" /etc/shells
register: homebrew_bash
ignore_errors: True
changed_when:
- homebrew_bash.rc != 0
- name: Check that homebrew installed /usr/local/bin/bash
tags: bash, chsh
stat:
path: /usr/local/bin/bash
register: homebrew_bash_binary
- name: Change shell (chsh) for macOS to homebrew managed versoin
tags: bash, chsh
shell: chsh -s /usr/local/bin/bash
become: yes
when:
- homebrew_bash.rc != 0
- homebrew_bash_binary.stat.exists = True

handle all the questions that is asked during Ubuntu upgrade through ansible?

I am working on upgrading my machines to Ubuntu 16 from Ubuntu 14. I have around 200+ machines. I wanted to do this through ansible so that I can automate it instead of doing it manually. When I run below command for the upgrade, it asked me some questions on the screen and I always chose default answers for those and it worked for me.
Most of the time there were some questions on the pop up screen and I answered default for those and sometimes there were questions on the command line which I answered to default 'N' when running the upgrade.
do-release-upgrade
So now if I want to do this through ansible how can we deal with those questions that pops up during the upgrade? I always want to chose default answer for those questions but is there any way to deal with this through ansible? Below is what I got but not sure how to deal with those questions.
---
- hosts: upgrade_test
serial: "{{ num_serial }}"
tasks:
# Use a block to perform tasks conditionally—only if running Ubuntu 14.04.
- block:
- debug:
msg: 'This server is running Ubuntu 14.04 LTS and will be upgraded to 16.04 LTS.'
- name: Run do-release-upgrade.
command: do-release-upgrade
# Note: In Ansible 2.7+, use the reboot module instead.
- name: Reboot the server.
command: reboot
async: 0
poll: 0
- name: Wait for server to reboot.
wait_for:
host: "{{ ansible_ssh_host }}"
port: 22
state: started
connection: local
become: no
when: ansible_distribution == 'Ubuntu' and ansible_distribution_version == '14.04'
You have two options here:
Either you script all the questions with the expect module
- name: Run do-release-upgrade.
expect:
command: do-release-upgrade
responses:
Question:
- N
- N
- N
- ...
It would maybe be the safest solution because you really control what happen and you can even regex script that the question are what you expect with the second form of the module
- name: Run do-release-upgrade.
expect:
command: do-release-upgrade
responses:
Configuration file '/etc/pam.d/login'.*: N
Still, that forces you to either script all the question or be one hundred percent sure you always have the same amount of questions.
Or you can use the utility command yes
Which answers y per default but could answer whatever you want, passed as argument
Name
yes - output a string repeatedly until killed
Synopsis
yes [STRING]...
yes OPTION
Description
Repeatedly output a line with all specified STRING(s), or 'y'.
Source: https://linux.die.net/man/1/yes
Default usage of yes
$ yes
y
y
y
y
^C
Usage of yes with argument
$ yes N
N
N
N
N
^C
So, with that, you can change you task to
- name: Run do-release-upgrade.
shell: yes N | do-release-upgrade
Note: you need to change from the command module to the shell module, because the command module does not accept the usage of pipes (|)
The command(s) will not be processed through the shell, so variables like $HOME and operations like "<", ">", "|", ";" and "&" will not work. Use the shell module if you need these features.
Source: https://docs.ansible.com/ansible/latest/modules/command_module.html

ANSIBLE - Answer yes no to questions

I am working on a playbook that will upgrade an app I have. Part of that upgrade means that I get asked a question.
Ideally I would like to be able to answer yes / no to these questions and not have to do any sort of pause or prompt for user input.
From what I have read online it is definitely possible but I cannot seem to get it to work for me.
Here is my code:
- name: Upgrade
expect:
command: /tmp/bin/update_script.sh
environment:
JAVA_HOME: /opt/java/
responses:
Question:
- Do you want to use the standard cipher suites [N]: n
You are mixing your response types. If you provide a list, it doesn't want/need the question. If you want to specifically match questions to responses, use a regex to match the question as the key of a dictionary.
So, version 1:
- name: Upgrade
expect:
command: /tmp/bin/update_script.sh
environment:
JAVA_HOME: /opt/java/
responses:
Question:
- n
or version 2 (check my regex*):
- name: Upgrade
expect:
command: /tmp/bin/update_script.sh
environment:
JAVA_HOME: /opt/java/
responses:
Question:
cipher: n
I have used expect, but not the ansible module, and it was years ago, and I never used it much. Hopefully this will work as-is either way, but be prepared to twiddle with it. ;)
Good luck!
This done what i needed .. Unfortunately it does not allow you answer yes / no but it gets the job done for now.
It would have been nice to have more control but its works as a temp workaround for now .. When I have a permanent solution I will post again.
- name: Upgrade Starting
shell: |
yes | /tmp/bin/update_script.sh
You should just pass the answer to the question as a variable using extra_variables. This will avoid the prompt and allow you to set the variable to some default value as well.
https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#passing-variables-on-the-command-line
ansible-playbook playbooks/release.yml --extra-vars 'updateciphers=True updatedcipherslist=["cipher1","cipher2","cipher3"]'
-> You may need to escape the double quotes.
vars:
updateciphers: False
tasks:
- name: Upgrade
command: /tmp/bin/update_script.sh
environment:
JAVA_HOME: /opt/java/
when: updateciphers
with_items: "{{ updatedcipherslist }}"

How to extend Ansible's setup module to gather more information?

On Solaris Ansible's setup module does not gather information about installed zones. How to extend the setup module to gather the output of zoneadm list -iv?
create a script named /etc/ansible/facts.d/zoneadm.fact and gather whatever information you need there. This can be whatever you want (bash/python/etc).
When you are done, echo it to stdout as json format.
Deploy that script via ansible, make it executable
Gather facts and notice that the new facts are present under ansible_local.zoneadm
More infos can be found here
I was dealing with the same dilemma ~2y ago. Previous reply is correct, but you have to deploy some stuff before and the rerun Ansible to get new facts too.
There's an option to write your own Ansible module, which returns JSON looking like:
{
'changed': false,
'ansible_facts': {
'sunos': {
'zonename': 'global',
'zones': {},
}
}
Facts returned by such module are then merged to ones coming from setup module. To be more portable, best to include such module into Ansible role and include just one task calling this module, plus add tag always, to get this fact collection run even when you choose subset of tasks by specifying tags on cmdline.
I've got my old role pushed here on GitHub. Probably will not work out-of-the-box... was used with Ansible 1.0, but get inspired.
The reason for my misunderstanding was, that I thought, I have to put something on the Ansible machine, which gets automatically deployed to the target system as it is done with the modules. But facts gathering works differently! One has to take cake, that the fatcs gathering scripts are already on the target system, before the setup starts working. I would say this is a design error in Ansible or at least a still not implemented feature.
To add this missing functionality, it is necessary to write a play, which works before all other things. I came up with the following solution:
---
- name: facts deployment
gather_facts: false
hosts: all
become: true
tasks:
- set_fact: setup_necessary=false
- file:
path: "/etc/ansible/facts.d"
state: directory
recurse: yes
register: facts_directory
- set_fact: setup_necessary=true
when: facts_directory.changed
- name: solaris facts
gather_facts: false
hosts: solaris
become: true
tasks:
- include: deploy_fact.yml
with_items:
- { shell: bash, file: nonglobal_zones }
- { shell: bash, file: solaris_eeprom }
- name: setup after facts update
gather_facts: false
hosts: all
tasks:
- setup:
when: setup_necessary
The above playbook does all plays with gather_facts: false, to prevent any setup run before the facts have been deployed. All plays set the variable setup_necessary, when any change to the target system has been made. It is not possible to use a handler for this, because handlers run at the end of a play, but not at the end of a playbook or after some plays (Ansible limitation 1).
First the directory gets created and after that all facts files are deployed. It is necessary to put the body of the look into a separate task file, because it is not possible to group two tasks together in a playbook (Ansible limitation 2).
The contents of the deploy_fact.yml file uses the template module to transfer the facts scripts to the target system.
---
- name: "/etc/ansible/facts.d/{{item.file}}.fact"
template:
src: "{{inventory_dir}}/facts.d/{{item.shell}}.j2"
dest: "/etc/ansible/facts.d/{{item.file}}.fact"
mode: 0755
register: facts_file
- set_fact: setup_necessary=true
when: facts_file.changed
The reason why I use the template module is, that every facts script needs some kind of error handling which takes care that proper JSON gets created in case of an error. This is my current error handling but it can be still improved. Some people also don't like set -eu, which is somehow a matter of taste.
#! /bin/bash
{% include item.file + '.bash' %}
set -eu
_stderr=$(mktemp)
trap 'rm -f "$_stderr"' EXIT
if _stdout=$(main 2>$_stderr); then
if [ "$_stdout" ]; then
echo "$_stdout"
else
echo null
fi
else
jq -Rsc "{\"ERROR\":{\"failed\":true,\"exit\":$?,\"msg\":.}}" $_stderr
fi
The template does nothing more than just including the file passed by the loop via the implicit item variable. The wrapper expects from the included file, that a main functions gets defined. This is my main function for non-global zones:
main ()
{
zoneadm list -i |
grep -v global |
jq -Rc . |
jq -sc .
}
And this one collects the Solaris eeprom data:
main ()
{
eeprom |
sed 's/^\([^=]*\)=\(.*\)$/{"\1":"\2"}/' |
sed 's/^\(.*\): data not available.$/{"\1":null}/' |
sed 's/:"false"}$/:false}/g' |
sed 's/:"true"}$/:true}/g' |
sed 's/:"\([0-9][0-9]*\)"}$/:\1}/' |
sed '/^{"boot-device"/{s/":"/":["/;s/ /","/g;s/"}$/"]}/;}' |
jq -sc add
}
My bottom line is that it is somehow possible to extend Ansible's facts gathering but it is far from obvious and it is a bit painful, because it makes any ad hoc usage of the setup module impossible. Instead of requiring the user to implement the above stuff, Ansible should move all of the above into the setup module (Ansible limitation 3).

Resources