DNA Center - Advanced Template Scripting

In my earlier blog post, I spent some time introducing the DNA Center templating engine. In this post, we'll go a little deeper into scripting in the Velocity Templating Language.

While I was attempting to write some templates for a project at work, I found that Cisco’s documentation on the subject is pretty lacking and that was the catalyst for me writing this post.

I wanted this to be more of a deep dive into the markup of the language. If you’re familiar with programming or scripting, none of these topics will be new to you but you may need a reference for the particular syntax. For those that are new to scripting, don’t fret, I’ll cover the basics and maybe you can add scripting to your resumé soon enough.

While the documentation on the Velocity Engine will probably be the best place to look if you don’t find what you’re looking for here, I wanted to provide blocks of code that may solve your real-world issues in the context of DNAC.

Screen Shot 2020-01-28 at 8.54.07 AM.png

Creating Variables

One of the easiest ways to create a variable in DNAC is to simply define a variable and then DNAC will automatically create a form option for that variable. Variables are simply defined using the dollar sign.

hostname $hostname

Variable inputs can be controlled in the Form Editor page to allow for text input or single or multiple select inputs.

You can set the value of a variable without forcing an input to be made. Simply set the variable in your script. While this might not seem very useful on the surface, you can create arrays and then avoid having to input the same values repeatedly every time you apply a template. I’ll show more below.

#set ($foo = "bar")

Formal Notation

Formal Notation is a way to declare a variable and to keep in explicitly separate from the text proceeding it. In most cases, shorthand variables as shown above will be sufficient, but you may have situation arise where you need to concatenate text together and this is where you will use formal variable notation.

Formal notation is done by simply surrounding the variable name after the $ with curly brackets. For example, I’m using formal notation below to create a hostname from two different variables passed to me from the form.

hostname $-Dist-1

With the input “HeadQuarters” for the variable, “BuildingName”, this above code will output “HeadQuarters-Dist-1”.

Arrays and Foreach Loops

Text inputs are generally best for variables where you need a decent amount of freedom in the input. For example, a hostname or username would generally be a text input. Single select inputs would be used in a situation where there is an either-or situation like whether a device hostname is going to contain a value or not. I might use multiple select inputs for choosing which DHCP servers are going to be configured as IP helper addresses on an interface.

Unless you use a foreach loop on a multiple select input, the selections are printed literally as an array, ["foo", "bar"].

You can create a statically defined array with the “set” keyword. #set($foobar=["foo","bar"]). Creating a variable as an array can make it easier if you have a list of values that you would like to be applied to all devices in a template buy don't want to have to key these in every time the template is ran. Setting these in the template allows me to more easily edit the list of values if I needed to add or remove. For example, if I had a list of DHCP helper addresses that I wanted to reuse multiple times throughout the template I could use the following code to generate them for all interfaces:

#set ($DHCPAddresses = ["10.1.1.100", "10.1.1.101", "10.1.2.100", "10.1.2.101"])
#foreach ($address in $DHCPAddresses)
 ip helper-address $address
#end

This text produces this output:

 ip helper-address 10.1.1.100
 ip helper-address 10.1.1.101
 ip helper-address 10.1.2.100
 ip helper-address 10.1.2.101

You can just as easily set a variable to be an integer as well. The obvious use case of this would be to loop through interface configs multiple times. Here, I'm configuring all 48 ports on each switch in a stack.

#set ($stack = 3)
#foreach ($switch in [1..$stack])
interface range GigabitEthernet$/0/1-48
 description User Port
 switchport mode access
 switchport access vlan 10
!
#end

This text produces this output:

interface range GigabitEthernet1/0/1-48
 description User Port
 switchport mode access
 switchport access vlan 10
!
interface range GigabitEthernet2/0/1-48
 description User Port
 switchport mode access
 switchport access vlan 10
!
interface range GigabitEthernet3/0/1-48
 description User Port
 switchport mode access
 switchport access vlan 10
!

Foreach Indexes

Occasionally, you may have a scenario where you need to know the index of a value in an array. In this example, I have a list of subnets that I need to create VLAN interfaces for. I'd like to keep my form inputs to a minimum so I'd like to just have a multiple select that asks for the subnet and then I can create the VLAN ID programmatically.

To accomplish this, I can use the $foreach.index variable to get the index. It should be noted that $foreach.index starts at 0 and counts up. If you would like this to start at 1, $foreach.count would be more useful.

Here, I've created a small script that will take a list of IP addresses and create both a VLAN and a VLAN interface starting at VLAN ID 100.

#set ($subnets = ["10.1.1.1", "10.1.2.1", "10.1.3.1", "10.1.4.1"])
#foreach ($address in $subnets)
vlan 10$foreach.index
 name Users_$address
interface vlan 10$foreach.index
 ip address $address 255.255.255.0
#end

The text produces the following output:

vlan 100
 name Users_10.1.1.1
interface vlan 100
 ip address 10.1.1.1 255.255.255.0
vlan 101
 name Users_10.1.2.1
interface vlan 101
 ip address 10.1.2.1 255.255.255.0
