I am following the redhat ansible course online and I followed the below steps to use multi-valued variable in a playbook. But I am hitting an error stating there's an undefined variable
Below is my arrays.yaml file
---
- name: show arrays
hosts: ansible1.example.com
vars_files:
- vars/users
tasks:
- name: print array values
debug:
msg: "User {{ item.username }} has homedirectory {{ item.homedir }} and shell {{ item.shell }}"
with_items: "{{ users }}"
And below is vars/users file
users:
linda:
username: linda
homedir: /home/linda
shell: /bin/bash
gokul:
username: gokul
homedir: /home/gokul
shell: /bin/bash
saha:
username: saha
homedir: /home/gokul/saha
shell: /bin/bash
And below is the error that I am hitting
ansible-playbook arrays.yaml
PLAY [show arrays] *****************************************************************************************************************************************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************************************************************************************************************************************
ok: [ansible1.example.com]
TASK [print array values] **********************************************************************************************************************************************************************************************************************************
fatal: [ansible1.example.com]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'username'\n\nThe error appears to be in '/home/ansible/install/arrays.yaml': line 7, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n - name: print array values\n ^ here\n"}
PLAY RECAP *************************************************************************************************************************************************************************************************************************************************
ansible1.example.com : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
However, when I try to refer individual values like below in the playbook, it works fine.
---
- name: show arrays
hosts: ansible1.example.com
vars_files:
- vars/users
tasks:
- name: print array values
debug:
msg: "User {{ users.gokul.username }} has homedirectory {{ users.gokul.homedir }} and shell {{ users.gokul.shell }}"
It's not possible to iterate a dictionary. Either the code or the data should be changed.
1. Change data
Use a list of users instead of a dictionary. For example, the list below
users:
- username: linda
homedir: /home/linda
shell: /bin/bash
- username: gokul
homedir: /home/gokul
shell: /bin/bash
- username: saha
homedir: /home/gokul/saha
shell: /bin/bash
would work as expected
- name: print array values
debug:
msg: "User {{ item.username }}
has homedirectory {{ item.homedir }}
and shell {{ item.shell }}"
loop: "{{ users }}"
2. Change code
It's possible to use the users dictionary with the filter dict2items. For example, the task below would give the same result
- name: print array values
debug:
msg: "User {{ item.value.username }}
has homedirectory {{ item.value.homedir }}
and shell {{ item.value.shell }}"
loop: "{{ users|dict2items }}"
Related
I have a JSON object of files and the destinations they need to be symlinked moved to. I would like to be able to have environment variables in the destinations and have them evaluated to know where they should be moved to. I'm attempting to do a regex_search with a lookup, but it isn't giving me the desired result.
Here is the json:
dotfiles.json
{
"mac": [
{
"name": ".gitconfig",
"destination": "$HOME/"
}
]
}
playbook: ansible/bootstrap.yaml
---
- name: "Bootstrapping Machine"
hosts: localhost
connection: local
ignore_errors: true
vars:
dotfiles: "{{ lookup('file', '../dotfiles.json') | from_json }}"
roles:
- role: "MacOSX"
when: "ansible_distribution == 'MacOSX'"
tasks: ansible/roles/MacOSX/tasks/main.yaml
- name: Symlink dotfiles
ansible.builtin.file:
src: "../../../dotfiles/{{ item.name }}"
dest: "{{ destination }}"
state: link
vars:
destination: "{{ item.destination | regex_replace('\\$\\w+', lookup('env', '\\1')) }}"
with_items: "{{ dotfiles.mac | list }}"
The destination variable evaluates to /.
Below is a quick and dirty fix of your original idea. This will only work if you target localhost (as lookups only run on localhost) hence will fail for a remote target (as the env var will be gathered for the current user on your local environment).
Moreover, the regexes/replace/lookups will probably fail as soon as you introduce more than one env var in your string(s). But at least you get a first working example for your particular situation and can build over it.
The following example playbook (using the same json file as in your question):
- name: "Replace dollar env var with local env value"
hosts: localhost
gather_facts: false
vars:
dotfiles: "{{ lookup('file', 'dotfiles.json') }}"
tasks:
- name: Replace dollar env var with local env value
vars:
env_var: >-
{{ item.destination | regex_replace('\$(\w+)/', '\1') }}
destination: >-
{{ item.destination | regex_replace('\$\w+[^/]', lookup('env', env_var)) }}{{ item.name }}
debug:
msg: "{{ destination }}"
loop: "{{ dotfiles.mac }}"
Gives (username redacted)
$ ansible-playbook bootstrap.yml
PLAY [Replace dollar env var with local env value] *****************************************************************************
TASK [Replace dollar env var with local env value] *****************************************************************************
ok: [localhost] => (item={'name': '.gitconfig', 'destination': '$HOME/'}) => {
"msg": "/home/somelocaluser/.gitconfig"
}
PLAY RECAP *****************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
The goal is to read the input file for a string that matches the regex expression then to match the expression to my config file.. when there is a match ansible is to replace the existing line in the config file with the matching line in the delta file.
I was able to perform this task but noticed that ansible would read one line and essentially be done. I added the .splitlines() option to my code so that it would read line by line and perform the same action but i received the following error:
- name: Search for multiple reg expressions and replace in config file
vars:
# pull data from config file
input: "{{ lookup('file', '{{ record }}').splitlines() }}"
delta: "{{ input | regex_search('^.*[a-zA-Z]+.*_.*[a-zA-Z]+.*?', multiline=True )}}"
delta1: "{{ input | regex_search('^.*[a-zA-Z]+.*_.*[a-zA-Z]+.*', multiline=True)}}"
record: "/etc/ansible/roles/file_config/files/records/records.config.{{ inventory_hostname }}"
lineinfile:
path: /dir/dir/WCRUcachedir/records.config
# Line to search/Match against
regexp: "{{item.From}}"
# Line to replace with
line: "{{item.To}}"
state: present
backup: yes
with_items:
- { From: '{{delta}}', To: '{{delta1}}' }
This happened to be my end result
"msg": "An unhandled exception occurred while templating '{{ input | regex_search('^.*[a-zA-Z]+.*_.*[a-zA-Z]+.*', multiline=True )}}'. Error was a <class 'ansible.errors.AnsibleError'>, original message: Unexpected templating type error occurred on ({{ input | regex_search('^.*[a-zA-Z]+.*_.*[a-zA-Z]+.*', multiline=True )}}): expected string or buffer"
}
these are what i believe my conflicting lines are
input: "{{ lookup('file', '{{ record }}').splitlines() }}"
AND
delta1: "{{ input | regex_search('^.[a-zA-Z]+._.[a-zA-Z]+.', multiline=True)}}"
OK, you do have another problem.
In the vars section, when you're setting delta and delta1, regex_search is expecting a string, but your passing a list (which splitlines() created). But you need it to work on one line at a time.
So, rather that input, use item, which will be set in the loop:
vars:
input: "{{ lookup('file', '{{ record }}').splitlines() }}"
delta: "{{ item | regex_search('^.*[a-zA-Z]+.*_.*[a-zA-Z]+.*?')}}"
delta1: "{{ item | regex_search('^.*[a-zA-Z]+.*_.*[a-zA-Z]+.*')}}"
Obviously, you don't need the multiline=True any more.
Now, the loop will look like this:
lineinfile:
path: /etc/opt/CCURcache/records.config
regexp: "{{ delta }}"
line: "{{ delta1 }}"
state: present
backup: yes
loop: "{{ input }}"
when: delta != ""
Yes, you only have one item to loop over. That item has two elements, From and To.
From is {{ input | regex_search('^.*[a-zA-Z]+.*_.*[a-zA-Z]+.*?', multiline=True )}}
To is {{ input | regex_search('^.*[a-zA-Z]+.*_.*[a-zA-Z]+.*', multiline=True)}}
All you did in the vars section was define strings, which will get executed later, when the regex and line are used in the module.
Now, assuming you need to put these together, you need to zip them:
lineinfile:
path: /etc/opt/CCURcache/records.config
regexp: "{{item.0}}"
line: "{{item.1}}"
state: present
backup: yes
loop: "{{ delta|zip(delta1)|list }}"
Here's a simple example:
---
- hosts: localhost
connection: local
gather_facts: no
vars:
list1:
- key_1
- key_2
- key_n
list2:
- value_1
- value_2
- value_n
tasks:
- name: Loop over lists
debug:
msg: "key is {{ item.0 }}; value is {{ item.1 }}"
loop: "{{ list1|zip(list2)|list }}"
And the results:
PLAY [localhost] *******************************************************************************
TASK [Loop over lists] *************************************************************************
ok: [localhost] => (item=['key_1', 'value_1']) => {
"msg": "key is key_1; value is value_1"
}
ok: [localhost] => (item=['key_2', 'value_2']) => {
"msg": "key is key_2; value is value_2"
}
ok: [localhost] => (item=['key_n', 'value_n']) => {
"msg": "key is key_n; value is value_n"
}
PLAY RECAP *************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Good luck!
I am trying to automate ACI with ansible, very new to ansible, as a start point, I started to create simple Tenant and VRF and I am using a variable with my playbook, here it is my playbook like below.
---
- name: Playbook
hosts: APIC
connection: local
gather_facts: no
tasks:
- name: Create new tenant
aci_tenant:
host: "{{ apic_host }}"
username: "{{ apic_username }}"
password: "{{ apic_password }}"
tenant: "{{ tenant_name }}"
validate_certs: False
description: "{{ tenant_description }}"
state: present
# - pause:
# seconds: 10
- name: Add a new VRF
aci_vrf:
host: "{{ apic_host }}"
username: "{{ apic_username }}"
password: "{{ apic_password }}"
tenant: "{{ tenant_name }}"
validate_certs: false
vrf: "{{ vrf_name }}"
policy_control_preference: enforced
policy_control_direction: ingress
state: present
# - pause:
# seconds: 30
and here is my variable file
---
apic_host: sandboxapicdc.cisco.com
apic_username: admin
apic_password: ciscopsdt
tenant_name: zhajili_TN
tenant_description: 'zhajili_first_ansible_tenant'
ap_name: 'AP'
ap_description: 'AP created with Ansible'
vrf_name: 'zhajili_VRF'
When I execute the playbook I receive the following error.
$ ansible-playbook tenants_vrfs.yml -i inventory -v
No config file found; using defaults
PLAY [Playbook] ************************************************************************************************************************************************************************************************
TASK [Create new tenant] ***************************************************************************************************************************************************************************************
fatal: [apicsim_sandbox]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'apic_host' is undefined\n\nThe error appears to be in '/Users/zhajili/Desktop/ansible/ACI/Cisco Sandbox/Tenants_and_VRFs/tasks/tenants_vrfs.yml': line 8, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n - name: Create new tenant\n ^ here\n"}
PLAY RECAP *****************************************************************************************************************************************************************************************************
apicsim_sandbox : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
I'm not able to figure out how I can pass a nested variable as parameter to Ansible's shell module & invoke the script "check.sh" however, below is what I tried.
---
- name: "Find the details here "
hosts: localhost
tasks:
- name: set_fact
set_fact:
fpath_APP: "{{ fpath_APP + [ item.split('.')[0] ~ '/' ~ item | basename ] }}"
with_items:
- "{{ Source_Files.split(',') }}"
vars:
fpath_APP: []
- name: Invoke shell script
- shell: "./check.sh {{ Number }} '{{ vars['fpath_' + Layer] }}' > hello.txt"
tasks:
- name: Ansible find files multiple patterns examples
find:
paths: /home/examples
patterns: "*.db"
recurse: yes
register: files_matched
- name: Search for Number in the matched files
command: grep -i {{ Number }} {{ item.path }}
with_items:
- "{{ files_matched.files }}"
The above playbook runs but does not invoke the shell module and completes without doing anything. See Output below:
$ ansible-playbook fpath.yml -e " Source_Filenames=/tmp/logs/filename1.src,/tmp/logs/33211.sql,/app/axmw/Jenkins/file1.mrt
Layer=APP Number=57550" [WARNING]: provided hosts list is empty, only
localhost is available. Note that the implicit localhost does not
match 'all'
[WARNING]: While constructing a mapping from fpath.yml, line 2,
column 3, found a duplicate dict key (tasks). Using last defined value
only.
PLAY [Find the details here]
**************************************************************************************************************************************************
TASK [Gathering Facts]
******************************************************************************************************************************************************** ok: [localhost]
PLAY RECAP
******************************************************************************************************************************************************************** localhost : ok=1 changed=0 unreachable=0
failed=0 skipped=0 rescued=0 ignored=0
Below are my failed attempts at changing the shell module syntax:
- shell: "./check.sh {{ Number }} '{{ 'fpath_' + vars[Layer] }}' > hello.txt"
Does not invoke Shell module
- shell: "./check.sh {{ Number }} '/"{{ vars['fpath_' + Layer] }}/"' > hello.txt"
Gives Syntax Error.
- shell: "./check.sh {{ Number }} /"'{{ vars['fpath_' + Layer] }}'/" > hello.txt"
Gives Syntax Error.
I'm on the latest version of ansible and python version is 2.7.5.
You want to use the vars lookup plugin: https://docs.ansible.com/ansible/latest/plugins/lookup/vars.html
For your example, above you would want to do:
- shell: "./check.sh {{ Number }} {{ lookup('vars', 'fpath_' + Layer) }}" > hello.txt"
I'm an Ansible AWX newbie. I'm trying to pass a variable between two tasks in a playbook. The playbook looks like this:
---
- name: start
hosts: all
gather_facts: no
tasks:
- name: Check Debian disk space
command: df --output=avail {{ a7_pcap_data_path }}
become: true
become_method: sudo
register: df_output
- name: Show Debian free space
vars:
a7_free_space_mb: "{{ (df_output.stdout_lines[1]|int / 1024)|round(0,'floor') }}"
a7_spare_space_mb: "{{ a7_free_space_mb|int - (a7_capture_volume_mb|int * 1.1) }}" # Allow 10% for safety
debug:
msg: "Spare space: {{ a7_spare_space_mb }} MB"
- name: Pass/fail check
debug:
msg: "Spare space: {{ a7_spare_space_mb }} MB"
The a7_pcap_data_pathand a7_capture_volume_mb are passed in as AWX variables. When I run this from the Job Template in AWX, I get this:
Identity added: /tmp/awx_57_PesTa2/credential_3 (/tmp/awx_57_PesTa2/credential_3)
SSH password:
PLAY [start] *******************************************************************
TASK [Check Debian disk space] *************************************************
changed: [ip-172-31-14-43.eu-west-1.compute.internal]
TASK [Show Debian free space] **************************************************
ok: [ip-172-31-14-43.eu-west-1.compute.internal] => {
"msg": "Spare space: 3020.0 MB"
}
TASK [Pass/fail check] *********************************************************
fatal: [ip-172-31-14-43.eu-west-1.compute.internal]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'a7_spare_space_mb' is undefined\n\nThe error appears to have been in '/var/lib/awx/projects/pre_checks/test.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 - name: Pass/fail check\n ^ here\n"}
PLAY RECAP *********************************************************************
ip-172-31-14-43.eu-west-1.compute.internal : ok=2 changed=1 unreachable=0 failed=1
The variable a7_spare_space_mb obviously is available in the task Show Debian free space but not in the subsequent task.
It appears that the scope of the a7_spare_space_mb variable is only within the task in which it's defined, but from what I've read, the scope should be the entire playbook.
What am I doing wrong?
Thanks and regards...Paul
An option would be to use set_fact
- set_fact:
a7_free_space_mb: "{{ (df_output.stdout_lines[1]|int / 1024)|round(0,'floor') }}"
a7_spare_space_mb: "{{ a7_free_space_mb|int - (a7_capture_volume_mb|int * 1.1) }}" # Allow 10% for safety
- name: Show Debian free space
debug:
msg: "Spare space: {{ a7_spare_space_mb }} MB"
- name: Pass/fail check
debug:
msg: "Spare space: {{ a7_spare_space_mb }} MB"