Ansible is written in Python, and uses the Jinja2 templating engine to implement variable substitution in playbooks, roles, and other Ansible YAML files. Variable substitution happens on the control node before tasks are sent and performed on the target machine.
---
- name: >
this play gathers facts from device and
shows inventory_hostname and configured hostname
hosts:
- router01
- router02
tasks:
- name: use ios_facts to gather facts from {{ inventory_hostname }}
ios_facts:
gather_subset: all
- name: show the configured hostname
debug:
msg: >
the configured hostname of {{ inventory_hostname }}
is {{ ansible_net_hostname }}Ansible Playbooks can be used as a front-end interface to the Jinja2 template engine. You can take advantage of Jinja2's powerful template capabilities without writing a single line of Python code.
Modeling the network as an autogenerated set of network device configurations promotes consistency and standardization. Many changes can be implemented by updating variable values or adding new ones. This is separation of concerns in action: data elements are managed independently of vendor- and model- specific implementation details, which are encoded in templates.
| Name | Format | Description |
|---|---|---|
| Variables file |
.ini or .yml/.yaml
|
File containing individual data elements; group_vars/ or host_vars/, and so on.
|
| Template file |
.j2
| Template file containing Jinja2 parameters (variable names), vendor- and model- specific contextual elements, and other boilerplate text. |
| Ansible playbook |
.yml
| The controlling file, an Ansible playbook. |
To configure network devices using templates:
Separate the platform-independent infrastructure data from platform-dependent configuration statement syntax.
Put the former into a vars file and the latter into a Jinja2 template.
By convention, the directory locations for these are vars/ and j2/.
Use the vars_files directive to load data as play variables.
Tasks can use the src option with *os_config modules to feed global configuration statements to target devices directly from Jinja2 templates.
An example is provided to illustrate how this works.
Feeding configuration statements from templates to devices works only with global config statements.
The *os_config modules for platforms with hierarchical interfaces have a parents option.
It is used to apply config statements at the correct context of a command-line interface hierarchy.
Parents only works with the lines option, which takes a list of config statements.
The src option, which can be used with template files, is mutually exclusive with lines and parents.
$ansible-doc ios_config...output omitted... -parentsThe ordered set of parents that uniquely identify the section or hierarchy the commands should be checked against. If the parents argument is omitted, the commands are checked against the set of top level or global commands. ...output omitted... -srcSpecifies the source path to the file that contains the configuration or configuration template to load. The path to the source file can either be the full path on the Ansible control host or a relative path from the playbook or role root directory.This argument is mutually exclusive with 'lines', 'parents'.
The configuration of static, local host names on IOS devices is illustrated here.
When a static host name exists on an IOS device, you can use the name instead of the IP address on that device wherever you would use an address: with the ping command, with ACLs, and so forth.
Note that these name-to-IP address mappings exist only on devices where they are configured. They are not part of the DNS service.
Static host names are configured with IOS CLI using this syntax:
(config)#ip hosthostname ipv4_address
The data to support IOS static host names consists of a list of hash/dicts that map names to IPv4 addresses.
Each row represents a host, and each row consists of a name field and an ipv4 field.
The file is saved as vars/hostnames.yml.
hostname_data:
- { name: spine01, ipv4: 10.0.0.1/32 }
- { name: spine02, ipv4: 10.0.0.11/32 }
- { name: leaf01, ipv4: 192.168.0.1/32 }
- { name: leaf02, ipv4: 192.168.0.2/32 }
- { name: cs01, ipv4: 172.16.0.1/32 }
- { name: server01, ipv4: 10.10.10.2/32 }
- { name: server02, ipv4: 192.168.10.2/32 }
- { name: server03, ipv4: 172.16.10.2/32 }
It is often useful to store IPv4 information using CIDR notation.
This makes it possible to extract various forms of information from a single unit of data: network address, subnet mask, machine address, and so forth.
It is recommended practice to use that, as illustrated in the previous definition of the hostname_data variable.
The data has been defined as a sequence of hashes/dictionaries, named hostname_data.
Given that data, the template used to generate IOS static host name commands is relatively simple.
The file name is saved as j2/ios-static-hostnames.j2.
{% for host in hostname_data %}
ip host {{ host.name }} {{ host.ipv4 | ipaddr('address') }}
{% endfor %}
The Jinja2 for command is used to loop over individual members of the hostname_data sequence as a dict object named host.
The host.name variable refers to the name field of the host object and host.ipv4 refers to its ipv4 field.
The ipaddr filter is used, together with the address argument, to obtain an IPv4 address from the original CIDR form of the data.
The IOS CLI command that displays static host names is show hosts.
You can use an Ansible ad hoc command with the ios_command module to view the results.
$ansible -m ios_command -a "commands='show hosts'"cs01 | SUCCESS => { "changed": false, "stdout_lines": [ [ "Host Port Flags Age Type Address(es)", "spine01 None (perm, OK) 0 IP 10.0.0.1", "spine02 None (perm, OK) 0 IP 10.0.0.11", "leaf01 None (perm, OK) 0 IP 192.168.0.1", "leaf02 None (perm, OK) 0 IP 192.168.0.2", "cs01 None (perm, OK) 0 IP 172.16.0.1", "server01 None (perm, OK) 0 IP 10.10.10.2", "server02 None (perm, OK) 0 IP 192.168.10.2", "server03 None (perm, OK) 0 IP 172.16.10.2" ] ] }