Avatar

The previous blog in this series covered embedding interactive and exec level commands into templates.   This blog focuses on passing in structured data to templates, something mainly applicable to API calls.

If you need a refresher on the template-programmer API, or a sample python script to use them, you can check out this blog post.  It also contains a link to a github repository with the sample code.

Sending Variables to a template.

The UI is a little restrictive as it does not allow JSON payloads to be sent.  The API does, and the template tool shows how it works.  First, list the contents of the template (named “int-desc” in the folder “adam”).  The body is pretty simple, just two variables, the script also calls those out explicitly.  As this is educational code, it shows the API calls needed.  The first API call looks for the template (you cannot search for a template by name), and the second get the latest version of it (remember templates are versioned).  In this case, version 7 is the latest.  It then shows the body of the template, and the parameters required (if any).  The parameters are formatted in such a way, that can be cut & pasted as an argument to the script in the next step.

$ ./template.py --template adam/int-desc
Looking for: adam/int-desc
https://dnac:443/dna/intent/api/v1/template-programmer/template
TemplateId: 019eaa38-a62e-4a12-adf6-866696d69e8f Version: 7 

https://dnac:443/dna/intent/api/v1/template-programmer/template/019eaa38-a62e-4a12-adf6-866696d69e8f
Showing Template Body:
interface $int
description ${desc}:AUTO


Required Parameters for template body: {"int":"","desc":""}

Bindings []

To call the template API, you need to send a JSON dictionary of parameters. Note the JSON is in single quotes, this is to protect the double quotes “” which are mandatory for JSON. In this example, the values for “int” and “desc” are provided as a JSON payload.

$ ./template.py --template adam/int-desc --device 10.10.15.200 --params '{"int":"g2/0/10","desc":"Example"}'

Typed Data

JSON payloads have types, strings, integers and Booleans.  It can be important to ensure the payload is passed using the appropriate type.

Consider the following template.  If the “interfaceMax” is set to 3, then g2/0/11-13 will be looped through.  Often this is used for stack members, but the example uses interface number for illustrative purposes.

#foreach ($intf in [1..${interfaceMax}])
interface range gig 2/0/1${intf}
description ADAM
#end

If this template is called as follows, nothing will happen.

./template.py --template adam/int-loop --device 10.10.15.200 --params '{"interfaceMax" : "3"}'

A subtle change will deliver the right result. The difference is that “3” is a string, while 3 is an integer.  The loop will go through the values 1,2,3.

./template.py --template adam/int-loop --device 10.10.15.200 --params '{"interfaceMax" : 3}'

Structured Data

The example only allows one interface and description, how about sending in a list of them?  This is really simple.  Consider the following template.  ${pairs} is a list, and each element has two attributes, and interface ($pair.int) and a description ($pair.desc).

#foreach ($pair in ${pairs})
interf $pair.int
desc $pair.desc
#end

To execute this template, provide the following payload. This might appear complex, but is quite simple (remember this is structured data, so likely machine generated).  A list of int/desc pairs is provided.

./template.py --template adam/json_input  --device 10.10.15.200 --params '{"pairs" : [{"int":"g2/0/10", "desc" : "one"},{"int":"g2/0/11", "desc":"two"}]}' 

A simple tip is to put the json payload into a JSON validation engine(you can even use python).

$ python
Python 3.7.6 (default, Dec 30 2019, 19:38:36) 
[Clang 10.0.0 (clang-1000.11.45.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import json
>>> print(json.dumps(json.loads('{"pairs" : [{"int":"g2/0/10", "desc" : "one"},{"int":"g2/0/11", "desc":"two"}]}'), indent=2))
{
  "pairs": [
    {
      "int": "g2/0/10",
      "desc": "one"
    },
    {
      "int": "g2/0/11",
      "desc": "two"
    }
  ]
} 

Another example allows you to push config snippets to a set of interfaces. Quite similar to the example above, except it takes a string of config (rather than just a description.

#foreach ($pair in ${pairs})
int $pair.int
$pair.config
#end

And it can be run as follows.  This will push the required lines of config to each interface..

./template.py --template adam/json_int_config  --device 10.10.15.200 --params '{"pairs" : [{"int":"g2/0/10", "config" : "switchport mode access\nswitchport access vlan 100"},{"int":"g2/0/11", "config":" switchport mode access\nswitchport access vlan 101"}]}'

Using Macros

While the previous is very flexible, allowing any config for any interface, it does not promote standardization.  The following template uses velocity macros for standard port configurations.  A macro is used for “userport” and it takes an argument “$vlan”. This is a private variable.  The “apport” macro takes two arguments.   The name of the macro is passed in (as a parameter) .  The “#evaluate” command is required to run the macro.

#macro  (userport $vlan)
description User Port
switchport access vlan $vlan
switchport mode access
switchport voice vlan 20
switchport host
#end

#macro (apport $desc $vlanrange)
 description $desc
 switchport trunk allowed vlan 10,$vlanrange
 switchport trunk native vlan 10
 switchport mode trunk
 spanning-tree portfast trunk
 ip arp inspection limit none
 cdp enable
#end

#foreach ($config in ${configs})
interface $config.int
#evaluate($config.macro_)
#end

The macro apport requires strings as arguments, so the quotes need to be escaped.

./template.py --template adam/macro_loop_json  --device 10.10.15.200 --params '{"configs" : [{"int":"g2/0/10", "macro_" : "#userport(100)"},{"int":"g2/0/11", "macro_":"#apport(\"link_AP1\", \"100,101\")"}]}'

What Next?

This blog shows examples of passing in structured data.   Don’t be overwhelmed by the perceived complexity of the payloads in these examples.  Remember, these will be programatically generated, and provided as input to the API call.  The examples are just a simple illustration of the concepts.

The next blog will look at the UI and how multiple templates can be applied to a site.

If you are interested in using the API to apply templates to devices, this blog post has a sample python tool for applying templates.

In the meantime, if you would like to learn more about Cisco DNA Center you could visit  Cisco Devnet.

Thanks for reading

@adamradford123

Related resources: