Using a job identifier to group ansible tasks into logical task group - ansible

I'm new to ansible but trying to progress and learn.
I'm running a number of API calls to configure a web proxy tool.
The flow is essentially, make a GET request to see if the object exists, if does not exist, follow up with a POST request to create the object. Pretty standard.
This procedure repeats itself multiple times as the product can have a number of instances of the same type of configuration. So I would like to make the GET/POST routine as generic repeatable as possible, by associating an id.
The playbook is:
set_fact:
'vh_task_id': 01
- name: check virtual host exists
uri:
method: GET
url: "{{ admin_api_url }}/hosts?vHost={{ public_virtual_host|urlencode() }}"
return_content: yes
headers:
X-XSRF-Header: "Access"
user: "{{ admin_user }}"
password: "{{ admin_password }}"
status_code: 200
validate_certs: no
register: response
- name: set fact virtual host task id response '{{ public_virtual_host }}'
set_fact:
'response_{{ vh_task_id }}': "{{ response }}"
This all works fine, up to this point and response_{{ vh_task_id }} correctly contains the response body from the above set_fact, as I can see it, if I output it.
The issue now becomes when I want to query the JSON in response_{{ vh_task_id }} as the variable to query to get the id value.
The set_fact, is setting a literal value ofresponse_01 rather than seeing the concatenation as a registered variable.
Here is an example of how I was trying to do it:
set_fact:
'vh_id': "{{ hostvars[inventory_hostname].response_{{ vh_task_id }} | json_query('json.items[0].id') }}"
Many thanks for any help.

Try this:
set_fact:
'vh_id': "{{ hostvars[inventory_hostname].hostvar | json_query('json.items[0].id') }}"
vars:
hostvar: response_{{ vh_task_id }}
Also you don't need explicit quote ' for plain string as variable name. Like, you can change 'vh_id': to vh_id: if no other reason to keep the name with quotes.

Thanks for your time to come back, I tested this but, in the end, I have since learnt that using the with_items, gives me better results and it more repeatable

Related

Multiple Set Facts not working with conditional set

I created roles for getting different passwords from CyberArk. I noticed that everything in the roles was the same except for the query parameter to find the password, so I decided to make the query parameter a variable, and I send it to a new single CyberArk role as a variable.
Example from Playbook:
- name: Get AVI Dev Or Prod password
import_role:
name: /Users/n0118883/python/ansible/roles/cyberark_creds
vars:
query_parm: "Username=admin;Address=avi;Environment=Development"
- name: Get Venafi password
import_role:
name: /Users/n0118883/python/ansible/roles/cyberark_creds
vars:
query_parm: "Username=sahsp-avi-venafi;Address=LM"
I was hoping to call the role, pass the query parameter, and use multiple "set_fact" with a when clause to have the correct password assigned to the correct fact.
Example from Role:
- name: Get PW from cyberark
cyberark_credential:
api_base_url: "https://cyberark.lmig.com"
app_id: "AVI_Cyberark_Automation"
query: "{{ query_parm }}"
register: cyberark_command_output
- set_fact:
admin_password: "{{ cyberark_command_output | json_query('result.Content')}}"
when: '"Address=avi" in query_parm'
- set_fact:
venafi_password: "{{ cyberark_command_output | json_query('result.Content')}}"
when: '"sahsp-avi-venaf" in query_parm'
- name: "Return"
debug:
msg: "{{ admin_password }}"
- name: "Return"
debug:
msg: "{{ venafi_password }}"
No matter what I change when I run the playbook, I either get the first password twice or I get the password for the first and the error "The error was: 'venafi_password' is undefined" for the second.
In one playbook, I go to CyberArk to get three different passwords. I'm not sure if I'm trying to do too much with a role and should go back to three separate roles, or is this possible, but I'm just doing it wrong.

Ansible - Is it possible to loop over a list of objects in input within a playbook

