How to iterate over a list in a condition - ansible

I have two hosts: One in production, another one in test.
test and prod are defined in a fact file available on target hosts (nodes).
[node]
type= prod
or
[node]
type= test
I have the following variables defined:
users:
- username: A
password: password_A
update_password: always
home: /home/A
state: present
nodes: ['test', 'prod']
- username: B
password: passwd_B
update_password: always
home: /home/B
state: present
nodes: ['test']
My A user shall be installed on production and test hosts.
B user only on test host.
Hereafter a role that works fine if i use a single value for nodes definition.
- name: create users
ansible.builtin.user:
name: "{{ item.username }}"
password: "{{ item.password }}"
uid: "{{ item.uid }}"
home: "{{ item.home }}"
create_home: yes
group : "{{ item.group }}"
shell: /bin/bash
state: present
expires: -1
with_items:
- "{{ users }}"
when: item.nodes == ansible_local['myfact']['node"']['type']
I don't know how to loop on each value of the item.nodes list and compare them with the local fact value. item.nodes[0], item.nodes[1], ... I might have other type of host, not only prod and test.
I tried subelements without success.

You don't need to iterate anything in your condition, you can assert that an element is in a list with the in test.
So your condition needs to be
when: ansible_local.myfact.node.type in item.nodes

Q: "A user shall be installed on production and test hosts.
B user only on test host."
A: Condition is not needed. Use filter selectattr and test if a list contains a value. For example, given the inventory for testing
shell> cat hosts
prod type=prod
test type=test
The task
shell> cat pb.yml
- hosts: all
tasks:
- debug:
msg: "Create user {{ item.username }}"
loop: "{{ users|selectattr('nodes', 'contains', type) }}"
loop_control:
label: "{{ item.username }}"
iterates selected users only
TASK [debug] ******************************************************
ok: [prod] => (item=A) =>
msg: Create user A
ok: [test] => (item=A) =>
msg: Create user A
ok: [test] => (item=B) =>
msg: Create user B

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)

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.

How to use the lookup plugin to get the directory path and file in Ansible

I have a two playbooks where one creates SSH Keys and the other one creates a new user and deploys the public ssh key for the new user created.
My issue is I created a task that create a new directory with a timestamp to store the relevant data, I was able to get the path to a variable where I added it as a dummy host so that I can be able to call that path with all my plays but it seems like I am unable to use the same variable in lookup so that I can be able to deploy the ssh key. Kindly assist, below are the relevant tasks.
# Create the directory with timestamp
- name: Create Directory with timestamp to store data that was run multiple times that day
when: inventory_hostname in groups['local']
file:
path: "{{store_files_path}}/{{ansible_date_time.date}}/{{ansible_date_time.time}}"
state: directory
mode: "0755"
register: dir_path
# Add the directory path to dummy host called save so that I can call it from other plays
- name: Add dir path:"{{dir_path.path}}" as a 'save' host
when: inventory_hostname in groups['local']
add_host:
name: "save"
dir: "{{dir_path.path}}"
# Deploying SSH Key I tried this -->
- name: Deploy Public Key to the server
when: inventory_hostname in groups['Servers']
authorized_key:
user: "{{hostvars['new-user']['user']}}"
state: present
key: "{{dir_path.path}}/SSH-Key.pub"
# ...this -->
- name: Deploy Public Key to the server
when: inventory_hostname in groups['Servers']
authorized_key:
user: "{{hostvars['new-user']['user']}}"
state: present
key: "{{ lookup('file','{{dir_path.path}}/SSH-Key.pub') }}"
# .... and this -->
- name: Deploy Public Key to the server
when: inventory_hostname in groups['Servers']
authorized_key:
user: "{{hostvars['new-user']['user']}}"
state: present
key: "{{ lookup('file','{{hostvars['save']['dir']}}/SSH-Key.pub') }}"
None of them worked, what am I doing wrong?
If you put a Jinja expression into a string in a Jinja expression, then you indeed end up with a your variable not being interpreted.
A basic example of this is:
- hosts: all
gather_facts: no
tasks:
- debug:
msg: "{{ '{{ foo }}' }}"
vars:
foo: bar
Which gives:
ok: [localhost] => {
"msg": "{{ foo }}"
}
When
- hosts: all
gather_facts: no
tasks:
- debug:
msg: "{{ foo }}"
vars:
foo: bar
Gives thes expected:
ok: [localhost] => {
"msg": "bar"
}
So in order to achieve what you want here, you should use the concatenation operator of Jinja: ~, in order to let Jinja interpret your variable and concatenate it with the rest of your "hardcoded" string.
Effectively ending with the instruction:
key: "{{ lookup('file', hostvars['save']['dir'] ~ '/SSH-Key.pub') }}"

