Skip to content

Conversation

@perseoGI
Copy link
Contributor

@perseoGI perseoGI commented Nov 21, 2024

Changelog: Feature: Introducing SSH runner
Docs: conan-io/docs#3972

Changes:

  • Fix creation of virtual environment
  • Retrieve current conan client version and install remotely if it does not exist
  • Error checks
  • Create a custom SSH Logger based on ConanOutput
  • Properly manage build and host profiles, copying to the runner machine if needed and keeping the jinja2 templates intact to allow remote runner to render them locally
  • General code refactor: some improvements like typing and simplifying code flow
  • Fix cache inconsistency when a package have been created remotely and already restored the cache locally. If the package is removed locally, the runner was never bringing the package back to local. This happens because it only compress the build recipes. Solved by always exporting all the packages and forcing a local cache refresh
  • Correctly manage special characters like & and * during ssh remote invoques
  • Add different runner types detection on profile composition
  • Refer to the issue that supports this Pull Request.
  • If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
  • I've read the Contributing guide.
  • I've followed the PEP8 style guides for Python code.
  • I've opened another PR in the Conan docs repo to the develop branch, documenting this one.

@perseoGI perseoGI marked this pull request as ready for review November 22, 2024 13:15
@perseoGI perseoGI modified the milestones: 2.10.0, 2.11 Nov 29, 2024
@memsharded memsharded modified the milestones: 2.11.0, 2.12.0 Dec 18, 2024
@czoido czoido modified the milestones: 2.12.0, 2.13.0 Jan 27, 2025
@perseoGI perseoGI force-pushed the pgi/ssh-runner branch 11 times, most recently from e0ce817 to 83c905d Compare January 30, 2025 15:19
@memsharded memsharded modified the milestones: 2.13.0, 2.14.0 Feb 24, 2025
@memsharded memsharded modified the milestones: 2.14.0, 2.15.0 Mar 10, 2025
@czoido czoido modified the milestones: 2.15.0, 2.16.0 Mar 31, 2025
assert "Restore: pkg/0.2:8631cf963dbbb4d7a378a64a6fd1dc57558bc2fe" in client.out
assert "Restore: pkg/0.2:8631cf963dbbb4d7a378a64a6fd1dc57558bc2fe metadata" in client.out
assert "Removing container" in client.out

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please enable pep-8 style checks in your IDE.

@AbrilRBS AbrilRBS modified the milestones: 2.16.0, 2.X Apr 24, 2025
from conan.test.assets.sources import gen_function_h, gen_function_cpp


def docker_from_env():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels like tests related to the Docker runner belong in a separate PR - if changes are needed for this PR, maybe open a new one that can be independently reviewed and merged ahead of this?

hostname = self.host_profile.runner.get("host")
if not hostname:
raise ConanException("Host not specified in runner.ssh configuration")
configfile = self.host_profile.runner.get("configfile", False)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is being changed - I like that we can accept a path to a file, rather than assume the user, but the name config variable seems odd - I would be more explicit - both openssh and paramiko call it "ssh_config"

if ssh_config and ssh_config.lookup(hostname):
hostname = ssh_config.lookup(hostname)['hostname']
self.client = SSHClient()
self.client.set_missing_host_key_policy(AutoAddPolicy()) # Auto accept unknown keys
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the sake of security, drop this line. Assume the user has previously explicitly added the host key - this needs to be documented and done outside of Conan's realm

python_command = remote_folder + "/venv" + "/Scripts" + "/python.exe"
else:
python_command = remote_folder + "/venv" + "/bin" + "/python"
if not self.remote_conn.run_command(f"{conan_cmd} remote update conancenter --url='https://center2.conan.io'", "Updating conancenter remote").success:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outside of the scope of the SSH runner - given that the runner will create a new conan home on the fly (and that this feature was never publicly released) - users will already have the correct value for Conan Center, so this is unnecessary.

def _install_conan_remotely(self, python_command: str, version: str):
self.output.verbose(f"Installing conan version: {version}")
# Note: this may fail on windows
result = self.remote_conn.run_command(f"{python_command} -m pip install conan{f'=={version}' if version != 'dev' else ' --upgrade'}", "Installing conan")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not --force-reinstall if we are currently assuming the venv only has a single version?

result = self.remote_conn.run_command(f'{self.remote_python_command} -c "import os; print(os.name)"', "Checking remote OS type")
if not result.success:
self.output.error("Unable to determine remote OS type")
self.is_remote_windows = result.stdout == "nt"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep self.remote_is_windows

self.client: SSHClient = client
self.runner_output = runner_output

def put(self, src: str, dst: str) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put, get and mkdir are specific FTP words - so they make sense for sftp.put(), but not so much for a RemoteConnection.put() - I would suggest calling them sftp_put() and spft_get() etc, if we are so inclined to have these in this class

if sys.stdout.isatty():
width, height = os.get_terminal_size()
else:
width, height = 80, 24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should fallback to paramiko's defaults, and not hardcode a new set without additional context

# This is the easiest and better working approach but not testable
# sys.stdout.buffer.write(line)
# sys.stdout.buffer.flush()
line = remove_cursor_movements(line.replace(b'\r', b'').decode(errors='ignore').strip())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I'm getting this right.. are we doing a line.replace() on every line (or rather, every chunk of read bytes, as they're not necessarily lines), and operating on each line with the remove_cursos_movements ?

what is the aim of this? it was working fine before (despite the slightly off logic for windows hosts) - we're dealing with potentially tens of megabytes of log data - should it not be streamed to the terminal as quickly as possible, minimising overhead to the fullest extent?

remote_tmp_dir = _Path(self.remote_create_dir)

# Check if recipe defines a root folder
app = ConanApp(self.conan_api)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be better explained in the PR description and I would suggest handling this in a separate PR for a proper discussion

With the proposed logic, the conanfile would be loaded 3 times:

  • during the first export locally
  • here
  • remotely, when invoking Conan create

We may as well simplify everything - we know that the recipe was already exported in the local cache - thus, all paths relative to the recipe were already resolved in order for that to succeed. We can export the recipe as it is in the cache, or use the cona cache save/restore for this - and just restore it remotely (we may need to switch a conan install + conan test instead) - but as I said, best discussed in a separate PR altogether

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants