Skip to content

Commit

Permalink
issues with click context - changed instructions to manually set env …
Browse files Browse the repository at this point in the history
…vars
  • Loading branch information
dannywade committed Sep 21, 2022
1 parent a6bb717 commit 1d86f7f
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 77 deletions.
36 changes: 25 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,38 @@ pip install dnac-sidekick
## Getting Started

### Authenticating to DNAC
Users can either store their DNAC info and login credentials (DNAC URL/username/password) as environment variables or use the CLI ` dnac-sidekick login` command to authenticate to their DNAC instance.

**CLI Login**
DNAC-Sidekick pulls all user-specific information from environmnet variables. Ideally, this would be a more automated process with less manual work for the user, but for now, the following environment variables must be set manually before using the tool:
```
dnac-sidekick login --dnac_url <url> --username <user> --password <password>
Unix/MacOS
export DNAC_URL=<https://dnac_url>
export DNAC_USER=<username>
export DNAC_PASS=<password>
Windows
set DNAC_URL=<https://dnac_url>
set DNAC_USER=<username>
set DNAC_PASS=<password>
```
Once completed, these values will be used to automatically generate a bearer token and store all these values as environment variables to use with future API requests.

**Environment Variables (recommended)**
Once set, we will need to generate a bearer token, which is used to authenticate to the DNAC REST API. You can manually generate this token using curl or Postman, but there's also a built-in command that will generate one for you. This will only work if the URL, username, and password environment variables are set.

```
dnac-sidekick login
Alternatively, you can set the environment variables yourself. If setting them manually, please use the following variable names:
Token generated successfully!
Copy token below and set as environment variable for future requests:
eyJhbGciOiJS.....
```
DNAC_URL=<https://dnac_url>
DNAC_USER=<username>
DNAC_PASS=<password>

*IMPORTANT:* Please make sure to generate the bearer token using the `dnac-sidekick login` command *AFTER* setting the necessary environment variables. Once the token is generated, don't forget to set it as an evironment variable as well.

```
Unix/MacOS
export DNAC_TOKEN=<token>
*IMPORTANT:* If setting the environment variables manually, please make sure to generate the bearer token using the `dnac-sidekick login` command *AFTER* setting the environment variables. Since the environment variables are already set, there's no need to set any additional flags.
Windows
set DNAC_TOKEN=<token>
```

## Usage
To see what commands are available, use the `--help` option. Here's a brief look at the current root commands available:
Expand Down
76 changes: 33 additions & 43 deletions dnac_sidekick/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import requests
from requests.auth import HTTPBasicAuth
import os
from dotenv import load_dotenv, set_key
from dotenv import load_dotenv

