Ansible when condition in loop - ansible

I'm trying to setup an role for rolling out users. I have a list of users with their variables and I would like to only roll out authorized_key when the variable pubkey is set. Here is my code:
provisioning_user:
- name: ansible
state: present
pubkeys:
- 'ssh-rsa peter-key-1 peter#key1'
- 'ssh-rsa peter-key-3 peter#key3'
root: true
# removes directorys associated with the user
remove: true
create_home: true
comment: Deploy user for ansible
# If set to true when used with home: , attempt to move the user’s old home
# directory to the specified directory if it isn’t there already and the
# old home exists.
#non_unique: false
#uid: 11
#group:
groups: admin, developer
# If false, user will only be added to the groups specified in groups,
# removing them from all other groups.
append: yes
password: '!'
#ssh_public_keyfiles: ['ansible.pub', 'patrick.pub']
#key: ssh-ed25519 AAAAC3NzetfqeafaC1lZDI1NTE5AAAAIHu28wqv0r4aqoK1obosoLCBP0vqZj8MIlkvpAbXv0LL
#key_options:
#key_comment:
key_exclusive: true
key_manage_dir: true
- name: testuser2
state: present
# removes directorys associated with the user
#remove: false
create_home: yes
comment: Deploy user for ansible
As you see, the second user has no attribute pubkeys. Here is my Ansible code:
- name: test key
authorized_key:
user: "{{ item.name }}"
key: "{{ '\n'.join(provisioning_user|map(attribute='pubkeys')|flatten) }}"
comment: "{{ item.key_comment | default('managed by ansible') }}"
state: "{{ item.state | default('true') }}"
exclusive: "{{ item.key_exclusive | default('true') }}"
key_options: "{{ item.key_options | default(omit) }}"
manage_dir: "{{ item.manage_dir | default('true') }}"
loop: "{{ provisioning_user }}"
when: item.pubkeys is defined
Thats what Ansible says:
fatal: [cloud.xxx.xxx]: FAILED! =>
msg: |-
The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'pubkeys'
The error appears to be in '/home/xxx/gitlab.com/xxx/ansible/roles/provisioning/tasks/keys.yaml': line 2, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
---
- name: test key
^ here
Can you please help me getting the when condition work? I only want this task to run if a user has pubkeys defined, when not just skipping it.

This doesn't make any sense:
key: "{{ '\n'.join(provisioning_user|map(attribute='pubkeys')|flatten) }}"
You're looping over the contents of provisioning_user in this task; provisioning_user doesn't have a key pubkeys, rather, each individual item in that list may have a pubkeys value. So you'd want something like:
key: "{{ '\n'.join(item.pubkeys) }}"
Making the complete task look like:
- name: test key
authorized_key:
user: "{{ item.name }}"
key: "{{ '\n'.join(item.pubkeys) }}"
comment: "{{ item.key_comment | default('managed by ansible') }}"
state: "{{ item.state | default('true') }}"
exclusive: "{{ item.key_exclusive | default('true') }}"
key_options: "{{ item.key_options | default(omit) }}"
manage_dir: "{{ item.manage_dir | default('true') }}"
loop: "{{ provisioning_user }}"
when: item.pubkeys is defined
Running the above in a test environment produces:
TASK [test key] *****************************************************************************************
changed: [node0] => (item={'name': 'ansible', 'state': 'present', 'pubkeys': ['ssh-rsa peter-key-1 peter#key1', 'ssh-rsa peter-key-3 peter#key3'], 'root': True, 'remove': True, 'create_home': True, 'comment': 'Deploy user for ansible', 'groups': 'admin, developer', 'append': True, 'password': '!', 'key_exclusive': True, 'key_manage_dir': True})
skipping: [node0] => (item={'name': 'testuser2', 'state': 'present', 'create_home': True, 'comment': 'Deploy user for ansible'})
...which is I think what you want.

Related

Nested loop with user and folder in Ansible

I have the following task:
- name: Create required folders.
become: true
ansible.builtin.file:
owner: "{{ item.key }}"
group: ftp
mode: '0755'
path: '/data/{{ item.key }}/in'
state: directory
loop: "{{ query('dict', ftp) | list }}"
when: "'state' not in item.value or item.value.state == 'present'"
And the following host variables with different users:
ftp:
test:
ssh_public_key: "XXXX"
password: "XXX"
home: /data/test
test2:
ssh_public_key: "XXXX"
password: "XXX"
home: /data/test2
What I want is to create two directories for every user :
path: '/data/{{ user }}/in' # item.key, in the code above
path: '/data/{{ user }}/out' # item.key, in the code above
But I already need the loop for iterating over the users itself:
loop: "{{ query('dict', ftp) | list }}"
How can I handle this, for example, with nested loop?
Use a product filter to generate every possible combination of user/folder.
loop: "{{ ftp.keys() | product(['in', 'out']) }}"
Then, respectively,
item.0 contains the users dictionary keys
item.1 contains the folders
It is not fully clear what your condition when does actually, in order to adapt it too, but I guess that you do have an absent or present state in those use dictionaries.
So, the resulting task should be something along the lines of
- name: Create required folders
ansible.builtin.file:
owner: "{{ item.0 }}"
group: ftp
mode: '0755'
path: "/data/{{ item.0 }}/{{ item.1 }}"
state: directory
loop: "{{ ftp.keys() | product(['in', 'out']) }}"
loop_control:
label: "/data/{{ item.0 }}/{{ item.1 }}"
when: "ftp[item.0].state | default('absent') == 'present'"
become: true
Given the task above, when run on those data:
ftp:
test:
state: present
test1:
test2:
state: present
It will yield:
TASK [Create required folders] ***************************************
ok: [localhost] => (item=/data/test/in)
ok: [localhost] => (item=/data/test/out)
skipping: [localhost] => (item=/data/test1/in)
skipping: [localhost] => (item=/data/test1/out)
ok: [localhost] => (item=/data/test2/in)
ok: [localhost] => (item=/data/test2/out)
Test it first, for example
- debug:
msg: "Create /data/{{ item.0.key }}/{{ item.1 }}"
with_nested:
- "{{ ftp|dict2items }}"
- [in, out]
when: item.0.value.state|d('present') == 'present'
gives (abridged)
msg: Create /data/test/in
msg: Create /data/test/out
msg: Create /data/test2/in
msg: Create /data/test2/out
Then try to create the dictionaries
- file:
owner: "{{ item.0.key }}"
group: ftp
mode: '0755'
path: "/data/{{ item.0.key }}/{{ item.1 }}"
state: directory
with_nested:
- "{{ ftp|dict2items }}"
- [in, out]
when: item.0.value.state|d('present') == 'present'
(not tested)

Playbook cannot find "{{ item }}" from loop variable inside role

