xbot.plugins.ssh 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- demo/lib/__init__.py +0 -0
- demo/lib/testbed.py +48 -0
- demo/lib/testcase.py +15 -0
- demo/testcases/__init__.py +0 -0
- demo/testcases/tc_sftp.py +99 -0
- demo/testcases/tc_ssh.py +73 -0
- tests/__init__.py +0 -0
- tests/run.py +39 -0
- tests/test_sftp.py +88 -0
- tests/test_ssh.py +99 -0
- tests/test_utils.py +18 -0
- venv/bin/jp.py +54 -0
- venv/bin/rst2html.py +23 -0
- venv/bin/rst2html4.py +26 -0
- venv/bin/rst2html5.py +34 -0
- venv/bin/rst2latex.py +26 -0
- venv/bin/rst2man.py +26 -0
- venv/bin/rst2odt.py +30 -0
- venv/bin/rst2odt_prepstyles.py +67 -0
- venv/bin/rst2pseudoxml.py +23 -0
- venv/bin/rst2s5.py +24 -0
- venv/bin/rst2xetex.py +27 -0
- venv/bin/rst2xml.py +23 -0
- venv/bin/rstpep2html.py +25 -0
- xbot/plugins/ssh/__init__.py +0 -0
- xbot/plugins/ssh/errors.py +19 -0
- xbot/plugins/ssh/sftp.py +211 -0
- xbot/plugins/ssh/ssh.py +309 -0
- xbot/plugins/ssh/utils.py +30 -0
- xbot/plugins/ssh/version.py +3 -0
- xbot.plugins.ssh-0.1.0.dist-info/LICENSE +24 -0
- xbot.plugins.ssh-0.1.0.dist-info/METADATA +276 -0
- xbot.plugins.ssh-0.1.0.dist-info/RECORD +35 -0
- xbot.plugins.ssh-0.1.0.dist-info/WHEEL +5 -0
- xbot.plugins.ssh-0.1.0.dist-info/top_level.txt +4 -0
demo/lib/__init__.py
ADDED
|
File without changes
|
demo/lib/testbed.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from xbot.framework import testbed
|
|
4
|
+
from xbot.plugins.ssh.ssh import SSHConnection
|
|
5
|
+
from xbot.plugins.ssh.sftp import SFTPConnection
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestBed(testbed.TestBed):
|
|
9
|
+
"""
|
|
10
|
+
TestBed for ssh demo.
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self, filepath: str):
|
|
13
|
+
"""
|
|
14
|
+
:param filepath: testbed filepath.
|
|
15
|
+
"""
|
|
16
|
+
super().__init__(filepath)
|
|
17
|
+
self._conns = {
|
|
18
|
+
'ssh': {},
|
|
19
|
+
'sftp': {}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def get_conn(
|
|
23
|
+
self,
|
|
24
|
+
typ: str,
|
|
25
|
+
role: str
|
|
26
|
+
) -> Union[SSHConnection, SFTPConnection]:
|
|
27
|
+
"""
|
|
28
|
+
Get specific connection to the specified role.
|
|
29
|
+
|
|
30
|
+
:param typ: connection type, 'ssh' or 'sftp'.
|
|
31
|
+
:param role: user role.
|
|
32
|
+
"""
|
|
33
|
+
typs = ('ssh', 'sftp')
|
|
34
|
+
if typ not in typs:
|
|
35
|
+
raise ValueError(f'Invalid connection type: {typ}, should be one of {typs}.')
|
|
36
|
+
for user in self.get('host.users'):
|
|
37
|
+
if user['role'] == role:
|
|
38
|
+
if role not in self._conns[typ]:
|
|
39
|
+
conn = SSHConnection() if typ == 'ssh' else SFTPConnection()
|
|
40
|
+
conn.connect(self.get('host.ip'),
|
|
41
|
+
user['name'],
|
|
42
|
+
user['password'],
|
|
43
|
+
port=self.get('host.sshport'))
|
|
44
|
+
self._conns[typ][role] = conn
|
|
45
|
+
return self._conns[typ][role]
|
|
46
|
+
raise ValueError(f'No such user: role={role}')
|
|
47
|
+
|
|
48
|
+
|
demo/lib/testcase.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from xbot.framework import testcase
|
|
2
|
+
|
|
3
|
+
from .testbed import TestBed
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestCase(testcase.TestCase):
|
|
7
|
+
"""
|
|
8
|
+
TestCase for ssh demo.
|
|
9
|
+
"""
|
|
10
|
+
@property
|
|
11
|
+
def testbed(self) -> TestBed:
|
|
12
|
+
"""
|
|
13
|
+
TestBed instance.
|
|
14
|
+
"""
|
|
15
|
+
return self.__testbed
|
|
File without changes
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
|
|
4
|
+
from xbot.framework.utils import assertx
|
|
5
|
+
|
|
6
|
+
from lib.testcase import TestCase
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class tc_sftp(TestCase):
|
|
10
|
+
"""
|
|
11
|
+
Testcase using SFTPConnection.
|
|
12
|
+
"""
|
|
13
|
+
TIMEOUT = 60
|
|
14
|
+
FAILFAST = True
|
|
15
|
+
TAGS = ['sftp']
|
|
16
|
+
|
|
17
|
+
def setup(self):
|
|
18
|
+
"""
|
|
19
|
+
Prepare test environment.
|
|
20
|
+
"""
|
|
21
|
+
self.sftp = self.testbed.get_conn('sftp', 'normal')
|
|
22
|
+
self.lhome = os.environ.get('HOME') or os.environ['HOMEPATH']
|
|
23
|
+
self.lputdir = os.path.join(self.lhome, 'lputdir')
|
|
24
|
+
self.lgetdir = os.path.join(self.lhome, 'lgetdir')
|
|
25
|
+
self.rputdir = '/tmp/rputgetdir'
|
|
26
|
+
self.rgetdir = self.rputdir
|
|
27
|
+
for d in (self.lputdir, self.lgetdir):
|
|
28
|
+
if os.path.exists(d):
|
|
29
|
+
shutil.rmtree(d)
|
|
30
|
+
self.sftp.makedirs(self.rputdir)
|
|
31
|
+
os.makedirs(os.path.join(self.lputdir, 'dir', 'subdir'))
|
|
32
|
+
os.system('whoami > %s' % os.path.join(self.lputdir, 'file'))
|
|
33
|
+
os.system('whoami > %s' % os.path.join(self.lputdir, 'dir', 'subfile'))
|
|
34
|
+
|
|
35
|
+
def step1(self):
|
|
36
|
+
"""
|
|
37
|
+
Test `putdir` method.
|
|
38
|
+
"""
|
|
39
|
+
l = os.path.join(self.lputdir, 'dir')
|
|
40
|
+
self.sftp.putdir(l, self.rputdir)
|
|
41
|
+
p1 = self.sftp.join(self.rputdir, 'dir', 'subdir')
|
|
42
|
+
assertx(self.sftp.exists(p1), '==', True)
|
|
43
|
+
p2 = self.sftp.join(self.rputdir, 'dir', 'subfile')
|
|
44
|
+
assertx(self.sftp.exists(p2), '==', True)
|
|
45
|
+
|
|
46
|
+
def step2(self):
|
|
47
|
+
"""
|
|
48
|
+
Test `putfile` method.
|
|
49
|
+
"""
|
|
50
|
+
l = os.path.join(self.lputdir, 'file')
|
|
51
|
+
self.sftp.putfile(l, self.rputdir)
|
|
52
|
+
p1 = self.sftp.join(self.rputdir, 'file')
|
|
53
|
+
assertx(self.sftp.exists(p1), '==', True)
|
|
54
|
+
# rename
|
|
55
|
+
self.sftp.putfile(l, self.rputdir, 'newfile')
|
|
56
|
+
p2 = self.sftp.join(self.rputdir, 'newfile')
|
|
57
|
+
assertx(self.sftp.exists(p2), '==', True)
|
|
58
|
+
|
|
59
|
+
def step3(self):
|
|
60
|
+
"""
|
|
61
|
+
Test `getdir` method.
|
|
62
|
+
"""
|
|
63
|
+
r = self.sftp.join(self.rgetdir, 'dir')
|
|
64
|
+
self.sftp.getdir(r, self.lgetdir)
|
|
65
|
+
p1 = os.path.join(self.lgetdir, 'dir', 'subdir')
|
|
66
|
+
assertx(os.path.exists(p1), '==', True)
|
|
67
|
+
p2 = os.path.join(self.lgetdir, 'dir', 'subfile')
|
|
68
|
+
assertx(os.path.exists(p2), '==', True)
|
|
69
|
+
|
|
70
|
+
def step4(self):
|
|
71
|
+
"""
|
|
72
|
+
Test `getfile` method.
|
|
73
|
+
"""
|
|
74
|
+
r = self.sftp.join(self.rgetdir, 'file')
|
|
75
|
+
self.sftp.getfile(r, self.lgetdir)
|
|
76
|
+
p1 = os.path.join(self.lgetdir, 'file')
|
|
77
|
+
assertx(os.path.exists(p1), '==', True)
|
|
78
|
+
# rename
|
|
79
|
+
self.sftp.getfile(r, self.lgetdir, 'newfile')
|
|
80
|
+
p2 = os.path.join(self.lgetdir, 'newfile')
|
|
81
|
+
assertx(os.path.exists(p2), '==', True)
|
|
82
|
+
|
|
83
|
+
def step5(self):
|
|
84
|
+
"""
|
|
85
|
+
Test `open` method.
|
|
86
|
+
"""
|
|
87
|
+
p = self.sftp.join(self.rputdir, 'file')
|
|
88
|
+
with self.sftp.open(p, 'w') as fp:
|
|
89
|
+
fp.write('xbot')
|
|
90
|
+
with self.sftp.open(p, 'r') as fp:
|
|
91
|
+
assertx(fp.read().decode('utf-8'), '==', 'xbot')
|
|
92
|
+
|
|
93
|
+
def teardown(self):
|
|
94
|
+
"""
|
|
95
|
+
Clean up test environment.
|
|
96
|
+
"""
|
|
97
|
+
shutil.rmtree(self.lputdir)
|
|
98
|
+
shutil.rmtree(self.lgetdir)
|
|
99
|
+
|
demo/testcases/tc_ssh.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from lib.testcase import TestCase
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class tc_ssh(TestCase):
|
|
5
|
+
"""
|
|
6
|
+
Testcase using SSHConnection.
|
|
7
|
+
"""
|
|
8
|
+
TIMEOUT = 60
|
|
9
|
+
FAILFAST = True
|
|
10
|
+
TAGS = ['ssh']
|
|
11
|
+
|
|
12
|
+
def setup(self):
|
|
13
|
+
"""
|
|
14
|
+
Prepare test environment.
|
|
15
|
+
"""
|
|
16
|
+
self.ssh = self.testbed.get_conn('ssh', 'normal')
|
|
17
|
+
|
|
18
|
+
def step1(self):
|
|
19
|
+
"""
|
|
20
|
+
Execute command `whoami`, expect `name` of the normal user.
|
|
21
|
+
"""
|
|
22
|
+
username = self.testbed.get("host.users[?role=='normal']")[0]['name']
|
|
23
|
+
self.ssh.exec('whoami', expect=username)
|
|
24
|
+
|
|
25
|
+
def step2(self):
|
|
26
|
+
"""
|
|
27
|
+
Execute command `sudo whoami`, expect `root`.
|
|
28
|
+
"""
|
|
29
|
+
self.ssh.sudo('whoami', expect='root')
|
|
30
|
+
|
|
31
|
+
def step3(self):
|
|
32
|
+
"""
|
|
33
|
+
Execute a interactive command.
|
|
34
|
+
"""
|
|
35
|
+
self.ssh.exec("read -p 'input: '", prompts={'input:': 'hello'})
|
|
36
|
+
|
|
37
|
+
def step4(self):
|
|
38
|
+
"""
|
|
39
|
+
Execute a command and expect no error.
|
|
40
|
+
"""
|
|
41
|
+
self.ssh.exec('ls /tmp', expect=0)
|
|
42
|
+
|
|
43
|
+
def step5(self):
|
|
44
|
+
"""
|
|
45
|
+
Execute a command and expect return code is 2.
|
|
46
|
+
"""
|
|
47
|
+
self.ssh.exec('ls /errpath', expect=2)
|
|
48
|
+
|
|
49
|
+
def step5(self):
|
|
50
|
+
"""
|
|
51
|
+
Execute a command and expect nothing.
|
|
52
|
+
"""
|
|
53
|
+
self.ssh.exec('ls /tmp', expect=None)
|
|
54
|
+
self.ssh.exec('ls /errpath', expect=None)
|
|
55
|
+
|
|
56
|
+
def step6(self):
|
|
57
|
+
"""
|
|
58
|
+
Execute a command and expect a specific output.
|
|
59
|
+
"""
|
|
60
|
+
self.ssh.exec('echo hello', expect='hello')
|
|
61
|
+
|
|
62
|
+
def step7(self):
|
|
63
|
+
"""
|
|
64
|
+
Execute a command with cd context manager.
|
|
65
|
+
"""
|
|
66
|
+
with self.ssh.cd('/tmp'):
|
|
67
|
+
self.ssh.exec('pwd', expect='/tmp')
|
|
68
|
+
|
|
69
|
+
def teardown(self):
|
|
70
|
+
"""
|
|
71
|
+
Clean up test environment.
|
|
72
|
+
"""
|
|
73
|
+
pass
|
tests/__init__.py
ADDED
|
File without changes
|
tests/run.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Run tests.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
import argparse
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from test_ssh import TestSSHConnection
|
|
11
|
+
from test_sftp import TestSFTPConnection
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
15
|
+
parser = argparse.ArgumentParser()
|
|
16
|
+
parser.add_argument('-H', '--host', required=True,
|
|
17
|
+
help='hostname or ip to test.')
|
|
18
|
+
parser.add_argument('-u', '--user', required=True,
|
|
19
|
+
help='username for host.')
|
|
20
|
+
parser.add_argument('-p', '--password', required=True,
|
|
21
|
+
help='password for host.')
|
|
22
|
+
parser.add_argument('-P', '--port', type=int, default=22,
|
|
23
|
+
help='ssh port for host.')
|
|
24
|
+
return parser
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == '__main__':
|
|
28
|
+
args = create_parser().parse_args()
|
|
29
|
+
TestSSHConnection.HOST = args.host
|
|
30
|
+
TestSSHConnection.USER = args.user
|
|
31
|
+
TestSSHConnection.PWD = args.password
|
|
32
|
+
TestSSHConnection.PORT = args.port
|
|
33
|
+
TestSFTPConnection.HOST = args.host
|
|
34
|
+
TestSFTPConnection.USER = args.user
|
|
35
|
+
TestSFTPConnection.PWD = args.password
|
|
36
|
+
TestSFTPConnection.PORT = args.port
|
|
37
|
+
startdir = Path(__file__).parent
|
|
38
|
+
testsuit = unittest.TestLoader().discover(startdir)
|
|
39
|
+
unittest.TextTestRunner(verbosity=2).run(testsuit)
|
tests/test_sftp.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Copyright (c) 2022-2023, zhaowcheng <zhaowcheng@163.com>
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
import shutil
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
sys.path.append(os.path.abspath(f'{__file__}/../..'))
|
|
8
|
+
|
|
9
|
+
from xbot.plugins.ssh.sftp import SFTPConnection
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestSFTPConnection(unittest.TestCase):
|
|
13
|
+
|
|
14
|
+
HOST = ''
|
|
15
|
+
PORT = 22
|
|
16
|
+
USER = ''
|
|
17
|
+
PWD = ''
|
|
18
|
+
|
|
19
|
+
sftp = SFTPConnection()
|
|
20
|
+
|
|
21
|
+
LHOME = os.environ.get('HOME') or os.environ['HOMEPATH']
|
|
22
|
+
LPUTDIR = os.path.join(LHOME, 'lputdir')
|
|
23
|
+
LGETDIR = os.path.join(LHOME, 'lgetdir')
|
|
24
|
+
RPUTDIR = '/tmp/rputgetdir'
|
|
25
|
+
RGETDIR = RPUTDIR
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def setUpClass(cls):
|
|
29
|
+
cls.sftp.connect(cls.HOST, cls.USER,
|
|
30
|
+
cls.PWD, cls.PORT)
|
|
31
|
+
if not cls.sftp.exists(cls.RPUTDIR):
|
|
32
|
+
cls.sftp.makedirs(cls.RPUTDIR)
|
|
33
|
+
os.makedirs(os.path.join(cls.LPUTDIR, 'dir', 'subdir'))
|
|
34
|
+
os.system('whoami > %s' % os.path.join(cls.LPUTDIR, 'file'))
|
|
35
|
+
os.system('whoami > %s' % os.path.join(cls.LPUTDIR, 'dir', 'subfile'))
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def tearDownClass(cls):
|
|
39
|
+
shutil.rmtree(cls.LPUTDIR)
|
|
40
|
+
shutil.rmtree(cls.LGETDIR)
|
|
41
|
+
cls.sftp.disconnect()
|
|
42
|
+
|
|
43
|
+
def test_01_putdir(self):
|
|
44
|
+
l = os.path.join(self.LPUTDIR, 'dir')
|
|
45
|
+
self.sftp.putdir(l, self.RPUTDIR)
|
|
46
|
+
p1 = self.sftp.join(self.RPUTDIR, 'dir', 'subdir')
|
|
47
|
+
self.assertTrue(self.sftp.exists(p1))
|
|
48
|
+
p2 = self.sftp.join(self.RPUTDIR, 'dir', 'subfile')
|
|
49
|
+
self.assertTrue(self.sftp.exists(p2))
|
|
50
|
+
|
|
51
|
+
def test_02_putfile(self):
|
|
52
|
+
l = os.path.join(self.LPUTDIR, 'file')
|
|
53
|
+
self.sftp.putfile(l, self.RPUTDIR)
|
|
54
|
+
p1 = self.sftp.join(self.RPUTDIR, 'file')
|
|
55
|
+
self.assertTrue(self.sftp.exists(p1))
|
|
56
|
+
# rename
|
|
57
|
+
self.sftp.putfile(l, self.RPUTDIR, 'newfile')
|
|
58
|
+
p2 = self.sftp.join(self.RPUTDIR, 'newfile')
|
|
59
|
+
self.assertTrue(self.sftp.exists(p2))
|
|
60
|
+
|
|
61
|
+
def test_03_getdir(self):
|
|
62
|
+
r = self.sftp.join(self.RGETDIR, 'dir')
|
|
63
|
+
self.sftp.getdir(r, self.LGETDIR)
|
|
64
|
+
p1 = os.path.join(self.LGETDIR, 'dir', 'subdir')
|
|
65
|
+
self.assertTrue(os.path.exists(p1))
|
|
66
|
+
p2 = os.path.join(self.LGETDIR, 'dir', 'subfile')
|
|
67
|
+
self.assertTrue(os.path.exists(p2))
|
|
68
|
+
|
|
69
|
+
def test_04_getfile(self):
|
|
70
|
+
r = self.sftp.join(self.RGETDIR, 'file')
|
|
71
|
+
self.sftp.getfile(r, self.LGETDIR)
|
|
72
|
+
p1 = os.path.join(self.LGETDIR, 'file')
|
|
73
|
+
self.assertTrue(os.path.exists(p1))
|
|
74
|
+
# rename
|
|
75
|
+
self.sftp.getfile(r, self.LGETDIR, 'newfile')
|
|
76
|
+
p2 = os.path.join(self.LGETDIR, 'newfile')
|
|
77
|
+
self.assertTrue(os.path.exists(p2))
|
|
78
|
+
|
|
79
|
+
def test_05_open(self):
|
|
80
|
+
p = self.sftp.join(self.RPUTDIR, 'file')
|
|
81
|
+
with self.sftp.open(p, 'a') as fp:
|
|
82
|
+
fp.write('xbot\n')
|
|
83
|
+
with self.sftp.open(p, 'r') as fp:
|
|
84
|
+
self.assertIn('xbot', fp.read().decode('utf-8'))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == '__main__':
|
|
88
|
+
unittest.main(verbosity=2)
|
tests/test_ssh.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Copyright (c) 2022-2023, zhaowcheng <zhaowcheng@163.com>
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
import doctest
|
|
5
|
+
import sys
|
|
6
|
+
import os
|
|
7
|
+
sys.path.append(os.path.abspath(f'{__file__}/../..'))
|
|
8
|
+
|
|
9
|
+
from xbot.plugins.ssh import ssh
|
|
10
|
+
from xbot.plugins.ssh.ssh import SSHConnection
|
|
11
|
+
from xbot.plugins.ssh.errors import SSHConnectError, SSHCommandError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load_tests(loader, tests, ignore):
|
|
15
|
+
tests.addTests(doctest.DocTestSuite(ssh))
|
|
16
|
+
return tests
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestSSHConnection(unittest.TestCase):
|
|
20
|
+
|
|
21
|
+
HOST = ''
|
|
22
|
+
PORT = 22
|
|
23
|
+
USER = ''
|
|
24
|
+
PWD = ''
|
|
25
|
+
|
|
26
|
+
conn = SSHConnection()
|
|
27
|
+
|
|
28
|
+
def setenv(self, name: str, value: str) -> None:
|
|
29
|
+
self.conn.exec(f'export {name}="{value}"')
|
|
30
|
+
|
|
31
|
+
def getenv(self, name: str) -> str:
|
|
32
|
+
return self.conn.exec(f'echo ${name}')
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def setUpClass(cls) -> None:
|
|
36
|
+
cls.conn.connect(cls.HOST, cls.USER, cls.PWD, cls.PORT)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def tearDownClass(cls) -> None:
|
|
40
|
+
cls.conn.disconnect()
|
|
41
|
+
|
|
42
|
+
def test_connect_error_pwd(self):
|
|
43
|
+
conn = SSHConnection()
|
|
44
|
+
with self.assertRaises(SSHConnectError) as cm:
|
|
45
|
+
conn.connect(self.HOST, self.USER, self.PWD + 'shit', self.PORT)
|
|
46
|
+
self.assertIn('please check whether the username and password are correct',
|
|
47
|
+
str(cm.exception))
|
|
48
|
+
|
|
49
|
+
def test_connect_error_port(self):
|
|
50
|
+
conn = SSHConnection()
|
|
51
|
+
with self.assertRaises(SSHConnectError) as cm:
|
|
52
|
+
conn.connect(self.HOST, self.USER, self.PWD, self.PORT + 1000)
|
|
53
|
+
self.assertRegex(str(cm.exception),
|
|
54
|
+
r'.*please check whether the port is (opened|correct).*')
|
|
55
|
+
|
|
56
|
+
def test_connect_error_ip(self):
|
|
57
|
+
conn = SSHConnection()
|
|
58
|
+
with self.assertRaises(SSHConnectError) as cm:
|
|
59
|
+
conn.connect('128.0.0.1', self.USER, self.PWD, self.PORT)
|
|
60
|
+
self.assertIn('please check whether the network is normal',
|
|
61
|
+
str(cm.exception))
|
|
62
|
+
|
|
63
|
+
def test_cmd_whoami(self):
|
|
64
|
+
self.conn.exec('whoami', expect=self.USER)
|
|
65
|
+
|
|
66
|
+
def test_cmd_sudo(self):
|
|
67
|
+
self.conn.sudo('whoami', expect='root')
|
|
68
|
+
|
|
69
|
+
def test_cmd_interact(self):
|
|
70
|
+
self.conn.exec("read -p 'input: '", prompts={'input:': 'hello'})
|
|
71
|
+
|
|
72
|
+
def test_expect_0(self):
|
|
73
|
+
self.conn.exec('ls /tmp', expect=0)
|
|
74
|
+
with self.assertRaises(SSHCommandError):
|
|
75
|
+
self.conn.exec('ls /errpath', expect=0)
|
|
76
|
+
|
|
77
|
+
def test_expect_2(self):
|
|
78
|
+
self.conn.exec('ls /errpath', expect=2)
|
|
79
|
+
|
|
80
|
+
def test_expect_none(self):
|
|
81
|
+
self.conn.exec('ls /tmp', expect=None)
|
|
82
|
+
self.conn.exec('ls /errpath', expect=None)
|
|
83
|
+
|
|
84
|
+
def test_expect_str(self):
|
|
85
|
+
self.conn.exec('echo hello', expect='hello')
|
|
86
|
+
with self.assertRaises(SSHCommandError):
|
|
87
|
+
self.conn.exec('echo hello', expect='world')
|
|
88
|
+
|
|
89
|
+
def test_timeout(self):
|
|
90
|
+
with self.assertRaises(TimeoutError):
|
|
91
|
+
self.conn.exec('sleep 3', timeout=1)
|
|
92
|
+
|
|
93
|
+
def test_cmd_cd(self):
|
|
94
|
+
with self.conn.cd('/tmp'):
|
|
95
|
+
self.assertEqual(self.conn.exec('pwd'), '/tmp')
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == '__main__':
|
|
99
|
+
unittest.main(verbosity=2)
|
tests/test_utils.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Copyright (c) 2022-2023, zhaowcheng <zhaowcheng@163.com>
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
import doctest
|
|
5
|
+
import sys
|
|
6
|
+
import os
|
|
7
|
+
sys.path.append(os.path.abspath(f'{__file__}/../..'))
|
|
8
|
+
|
|
9
|
+
from xbot.plugins.ssh import utils
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def load_tests(loader, tests, ignore):
|
|
13
|
+
tests.addTests(doctest.DocTestSuite(utils))
|
|
14
|
+
return tests
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if __name__ == '__main__':
|
|
18
|
+
unittest.main(verbosity=2)
|
venv/bin/jp.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/Users/wan/CodeProjects/xbot.plugins.ssh/venv/bin/python3.6
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import argparse
|
|
6
|
+
from pprint import pformat
|
|
7
|
+
|
|
8
|
+
import jmespath
|
|
9
|
+
from jmespath import exceptions
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
parser = argparse.ArgumentParser()
|
|
14
|
+
parser.add_argument('expression')
|
|
15
|
+
parser.add_argument('-f', '--filename',
|
|
16
|
+
help=('The filename containing the input data. '
|
|
17
|
+
'If a filename is not given then data is '
|
|
18
|
+
'read from stdin.'))
|
|
19
|
+
parser.add_argument('--ast', action='store_true',
|
|
20
|
+
help=('Pretty print the AST, do not search the data.'))
|
|
21
|
+
args = parser.parse_args()
|
|
22
|
+
expression = args.expression
|
|
23
|
+
if args.ast:
|
|
24
|
+
# Only print the AST
|
|
25
|
+
expression = jmespath.compile(args.expression)
|
|
26
|
+
sys.stdout.write(pformat(expression.parsed))
|
|
27
|
+
sys.stdout.write('\n')
|
|
28
|
+
return 0
|
|
29
|
+
if args.filename:
|
|
30
|
+
with open(args.filename, 'r') as f:
|
|
31
|
+
data = json.load(f)
|
|
32
|
+
else:
|
|
33
|
+
data = sys.stdin.read()
|
|
34
|
+
data = json.loads(data)
|
|
35
|
+
try:
|
|
36
|
+
sys.stdout.write(json.dumps(
|
|
37
|
+
jmespath.search(expression, data), indent=4, ensure_ascii=False))
|
|
38
|
+
sys.stdout.write('\n')
|
|
39
|
+
except exceptions.ArityError as e:
|
|
40
|
+
sys.stderr.write("invalid-arity: %s\n" % e)
|
|
41
|
+
return 1
|
|
42
|
+
except exceptions.JMESPathTypeError as e:
|
|
43
|
+
sys.stderr.write("invalid-type: %s\n" % e)
|
|
44
|
+
return 1
|
|
45
|
+
except exceptions.UnknownFunctionError as e:
|
|
46
|
+
sys.stderr.write("unknown-function: %s\n" % e)
|
|
47
|
+
return 1
|
|
48
|
+
except exceptions.ParseError as e:
|
|
49
|
+
sys.stderr.write("syntax-error: %s\n" % e)
|
|
50
|
+
return 1
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == '__main__':
|
|
54
|
+
sys.exit(main())
|
venv/bin/rst2html.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/Users/wan/CodeProjects/xbot.plugins.ssh/venv/bin/python3.6
|
|
2
|
+
|
|
3
|
+
# $Id: rst2html.py 4564 2006-05-21 20:44:42Z wiemann $
|
|
4
|
+
# Author: David Goodger <goodger@python.org>
|
|
5
|
+
# Copyright: This module has been placed in the public domain.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
A minimal front end to the Docutils Publisher, producing HTML.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import locale
|
|
13
|
+
locale.setlocale(locale.LC_ALL, '')
|
|
14
|
+
except:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
from docutils.core import publish_cmdline, default_description
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
description = ('Generates (X)HTML documents from standalone reStructuredText '
|
|
21
|
+
'sources. ' + default_description)
|
|
22
|
+
|
|
23
|
+
publish_cmdline(writer_name='html', description=description)
|
venv/bin/rst2html4.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/Users/wan/CodeProjects/xbot.plugins.ssh/venv/bin/python3.6
|
|
2
|
+
|
|
3
|
+
# $Id: rst2html4.py 7994 2016-12-10 17:41:45Z milde $
|
|
4
|
+
# Author: David Goodger <goodger@python.org>
|
|
5
|
+
# Copyright: This module has been placed in the public domain.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
A minimal front end to the Docutils Publisher, producing (X)HTML.
|
|
9
|
+
|
|
10
|
+
The output conforms to XHTML 1.0 transitional
|
|
11
|
+
and almost to HTML 4.01 transitional (except for closing empty tags).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import locale
|
|
16
|
+
locale.setlocale(locale.LC_ALL, '')
|
|
17
|
+
except:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
from docutils.core import publish_cmdline, default_description
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
description = ('Generates (X)HTML documents from standalone reStructuredText '
|
|
24
|
+
'sources. ' + default_description)
|
|
25
|
+
|
|
26
|
+
publish_cmdline(writer_name='html4', description=description)
|
venv/bin/rst2html5.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/Users/wan/CodeProjects/xbot.plugins.ssh/venv/bin/python3.6
|
|
2
|
+
# -*- coding: utf8 -*-
|
|
3
|
+
# :Copyright: © 2015 Günter Milde.
|
|
4
|
+
# :License: Released under the terms of the `2-Clause BSD license`_, in short:
|
|
5
|
+
#
|
|
6
|
+
# Copying and distribution of this file, with or without modification,
|
|
7
|
+
# are permitted in any medium without royalty provided the copyright
|
|
8
|
+
# notice and this notice are preserved.
|
|
9
|
+
# This file is offered as-is, without any warranty.
|
|
10
|
+
#
|
|
11
|
+
# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause
|
|
12
|
+
#
|
|
13
|
+
# Revision: $Revision: 8567 $
|
|
14
|
+
# Date: $Date: 2020-09-30 13:57:21 +0200 (Mi, 30. Sep 2020) $
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
A minimal front end to the Docutils Publisher, producing HTML 5 documents.
|
|
18
|
+
|
|
19
|
+
The output is also valid XML.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import locale # module missing in Jython
|
|
24
|
+
locale.setlocale(locale.LC_ALL, '')
|
|
25
|
+
except locale.Error:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
from docutils.core import publish_cmdline, default_description
|
|
29
|
+
|
|
30
|
+
description = (u'Generates HTML5 documents from standalone '
|
|
31
|
+
u'reStructuredText sources.\n'
|
|
32
|
+
+ default_description)
|
|
33
|
+
|
|
34
|
+
publish_cmdline(writer_name='html5', description=description)
|
venv/bin/rst2latex.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/Users/wan/CodeProjects/xbot.plugins.ssh/venv/bin/python3.6
|
|
2
|
+
|
|
3
|
+
# $Id: rst2latex.py 5905 2009-04-16 12:04:49Z milde $
|
|
4
|
+
# Author: David Goodger <goodger@python.org>
|
|
5
|
+
# Copyright: This module has been placed in the public domain.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
A minimal front end to the Docutils Publisher, producing LaTeX.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import locale
|
|
13
|
+
locale.setlocale(locale.LC_ALL, '')
|
|
14
|
+
except:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
from docutils.core import publish_cmdline
|
|
18
|
+
|
|
19
|
+
description = ('Generates LaTeX documents from standalone reStructuredText '
|
|
20
|
+
'sources. '
|
|
21
|
+
'Reads from <source> (default is stdin) and writes to '
|
|
22
|
+
'<destination> (default is stdout). See '
|
|
23
|
+
'<http://docutils.sourceforge.net/docs/user/latex.html> for '
|
|
24
|
+
'the full reference.')
|
|
25
|
+
|
|
26
|
+
publish_cmdline(writer_name='latex', description=description)
|
venv/bin/rst2man.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/Users/wan/CodeProjects/xbot.plugins.ssh/venv/bin/python3.6
|
|
2
|
+
|
|
3
|
+
# Author:
|
|
4
|
+
# Contact: grubert@users.sf.net
|
|
5
|
+
# Copyright: This module has been placed in the public domain.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
man.py
|
|
9
|
+
======
|
|
10
|
+
|
|
11
|
+
This module provides a simple command line interface that uses the
|
|
12
|
+
man page writer to output from ReStructuredText source.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import locale
|
|
16
|
+
try:
|
|
17
|
+
locale.setlocale(locale.LC_ALL, '')
|
|
18
|
+
except:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
from docutils.core import publish_cmdline, default_description
|
|
22
|
+
from docutils.writers import manpage
|
|
23
|
+
|
|
24
|
+
description = ("Generates plain unix manual documents. " + default_description)
|
|
25
|
+
|
|
26
|
+
publish_cmdline(writer=manpage.Writer(), description=description)
|