Ansible 2.8: Access a list object element - ansible

In Ansible 2.8, I need to deploy and config Bind 9 DNS on Ubuntu Server VMs. I have a:
DNS Ansible role to do the installation and config,
a list of variables per domain zone (as DNS record type, domain name, dns entry,...). Until here, it works, the issues appear when I try to make it accept the next requirement:
potentially, several domain zones to configure in the same call, threfore, I send to it a list with groups of variables (mentioned in 2).
For now, in the shell, I call it with 1 element list, using:
--extra-vars "{"dns_entry_conf":
[domain=example.gal ip=192.168.167.166
nameserver1=example.gal nameserver1_ip=192.168.167.164
dns_record1_type=A ...]}"
Inside the role, the roles/dns/tasks/configure.yml file receives the right value, but the file that follows, doesn't: it says "list object has no attribute", and I started debugging in the configure.yml file, but I am not sure how to access the list object item:
---
- debug:
msg: "{{dns_entry_conf}}"
- debug:
msg: "{{dns_entry_conf | json_query(\"domain\") }}"
The first line prints what it should, but the 2nd does not... How I can access the value
ASK [dns : debug] **********************************************************************************
task path: /etc/ansible/roles/dns/tasks/configure.yml:2
ok: [ubuntuServer16_test] => {
"msg": [
"domain=example.gal ip=192.168.167.166 nameserver1=example.gal nameserver1_ip=192.168.167.164
dns_record1_type=A ...
]
}
TASK [dns : debug] **********************************************************************************
task path: /etc/ansible/roles/dns/tasks/configure.yml:4
ok: [ubuntuServer16_test] => {
"msg": ""
}
In debug, tried with the msg's: "{{ dns_entry_conf.domain }}", "{{ dns_entry_conf.0 }}", "{{dns_entry_conf | json_query(\"domain\") }}", "{{ dns_entry_conf.list | json_query('[*].domain') }}", and others that were sintactically wrong, but it never outputs what I want.
Probably there are more wrong things (I am no Ansible expert), but, for now, just trying to debug and fix one by one, so, I just want to know how I can access the "dns_entry_conf.domain" item, please... some idea?

Option1:
with extra-vars as below:
--extra-vars '{"dns_entry_conf":{"domain":example,"ip":1.2.3.4}}'
Playbook:
- debug:
msg: "{{dns_entry_conf.domain}}"
Output:
ok: [localhost] => {
"msg": "example"
}
Option2:
with extra vars as below:
--extra-vars '{"dns_entry_conf":["domain":example,"ip":1.2.3.4]}'
In Playbook tey as below:
- debug:
msg: "{{dns_entry_conf[0].domain}}"
Output:
ok: [localhost] => {
"msg": "example"
}
Option 3:
Pass the variables in the playbook.
vars:
dns_entry_conf:
domain: example
ip: 1.2.34.4
tasks:
- debug:
msg: "{{dns_entry_conf.domain}}"
Output:
ok: [localhost] => {
"msg": "example"
}

Related

What is the best way to dynamically add variables as Ansible Facts?

