Ansible - how to split a list into two lists? - list

I searched and tried every way to split a list into two lists.
There were no conventional filters or anything supported.
There were examples and posts about combining lists into one, but not the other way around.
I developed below code but it does not work with very strange reason, list does not have attribute 0?
---
- hosts: localhost
gather_facts: no
vars:
- listA: ['a','b','c','d']
- listB: []
- listC: []
tasks:
- block:
- debug:
var: listA[0]
- debug:
var: listB
- debug:
var: listC
- set_fact:
listB: "{{ listB + [listA[item]] }}"
with_sequence: start=0 end=3 stride=2
- set_fact:
listC: "{{ listC + [listA[item]] }}"
with_sequence: start=1 end=3 stride=2
- block:
- debug:
var: listA
- debug:
var: listB
- debug:
var: listC
This is the test run outcome with Ansible 2.1.1.0
$ ansible-playbook test_sequenceeasy.yml
[WARNING]: log file at '{{planfile | dirname}}/AnsibleLog.txt' is not writeable and we cannot create it, aborting
PLAY [localhost] ***************************************************************
TASK [debug] *******************************************************************
ok: [localhost] => {
"listA[0]": "a"
}
TASK [debug] *******************************************************************
ok: [localhost] => {
"listB": []
}
TASK [debug] *******************************************************************
ok: [localhost] => {
"listC": []
}
TASK [set_fact] ****************************************************************
fatal: [localhost]: FAILED! => {"failed": true, "msg": "the field 'args' has an invalid value, which appears to include a variable that is undefined. The error was: 'list object' has no attribute u'0'\n\nThe error appears to have been in '/apps/infra/Tools/Ansible_WLNMiddleware/test_sequenceeasy.yml': line 17, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n var: listC\n - set_fact:\n ^ here\n"}
NO MORE HOSTS LEFT *************************************************************
[WARNING]: Could not create retry file 'test_sequenceeasy.retry'. [Errno 2] No such file or directory: ''

I found the cause.
The problem about the error message "'list object' has no attribute u'0'" was that Ansible is not recognizing 0 as number, but it think 0 is string.
What? How can Ansible iterate start, end and stride and store the value into "String"? - That I don't know.
But the problem solved with below code update:
---
- hosts: localhost
gather_facts: no
vars:
- listA: ['a','b','c','d']
- listB: []
- listC: []
tasks:
- block:
- debug:
var: listA[0]
- debug:
var: listB
- debug:
var: listC
- set_fact:
listB: "{{ listB + [ listA[item|int] ] }}"
with_sequence: start=0 end=3 stride=2
- set_fact:
listC: "{{ listC + [ listA[item|int] ] }}"
with_sequence: start=1 end=3 stride=2
- block:
- debug:
var: listA
- debug:
var: listB
- debug:
var: listC
And the result is:
$ ansible-playbook test_sequenceeasy.yml
[WARNING]: log file at '{{planfile | dirname}}/AnsibleLog.txt' is not writeable and we cannot create it, aborting
PLAY [localhost] ***************************************************************
TASK [debug] *******************************************************************
ok: [localhost] => {
"listA[0]": "a"
}
TASK [debug] *******************************************************************
ok: [localhost] => {
"listB": []
}
TASK [debug] *******************************************************************
ok: [localhost] => {
"listC": []
}
TASK [set_fact] ****************************************************************
ok: [localhost] => (item=0)
ok: [localhost] => (item=2)
TASK [set_fact] ****************************************************************
ok: [localhost] => (item=1)
ok: [localhost] => (item=3)
TASK [debug] *******************************************************************
ok: [localhost] => {
"listA": [
"a",
"b",
"c",
"d"
]
}
TASK [debug] *******************************************************************
ok: [localhost] => {
"listB": [
"a",
"c"
]
}
TASK [debug] *******************************************************************
ok: [localhost] => {
"listC": [
"b",
"d"
]
}
PLAY RECAP *********************************************************************
localhost : ok=8 changed=0 unreachable=0 failed=0