Using Ansible loop to create multiple users: Undefined error

I am using the following ansible code to create multiple unix user accounts
---
- hosts: test
become: true
tasks:
- name: more complex items to add several users
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
groups: "{{ item.groups }}"
state: present
with_items: "{{ user_details }}"
I am storing the user information by using a separate a variable file as below
`cat /etc/ansible/vars.yml
---
user_details:
- { name: testuser1, uid: 1002, groups: "admin, logs" }
- { name: testuser2, uid: 1003, groups: logs: }`
To execute above playbook , I tried with both the commands below
sudo ansible-playbook /etc/ansible/userloop.yml -e /etc/ansible/vars.yml
sudo ansible-playbook /etc/ansible/userloop.yml
but both commands are failing with below error
fatal: [host-003]: FAILED! => {"msg": "'user_details' is undefined"}
fatal: [host-004]: FAILED! => {"msg": "'user_details' is undefined"}
How to resolve the issue ? I want to maintain a separate variable file to store the user information rather then putting them in the same playbook file .
You can also refer the multiple variable files in playbooks like below
- hosts: all
become: true
vars_files:
- /etc/ansible/vars.yml
tasks:
- name: more complex items to add several users
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
groups: "{{ item.groups }}"
state: present
with_items: "{{ user_details }}"
The type of variables is in the column "Parameter" of the module user. Try the structure of the data below
user_details:
- {name: 'testuser1', uid: 1002, groups: ['admin', 'logs']}
- {name: 'testuser2', uid: 1003, groups: ['logs']}
You are missing # while passing the vars.yml. Hence, the ansible is not reading the file. Try the below command. It works for me.
sudo ansible-playbook /etc/ansible/userloop.yml -e #/etc/ansible/vars.yml

Ansible check if inventory_hostname is in list

I am trying to check if inventory_hostname is in a list into an imported variable.
vars/users.yml file:
---
users:
- username: user1
comment: "User 1"
group: admin
password: "sha password"
keys:
active:
- "ssh-rsa etc"
admin: yes
- username: user2
comment: "User 2"
group: users
groups: deployer
keys:
active:
- "ssh-rsa etc"
hosts:
user:
- host1
- host2
deployer:
- host3
I want to execute a task only if inventory_hostname is into any of hosts lists (user, deployer, others ...).
I tried this:
- name: Create users
user:
name: "{{ item.username }}"
comment: "{{ item.comment | default('User {{item.username}}') }}"
password: "{{ item.password | default('!') }}"
state: "{{ item.state | default('present') }}"
shell: "{{ item.shell | default('/bin/bash') }}"
group: "{{ item.group | default('users') }}"
with_items: '{{ users }}'
when: item.username is defined and ((item.admin is defined and item.admin == True) or (item.hosts is defined and item.hosts.user is defined and inventory_hostname in item.hosts.user)
It works for user1 (which have admin enabled) but not for user2 (if this play is executed on host1 which is included into user2's hosts.user list).
Well .. I tried your code snippet and it works well for both users. Only thing which can make it fail is that hostnames in item.host.user are not matching the inventory_hostname. You can try to debug the inventory_hostname before this task to see what are the inventory hostnames read by ansible and whether you have specified them correctly in item.host.user list.
- debug: var=inventory_hostname

Resources