Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Environment variables JSNAPY_PASSWORD and JSNAPY_USERNAME #407

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,21 @@ $ docker run -it -v $PWD:/scripts jsnapy --snap pre -f config_check.yml

### Build Arguments

The following build arguments are currently supported:
The following environment variable are currently supported:

| ARG | Default Value |
|---------------|---------------|
| `JSNAPY_HOME` | `/jsnapy` |
| ARG | Default Value |
|-------------------|---------------|
| `JSNAPY_HOME` | `/jsnapy` |
| `JSNAPY_USERNAME` | `` |
| `JSNAPY_PASSWORD` | `` |

`JSNAPY_USERNAME` and `JSNAPY_PASSWORD` can be used to set the username and
password for device logins.

Username and password order of precedence
1. configuration file
2. Environment variable
3. command line

## Hello, World

Expand Down
42 changes: 29 additions & 13 deletions lib/jnpr/jsnapy/jsnapy.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from jnpr.jsnapy.check import Comparator
from jnpr.jsnapy.notify import Notification
from jnpr.junos import Device
from jnpr.jsnapy import version
from jnpr.jsnapy.operator import Operator
from jnpr.jsnapy.snap import Parser
from jnpr.junos.exception import ConnectAuthError
Expand Down Expand Up @@ -330,29 +329,28 @@ def get_config_file(self):
if conf_file is not None:
if os.path.isfile(conf_file):
config_file = open(conf_file, "r")
self.main_file = yaml.load(config_file, Loader=yaml.FullLoader)
elif os.path.isfile(
os.path.join(get_path("DEFAULT", "config_file_path"), conf_file)
):
fpath = get_path("DEFAULT", "config_file_path")
config_file = open(os.path.join(fpath, conf_file), "r")
self.main_file = yaml.load(config_file, Loader=yaml.FullLoader)
else:
self.logger.error(
colorama.Fore.RED
+ "ERROR!! Config file '%s' is not present " % conf_file,
extra=self.log_detail,
)
sys.exit(1)
self.main_file = yaml.load(config_file, Loader=yaml.FullLoader)
else:
if self.args.hostname and self.args.testfiles:
temp_dict = {
"hosts": [{"device": "", "username": "", "passwd": ""}],
"tests": [],
}
temp_dict["hosts"][0]["device"] = self.args.hostname
temp_dict["hosts"][0]["username"] = self.args.login
temp_dict["hosts"][0]["passwd"] = self.args.passwd
temp_dict["hosts"][0]["username"] = self.get_device_login()
temp_dict["hosts"][0]["passwd"] = self.get_device_passwd()
for tfile in self.args.testfiles:
temp_dict["tests"].append(tfile)
self.main_file = temp_dict
Expand Down Expand Up @@ -502,11 +500,29 @@ def extract_device_information(self, host_dict):
# login credentials are given from command line
host_dict["0"] = {
"device": self.args.hostname,
"username": self.args.login,
"passwd": self.args.passwd,
"username": self.get_device_login(),
"passwd": self.get_device_passwd(),
}
self.host_list.append(self.args.hostname)

def get_device_passwd(self):
""" Password finder and/or asker """
# take either environment variable or the cli parsed password
passwd = os.environ.get('JSNAPY_PASSWORD', self.args.passwd)
if passwd == "":
# if both fail prompt for a password
passwd = getpass.getpass(prompt="Password: ")
return passwd

def get_device_login(self):
""" Login finder and/or asker """
# take either environment variable or the cli parsed login
login = os.environ.get('JSNAPY_LOGIN', self.args.login)
if login == "":
# if both fail prompt for a login -- drop support for Python2
login = input(prompt="Username: ")
return login

def get_test(self, config_data, hostname, snap_file, post_snap, action, **kwargs):
"""
Analyse testfile and return object of operator.Operator containing test details
Expand Down Expand Up @@ -652,10 +668,8 @@ def connect(
)
if username is None:
if username is None:
if sys.version < "3":
username = raw_input("\nEnter User name: ")
else:
username = input("\nEnter User name: ")
# Py3 only
username = input("\nEnter User name: ")
dev = Device(
host=hostname,
user=username,
Expand Down Expand Up @@ -733,8 +747,10 @@ def connect_multiple_device(

for (iter, key_value) in iteritems(host_dict):
hostname = key_value.get("device")
username = self.args.login or key_value.get("username")
password = self.args.passwd or key_value.get("passwd")
# get username and password from check config file or other
# location, see get_device_XXXXXX methods
username = key_value.get("username", self.get_device_login())
password = key_value.get("passwd", self.get_device_passwd())
key_value = self.get_values(key_value)
# extract the other arguments passed in file.
# port passed in argument has higher precedence than in file
Expand Down
41 changes: 34 additions & 7 deletions tests/unit/test_jsnapy.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import unittest
import yaml
import os
import sys
# from contextlib import nested
from contextlib import contextmanager
from io import StringIO
from jnpr.jsnapy import version
from jnpr.jsnapy.jsnapy import SnapAdmin
from mock import patch, MagicMock, call, ANY
# from contextlib import nested
from jnpr.junos.device import Device
from nose.plugins.attrib import attr

from unittest.mock import patch, MagicMock, call, ANY

import argparse
from jnpr.junos.device import Device
import os
import sys
import unittest
import yaml

# try: input = raw_input
# except NameError: pass
Expand All @@ -18,6 +22,15 @@
else:
builtin_string = 'builtins.'

@contextmanager
def input(arg):
with patch("sys.stdin", StringIO(f"{arg}")):
yield

@contextmanager
def secret_input(arg):
with patch("getpass.getpass", side_effect=arg):
yield

@attr('unit')
class TestSnapAdmin(unittest.TestCase):
Expand Down Expand Up @@ -1071,3 +1084,17 @@ def test_operation_snapcheck_local_config(
call('1.1.1.1', config_data, 'PRE_314', None, 'snapcheck')
]
mock_check.assert_has_calls(expected_calls_made, any_order=True)

def test_get_device_login(self):
"""test getting the password from user"""
js = SnapAdmin()
with input("dummy.username"):
self.assertEqual(js.get_device_login(), "dummy.username")

def test_get_device_passwd(self):
""" test getting hidden text from the user """
js = SnapAdmin()
with secret_input("a$$w0rd"):
self.asserEqual(js.get_device_login(), "a$$w0rd")