Missing variable in jinja2, no output with Ansible - ansible

I have an Ansible playbook that gathers facts from Cisco switches.
---
- hosts: switches
gather_facts: False
connection: network_cli
vars:
backup_root: ./configs
cli:
host: "{{ inventory_hostname }}"
tasks:
- name: ensure device folder is created
file:
path: "{{ backup_root }}/{{ inventory_hostname }}"
state: directory
- name: Gather all facts
cisco.ios.ios_facts:
gather_subset: all
- name: Serial Number
debug: var=ansible_net_serialnum
- name: Model
debug: var=ansible_net_model
- name: Hostname
debug: var=ansible_net_hostname
- name: Version
debug: var=ansible_net_version
- name: CDP
debug: var=ansible_net_neighbors
- name: Config file
debug: var=ansible_net_config
- name: Stack SW Model Numbs
debug: var=ansible_net_stacked_models
- name: Stack SW Model Numbs
debug: var=ansible_net_stacked_serialnums
- name: Get VLAN Info
cisco.ios.ios_command:
commands: show vlan brief
register: show_vlan
- name: get timestamp
command: date +%Y%m%d
register: timestamp
- name: Generate configuration files
template:
src=roles/discovery/templates/ios_switches.j2
dest="{{ backup_root }}/{{ inventory_hostname }}/{{ inventory_hostname }}.txt" `
Here is the jinja file.
Hostname: {{ ansible_net_hostname }}
Model: {{ansible_net_model}}
Serial Number: {{ansible_net_serialnum}}
IOS Version: {{ansible_net_version}}
IOS Image: {{ansible_net_image}}
Switch Stack Models:
{{ansible_net_stacked_models | to_nice_yaml(indent=2)}}
Switch Stack Serials:
{{ansible_net_stacked_serialnums | to_nice_yaml(indent=2)}}
CDP Neighbors:
{{ansible_net_neighbors | to_nice_yaml(indent=2)}}
Configuration:
{{ansible_net_config}}
VLAN:
{{show_vlan.stdout[0] | to_nice_yaml(indent=2)}}
This all works fine until it hits a switch that cannot stack (e.g. chassis or VSS). When I run the playbook, I get the following-
msg: 'AnsibleUndefinedVariable: ''ansible_net_stacked_models'' is undefined
I've tried using if in Jinja2 like the following
...
Switch Stack Models:
{% if ansible_net_stacked_models is not defined %}
NOT A STACKABLE SWITCH
{% else %}
{{ansible_net_stacked_models | to_nice_yaml(indent=2)}}
{% endif %}
however it fails in the Jinja rendering and does not produce any output.
Is there a way to ignore missing variables in jinja or is there a better way to do this?

you can set a default value to your variable if is not defined
{{ ansible_net_stacked_models|default("NOT A STACKABLE SWITCH", true) | to_nice_yaml(indent=2) }}

Come to find out the error was stopping Ansible from even calling the jinja file. I dug around some more and found that I could set the variable to a default value in the inventory.
[switch:vars]
ansible_net_stacked_models='NOT A STACKABLE SWITCH'
When running the playbook if the device has valid info it will overwrite the default variable we defined in inventory. If the device doesn't have valid info, Ansible will simply pass the default variable we set in inventory, over to jinja2.

Related

Template lookup in group/host vars rendered multiple times during a play

I am running into an issue where my template is rendered multiple times during a play. It seems like it is happening when the lookup plugin is called either from group or host vars. This is leading to slowness in my playbook execution.
I simplified my playbook and inventory to provide an example.
Below is the playbook I use to demonstrate the issue I am seeing:
---
- name: 1st play
hosts: mux01
connection: local
gather_facts: no
tasks:
- name: mux01 - set uplinks
set_fact:
uplinks: "{{ lookup('template', 'inventories/test_lookup/group_vars/mux_uplinks.j2') }}"
- name: mux01 - show uplink
debug:
msg: "{{ device[inventory_hostname].uplink }}"
- name: mux01 - show uplink again
debug:
msg: "{{ device[inventory_hostname].uplink }}"
- name: 2nd play
hosts: mux02
connection: local
gather_facts: no
tasks:
- name: mux02 - show uplink
debug:
msg: "{{ device[inventory_hostname].uplink }}"
- name: mux02 - show uplink again
debug:
msg: "{{ device[inventory_hostname].uplink }}"
For mux01, I am setting the uplinks variable using the template plugin and then print that variable twice. For mux02, the uplinks variable is set in the host vars file, so here I am just printing that variable twice as well.
Here is my mux01 host var file:
device:
mux01:
loopback: 1.1.1.1
uplink: "{{ uplinks.1 }}"
And here is my mux02 host var file:
uplinks: "{{ lookup('template', 'inventories/test_lookup/group_vars/mux_uplinks.j2') }}"
device:
mux02:
loopback: 1.1.1.1
uplink: "{{ uplinks.2 }}"
In this scenario, my mux_uplinks.j2 template is just updating the mux_uplinks variable in a for loop:
{% set mux_uplinks = {} %}
{% for i in range(1,100000) %}
{% set _ = mux_uplinks.update({i:i}) %}
{% endfor %}
{{ mux_uplinks }}
And finally when I run the playbook and profile it, I get the following:
mux01 - set uplinks ------------------ 8.45s
mux02 - show uplink ------------------ 7.83s
mux02 - show uplink again ------------ 7.71s
mux01 - show uplink again ------------ 0.64s
mux01 - show uplink ------------------ 0.63s
So it really seems to me that the template is being rendered multiple times if it is being called from the host/group vars. Anyone has any idea why?

Ansible search sublists for value

A webhook triggers an AWX job and I want to run the deployment on a certain host depending on the service, since they run on different servers. I need to know which server uses that service to set is as a var so it can be used as a host in the following play.
My variable inside vars.yaml looks like this:
staging_hosts:
server1: ['service1', 'service2', 'service3']
server2: ['service4', 'service5', 'service6']
server3: ['service7', 'service8', 'service9']
Playbook:
- name: write deployment hosts
hosts: localhost
vars:
deployment_hosts: absent
vars_files:
- ./group_vars/vars.yaml
tasks:
- set_fact:
modified_repos: (small regex filter to find modified repository)
- set_fact:
deployment_hosts: "{{ item }}"
when: '{{ modified_repos }} in {{ item }}'
with_list:
- "{{ staging_hosts }}"
- name: connect to Cluster
hosts: "{{ hostvars['localhost']['deployment_hosts'] }}"
What can I do against this warning and error?
[WARNING]: conditional statements should not include jinja2 templating
delimiters such as {{ }} or {% %}. Found: {{ modified_repos }} in {{ item }}
fatal: [localhost]: FAILED! => {"msg": "The conditional check '{{ modified_repos }} in {{ item }}' failed. True {% else %} False {% endif %}): unhashable type: 'list'
Oh I forgot to mention. It is important, that deployment_hosts could also contain two hosts if modified repos include for example service1 and service4.
Q: "deployment_hosts could also contain two hosts if modified repos include for example service1 and service4."
A: Use intersect filter. For example, the playbook
- hosts: localhost
vars:
staging_hosts:
server1: ['service1', 'service2', 'service3']
server2: ['service4', 'service5', 'service6']
server3: ['service7', 'service8', 'service9']
modified_repos: ['service1', 'service4']
tasks:
- set_fact:
deployment_hosts: "{{ deployment_hosts|default([]) + [item.key] }}"
loop: "{{ staging_hosts|dict2items }}"
when: modified_repos|intersect(item.value)|length > 0
- debug:
var: deployment_hosts
gives
deployment_hosts:
- server1
- server2

Ansible jinja 2 template value should not changed

I have written jinja2 template in ansible. What i am trying to achieve is that if the service_name is not mentioned and if service_name already exists on the remote machine, ansible should not change the service_name with default name mentioned in the template. However, when the service_name is not defined, ansible replaces service name with "abc" on remote machine even service_name exists. Any help would be appreciated.
active={{ active_status}}
instrument={{ instrument_status }}
{% if service_name is defined %}
service_name={{ service_name }}
{% else %}
service_name=abc
{% endif %}
Thanks
Following my above comment, here is a possible example implementation to meet your requirements. test_template.j2 is the exact copy of your current template. You can pass the service name as an extra variable to test (-e service_name=my_service)
Basically, if service_name is not defined, we:
Check if the remote file already exists and slurp its content into a var
Look for the relevant line in the file. Note: the regex_replace('None', '') is here to make sure we get an empty string if previous search/matches did not return anything.
Set the service name only if something relevant was found in the prior tasks
Once this check/setting is done correctly, you simply have to copy your template, what ever the case is.
---
- name: Conditional writing of template
hosts: localhost
gather_facts: false
vars:
my_dest: /tmp/test_file.txt
active_status: some active value
instrument_status: some instrument value
tasks:
- name: Try to read service name from existing file when it is not defined
when: service_name is not defined
block:
- name: Check if file exists
stat:
path: "{{ my_dest }}"
register: my_file
- name: Try to read target file if exists
slurp:
src: "{{ my_dest }}"
when: my_file.stat.exists
register: my_file_slurp
- name: Look for service name if there
set_fact:
looked_service: >-
{{
my_file_slurp.content
| b64decode
| regex_search('^service_name=.*$', multiline=true)
| regex_replace('^service_name=(.*)$', '\1')
| regex_replace('None', '')
}}
when: my_file.stat.exists
- name: Update service name if found
set_fact:
service_name: "{{ looked_service }}"
when: looked_service | default('') | length > 0
- name: Copy template file to destination
template:
src: test_template.j2
dest: "{{ my_dest }}"

ansible - Incorrect type. Expected "object"

site.yaml
---
- name: someapp deployment playbook
hosts: localhost
connection: local
gather_facts: no
vars_files:
- secrets.yml
environment:
AWS_DEFAULT_REGION: "{{ lookup('env', 'AWS_DEFAULT_VERSION') | default('ca-central-1', true) }}"
tasks:
- include: tasks/create_stack.yml
- include: tasks/deploy_app.yml
create_stack.yml
---
- name: task to create/update stack
cloudformation:
stack_name: someapp
state: present
template: templates/stack.yml
template_format: yaml
template_parameters:
VpcId: "{{ vpc_id }}"
SubnetId: "{{ subnet_id }}"
KeyPair: "{{ ec2_keypair }}"
InstanceCount: "{{ instance_count | default(1) }}"
DbSubnets: "{{ db_subnets | join(',') }}"
DbAvailabilityZone: "{{ db_availability_zone }}"
DbUsername: "{{ db_username }}"
DbPassword: "{{ db_password }}"
tags:
Environment: test
register: cf_stack
- name: task to output stack output
debug: msg={{ cf_stack }}
when: debug is defined
Error at line debug: msg={{ cf_stack }} saying:
This module prints statements during execution and can be useful for debugging variables or expressions without necessarily halting the playbook. Useful for debugging together with the 'when:' directive.
This module is also supported for Windows targets.
Incorrect type. Expected "object".
Ansible documentation allows the above syntax, as shown here
$ ansible --version
ansible 2.5.1
....
How to resolve this error?
You still need to remember quotes for lines starting with a {, even when using short-hand notation:
- debug: msg="{{ cf_stack }}"
This would be more obvious using full YAML notation:
- debug:
msg: "{{ cf_stack }}"
Also, given this is a variable, you could just do:
- debug:
var: cf_stack

Return Variable from Included Ansible Playbook

I have seen how to register variables within tasks in an ansible playbook and then use those variables elsewhere in the same playbook, but can you register a variable in an included playbook and then access those variables back in the original playbook?
Here is what I am trying to accomplish:
This is my main playbook:
- include: sub-playbook.yml job_url="http://some-jenkins-job"
- hosts: localhost
roles:
- some_role
sub-playbook.yml:
---
- hosts: localhost
tasks:
- name: Collect info from Jenkins Job
script: whatever.py --url "{{ job_url }}"
register: jenkins_artifacts
I'd like to be able to access the jenkins_artifacts results back in main_playbook if possible. I know you can access it from other hosts in the same playbook like this: "{{ hostvars['localhost']['jenkins_artifacts'].stdout_lines }}"
Is it the same idea for sharing across playbooks?
I'm confused what this question is about. Just use the variable name jenkins_artifacts:
- include: sub-playbook.yml job_url="http://some-jenkins-job"
- hosts: localhost
debug:
var: jenkins_artifacts
This might seem complicated but I love doing this in my Playbooks:
rc defines the name of the variable which contains the return value
ar gives the arguments to the include tasks
master.yml:
- name: verify_os
include_tasks: "verify_os/main.yml"
vars:
verify_os:
rc: "isos_present"
ar:
image: "{{ os.ar.to_os }}"
verify_os/main.yml:
---
- name: check image on device
ios_command:
commands:
- "sh bootflash: | inc {{ verify_os.ar.image }}"
register: image_check
- name: check if available
shell: "printf '{{ image_check.stdout_lines[0][0] }}\n' | grep {{ verify_os.ar.image }} | wc -l"
register: image_available
delegate_to: localhost
- set_fact: { "{{ verify_os.rc }}": "{{ true if image_available.stdout == '1' else false }}" }
...
I can now use the isos_present variable anywhere in the master.yml to access the returned value.

Resources