From f5a056891e9943b0621bfb6bd1a1b2d56009926c Mon Sep 17 00:00:00 2001 From: Stephen Brown II Date: Thu, 26 Mar 2020 09:34:48 -0600 Subject: [PATCH] Output proper case for known config keys Also add tests on python 3.7 and 3.8 and remove 3.2 and 3.3 from travis and setup.py classifiers --- .travis.yml | 6 +- constraints.txt | 6 ++ requirements-dev.txt | 6 +- setup.py | 6 +- storm/__init__.py | 6 +- storm/__main__.py | 7 ++- storm/parsers/ssh_config_parser.py | 99 ++++++++++++++++++++++++++++++ tests.py | 46 +++++++------- tox.ini | 13 ++-- 9 files changed, 151 insertions(+), 44 deletions(-) create mode 100644 constraints.txt diff --git a/.travis.yml b/.travis.yml index c9c7d07..46c26e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,11 @@ language: python python: - "2.7" - - "3.2" - - "3.3" - "3.4" - "3.5" - "3.6" - - "3.6-dev" - - "3.7-dev" + - "3.7" + - "3.8" - "nightly" install: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi diff --git a/constraints.txt b/constraints.txt new file mode 100644 index 0000000..216e6c0 --- /dev/null +++ b/constraints.txt @@ -0,0 +1,6 @@ +setuptools>18.5,<46;python_version<'3.5' +zipp<2;python_version<'3.6' +coverage==3.7.1;python_version<'3.8' +coverage>=5.0.0;python_version>='3.8' +tox<=3.14.0;python_version<'3.4' +tox>3.14.0;python_version>='3.4' \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index 47d5a5b..a67444f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,3 @@ -nose==1.3.1 -coverage==3.7.1 -tox==1.7.1 +nose +coverage +tox diff --git a/setup.py b/setup.py index cfc35e5..a839c65 100644 --- a/setup.py +++ b/setup.py @@ -35,9 +35,11 @@ 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'License :: OSI Approved :: MIT License', 'Topic :: System :: Systems Administration', ] diff --git a/storm/__init__.py b/storm/__init__.py index 7ad7715..84294e1 100644 --- a/storm/__init__.py +++ b/storm/__init__.py @@ -15,7 +15,7 @@ ERRORS = { "already_in": "{0} is already in your sshconfig. " "use storm edit or storm update to modify.", - "not_found": "{0} doesn\'t exists in your sshconfig. " + "not_found": "{0} doesn't exist in your sshconfig. " "use storm add command to add.", } @@ -45,11 +45,11 @@ def clone_entry(self, name, clone_name, keep_original=True): if not host: raise ValueError(ERRORS["not_found"].format(name)) - # check if an entry with the clone name already exists + # check if an entry with the clone name already exists if name == clone_name \ or self.is_host_in(clone_name, return_match=True) is not None: raise ValueError(ERRORS["already_in"].format(clone_name)) - + self.ssh_config.add_host(clone_name, host.get('options')) if not keep_original: self.ssh_config.delete_host(name) diff --git a/storm/__main__.py b/storm/__main__.py index b2dcf7e..b1418ed 100644 --- a/storm/__main__.py +++ b/storm/__main__.py @@ -11,7 +11,8 @@ from storm import Storm from storm.parsers.ssh_uri_parser import parse -from storm.utils import (get_formatted_message, colored) +from storm.parsers.ssh_config_parser import lower_config_map +from storm.utils import get_formatted_message, colored from storm.kommandr import * from storm.defaults import get_default from storm import __version__ @@ -227,7 +228,8 @@ def list(config=None): if isinstance(value, collections.Sequence): if isinstance(value, builtins.list): value = ",".join(value) - + + key = lower_config_map.get(key.lower(), key) result += "{0}={1} ".format(key, value) if extra: result = result[0:-1] @@ -238,6 +240,7 @@ def list(config=None): " (*) General options: \n", "green", attrs=["bold",] ) for key, value in six.iteritems(host.get("options")): + key = lower_config_map.get(key.lower(), key) if isinstance(value, type([])): result_stack += "\t {0}: ".format( colored(key, "magenta") diff --git a/storm/parsers/ssh_config_parser.py b/storm/parsers/ssh_config_parser.py index 20e8400..8f5bf18 100644 --- a/storm/parsers/ssh_config_parser.py +++ b/storm/parsers/ssh_config_parser.py @@ -82,6 +82,104 @@ def parse(self, file_obj): self._config.append(host) +# Current as of OpenSSH 8.2 +# Source: https://man.openbsd.org/ssh_config +config_options = [ + "Host", + "Match", + "AddKeysToAgent", + "AddressFamily", + "BatchMode", + "BindAddress", + "BindInterface", + "CanonicalDomains", + "CanonicalizeFallbackLocal", + "CanonicalizeHostname", + "CanonicalizeMaxDots", + "CanonicalizePermittedCNAMEs", + "CASignatureAlgorithms", + "CertificateFile", + "ChallengeResponseAuthentication", + "CheckHostIP", + "Ciphers", + "ClearAllForwardings", + "Compression", + "ConnectionAttempts", + "ConnectTimeout", + "ControlMaster", + "ControlPath", + "ControlPersist", + "DynamicForward", + "EnableSSHKeysign", + "EscapeChar", + "ExitOnForwardFailure", + "FingerprintHash", + "ForwardAgent", + "ForwardX11", + "ForwardX11Timeout", + "ForwardX11Trusted", + "GatewayPorts", + "GlobalKnownHostsFile", + "GSSAPIAuthentication", + "GSSAPIDelegateCredentials", + "HashKnownHosts", + "HostbasedAuthentication", + "HostbasedKeyTypes", + "HostKeyAlgorithms", + "HostKeyAlias", + "Hostname", + "IdentitiesOnly", + "IdentityAgent", + "IdentityFile", + "IgnoreUnknown", + "Include", + "IPQoS", + "KbdInteractiveAuthentication", + "KbdInteractiveDevices", + "KexAlgorithms", + "LocalCommand", + "LocalForward", + "LogLevel", + "MACs", + "NoHostAuthenticationForLocalhost", + "NumberOfPasswordPrompts", + "PasswordAuthentication", + "PermitLocalCommand", + "Port", + "PreferredAuthentications", + "ProxyCommand", + "ProxyJump", + "ProxyUseFdpass", + "PubkeyAcceptedKeyTypes", + "PubkeyAuthentication", + "RekeyLimit", + "RemoteCommand", + "RemoteForward", + "RequestTTY", + "RevokedHostKeys", + "SecurityKeyProvider", + "SendEnv", + "ServerAliveCountMax", + "ServerAliveInterval", + "SetEnv", + "StreamLocalBindMask", + "StreamLocalBindUnlink", + "StrictHostKeyChecking", + "SyslogFacility", + "TCPKeepAlive", + "Tunnel", + "TunnelDevice", + "UpdateHostKeys", + "User", + "UserKnownHostsFile", + "VerifyHostKeyDNS", + "VisualHostKey", + "XAuthLocation", +] + +lower_config_map = {x.lower(): x for x in config_options} + + class ConfigParser(object): """ Config parser for ~/.ssh/config files. @@ -214,6 +312,7 @@ def dump(self): continue host_item_content = "Host {0}\n".format(host_item.get("host")) for key, value in six.iteritems(host_item.get("options")): + key = lower_config_map.get(key.lower(), key) if isinstance(value, list): sub_content = "" for value_ in value: diff --git a/tests.py b/tests.py index 4d305f0..98f6e99 100644 --- a/tests.py +++ b/tests.py @@ -105,24 +105,24 @@ def test_list_command(self): "server1 -> nixcraft@server1.cyberciti.biz:4242", "uk.gw.lan uk.lan -> nixcraft@192.168.0.251:22", ], [ - "[custom options] identityfile=~/.ssh/aws.apache.key", - "[custom options] identityfile=~/.ssh/nas01.key", - "identityfile=~/.ssh/vps1.cyberciti.biz.key", - "localforward=3128 127.0.0.1:3128", - "[custom options] identityfile=/nfs/shared/users/nixcraft/keys/server1/id_rsa,/tmp/x.rsa", - "[custom options] proxycommand=ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null", + "[custom options] IdentityFile=~/.ssh/aws.apache.key", + "[custom options] IdentityFile=~/.ssh/nas01.key", + "IdentityFile=~/.ssh/vps1.cyberciti.biz.key", + "LocalForward=3128 127.0.0.1:3128", + "[custom options] IdentityFile=/nfs/shared/users/nixcraft/keys/server1/id_rsa,/tmp/x.rsa", + "[custom options] ProxyCommand=ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null", ] general_options = { - "forwardx11": "no", + "ForwardX11": "no", "protocol": "2", - "user": "nixcraft", - "forwardagent": "no", - "forwardx11trusted": "yes", - "serveralivecountmax": "30", - "serveraliveinterval": "60", - "port": "22", - "localforward": "3128 127.0.0.1:3128, 3129 127.0.0.1:3128", + "User": "nixcraft", + "ForwardAgent": "no", + "ForwardX11Trusted": "yes", + "ServerAliveCountMax": "30", + "ServerAliveInterval": "60", + "Port": "22", + "LocalForward": "3128 127.0.0.1:3128, 3129 127.0.0.1:3128", } for host in hosts: @@ -175,9 +175,9 @@ def test_advanced_add(self): with open(self.config_file) as f: # check that property is really flushed out to the config? content = f.read().encode('ascii') - self.assertIn(b'identityfile "/tmp/idfilecheck.rsa"', content) - self.assertIn(b"stricthostkeychecking yes", content) - self.assertIn(b"userknownhostsfile /dev/advanced_test", content) + self.assertIn(b'IdentityFile "/tmp/idfilecheck.rsa"', content) + self.assertIn(b"StrictHostKeyChecking yes", content) + self.assertIn(b"UserKnownHostsFile /dev/advanced_test", content) def test_add_with_idfile(self): out, err, rc = self.run_cmd('add postgresql-server postgres@192.168.1.1 {0} {1}'.format( @@ -190,7 +190,7 @@ def test_add_with_idfile(self): with open(self.config_file) as f: content = f.read().encode('ascii') - self.assertIn(b'identityfile "/tmp/idfileonlycheck.rsa"', content) + self.assertIn(b'IdentityFile "/tmp/idfileonlycheck.rsa"', content) def test_basic_edit(self): out, err, rc = self.run_cmd('edit aws.apache basic_edit_check@10.20.30.40 {0}'.format(self.config_arg)) @@ -225,8 +225,8 @@ def test_update(self): with open(self.config_file) as f: content = f.read().encode('ascii') - self.assertIn(b"user daghan", content) # see daghan: http://instagram.com/p/lfPMW_qVja - self.assertIn(b"port 42000", content) + self.assertIn(b"User daghan", content) # see daghan: http://instagram.com/p/lfPMW_qVja + self.assertIn(b"Port 42000", content) def test_update_regex(self): @@ -336,7 +336,7 @@ def test_config_load(self): def test_config_dump(self): self.storm.ssh_config.write_to_ssh_config() - for search_str in ("hostname 1.1.1.1", "Host netscaler", "Host *"): + for search_str in ("Hostname 1.1.1.1", "Host netscaler", "Host *"): with open('/tmp/ssh_config') as fd: self.assertIn(search_str, fd.read()) @@ -360,11 +360,11 @@ def test_clone_host(self): has_yahoo = False for item in self.storm.ssh_config.config_data: - if item.get("host") == 'yahoo': + if item.get("host") == 'yahoo': has_yahoo = True break - self.assertEqual(True, has_yahoo) + self.assertEqual(True, has_yahoo) self.assertEqual(item.get("options").get("port"), '24') self.assertEqual(item.get("options").get("identityfile"), '"/tmp/tmp.pub"') self.assertEqual(item.get("options").get("user"), 'ops') diff --git a/tox.ini b/tox.ini index 0cabe09..5e4c2e1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,15 +1,14 @@ [tox] -envlist = py27,py32,py33,py34 +envlist = py27,py32,py33,py34,py35,py36,py37,py38 skipsdist = True [testenv] +whitelist_externals = rm +commands_pre = + rm -f .coverage commands = - pip install --allow-all-external -e . + pip install -e . nosetests deps = + -cconstraints.txt -rrequirements-dev.txt - -[testenv:py26] -deps = - {[testenv]deps} - unittest2