The goal is to remove one or more snapshots to specified hosts. First and second role is working.
The issue is the third role.
If name of snapshot is defined, use second role.
If no name of snapshot is defined use the third role with the names from variable "days" and loop it trough the list. The role will then remove snapshots with those names, if they exist.
My playbook:
- name: Managing Snapshot(s)
hosts: "{{ target }}"
become: false
gather_facts: no
roles:
- confirm
- { role: remove-snapshot, snapshot_state: "{{ sss | default('absent') }}", snapshot_name: "{{ ssn }}", when: ssn is defined }
- { role: remove-snapshot, snapshot_state: "{{ sss | default('absent') }}", snapshot_name: "{{ item }}", loop: "{{ days }}", when: ssn is undefined }
Command i run for third role:
ansible-playbook snapshot-remove.yml --ask-vault-pass -e target=all -e sss=absent
Task role is running:
- name: remove Snapshot
vars:
ansible_python_interpreter: /usr/bin/python3
vmware_guest_snapshot:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
datacenter: "{{ vcenter_datacenter }}"
folder: "/vm"
name: "{{ vm_name | default(inventory_hostname) }}"
validate_certs: false
state: "{{ snapshot_state | default('absent') }}"
snapshot_name: "{{ snapshot_name | default('Using_Default_Text') }}"
description: "{{ lookup('env','USER') }} {{ lookup('pipe', 'date +\"%F\"') }}"
delegate_to: localhost
Variable "days" that is inside role/remove-snapshot/vars/main.yml:
vars:
days:
- lookup('pipe', 'date +\"%F\"')
- lookup('pipe', 'date -d "-1 days" +\"%F\"')
- lookup('pipe', 'date -d "-2 days" +\"%F\"')
- lookup('pipe', 'date -d "-3 days" +\"%F\"')
Error i receive:
fatal: [HOST-MGM]: FAILED! => {
"msg": "The task includes an option with an undefined variable. The error was: {{ item }}: 'item' is undefined\n\nThe error appears to be in '/home/user/linux-patching/ansible/roles/remove-snapshot/tasks/main.yml': line 1, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: remove Snapshot\n ^ here\n"
}
I have tried using vars, include_vars and vars_files inside third role and received the same error. Creating a new role might solve the issue but i would prefer not creating one more role.
Decided to solve my problem in a different way.
Decided to add date to description of snapshot for when snapshot was created and the name of snapshot will be the default name "OS_Patching".
With this i will be able to remove snapshots created from another playbook whos purpose it to create snapshot and patch the host.
New code:
- name: Managing Snapshot(s)
hosts: "{{ target }}"
become: false
gather_facts: no
roles:
- confirm
- {
role: snapshot,
snapshot_state: "{{ sss | default('absent') }}",
snapshot_name: "{{ ssn | default('OS_Patching') }}"
}
Command:
ansible-playbook snapshot.yml --ask-vault-pass -e target=<hosts> -e sss=<state> -e ssn=<name>
They way i wanted to do was more complicated, this playbook should be easier for managing snapshots.

Ansible when condition registered from csv