I am trying to create a playbook which is managing to create some load balancers.
The playbook takes a configuration YAML in input, which is formatted like so:
-----configuration.yml-----
virtual_servers:
- name: "test-1.local"
type: "standard"
vs_port: 443
description: ""
monitor_interval: 30
ssl_flag: true
(omissis)
As you can see, this defines a list of load balancing objects with the relative specifications.
If I want to create for example a monitor instance, which depends on these definitions, I created this task which is defined within a playbook.
-----Playbook snippet-----
...
- name: "Creator | Create new monitor"
include_role:
name: vs-creator
tasks_from: pool_creator
with_items: "{{ virtual_servers }}"
loop_control:
loop_var: monitor_item
...
-----Monitor Task-----
- name: "Set monitor facts - Site 1"
set_fact:
monitor_name: "{{ monitor_item.name }}"
monitor_vs_port: "{{ monitor_item.vs_port }}"
monitor_interval: "{{ monitor_item.monitor_interval}}"
monitor_partition: "{{ hostvars['localhost']['vlan_partition'] | first }}"
...
(omissis)
- name: "Create HTTP monitor - Site 1"
bigip_monitor_http:
state: present
name: "{{ monitor_name }}_{{ monitor_vs_port }}.monitor"
partition: "{{ monitor_partition }}"
interval: "{{ monitor_interval }}"
timeout: "{{ monitor_interval | int * 3 | int + 1 | int }}"
provider:
server: "{{ inventory_hostname}}"
user: "{{ username }}"
password: "{{ password }}"
delegate_to: localhost
when:
- site: 1
- monitor_item.name | regex_search(regex_site_1) != None
...
As you can probably already see, I have a few problems with this code, the main one which I would like to optimize is the following:
The creation of a load balancer (virtual_server) involves multiple tasks (creation of a monitor, pool, etc...), and I would need to treat each list element in the configuration like an object to create, with all the necessary definitions.
I would need to do this for different sites which pertain to our datacenters - for which I use regex_site_1 and site: 1 in order to get the correct one... though I realize that this is not ideal.
The script, as of now, does that, but it's not well-managed I believe, and I'm at a loss on what approach should I take in developing this playbook: I was thinking about looping over the playbook with each element from the configuration list, but apparently, this is not possible, and I'm wondering if there's any way to do this, if possible with an example.
Thanks in advance for any input you might have.
If you can influence input data I advise to turn elements of virtual_servers into hosts.
In this case inventory will look like this:
virtual_servers:
hosts:
test-1.local:
vs_port: 443
description: ""
monitor_interval: 30
ssl_flag: true
And all code code will become a bliss:
- hosts: virtual_servers
tasks:
- name: Doo something
delegate_to: other_host
debug: msg=done
...
Ansible will create all loops for you for free (no need for include_roles or odd loops), and most of things with variables will be very easy. Each host has own set of variable which you just ... use.
And part where 'we are doing configuration on a real host, not this virtual' is done by use of delegate_to.
This is idiomatic Ansible and it's better to follow this way. Every time you have include_role within loop, you for sure made a mistake in designing the inventory.

Access HostVar in Ansible

I'm new to Ansible, I'm trying to use the 'Name' fact that is stored in the hostvars of ansible but, I keep getting variable not set.
I have pumped out all the host vars to confirm it is there, using the following task in my play:
- name: Print all variables for each remote device
debug:
var: hostvars[inventory_hostname]
run_once: true
delegate_to: "{{ groups['dir_tier'] | first }}"
I can see the fact is there, truncated output of hostvars:
{"hostvars[inventory_hostname]":
"{'tld':'{{environment_name}}.{{mgmt_domain}}',
'download_dir':'/tmp',
'archive_file_server':
'{{
file_server
}}',
'transport':
'repositories',
'java_package':
'jdk',
'Name':
'idserver99'
}
Have tried several ways to target the 'Name' but keep coming back to the not defined issue.
hostvars[inventory_hostname][Name]
hostvars[inventory_hostname].[Name]
{{ hostvars[inventory_hostname].[Name] }}
{{ hostvars[inventory_hostname][Name] }}
I dont seem to be able to crack the correct syntax pointer welcome.
Keen to learn.

Nested variable in Ansible

I have a variable client: client1 and I want to use it to fetch multiple facts into a template like below.
"{{ hostvars[{{ client }}][ansible_default_ipv4][address] }}"
"{{ hostvars[{{ client }}][ansible_hostname] }}"
"{{ hostvars[{{ client }}][ansible_fqdn] }}"
I have tried removing the braces, using a dictionary[key] in place of client but couldn't figure out. What is the right usage here?
The right answer is: "{{ hostvars[client]['ansible_default_ipv4']['address'] }}"

Can I use Jinja2 `map` filter in an Ansible play to get values from an array of objects?

I have a playbook for creating some EC2 instances and then doing some stuff with them. The relevant pieces are approximately like:
- name: create ec2 instances
ec2:
id: '{{ item.name }}'
instance_type: '{{ item.type }}'
register: ec2
with_items: '{{ my_instance_defs }}'
- name: wait for SSH
wait_for:
host: '{{ item.instances[0].private_ip }}'
port: 22
with_items: '{{ ec2.results }}'
This works as intended, but I am not especially happy with the item.instances[0].private_ip expression, partly because it shows really large objects in the play summary. I would love to have the with_items part just be an array of IP addresses, rather than an array of objects with arrays of objects inside them. In Python, I would just do something like:
ips = [r['instances'][0]['private_ip'] for r in ec2['results']]
And then I would use with_items: '{{ ips }}' in the second task.
Is there a way I can do the same thing using a J2 filter in the YAML of the play? Seems like http://docs.ansible.com/ansible/playbooks_filters.html#extracting-values-from-containers might be helpful, but I think that presupposes I have an array of keys/indices/whatever.
map filter it your friend here.
Something like this:
with_items: "{{ ec2.results | map(attribute='instances') | map('first') | map(attribute='private_ip') | list }}"
The code above is not tested.
You may want to try with debug first and gradually add maps to get required result.
Don't forget to put | list at the end to make your map readable.
My example is pulled from my playbook removing a autoscaling ecs cluster. I modified the above answer to get mine working.
- name: get list of instances in ASG
ec2_instance_facts:
filters:
"tag:aws:autoscaling:groupName": "{{item.name}}-{{stack}}-scalinggroup"
register: asg_host_list
- name: list ecs info
debug:
msg: "{{asg_host_list}}"
- name: get just hosts id's
set_fact:
hostlist: "{{ asg_host_list.instances | map(attribute='instance_id') | list }}"
For my use hostlist can be fed directly into ecs_instance since it takes a list of instance ids to process.
So, this is tested and works.

Resources