cisco-en-programmability / dnacentersdk

Cisco DNA Center Python SDK
https://dnacentersdk.readthedocs.io/en/latest/
MIT License
70 stars 33 forks source link

Command Runner issue: cannot save command output to file #50

Closed zhenningx closed 2 years ago

zhenningx commented 2 years ago

From this blog: https://blogs.cisco.com/developer/network-automation-cisco-dna-center-sdk-2

How do you "Retrieve the results and save them to file"? This part is missing from the blog.

The result from the code given in the blog is a class cmd_output, not a file with the results.

More discussion here: https://community.cisco.com/t5/cisco-digital-network/dnac-command-runner-question/m-p/4570843

Need to have a way to save the command runner to a file to complete the task.

wastorga commented 2 years ago

Hi, @zhenningx.

On the blog post, there seem to be missing code or missing parameters for the actual download of the file.

As stated in the documentation, the function will return a urllib3.response.HTTPResponse class.

The download_a_file_by_fileid function has parameters that handle that response, iterate over its content and write it to file. Those parameters are dirpath and save_file; the file's name is the Content-Disposition header's filename value.

So one quick way to do it is changing this line

cmd_output = dnac.file.download_a_file_by_fileid(task_progress['fileId'])

to

// This saves to the current working directory got by Python: os.getcwd()
cmd_output = dnac.file.download_a_file_by_fileid(task_progress['fileId'], save_file=True).

or

// This saves to the directory /tmp
cmd_output = dnac.file.download_a_file_by_fileid(task_progress['fileId'], dirpath='/tmp', save_file=True).

I hope that helps.

zhenningx commented 2 years ago

That worked. Thanks so much!

zhenningx commented 2 years ago

Hello, is it possible to specify a file name with The download_a_file_by_field function? If I loop through multiple devices, it creates multiple files. I want to present one file with the show command outputs from multiple devices.

Thanks!

wastorga commented 2 years ago

For now, it is not possible. However, I am working on a solution. I'll update here when it is done.

wastorga commented 2 years ago

The new dnacentersdk version, v2.4.8 is up.

Below is a quick non-production code that demonstrates the changes.

import json
import logging

from dnacentersdk import DNACenterAPI

api = DNACenterAPI(verify=False, debug=False)

logging.getLogger('dnacentersdk').addHandler(logging.StreamHandler())

def get_device_list():
  devices = api.devices.get_device_list()
  device_list = []
  for device in devices.response:
    if device.family == 'Switches and Hubs':
      device_list.append(device.id)
  return device_list

def cmd_runner(device):
  run_cmd = api.command_runner.run_read_only_commands_on_devices(commands=["show run"], deviceUuids=[device])
  task_info = api.task.get_task_by_id(run_cmd.response.taskId)
  task_progress = task_info.response.progress
  while task_progress == 'CLI Runner request creation':
    task_progress = api.task.get_task_by_id(run_cmd.response.taskId).response.progress
  task_progress = json.loads(task_progress)
  return task_progress

def solution_issue():
  device_list = get_device_list()

  filename = "final_command_output.log"
  filedata = bytes()
  for device in device_list:
    task_progress = cmd_runner(device)
    # save_file=False to avoid downloading the files of each device's command output
    cmd_output = api.file.download_a_file_by_fileid(task_progress['fileId'], save_file=False)
    # Adds an optional line to describe which original filename corresponds to the content
    filedata += bytes("\nFilename: " + cmd_output.filename + '\n', encoding='utf-8')
    # Adds the download data of the current device's command output
    filedata += cmd_output.data
  # Waits til the end to write to the file
  with open(filename, 'w') as f:
    f.write(filedata.decode(encoding='utf-8'))

if __name__ == '__main__':
  solution_issue()
wastorga commented 2 years ago

@zhenningx

You could also pass the list of device ids directly to the run_read_only_commands_on_devices.


def cmd_runner_v2(devices):
  run_cmd = api.command_runner.run_read_only_commands_on_devices(commands=["show run"], deviceUuids=devices)
  task_info = api.task.get_task_by_id(run_cmd.response.taskId)
  task_progress = task_info.response.progress
  while task_progress == 'CLI Runner request creation':
    task_progress = api.task.get_task_by_id(run_cmd.response.taskId).response.progress
  task_progress = json.loads(task_progress)
  return task_progress

def solution_issue_v2():
  device_list = get_device_list()
  filename = "final_command_output_2.log"
  task_progress = cmd_runner_v2(device_list)
  api.file.download_a_file_by_fileid(task_progress['fileId'], save_file=True,
                                     dirpath='/tmp/devices',
                                     filename=filename)

if __name__ == '__main__':
  solution_issue_v2()

Note: for both solutions you will need the new SDK version

I hope that helps.

zhenningx commented 2 years ago

@wastorga The solution works well! Thanks!

wastorga commented 2 years ago

@zhenningx. Your welcome. If there is nothing more, please close the issue.

zhenningx commented 2 years ago

Closing the issue. Thanks again.