I want to create user and gather their info in local file but with loop register is now working as expected.
I thought it was an indentation problem but no luck.
My playbook
---
- hosts: localhost
tasks:
- name: Clearing Local file
local_action: shell echo "Zone,docode,doname,testuser Output" > user.csv
- hosts: app
tasks:
- name: Creating user Testuser
become: yes
user:
name: "{{ item }}"
state: present
shell: "/bin/bash"
password: "$6$mysecretsalt$qyctTVhRMS1ZSnCuzQNAM8Y7V/yqSEnyRbal0IYXSqSEVKkXF8ZmXBZoRIaN/PvzE/msq8iOJO830OOCG89va/"
update_password: always
groups: santosh
shell: id "{{item}}"
ragister: userout
loop:
- newuser1
- newuser2
- newuser3
- debug:
var=userout
which gives the following error when executed
ERROR! conflicting action statements: shell, user
The error appears to have been in '/home/santosh/ans-home/playbooks/Create_User_and_Gather_output.yml': line 12, column 7, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- name: Creating user Testuser
^ here
You cannot call several modules in one task, you need to separate each call in its own task, as reported by the error message.
I understand why you tried it: to take advantage of the same loop for several task. Unfortunately this is not possible. You could move your set of tasks to a separate file and include it in a loop if you really have to loop over a significant amount of tasks. This is not really required in your situation because:
you only have two tasks
you can compact writing of your loop by using a declared var for reuse
and most essentially because you don't need your second task
In fact, the user module will return the uid of the user it created or that is existing in its result. You just have to register the result of calling the user module.
Just try the following for your second play:
- name: Resgister application users
hosts: app
vars:
user_list:
- newuser1
- newuser2
- newuser3
tasks:
- name: Create the users if they don't exist
user:
name: "{{ item }}"
state: present
shell: "/bin/bash"
password: "$6$mysecretsalt$qyctTVhRMS1ZSnCuzQNAM8Y7V/yqSEnyRbal0IYXSqSEVKkXF8ZmXBZoRIaN/PvzE/msq8iOJO830OOCG89va/"
update_password: always
groups: santosh
register: create_users
loop: "{{ user_list }}"
- name: Show ids of users
debug:
msg: "The uid of user {{ item.name }} is: {{ item.uid }}"
loop: "{{ create_users.results }}"
And as a side note: for your first play, do yourself a favor and stop using the old local_action syntax in favor of delegate_to: localhost for a task. It is not even required in your case as your are already targeting your play to localhost only.
Related
I have a variables file that includes important info about our databases; the server they are on, the db version, the DB_HOME directory, etc. In the variables file, I would like to dynamically create lists that capture the unique values of those properties, so they can be easily iterated through in a task.
I have equivalent functionality by creating the list on the fly in a task's loop option, but that means repeating that loop syntax (violates DRY principle) and I would like less sophisticated Ansible colleagues to be able to use a pre-defined list.
example of the variables file databases.yml:
databases:
- name: test_db1
server: ora_901
listener: LISTENER_XYZ
version: '11.2.0.4'
oracle_home: '/app/oracle/product/11.2.0.4/db_home'
- name: test_db2
server: ora_902
listener: LISTENER_ABC
version: '11.2.0.4'
oracle_home: '/app/oracle/product/11.2.0.4/db_home'
## This didn't work... was hoping I could build this list dynamically
listeners:
- name: "{{ item }}"
loop: "{{ databases | map(attribute = 'listener') | list | unique }}"
servers:
- name: "{{ item }}"
loop: "{{ databases | map(attribute = 'server') | list | unique }}"
I would then use this loop through either the 'listeners' or 'servers' lists directly with some tasks.
When I tried a task that referenced the listeners variable, it failed. Referencing databases works and all items are returned, so I know it's getting some data from the vars_file...
- vars_files:
- vars/databases.yml
tasks:
- debug:
msg: "{{ databases }}"
- debug:
msg: "{{ listeners }}"
TASK [debug] **************************************************************************
ok: [FQDN] => {
"msg": [
{
"listener": "LISTENER_XYZ",
"name": "test_db1",
"oracle_home": "/app/oracle/product/11.2.0.4/db_home",
"server": "ora_901",
"version": "11.2.0.4"
},
{
"listener": "LISTENER_ABC",
"name": "test_db2",
"oracle_home": "/app/oracle/product/11.2.0.4/dbhome_1",
"server": "ora_902",
"version": "11.2.0.4"
},
fatal: [FQDN]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'item' is undefined\n\nThe error appears to have been in '/home/xxx/test_vars.yml': line 21, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - debug:\n ^ here\n"}
I would really like to keep these dynamic definitions in the same place as the server definitions, and don't see why it wouldn't be possible, I'm just sure I'm using the wrong mechanism.
Check this out from Ansible doc// Just to give you an idea, about "loop_control".
You can nest two looping tasks using include_tasks. However, by default Ansible sets the loop variable item for each loop. This means the inner, nested loop will overwrite the value of item from the outer loop. You can specify the name of the variable for each loop using loop_var with loop_control:
# main.yml
- include_tasks: inner.yml
loop:
- 1
- 2
- 3
loop_control:
loop_var: outer_item
# inner.yml
- debug:
msg: "outer item={{ outer_item }} inner item={{ item }}"
loop:
- a
- b
- c
I'm importing an Ansible role in a play and running its 'install' task.The role is meant to create VMs on an hypervisor like Vbox and works fine.
However, I want to use it to create several VMs at the same time, and I must provide 2 variables for this purpose :
- vm_ip : the ip of the vm to be created
- vm_name : the name of the vm to be created
I have already tried almost everything with loops, with_items and other things. For instance, this code doesn't work :
- name: Create VMs
hosts: localhost
tasks:
- import_role:
name: vm_creation
tasks_from: install
vars:
vm_ip: "{{ item.ips }}"
vm_name: "{{ item.names }}"
loop:
- { ips: '192.168.20.4', names: 'test4' }
- { ips: '192.168.20.5', names: 'test5' }
It is supposed to create both .20.4 and .20.5 VM's but the play crashes telling me this :"The task includes an option with an undefined variable. The error was: 'item' is undefined
You appear to have mis-indented the loop directive. In doing so, you have defined a variable named loop rather than actually creating a loop (this is why item is undefined).
You will also need to use include_role rather than import_role. You can read about the difference between include_role and import_role in the documentation.
- name: Create VMs
hosts: localhost
tasks:
- include_role:
name: vm_creation
tasks_from: install
vars:
vm_ip: "{{ item.ips }}"
vm_name: "{{ item.names }}"
loop:
- { ips: '192.168.20.4', names: 'test4' }
- { ips: '192.168.20.5', names: 'test5' }
I have a simple shell task which is feed with_items. I want to have the 'changed' status only when the run actually did something. So for that I have to parse the output and look for a «Done».
The issue is using a register at the same time that the with_items generates a list of results. And I don't find a way to access the stdout of the current loop iteration.
---
- name: Test to set changed status only on when output match with 'Done'
hosts: my-host-01
gather_facts: no
tasks:
- name: Task doing stuff
shell: |
echo {{ item }}
register: do_stuff
with_items:
- "Doing... Done!"
- "Nothing to do"
#changed_when: do_stuff.results[{{ item.id }}].stdout.find('Done')
- name: Print 'do_stuff' var to debug
debug:
var: do_stuff
- name: Try to access to the desired stdout
debug:
var: do_stuff.results[0].stdout
The expected result that I want would be:
TASK [Task doing stuff]
*************************************************************************
changed: [cnode02] => (item=Doing... Done!)
Ok: [cnode02] => (item=Nothing to do)
I had a similar problem and the following example from the ansible documentation was very helpful to understand it:
During iteration, the result of the current item will be placed in the
variable:
- shell: echo "{{ item }}"
with_items:
- one
- two
register: echo
changed_when: echo.stdout != "one"
https://docs.ansible.com/ansible/2.4/playbooks_loops.html#using-register-with-a-loop
In your case the condition with stdout.find(...) will not work. I successfully tested it with '"string" in var.stdout':
- name: Task doing stuff
shell: |
echo {{ item }}
register: do_stuff
with_items:
- "Doing... Done!"
- "Nothing to do"
changed_when: '"Done" in do_stuff.stdout'
The following example works using the changed_when: condition, for each item separately.
- name: Task doing stuff
shell: |
echo {{ item }}
register: task_result
with_items:
- "Doing... Done!"
- "Nothing to do"
changed_when: task_result.stdout | regex_search('Done')
In your task, you must use register: to store the result, e.g. register: task_result. During execution, especially for the changed_when: and failed_when: conditions, this is directly available via the variable name, i.e. via task_result.stdout.
Here you can now e.g. search for the desired text via function regex_search( ), or also for a certain line of the output. To search for a whole line, the function offers multiline=True as another parameter. More information about the function regex_search( ) can be found in the Ansible Docs.
As soon as the task with all its items has been executed, the result (as you assumed) is available in the variable task_result (i.e. under the same name) as a list via the key results, i.e. via task_result.results[ITEM_ID].stdout.
I want to run a loop in Ansible the number of times which is defined in a variable. Is this possible somehow?
Imagine a list of servers and we want to create some numbered files on each server. These values are defined in vars.yml:
server_list:
server1:
name: server1
os: Linux
num_files: 3
server2:
name: server2
os: Linux
num_files: 2
The output I desire is that the files /tmp/1, /tmp/2 and /tmp/3 are created on server1, /tmp/1 and /tmp/2 are created on server2. I have tried to write a playbook using with_nested, with_dict and with_subelements but I can't seem to find any way to to this:
- hosts: "{{ target }}"
tasks:
- name: Load vars
include_vars: vars.yml
- name: Create files
command: touch /tmp/{{ loop_index? }}
with_dict: {{ server_list[target] }}
loop_control:
loop_var: {{ item.value.num_files }}
If I needed to create 50 files on each server I can see how I could do this if I were to have a list variable for each server with 50 items in it list which is simply the numbers 1 to 50, but that would be a self defeating use of Ansible.
There is a chapter in the docs: Looping over Integer Sequences (ver 2.4)
For your task:
- file:
state: touch
path: /tmp/{{ item }}
with_sequence: start=1 end={{ server_list[target].num_files }}
Update: things has changed in Ansible 2.5. See separate docs page for sequence plugin.
New loop syntax is:
- file:
state: touch
path: /tmp/{{ item }}
loop: "{{ query('sequence', 'start=1 end='+(server_list[target].num_files)|string) }}"
Unfortunately sequence accepts only string-formatted parameters, so parameters passing to query looks quite clumsy.
I have a set of variables which define FQDNs.
domains:
- erp: erp.mycompany.com
- crm: crm.mycompany.com
- git: git.mycompany.com
Indeed, I both need to loop over them and access them namely (in a template file). So accessing them like domains.erpworks like a charm. But I can't get ansible to loop over these.
Obviously, if I do:
- name: Print domains
debug:
msg: test {{ item }}
with_items:
- "{{ domains }}"
It prints both the key and the value… And if I do:
- name: Print domains
debug:
msg: test {{ domains[{{ item }}] }}
with_items:
- "{{ domain }}"
But that doesn't work. I also tried the hashes form as mentionned in the docs, but didn't get any luck either…
Finally, I had to use a dict.
It didn't work the first time because unlike with_items, which has the items going each on their own line, with_dict is a one liner without - before the element to loop through.
domains:
erp:
address: erp.mycompany.com
crm:
address: crm.mycompany.com
git:
address: git.mycompany.com
# used by letsencrypt
webserverType: apache2
withCerts: true
tasks:
- name: Print phone records
debug:
msg: "{{ item.value.address }}"
with_dict: "{{ domains }}"
# I can still access a given domain by its name when needed like so:
{{ domains.erp.address }}
Looks like you figured out your issue. Your original attempt uses a list of dictionaries that do not contain the same keys, making it difficult to access the values uniformly across each list item.
Your second solution creates a dictionary where the keys refer to other dictionaries.
Another solution than what you posted if you still wanted to use a list:
- hosts: localhost
vars:
domains:
- name: erp
address: erp.mycompany.com
- name: crm
address: crm.mycompany.com
- name: git
address: git.mycompany.com
tasks:
- name: Print phone records
debug:
msg: "{{ item.address }}"
with_items: "{{ domains }}"
To me this approach is simpler but your second approach works as well.