I have an Ansible task where I navigate to a YAML variable file in GitHub, download the file, and add the variables as Ansible Facts where they're later used.
My YAML file looks like:
---
foo: bar
hello: world
I have a method where I loop over this file, and individually add the key/value pairs as the facts:
- name: Grab contents of variable file
win_shell: cat '{{ playbook_dir }}/DEV1.yml'
register: raw_config
- name: Add variables to workspace
vars:
config: "{{ raw_config.stdout | from_yaml }}"
set_fact:
"{{ item.key }}": "{{ item.value }}"
loop: "{{ config | dict2items }}"
This works but generates much larger log outputs that look like:
ok: [localhost] => (item={u'key': u'foo', u'value': u'bar'}) => {
"ansible_facts": {
"foo": "bar"
},
"ansible_loop_var": "item",
"changed": false,
"item": {
"key": "foo",
"value": "bar"
}
}
ok: [localhost] => (item={u'key': u'hello', u'value': u'world'}) => {
"ansible_facts": {
"hello": "world"
},
"ansible_loop_var": "item",
"changed": false,
"item": {
"key": "hello",
"value": "world"
}
}
I was wondering if it was possible to add the entire variable file as Ansible Facts instead of needing to loop through it. The way I tried was like:
- name: Grab contents of variable file
win_shell: cat '{{ playbook_dir }}/DEV1.yml'
register: raw_config
- name: Add variables to workspace
vars:
config: '{{ raw_config.stdout | from_yaml }}'
set_fact: '{{ config }}'
This almost works, but it looks like this:
ok: [msf1vpom04d.corp.tjxcorp.net] => {
"ansible_facts": {
"_raw_params": {
"foo": "bar",
"hello": "world"
…
Can I add the entire object as Ansible Facts without generating this _raw_params object?
... where I navigate to a YAML variable file in GitHub, download the file, and add the variables ... I was wondering if it was possible to add the entire variable file ...
There are several possibilities.
One option (annot.: like in Ansible Tower) can be to checkout, download, sync the variable file before executing the playbook. To do so
curl --silent --user "${ACCOUNT}:${PASSWORD}" -X GET "https://${REPOSITORY_URL}/raw/group_vars/test?at=refs%2Fheads%2Fmaster" -o group_vars/test && \
sshpass -p ${PASSWORD} ansible-playbook --user ${ACCOUNT} --ask-pass test.yml
... used a Bitbucket URL here for demonstration and test
The advantage of this approach is that there is no implementation or logic within the playbooks necessary at all. The only requirement is just to Organize host and group variables. Furthermore it is the there recommended approach
Keeping your inventory file and variables in a git repo (or other version control) is an excellent way to track changes to your inventory and host variables.
An other option would be to use include_vars module to
Loads YAML/JSON variables dynamically from a file or directory, recursively, during task runtime.
In respect to simplicity it is still recommended to sync before execute.
Further Q&A
... and as already mentioned within the comments
Getting variable values from URL
You can take advantage of the play level vars_files parameter and the fact that it will be loaded and expanded for each running task. We just need to have a fallback file when your file does not yet locally exist so that we don't get an error (during facts gathering for example).
Here is an example with a uri of mine containing default values for an ansible role.
First, create an empty.yml file which will be empty as its name suggest. (It's adjacent to my playbook for the example but you can put it wherever your want, just reflect this in your playbook accordingly)
Then the following playbook:
---
- hosts: localhost
gather_facts: false
vars:
external_vars_uri: https://raw.githubusercontent.com/ansible-ThoTeam/nexus3-oss/main/defaults/main.yml
external_vars_file: /tmp/external_vars.yml
vars_files:
- "{{ lookup('first_found', [external_vars_file, 'empty.yml']) }}"
tasks:
- name: make sure we have our external file
get_url:
url: "{{ external_vars_uri }}"
dest: "{{ external_vars_file }}"
# Note: we're only using localhost here so the below
# parameters are useless. But they will be necessary
# if you target other (groups of) hosts.
run_once: true
delegate_to: localhost
- name: debug a var we know is in the external file
debug:
var: nexus_repos_maven_proxy
Gives:
$ ansible-playbook play.yml
PLAY [localhost] **************************************************************************************************************************************************************************************************
TASK [make sure we have our external file] ************************************************************************************************************************************************************************
changed: [localhost]
TASK [debug a var we know is in the external file] ****************************************************************************************************************************************************************************
ok: [localhost] => {
"nexus_repos_maven_proxy": [
{
"layout_policy": "permissive",
"name": "central",
"remote_url": "https://repo1.maven.org/maven2/"
},
{
"name": "jboss",
"remote_url": "https://repository.jboss.org/nexus/content/groups/public-jboss/"
}
]
}
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

fail custom message with item name

I have the following task which works perfectly as expected. I am wondering if the failure message can be a bit more informative since I am passing no_log: true without which I could see the entire result in the logs. Something like:
More than one access keys available for the cos account {{ item.name }}
- name: Fail if more than one key is available for any of the COS accounts
fail: msg="More than one access keys available for the cos account"
when: (item.json)|length > 1
with_items: '{{ old_existing_creds.results }}'
no_log: true
In fact I noticed I could not even see the msg. The o/p I got is:
TASK [Fail if more than one key is available for any of the COS accounts] *****************************************************************************
skipping: [localhost] => (item=None)
failed: [localhost] (item=None) => {"censored": "the output has been hidden due to the fact that 'no_log: true' was specified for this result", "changed": false}
fatal: [localhost]: FAILED! => {"censored": "the output has been hidden due to the fact that 'no_log: true' was specified for this result", "changed": false}
to retry, use: --limit #/root/deployment/generateSLKeys.retry
You've asked Ansible not to log the result of your task by setting no_log: true, so you're not going to be able to see the result of the fail task. You can hack around this by first creating a new variable that maps names from your old_existing_creds variables to the length of the json attribute, like this:
---
- hosts: localhost
gather_facts: false
vars:
old_existing_creds:
results:
- json: [secret1,secret2]
name: foo
- json: [secret1]
name: bar
tasks:
- name: check length of json array
set_fact:
key_check: "{{ key_check|default({})|combine({item.name: item.json|length}) }}"
loop: "{{ old_existing_creds.results }}"
- debug:
var: key_check
- name: Fail if more than one key is available for any of the COS accounts
fail:
msg: "More than one access keys available for the cos account {{ item.key }}"
when: (item.value > 1)
loop: "{{ key_check|dict2items }}"
This will output:
TASK [Fail if more than one key is available for any of the COS accounts] *********************
failed: [localhost] (item={'key': u'foo', 'value': 2}) => {"changed": false, "item": {"key": "foo", "value": 2}, "msg": "More than one access keys available for the cos account foo"}
skipping: [localhost] => (item={'key': u'bar', 'value': 1})
As you can see, it shows the message from the fail task, which includes the account name, but it does not expose credentials in the log.

How to loop through inventory and assign value in Ansible

I have a task in my Ansible playbook that I'm wanting iterate over each host in the group that I have and for each host I would like to assign a name from the hostname list that I've created in the vars folder.
I'm familiar with looping through inventory already by writing loop: "{{ groups['mygroup'] }}" and I have a list of hostnames I would like to assign each IP in 'mygroup' within the host file.
# In tasks file - roles/company/tasks/main.yml
- name: change hostname
win_hostname:
name: "{{ item }}"
loop: "{{ hostname }}"
register: res
# In the Inventory file
[company]
10.0.10.128
10.0.10.166
10.0.10.200
# In vars - roles/company/vars/main.yml
hostname:
- GL-WKS-18
- GL-WKS-19
- GL-WKS-20
# site.yml file located under /etc/ansible
- hosts: company
roles:
- common
- company #This is where the loop exists mentioned above.
# Command to run playbook
ansible-playbook -i hosts company.yml
I seem to have the individual pieces down or know about it, but how can I combine iterating over hosts from an inventory group and assign names that I have in an already created list (in roles vars folder) already?
UPDATE
the task mentioned above has been updated to reflect changes mentioned in answer:
- name: change hostname
win_hostname:
name: "{{ item.1 }}"
loop: {{ groups.company|zip(hostname)|list }}"
register: res
However the output I'm getting is incorrect, this should not run 9 times rather only three times, once per IP in the [company] group in the inventory. Also there are only three hostnames in a list that need to be assigned to each of the hosts in the inventory sheet.
changed: [10.0.10.128] => (item=[u'10.0.10.128', u'GL-WKS-18'])
changed: [10.0.10.166] => (item=[u'10.0.10.128', u'GL-WKS-18'])
changed: [10.0.10.200] => (item=[u'10.0.10.128', u'GL-WKS-18'])
changed: [10.0.10.128] => (item=[u'10.0.10.166', u'GL-WKS-19'])
changed: [10.0.10.166] => (item=[u'10.0.10.166', u'GL-WKS-19'])
changed: [10.0.10.200] => (item=[u'10.0.10.166', u'GL-WKS-19'])
ok: [10.0.10.128] => (item=[u'10.0.10.200', u'GL-WKS-20'])
ok: [10.0.10.166] => (item=[u'10.0.10.200', u'GL-WKS-20'])
ok: [10.0.10.200] => (item=[u'10.0.10.200', u'GL-WKS-20'])
Whenever I have a question about looping in Ansible I also go visit the Loops documentation. It sounds like you want to iterate over two lists in parallel, pairing an item from the list of hosts in your inventory with an item from the list of hostnames. In previous versions of Ansible that would suggest using the with_together loop, while with more recent versions of Ansible that suggests the zip filter (there's an example in the docs here).
To demonstrate this for your use case, I started with an inventory file that has three hosts:
[mygroup]
hostA ansible_host=localhost
hostB ansible_host=localhost
hostC ansible_host=localhost
And the following playbook:
---
- hosts: all
- hosts: localhost
gather_facts: false
vars:
hostnames:
- hostname01
- hostname02
- hostname03
tasks:
- name: change hostname
debug:
msg:
win_hostname:
name: "{{ item }}"
loop: "{{ groups.mygroup|zip(hostnames)|list }}"
Here I'm using a debug task instead of actually running the win_hostname task. The output of running:
ansible-playbook -i hosts playbook.yml
Looks like:
TASK [change hostname] ********************************************************************************************************************************
ok: [localhost] => (item=[u'hostA', u'hostname01']) => {
"msg": {
"win_hostname": {
"name": [
"hostA",
"hostname01"
]
}
}
}
ok: [localhost] => (item=[u'hostB', u'hostname02']) => {
"msg": {
"win_hostname": {
"name": [
"hostB",
"hostname02"
]
}
}
}
ok: [localhost] => (item=[u'hostC', u'hostname03']) => {
"msg": {
"win_hostname": {
"name": [
"hostC",
"hostname03"
]
}
}
}
As you can see, it's paired each host from the inventory with a hostname from the hostnames list.
Update
Based on the additional information you've provided, I think what you
actually want is this:
- name: change hostname
win_hostname:
name: "{{ hostnames[group.company.index(inventory_hostname) }}"
This will assign one value from hostname to each host in your
inventory. We're looking up the position of the current
inventory_hostname in your group, and then using that to index into
the hostnames list.

Ansible playbook - syntax in set_fact with variables and ip filter - centos7

In centos7, I am trying to define a remote host, where IP is the back-end or front-end IP
Then I ordered the IPADDR filter and there were some obstacles.
For example,
# {{ ansible_all_ipv4.addresses | ipaddr('192.168.0.0/22') }}
['192.168.1.2', '192.168.2.2']
# {{ ansible_all_ipv4.addresses | ipaddr('192.168.1.0/24') }}
['192.168.1.2']
As my case, since in VM have 2 different IPs like 192.168.56.101 and 172.16.1.10, i want to pre-define the network and prefix by variables to get the 192.168.56.101. Then i set:
[defaults/main.yml]
backend:
network: 192.168.56.0
prefix: 24
[tasks/main.yml]
- debug: var="{{item}}"
with_items:
- "ansible_all_ipv4_addresses|ipaddr('''{{backend.network}}/{{backend.prefix}}''')"
result:
TASK [test : debug] ************************************************************
ok: [localhost] => (item=ansible_all_ipv4_addresses|ipaddr('''192.168.56.0/24''')) => {
"ansible_all_ipv4_addresses|ipaddr('''192.168.56.0/24''')": [
"192.168.56.101" <------ THAT IS WHAT I WANT
],
"changed": false,
"item": "ansible_all_ipv4_addresses|ipaddr('''192.168.56.0/24''')"
Then I try to set fact and export the result, its not my want.
- name: define backend ip
set_fact: backendIP="{{ item }}"
with_items:
- "ansible_all_ipv4_addresses|ipaddr('''{{network}}/{{prefix}}''')"
- debug: var=backendIP
result:
TASK [test : define backend ip] ***********************************************************
ok: [localhost] => (item=ansible_all_ipv4_addresses|ipaddr('''192.168.56.0/24'''))
TASK [test : debug] ************************************************************
ok: [localhost] => {
"backendIP": "ansible_all_ipv4_addresses|ipaddr('''192.168.56.0/24''')"
So how can I set the variable through this situation
Since you want a single result, you don’t need any loops.
This is what you want (if you are sure your filter matches a single address):
set_fact:
backend: "{{ ansible_all_ipv4_addresses|ipaddr(backend.network + '/' + backend.prefix|string) }}"

Ansible to store variable facts

Using Ansible here, I'm gathering facts about a container:
- name: start my container
lxd_container:
name: vm_srv1
state: started
register: st
- debug: msg="{{ st.addresses }}"
Running the playbook produces the following:
TASK [manager : debug] *********************************************************
ok: [lxc.myvmhost ] => {
"msg": {
"eth0": [
"10.0.3.103"
]
}
}
I would like to store eth0 value into a file
I have added
- debug: msg="{{ st.addresses['eth0'] }}"
output:
TASK [manager : debug] *********************************************************
ok: [lxc.myvmhost ] => {
"msg": [
"10.0.3.103"
]
}
When storing the output to a file
- lineinfile: dest=/tmp/file line="{{ st.addresses.eth0 }}"
I get the following:
Hello world
['10.0.3.103']
How can I store the IP address without any funny bagged that Ansible adds?
In your example eth0 is a list of ip addresses, so to fetch the first element of the given list, use:
st.addresses.eth0[0]

Resources