Related

Ansible tagging AWS EBS volumes using ec2_instance_info module to pull info

I'm running ansible 2.9
I am trying pull ec2 info with the "ec2_instance_info" module.
Ultimately, I need to tag instance-attached EBS volumes with the instance_id, and EBS device_name.
These are the tasks that I have working:
- name: Gather ec2_metadata_facts
action: ec2_metadata_facts
- name: pull instance info with ec2_instance_info
ec2_instance_info:
region: "{{ lookup('env','AWS_DEFAULT_REGION') }}"
aws_access_key: "{{ lookup('env','AWS_ACCESS_KEY_ID') }}"
aws_secret_key: "{{ lookup('env','AWS_SECRET_ACCESS_KEY') }}"
instance_ids: "{{ ansible_ec2_instance_id }}"
register: ec2_node_info
#prints Too much info, but useful for debugging
# - name: print ALL info from ec2_instance_info
# debug:
# msg: "dict: {{ec2_node_info}} "
- name: print Flattened dev_name and vol_ID
debug:
msg:
- "{{ ec2_node_info | json_query(dev_name)|flatten(levels=1) }}"
- "{{ ec2_node_info | json_query(vol_id)|flatten(levels=1)}}"
vars:
vol_id: "instances[*].block_device_mappings[*].ebs.volume_id"
dev_name: "instances[*].block_device_mappings[*].device_name"
This is the output:
TASK [Gather ec2_metadata_facts (use -vv to show all)] ********************************************************
ok: [prd-node-01]
TASK [pull instance info with ec2_instance_info] ********************************************************
ok: [prd-node-01]
TASK [print Flattened dev_name and vol_ID] *********************************************************
ok: [prd-node-01] =>
msg:
- - /dev/sdf
- /dev/sdg
- /dev/sdh
- /dev/sdi
- /dev/sda1
- - vol-0758995d43a43aa04
- vol-0037fd24cc8229551
- vol-0ab0ae909b39f2b32
- vol-0987e6f6af374ec20
- vol-0ebf1d896c94b1fbf
I have the data I need. But, I can't figure out how to access the individual elements.
json_query is new to me. I was originally trying something like this:
- name: Print Volume elements
debug:
msg: "{{item.device_name}} {{item.volume_id}}"
loop: "{{ ec2_node_info.instances[0].block_device_mapping }}"
But that gives:
TASK [Print Volume elements] ********************************************************
fatal: [prd-node-01]: FAILED! =>
msg: '''dict object'' has no attribute ''block_device_mapping'''
many of my nodes have 5 volumes. Some have only one, some have more.
I need to be able to reference:
instance_id: {{volume_id[1]}} {{dev_name[1]}}
instance_id: {{volume_id[2]}} {{dev_name[2]}}
instance_id: {{volume_id[?]}} {{dev_name[?]}}
etc...
Any help appreciated.
json_query() is, as usual, an unnecessary complication. Use plain Jinja instead, with the subelements filter:
- debug:
msg: "{{ item.0.instance_id }} / {{ item.1.ebs.volume_id }} / {{ item.1.device_name }}"
loop: "{{ ec2_node_info.instances | subelements('block_device_mappings') }}"
loop_control:
label: "{{ item.0.instance_id }} / {{ item.1.device_name }}"
TASK [debug] *******************************************************************
ok: [localhost] => (item=i-0fe0b60f708f8adf7 / /dev/xvda) => {
"msg": "i-0fe0b60f708f8adf7 / vol-0a454b7f9f3d17dda / /dev/xvda"
}
ok: [localhost] => (item=i-0fe0b60f708f8adf7 / xvdd) => {
"msg": "i-0fe0b60f708f8adf7 / vol-0f4f7e914b7f7f53c / xvdd"
}
ok: [localhost] => (item=i-0fe0b60f708f8adf7 / /dev/sdf) => {
"msg": "i-0fe0b60f708f8adf7 / vol-0ef1bf1ddc3abe6ff / /dev/sdf"
}
ok: [localhost] => (item=i-052df13a8406e870d / /dev/xvda) => {
"msg": "i-052df13a8406e870d / vol-0a0132a2ee3a087ce / /dev/xvda"
}
ok: [localhost] => (item=i-052df13a8406e870d / xvdd) => {
"msg": "i-052df13a8406e870d / vol-0870514b73c8f7166 / xvdd"
}
ok: [localhost] => (item=i-052df13a8406e870d / xvde) => {
"msg": "i-052df13a8406e870d / vol-062b98fc235ef094e / xvde"
}
ok: [localhost] => (item=i-052df13a8406e870d / xvdf) => {
"msg": "i-052df13a8406e870d / vol-0db0eaa45ddfa3df4 / xvdf"

Ansible not executing rds_instance task due to wrong variable evaluation

I am struggeling with ansible since a task defined a playbook is not being executed to manage an AWS rds instance.
This is the command I execute within an jenkins pipeline:
state: "running"
identifier: "myDatabase"
sh "ansible-playbook ${env.WORKSPACE}/cost-optimization/ansible/manage_rds.yml --extra-vars 'instanceState=${state} identifier=${dbsIdentifier}'"
The playbook looks like this:
manage_rds.yml:
---
- hosts: localhost
vars:
rdsState: "{{instanceState}}"
rdsIdentifier: "{{identifier|lower}}"
tasks:
- name: "Starting RDS instances"
rds_instance:
state: running
db_instance_identifier: "{{ rdsIdentifier }}"
wait: yes
register: rds_result
when: rdsState == "running"
- name: "Stopping RDS instances"
rds_instance:
state: stopping
db_instance_identifier: "{{ rdsIdentifier }}"
wait: yes
register: rds_result
when: rdsState == "stopped"
- name: Show RDS result
debug:
var: rds_result
- import_tasks: tasks/task_create_partial_report.yml
vars:
identifier: "{{rdsIdentifier|lower}}"
partial: "db"
I expect the AWS RDS instance is being spinned up.
Instead the result looks like this:
TASK [Starting RDS instances] **************************************************
ok: [localhost]
TASK [Stopping RDS instances] **************************************************
skipping: [localhost]
TASK [Show RDS result] *********************************************************
ok: [localhost] => {
"rds_result": {
"changed": false,
"skip_reason": "Conditional result was False",
"skipped": true
}
}
Any idea how to solve this?
EDIT:
I followed the recommendation from below.
However, the RDS instance is still not affected:
+ ansible-playbook /var/lib/jenkins/workspace/.../ansible/manage_rds.yml --extra-vars 'instanceState=running identifier=myDatabase'
PLAY [localhost] ***************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [Starting RDS instances] **************************************************
ok: [localhost]
TASK [Show RDS result] *********************************************************
ok: [localhost] => {
"rds_result": {
"allocated_storage": 20,
"associated_roles": [],
"auto_minor_version_upgrade": false,
"availability_zone": "eu-central-1a",
"backup_retention_period": 21,
"ca_certificate_identifier": "rds-ca-2015",
"changed": false,
"character_set_name": "SQL_Latin1_General_CP1_CI_AS",
"copy_tags_to_snapshot": true,
"db_instance_arn": "arn:aws:rds:mydatabase",
"db_instance_class": "db.t2.micro",
"db_instance_identifier": "mydatabase",
"db_instance_port": 0,
"db_instance_status": "stopped",
"db_parameter_groups": [
{
"db_parameter_group_name": "....-sqlserver-ex-14-00",
"parameter_apply_status": "in-sync"
}
],
...
So the state remains stopped althoug it is set to running.
register will register the result of a task, what ever that is even, if it is skipped.
In your case, you run the first task when: rdsState == "running", register the result of that successful run and immediately override your registered var with a skipped task result.
There are several solutions to go other this. I'll just give 2 out of my hat here:
Debug the var after each task
You could add the following task after each rds_instance call
- name: Show RDS result
debug:
var: rds_result
when: rds_result is not skipped
That would only print out after each task if it was actually run.
Run a single parameterized task
You need a mapping between your variable value and the one expected in the module params. Add the following to your vars:
my_rds_state:
running:
name: started
description: Starting
stopped:
name: stopped
description: Stopping
Then you can have a single task that will do the job whatever the param:
- name: "{{ my_rds_state[rdsState].description }} RDS instances"
rds_instance:
state: "{{ my_rds_state[rdsState].name }}"
db_instance_identifier: "{{ rdsIdentifier }}"
wait: yes
register: rds_result
- name: Show RDS result
debug:
var: rds_result

Ansible - Check if string exists in file with regex

I need to validate that the PermitRootLogin parameter is equal to "no", for example:
PermitRootLogin no
But sometimes between these words there is more than one space. For this reason I use a regex, but apparently I do it wrong. This is the line that seems to be bad:
when: check_config.stdout.find('PermitRootLogin\s+no') != -1
Any idea how to fix this?
- hosts: redhat
tasks:
- name: check file
shell: cat /etc/ssh/sshd_config
register: check_config
- name: compare string
when: check_config.stdout.find('PermitRootLogin\s+no') != -1
debug: msg="this server is ok"
Q: "Validate that the PermitRootLogin parameter is equal to no."
A: Put the below declaration into the vars
match_lines: "{{ check_config.stdout_lines|
map('regex_search', '^\\s*PermitRootLogin\\s+no$')|
select }}"
and test the length of the list
- debug:
msg: this server is OK
when: match_lines|length > 0
Example of a complete playbook for testing
- hosts: localhost
vars:
match_lines: "{{ check_config.stdout_lines|
map('regex_search', '^\\s*PermitRootLogin\\s+no$')|
select }}"
tasks:
- command: cat /etc/ssh/sshd_config
register: check_config
- debug:
var: match_lines
- debug:
msg: This server is OK
when: match_lines|length > 0
gives, for example (abridged)
TASK [debug] *******************************************
ok: [localhost] =>
match_lines:
- PermitRootLogin no
TASK [debug] *******************************************
ok: [localhost] =>
msg: This server is OK
Given the inventory below set hosts -hosts: rehat
shell> cat hosts
[redhat]
test_11
test_12
test_13
The playbook gives, for example (abridged)
TASK [debug] *******************************************
ok: [test_11] =>
match_lines:
- PermitRootLogin no
ok: [test_12] =>
match_lines: []
ok: [test_13] =>
match_lines: []
TASK [debug] *******************************************
skipping: [test_12]
ok: [test_11] =>
msg: This server is OK
skipping: [test_13]
You can use lookup to simplify the task if the play is running at the localhost only. For example, the playbook below gives the same result
- hosts: localhost
tasks:
- debug:
msg: This server is OK
when: match_lines|length > 0
vars:
match_lines: "{{ lookup('file', '/etc/ssh/sshd_config').splitlines()|
map('regex_search', '^\\s*PermitRootLogin\\s+no$')|
select }}"
If you want to put/replace the line in the config use lineinfile. For example,
- lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PermitRootLogin(.*)$'
line: 'PermitRootLogin no'

