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.
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.
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.
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.
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:
This text produces this output:
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.
This text produces this output:
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.
The text produces the following output:
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.
Use your macro with #YourMacroName
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.
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.
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.
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.
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.
With the input of 4 for the variable “APInterfaces”, the above code produces the following output:
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.
Multi-line comments blocks are also possible by starting the block with a #** and ending the block with *#. Those comments look like this: