Remote Dev Plugin Part 4 - bug fix and feature improvement#2569
Remote Dev Plugin Part 4 - bug fix and feature improvement#2569sfc-gh-zzhu wants to merge 2 commits into08-17-enable_remote_ssh_connectionfrom
Conversation
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
f2d260d to
e7f252e
Compare
|
|
||
| folder_uri = f"vscode-remote://ssh-remote+{service_name}{remote_path}" | ||
| try: | ||
| subprocess.run([binary_name, "--folder-uri", folder_uri], check=False) |
There was a problem hiding this comment.
The subprocess.run() call uses check=False, which means IDE launch failures won't raise exceptions. This could create a misleading user experience where the CLI appears to succeed even when the IDE fails to start. Consider either:
- Using
check=Trueto automatically raise exceptions on non-zero exit codes, or - Capturing the return value and checking it manually:
result = subprocess.run([binary_name, "--folder-uri", folder_uri], check=False) if result.returncode != 0: raise CliError(f"Failed to launch {binary_name}, exit code: {result.returncode}")
This would provide clearer feedback to users when their IDE fails to launch.
| subprocess.run([binary_name, "--folder-uri", folder_uri], check=False) | |
| result = subprocess.run([binary_name, "--folder-uri", folder_uri], check=False) | |
| if result.returncode != 0: | |
| raise CliError(f"Failed to launch {binary_name}, exit code: {result.returncode}") | |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
e7f252e to
cf658b9
Compare
cf658b9 to
db13c9c
Compare
| help="Internal Snowflake stage to mount for persistent storage. " | ||
| "The stage will be mounted at two locations: " | ||
| "1) 'stage/user-default' -> '/root/user-default' (workspace files), " | ||
| "2) 'stage/.vscode-server/data' -> '/root/.vscode-server/data' (VS Code settings). " | ||
| "Example: --stage @my_stage or --stage @my_stage/project", |
There was a problem hiding this comment.
Just curious what are the considerations for using block storage instead? Is there a reason we're not going with that currently?
| "--ssh", | ||
| help="Set up SSH configuration for connecting to the remote environment. This is a blocking command that keeps SSH connections alive.", | ||
| ), | ||
| code: bool = typer.Option( | ||
| False, | ||
| "--code", | ||
| help="Open VS Code connected to the remote service over SSH (mutually exclusive with --ssh and --cursor)", | ||
| ), | ||
| cursor: bool = typer.Option( | ||
| False, | ||
| "--cursor", | ||
| help="Open Cursor connected to the remote service over SSH (mutually exclusive with --ssh and --code)", | ||
| ), |
There was a problem hiding this comment.
WDYT of making these full subcommands rather than options? i.e. snow remote start cursor instead of snow remote start --cursor
There was a problem hiding this comment.
or alternatively flatten it a bit: snow remote cursor. Basically the commands would be:
snow remote start - start the service in the background, display the URL needed to connect by web interface
snow remote ssh - blocking command*, opens SSH tunnel
snow remote code - blocking command, open SSH tunnel + VSCode. As a bonus would be neat if this automatically exits if the user closes VSCode
snow remote cursor - same idea
*could consider making this non-blocking and spin up a bg process in the future, tbd
| launching_ide = code or cursor | ||
| if launching_ide: | ||
| manager.validate_ide_requirements(name, eai_name) |
There was a problem hiding this comment.
nit: doesn't it make sense to just ssh = ssh or launching_ide since IDEs presumably will require ssh?
| cc.warning( | ||
| f"Service '{service_name}' is already running. " | ||
| "Configuration changes provided to 'snow remote start' will be ignored for existing services. " | ||
| f"To apply changes, delete the service first with 'snow remote delete {service_name}' and then start it again with the new configuration." | ||
| ) |
There was a problem hiding this comment.
consider making this an error instead
|
|
||
| # Check if user provided configuration options that would modify the service | ||
| self._warn_about_config_changes(service_name, **config_options) | ||
|
|
There was a problem hiding this comment.
nit: consider refactoring so we're not calling this in three places
|
|
||
| folder_uri = f"vscode-remote://ssh-remote+{service_name}{remote_path}" | ||
| try: | ||
| subprocess.run([binary_name, "--folder-uri", folder_uri], check=False) |
db13c9c to
c9a1a5d
Compare
2610027 to
6d2eb90
Compare
|
|
||
| if not token: | ||
| cc.step("❌ Unable to get token. Retrying in 30 seconds...") | ||
| self._wait_with_shutdown_check(SSH_RETRY_INTERVAL) | ||
| self._interruptible_sleep(SSH_RETRY_INTERVAL) |
There was a problem hiding this comment.
There's an inconsistency between error handling approaches. The _get_fresh_token() method now raises exceptions rather than returning None, but this code still checks for None values. Consider updating this exception handling to align with the new behavior:
try:
token = self._get_fresh_token()
except Exception as e:
cc.step(f"❌ Unable to get token: {e}. Retrying in 30 seconds...")
self._interruptible_sleep(SSH_RETRY_INTERVAL)
continueThis would properly catch and handle exceptions from the updated _get_fresh_token() implementation.
| if not token: | |
| cc.step("❌ Unable to get token. Retrying in 30 seconds...") | |
| self._wait_with_shutdown_check(SSH_RETRY_INTERVAL) | |
| self._interruptible_sleep(SSH_RETRY_INTERVAL) | |
| try: | |
| token = self._get_fresh_token() | |
| except Exception as e: | |
| cc.step(f"❌ Unable to get token: {e}. Retrying in 30 seconds...") | |
| self._interruptible_sleep(SSH_RETRY_INTERVAL) | |
| continue |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
c9a1a5d to
784cd7a
Compare
| response = requests.get( | ||
| f"https://{endpoint_url}", | ||
| headers=headers, | ||
| timeout=ENDPOINT_REQUEST_TIMEOUT_SECONDS, | ||
| ) |
There was a problem hiding this comment.
Security Concern: URL Construction
The current URL construction in validate_endpoint_ready() may be vulnerable to protocol downgrade attacks. When prepending https:// to endpoint_url without validating its content, there's a risk of creating malformed URLs if endpoint_url already contains a protocol scheme.
For example, if endpoint_url contains http://example.com, the resulting URL would be https://http://example.com, which could lead to unexpected behavior or security issues.
Consider either:
- Validating that
endpoint_urldoesn't already contain a protocol scheme - Using a URL parsing library to properly construct the URL with HTTPS
# Example fix using validation
if "://" in endpoint_url:
raise CliError(f"Endpoint URL should not contain protocol: {endpoint_url}")
response = requests.get(f"https://{endpoint_url}", ...)
# Or using URL parsing
from urllib.parse import urlparse, urlunparse
parsed = urlparse(endpoint_url)
if parsed.scheme:
# Strip existing scheme and ensure HTTPS
parts = list(parsed)
parts[0] = "https"
url = urlunparse(parts)
else:
url = f"https://{endpoint_url}"
response = requests.get(url, ...)| response = requests.get( | |
| f"https://{endpoint_url}", | |
| headers=headers, | |
| timeout=ENDPOINT_REQUEST_TIMEOUT_SECONDS, | |
| ) | |
| if "://" in endpoint_url: | |
| raise CliError(f"Endpoint URL should not contain protocol: {endpoint_url}") | |
| response = requests.get( | |
| f"https://{endpoint_url}", | |
| headers=headers, | |
| timeout=ENDPOINT_REQUEST_TIMEOUT_SECONDS, | |
| ) |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
| # If result is None, service needs recreation - continue to creation | ||
| else: | ||
| log.debug("Service %s does not exist, creating...", service_name) | ||
| cc.step(f"Service {service_name} does not exist, creating...") |
There was a problem hiding this comment.
Inconsistent logging level for service creation message
There's an inconsistency in how service creation is logged. When a service doesn't exist:
cc.step(f"Service {service_name} does not exist, creating...")This uses cc.step() which displays to users, while the similar check above uses:
log.debug("Service %s is pending, waiting for it to be ready...", service_name)For consistency, either:
- Use
log.debug()for both messages to keep implementation details hidden from users - Use
cc.step()for both to provide consistent user-facing progress updates
This would make the logging behavior more predictable and maintain a consistent level of verbosity in the user interface.
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
6d2eb90 to
77accb6
Compare
582d471 to
b9a97f7
Compare
77accb6 to
85af513
Compare
b9a97f7 to
01d14d8
Compare
85af513 to
583ba5c
Compare

Pre-review checklist
Changes description
This PR enhances the
snow remotecommand with several new features and improvements:IDE Integration:
--codeflag to launch VS Code connected to the remote service--cursorflag to launch Cursor connected to the remote serviceImproved Stage Mounting:
Error Handling:
ValueErrorwith more specificCliErrorfor better error messagesUser Experience:
The changes maintain backward compatibility while adding new functionality for IDE integration and persistent storage.