Ansible script is executing but loop is failing - ansible

I am trying to write a script (adduser.yml) to create users in a linux machine which imports users list from userlist.csv file. When I executed adduser.yml, the loop failed creating only 1st user. Can someone help me understand what mistake I am doing and how to correct it?
userlist.csv:
id,username,password,sudo
1,ansible1,ansible#123,yes
2,ansible2,ansible#123,no
3,ansible3,ansible#123,yes
4,ansible4,ansible#123,yes
adduser.yml:
---
## executed but until loop failed; check
- hosts: 192.168.0.3
vars:
count: "{{COUNT}}"
x: "1"
uname: "{{ lookup('csvfile', '{{x}} file=userlist.csv delimiter=, col=1') }}"
password: "{{ lookup('csvfile', '{{x}} file=userlist.csv delimiter=, col=2') }}"
sudo: "{{ lookup('csvfile', '{{x}} file=userlist.csv delimiter=, col=3') }}"
tasks:
name: "user add"
action:
- user:
x: "x+=1"
name: "{{ uname }}"
password: "{{ password }}"
state: present
register: x
until: "{{x > count}}"
name: "add to sudoers"
when: sudo == "yes"
lineinfile:
dest: /etc/sudoers

There are quite some things that will not work as you expected them. First thing, your loop is defined for your first task. That means only the user task will be repeated. Neither the sudo task nor the vars definition at the top will be looped. But don't try to re-define your loop, this is not going to work with vars.
Ansible has no build-in way to read vars from a csv file other than the csv lookup which will read exactly one line. But as said, you can not combine that with a loop.
I see two options you have:
Do not use csv. Ansible is mostly bound to yaml. If you'd had a yaml definition of your users, you simply could use the include_vars module to load these vars.
If you are bound to csv, you could try to use this includecsv module. (I have no experience with it and can not tell if it actually works)
Now, let's assume you have loaded your users into a list either from yaml or from csv with mentioned module. Then you'd simply loop with with_items:
tasks:
- name: "user add"
user:
name: "{{ item['uname'] }}"
password: "{{ item['password'] }}"
state: present
with_items: users_you_loaded
- name: "add to sudoers"
when: "{{ item['sudo'] }} == 'yes'"
...
with_items: users_you_loaded

Related

Ansible trim string

I'm trying write role to install MySql 8, and get problem with this:
- name: Extract root password from logs into {{ mysql_root_old_password }} variable
ansible.builtin.slurp:
src: "{{ mysql_logfile_path }}"
register: mysql_root_old_password
#when: "'mysql' in ansible_facts.packages"
- name: Extract root password from logs into {{ mysql_root_old_password }} variable
set_fact:
mysql_root_old_password: "{{ mysql_root_old_password.content | b64decode | regex_findall('generated for root#localhost: (.*)$', 'multiline=true') }}"
#when: "'mysqld' in ansible_facts.packages"
- name: Get Server template
ansible.builtin.template:
src: "{{ item.name }}.j2"
dest: "{{ item.path }}"
loop:
- { name: "my.cnf", path: "/root/.my.cnf" }
notify:
- Restart mysqld
on the .my.cnf I get password with quotes and brackets:
[client]
user=root
password=['th6k(gZeJSt4']
How to trim that?
What I try:
- name: trim password
set_fact:
mysql_root_old_password2: "{{ mysql_root_old_password | regex_findall('[a-zA-Z0-9,()!##$%^&*]{12}')}}"
Thanks.
The result of regex_findall is a list because there might be more matches. Take the last item
- set_fact:
mysql_root_old_password: "{{ mysql_root_old_password.content|
b64decode|
regex_findall('generated for root#localhost: (.*)$', 'multiline=true')|
last }}"
From your description
on the .my.cnf I get password with quotes and brackets ... How to trim that
I understand that you like to read a INI file like my.cnf.ini
[client]
user=root
password=['A1234567890B']
where the value of the key password looks like a list with one element in YAML and the structure doesn't change, but you are interested in the value without leading and trailing square brackets and single quotes only.
To do so there are several possibilities.
Via Ansible Lookup plugins
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Extract root password from INI file
debug:
msg: "{{ lookup('ini', 'password section=client file=my.cnf.ini') }}"
register: result
- name: Show result with type
debug:
msg:
- "{{ result.msg }}"
- "result.msg is of type {{ result.msg | type_debug }}"
- "Show password only {{ result.msg[0] }}" # the first list element
Such approach will work on the Control Node.
Like all templating, lookups execute and are evaluated on the Ansible control machine.
Further Q&A
How to read a line from a file into an Ansible variable
What is the difference between .ini and .conf?
Further Documentation
ini lookup – read data from an INI file
Via Ansible shell module, sed and cut.
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Extract root password from INI file
shell:
cmd: echo $(sed -n 's/^password=//p' my.cnf.ini | cut -d "'" -f 2)
register: result
- name: Show result
debug:
msg: "{{ result.stdout }}"
Please take note regarding "I get password with quotes and brackets ... ['<pass>'] ... How to trim that?" that from perspective of the SQL service, .cnf file, [' and '] are actually part of the password!
- name: Server template
template:
src: "my.cnf.ini"
dest: "/root/.my.cnf"
If that is correct they will be necessary for proper function of the service.

Ansible nested variable in playbook

I'm trying to use getent to lookup a user's home directory and then apply that information to a copy job. I know we can't do nested variables, but I'm just stuck. I have been playing with the lookup('var's...) syntax and various ways to pull a value out of a dict. In this particular case I do know the user's home directory, but now it's more an exercise in figuring this out. ANSIBLE_USER is defined as ansible
My playbook is:
#lookup ansible user's home directory
#drops the value into a getent_passwd variable
- hosts: all
become_user: root
become: true
tasks:
- name: get info
getent:
key: "{{ ANSIBLE_USER }}"
database: passwd
- debug:
var: getent_passwd.{{ ANSIBLE_USER }}.4
- set_fact:
ANSIBLE_HOME: "{{ getent_passwd['ansible'][4] }}"
- hosts: all
become_user: root
become: true
tasks:
- name: copy iptables files
copy:
src: "iptables/{{ IPTABLESCONFSRC }}/iptables.sh"
dest: "{{ ANSIBLE_HOME }}/temp/iptables.sh"
This works because I'm manually defining the 'ansible' string in the ANSIBLE_HOME line. However, what I'm functionally trying to accomplish is:
ANSIBLE_HOME: "{{ getent_passwd['{{ ANSIBLE_HOME }}'][4] }}"
The best I can get is a undefined variable error because I end up looking for: getent_passwd[ansible][4] or getent_passwd.ansible.4 and that doesn't exist via:
ANSIBLE_HOME: "{{ lookup('vars', 'getent_passwd.' + ANSIBLE_USER + '.4') }}"
or
ANSIBLE_HOME: "{{ lookup('vars', 'getent_passwd[' + ANSIBLE_USER + '][4]') }}"
The debug output shows:
ok: [HOSTNAME] => {
"getent_passwd.ansible.4": "/home/ansible"
}
This seems to work because the debug var is already considered Jinja, so it's effectively double nesting for you.
Oh geeze. Guess this was easier then what I was thinking. This:
ANSIBLE_HOME: "{{ getent_passwd[ANSIBLE_USER][4] }}"
works fine.

Iterate with Ansible with_dict over list of a dictionaries

I am stuck in iterating over the list of a dictionary. Sample vars.yml and the minimal playbook is bellow.
---
- hosts: localhost
connection: local
gather_facts: false
become: false
vars:
csvfile: "{{ lookup('file', 'vars/users.csv') }}"
tasks:
- name: Convert CSV to YAML
template:
src: "./users_csv.j2"
dest: "vars/users.yml"
run_once: true
- name: Include users from users.yml to users variable
include_vars:
file: vars/users.yml
name: users
- debug:
msg: "{{ users.value }}"
with_dict:
- "{{ users }}"
My Jinja2 template produces a list of dictionaries in YAML format as below:
--
users:
- username: Auser1
group: Admin
- username: Auser2
group: Admin
- username: Auser3
group: User
Anyhow, when I am iterating the dictionary, I am not able to get for example a username or group.
Most far I got is getting a fatal error message saying:
fatal: [localhost]: FAILED! => {"msg": "with_dict expects a dict"}
I know how to iterate over the list, but I don't have an idea why it fails here.
The users is not a dictionary, its a list variable of dictionaries.
if you want to parse this variable in a loop, you can use:
- debug:
msg: "username: {{ item.username }}, group: {{ item.group }}"
with_items:
- "{{ users.users }}"
hope it helps
UPDATE
i noticed now that when including the var file, you pass the name: users instruction as well. this cause all the variables of the file to be placed under the users variable. So to refer to the users list which is defined in the variable file, you need to use users.users.
updated the with_items to:
with_items:
- "{{ users.users }}"

Ansible recursive checks in playbooks

