cloudx-proxy 0.2.0__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cloudx_proxy/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.2.0'
16
- __version_tuple__ = version_tuple = (0, 2, 0)
15
+ __version__ = version = '0.3.1'
16
+ __version_tuple__ = version_tuple = (0, 3, 1)
cloudx_proxy/cli.py CHANGED
@@ -16,9 +16,9 @@ def cli():
16
16
  @click.argument('port', type=int, default=22)
17
17
  @click.option('--profile', default='vscode', help='AWS profile to use (default: vscode)')
18
18
  @click.option('--region', help='AWS region (default: from profile, or eu-west-1 if not set)')
19
- @click.option('--key-path', help='Path to SSH public key (default: ~/.ssh/vscode/vscode.pub)')
19
+ @click.option('--ssh-key', default='vscode', help='SSH key name to use (default: vscode)')
20
20
  @click.option('--aws-env', help='AWS environment directory (default: ~/.aws, use name of directory in ~/.aws/aws-envs/)')
21
- def connect(instance_id: str, port: int, profile: str, region: str, key_path: str, aws_env: str):
21
+ def connect(instance_id: str, port: int, profile: str, region: str, ssh_key: str, aws_env: str):
22
22
  """Connect to an EC2 instance via SSM.
23
23
 
24
24
  INSTANCE_ID is the EC2 instance ID to connect to (e.g., i-0123456789abcdef0)
@@ -34,7 +34,7 @@ def connect(instance_id: str, port: int, profile: str, region: str, key_path: st
34
34
  port=port,
35
35
  profile=profile,
36
36
  region=region,
37
- public_key_path=key_path,
37
+ ssh_key=ssh_key,
38
38
  aws_env=aws_env
39
39
  )
40
40
 
@@ -65,6 +65,8 @@ def setup(profile: str, ssh_key: str, aws_env: str):
65
65
  try:
66
66
  setup = CloudXSetup(profile=profile, ssh_key=ssh_key, aws_env=aws_env)
67
67
 
68
+ print("\n\033[1;95m=== cloudx-proxy Setup ===\033[0m\n")
69
+
68
70
  # Set up AWS profile
69
71
  if not setup.setup_aws_profile():
70
72
  sys.exit(1)
@@ -73,10 +75,10 @@ def setup(profile: str, ssh_key: str, aws_env: str):
73
75
  if not setup.setup_ssh_key():
74
76
  sys.exit(1)
75
77
 
76
- # Get CloudX environment and instance details
77
- cloudx_env = click.prompt("Enter CloudX environment (e.g., dev, prod)", type=str)
78
- instance_id = click.prompt("Enter EC2 instance ID (e.g., i-0123456789abcdef0)", type=str)
79
- hostname = click.prompt("Enter hostname for the instance", type=str)
78
+ # Get environment and instance details
79
+ cloudx_env = setup.prompt("Enter environment", getattr(setup, 'default_env', None))
80
+ instance_id = setup.prompt("Enter EC2 instance ID (e.g., i-0123456789abcdef0)")
81
+ hostname = setup.prompt("Enter hostname for the instance")
80
82
 
81
83
  # Set up SSH config
82
84
  if not setup.setup_ssh_config(cloudx_env, instance_id, hostname):
@@ -86,7 +88,7 @@ def setup(profile: str, ssh_key: str, aws_env: str):
86
88
  setup.wait_for_setup_completion(instance_id)
87
89
 
88
90
  except Exception as e:
89
- print(f"Error: {str(e)}", file=sys.stderr)
91
+ print(f"\n\033[91mError: {str(e)}\033[0m", file=sys.stderr)
90
92
  sys.exit(1)
91
93
 
92
94
  if __name__ == '__main__':
cloudx_proxy/core.py CHANGED
@@ -7,7 +7,7 @@ from botocore.exceptions import ClientError
7
7
 
8
8
  class CloudXClient:
9
9
  def __init__(self, instance_id: str, port: int = 22, profile: str = "vscode",
10
- region: str = None, public_key_path: str = None, aws_env: str = None):
10
+ region: str = None, ssh_key: str = "vscode", aws_env: str = None):
11
11
  """Initialize CloudX client for SSH tunneling via AWS SSM.
12
12
 
13
13
  Args:
@@ -15,7 +15,7 @@ class CloudXClient:
15
15
  port: SSH port number (default: 22)
16
16
  profile: AWS profile to use (default: "vscode")
17
17
  region: AWS region (default: from profile)
18
- public_key_path: Path to SSH public key (default: ~/.ssh/vscode/vscode.pub)
18
+ ssh_key: SSH key name to use (default: "vscode")
19
19
  aws_env: AWS environment directory (default: None, uses ~/.aws)
20
20
  """
21
21
  self.instance_id = instance_id
@@ -39,10 +39,9 @@ class CloudXClient:
39
39
  self.ec2 = self.session.client('ec2')
40
40
  self.ec2_connect = self.session.client('ec2-instance-connect')
41
41
 
42
- # Default public key path if not provided
43
- if not public_key_path:
44
- public_key_path = os.path.expanduser("~/.ssh/vscode/vscode.pub")
45
- self.public_key_path = Path(public_key_path)
42
+ # Set up SSH key path
43
+ self.ssh_dir = os.path.expanduser("~/.ssh/vscode")
44
+ self.ssh_key = os.path.join(self.ssh_dir, f"{ssh_key}.pub")
46
45
 
47
46
  def log(self, message: str) -> None:
48
47
  """Log message to stderr to avoid interfering with SSH connection."""
@@ -88,7 +87,7 @@ class CloudXClient:
88
87
  def push_ssh_key(self) -> bool:
89
88
  """Push SSH public key to instance via EC2 Instance Connect."""
90
89
  try:
91
- with open(self.public_key_path) as f:
90
+ with open(self.ssh_key) as f:
92
91
  public_key = f.read()
93
92
 
94
93
  self.ec2_connect.send_ssh_public_key(
cloudx_proxy/setup.py CHANGED
@@ -9,7 +9,7 @@ from botocore.exceptions import ClientError
9
9
 
10
10
  class CloudXSetup:
11
11
  def __init__(self, profile: str = "vscode", ssh_key: str = "vscode", aws_env: str = None):
12
- """Initialize CloudX setup.
12
+ """Initialize cloudx-proxy setup.
13
13
 
14
14
  Args:
15
15
  profile: AWS profile name (default: "vscode")
@@ -24,6 +24,15 @@ class CloudXSetup:
24
24
  self.ssh_config_file = self.ssh_dir / "config"
25
25
  self.ssh_key_file = self.ssh_dir / f"{ssh_key}"
26
26
  self.using_1password = False
27
+ self.default_env = None
28
+
29
+ def print_header(self, text: str) -> None:
30
+ """Print a section header.
31
+
32
+ Args:
33
+ text: The header text
34
+ """
35
+ print(f"\n\n\033[1;94m=== {text} ===\033[0m")
27
36
 
28
37
  def print_status(self, message: str, status: bool = None, indent: int = 0) -> None:
29
38
  """Print a status message with optional checkmark/cross.
@@ -42,6 +51,23 @@ class CloudXSetup:
42
51
  else:
43
52
  print(f"{prefix}○ {message}")
44
53
 
54
+ def prompt(self, message: str, default: str = None) -> str:
55
+ """Display a colored prompt for user input.
56
+
57
+ Args:
58
+ message: The prompt message
59
+ default: Default value (shown in brackets)
60
+
61
+ Returns:
62
+ str: User's input or default value
63
+ """
64
+ if default:
65
+ prompt_text = f"\033[93m{message} [{default}]: \033[0m"
66
+ else:
67
+ prompt_text = f"\033[93m{message}: \033[0m"
68
+ response = input(prompt_text)
69
+ return response if response else default
70
+
45
71
  def setup_aws_profile(self) -> bool:
46
72
  """Set up AWS profile using aws configure command.
47
73
 
@@ -57,54 +83,45 @@ class CloudXSetup:
57
83
  os.environ["AWS_CONFIG_FILE"] = os.path.join(aws_env_dir, "config")
58
84
  os.environ["AWS_SHARED_CREDENTIALS_FILE"] = os.path.join(aws_env_dir, "credentials")
59
85
 
60
- # Check if profile exists
61
- session = boto3.Session(profile_name=self.profile)
86
+ # Try to create session with profile
87
+ try:
88
+ session = boto3.Session(profile_name=self.profile)
89
+ except:
90
+ # Profile doesn't exist, create it
91
+ self.print_status(f"AWS profile '{self.profile}' not found", False, 2)
92
+ self.print_status("Setting up AWS profile...", None, 2)
93
+ print("\033[96mPlease enter your AWS credentials:\033[0m")
94
+
95
+ # Use aws configure command
96
+ subprocess.run([
97
+ 'aws', 'configure',
98
+ '--profile', self.profile
99
+ ], check=True)
100
+
101
+ # Create new session with configured profile
102
+ session = boto3.Session(profile_name=self.profile)
103
+
104
+ # Verify the profile works
62
105
  try:
63
106
  identity = session.client('sts').get_caller_identity()
64
107
  user_arn = identity['Arn']
65
108
 
66
- if any(part.startswith('cloudX-') for part in user_arn.split('/')):
109
+ # Extract environment from IAM user name
110
+ user_parts = [part for part in user_arn.split('/') if part.startswith('cloudX-')]
111
+ if user_parts:
112
+ self.default_env = user_parts[0].split('-')[1] # Extract env from cloudX-{env}-{user}
67
113
  self.print_status(f"AWS profile '{self.profile}' exists and matches cloudX format", True, 2)
114
+ return True
68
115
  else:
69
- self.print_status(f"AWS profile '{self.profile}' exists but doesn't match cloudX-{{env}}-{{user}} format", False, 2)
70
- return True
116
+ self.print_status(f"AWS profile exists but doesn't match cloudX-{{env}}-{{user}} format", False, 2)
117
+ self.print_status("Please ensure your IAM user follows the format: cloudX-{env}-{username}", None, 2)
118
+ return False
71
119
  except ClientError:
72
- self.print_status(f"AWS profile '{self.profile}' not found or invalid", False, 2)
73
-
74
- # Ask user if they want to set up the profile
75
- setup_profile = input(f"Would you like to set up AWS profile '{self.profile}'? (Y/n): ").lower() != 'n'
76
- if not setup_profile:
77
- self.print_status("Skipping AWS profile setup", None, 2)
78
- return True
79
-
80
- # Profile doesn't exist or is invalid, set it up
81
- self.print_status("Setting up AWS profile...", None, 2)
82
- print("Please enter your AWS credentials:")
83
-
84
- # Use aws configure command
85
- subprocess.run([
86
- 'aws', 'configure',
87
- '--profile', self.profile
88
- ], check=True)
89
-
90
- # Verify the profile works
91
- session = boto3.Session(profile_name=self.profile)
92
- identity = session.client('sts').get_caller_identity()
93
- user_arn = identity['Arn']
94
-
95
- if any(part.startswith('cloudX-') for part in user_arn.split('/')):
96
- self.print_status("AWS profile setup complete and matches cloudX format", True, 2)
97
- else:
98
- self.print_status("AWS profile setup complete but doesn't match cloudX-{env}-{user} format", False, 2)
99
-
100
- return True
120
+ self.print_status("Invalid AWS credentials", False, 2)
121
+ return False
101
122
 
102
123
  except Exception as e:
103
- self.print_status(f"Error: {str(e)}", False, 2)
104
- continue_setup = input("Would you like to continue anyway? (Y/n): ").lower() != 'n'
105
- if continue_setup:
106
- self.print_status("Continuing setup despite AWS profile issues", None, 2)
107
- return True
124
+ self.print_status(f"\033[1;91mError:\033[0m {str(e)}", False, 2)
108
125
  return False
109
126
 
110
127
  def setup_ssh_key(self) -> bool:
@@ -113,7 +130,8 @@ class CloudXSetup:
113
130
  Returns:
114
131
  bool: True if key was set up successfully
115
132
  """
116
- self.print_status("Checking SSH key configuration...")
133
+ self.print_header("SSH Key Configuration")
134
+ self.print_status(f"Checking SSH key '{self.ssh_key}' configuration...")
117
135
 
118
136
  try:
119
137
  # Create .ssh/vscode directory if it doesn't exist
@@ -124,11 +142,11 @@ class CloudXSetup:
124
142
 
125
143
  if key_exists:
126
144
  self.print_status(f"SSH key '{self.ssh_key}' exists", True, 2)