Extract substring from variable in Ansible

Edited:
I wrote this playbook but it does not show the extracted variable:
---
- hosts: fppc
gather_facts: false
remote_user: xyz
connection: local
tasks:
- name: N1
ios_command:
commands:
- sh run | i bann
register: sr
- debug: msg="{{ sr.stdout}}"
- set_fact:
rid: "{{ sr.stdout | regex_search('.*ID: (..)') }}"
- debug: msg="{{ rid }}"
Execution:
ansible#Ansible:~$ ansible-playbook pb1.yml
PLAY [fppc] *************************************************************************
TASK [N1] ***************************************************************************
ok: [192.168.250.161]
TASK [debug] ************************************************************************
ok: [192.168.250.161] => {
"msg": [
"banner login ^CID: A4"
]
}
TASK [set_fact] *********************************************************************
fatal: [192.168.250.161]: FAILED! => {"failed": true, "msg": "Unexpected templating type error occurred on ({{ sr.stdout | regex_search('.*ID: (..)') }}): expected string or buffer"}
to retry, use: --limit #/home/ansible/pb1.retry
PLAY RECAP **************************************************************************
192.168.250.161 : ok=2 changed=0 unreachable=0 failed=1
ansible#Ansible:~$
I found the solution:
---
- hosts: fppc
gather_facts: false
remote_user: xyz
connection: local
tasks:
- name: N1
ios_command:
commands:
- sh run
register: sr
- set_fact:
temp: "{{ sr.stdout_lines | join }}"
- set_fact:
rid: "{{ temp | regex_replace('.*ID: (..).*', '\\1') }}"
- debug: msg="{{ rid }}"
Execution:
ansible#Ansible:~$ ansible-playbook pb1.yml
PLAY [fppc] ********************************************************************
TASK [N1] **********************************************************************
ok: [192.168.250.161]
TASK [set_fact] ****************************************************************
ok: [192.168.250.161]
TASK [set_fact] ****************************************************************
ok: [192.168.250.161]
TASK [debug] *******************************************************************
ok: [192.168.250.161] => {
"msg": "A4"
}
PLAY RECAP *********************************************************************
192.168.250.161 : ok=4 changed=0 unreachable=0 failed=0
Other solution, more logical:
---
- hosts: fppc
gather_facts: false
remote_user: xyz
connection: local
tasks:
- name: N1
ios_command:
commands:
- sh run
register: sr
- set_fact:
rid: "{{ sr.stdout | regex_search('(?<=ID:)\\s+\\S+', multiline=True) | trim }}"
- debug: msg="{{ rid }}"
Execution:
ansible#Ansible:~$ ansible-playbook pb1.yml
PLAY [fppc] ********************************************************************
TASK [N1] **********************************************************************
ok: [192.168.250.161]
TASK [set_fact] ****************************************************************
ok: [192.168.250.161]
TASK [debug] *******************************************************************
ok: [192.168.250.161] => {
"msg": "A4"
}
PLAY RECAP *********************************************************************
192.168.250.161 : ok=3 changed=0 unreachable=0 failed=0

