diff --git a/.travis.yml b/.travis.yml
index c3a81b4..515d349 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,6 @@ python:
- "2.7"
- "3.4"
- "3.5"
- - "3.5-dev" # 3.5 development branch
install:
- git clone https://github.com/sstephenson/bats.git && cd bats && ./install.sh .. && cd -
- sudo apt-get install pandoc
diff --git a/termius/porting/providers/securecrt/parser.py b/termius/porting/providers/securecrt/parser.py
index 661687f..962c309 100644
--- a/termius/porting/providers/securecrt/parser.py
+++ b/termius/porting/providers/securecrt/parser.py
@@ -1,14 +1,15 @@
# -*- coding: utf-8 -*-
"""Module with SecureCRT parser."""
-import collections
from os.path import expanduser
+
class SecureCRTConfigParser(object):
"""SecureCRT xml parser."""
meta_sessions = ['Default']
def __init__(self, xml):
+ """Construct parser instance."""
self.xml = xml
self.tree = {}
@@ -23,6 +24,7 @@ def parse_hosts(self):
return self.tree
def parse_sessions(self, sessions, parent_node):
+ """Parse SecureCRT sessions."""
for session in sessions:
if session.get('name') not in self.meta_sessions:
if not self.is_session_group(session):
@@ -31,13 +33,14 @@ def parse_sessions(self, sessions, parent_node):
continue
parent_node[host['label']] = host
else:
- parent_node[session.get('name')] = {'group': True}
+ parent_node[session.get('name')] = {'__group': True}
self.parse_sessions(
session.getchildren(),
parent_node[session.get('name')]
)
def is_session_group(self, session):
+ """Check node element type"""
return self.get_element_by_name(
session.getchildren(), 'Hostname'
) is None
diff --git a/termius/porting/providers/securecrt/provider.py b/termius/porting/providers/securecrt/provider.py
index 160d67a..a2528ce 100644
--- a/termius/porting/providers/securecrt/provider.py
+++ b/termius/porting/providers/securecrt/provider.py
@@ -16,7 +16,7 @@ class SecureCRTPortingProvider(BasePortingProvider):
logger = logging.getLogger(__name__)
def __init__(self, source, *args, **kwargs):
- """Contruct new service to sync ssh config."""
+ """Construct provider instance."""
super(SecureCRTPortingProvider, self).__init__(*args, **kwargs)
xml_root = ElementTree.parse(source).getroot()
@@ -27,7 +27,7 @@ def export_hosts(self):
pass
def provider_hosts(self):
- """Retrieve host instances from ssh config."""
+ """Retrieve SecureCRT sessions from xml config."""
result_hosts = []
tree = self.parser.parse_hosts()
@@ -54,14 +54,16 @@ def provider_hosts(self):
self.create_entries_from_tree(tree, result_hosts, root_group)
self.logger.info('Parsed hosts %i' % len(result_hosts))
+ self.logger.info('Importing...')
return result_hosts
def create_entries_from_tree(self, tree, result_hosts, parent_group=None):
- for label, node in tree.iteritems():
+ """Create instances from groups tree."""
+ for label, node in tree.items():
if not isinstance(node, dict):
continue
- if not node.get('group', None):
+ if not node.get('__group', None):
result_hosts.append(
self.create_host(node, parent_group)
)
@@ -70,6 +72,7 @@ def create_entries_from_tree(self, tree, result_hosts, parent_group=None):
self.create_entries_from_tree(node, result_hosts, group)
def create_host(self, raw_host, group):
+ """Create instances from groups tree."""
host = Host(
label=raw_host['label'],
address=raw_host['hostname'],
@@ -91,10 +94,11 @@ def create_host(self, raw_host, group):
return host
def create_key(self, identity_paths):
- with open(identity_paths[0], 'r') as private_key_file:
+ """Create ssh key instance."""
+ with open(identity_paths[0], 'rb') as private_key_file:
private_key = private_key_file.read()
- with open(identity_paths[1], 'r') as public_key_file:
+ with open(identity_paths[1], 'rb') as public_key_file:
public_key = public_key_file.read()
return SshKey(
diff --git a/tests/unit/porting/providers/securecrt_test.py b/tests/unit/porting/providers/securecrt_test.py
new file mode 100644
index 0000000..0d05e52
--- /dev/null
+++ b/tests/unit/porting/providers/securecrt_test.py
@@ -0,0 +1,161 @@
+from io import BytesIO
+from unittest import TestCase
+
+from mock import patch, mock_open, call
+
+from termius.core.models.terminal import Host, Group, SshConfig, Identity, \
+ SshKey
+from termius.porting.providers.securecrt.provider import\
+ SecureCRTPortingProvider
+
+
+class SecureCRTProviderTest(TestCase):
+ def test_tree_parsing(self):
+ securecrt_config = """
+
+
+
+ 0
+ addr0
+ user0
+
+
+
+
+ 1
+ addr1
+ user1
+
+
+
+
+ 2
+ addr2
+ user2
+
+
+ serial_user
+
+
+
+
+
+ """
+ xml_path = '/some/path/securecrt.xml'
+ root_group = Group(label='SecureCRT')
+ hosts_folder = Group(
+ label='hosts', parent_group=root_group
+ )
+ expected = [
+ Host(
+ label='host2',
+ address='addr2',
+ group=Group(label='folder1', parent_group=hosts_folder),
+ ssh_config=SshConfig(
+ port='2',
+ identity=Identity(
+ label='user2', username='user2', is_visible=False
+ )
+ )
+ ),
+ Host(
+ label='host1',
+ address='addr1',
+ group=Group(label='folder0', parent_group=hosts_folder),
+ ssh_config=SshConfig(
+ port='1',
+ identity=Identity(
+ label='user1', username='user1', is_visible=False
+ )
+ )
+ ),
+ Host(
+ label='host0', address='addr0', group=root_group,
+ ssh_config=SshConfig(
+ port='0',
+ identity=Identity(
+ label='user0', username='user0', is_visible=False
+ )
+ )
+ )
+ ]
+
+ with patch('xml.etree.ElementTree.open',
+ mock_open(read_data=securecrt_config)) as mocked_xml:
+ service = SecureCRTPortingProvider(xml_path, None, '')
+ hosts = service.provider_hosts()
+ mocked_xml.assert_called_once_with(xml_path, 'rb')
+ self.assertEquals(
+ self.sort_hosts(hosts),
+ self.sort_hosts(expected)
+ )
+
+ def test_ssh2_identity_parsing(self):
+ public_key = b'public'
+ private_key = b'private'
+ xml_path = '/some/path/securecrt.xml'
+
+ root_group = Group(
+ label='SecureCRT',
+ ssh_config=SshConfig(
+ identity=Identity(
+ label='SecureCRT',
+ is_visible=False,
+ ssh_key=SshKey(
+ label='SecureCRT',
+ private_key=private_key,
+ public_key=public_key
+ )
+ )
+ )
+ )
+ securecrt_config = """
+
+
+
+ 0
+ addr0
+ user0
+
+
+
+ /Users/termius/folder/key.pub::rawkey
+
+
+ """
+ expected = [
+ Host(
+ label='host0', group=root_group, address='addr0',
+ ssh_config=SshConfig(port='0',
+ identity=Identity(
+ label='user0',
+ is_visible=False,
+ username='user0'
+ )
+ )
+ )
+ ]
+ expected_calls = [
+ call('/Users/termius/folder/key', 'rb'),
+ call('/Users/termius/folder/key.pub', 'rb')
+ ]
+
+ with patch('xml.etree.ElementTree.open',
+ mock_open(read_data=securecrt_config)) as mocked_xml:
+
+ with patch('termius.porting.providers.securecrt.provider.open',
+ mock_open(read_data='')) as mocked_open:
+ service = SecureCRTPortingProvider(xml_path, None, '')
+ mocked_open.side_effect = [
+ BytesIO(private_key), BytesIO(public_key)
+ ]
+ hosts = service.provider_hosts()
+ self.assertEquals(
+ self.sort_hosts(hosts),
+ self.sort_hosts(expected)
+ )
+ mocked_xml.assert_called_once_with(xml_path, 'rb')
+ mocked_open.assert_has_calls(expected_calls)
+
+ def sort_hosts(self, hosts):
+ return sorted(hosts, key=lambda host: host['address'])
\ No newline at end of file