from dnac_sidekick.inventory import commands as inventory_cmds
from dnac_sidekick.health import commands as health_cmds
Expand All @@ -44,48 +44,19 @@ def dnac_cli(ctx):
Extract sensitive info from environment variables that will be used to connect to DNA Center and add to Click Context.
By adding to Click Context, these values can be used across all commands.
"""
set_dnac_url = os.environ.get("DNAC_URL")
# must remove trailing '/' in URL, if one is provided by user. Otherwise, it will mess up future API calls if not caught and removed.
if set_dnac_url[-1] == "/":
dnac_url = set_dnac_url.rstrip(set_dnac_url[-1])
else:
dnac_url = set_dnac_url
username = os.environ.get("DNAC_USER")
password = os.environ.get("DNAC_PASS")
token = os.environ.get("DNAC_TOKEN")
if (dnac_url, username, password, token):
ctx.obj = DnacUser(
dnac_url=dnac_url, username=username, password=password, token=token
)
else:
click.echo("A necessary environment variable is not set.")
pass


@dnac_cli.command
@click.option(
"--dnac_url",
default="",
envvar="DNAC_URL",
help="IP/hostname to the DNA Center appliance",
)
@click.option(
"--username", default="", envvar="DNAC_USER", help="User for login account"
)
@click.option(
"--password",
default="",
envvar="DNAC_PASS",
hide_input=True,
help="Password for login account",
)
@click.pass_context
def login(ctx, dnac_url, username, password):
"""Use username and password to authenticate to DNAC."""
click.echo("Attempting to login to DNAC...")
if not dnac_url:
raise click.ClickException(
"DNAC URL has not been provided and has not been set as an environment variable."
)
def login():
"""Helper function to quickly generate bearer token and authenticate to DNAC."""
# Pull env vars that should be set by user
dnac_url = os.environ.get("DNAC_URL")
username = os.environ.get("DNAC_USER")
password = os.environ.get("DNAC_PASS")
# Confirm set env var values are not None
if None in (dnac_url, username, password):
raise click.ClickException("A necessary environment variable has not been set.")
# Since value is being read from env var, and not context, need to add extra logic
if dnac_url[-1] == "/":
dnac_url = dnac_url.rstrip(dnac_url[-1])
Expand All @@ -94,6 +65,7 @@ def login(ctx, dnac_url, username, password):
"Accept": "application/json",
}
dnac_token_url = f"{dnac_url}/dna/system/api/v1/auth/token"
click.echo("Attempting to login to DNAC...")
token = requests.post(
url=dnac_token_url,
headers=headers,
Expand All @@ -104,8 +76,11 @@ def login(ctx, dnac_url, username, password):
actual_token = token.json()["Token"]
click.echo("Token generated successfully!")
# Set new token as env var and update .env file
os.environ["DNAC_TOKEN"] = actual_token
set_key(dotenv_file, "DNAC_TOKEN", os.environ["DNAC_TOKEN"])
click.echo(
"Copy token below and set as environment variable for future requests:"
)
click.echo(actual_token)

elif token.status_code == 401:
click.echo(
"Unauthorized. Token not generated. Please make sure a proper username and password are provided and correct."
Expand All @@ -120,7 +95,22 @@ def login(ctx, dnac_url, username, password):
def get(ctx):
"""Action for read-only tasks and gathering information."""
click.echo("Getting information...")
pass

# Confirm all the necessary env vars are set
dnac_url = os.environ.get("DNAC_URL")
dnac_user = os.environ.get("DNAC_USER")
dnac_pass = os.environ.get("DNAC_PASS")
dnac_token = os.environ.get("DNAC_TOKEN")
# Add values to context for read-only actions to use
if None not in (dnac_url, dnac_user, dnac_pass, dnac_token):
ctx.obj = DnacUser(
dnac_url=dnac_url,
username=dnac_user,
password=dnac_pass,
token=dnac_token,
)
else:
raise click.ClickException("A necessary environment variable has not been set.")


@get.group()
Expand Down
32 changes: 17 additions & 15 deletions dnac_sidekick/device_commands/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,39 @@
import click
import requests
import json
import os


@click.command
@click.option(
"--device", default="", help="Specify a device's hostname to run commands."
"--device", required=True, help="Specify a device's hostname to run commands."
)
@click.option(
"--command", default="", help="Specify a command to run on the specified device."
"--command", required=True, help="Specify a command to run on the specified device."
)
@click.pass_context
def command_runner(ctx, device, command):
def command_runner(device, command):
"""Run 'show' commands on network devices in DNAC."""
# Confirm all the necessary env vars are set
dnac_url = os.environ.get("DNAC_URL")
dnac_user = os.environ.get("DNAC_USER")
dnac_pass = os.environ.get("DNAC_PASS")
dnac_token = os.environ.get("DNAC_TOKEN")
# Add values to context for read-only actions to use
if None in (dnac_url, dnac_user, dnac_pass, dnac_token):
raise click.ClickException("A necessary environment variable has not been set.")
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"X-Auth-Token": ctx.obj.token,
"X-Auth-Token": dnac_token,
}
if not ctx.obj.dnac_url:
raise click.ClickException(
"DNAC URL has not been provided or has not been set as an environment variable."
)
dnac_devices_url = (
f"{ctx.obj.dnac_url}/dna/intent/api/v1/network-device?hostname={device}"
)
dnac_devices_url = f"{dnac_url}/dna/intent/api/v1/network-device?hostname={device}"
net_devices_resp = requests.get(url=dnac_devices_url, headers=headers, verify=False)
if net_devices_resp.status_code == 200:
dev_id = net_devices_resp.json()["response"][0].get("id")
if not dev_id:
raise click.ClickException("Device hostname not found in inventory.")
dnac_command_run = (
f"{ctx.obj.dnac_url}/dna/intent/api/v1/network-device-poller/cli/read-request"
f"{dnac_url}/dna/intent/api/v1/network-device-poller/cli/read-request"
)
payload = {
"timeout": 5,
Expand All @@ -55,7 +57,7 @@ def command_runner(ctx, device, command):
print(f"Status code: {comm_run_resp.status_code}")
print(f"Error: {comm_run_resp.text}")
# Get task by ID
task_check = f"{ctx.obj.dnac_url}/api/v1/task/{task_id}"
task_check = f"{dnac_url}/api/v1/task/{task_id}"
task_check_resp = requests.get(url=task_check, headers=headers, verify=False)
if task_check_resp.status_code == 200:
tasks_found = task_check_resp.json()["response"]
Expand All @@ -73,7 +75,7 @@ def command_runner(ctx, device, command):
print(f"Error! Error message: {task_check_resp.text}")
raise click.ClickException("File ID not found.")
# Get file by ID
dnac_get_file = f"{ctx.obj.dnac_url}/dna/intent/api/v1/file/{file_id}"
dnac_get_file = f"{dnac_url}/dna/intent/api/v1/file/{file_id}"
file_resp = requests.get(url=dnac_get_file, headers=headers, verify=False)
if net_devices_resp.status_code == 200:
command_output = file_resp.json()[0]["commandResponses"]["SUCCESS"][command]
Expand Down
17 changes: 9 additions & 8 deletions tests/test_dnac_sidekick.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@
If the DNAC URL or any login credentials change, the tests will need changed accordingly.
"""

os.environ["DNAC_URL"] = "https://sandboxdnac.cisco.com"

def test_env_vars_set():
dnac_url = os.environ.get("DNAC_URL")
dnac_user = os.environ.get("DNAC_USER")
dnac_pass = os.environ.get("DNAC_PASS")
dnac_token = os.environ.get("DNAC_TOKEN")

assert None not in (dnac_url, dnac_user, dnac_pass, dnac_token)


def test_dnac_login():
Expand All @@ -23,12 +30,6 @@ def test_dnac_login():
dnac_cli,
[
"login",
"--dnac_url",
"https://sandboxdnac.cisco.com",
"--username",
"devnetuser",
"--password",
"Cisco123!",
],
)
assert result.exit_code == 0
Expand Down Expand Up @@ -70,7 +71,7 @@ def test_dnac_command_runner():
runner = CliRunner()
result = runner.invoke(
dnac_cli,
["command-runner", "--device", "spine1.abc.inc", "--command", "show version"],
["command-runner", "--device", "spine1.abc.inc", "--command", "show run"],
)
time.sleep(3)
assert result.exit_code == 0
Expand Down

0 comments on commit 1d86f7f

Please sign in to comment.