ansible parse extra var use default if regex fails

I have the following code which isn't throwing an error but the fact is empty
- shell: echo '{{ p }}'
register: results
- debug:
var: results
- set_fact:
myrepo: "{{ results.stdout | regex_search(regexp,'\\1') | default ( {'0':'global'} ) }}"
vars:
regexp: '(.*)/(.*)'
Here is the output
TASK [command] **************************************************************************************************************************************************************************************
changed: [localhost]
TASK [debug] ****************************************************************************************************************************************************************************************
ok: [localhost] => {
"results": {
"changed": true,
"cmd": "echo 'tim'",
"delta": "0:00:00.095831",
"end": "2017-09-06 16:37:19.977023",
"rc": 0,
"start": "2017-09-06 16:37:19.881192",
"stderr": "",
"stderr_lines": [],
"stdout": "tim",
"stdout_lines": [
"tim"
]
}
}
TASK [set_fact] *************************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ****************************************************************************************************************************************************************************************
ok: [localhost] => {
"myrepo": ""
}
The command is ansible-playbook -i hosts -c local file.yml --extra-vars "p=tim" I want myrepo to be global if the regex results are empty
Without any parameters default filter is triggered only when value is Undefined. But result of unmatched regexp is an empty string, which is not Undefined. You may want to set boolean flag:
- set_fact:
myrepo: "{{ results.stdout | regex_search(regexp,'\\1') | default('global', boolean=True) }}"
vars:
regexp: '(.*)/(.*)'