|
12 | 12 | from io import StringIO |
13 | 13 |
|
14 | 14 | import boto3 |
15 | | -import paramiko |
16 | 15 | from botocore.exceptions import ClientError |
17 | | -from paramiko.client import AutoAddPolicy, SSHClient |
18 | | -from scp import SCPClient |
19 | 16 | from vmtest.util import get_free_port, wait_ssh_ready |
20 | 17 |
|
21 | 18 | AWS_REGION = "us-east-1" |
22 | 19 |
|
23 | | -# XXX: find better way to control this |
24 | | -if os.environ.get("OSBUILD_TEST_QEMU_VERBOSE"): |
25 | | - logging.getLogger("paramiko").setLevel(logging.DEBUG) |
26 | | - logging.getLogger("paramiko").addHandler(logging.StreamHandler(sys.stderr)) |
| 20 | + |
| 21 | +_non_interactive_ssh = [ |
| 22 | + "-o", "UserKnownHostsFile=/dev/null", |
| 23 | + "-o" "StrictHostKeyChecking=no", |
| 24 | +] |
27 | 25 |
|
28 | 26 |
|
29 | 27 | class VM(abc.ABC): |
@@ -54,50 +52,46 @@ def force_stop(self): |
54 | 52 | Stop the VM and clean up any resources that were created when setting up and starting the machine. |
55 | 53 | """ |
56 | 54 |
|
57 | | - def _get_ssh_transport(self, user, password="", keyfile=None): |
58 | | - if not self.running(): |
59 | | - self.start() |
60 | | - client = SSHClient() |
61 | | - client.set_missing_host_key_policy(AutoAddPolicy) |
62 | | - # workaround, see https://github.com/paramiko/paramiko/issues/2048 |
63 | | - pkey = None |
64 | | - if keyfile: |
65 | | - for klass in paramiko.key_classes: |
66 | | - try: |
67 | | - pkey = klass.from_private_key_file(keyfile) |
68 | | - break |
69 | | - except paramiko.ssh_exception.SSHException: |
70 | | - continue |
71 | | - if pkey is None: |
72 | | - raise RuntimeError(f"cannot load {keyfile}, tried {paramiko.key_classes}") |
73 | | - client.connect( |
74 | | - self._address, self._ssh_port, |
75 | | - user, password, pkey=pkey, |
76 | | - allow_agent=False, look_for_keys=False) |
77 | | - return client.get_transport() |
| 55 | + def _sshpass(self, password): |
| 56 | + if not password: |
| 57 | + return [] |
| 58 | + return ["sshpass", "-p", password] |
78 | 59 |
|
79 | 60 | def run(self, cmd, user, password="", keyfile=None): |
80 | 61 | """ |
81 | 62 | Run a command on the VM via SSH using the provided credentials. |
82 | 63 | """ |
83 | | - tr = self._get_ssh_transport(user, password, keyfile) |
84 | | - chan = tr.open_session() |
85 | | - chan.get_pty() |
86 | | - chan.exec_command(cmd) |
87 | | - stdout_f = chan.makefile() |
| 64 | + if not self.running(): |
| 65 | + self.start() |
| 66 | + ssh_cmd = self._sshpass(password) + [ |
| 67 | + "ssh", "-p", str(self._ssh_port), |
| 68 | + ] + _non_interactive_ssh |
| 69 | + if keyfile: |
| 70 | + ssh_cmd.extend(["-i", keyfile]) |
| 71 | + ssh_cmd.append(f"{user}@{self._address}") |
| 72 | + ssh_cmd.append(cmd) |
88 | 73 | output = StringIO() |
89 | | - while True: |
90 | | - out = stdout_f.readline() |
91 | | - if not out: |
92 | | - break |
93 | | - self._log(out) |
94 | | - output.write(out) |
95 | | - exit_status = stdout_f.channel.recv_exit_status() |
96 | | - return exit_status, output.getvalue() |
| 74 | + with subprocess.Popen( |
| 75 | + ssh_cmd, |
| 76 | + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, |
| 77 | + text=True, bufsize=1, |
| 78 | + ) as p: |
| 79 | + for out in p.stdout: |
| 80 | + self._log(out) |
| 81 | + output.write(out) |
| 82 | + return p.returncode, output |
97 | 83 |
|
98 | 84 | def scp(self, src, dst, user, password="", keyfile=None): |
99 | | - with SCPClient(self._get_ssh_transport(user, password, keyfile)) as scp: |
100 | | - scp.put(src, dst) |
| 85 | + if not self.running(): |
| 86 | + self.start() |
| 87 | + scp_cmd = self._sshpass(password) + [ |
| 88 | + "scp", "-P", str(self._ssh_port), |
| 89 | + ] + _non_interactive_ssh |
| 90 | + if keyfile: |
| 91 | + scp_cmd.extend(["-i", keyfile]) |
| 92 | + scp_cmd.append(src) |
| 93 | + scp_cmd.append(f"{user}@{self._address}:{dst}") |
| 94 | + subprocess.check_call(scp_cmd) |
101 | 95 |
|
102 | 96 | @abc.abstractmethod |
103 | 97 | def running(self): |
|
0 commit comments