127
- self.using_1password = input("Would you like to use 1Password SSH agent? (y/N): ").lower() == 'y'
145
+ self.using_1password = self.prompt("Would you like to use 1Password SSH agent?", "N").lower() == 'y'
128
146
  if self.using_1password:
129
147
  self.print_status("Using 1Password SSH agent", True, 2)
130
148
  else:
131
- store_in_1password = input("Would you like to store the private key in 1Password? (y/N): ").lower() == 'y'
149
+ store_in_1password = self.prompt("Would you like to store the private key in 1Password?", "N").lower() == 'y'
132
150
  if store_in_1password:
133
151
  if self._store_key_in_1password():
134
152
  self.print_status("Private key stored in 1Password", True, 2)
@@ -144,11 +162,11 @@ class CloudXSetup:
144
162
  ], check=True)
145
163
  self.print_status("SSH key generated", True, 2)
146
164
 
147
- self.using_1password = input("Would you like to use 1Password SSH agent? (y/N): ").lower() == 'y'
165
+ self.using_1password = self.prompt("Would you like to use 1Password SSH agent?", "N").lower() == 'y'
148
166
  if self.using_1password:
149
167
  self.print_status("Using 1Password SSH agent", True, 2)
150
168
  else:
151
- store_in_1password = input("Would you like to store the private key in 1Password? (y/N): ").lower() == 'y'
169
+ store_in_1password = self.prompt("Would you like to store the private key in 1Password?", "N").lower() == 'y'
152
170
  if store_in_1password:
153
171
  if self._store_key_in_1password():
154
172
  self.print_status("Private key stored in 1Password", True, 2)
@@ -159,7 +177,7 @@ class CloudXSetup:
159
177
 
160
178
  except Exception as e:
161
179
  self.print_status(f"Error: {str(e)}", False, 2)
162
- continue_setup = input("Would you like to continue anyway? (Y/n): ").lower() != 'n'
180
+ continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
163
181
  if continue_setup:
164
182
  self.print_status("Continuing setup despite SSH key issues", None, 2)
165
183
  return True
@@ -177,13 +195,65 @@ class CloudXSetup:
177
195
  subprocess.run([
178
196
  'op', 'document', 'create',
179
197
  str(self.ssh_key_file),
180
- '--title', f'CloudX SSH Key - {self.ssh_key}'
198
+ '--title', f'cloudx-proxy SSH Key - {self.ssh_key}'
181
199
  ], check=True)
182
200
  return True
183
201
  except subprocess.CalledProcessError:
184
202
  print("Error: 1Password CLI not installed or not signed in.")
185
203
  return False
186
204
 
205
+ def _add_host_entry(self, cloudx_env: str, instance_id: str, hostname: str, current_config: str) -> bool:
206
+ """Add settings to a specific host entry.
207
+
208
+ Args:
209
+ cloudx_env: CloudX environment
210
+ instance_id: EC2 instance ID
211
+ hostname: Hostname for the instance
212
+ current_config: Current SSH config content
213
+
214
+ Returns:
215
+ bool: True if settings were added successfully
216
+ """
217
+ try:
218
+ # Build host entry with all settings
219
+ proxy_command = "uvx cloudx-proxy connect %h %p"
220
+ if self.profile != "vscode":
221
+ proxy_command += f" --profile {self.profile}"
222
+ if self.aws_env:
223
+ proxy_command += f" --aws-env {self.aws_env}"
224
+ if self.ssh_key != "vscode":
225
+ proxy_command += f" --ssh-key {self.ssh_key}"
226
+
227
+ host_entry = f"""
228
+ Host cloudx-{cloudx_env}-{hostname}
229
+ HostName {instance_id}
230
+ User ec2-user
231
+ """
232
+ if self.using_1password:
233
+ host_entry += f""" IdentityAgent ~/.1password/agent.sock
234
+ IdentityFile {self.ssh_key_file}.pub
235
+ IdentitiesOnly yes
236
+ """
237
+ else:
238
+ host_entry += f""" IdentityFile {self.ssh_key_file}
239
+ """
240
+ host_entry += f""" ProxyCommand {proxy_command}
241
+ """
242
+
243
+ # Append host entry
244
+ with open(self.ssh_config_file, 'a') as f:
245
+ f.write(host_entry)
246
+ self.print_status("Host entry added with settings", True, 2)
247
+ return True
248
+
249
+ except Exception as e:
250
+ self.print_status(f"\033[1;91mError:\033[0m {str(e)}", False, 2)
251
+ continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
252
+ if continue_setup:
253
+ self.print_status("Continuing setup despite SSH config issues", None, 2)
254
+ return True
255
+ return False
256
+
187
257
  def setup_ssh_config(self, cloudx_env: str, instance_id: str, hostname: str) -> bool:
