Skip to content

Commit 1d86f7f

Browse files
committed
issues with click context - changed instructions to manually set env vars
1 parent a6bb717 commit 1d86f7f

File tree

4 files changed

+84
-77
lines changed

4 files changed

+84
-77
lines changed

README.md

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,38 @@ pip install dnac-sidekick
1515
## Getting Started
1616

1717
### Authenticating to DNAC
18-
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.
19-
20-
**CLI Login**
18+
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:
2119
```
22-
dnac-sidekick login --dnac_url <url> --username <user> --password <password>
20+
Unix/MacOS
21+
export DNAC_URL=<https://dnac_url>
22+
export DNAC_USER=<username>
23+
export DNAC_PASS=<password>
24+
25+
Windows
26+
set DNAC_URL=<https://dnac_url>
27+
set DNAC_USER=<username>
28+
set DNAC_PASS=<password>
2329
```
24-
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.
2530

26-
**Environment Variables (recommended)**
31+
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.
32+
33+
```
34+
dnac-sidekick login
2735
28-
Alternatively, you can set the environment variables yourself. If setting them manually, please use the following variable names:
36+
Token generated successfully!
37+
Copy token below and set as environment variable for future requests:
38+
eyJhbGciOiJS.....
2939
```
30-
DNAC_URL=<https://dnac_url>
31-
DNAC_USER=<username>
32-
DNAC_PASS=<password>
40+
41+
*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.
42+
3343
```
44+
Unix/MacOS
45+
export DNAC_TOKEN=<token>
3446
35-
*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.
47+
Windows
48+
set DNAC_TOKEN=<token>
49+
```
3650

3751
## Usage
3852
To see what commands are available, use the `--help` option. Here's a brief look at the current root commands available:

dnac_sidekick/cli.py

Lines changed: 33 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import requests
1818
from requests.auth import HTTPBasicAuth
1919
import os
20-
from dotenv import load_dotenv, set_key
20+
from dotenv import load_dotenv
2121

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

6349

