How can I transform some items values into new items value? - filter

This is my var:
zones:
- hostname: host10
cname: "{{cname1|default(omit)}}"
zone: ["v120","v121","linux"]
ips: 8
- hostname: host11
cname: "{{cname2|default(omit)}}"
zone: ["v120","v121"]
ips: 10
- hostname: host12
zone: [linux]
ips: 88
How can I build the same var with little replacement on zones.zone (substitute v120 to 120, v121 to 121, linux to v120):
zones:
- hostname: host10
cname: "{{cname1|default(omit)}}"
zone: ["120","121","120"]
ips: 8
- hostname: host11
cname: "{{cname2|default(omit)}}"
zone: ["120","121"]
ips: 10
- hostname: host12
zone: [120]
ips: 88
I tried a lot of combinations with some filters, in particulary "combine" and "regex_replace"
regex_replace('^v(.*)$', '\\1')
but never succeded to get the final objective. It seemed to be so simple to do, I must miss something...

Create a dictionary substitute and iterate zones. In each iteration extract substituted zone, combine the dictionary with the item and concatenate the list zones2. For example
- set_fact:
zones2: "{{ zones2|default([]) +
[item|combine({'zone': zone})] }}"
loop: "{{ zones }}"
vars:
zone: "{{ item.zone|map('extract', substitute)|list }}"
substitute:
v120: 120
v121: 121
linux: 120
- debug:
var: zones2
gives
zones2:
- hostname: host10
ips: 8
zone:
- 120
- 121
- 120
- hostname: host11
ips: 10
zone:
- 120
- 121
- hostname: host12
ips: 88
zone:
- 120
Mapping regex_replace
- set_fact:
zones2: "{{ zones2|default([]) +
[item|combine({'zone': zone})] }}"
loop: "{{ zones }}"
vars:
zone: "{{ item.zone|
map('regex_replace', regex, replace)|
map('regex_replace', regex2, replace2)|
list }}"
regex: '^v(.*)$'
replace: '\1'
regex2: '^linux$'
replace2: '120'
- debug:
var: zones2
gives
zones2:
- hostname: host10
ips: 8
zone:
- '120'
- '121'
- '120'
- hostname: host11
ips: 10
zone:
- '120'
- '121'
- hostname: host12
ips: 88
zone:
- '120'
"Understand what does the extract filter"
Show the results of the extract filter
- debug:
msg: |
{{ item.zone|to_yaml }}{{ zone|to_yaml }}
loop: "{{ zones }}"
vars:
zone: "{{ item.zone|map('extract', substitute)| list }}"
substitute:
v120: 120
v121: 121
linux: 120
gives (abridged)
msg: |-
[v120, v121, linux]
[120, 121, 120]
msg: |-
[v120, v121]
[120, 121]
msg: |-
[linux]
[120]
"Understand what does the combine solution"
Show the results of the combine filter
- debug:
msg: "{{ item|combine({'zone': zone})|
to_yaml}}"
loop: "{{ zones }}"
vars:
zone: "{{ item.zone|map('extract', substitute)| list }}"
substitute:
v120: 120
v121: 121
linux: 120
gives (abridged)
msg: |-
cname: __omit_place_holder__3938838202c238b505bfc39af57a09b4fe9972f6
hostname: host10
ips: 8
zone: [120, 121, 120]
msg: |-
cname: __omit_place_holder__3938838202c238b505bfc39af57a09b4fe9972f6
hostname: host11
ips: 10
zone: [120, 121]
msg: |-
hostname: host12
ips: 88
zone: [120]
""zones2": "[AnsibleUndefined, AnsibleUndefined]" How can you explian that?"
The code below works as expected
- set_fact:
zones2: "{{ item.zone|map('extract', substitute)|list }}"
loop: "{{ zones }}"
register: zones_list
vars:
substitute:
v120: 120
v121: 121
linux: 120
- debug:
msg: "{{ zones_list.results|map(attribute='ansible_facts')|list|
to_yaml }}"
give (abridged)
msg: |-
- zones2: [120, 121, 120]
- zones2: [120, 121]
- zones2: [120]

Related

how to make a list from ansible_facts with multiple hosts

