Write Host Variables to file in a loop with ansible - ansible

I am trying to build up a config file that contains the list of my inventory host servers and their fields eg. IP,FQDN etc.
Here is my part of inventory file :
ocp_cluster:
hosts:
anbootstrap.ocp.hattusas.tst:
fqdn: anbootstrap.ocp.hattusas.tst
ip: 10.72.217.92
anmaster1.ocp.hattusas.tst:
fqdn: anmaster1.ocp.hattusas.tst
ip: 10.72.217.93
anmaster2.ocp.hattusas.tst:
fqdn: anmaster2.ocp.hattusas.tst
ip: 10.72.217.94
anmaster3.ocp.hattusas.tst:
And here is my playbook:
- name: Adding OCP Clusters to DHCP configuration
debug:
"{{ hostvars[item][fqdn] }}"
loop: "{{ groups['ocp_cluster'] }}"
(I will use blockinfile soon)
When I ran my playbook I am getting undefined error fqdn. I tried using a for loop and it didn't help. Any suggestions?
Thanks a lot.

The fixed task is below
- debug:
msg: "{{ hostvars[item]['fqdn'] }}"
loop: "{{ groups['ocp_cluster'] }}"
Debug parameter msg was missing
fqdn is an attribute of a dictionary. It must be quoted in brackets. Similar to ocp_cluster.
It's possible to use the dot notation and simplify the references to the attributes of the dictionaries
- debug:
msg: "{{ hostvars[item].fqdn }}"
loop: "{{ groups.ocp_cluster }}"

Related

Ansible loop returns outputs entire object

I am gathering information about AWS ec2 instances and then attempting to loop through them to output the instance_id property of the registered results.
When I run through the loop I get the expected results, but I also get the entire registered object outputted as well. It appears to flatten the object to a string and output it. What is the reason for the additional output and is there a better loop method I should use?
Thank you in advance!
---
- hosts: localhost
gather_facts: false
connection: local
tasks:
- name: get ec2 instance info
ec2_instance_info:
region: us-east-1
filters:
"tag:app": ansible
"tag:env": dev
register: ec2
- debug:
msg: "{{ item['instance_id'] }}"
loop: "{{ ec2['instances'] }}"
FIX
- debug:
msg: "{{ item['instance_id'] }}"
loop: "{{ ec2['instances'] }}"
loop_control:
label: "{{ item.instance_id }}"
I think I found your answer #duffney.
By the looks of things Was addressed as a bug/feature and amended
https://github.com/ansible/ansible/issues/35493
Does it help out what you are looking for?

Ansible 'with_items' doesn't pass over to Ansible role

I can't figure this out. I have host_vars that I want to pass into my Ansible role. I can iterate through the items in my playbook, but it doesn't work if I give the item into my Ansible role. My host inventory looks like this:
hosts:
host_one:
domain: one
ip: 172.18.1.1
connection:
- connection_name: two
connection_ip: 172.18.1.2
- connection_index: three
local_hub_ip: 172.18.1.3
host_two:
domain: two
ip: 172.18.1.2
For instance,this works correctly:
tasks:
- debug:
msg: "{{item.connection_name}}"
with_items:
- "{{ connection }}"
will correctly print out the connections.connection_name for each connection I have, "two" and "three". However, if I try to pass it into a role:
tasks:
- name: Adding several connections
include_role:
name: connection-create
with_items:
- "{{ connection }}"
in which my role "connection-create" uses a variable called "connection_name" I get a:
FAILED! => {"msg": "'connection_name' is undefined"}
Why doesn't this work?
The loop with_items: "{{ connection }}" creates the loop variable item. The included role can use
item.connection_name
item.connection_ip
The loop variable can be renamed, if needed. See Loop control

Ansible hosts' inventory yml doesn't support repeated ip with_items

The following:
store_controller:
hosts:
SERVER:
ansible_host: "{{ STORE_CTL }}"
mgmt_ip: "{{ ansible_host }}"
global_mgmt:
hosts:
SERVER:
ansible_host: "{{ NOMAD_SERVER }}"
mgmt_ip: "{{ ansible_host }}"
node_exporter: ##if comment the part from here to end, it's ok####
hosts:
"{{ item }}":
ansible_host: "{{ item }}"
mgmt_ip: "{{ ansible_host }}"
with_items:
- "172.7.7.1"
- "172.7.7.9"
- "172.7.7.12"
But Ansible doesn't let me use 'with_items' here. It seems that ansible does not support iterator over hosts.
How can I define the hosts array in group node_exporter for my three IPs ?
All you need to define is this:
node_exporter:
hosts:
172.7.7.1:
172.7.7.9:
172.7.7.12:
vars:
mgmt_ip: "{{ inventory_hostname }}"
Explanation:
mgmt_ip defines only a template (string value) which will be resolved at the time it is used.
For each target machine inventory_hostname (thus mgmt_ip) will resolve to the IP address of currently executing host.
Using ansible_host to assign the same value as the inventory host name is an empty action, so you don't need that at all.
I don't think it brings any value/clarity to the code, but since you commented it was working for you, that's the way to achieve it with multiple hosts. All you achieve is creating an alias mgmt_ip to inventory_hostname.
Regarding the premise of the question:
with_items: for YAML is a dictionary key name (string value). It is Ansible that might or might not make use of it.
It makes use of it when it is specified in a task (there it has a semantic meaning).
Otherwise it either ignores it (never uses this key), or reports an error.
Looking quickly at your code, you have an indentation problem.
Try to add 2 spaces before "{{item}}:"

Ansible - how to conditionally invert variables in a playbook