vlan 102
 name Users_10.1.3.1
interface vlan 102
 ip address 10.1.3.1 255.255.255.0
vlan 103
 name Users_10.1.4.1
interface vlan 103
 ip address 10.1.4.1 255.255.255.0

Macros

The templating engine allows you to create macros that can then be used repeatedly throughout your configuration. Very often these macros would be used for things like interface configurations, but you could easily use them to create BGP neighbors, VRFs, AAA servers, or more. To create a macro just use the macro function and give your macro a descriptive name and then end the block of configuration with the #end keyword.

#macro(distribution_interface)
 switchport
 switchport mode trunk
 switchport trunk allowed vlan 100-399
 switchport trunk native vlan 100
 storm-control broadcast level .4
 channel-group 1 mode active 
#end

Use your macro with #YourMacroName

interface TenGigabitEthernet1/0/8
 #distribution_interface

Arguments can also be passed to macros by declaring a variable in the macro definition. Then the variable can be passed through to the macro when calling it.

Note that while this is valid code from the Velocity Engines point of view. It seems to break the template editor in the version I’m testing on 1.3.1.3.2. I would advise copying your template to a new one before adding this code as I have been unable to recover the template after it crashes. The only fix seems to be to delete the template and start over again.

#macro(access_interface $vlan)
 switchport mode access
 switchport access vlan $vlan
 spanning-tree portfast
 spanning-tree bpduguard enable
#end

interface gigabitethernet1/0/1
#access_interface(10)

IF/ELSE Statements

If you've ever done scripting before you are no doubt very familiar with IF/ELSE/ELSEIF statements. At their very simplest they evaluate a condition and then execute the following code. Here, I've created a condition that evaluates the number of switches in a stack to determine if both of the uplink ports should be on the same switch or spread across the first two switches in the stack. Notice that the interface configuration didn't change, only the interface range command if the variable $stack was greater than 1.

#set ($stack = 4)
#if( $stack > 1 )
interface range TenGigabitEthernet1/1/8,TenGigabitEthernet2/1/8
#else
interface range TenGigabitEthernet1/1/7-8
#end 
 Description Uplink to Distribution Switch 
 switchport mode trunk 
 switchport trunk allowed vlan 100-200
 channel-group 1 mode active


This could easily be rewritten to evaluate with the opposite logic where the if statement is only evaluated as true if $stack is equal to 1.

#set ($stack = 1)
#if( $stack == 1 )
interface range TenGigabitEthernet1/1/7-8
#else
interface range TenGigabitEthernet1/1/8,TenGigabitEthernet2/1/8
#end
 Description Uplink to Distribution Switch
 switchport mode trunk 
 switchport trunk allowed vlan 100-200 
 channel-group 1 mode active

Functions

Certain functions are available to you in the template editor. The split function takes a string and delimiter as an argument and creates an array from the two. This array could be passed to a foreach loop or you could specify the index like I have in the example below. Remember that indexes start at 0 and should be surrounded by brackets.

In my example below, my $usernamepws variable could be passed as a multiple select input with both usernames and passwords in the same string. While I admit usernames should probably be handled by an authentication server but it shows the versatility that can be had in the templating engine.

I won't go into all of the available functions but you can see their implementation below and how useful they can be.

#set ($usernamepws = ["admin/cisco123"],["superuser/password123"])
#foreach ($user in $usernamepws)
#set ($split = $user.split("/"))
username $split[0] privilege 15 algorithm-type scrypt secret $split[1]
#end

Math

As with any programming language, we can do all sorts of math. Available operators are + - * / %.

In my example, I want to configure a block of interfaces at the end of a 48-port switch but I want the number of interfaces to be variable. I'm passing the variable, $APinterfaces and using a little math to determine where the interface range should start.

Note: I know the below code seems weird to use 49 but it’s correct.

#set( $APstart = 49 - $APinterfaces )
interface range GigabitEthernet1/0/$-48
 description Access Point
 switchport mode access
 switchport access vlan 20

With the input of 4 for the variable “APInterfaces”, the above code produces the following output:

interface range GigabitEthernet1/0/45-48
 description Access Point
 switchport mode access
 switchport access vlan 20

Comments

Commenting is important in your templates because you can leave notes to yourself or your team members about what your script is trying to accomplish or to simply break up the configuration into different sections to make it easier to read. Comments are created simply by prepending the line with ##. This will stop the line from being rendered in the output of the script.

## This is a comment.

Multi-line comments blocks are also possible by starting the block with a #** and ending the block with *#. Those comments look like this:

#**
  This is a multi-line comment block.
  You can type as much here as you would like.
  Maybe information about the author, date, 
  version, or platform information.
*#
Ryan Harris

I’m Ryan and I’m a Senior Network Engineer for BlueAlly (formerly NetCraftsmen) in North Carolina doing routing, switching, and security. I’m very interested in IPv6 adoption, SD-Access, and Network Optimization. Multi-vendor with the majority of my work being with Cisco and Palo Alto.

Previous
Previous

Applications I use as a Network Engineer

Next
Next

How are ACLs different in IPv6?