Skip to content

Commit 8643736

Browse files
committed
ssh runner: partially migrated RemoteConnection from conan-io#17357
1 parent 2825235 commit 8643736

File tree

1 file changed

+74
-30
lines changed

1 file changed

+74
-30
lines changed

conan/internal/runner/ssh.py

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from pathlib import Path
22
import pathlib
33
import tempfile
4+
from typing import Iterable
45

56
from conan.api.output import Color, ConanOutput
67
from conan.errors import ConanException
@@ -9,6 +10,8 @@
910
from io import BytesIO
1011
import sys
1112

13+
from conan.internal.runner.output import RunnerOutput
14+
1215
def ssh_info(msg, error=False):
1316
fg=Color.BRIGHT_MAGENTA
1417
if error:
@@ -48,6 +51,8 @@ def __init__(self, conan_api, command, host_profile, build_profile, args, raw_ar
4851
self.client = SSHClient()
4952
self.client.load_system_host_keys()
5053
self.client.connect(hostname)
54+
self.runner_output = RunnerOutput(hostname)
55+
self.remote_conn = RemoteConnection(self.client, self.runner_output)
5156

5257

5358
def run(self, use_cache=True):
@@ -138,14 +143,7 @@ def ensure_runner_environment(self):
138143
ssh_info(f"Expected remote conan command: {conan_cmd}")
139144

140145
# Check if remote Conan executable exists, otherwise invoke pip inside venv
141-
sftp = self.client.open_sftp()
142-
try:
143-
sftp.stat(conan_cmd)
144-
has_remote_conan = True
145-
except FileNotFoundError:
146-
has_remote_conan = False
147-
finally:
148-
sftp.close()
146+
has_remote_conan = self.remote_conn.check_file_exists(conan_cmd)
149147

150148
if not has_remote_conan:
151149
_, _stdout, _stderr = self.client.exec_command(f"{python_command} -m venv {conan_venv}")
@@ -183,26 +181,15 @@ def ensure_runner_environment(self):
183181
_, _stdout, _stderr = self.client.exec_command(f"{self.remote_conan} config home")
184182
ssh_info(f"Remote conan config home returned: {_stdout.read().decode().strip()}")
185183
_, _stdout, _stderr = self.client.exec_command(f"{self.remote_conan} profile detect --force")
186-
self._copy_profiles()
187-
188-
189-
def _copy_profiles(self):
190-
sftp = self.client.open_sftp()
184+
self._sync_conan_config()
191185

192-
# TODO: very questionable choices here
193-
try:
194-
profiles = {
195-
self.args.profile_host[0]: self.host_profile.dumps(),
196-
self.args.profile_build[0]: self.build_profile.dumps()
197-
}
198-
199-
for name, contents in profiles.items():
200-
dest_filename = self.remote_conan_home + f"/profiles/{name}"
201-
sftp.putfo(BytesIO(contents.encode()), dest_filename)
202-
except:
203-
raise ConanException("Unable to copy profiles to remote")
204-
finally:
205-
sftp.close()
186+
def _sync_conan_config(self):
187+
# Transfer conan config to remote
188+
self.remote_conn.put_dir(
189+
self.conan_api.config.home(),
190+
self.remote_conan_home,
191+
exclude_dir=["__pycache__", "p"],
192+
)
206193

207194
def copy_working_conanfile_path(self):
208195
resolved_path = Path(self.args.path).resolve()
@@ -220,6 +207,7 @@ def copy_working_conanfile_path(self):
220207
self.remote_create_dir = _stdout.read().decode().strip().replace("\\", '/')
221208

222209
# Copy current folder to destination using sftp
210+
# self.remote_conn.put_dir(resolved_path.as_posix(), self.remote_create_dir)
223211
_Path = pathlib.PureWindowsPath if self.remote_is_windows else pathlib.PurePath
224212
sftp = self.client.open_sftp()
225213
for root, dirs, files in os.walk(resolved_path.as_posix()):
@@ -265,8 +253,64 @@ def update_local_cache(self, json_result):
265253
if stdout.channel.recv_exit_status() != 0:
266254
raise ConanException("Unable to save remote conan cache state")
267255

268-
sftp = self.client.open_sftp()
269256
with tempfile.TemporaryDirectory() as tmp:
270257
local_cache_tgz = os.path.join(tmp, 'cache.tgz')
271-
sftp.get(conan_cache_tgz, local_cache_tgz)
272-
package_list = self.conan_api.cache.restore(local_cache_tgz)
258+
self.remote_conn.get(conan_cache_tgz, local_cache_tgz)
259+
self.conan_api.cache.restore(local_cache_tgz)
260+
261+
262+
class RemoteConnection:
263+
def __init__(self, client, runner_output: RunnerOutput):
264+
from paramiko.client import SSHClient
265+
self.client: SSHClient = client
266+
self.runner_output = runner_output
267+
268+
def put(self, src: str, dst: str) -> None:
269+
try:
270+
sftp = self.client.open_sftp()
271+
sftp.put(src, dst)
272+
sftp.close()
273+
except IOError as e:
274+
self.runner_output.error(f"Unable to copy {src} to {dst}:\n{e}")
275+
276+
def put_dir(self, src: str, dst: str, exclude_dir: Iterable[str] = []) -> None:
277+
source_folder = Path(src)
278+
destination_folder = Path(dst)
279+
for item in source_folder.iterdir():
280+
dest_item = (destination_folder / item.name).as_posix()
281+
if item.is_file():
282+
self.runner_output.status(f"Copying file {item.as_posix()} to {dest_item}")
283+
self.put(item.as_posix(), dest_item)
284+
elif item.is_dir() and item.name not in exclude_dir:
285+
self.runner_output.status(f"Copying directory {src} to {dst}")
286+
self.mkdir(dest_item, ignore_existing=True)
287+
self.put_dir(item.as_posix(), dest_item, exclude_dir)
288+
289+
def get(self, src: str, dst: str) -> None:
290+
try:
291+
sftp = self.client.open_sftp()
292+
sftp.get(src, dst)
293+
sftp.close()
294+
except IOError as e:
295+
self.runner_output.error(f"Unable to copy from remote {src} to {dst}:\n{e}")
296+
297+
def mkdir(self, folder: str, ignore_existing=False) -> None:
298+
sftp = self.client.open_sftp()
299+
try:
300+
sftp.mkdir(folder)
301+
except IOError:
302+
if ignore_existing:
303+
pass
304+
else:
305+
raise
306+
finally:
307+
sftp.close()
308+
309+
def check_file_exists(self, file: str) -> bool:
310+
try:
311+
sftp = self.client.open_sftp()
312+
sftp.stat(file)
313+
sftp.close()
314+
return True
315+
except FileNotFoundError:
316+
return False

0 commit comments

Comments
 (0)