I'm using csv file as ingest data for my playbooks, but im having trouble with my when condition. it's either both task will skipped or both task will be ok, my objective is if ansible see the string in when condition it will skipped for the specific instance.
here is my playbook
- name: "Read ingest file from CSV return a list"
community.general.read_csv:
path: sample.csv
register: ingest
- name: debug ingest
debug:
msg: "{{ item.AWS_ACCOUNT }}"
with_items:
- "{{ ingest.list }}"
register: account
- name: debug account
debug:
msg: "{{ account.results | map(attribute='msg') }}"
register: accountlist
- name:
become: yes
become_user: awx
delegate_to: localhost
environment: "{{ proxy_env }}"
block:
- name: "Assume role"
community.aws.sts_assume_role:
role_arn: "{{ item.ROLE_ARN }}"
role_session_name: "pm"
with_items:
- "{{ ingest.list }}"
register: assumed_role
when: "'aws-account-rnd' not in account.results | map(attribute='msg')"
here is the content of sample.csv
HOSTNAME
ENVIRONMENT
AWS_ACCOUNT
ROLE_ARN
test1
dev
aws-account-rnd
arn:aws:iam::XXXX1
test2
uat
aws-account-uat
arn:aws:iam::XXXX2
my objective is to skipped all items in the csv file with aws-acount-rnd
Your condition does not mention item so it will have the same result for all loop items.
Nothing you've shown requires the weird abuse of debug + register that you're doing, and it is in fact getting in your way.
- name: Read CSV file
community.general.read_csv:
path: sample.csv
register: ingest
- name: Assume role
community.aws.sts_assume_role:
role_arn: "{{ item.ROLE_ARN }}"
role_session_name: pm
delegate_to: localhost
become: true
become_user: awx
environment: "{{ proxy_env }}"
loop: "{{ ingest.list }}"
when: item.AWS_ACCOUNT != 'aws-account-rnd'
register: assumed_role
If you'll always only care about one match you can also do this without a loop or condition at all:
- name: Assume role
community.aws.sts_assume_role:
role_arn: "{{ ingest.list | rejectattr('AWS_ACCOUNT', '==', 'aws-account-rnd') | map(attribute='ROLE_ARN') | first }}"
role_session_name: pm
delegate_to: localhost
become: true
become_user: awx
environment: "{{ proxy_env }}"
register: assumed_role
my objective is to skipped all items in the csv file with aws-acount-rnd
The multiple debug you have with register, seems to be a long-winded approach IMHO.
A simple task to debug the Role ARN, only if the account does not match aws-acount-rnd.
- name: show ROLE_ARN when account not equals aws-account-rnd
debug:
var: item['ROLE_ARN']
loop: "{{ ingest.list }}"
when: item['AWS_ACCOUNT'] != 'aws-account-rnd'
This results in:
TASK [show ROLE_ARN when account not equals aws-account-rnd] **********************************************************************************************************************
skipping: [localhost] => (item={'HOSTNAME': 'test1', 'ENVIRONMENT': 'dev', 'AWS_ACCOUNT': 'aws-account-rnd', 'ROLE_ARN': 'arn:aws:iam:XXXX1'})
ok: [localhost] => (item={'HOSTNAME': 'test2', 'ENVIRONMENT': 'uat', 'AWS_ACCOUNT': 'aws-account-uat', 'ROLE_ARN': 'arn:aws:iam:XXXX2'}) => {
"ansible_loop_var": "item",
"item": {
"AWS_ACCOUNT": "aws-account-uat",
"ENVIRONMENT": "uat",
"HOSTNAME": "test2",
"ROLE_ARN": "arn:aws:iam:XXXX2"
},
"item['ROLE_ARN']": "arn:aws:iam:XXXX2"
}
The same logic can be used to pass the item.ROLE_ARN to community.aws.sts_assume_role task.

Ansible - skip undefined variable in dict

