ssh in python

 

paramiko package를 사용한다. 자주 ssh 접속하는 경우 key를 미리 등록해놓는 것이 편하다.(ssh-copy-id)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
from abc import ABCMeta, abstractmethod
from paramiko import SSHClient, AutoAddPolicy
from subprocess import run


class ConnectorFactory:
    def __init__(self, config):
        self.config = config
        if 'local' in config:
            self.local = config['local']
        else:
            self.local = False

    def get(self):
        if self.local:
            return LocalConnector(self.config)
        else:
            return ServerConnector(self.config)
class Connector(metaclass=ABCMeta):
    def __init__(self, config):
        self.config    = config
        self.id        = f" {config['username']}@{config['hostname']}:{config['port']} "
        self.bg_format = "nohup %s >> nohup.log 2>&1 &"

    def cmds(self, cmds):
        cmds = cmds.strip()
        proc_cmds = self._process_cmds(cmds)
        _, stdout, stderr = self._execute_cmds(proc_cmds)
        self._process_result(stdout, stderr)
    @abstractmethod
    def _process_cmds(self, cmds):
        pass
    @abstractmethod
    def _execute_cmds(self, cmds):
        pass
    @abstractmethod
    def _process_result(self, stdout, stderr):
        pass
class ServerConnector(Connector):
    def __init__(self, config):
        super().__init__(config)
        self.client = SSHClient()
        self.client.set_missing_host_key_policy(AutoAddPolicy)

    def cmds(self, cmds):
        self.client.connect(**self.config)
        super().cmds(cmds)
        self.client.close()

    def _process_cmds(self, cmds):
        return '\n'.join([self.bg_format % c.replace('&', '') if '&' in c else c for c in cmds.split('\n')])

    def _execute_cmds(self, cmds):
        return self.client.exec_command(cmds)

    def _process_result(self, stdout, stderr):
        for fd in (stdout, stderr):
            if lines := fd.readlines():
                print()
                print(f"┌{self.id:<80}┐")
                for line in lines:
                    print('│', line.strip())
                print(f"└{'─'*80}┘")
class LocalConnector(Connector):
    def _process_cmds(self, cmds):
        return ';'.join([self.bg_format % c.replace('&', '') if '&' in c else c for c in cmds.split('\n')])

    def _execute_cmds(self, cmds):
        stds = run(cmds, shell=True, executable='/bin/bash', capture_output=True)
        return None, stds.stdout, stds.stderr

    def _process_result(self, stdout, stderr):
        for fd in (stdout, stderr):
            if lines := fd.decode().strip():
                print()
                print(f"┌{self.id:<80}┐")
                for line in lines.split('\n'):
                    print('│', line)
                print(f"└{'─'*80}┘")


if __name__ == '__main__':
    cmds = """
    source /opt/conda/bin/activate rapids
    which python
    """

    configs = [
        dict(hostname='123.456.78.910', port=26030, username='root'),
        dict(hostname='123.456.78.911', port=10022, username='root', local=True)
    ]

    for config in configs:
        conn = ConnectorFactory(config).get()
        conn.cmds(cmds)
┌ root@123.456.78.910:26030 ─────────────────────────────────────────────────────┐
│ /opt/conda/envs/rapids/bin/python
└────────────────────────────────────────────────────────────────────────────────┘

┌ root@123.456.78.911:10022 ─────────────────────────────────────────────────────┐
│ /opt/conda/envs/rapids/bin/python
└────────────────────────────────────────────────────────────────────────────────┘