diff --git a/README.md b/README.md index c32b9e8c..fd20a8ca 100755 --- a/README.md +++ b/README.md @@ -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 diff --git a/lib/jnpr/jsnapy/jsnapy.py b/lib/jnpr/jsnapy/jsnapy.py index 999d0942..2ca9220c 100755 --- a/lib/jnpr/jsnapy/jsnapy.py +++ b/lib/jnpr/jsnapy/jsnapy.py @@ -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 @@ -330,13 +329,11 @@ 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 @@ -344,6 +341,7 @@ def get_config_file(self): 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 = { @@ -351,8 +349,8 @@ def get_config_file(self): "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 @@ -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 @@ -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, @@ -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 diff --git a/tests/unit/test_jsnapy.py b/tests/unit/test_jsnapy.py index 0d2b47fc..7c903a49 100644 --- a/tests/unit/test_jsnapy.py +++ b/tests/unit/test_jsnapy.py @@ -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 @@ -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): @@ -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") + +