I`m using ipa_user module to setup users. There is variable passsword which force new password.
For some users (when var is not in dict) I would like to skip it in iteration, but it always fail.
This is snippet from my playbook. Ansible version is 2.7
task:
- name: adding ipa users
ipa_user:
name: "{{ item.value.login }}"
state: "{{ item.value.state }}"
givenname: "{{ item.value.givenname }}"
sn: "{{ item.value.surname }}"
mail: "{{ item.value.mail }}"
telephonenumber: "{{ item.value.telephonenumber }}"
title: "{{ item.value.title }}"
password: "{{ item.value.password }}" <<- to be skipped if not found
ipa_host: ipa.gdi.telekom.de
ipa_user: admin
ipa_pass: "{{ ipa_pass }}"
with_dict: "{{ipausers}}"
when: item.key in ipausers.keys()
register: output_ipa_users
Log:
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'password'\n\nThe error appears to have been in '/builds/gitlab/infra/user-management/roles/free-ipa/tasks/main.yml': line 13, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: adding ipa users\n ^ here\n"}
Note: I tried it with:
with_dict: "{{ipausers|default({})}}"
ignore_errors: yes
without success
Not sure if it'll be much help to you now but for others than stumble on this post, I ended up with something like below for a similar problem. I'm using Ansible 2.7.8.
- name: Creating user accounts...
user:
name: "{{ item.name }}"
state: "{{ item.state }}"
comment: "{{ item.comment | default(omit) }}"
group: "{{ item.groups is defined | ternary((item.groups|default([]))[0], omit) }}"
groups: "{{ item.groups | default(omit) }}"
password: "{{ item.password_hash | default(omit) }}"
uid: "{{ item.uid | default(omit) }}"
with_items: "{{ managed_users }}"
The solution is
group: "{{ item.groups is defined | ternary((item.groups|default([]))[0], omit) }}"
If groups isn't in item then Ansible will omit the group part of this tasks but jinja2 will evaluate item.groups[0] anyway. So to allow for this we have to use item.groups|default([]) so jinja2 uses an empty list when groups isn't defined instead of throwing a 'dict object' has no attribute error. The omit part is similar to the default(omit) filter where Ansible simply omits the option from the task.
Lubo's problem is a little simpler so using just default(omit) filter should work. That said as password is required so the entire task should be skipped with a conditional.
- name: adding ipa users
ipa_user:
name: "{{ item.value.login }}"
state: "{{ item.value.state }}"
givenname: "{{ item.value.givenname }}"
sn: "{{ item.value.surname }}"
mail: "{{ item.value.mail }}"
telephonenumber: "{{ item.value.telephonenumber }}"
title: "{{ item.value.title }}"
password: "{{ item.value.password | default(omit) }}" #<-- would be omitted
ipa_host: ipa.gdi.telekom.de
ipa_user: admin
ipa_pass: "{{ ipa_pass }}"
with_dict: "{{ipausers}}"
when: item.key in ipausers.keys() and item.key.password is defined #<-- second check for when password is not defined.
register: output_ipa_users
If you want to completely skip the ipa_user module execution when password is not defined, check for its presence in your when clause:
when: item.value.password | default('') | length > 0
If you want to execute the ipa_user module without specifying a password for user if it does not exists, use the omit placeholder in your module params:
password: "{{ item.value.password | default(omit) }}"
Note: your current when clause can be removed. It will always return true as you are looping over a dict and later checking if the current key in the loop is part of that dict.
There is a special omit variable to omit module parameters.
password: "{{ item.value.password|default(omit) }}"
To make a playbook or a role reusable it is a good idea to declare all parameters of a module in the task and default(omit) parameters that are not required.

`with_items` output is too verbose

I wrote an ansible task to iterate over a list of settings using with_items. Now all my settings are logged when I run ansible. It is very verbose and makes it hard to see what is happening. But, if I disable all the output with no_log, I will have no way to identify specific items when they fail.
How could the output be improved — to show only an identifier for each item?
Example task:
- authorized_key:
user: "{{ item.user }}"
key: "{{ item.key }}"
with_items: "{{ ssh_keys }}"
Example output:
TASK [sshkey-alan-sysop : ssh authorized keys] *********************************
ok: [brick] => (item={u'user': u'alan-sysop', u'key': u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAgRe16yLZa8vbzsrxUpT5MdHoEYYd/awAnEWML4g+YoUvLDKr+zwmu78ze/E1NSipoZejXpggUYRVhh8MOiCX6qpUguBDWZFlvSCE/7uXWWg7Oht0f1kDS2xU7YiycPIzMN1dmUEFY9AixnN936Dq6nOtEzgBwjo66I1YC/5jrsQEqF19shx43A4DTFlPUz/PnsqHl2ESrkIk3e8zyidaPN2pRbA5iKzdvPW4E2W2tKw9ll40vqRXzaWIF7v293Ostwi1IPi2erlC777DhjZUhZ1VGXIR7FDAfANzalrMe6c/ZysiXewiUYgMw0I8Dh1LK3QMj9Kuo35S5E0Xj3TB alan-sysop#alan-laptop'})
There's loop_control for that:
- authorized_key:
user: "{{ item.user }}"
key: "{{ item.key }}"
with_items: "{{ ssh_keys }}"
loop_control:
label: "{{ item.user }}"
The identifiers can be used as keys of a dictionary.
- authorized_key:
user: "{{ item }}"
key: "{{ ssh_keys[item] }}"
with_items: "{{ ssh_keys.keys() }}"
Example output:
TASK [sshkey-alan-sysop : ssh authorized keys] *********************************
ok: [brick] => (item=alan-sysop)

Resources