6450
@dnac_cli.command
65-
@click.option(
66-
"--dnac_url",
67-
default="",
68-
envvar="DNAC_URL",
69-
help="IP/hostname to the DNA Center appliance",
70-
)
71-
@click.option(
72-
"--username", default="", envvar="DNAC_USER", help="User for login account"
73-
)
74-
@click.option(
75-
"--password",
76-
default="",
77-
envvar="DNAC_PASS",
78-
hide_input=True,
79-
help="Password for login account",
80-
)
81-
@click.pass_context
82-
def login(ctx, dnac_url, username, password):
83-
"""Use username and password to authenticate to DNAC."""
84-
click.echo("Attempting to login to DNAC...")
85-
if not dnac_url:
86-
raise click.ClickException(
87-
"DNAC URL has not been provided and has not been set as an environment variable."
88-
)
51+
def login():
52+
"""Helper function to quickly generate bearer token and authenticate to DNAC."""
53+
# Pull env vars that should be set by user
54+
dnac_url = os.environ.get("DNAC_URL")
55+
username = os.environ.get("DNAC_USER")
56+
password = os.environ.get("DNAC_PASS")
57+
# Confirm set env var values are not None
58+
if None in (dnac_url, username, password):
59+
raise click.ClickException("A necessary environment variable has not been set.")
8960
# Since value is being read from env var, and not context, need to add extra logic
9061
if dnac_url[-1] == "/":
9162
dnac_url = dnac_url.rstrip(dnac_url[-1])
@@ -94,6 +65,7 @@ def login(ctx, dnac_url, username, password):
9465
"Accept": "application/json",
9566
}
9667
dnac_token_url = f"{dnac_url}/dna/system/api/v1/auth/token"
68+
click.echo("Attempting to login to DNAC...")
9769
token = requests.post(
9870
url=dnac_token_url,
9971
headers=headers,
@@ -104,8 +76,11 @@ def login(ctx, dnac_url, username, password):
10476
actual_token = token.json()["Token"]
10577
click.echo("Token generated successfully!")
10678
# Set new token as env var and update .env file
107-
os.environ["DNAC_TOKEN"] = actual_token
108-
set_key(dotenv_file, "DNAC_TOKEN", os.environ["DNAC_TOKEN"])
79+
click.echo(
80+
"Copy token below and set as environment variable for future requests:"
81+
)
82+
click.echo(actual_token)
83+
10984
elif token.status_code == 401:
11085
click.echo(
11186
"Unauthorized. Token not generated. Please make sure a proper username and password are provided and correct."
@@ -120,7 +95,22 @@ def login(ctx, dnac_url, username, password):
12095
def get(ctx):
12196
"""Action for read-only tasks and gathering information."""
12297
click.echo("Getting information...")
123-
pass
98+
99+
# Confirm all the necessary env vars are set
100+
dnac_url = os.environ.get("DNAC_URL")
101+
dnac_user = os.environ.get("DNAC_USER")
102+
dnac_pass = os.environ.get("DNAC_PASS")
103+
dnac_token = os.environ.get("DNAC_TOKEN")
104+
# Add values to context for read-only actions to use
105+
if None not in (dnac_url, dnac_user, dnac_pass, dnac_token):
106+
ctx.obj = DnacUser(
107+
dnac_url=dnac_url,
108+
username=dnac_user,
109+
password=dnac_pass,
110+
token=dnac_token,
111+
)
112+
else:
113+
raise click.ClickException("A necessary environment variable has not been set.")
124114

125115

126116
@get.group()

dnac_sidekick/device_commands/commands.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,39 @@
33
import click
44
import requests
55
import json
6+
import os
67

78

89
@click.command
910
@click.option(
10-
"--device", default="", help="Specify a device's hostname to run commands."
11+
"--device", required=True, help="Specify a device's hostname to run commands."
1112
)
1213
@click.option(
13-
"--command", default="", help="Specify a command to run on the specified device."
14+
"--command", required=True, help="Specify a command to run on the specified device."
1415
)
15-
@click.pass_context
16-
def command_runner(ctx, device, command):
16+
def command_runner(device, command):
1717
"""Run 'show' commands on network devices in DNAC."""
18+
# Confirm all the necessary env vars are set
19+
dnac_url = os.environ.get("DNAC_URL")
20+
dnac_user = os.environ.get("DNAC_USER")
21+
dnac_pass = os.environ.get("DNAC_PASS")
22+
dnac_token = os.environ.get("DNAC_TOKEN")
23+
# Add values to context for read-only actions to use
24+
if None in (dnac_url, dnac_user, dnac_pass, dnac_token):
25+
raise click.ClickException("A necessary environment variable has not been set.")
1826
headers = {
1927
"Content-Type": "application/json",
2028
"Accept": "application/json",
21-
"X-Auth-Token": ctx.obj.token,
29+
"X-Auth-Token": dnac_token,
2230
}
23-
if not ctx.obj.dnac_url:
24-
raise click.ClickException(
25-
"DNAC URL has not been provided or has not been set as an environment variable."
26-
)
27-
dnac_devices_url = (
28-
f"{ctx.obj.dnac_url}/dna/intent/api/v1/network-device?hostname={device}"
29-
)
31+
dnac_devices_url = f"{dnac_url}/dna/intent/api/v1/network-device?hostname={device}"
3032
net_devices_resp = requests.get(url=dnac_devices_url, headers=headers, verify=False)
3133
if net_devices_resp.status_code == 200:
3234
dev_id = net_devices_resp.json()["response"][0].get("id")
3335
if not dev_id:
3436
raise click.ClickException("Device hostname not found in inventory.")
3537
dnac_command_run = (
36-
f"{ctx.obj.dnac_url}/dna/intent/api/v1/network-device-poller/cli/read-request"
38+
f"{dnac_url}/dna/intent/api/v1/network-device-poller/cli/read-request"
3739
)
3840
payload = {
3941
"timeout": 5,
@@ -55,7 +57,7 @@ def command_runner(ctx, device, command):
5557
print(f"Status code: {comm_run_resp.status_code}")
5658
print(f"Error: {comm_run_resp.text}")
5759
# Get task by ID
58-
task_check = f"{ctx.obj.dnac_url}/api/v1/task/{task_id}"
60+
task_check = f"{dnac_url}/api/v1/task/{task_id}"
5961
task_check_resp = requests.get(url=task_check, headers=headers, verify=False)
6062
if task_check_resp.status_code == 200:
6163
tasks_found = task_check_resp.json()["response"]
@@ -73,7 +75,7 @@ def command_runner(ctx, device, command):
7375
print(f"Error! Error message: {task_check_resp.text}")
7476
raise click.ClickException("File ID not found.")
7577
# Get file by ID
76-
dnac_get_file = f"{ctx.obj.dnac_url}/dna/intent/api/v1/file/{file_id}"
78+
dnac_get_file = f"{dnac_url}/dna/intent/api/v1/file/{file_id}"
7779
file_resp = requests.get(url=dnac_get_file, headers=headers, verify=False)
7880
if net_devices_resp.status_code == 200:
7981
command_output = file_resp.json()[0]["commandResponses"]["SUCCESS"][command]

tests/test_dnac_sidekick.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@
1313
If the DNAC URL or any login credentials change, the tests will need changed accordingly.
1414
"""
1515

16-
os.environ["DNAC_URL"] = "https://sandboxdnac.cisco.com"
16+
17+
def test_env_vars_set():
18+
dnac_url = os.environ.get("DNAC_URL")
19+
dnac_user = os.environ.get("DNAC_USER")
20+
dnac_pass = os.environ.get("DNAC_PASS")
21+
dnac_token = os.environ.get("DNAC_TOKEN")
22+
23+
assert None not in (dnac_url, dnac_user, dnac_pass, dnac_token)
1724

1825

1926
def test_dnac_login():
@@ -23,12 +30,6 @@ def test_dnac_login():
2330
dnac_cli,
2431
[
2532
"login",
26-
"--dnac_url",
27-
"https://sandboxdnac.cisco.com",
28-
"--username",
29-
"devnetuser",
30-
"--password",
31-
"Cisco123!",
3233
],
3334
)
3435
assert result.exit_code == 0
@@ -70,7 +71,7 @@ def test_dnac_command_runner():
7071
runner = CliRunner()
7172
result = runner.invoke(
7273
dnac_cli,
73-
["command-runner", "--device", "spine1.abc.inc", "--command", "show version"],
74+
["command-runner", "--device", "spine1.abc.inc", "--command", "show run"],
7475
)
7576
time.sleep(3)
7677
assert result.exit_code == 0

0 commit comments

Comments
 (0)