188
258
  """Set up SSH config for the instance.
189
259
 
@@ -221,52 +291,78 @@ class CloudXSetup:
221
291
  Returns:
222
292
  bool: True if config was set up successfully
223
293
  """
294
+ self.print_header("SSH Configuration")
224
295
  self.print_status("Setting up SSH configuration...")
225
296
 
226
297
  try:
227
- # Check if we need to create base config
228
- need_base_config = True
298
+ # Check existing configuration
229
299
  if self.ssh_config_file.exists():
230
300
  current_config = self.ssh_config_file.read_text()
231
301
  # Check if configuration for this environment already exists
232
302
  if f"Host cloudx-{cloudx_env}-*" in current_config:
233
- need_base_config = False
234
303
  self.print_status(f"Found existing config for cloudx-{cloudx_env}-*", True, 2)
235
-
236
- if need_base_config:
237
- self.print_status(f"Creating new config for cloudx-{cloudx_env}-*", None, 2)
238
- # Build ProxyCommand with all necessary parameters
239
- proxy_command = f"uvx cloudx-proxy connect %h %p --profile {self.profile}"
240
- if self.aws_env:
241
- proxy_command += f" --aws-env {self.aws_env}"
242
- if self.ssh_key != "vscode":
243
- proxy_command += f" --key-path {self.ssh_key_file}.pub"
304
+ choice = self.prompt(
305
+ "Would you like to (1) override the existing config or "
306
+ "(2) add settings to the specific host entry?",
307
+ "1"
308
+ )
309
+ if choice == "2":
310
+ # Add settings to specific host entry
311
+ self.print_status("Adding settings to specific host entry", None, 2)
312
+ return self._add_host_entry(cloudx_env, instance_id, hostname, current_config)
313
+ else:
314
+ # Remove existing config for this environment
315
+ self.print_status("Removing existing configuration", None, 2)
316
+ lines = current_config.splitlines()
317
+ new_lines = []
318
+ skip = False
319
+ for line in lines:
320
+ if line.strip() == f"Host cloudx-{cloudx_env}-*":
321
+ skip = True
322
+ elif skip and line.startswith("Host "):
323
+ skip = False
324
+ if not skip:
325
+ new_lines.append(line)
326
+ current_config = "\n".join(new_lines)
327
+ with open(self.ssh_config_file, 'w') as f:
328
+ f.write(current_config)
329
+
330
+ # Create base config
331
+ self.print_status(f"Creating new config for cloudx-{cloudx_env}-*", None, 2)
332
+ # Build ProxyCommand with only non-default parameters
333
+ proxy_command = "uvx cloudx-proxy connect %h %p"
334
+ if self.profile != "vscode":
335
+ proxy_command += f" --profile {self.profile}"
336
+ if self.aws_env:
337
+ proxy_command += f" --aws-env {self.aws_env}"
338
+ if self.ssh_key != "vscode":
339
+ proxy_command += f" --ssh-key {self.ssh_key}"
244
340
 
245
- # Build base configuration
246
- base_config = f"""# CloudX SSH Configuration
341
+ # Build base configuration
342
+ base_config = f"""# cloudx-proxy SSH Configuration
247
343
  Host cloudx-{cloudx_env}-*
248
344
  User ec2-user
249
345
  """
250
- # Add 1Password or standard key configuration
251
- if self.using_1password:
252
- base_config += f""" IdentityAgent ~/.1password/agent.sock
346
+ # Add 1Password or standard key configuration
347
+ if self.using_1password:
348
+ base_config += f""" IdentityAgent ~/.1password/agent.sock
253
349
  IdentityFile {self.ssh_key_file}.pub
254
350
  IdentitiesOnly yes
255
351
  """
256
- else:
257
- base_config += f""" IdentityFile {self.ssh_key_file}
352
+ else:
353
+ base_config += f""" IdentityFile {self.ssh_key_file}
258
354
  """
259
- # Add ProxyCommand
260
- base_config += f""" ProxyCommand {proxy_command}
355
+ # Add ProxyCommand
356
+ base_config += f""" ProxyCommand {proxy_command}
261
357
  """
262
-
263
- # If file exists, append the new config, otherwise create it
264
- if self.ssh_config_file.exists():
265
- with open(self.ssh_config_file, 'a') as f:
266
- f.write("\n" + base_config)
267
- else:
268
- self.ssh_config_file.write_text(base_config)
269
- self.print_status("Base configuration created", True, 2)
358
+
359
+ # If file exists, append the new config, otherwise create it
360
+ if self.ssh_config_file.exists():
361
+ with open(self.ssh_config_file, 'a') as f:
362
+ f.write("\n" + base_config)
363
+ else:
364
+ self.ssh_config_file.write_text(base_config)
365
+ self.print_status("Base configuration created", True, 2)
270
366
 
271
367
  # Add specific host entry
272
368
  self.print_status(f"Adding host entry for cloudx-{cloudx_env}-{hostname}", None, 2)
@@ -296,14 +392,14 @@ Host cloudx-{cloudx_env}-{hostname}
296
392
 
297
393
  self.print_status("\nSSH configuration summary:", None)
298
394
  self.print_status(f"Main config: {main_config}", None, 2)
299
- self.print_status(f"CloudX config: {self.ssh_config_file}", None, 2)
395
+ self.print_status(f"cloudx-proxy config: {self.ssh_config_file}", None, 2)
300
396
  self.print_status(f"Connect using: ssh cloudx-{cloudx_env}-{hostname}", None, 2)
301
397
 
302
398
  return True
303
399
 
304
400
  except Exception as e:
305
- self.print_status(f"Error: {str(e)}", False, 2)
306
- continue_setup = input("Would you like to continue anyway? (Y/n): ").lower() != 'n'
401
+ self.print_status(f"\033[1;91mError:\033[0m {str(e)}", False, 2)
402
+ continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
307
403
  if continue_setup:
308
404
  self.print_status("Continuing setup despite SSH config issues", None, 2)
309
405
  return True
@@ -323,15 +419,20 @@ Host cloudx-{cloudx_env}-{hostname}
323
419
  ssm = session.client('ssm')
324
420
 
325
421
  # Check if instance is online in SSM
422
+ self.print_status("Checking instance status in SSM...", None, 4)
326
423
  response = ssm.describe_instance_information(
327
424
  Filters=[{'Key': 'InstanceIds', 'Values': [instance_id]}]
328
425
  )
329
426
  is_running = bool(response['InstanceInformationList'])
330
427
 
331
428
  if not is_running:
429
+ self.print_status("Instance is not accessible via SSM", False, 4)
332
430
  return False, False
333
431
 
432
+ self.print_status("Instance is accessible via SSM", True, 4)
433
+
334
434
  # Check setup status using SSM command
435
+ self.print_status("Checking setup status...", None, 4)
335
436
  response = ssm.send_command(
336
437
  InstanceIds=[instance_id],
337
438
  DocumentName='AWS-RunShellScript',
@@ -358,10 +459,16 @@ Host cloudx-{cloudx_env}-{hostname}
358
459
 
359
460
  is_setup_complete = result['Status'] == 'Success' and result['StandardOutputContent'].strip() == 'DONE'
360
461
 
462
+ if is_setup_complete:
463
+ self.print_status("Setup is complete", True, 4)
464
+ else:
465
+ status = result['StandardOutputContent'].strip()
466
+ self.print_status(f"Setup status: {status}", None, 4)
467
+
361
468
  return True, is_setup_complete
362
469
 
363
470
  except Exception as e:
364
- print(f"Error checking instance setup: {e}")
471
+ self.print_status(f"\033[1;91mError:\033[0m {str(e)}", False, 4)
365
472
  return False, False
366
473
 
367
474
  def wait_for_setup_completion(self, instance_id: str) -> bool:
@@ -373,13 +480,14 @@ Host cloudx-{cloudx_env}-{hostname}
373
480
  Returns:
374
481
  bool: True if setup completed successfully
375
482
  """
483
+ self.print_header("Instance Setup Check")
376
484
  self.print_status(f"Checking instance {instance_id} setup status...")
377
485
 
378
486
  is_running, is_complete = self.check_instance_setup(instance_id)
379
487
 
380
488
  if not is_running:
381
489
  self.print_status("Instance is not running or not accessible via SSM", False, 2)
382
- continue_setup = input("Would you like to continue anyway? (Y/n): ").lower() != 'n'
490
+ continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
383
491
  if continue_setup:
384
492
  self.print_status("Continuing setup despite instance access issues", None, 2)
385
493
  return True
@@ -389,7 +497,7 @@ Host cloudx-{cloudx_env}-{hostname}
389
497
  self.print_status("Instance setup is complete", True, 2)
390
498
  return True
391
499
 
392
- wait = input("Instance setup is not complete. Would you like to wait? (Y/n): ").lower() != 'n'
500
+ wait = self.prompt("Instance setup is not complete. Would you like to wait?", "Y").lower() != 'n'
393
501
  if not wait:
394
502
  self.print_status("Skipping instance setup check", None, 2)
395
503
  return True
@@ -401,7 +509,7 @@ Host cloudx-{cloudx_env}-{hostname}
401
509
 
402
510
  if not is_running:
403
511
  self.print_status("Instance is no longer running or accessible", False, 2)
404
- continue_setup = input("Would you like to continue anyway? (Y/n): ").lower() != 'n'
512
+ continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
405
513
  if continue_setup:
406
514
  self.print_status("Continuing setup despite instance issues", None, 2)
407
515
  return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cloudx-proxy
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: SSH proxy command to connect VSCode with Cloud9/CloudX instance using AWS Systems Manager
5
5
  Author-email: easytocloud <info@easytocloud.com>
6
6
  License: MIT License
@@ -0,0 +1,11 @@
1
+ cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
2
+ cloudx_proxy/_version.py,sha256=HzPz9rq3s1AiZXregKlqKaJJ2wGMtvH_a3V9la9CnpM,411
3
+ cloudx_proxy/cli.py,sha256=Ph-m8lDsdU2zZab9Y6YgBBzd_UDouBnfNrYFFx0bI_E,3426
4
+ cloudx_proxy/core.py,sha256=WjKoqMmmnt6e_4JMeq4gTka75JAvQcMUs9r9XUBLmFE,7289
5
+ cloudx_proxy/setup.py,sha256=Y8YYMJ47fb57FAr6llQaFGuVOQ-fstYEg_Pdv5uCd-A,22486
6
+ cloudx_proxy-0.3.1.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
7
+ cloudx_proxy-0.3.1.dist-info/METADATA,sha256=9IDcysHjzmeyFMs0ODmS6gfC98rXcCyj-Ug2RsJBxyI,10216
8
+ cloudx_proxy-0.3.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
9
+ cloudx_proxy-0.3.1.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
10
+ cloudx_proxy-0.3.1.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
11
+ cloudx_proxy-0.3.1.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
2
- cloudx_proxy/_version.py,sha256=H-qsvrxCpdhaQzyddR-yajEqI71hPxLa4KxzpP3uS1g,411
3
- cloudx_proxy/cli.py,sha256=zqOIynqNr76JE6VRF7ifn3Fr0Z5C6CjnAHQgupF30BU,3374
4
- cloudx_proxy/core.py,sha256=j6CUKdg2Ikcoi-05ceXMGA_c1aGWBhN9_JevbkLkaUY,7383
5
- cloudx_proxy/setup.py,sha256=rGpH02DxM-UOT0_fY47BLyOD6IobmGvs_jOCgtGENqc,18009
6
- cloudx_proxy-0.2.0.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
7
- cloudx_proxy-0.2.0.dist-info/METADATA,sha256=vXyuzU2tmrQBhTRjlBFXcRYqLrfDv0AAymMz5nAvZsU,10216
8
- cloudx_proxy-0.2.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
9
- cloudx_proxy-0.2.0.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
10
- cloudx_proxy-0.2.0.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
11
- cloudx_proxy-0.2.0.dist-info/RECORD,,