Command runner guide
Introduction
Command runner is the feature in Cisco DNA Center that allows you to execute commands on the devices managed by Cisco DNA Center.
At the moment, command runner supports read-only commands and not configuration ones.
Goal
The goals of this guide are:
- Execute a show version command
- Execute a show ip interface brief command
- Retrieve the results
Endpoints and methods used
- POST
/dna/system/api/v1/auth/token
- GET
/dna/intent/api/v1/network-device
- POST
/dna/intent/api/v1/network-device-poller/cli/read-request
- GET
/dna/intent/api/v1/task/{task_id}
- GET
/dna/intent/api/v1/file/{file_Id}
Prerequisites
For this guide, it is recommended that the developer is familiar with authenticating and obtaining the device ID of the target devices.
Environment
This guide was developed using:
Command Runner API
The command runner API is composed of two endpoints: The first one is to get all the accepted keywords for the CLI command and the second one to send the commands.
For this guide, we will execute a command runner call. The following steps are needed for a succesful API execution:
- Authenticate against the DNA Center API
- Obtain the IDs of the devices you want to send the commands to
- Execute the command(s) against a list of devices and obtain the task ID
- Use the task ID to query the Task API for a result, if successful obtain the file ID of the result
- Retrieve the file contents from the file API
Get Device ID
First, we need to authenticate and retrieve a token from the API.
Do not use
verify=False
orurllib3.disable_warnings()
if you are not sure of its purpose. Read Authentication and Authorization.
import requests
from requests.auth import HTTPBasicAuth
import urllib3
urllib3.disable_warnings()
BASE_URL = 'https://<IP Address>'
AUTH_URL = '/dna/system/api/v1/auth/token'
USERNAME = '<USERNAME>'
PASSWORD = '<PASSWORD>'
response = requests.post(BASE_URL + AUTH_URL, auth=HTTPBasicAuth(USERNAME, PASSWORD), verify=False)
token = response.json()['Token']
With the token, we can procede to query the devices API to get the list of devices we want to send the command to. We will filter based on platform ID:
headers = {'X-Auth-Token': token, 'Content-Type': 'application/json'}
DEVICES_URL = '/dna/intent/api/v1/network-device'
query_string_params = {'platformId': 'C9500-40X'}
response = requests.get(BASE_URL + DEVICES_URL, headers = headers, params=query_string_params, verify=False)
devices = []
for device in response.json()['response']:
devices.append(device['id'])
Command runner
This is the API that enables the user to send a list of commands and devices where the commands should be executed. Command runner is an asynchronous API, so the result of the execution is not obtained immediately. Check the async guide for mor information.
The main parameters for the call are:
deviceUuidDs
: List of devices IDs, captured from the Devices API callcommands
: List of commands to be executed
We will send the commands show version
and show ip interface brief
payload = {
"commands": [
"show version",
"show ip int brief"
],
"deviceUuids": devices,
"timeout": 0
}
COMMAND_RUNNER_SEND_URL = '/dna/intent/api/v1/network-device-poller/cli/read-request'
response = requests.post(BASE_URL + COMMAND_RUNNER_SEND_URL, data=json.dumps(payload), headers=headers, verify=False)
task_id = response.json()['response']['taskId']
Task API
The Task API consists of 5 endpoints. For this guide we will use the Get task by id endpoint to get the status of the task we executed with the command runner API.
TASK_BY_ID_URL = '/dna/intent/api/v1/task/{task_id}'
response = requests.get(BASE_URL + TASK_BY_ID_URL.format(task_id=task_id), headers=headers, verify=False)
progress_json = json.loads(response.json()['response']['progress'])
file_id = progress_json['fileId']
File API
Once the task is done, the final step is to retrieve the generated file, so we will use the Download file by fileId endpoint.
FILE_GET_BY_ID = '/dna/intent/api/v1/file/{file_id}'
response = requests.get(BASE_URL + FILE_GET_BY_ID.format(file_id=file_id), headers=headers, verify=False)
file_json = response.json()
The format of the response is a json with the following fields:
[
{
'deviceUuid': 'ABC',
'commandResponses': {
'SUCCESS': {
'show version': 'show version\nCisco IOS XE Software...',
'show ip int brief': 'show ip int brief\nInterface...'
},
'FAILURE': {},
'BLACKLISTED': {}
}
},
{
'deviceUuid': 'XYZ',
'commandResponses': {
'SUCCESS': {
'show version': 'show version\nCisco IOS XE Software...',
'show ip int brief': 'show ip int brief\nInterface...'
},
'FAILURE': {},
'BLACKLISTED': {}
}
}
]
With the reference above, in order to extract the response of the show ip int brief
of the second device, it is possible to do:
print(file_json[1]['commandResponses']['SUCCESS']['show ip int brief'])
Code
The repository for this guide is here. The final code with functions is shown below.
# Modules import
import requests
from requests.auth import HTTPBasicAuth
import json
import time
# Disable SSL warnings. Not needed in production environments with valid certificates
import urllib3
urllib3.disable_warnings()
# Authentication
BASE_URL = 'https://<IP Address>'
AUTH_URL = '/dna/system/api/v1/auth/token'
USERNAME = '<USERNAME>'
PASSWORD = '<PASSWORD>'
# URLs
DEVICES_URL = '/dna/intent/api/v1/network-device'
COMMAND_RUNNER_SEND_URL = '/dna/intent/api/v1/network-device-poller/cli/read-request'
TASK_BY_ID_URL = '/dna/intent/api/v1/task/{task_id}'
FILE_GET_BY_ID = '/dna/intent/api/v1/file/{file_id}'
# Get Authentication token
def get_dnac_jwt_token():
response = requests.post(BASE_URL + AUTH_URL,
auth=HTTPBasicAuth(USERNAME, PASSWORD),
verify=False)
token = response.json()['Token']
return token
# Get devices by platform ID
def get_devices_ids(headers, query_string_params):
response = requests.get(BASE_URL + DEVICES_URL, headers=headers,
params=query_string_params, verify=False)
devices = []
for device in response.json()['response']:
devices.append(device['id'])
return devices
# Send commands to devices
def send_commands(headers, payload):
response = requests.post(BASE_URL + COMMAND_RUNNER_SEND_URL, json=payload,
headers=headers, verify=False)
return response.json()['response']
# Get Task result
def get_task(headers, task_id):
response = requests.get(BASE_URL + TASK_BY_ID_URL.format(task_id=task_id), headers=headers, verify=False)
return response
# Get file with command results
def get_file(headers, file_id):
response = requests.get(BASE_URL + FILE_GET_BY_ID.format(file_id=file_id), headers=headers, verify=False)
return response.json()
def main():
# obtain the Cisco DNA Center Auth Token
token = get_dnac_jwt_token()
headers = {'X-Auth-Token': token, 'Content-Type': 'application/json'}
query_string_params = {'platformId': 'C9500-40X'}
devices = get_devices_ids(headers, query_string_params)
payload = {
"commands": [
"show version",
"show ip int brief"
],
"deviceUuids": devices,
"timeout": 0
}
response = send_commands(headers, payload)
# Wait to have a response back from the devices
time.sleep(10)
response = get_task(headers, response['taskId'])
progress_json = json.loads(response.json()['response']['progress'])
file_id = progress_json['fileId']
# Get file
response = get_file(headers, file_id)
print(response[0]['commandResponses']['SUCCESS']['show ip int brief'])
if __name__ == "__main__":
main()