Access yaml list of dictionaries file with ansible - list

So I am trying take values from file, let's call it "test.yaml"
file looks like this (sorry for long output, but it is the shortest cut containing all patterns and structure):
---
results:
- failed: false
item: XXX.XX.XX.XX
invocation:
module_args:
validate_certs: false
vm_type: vm
show_tag: false
username: DOMAIN\domain-user
proxy_host:
proxy_port:
show_attribute: false
password: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
port: XXX
folder:
hostname: XXX.XX.XX.XX
changed: false
virtual_machines:
- ip_address: XXX.XX.XX.XX
mac_address:
- XX:XX:XX:aa:XX:XX
uuid: XXXX-XX-XX-XXXX-XXXXX
guest_fullname: Red Hat Enterprise Linux X (XX-bit)
moid: vm-XXX
folder: "/DOMAIN-INTERXION/vm"
cluster:
attributes: {}
power_state: poweredOn
esxi_hostname: esx.hostname
tags: []
guest_name: VMnameXX
vm_network:
XX:XX:XX:aa:XX:XX:
ipv6:
- XX::XXX:XX:XXXX
ipv4:
- XXX.XX.XX.XX
I would like, for example to have something like:
results.invocation.virtual_machines.ip_address
results.invocation.module_args.user_name
I tried all kind of stuff but it doesn't work :)
last attempt is this:
---
- name: demo how register works
hosts: localhost
tasks:
- name: Include all .json and .jsn files in vars/all and all nested directories (2.3)
include_vars:
file: test.yml
name: vm
- name: debug
debug:
msg: "{{ item.0.item }}"
with_subelements:
- "{{ vm.results }}"
- virtual_machines
register: subelement

following your structure and after fixing some errors:
results.invocation.virtual_machines.ip_address is results[0].virtual_machines[0].ip_address
and
results.invocation.module_args.user_name is results[0].invocation.module_args.username
(results and virtual_machines are arrays, write results[0] or results.0 is same)
so a sample of playbook doing job:
- name: vartest
hosts: localhost
tasks:
- name: Include all .json and .jsn files in vars/all and all nested directories (2.3)
include_vars:
file: test.yml
name: vm
- name: ip
set_fact:
ipadress: "{{ vm.results[0].virtual_machines[0].ip_address }}"
- name: username
set_fact:
username: "{{ vm.results[0].invocation.module_args.username }}"
- name: display
debug:
msg: "ip: {{ ipadress }} and username: {{ username }}"
result:
ok: [localhost] =>
msg: 'ip: XXX.XX.XX.XX and username: DOMAIN\domain-user'

Related

Parsing a list of strings in ansible

I have this list:
ok: [localhost] => {
"ansible_facts": {
"servs": [
"Is active: Yes\nHost Name: northrend\n Ip Address: 192.168.1.12",
"Is active: Yes\nHost Name: pandaria \n IP Address: 192.168.1.30"
]
},
I've been trying to parse this list to extract the value of "Host name" but I just keep on getting the following error:
fatal: [localhost]: FAILED! => {
"msg": "Unexpected templating type error occurred on ({{ servs | regex_search('Host Name: (.)+\n'),multiline=True) }}): expected string or buffer"
}
This is what I tried:
- set_fact:
output: {{ servs | regex_search('Host Name: (.)+\n'), multiline=True) }}
How do you properly parse the contents of this list?
The list servs is nearly valid YAML. Only the indentation of the attribute 'Ip Address' is wrong. One space shifted to the right. If you manage to get rid of this space, e.g.
servs:
- "Is active: Yes\nHost Name: northrend\nIp Address: 192.168.1.12"
- "Is active: Yes\nHost Name: pandaria \nIP Address: 192.168.1.30"
the situation would be trivial. Simply convert the list of strings to a list of dictionaries, e.g.
- set_fact:
servs_list: "{{ servs_list|d([]) + [item|from_yaml] }}"
loop: "{{ servs }}"
gives
servs_list:
- Host Name: northrend
Ip Address: 192.168.1.12
Is active: true
- Host Name: pandaria
IP Address: 192.168.1.30
Is active: true
If you can't fix the indentation parse the data on your own. The task below gives the same result
- set_fact:
servs_list: "{{ servs_list|d([]) + [item|
regex_replace(regex, replace)|
from_yaml] }}"
loop: "{{ servs }}"
vars:
regex: '\n '
replace: '\n'
Get the output
- set_fact:
output: "{{ servs_list|map(attribute='Host Name')|list }}"
gives
output:
- northrend
- Pandaria
You have multiple errors here:
regex_search works on strings, not lists. So you need to apply it to each list element.
Your regex creates one group for each character in your hostname. The group should be (.+).
You are returning the whole match (including Host Name:) and not just the name itself. You need to specify which matching group to return.
I guess the list is from the output of some script or in some file. It would be a lot better to have it as json or yaml and then use from_json or from_yaml.
If you want to keep it like that, you need to do this:
- hosts: all
vars:
servs:
- |
Is active: Yes
Host Name: northrend
Ip Address: 192.168.1.12
- |
Is active: Yes
Host Name: pandaria
IP Address: 192.168.1.30
tasks:
- debug:
msg: "{{ servs }}"
- set_fact:
output: "{{ servs | map('regex_search', 'Host Name: (.+)\n', '\\1') | list }}"
- debug:
msg: "{{ output }}"
This is the output:
TASK [debug] ************
ok: [local] => {
"msg": [
"Is active: Yes\nHost Name: northrend\nIp Address: 192.168.1.12\n",
"Is active: Yes\nHost Name: pandaria\nIP Address: 192.168.1.30\n"
]
}
TASK [set_fact] ***************
ok: [local]
TASK [debug] ************
ok: [local] => {
"msg": [
[
"northrend"
],
[
"pandaria"
]
]
}

Is there a way to deploy a file using Ansible copy and replace values from parameter store?

I would like to know if I can use Ansible template/copy and replace multiple values in a file(Ex: .properties,.xml, etc..) while deploying the file to destination with values from Parameter store?
Ex-file: app.properties
app.timeout.value=APP-TIMEOUT-VALUE
app.zone.value=APP-ZONE-VALUE
Ex-playbook: app.yaml
- name: Deploy app properties
template:
src: path/app_name.properties
dest: /../app_name.properties
notify:
- restart app
In the above example I want to replace values like(APP-TIMEOUT-VALUE, APP-ZONE-VALUE, etc..) with actual values stored in Parameter Store with Keys as(APP-TIMEOUT-VALUE, APP-ZONE-VALUE, etc..).
Someone please suggest me if there is a straight forward way for it without additional scripts.
Many Thanks
Given the file
shell> cat app.properties
app.timeout.value=APP_TIMEOUT_VALUE
app.zone.value=APP_ZONE_VALUE
Create the template
shell> cat app.properties.j2
app.timeout.value={{ APP_TIMEOUT_VALUE }}
app.zone.value={{ APP_ZONE_VALUE }}
If you can't create the template manually the block below would do it for you
- block:
- copy:
force: false
src: app.properties
dest: app.properties.j2
- lineinfile:
backrefs: true
path: app.properties.j2
regex: '^(.*)=\s*{{ item }}\s*$'
line: '\1={{ eval_open }}{{ item }}{{ eval_close }}'
loop:
- APP_TIMEOUT_VALUE
- APP_ZONE_VALUE
delegate_to: localhost
run_once: true
vars:
eval_open: "{{ '{{ ' }}"
eval_close: "{{ ' }}' }}"
Customize the paths and loop to your needs
Then, use the template. For example
- template:
src: app.properties.j2
dest: app_name.properties
vars:
APP_TIMEOUT_VALUE: 10
APP_ZONE_VALUE: 1
gives
shell> cat app_name.properties
app.timeout.value=10
app.zone.value=1
If you can't rename the names of the variables put them into a dictionary. There is no restriction for a key of a dictionary except for being unique. For example
shell> cat app.properties.j2
app.timeout.value={{ app_conf["APP-TIMEOUT-VALUE"] }}
app.zone.value={{ app_conf["APP-ZONE-VALUE"] }}
If you can't create the template manually the block below would do it for you
- block:
- copy:
force: false
src: app.properties
dest: app.properties.j2
- lineinfile:
backrefs: true
path: app.properties.j2
regex: '^(.*)=\s*{{ item }}\s*$'
line: '\1={{ eval_open }}app_conf["{{ item }}"]{{ eval_close }}'
loop:
- APP-TIMEOUT-VALUE
- APP-ZONE-VALUE
delegate_to: localhost
run_once: true
vars:
eval_open: "{{ '{{ ' }}"
eval_close: "{{ ' }}' }}"
Then, the template below will give the same result
- template:
src: app.properties.j2
dest: app_name.properties
vars:
app_conf:
APP-TIMEOUT-VALUE: 10
APP-ZONE-VALUE: 1

vmware_tag_manager, list to string trouble

Playbook below seems to be having trouble running thru the list of VM names..
error is as follows:
(type string). If this does not look like what you expect, quote the entire
value to ensure it does not change.
fatal: [127.0.0.1]: FAILED! => {"changed": false, "msg": "Failed to find the managed object for [
then spits out the list separated with \name\
what am I doing wrong here?
- hosts: localhost
vars_prompt:
- name: vcenter_hostname
prompt: Please provide the vcenter server hostname.
private: no
- name: vcenter_username
prompt: Please provide the vcenter server username.
private: no
- name: vcenter_password
prompt: Please provide the password for the username.
unsafe: yes
private: yes
vars:
vm_name: "{{lookup('file', '/etc/ansible/lists/lab_common_backup.txt')}}"
tasks:
- name: vm_tag_add
vmware_tag_manager:
hostname: '{{ vcenter_hostname }}'
username: '{{ vcenter_username }}'
password: '{{ vcenter_password }}'
validate_certs: no
tag_names:
- 'Labs: Common Backup'
object_name: "{{ vm_name }}"
object_type: VirtualMachine
state: remove
I was able to get this working by using .split(), the default delimiter for split is a space..
object_name: "{{ vm_name.split() }}"

Access element of list as dict

I'm trying to access the elements of a list, but whenever I try to access it's elements they are treated as text (see the debug output).
How can I loop over the elements of content[0]?
ansible version: 2.5.1
playbook.yml:
- hosts: localhost
gather_facts: no
vars:
content: []
tasks:
- name: Call the Consul API to get all registered services
uri:
url: "https://{{API_URL}}/v1/health/service/{{item}}?Status=critical"
headers:
Authorization: "Bearer {{TOKEN}}"
method: GET
return_content: yes
status_code: 200
body_format: json
force: yes
register: r
with_items:
- service1
- service2
- set_fact:
content: "{{ lookup('flattened', r | json_query('results[*].content')) }}"
- debug:
msg: "{{ content[0][0] | type_debug }}"
- debug:
msg: "{{ item | type_debug }}"
with_items: "{{ content[0] }}"
debug output:
TASK [debug]
ok: [127.0.0.1] => {
"msg": "dict"
}
TASK [debug]
ok: [127.0.0.1] => (item=None) => {
"msg": "AnsibleUnsafeText"
}
I'm hoping this question and answer will help you. I think the approach you are taking is the problem.

Getting correct subnet_id from Ansible [duplicate]

I've got a dictionary with different names like
vars:
images:
- foo
- bar
Now, I want to checkout repositories and afterwards build docker images only when the source has changed.
Since getting the source and building the image is the same for all items except the name I created the tasks with with_items: images
and try to register the result with:
register: "{{ item }}"
and also tried
register: "src_{{ item }}"
Then I tried the following condition
when: "{{ item }}|changed"
and
when: "{{ src_item }}|changed"
This always results in fatal: [piggy] => |changed expects a dictionary
So how can I properly save the results of the operations in variable names based on the list I iterate over?
Update: I would like to have something like that:
- hosts: all
vars:
images:
- foo
- bar
tasks:
- name: get src
git:
repo: git#foobar.com/repo.git
dest: /tmp/repo
register: "{{ item }}_src"
with_items: images
- name: build image
shell: "docker build -t repo ."
args:
chdir: /tmp/repo
when: "{{ item }}_src"|changed
register: "{{ item }}_image"
with_items: images
- name: push image
shell: "docker push repo"
when: "{{ item }}_image"|changed
with_items: images
So how can I properly save the results of the operations in variable names based on the list I iterate over?
You don't need to. Variables registered for a task that has with_items have different format, they contain results for all items.
- hosts: localhost
gather_facts: no
vars:
images:
- foo
- bar
tasks:
- shell: "echo result-{{item}}"
register: "r"
with_items: "{{ images }}"
- debug: var=r
- debug: msg="item.item={{item.item}}, item.stdout={{item.stdout}}, item.changed={{item.changed}}"
with_items: "{{r.results}}"
- debug: msg="Gets printed only if this item changed - {{item}}"
when: item.changed == true
with_items: "{{r.results}}"