-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d5ac68f
Showing
9 changed files
with
377 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
*.pyc | ||
__pycache__ | ||
venv | ||
.cache | ||
*.egg-info | ||
.eggs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sshconfig |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
language: python | ||
python: | ||
- "2.7" | ||
install: | ||
- python setup.py install | ||
script: | ||
- "py.test ." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2017 Søren A. Davidsen | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
|
||
sshconfig | ||
=========== | ||
|
||
Sshconfig is a library for reading and modifying your ssh/config file in a non-intrusive way, meaning | ||
your file should look more or less the same after modifications. Idea is to keep it simple, | ||
so you can modify it for your needs. | ||
|
||
Read more about ssh config files here: [Create SSH config file on Linux](https://www.cyberciti.biz/faq/create-ssh-config-file-on-linux-unix/) | ||
|
||
|
||
Installation and usage | ||
--------------------------- | ||
|
||
Install through pip is the most easy way. You can install from the Git source directly: | ||
|
||
pip install -e https://github.com/sorend/sshconfig.git | ||
|
||
Below is some example use: | ||
|
||
from __future__ import print_function | ||
from sshconfig import read_ssh_config, empty_ssh_config | ||
from os.path import expanduser | ||
|
||
c = read_ssh_config(expanduser("~/.ssh/config")) | ||
print("hosts", c.hosts()) | ||
|
||
# assuming you have a host "svu" | ||
print("svu host", c.host("svu")) # print the settings | ||
c.update("svu", Hostname="ssh.svu.local", Port=1234) | ||
print("svu host now", c.host("svu")) | ||
|
||
c.add("newsvu", Hostname="ssh-new.svu.local", Port=22, User="stud1234") | ||
print("newsvu", c.host("newsvu")) | ||
|
||
c.rename("newsvu", "svu-new") | ||
print("svu-new", c.host("svu-new")) | ||
|
||
c.write(expanduser("~/.ssh/newconfig")) # write to new file | ||
|
||
# creating a new config file. | ||
c2 = empty_ssh_config() | ||
c2.add("svu", Hostname="ssh.svu.local", User="teachmca", Port=22) | ||
c2.write("newconfig") | ||
|
||
|
||
About | ||
----- | ||
|
||
sshconfig is created at the Department of Computer Science at Sri Venkateswara University, Tirupati, INDIA by a student as part of his projects. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# -*- coding: utf-8 -*- | ||
from setuptools import setup | ||
|
||
MY_VERSION = '0.0.1' | ||
|
||
setup( | ||
name='sshconfig', | ||
version=MY_VERSION, | ||
description='Lightweight SSH config library', | ||
author='Søren Atmakuri Davidsen', | ||
author_email='[email protected]', | ||
url='https://github.com/sorend/sshconfig', | ||
download_url='https://github.com/sorend/sshconfig/tarball/%s' % (MY_VERSION,), | ||
license='MIT', | ||
keywords=['ssh', 'config'], | ||
install_requires=[ | ||
], | ||
setup_requires=['pytest-runner'], | ||
tests_require=['pytest'], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
|
||
import re | ||
|
||
def read_ssh_config(path): | ||
""" | ||
Read ssh config file and return parsed SshConfig | ||
""" | ||
with open(path, "r") as f: | ||
lines = f.read().splitlines() | ||
return SshConfig(lines) | ||
|
||
def empty_ssh_config(): | ||
""" | ||
Creates a new empty ssh configuration. | ||
""" | ||
return SshConfig([]) | ||
|
||
def _key_value(line): | ||
no_comment = line.split("#")[0] | ||
return [ x.strip() for x in re.split(r"\s+", no_comment.strip(), 1) ] | ||
|
||
class SshConfig(object): | ||
""" | ||
Class for manipulating SSH configuration. | ||
""" | ||
def __init__(self, lines): | ||
self.lines_ = [] | ||
self.hosts_ = set() | ||
self.parse(lines) | ||
|
||
def parse(self, lines): | ||
cur_entry = None | ||
for line in lines: | ||
kv = _key_value(line) | ||
if len(kv) > 1: | ||
k, v = kv | ||
if k == "Host": | ||
cur_entry = v | ||
self.hosts_.add(v) | ||
self.lines_.append(dict(line=line, host=cur_entry, key=k, value=v)) | ||
else: | ||
self.lines_.append(dict(line=line, host=None)) | ||
|
||
def hosts(self): | ||
""" | ||
Return the hosts found in the configuration. | ||
Returns | ||
------- | ||
Tuple of Host entries (including "*" if found) | ||
""" | ||
return tuple(self.hosts_) | ||
|
||
def host(self, host): | ||
""" | ||
Return the configuration of a specific host as a dictionary. | ||
Parameters | ||
---------- | ||
host : the host to return values for. | ||
Returns | ||
------- | ||
dict of key value pairs, excluding "Host" | ||
""" | ||
if host in self.hosts_: | ||
return { k: v for k, v in [ (x["key"], x["value"]) for x in self.lines_ | ||
if x["host"] == host and x["key"] != "Host" ]} | ||
else: | ||
return {} | ||
|
||
def update(self, host, **kwargs): | ||
""" | ||
Update configuration for an existing host. Note, you can update the "Host" value, but | ||
it will still be referred to by the old "Host" value. | ||
Parameters | ||
---------- | ||
host : the Host to modify. | ||
**kwargs : The new configuration parameters | ||
""" | ||
if host not in self.hosts_: | ||
raise ValueError("Host %s: not found." % host) | ||
|
||
if "Host" in kwargs: | ||
raise ValueError("Cannot modify Host value with update, use rename.") | ||
|
||
def update_line(k, v): | ||
return "\t%s\t%s" % (k, v) | ||
|
||
for key, value in kwargs.items(): | ||
found = False | ||
for line in self.lines_: | ||
if line["host"] == host and line["key"] == key: | ||
line["value"] = value | ||
line["line"] = update_line(key, value) | ||
found = True | ||
|
||
if not found: | ||
max_idx = max([ idx for idx, line in enumerate(self.lines_) if line["host"] == host ]) | ||
self.lines_.insert(max_idx + 1, dict(line=update_line(key, value), | ||
host=host, key=key, value=value)) | ||
|
||
def rename(self, old_host, new_host): | ||
""" | ||
Renames a host configuration. | ||
Parameters | ||
---------- | ||
old_host : the host to rename. | ||
new_host : the new host value | ||
""" | ||
if new_host in self.hosts_: | ||
raise ValueError("Host %s: already exists." % new_host) | ||
for line in self.lines_: # update lines | ||
if line["host"] == old_host: | ||
line["host"] = new_host | ||
if line["key"] == "Host": | ||
line["value"] = new_host | ||
line["line"] = "Host\t%s" % new_host | ||
self.hosts_.remove(old_host) # update host cache | ||
self.hosts_.add(new_host) | ||
|
||
def add(self, host, **kwargs): | ||
""" | ||
Add another host to the SSH configuration. | ||
Parameters | ||
---------- | ||
host: The Host entry to add. | ||
**kwargs: The parameters for the host (without "Host" parameter itself) | ||
""" | ||
if host in self.hosts_: | ||
raise ValueError("Host %s: exists (use update)." % host) | ||
self.hosts_.add(host) | ||
self.lines_.append(dict(line="", host=None)) | ||
self.lines_.append(dict(line="Host\t%s" % host, host=host, key="Host", value=host)) | ||
for k, v in kwargs.items(): | ||
self.lines_.append(dict(line="\t%s\t%s" % (k, str(v)), host=host, key=k, value=v)) | ||
|
||
def config(self): | ||
""" | ||
Return the configuration as a string. | ||
""" | ||
return "\n".join([ x["line"] for x in self.lines_ ]) | ||
|
||
def write(self, path): | ||
""" | ||
Writes ssh config file | ||
Parameters | ||
---------- | ||
path : The file to write to | ||
""" | ||
with open(path, "w") as f: | ||
f.write(self.config()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
|
||
# comment | ||
Host * | ||
User something | ||
|
||
# comment 2 | ||
Host svu | ||
Hostname www.svuniversity.ac.in | ||
Port 22 | ||
ProxyCommand nc -w 300 -x localhost:9050 %h %p | ||
|
||
# another comment | ||
# bla bla |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
from __future__ import print_function | ||
import sshconfig | ||
import pytest | ||
import os | ||
|
||
test_config = os.path.join(os.path.dirname(__file__), "test_config") | ||
|
||
def test_parsing(): | ||
c = sshconfig.read_ssh_config(test_config) | ||
assert len(c.hosts()) == 2 | ||
assert c.host("*")["User"] == "something" | ||
assert c.host("svu")["ProxyCommand"] == "nc -w 300 -x localhost:9050 %h %p" | ||
|
||
s1 = c.config().splitlines() | ||
s2 = open(test_config).readlines() | ||
assert len(s1) == len(s2) | ||
|
||
def test_update(): | ||
c = sshconfig.read_ssh_config(test_config) | ||
|
||
c.update("svu", Compression="no", Port=2222) | ||
assert "\tCompression\tno" in c.config() | ||
assert "\tPort\t2222" in c.config() | ||
|
||
def test_update_host_failed(): | ||
c = sshconfig.read_ssh_config(test_config) | ||
|
||
with pytest.raises(ValueError): | ||
c.update("svu", Host="svu-new") | ||
|
||
def test_rename(): | ||
|
||
c = sshconfig.read_ssh_config(test_config) | ||
|
||
assert c.host("svu")["Hostname"] == "www.svuniversity.ac.in" | ||
|
||
c.rename("svu", "svu-new") | ||
|
||
assert "Host\tsvu-new" in c.config() | ||
assert "Host\tsvu\n" not in c.config() | ||
assert "svu" not in c.hosts() | ||
assert "svu-new" in c.hosts() | ||
|
||
c.update("svu-new", Port=123) # has to be success | ||
assert c.host("svu-new")["Port"] == 123 | ||
assert c.host("svu-new")["Hostname"] == "www.svuniversity.ac.in" # still same | ||
|
||
with pytest.raises(ValueError): # we can't refer to the renamed host | ||
c.update("svu", Port=123) | ||
|
||
def test_update_fail(): | ||
c = sshconfig.read_ssh_config(test_config) | ||
|
||
with pytest.raises(ValueError): | ||
c.update("notfound", Port=1234) | ||
|
||
def test_add(): | ||
|
||
c = sshconfig.read_ssh_config(test_config) | ||
|
||
c.add("venkateswara", Hostname="venkateswara.onion", User="other", Port=22, | ||
ProxyCommand="nc -w 300 -x localhost:9050 %h %p") | ||
|
||
assert "venkateswara" in c.hosts() | ||
assert c.host("venkateswara")["ProxyCommand"] == "nc -w 300 -x localhost:9050 %h %p" | ||
|
||
assert "Host\tvenkateswara" in c.config() | ||
|
||
with pytest.raises(ValueError): | ||
c.add("svu") | ||
|
||
with pytest.raises(ValueError): | ||
c.add("venkateswara") | ||
|
||
def test_save(): | ||
import tempfile | ||
|
||
tc = os.path.join(tempfile.gettempdir(), "temp_ssh_config-4123") | ||
try: | ||
c = sshconfig.read_ssh_config(test_config) | ||
|
||
c.update("svu", Hostname="ssh.svuniversity.ac.in", User="mca") | ||
c.write(tc) | ||
|
||
c2 = sshconfig.read_ssh_config(tc) | ||
assert c2.host("svu")["Hostname"] == "ssh.svuniversity.ac.in" | ||
assert c2.host("svu")["User"] == "mca" | ||
|
||
finally: | ||
os.remove(tc) | ||
|
||
def test_empty(): | ||
import tempfile | ||
tc = os.path.join(tempfile.gettempdir(), "temp_ssh_config-123") | ||
try: | ||
c = sshconfig.empty_ssh_config() | ||
c.add("svu33", Hostname="ssh33.svu.local", User="mca", Port=22) | ||
c.write(tc) | ||
c2 = sshconfig.read_ssh_config(tc) | ||
assert 1 == len(c2.hosts()) | ||
assert c2.host("svu33")["Hostname"] == "ssh33.svu.local" | ||
finally: | ||
os.remove(tc) |