I needed to be able to invert variables stored in a JSON file that is passed to the playbook from the command line.
These are the tasks that I set up (they are identical except for vars), this is a fragment of a playbook:
- name: Prepare a .sql file
delegate_to: 127.0.0.1
mysql_db:
name: "{{ source['database']['db_name'] }}"
state: dump
login_host: "{{ source['database']['host'] }}"
login_user: "{{ source['database']['user'] }}"
login_password: "{{ source['database']['password'] }}"
target: test_db.sql
when: invert is not defined
- name: Prepare a .sql file (inverted)
delegate_to: 127.0.0.1
mysql_db:
name: "{{ target['database']['db_name'] }}"
state: dump
login_host: "{{ target['database']['host'] }}"
login_user: "{{ target['database']['user'] }}"
login_password: "{{ target['database']['password'] }}"
target: test_db.sql
when: invert is defined
So consequently when I execute
ansible-playbook -i hosts playbook.yml --extra-vars "#dynamic_vars.json"
the first task is executed. If I execute
ansible-playbook -i hosts playbook.yml --extra-vars "#dynamic_vars.json" --extra-vars "invert-yes"
the second task is executed that takes the same hash as parameters, but only swaps source for target (which essentially becomes a source in my playbook).
As you can see, this is a very simplistic approach, there is a lot of unnecessary duplication, I just do not like it. However, I cannot think of a better way to be able to revert variables at the command line without building some more complex include logic.
Perhaps you can advice me on how I can do it better? Thanks!
I'm a big fan of YAMLs anchors and references when it comes to the topic of avoiding repetition. Since the content is dynamic, you could take advantage of with_items, which can be used to pass a parameter like so:
- &sqldump
name: Prepare a .sql file
delegate_to: 127.0.0.1
mysql_db:
name: "{{ item['database']['db_name'] }}"
state: dump
login_host: "{{ item['database']['host'] }}"
login_user: "{{ item['database']['user'] }}"
login_password: "{{ item['database']['password'] }}"
target: test_db.sql
when: invert is not defined
with_items:
- source
- <<: *sqldump
name: Prepare a .sql file (inverted)
when: invert is defined
with_items:
- target
The 2nd task is a perfect clone of the first one, you then override the name, condition and the loop with_items to pass the target instead of the source.
After reading your answer to #ydaetskcoR it sounds like you have quite some cases where you need to use the data from one or the other dict. Maybe in that case it then would make sense to just define the var globally depending on the invert parameter. Your vars file could look like that:
---
source:
database: ...
db_name: ...
target:
database: ...
db_name: ...
data: "{{ target if invert is defined else source }}"
You then simply can use data in all your tasks without dealing with conditions any further.
- name: Prepare a .sql file
delegate_to: 127.0.0.1
mysql_db:
name: "{{ data['database']['db_name'] }}"
state: dump
login_host: "{{ data['database']['host'] }}"
login_user: "{{ data['database']['user'] }}"
login_password: "{{ data['database']['password'] }}"
target: test_db.sql
Of course, this way you have a fixed task name which does not change with the param you pass.
If you are attempting to do the same thing but just want to specify different variables depending on the host/group then a better approach may be to simply set these as host/group vars and run it as a single task.
If we set up our inventory file a bit like this:
[source_and_target-nodes:children]
source-nodes
target-nodes
[source-nodes]
source database_name='source_db' database_login_user='source_user' database_login_pass='source_pass'
[target-nodes]
target database_name='target_db' database_login_user='target_user' database_login_pass='target_pass'
Then we can target the task at the source_and_target-nodes like so:
- name: Prepare a .sql file
hosts: source_and_target-nodes
mysql_db:
name: "{{ database_name }}"
state: dump
login_host: "{{ inventory_hostname }}"
login_user: "{{ database_login_user }}"
login_password: "{{ database_login_pass }}"
target: test_db.sql
You won't be able to access the host vars of a different host this easily if you need to use delegate_to as you are in your question but if you are simply needing to run the play locally you can instead set ansible_connection to local in your host/group vars or setting connection: local in the play.

How can Ansible "register" in a variable the result of including a playbook?

How can an Ansible playbook register in a variable the result of including another playbook?
For example, would the following register the result of executing tasks/foo.yml in result_of_foo?
tasks:
- include: tasks/foo.yml
- register: result_of_foo
How else can Ansible record the result of a task sequence?
The short answer is that this can't be done.
The register statement is used to store the output of a single task into a variable. The exact contents of the registered variable can vary widely depending on the type of task (for example a shell task will include stdout & stderr output from the command you run in the registered variable, while the stat task will provide details of the file that is passed to the task).
If you have an include file with an arbitrary number of tasks within it then Ansible would have no way of knowing what to store in the variable in your example.
Each individual task within your include file can register variables, and you can reference those variables elsewhere, so there's really no need to even do something like this.
I was able to do this by passing a variable name as a variable to be used in the task. I included my main.yaml and included cgw.yaml files below.
main.yaml:
- name: Create App A CGW
include: cgw.yaml
vars:
bgp_asn: "{{ asn_spoke }}"
ip_address: "{{ eip_app_a.public_ip }}"
name: cgw-app-a
region: "{{ aws_region }}"
aws_access_key: "{{ ec2_access_key }}"
aws_secret_key: "{{ ec2_secret_key }}"
register: cgw_app_a
cgw.yaml:
- name: "{{ name }}"
ec2_customer_gateway:
bgp_asn: "{{ bgp_asn }}"
ip_address: "{{ ip_address }}"
name: "{{ name }}"
region: "{{ region }}"
aws_access_key: "{{ aws_access_key }}"
aws_secret_key: "{{ aws_secret_key }}"
register: "{{ register }}"

Resources