I'm trying to make a list with IP addresses of various hosts and then use this list in another task. My question is, how can I pick an IP (I need the public IP) from the output of each host and add it to a list? I need the IPs that do not start with 10..
Later, I need to use this list in another task.
I extract this information by running this playbook:
- hosts: facts
become: true
gather_facts: True
tasks:
- debug:
msg: "The ip: {{ item }}"
with_items: "{{ ansible_all_ipv4_addresses }}"
Later, I need to use this list in another task:
- wait_for:
host: "{{ item[0] }}"
port: "{{ item[1] }}"
state: started
delay: 0
timeout: 2
delegate_to: localhost
become: false
ignore_errors: no
ignore_unreachable: yes
register: result
failed_when: not result.failed
with_nested:
- [ IP LIST HERE]
- [443,80,9200,9300,22,5432,6432]
You can access those values from the hostvars right away, then use a reject filter with a match test in order to reject what you don't want to test for.
Which, in a debug task would gives:
# note: ports list reduced for brevity
- debug:
msg: "I should wait for interface {{ item.0 }}:{{ item.1 }}"
loop: >-
{{
hostvars
| dict2items
| selectattr('key', 'in', ansible_play_hosts)
| map(attribute='value.ansible_all_ipv4_addresses', default=[])
| flatten
| reject('match', '10\..*')
| product(_ports)
}}
loop_control:
label: "{{ item.0 }}"
run_once: true
delegate_to: localhost
vars:
_ports:
- 22
- 80
In my lab, this give:
ok: [ansible-node-1 -> localhost] => (item=172.18.0.3) =>
msg: I should wait for interface 172.18.0.3:22
ok: [ansible-node-1 -> localhost] => (item=172.18.0.3) =>
msg: I should wait for interface 172.18.0.3:80
ok: [ansible-node-1 -> localhost] => (item=172.18.0.4) =>
msg: I should wait for interface 172.18.0.4:22
ok: [ansible-node-1 -> localhost] => (item=172.18.0.4) =>
msg: I should wait for interface 172.18.0.4:80
Try the example below
shell> cat pb.yml
- hosts: all
vars:
ip_list: "{{ ansible_play_hosts|
map('extract', hostvars, 'ansible_all_ipv4_addresses')|
map('first')|list }}"
ip_list_reject: "{{ ip_list|reject('match', '10\\.')|list }}"
tasks:
- setup:
gather_subset: network
- block:
- debug:
var: ip_list
- debug:
var: ip_list_reject
- wait_for:
host: "{{ item.0 }}"
port: "{{ item.1 }}"
state: started
delay: 0
timeout: 2
delegate_to: localhost
register: result
with_nested:
- "{{ ip_list_reject }}"
- [443, 80, 9200, 9300, 22, 5432, 6432]
run_once: true

Ansible: Create dictionary from an intent file

shell> cat myfile.yml
"ABC":
"ABC-C01":
- host: "a1"
prefixlen: "19"
- host: "a2"
prefixlen: "19"
"DEF":
"DEF-C01":
- host: "d1"
prefixlen: "19"
- host: "d2"
prefixlen: "19"
My expected answer below from the intent file :
ABC-C01:
- a1.domain
- a2.domain
DEF-C01:
- d1.domain
- d2.domain
I was able to get the list individually.
- include_vars:
file: myfile.yml
name: myfile
- set_fact:
dc: "{{ myfile.keys()|list }}"
Gives: 'ABC' and 'DEF'
- set_fact:
cl: "{{ cl|default([]) + myfile[item].keys()|list }}"
with_items: "{{ dc}}"
Gives: 'ABC-C01' and 'DEF-C01'
Put the below declarations into the vars
myfile: "{{ lookup('file', 'myfile.yml')|from_yaml }}"
myfile_groups: "{{ dict(myfile.values()|
map('dict2items')|
json_query(_query)) }}"
_query: '[].[key, value[].join(``,[host, `.domain`])]'
gives what you want
myfile_groups:
ABC-C01:
- a1.domain
- a2.domain
DEF-C01:
- d1.domain
- d2.domain

Variable is empty or no defined in Ansible

When I run my playbook I see the following error:
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: list object has no element 0\n\nThe e
rror appears to be in '/etc/ansible/loyalty/tasks/create_ec2_stage.yaml': line 63, column 7, but may\nbe elsewhere in the file depending on the exac
t syntax problem.\n\nThe offending line appears to be:\n\n register: ec2_metadata\n - name: Parse < JSON >\n ^ here\n"}
I run the playbook this way:
/usr/bin/ansible-playbook -i hosts --extra-vars "CARRIER=xx" tasks/create_ec2_stage.yaml
Here is my playbook:
---
- name: New EC2 instances
hosts: localhost
gather_facts: no
vars_files:
- /etc/ansible/loyalty/vars/vars.yaml
tasks:
- name: Run EC2 Instances
amazon.aws.ec2_instance:
name: "new-{{ CARRIER }}.test"
aws_secret_key: "{{ ec2_secret_key }}"
aws_access_key: "{{ ec2_access_key }}"
region: us-east-1
key_name: Kiu
instance_type: t2.medium
image_id: xxxxxxxxxxxxx
wait: yes
wait_timeout: 500
volumes:
- device_name: /dev/xvda
ebs:
volume_type: gp3
volume_size: 20
delete_on_termination: yes
vpc_subnet_id: xxxxxxxxxxxx
network:
assign_public_ip: no
security_groups: ["xxxxxxxxxx", "xxxxxxxxxxx", "xxxxxxxxxxxxxx"]
tags:
Enviroment: TEST
count: 1
- name: Pause Few Seconds
pause:
seconds: 20
prompt: "Please wait"
- name: Get Information for EC2 Instances
ec2_instance_info:
region: us-east-1
filters:
"tag:Name": new-{{ CARRIER }}.test
register: ec2_metadata
- name: Parse JSON
set_fact:
ip_addr: "{{ ec2_metadata.instances[0].network_interfaces[0].private_ip_address }}"
If I create a slightly smaller playbook to query the private IP address of an existing instance, I don’t see any error.
---
- name: New EC2 Instances
hosts: localhost
gather_facts: no
vars_files:
- /etc/ansible/loyalty/vars/vars.yaml
vars:
pwd_alias: "{{ lookup('password', '/dev/null length=15 chars=ascii_letters') }}"
CARRIER_UPPERCASE: "{{ CARRIER | upper }}"
tasks:
- set_fact:
MY_PASS: "{{ pwd_alias }}"
- name: Get EC2 info
ec2_instance_info:
region: us-east-1
filters:
"tag:Name": new-{{ CARRIER }}.stage
register: ec2_metadata
- name: Parsing JSON
set_fact:
ip_addr: "{{ ec2_metadata.instances[0].network_interfaces[0].private_ip_address }}"
- name: Show Result
debug:
msg: "{{ ip_addr }}"
Results in
TASK [Show Result] ******************************************************
ok: [localhost] => {
"msg": "172.31.x.x"
}
I am creating an EC2 instance on Amazon and querying the private IP to use that IP on other services like router53 and Cloudflare, other tasks do not add them because the error is in the fact.
You don't have to query AWS with the ec2_instance_info module since the module amazon.aws.ec2_instance already returns you the newly created EC2 when the parameter wait is set to yes, as it is in your case.
You just need to register the return of this task.
So, given the two tasks:
- name: Run EC2 Instances
amazon.aws.ec2_instance:
name: "new-{{ CARRIER }}.test"
aws_secret_key: "{{ ec2_secret_key }}"
aws_access_key: "{{ ec2_access_key }}"
region: us-east-1
key_name: Kiu
instance_type: t2.medium
image_id: xxxxxxxxxxxxx
wait: yes
wait_timeout: 500
volumes:
- device_name: /dev/xvda
ebs:
volume_type: gp3
volume_size: 20
delete_on_termination: yes
vpc_subnet_id: xxxxxxxxxxxx
network:
assign_public_ip: no
security_groups: ["xxxxxxxxxx", "xxxxxxxxxxx", "xxxxxxxxxxxxxx"]
tags:
Enviroment: TEST
count: 1
register: ec2
- set_fact:
ip_addr: "{{ ec2.instance[0].private_ip_address }}"
You should have the private IP address of the newly created EC2 instance.

Loop from 0 to 100 with ansible

I would like to loop from s0 to s60 and from s100 to s100 with this command:
- name: "Network scan at port 22 {{ nom_base }}"
when: inventory_hostname in groups['exos_switch']
wait_for:
port: 22
host: "{{ nom_base }}-{{ item }}"
state: started
timeout: 2
with_items:
- s0
- s1
- s2
- s3
- s4
- s5
....
- s60
- s100
...
- s110
Any idea?
In respect to your question and comment a solution like
- name: Show sequence
debug:
msg: "s{{ item }}"
with_sequence:
- "0-60"
- "100-110"
tags: seq
might work for you.
- name: "Network scan at port 22 {{ nom_base }}"
wait_for:
port: 22
host: "{{ nom_base }}-s{{ item }}"
state: started
timeout: 2
with_sequence:
- "0-60"
- "100-110"
when: inventory_hostname in groups['exos_switch']

Ansible - Working with block module

I'm starting out with Ansible and AWS.
I've created a playbook that launch new instance and later should attach existing volume to the new instance:
- name: launch instance
ec2:
key_name: "{{ aws_vars.key_name }}"
group: "{{ aws_vars.security_group }}"
instance_type: "{{ aws_vars.instance_type }}"
image: "{{ aws_vars.image }}"
region: "{{ aws_vars.region }}"
wait: yes
count: 1
instance_tags: "{{ tags }}"
monitoring: yes
vpc_subnet_id: "{{ subnetid }}"
assign_public_ip: yes
register: destination
- name: Attach volumes
ec2_vol:
device_name: xvdf
instance: "{{ destination.instances[0].instance_id }}"
region: "{{ aws_vars.region }}"
tags: "{{ ec2_tags }}"
id: "{{ volume_id }}"
delete_on_termination: yes
with_items: "{{ destination }}"
So far, so good, everything works.
I would like to add a clean-up method, so in case there will be an error of any type in the later modules, I won't have any garbage instances.
I've understood that the idea here will be to use block module, however when I've tried working with block here, nothing really happens:
---
# EC2 Migrations.
- hosts: localhost,
connection: local
gather_facts: no
tasks:
- name: Vars
include_vars:
dir: files
name: aws_vars
- name: Create instance
block:
- debug:
msg: "Launcing EC2"
notify:
- launch_instance
- Cloudwatch
rescue:
- debug:
msg: "Rolling back"
notify: stop_instance
handlers:
- name: launch_instance
ec2:
key_name: "{{ aws_vars.key_name }}"
group: "{{ aws_vars.security_group }}"
instance_type: "{{ aws_vars.instance_type }}"
image: "{{ aws_vars.image }}"
region: "{{ region }}"
wait: yes
count: 1
monitoring: yes
assign_public_ip: yes
register: new_ec2
- debug: msg="{{ new_ec2.instances[0].id }}"
- name: stop_instance
ec2:
instance_type: "{{ aws_vars.instance_type }}"
instance_ids: "{{ new_ec2.instances[0].id }}"
state: stopped
- name: Cloudwatch
ec2_metric_alarm:
state: present
region: "{{ aws_vars.region }}"
name: "{{ new_ec2.id }}-High-CPU"
metric: "CPUUtilization"
namespace: "AWS/EC2"
statistic: Average
comparison: ">="
threshold: "90.0"
period: 300
evaluation_periods: 3
unit: "Percent"
description: "Instance CPU is above 90%"
dimensions: "{'InstanceId': '{{ new_ec2.instances[0].id }}' }"
alarm_actions: "{{ aws_vars.sns_arn }}"
ok_actions: "{{ aws_vars.sns_arn }}"
with_items: "{{ new_ec2 }}"
You put debug task into the block. The debug module returns ok status, so:
it does not call a handler (this required changed status),
1.the rescue-section is never triggered (this requires failed status).
Thus it is expected that "nothing really happens".
You need to put your actual tasks into the block (I leave them intact, assuming they are correct):
- name: Create instance
block:
- name: launch_instance
ec2:
key_name: "{{ aws_vars.key_name }}"
group: "{{ aws_vars.security_group }}"
instance_type: "{{ aws_vars.instance_type }}"
image: "{{ aws_vars.image }}"
region: "{{ region }}"
wait: yes
count: 1
monitoring: yes
assign_public_ip: yes
register: new_ec2
- debug:
msg: "{{ new_ec2.instances[0].id }}"
- name: Cloudwatch
ec2_metric_alarm:
state: present
region: "{{ aws_vars.region }}"
name: "{{ new_ec2.id }}-High-CPU"
metric: "CPUUtilization"
namespace: "AWS/EC2"
statistic: Average
comparison: ">="
threshold: "90.0"
period: 300
evaluation_periods: 3
unit: "Percent"
description: "Instance CPU is above 90%"
dimensions: "{'InstanceId': '{{ new_ec2.instances[0].id }}' }"
alarm_actions: "{{ aws_vars.sns_arn }}"
ok_actions: "{{ aws_vars.sns_arn }}"
with_items: "{{ new_ec2 }}"
rescue:
- name: stop_instance
ec2:
instance_type: "{{ aws_vars.instance_type }}"
instance_ids: "{{ new_ec2.instances[0].id }}"
state: stopped

Resources