summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJeff Forcier <jeff@bitprophet.org>2019-07-02 21:19:50 -0400
committerJeff Forcier <jeff@bitprophet.org>2019-08-26 20:48:25 -0400
commit77ea8aca1c78dcc1f00b675e3212d253018f3004 (patch)
tree7d306949b5c334387c713f98cb62875e5c3f1b1c
parent5a56eed757c598301f1cc1f5a31e9d06aa081ac1 (diff)
Add new SSHConfig constructors
-rw-r--r--paramiko/config.py45
-rw-r--r--paramiko/util.py3
-rw-r--r--sites/www/changelog.rst4
-rw-r--r--tests/test_config.py53
4 files changed, 88 insertions, 17 deletions
diff --git a/paramiko/config.py b/paramiko/config.py
index af194452..f9ea02dc 100644
--- a/paramiko/config.py
+++ b/paramiko/config.py
@@ -49,9 +49,54 @@ class SSHConfig(object):
def __init__(self):
"""
Create a new OpenSSH config object.
+
+ Note: the newer alternate constructors `from_path`, `from_file` and
+ `from_text` are simpler to use, as they parse on instantiation. For
+ example, instead of::
+
+ config = SSHConfig()
+ config.parse(open("some-path.config")
+
+ you could::
+
+ config = SSHConfig.from_file(open("some-path.config"))
+ # Or more directly:
+ config = SSHConfig.from_path("some-path.config")
+ # Or if you have arbitrary ssh_config text from some other source:
+ config = SSHConfig.from_text("Host foo\\n\\tUser bar")
"""
self._config = []
+ @classmethod
+ def from_text(cls, text):
+ """
+ Create a new, parsed `SSHConfig` from ``text`` string.
+
+ .. versionadded:: 2.7
+ """
+ return cls.from_file(StringIO(text))
+
+ @classmethod
+ def from_path(cls, path):
+ """
+ Create a new, parsed `SSHConfig` from the file found at ``path``.
+
+ .. versionadded:: 2.7
+ """
+ with open(path) as flo:
+ return cls.from_file(flo)
+
+ @classmethod
+ def from_file(cls, flo):
+ """
+ Create a new, parsed `SSHConfig` from file-like object ``flo``.
+
+ .. versionadded:: 2.7
+ """
+ obj = cls()
+ obj.parse(flo)
+ return obj
+
def parse(self, file_obj):
"""
Read an OpenSSH config from the given file object.
diff --git a/paramiko/util.py b/paramiko/util.py
index 29c52bfb..93970289 100644
--- a/paramiko/util.py
+++ b/paramiko/util.py
@@ -194,6 +194,9 @@ def load_host_keys(filename):
def parse_ssh_config(file_obj):
"""
Provided only as a backward-compatible wrapper around `.SSHConfig`.
+
+ .. deprecated:: 2.7
+ Use `SSHConfig.from_file` instead.
"""
config = SSHConfig()
config.parse(file_obj)
diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst
index e664f7d4..6718f888 100644
--- a/sites/www/changelog.rst
+++ b/sites/www/changelog.rst
@@ -2,6 +2,10 @@
Changelog
=========
+- :feature:`-` Add new convenience classmethod constructors to
+ `~paramiko.config.SSHConfig`: `~paramiko.config.SSHConfig.from_text`,
+ `~paramiko.config.SSHConfig.from_file`, and
+ `~paramiko.config.SSHConfig.from_path`. No more annoying two-step process!
- :release:`2.6.0 <2019-06-23>`
- :feature:`1463` Add a new keyword argument to `SSHClient.connect
<paramiko.client.SSHClient.connect>` and `~paramiko.transport.Transport`,
diff --git a/tests/test_config.py b/tests/test_config.py
index cecb6204..185c0173 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -9,7 +9,7 @@ from pytest import raises, mark
from paramiko.py3compat import StringIO
from paramiko import SSHConfig, SSHConfigDict
-from paramiko.util import lookup_ssh_host_config, parse_ssh_config
+from paramiko.util import lookup_ssh_host_config
from .util import _support
@@ -21,8 +21,27 @@ class TestSSHConfig(object):
def teardown(self):
self.config_flo.close()
+ def test_init(self):
+ # No args!
+ with raises(TypeError):
+ SSHConfig("uh oh!")
+ # No args.
+ assert not SSHConfig()._config
+
+ def test_from_text(self):
+ config = SSHConfig.from_text(self.config_flo.read())
+ assert config.lookup("foo.example.com")["user"] == "robey"
+
+ def test_from_file(self):
+ config = SSHConfig.from_file(self.config_flo)
+ assert config.lookup("whatever")["user"] == "robey"
+
+ def test_from_path(self):
+ config = SSHConfig.from_path(_support("robey.config"))
+ assert config.lookup("meh.example.com")["port"] == "3333"
+
def test_parse_config(self):
- config = parse_ssh_config(self.config_flo)
+ config = SSHConfig.from_file(self.config_flo)
expected = [
{"host": ["*"], "config": {}},
{
@@ -42,7 +61,7 @@ class TestSSHConfig(object):
assert config._config == expected
def test_host_config(self):
- config = parse_ssh_config(self.config_flo)
+ config = SSHConfig.from_file(self.config_flo)
for host, values in {
"irc.danger.com": {
@@ -82,7 +101,7 @@ Host *
Port 3333
"""
f = StringIO(test_config_file)
- config = parse_ssh_config(f)
+ config = SSHConfig.from_file(f)
host = "www13.example.com"
expected = {"hostname": host, "port": "22"}
assert lookup_ssh_host_config(host, config) == expected
@@ -99,7 +118,7 @@ Host equals-delimited
ProxyCommand=foo bar=biz baz
"""
f = StringIO(conf)
- config = parse_ssh_config(f)
+ config = SSHConfig.from_file(f)
for host in ("space-delimited", "equals-delimited"):
value = lookup_ssh_host_config(host, config)["proxycommand"]
assert value == "foo bar=biz baz"
@@ -108,7 +127,7 @@ Host equals-delimited
"""
ProxyCommand should perform interpolation on the value
"""
- config = parse_ssh_config(
+ config = SSHConfig.from_file(
StringIO(
"""
Host specific
@@ -135,7 +154,7 @@ Host *
"""
Tilde (~) should be expanded inside ProxyCommand
"""
- config = parse_ssh_config(
+ config = SSHConfig.from_file(
StringIO(
"""
Host test
@@ -164,7 +183,7 @@ Host *
Port 3333
"""
f = StringIO(test_config_file)
- config = parse_ssh_config(f)
+ config = SSHConfig.from_file(f)
host = "www13.example.com"
expected = {"hostname": host, "port": "8080"}
assert lookup_ssh_host_config(host, config) == expected
@@ -196,7 +215,7 @@ ProxyCommand foo=bar:%h-%p
}.items():
f = StringIO(test_config_file)
- config = parse_ssh_config(f)
+ config = SSHConfig.from_file(f)
assert lookup_ssh_host_config(host, config) == values
def test_host_config_test_identityfile(self):
@@ -226,7 +245,7 @@ IdentityFile id_dsa22
}.items():
f = StringIO(test_config_file)
- config = parse_ssh_config(f)
+ config = SSHConfig.from_file(f)
assert lookup_ssh_host_config(host, config) == values
def test_config_addressfamily_and_lazy_fqdn(self):
@@ -237,7 +256,7 @@ IdentityFile id_dsa22
AddressFamily inet
IdentityFile something_%l_using_fqdn
"""
- config = parse_ssh_config(StringIO(test_config))
+ config = SSHConfig.from_file(StringIO(test_config))
assert config.lookup(
"meh"
) # will die during lookup() if bug regresses
@@ -249,7 +268,7 @@ IdentityFile something_%l_using_fqdn
assert config.lookup("abcqwerty")["hostname"] == "127.0.0.1"
def test_get_hostnames(self):
- config = parse_ssh_config(self.config_flo)
+ config = SSHConfig.from_file(self.config_flo)
expected = {"*", "*.example.com", "spoo.example.com"}
assert config.get_hostnames() == expected
@@ -281,7 +300,7 @@ Host param4 "p a r" "p" "par" para
"para": {"hostname": "para", "port": "4444"},
}
f = StringIO(test_config_file)
- config = parse_ssh_config(f)
+ config = SSHConfig.from_file(f)
for host, values in res.items():
assert lookup_ssh_host_config(host, config) == values
@@ -312,7 +331,7 @@ Host param3 parara
},
}
f = StringIO(test_config_file)
- config = parse_ssh_config(f)
+ config = SSHConfig.from_file(f)
for host, values in res.items():
assert lookup_ssh_host_config(host, config) == values
@@ -356,7 +375,7 @@ Host proxycommand-with-equals-none
}.items():
f = StringIO(test_config_file)
- config = parse_ssh_config(f)
+ config = SSHConfig.from_file(f)
assert lookup_ssh_host_config(host, config) == values
def test_proxycommand_none_masking(self):
@@ -428,7 +447,7 @@ class TestSSHConfigDict(object):
Port 3333
"""
f = StringIO(test_config_file)
- config = parse_ssh_config(f)
+ config = SSHConfig.from_file(f)
assert config.lookup("foo.example.com").as_int("port") == 2222
def test_SSHConfig_wildcard_host_dicts_are_SSHConfigDict_instances(self):
@@ -440,5 +459,5 @@ class TestSSHConfigDict(object):
Port 3333
"""
f = StringIO(test_config_file)
- config = parse_ssh_config(f)
+ config = SSHConfig.from_file(f)
assert config.lookup("anything-else").as_int("port") == 3333