Network Automation Cookbook
上QQ阅读APP看书,第一时间看更新

There's more...

The declarative Ansible modules that we have outlined in this section provide a simple way to configure the basic system-level parameters for Juniper devices. However, they might not cover all the parameters that we need to set up on a Juniper device. In order to have more control and flexibility to configure the system-level parameters on a Juniper device, we can use Jinja2 templates along with the Ansible template module to generate the specific system-level configuration needed for our deployment. In this section, we will outline this method in order to achieve this goal, and this is the method that we will use in subsequent recipes to generate the configuration for the other devices.

We are going to reuse this method to generate the configuration for our Juniper devices for different sections, such as system, interfaces, OSPF, and MPLS. We are going to create an Ansible role in order to include all the Jinja2 templates and tasks required to generate the final configuration that we will push to our devices. The following procedures outline the steps needed to create the role and to use this role to generate the configuration:

  1. Create a new roles directory and add a new role called build_router_config with the following directory structure:
$ tree roles/
roles/
└── build_router_config
├── tasks
└── templates
  1. Under the tasks folder, create a build_config_dir.yml YAML file to create the required folders to store the configuration that will be generated, as follows:
$ cat roles/build_router_config/tasks/build_config_dir.yml

---

- name: Create Config Directory
file: path={{config_dir}} state=directory
run_once: yes

- name: Create Temp Directory per Node
file: path={{tmp_dir}}/{{inventory_hostname}} state=directory

- name: SET FACT >> Build Directory
set_fact:
build_dir: "{{tmp_dir}}/{{inventory_hostname}}"
  1. Under the templates folder, create a new folder called junos, and within this folder, create a new Jinja2 template called mgmt.j2, with the following content:
$ cat roles/build_router_config/templates/junos/mgmt.j2

system {
host-name {{inventory_hostname}};
no-redirects;
{% if global.dns is defined %}
name-server {
{% for dns_server in global.dns %}
{{dns_server}};
{% endfor %}
}
{% endif %}
root-authentication {
encrypted-password "{{ global.root_pwd}}"; ## SECRET-DATA
}
login {
{% for user in global.users if user.hash is defined %}
user {{ user.username }} {
class super-user;
authentication {
encrypted-password "{{user.hash}}"; ## SECRET-DATA
}
}
{% endfor %}
{% for user in global.users if user.SSH_key is defined %}
user {{ user.username }} {
class {{ user.role }};
authentication {
SSH-rsa "{{lookup('file',user.SSH_key)}}"; ## SECRET-DATA
}
}
{% endfor %}
}
}
  1. Under the tasks folder, create a new YAML file called build_device_config.yml, with the following task to create the system configuration:
$ cat roles/build_router_config/tasks/build_device_config.yml

---
- name: "System Configuration"
template:
src: "{{Ansible_network_os}}/mgmt.j2"
dest: "{{build_dir}}/00_mgmt.cfg"
tags: mgmt
  1. Create a main.yml file under the tasks folder, with the following tasks:
$ cat roles/build_router_config/tasks/main.yml

---
- name: Build Required Directories
import_tasks: build_config_dir.yml

- name: Build Device Configuration
import_tasks: build_device_config.yml

- name: "Remove Old Assembled Config"
file:
path: "{{config_dir}}/{{ inventory_hostname }}.cfg"
state: absent

- name: Build Final Device Configuration
assemble:
src: "{{ build_dir }}"
dest: "{{config_dir}}/{{ inventory_hostname }}.cfg"

- name: Remove Build Directory
file: path={{ tmp_dir }} state=absent
run_once: yes
  1. Update the pb_jnpr_net_build.yml playbook with the following task to generate the configuration for all Juniper devices in our inventory:
$ cat pb_jnpr_net_build.yml

- name: Build Device Configuration
import_role:
name: build_router_config
vars:
Ansible_connection: local
tags: build

In this method, we create a role called build_router_config and we create a new Jinja2 template called mgmt.j2, which includes the template for Junos OS system-level configuration. We use the Ansible template module in order to render the Jinja2 template with the Ansible variables defined under the group_vars/all.yml file. In order to save the configuration for each device, we create the configs folder directory, which stores the final configuration for each device.

Since we will use this approach in order to generate the configuration for each section (MGMT, OSPF, MPLS, and so on), we will segment each section into a separate Jinja2 template, and we will generate each section in a separate file. We use the assemble module in order to group all these different sections into a single configuration file, which we will store in the configs directory. This is the final and assembled configuration file for each device. We store the temporary configuration snippets for each section in a temporary folder for each device, and we delete this temporary folder at the end of the playbook run. This is because we assembled the final configuration for the device, and we don't require these configuration snippets anymore.


In this playbook, we set the Ansible_connection to local as we don't need to connect to the devices in order to run any of the tasks within our role. We are only generating the configuration on the Ansible control machine, therefore all the tasks need to run locally on the Ansible control machine. Therefore, there is no need to connect to the remotely managed nodes.

Once we run the playbook, we can see that the following configuration files are created inside the configs directory:

$ tree configs/
configs/
├── mxp01.cfg
├── mxp02.cfg
├── mxpe01.cfg
└── mxpe02.cfg

We can see the configuration generated for the mxpe01 device as an example, as follows:

$ cat configs/mxpe01.cfg
system {
host-name mxpe01;
no-redirects;
name-server {
172.20.1.1;
172.20.1.15;
}
root-authentication {
encrypted-password "$1$ciI4raxU$XfCVzABJKdALim0aWVMql0"; ## SECRET-DATA
}
login {
user Ansible {
class super-user;
authentication {
encrypted-password "$1$mR940Z9C$ipX9sLKTRDeljQXvWFfJm1"; ##
SECRET-DATA
}
}
user admin {
class super-user;
authentication {
SSH-rsa "SSH-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0/wvdC5ycAanRorlfMYDMAv5OTcYAALlE2bdboajsQPQNEw1Li3N0J50OJBWXX+FFQuF7JKpM32vNQjQN7BgyaBWQGxv+Nj0ViVP+8X8Wuif0m6bFxBYSaPbIbGogDjPu4qU90Iv48NGOZpcPLqZthtuN7yZKPshX/0YJtXd2quUsVhzVpJnncXZMb4DZQeOin7+JVRRrDz6KP6meIylf35mhG3CV5VqpoMjYTzkDiHwIrFWVMydd4C77RQu27N2HozUtZgJy9KD8qIJYVdP6skzvp49IdInwhjOA+CugFQuhYhHSoQxRxpws5RZlvrN7/0h0Ahc3OwHaUWD+P7lz Ansible@centos7.localdomain"; ## SECRET-DATA
}
}
}
}

In subsequent recipes, we will outline how to push the generated configuration into the Juniper devices using another Ansible module.