We need to go through this structure
Zone spec
https://gist.github.com/git001/9230f041aaa34d22ec82eb17d444550c
I was able to run the following snipplet but now I'm stucked at the error checking.
playbook
--
- hosts: all
gather_facts: no
vars_files:
- "../doc/application-zone-spec.yml"
roles:
- { role: ingress_add, customers: "{{ application_zone_spec }}" }
role
- name: check if router exists
shell: "oc get dc -n default {{ customers.zone_name }}-{{ item.type }}"
with_items: "{{ customers.ingress }}"
ignore_errors: True
register: check_router
- name: Print ingress hostnames
debug: var=check_router
- name: create new router
shell: "echo 'I will create a router'"
with_items: "{{ customers.ingress }}"
when: check_router.rc == 1
Output of a ansible run
https://gist.github.com/git001/dab97d7d12a53edfcf2a69647ad543b7
The problem is that I need to go through the ingress items and I need to map the error of the differnt types from the "check_router" register.
It would be nice to make something like.
Pseudo code.
Iterate through the "customers.ingress"
check in "check_router" if the rc is ! 0
execute command.
We use.
ansible-playbook --version
ansible-playbook 2.1.0.0
config file = /etc/ansible/ansible.cfg
configured module search path = Default w/o overrides
You can replace the second loop with:
- name: create new router
shell: "echo 'I will create a router with type {{ item.item }}'"
with_items: "{{ check_router.results }}"
when: item.rc == 1
This will iterate over every step of check_route loop and you can access original items via item.item.

Conditionally define variable in Ansible

I want to conditionally define a variable in an Ansible playbook like this:
my_var: "{{ 'foo' if my_condition}}"
I would like the variable to remain undefined if the condition does not resolve to true.
Ansible gives the following error if I try to execute the code:
fatal: [foo.local] => {'msg': 'AnsibleUndefinedVariable: One or more undefined
variables: the inline if-expression on line 1 evaluated
to false and no else section was defined.', 'failed': True}
Why is this an error anyway?
The complete case looks like this:
{role: foo, my_var: "foo"}
If my_var is defined, the role does something special. In some cases, I don't want the role to do this. I could use when: condition, but then I would have to copy the whole role block. I could also use an extra bool variable, but I would like a solution without having to change the "interface" to the role.
Any ideas?
You could use something like this:
my_var: "{{ 'foo' if my_condition else '' }}"
The 'else' will happen if condition not match, and in this case will set a empty value for the variable. I think this is a short, readable and elegant solution.
This code may help you to define a variable with condition.
- hosts: node1
gather_facts: yes
tasks:
- name: Check File
shell: ls -ld /etc/postfix/post-install
register: result
ignore_errors: yes
- name: Define Variable
set_fact:
exists: "{{ result.stdout }}"
when: result|success
- name: Display Variable
debug: msg="{{ exists }}"
ignore_errors: yes
So here the exists will display only if the condition is true.
My example, after https://stackoverflow.com/a/43403229/5025060:
vars:
sudoGroup: "{{ 'sudo' if ansible_distribution == 'Ubuntu' else 'wheel' }}"
Because of the different sudo conventions used by Ubuntu versus other platforms, here I am telling Ansible to set a variable named sudoGroup to sudo if the platform is Ubuntu, otherwise set it to wheel.
Later in my playbook, I combine the variable with Ansible's user module to add either sudo or wheel to an account's secondary groups depending on the OS Ansible is running on:
- name: Add or update bob account
user:
name: bob
uid: 3205
groups: "{{ sudoGroup }}"
append: yes
NOTES:
Double quotes around the {{ variable }} are required in the user: groups: definition above.
Once I define sudoGroup as above in my playbook's global vars: section, Ansible configures it at run time (based on ansible_distribution) for each target I define in my hosts: section.
I believe you're after the default(omit) filter. (Reference).
As per the example, mode will behave like it wasn't set at all for the first two items in the loop.
- name: touch files with an optional mode
file:
dest: "{{item.path}}"
state: touch
mode: "{{item.mode|default(omit)}}"
loop:
- path: /tmp/foo
- path: /tmp/bar
- path: /tmp/baz
mode: "0444"
This can be set as with bool:
- name: Conditional (true and false)
set_fact:
my_boolean_set_to_be: "{{ 'true' if my_var == 'foo' else 'false' }}"
- name: Display Variable
debug: msg="{{ my_boolean_set_to_be }}"
This can be set as for more conditionals like 'if-ifelse-else' statements:
- name: Conditional for 'my_var' (2 options and one default)
set_fact:
my_var_set_to_be: "{{ 'breakfast' if my_var == 'morning' else 'lunch' if my_var == 'afternoon' else 'dinner' }}"
- name: Display Variable
debug: msg="{{ my_var_set_to_be }}"

Resources