cloudx-proxy 0.3.6__py3-none-any.whl → 0.3.7__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.3.6'
16
- __version_tuple__ = version_tuple = (0, 3, 6)
15
+ __version__ = version = '0.3.7'
16
+ __version_tuple__ = version_tuple = (0, 3, 7)
cloudx_proxy/setup.py CHANGED
@@ -24,7 +24,6 @@ class CloudXSetup:
24
24
  self.ssh_dir = Path(self.home_dir) / ".ssh" / "vscode"
25
25
  self.ssh_config_file = self.ssh_dir / "config"
26
26
  self.ssh_key_file = self.ssh_dir / f"{ssh_key}"
27
- self.using_1password = False
28
27
  self.default_env = None
29
28
 
30
29
  def print_header(self, text: str) -> None:
@@ -143,17 +142,6 @@ class CloudXSetup:
143
142
 
144
143
  if key_exists:
145
144
  self.print_status(f"SSH key '{self.ssh_key}' exists", True, 2)
146
- if platform.system() != 'Windows':
147
- self.using_1password = self.prompt("Would you like to use 1Password SSH agent?", "N").lower() == 'y'
148
- if self.using_1password:
149
- self.print_status("Using 1Password SSH agent", True, 2)
150
- else:
151
- store_in_1password = self.prompt("Would you like to store the private key in 1Password?", "N").lower() == 'y'
152
- if store_in_1password:
153
- if self._store_key_in_1password():
154
- self.print_status("Private key stored in 1Password", True, 2)
155
- else:
156
- self.print_status("Failed to store private key in 1Password", False, 2)
157
145
  else:
158
146
  self.print_status(f"Generating new SSH key '{self.ssh_key}'...", None, 2)
159
147
  subprocess.run([
@@ -164,17 +152,6 @@ class CloudXSetup:
164
152
  ], check=True)
165
153
  self.print_status("SSH key generated", True, 2)
166
154
 
167
- if platform.system() != 'Windows':
168
- self.using_1password = self.prompt("Would you like to use 1Password SSH agent?", "N").lower() == 'y'
169
- if self.using_1password:
170
- self.print_status("Using 1Password SSH agent", True, 2)
171
- else:
172
- store_in_1password = self.prompt("Would you like to store the private key in 1Password?", "N").lower() == 'y'
173
- if store_in_1password:
174
- if self._store_key_in_1password():
175
- self.print_status("Private key stored in 1Password", True, 2)
176
- else:
177
- self.print_status("Failed to store private key in 1Password", False, 2)
178
155
 
179
156
  return True
180
157
 
@@ -186,44 +163,6 @@ class CloudXSetup:
186
163
  return True
187
164
  return False
188
165
 
189
- def _store_key_in_1password(self) -> bool:
190
- """Store SSH private key in 1Password.
191
-
192
- Returns:
193
- bool: True if key was stored successfully
194
- """
195
- try:
196
- # Check if 1Password CLI is available
197
- subprocess.run(['op', '--version'], check=True, capture_output=True)
198
-
199
- # Check if 1Password SSH agent is running
200
- agent_sock = Path.home() / ".1password" / "agent.sock"
201
- if platform.system() == 'Darwin':
202
- agent_sock = Path.home() / "Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
203
-
204
- if not agent_sock.exists():
205
- self.print_status("1Password SSH agent not running. Please enable it in 1Password settings.", False, 2)
206
- return False
207
-
208
- # Read private key content
209
- with open(self.ssh_key_file, 'r') as f:
210
- private_key = f.read()
211
-
212
- # Store private key in 1Password as document
213
- subprocess.run([
214
- 'op', 'document', 'create',
215
- '--title', f'cloudx-proxy SSH Key - {self.ssh_key}',
216
- ], input=private_key.encode(), check=True)
217
-
218
- # Remove private key file but keep public key
219
- os.remove(self.ssh_key_file)
220
- self.print_status("Private key stored in 1Password and removed from disk", True, 2)
221
- self.print_status("Please import the key into 1Password SSH agent through the desktop app", True, 2)
222
- return True
223
- except subprocess.CalledProcessError:
224
- print("Error: 1Password CLI not installed or not signed in.")
225
- return False
226
-
227
166
  def _add_host_entry(self, cloudx_env: str, instance_id: str, hostname: str, current_config: str) -> bool:
228
167
  """Add settings to a specific host entry.
229
168
 
@@ -251,18 +190,7 @@ Host cloudx-{cloudx_env}-{hostname}
251
190
  HostName {instance_id}
252
191
  User ec2-user
253
192
  """
254
- if self.using_1password and platform.system() != 'Windows':
255
- # Use platform-specific agent socket path
256
- if platform.system() == 'Darwin': # macOS
257
- agent_sock = "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
258
- else: # Linux
259
- agent_sock = "~/.1password/agent.sock"
260
- host_entry += f""" IdentityAgent {agent_sock}
261
- IdentityFile {self.ssh_key_file}.pub
262
- IdentitiesOnly yes
263
- """
264
- else:
265
- host_entry += f""" IdentityFile {self.ssh_key_file}
193
+ host_entry += f""" IdentityFile {self.ssh_key_file}
266
194
  IdentitiesOnly yes
267
195
  """
268
196
  host_entry += f""" ProxyCommand {proxy_command}
@@ -372,19 +300,7 @@ Host cloudx-{cloudx_env}-*
372
300
  User ec2-user
373
301
  """
374
302
  # Add key configuration
375
- if self.using_1password and platform.system() != 'Windows':
376
- # Use platform-specific agent socket path
377
- if platform.system() == 'Darwin': # macOS
378
- agent_sock = "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
379
- else: # Linux
380
- agent_sock = "~/.1password/agent.sock"
381
- base_config += f""" IdentityAgent {agent_sock}
382
- IdentityFile {self.ssh_key_file}.pub
383
- IdentitiesOnly yes
384
-
385
- """
386
- else:
387
- base_config += f""" IdentityFile {self.ssh_key_file}
303
+ base_config += f""" IdentityFile {self.ssh_key_file}
388
304
  IdentitiesOnly yes
389
305
 
390
306
  """
@@ -441,71 +357,93 @@ Host cloudx-{cloudx_env}-{hostname}
441
357
  return True
442
358
  return False
443
359
 
444
- def check_instance_setup(self, instance_id: str) -> Tuple[bool, bool]:
360
+ def check_instance_setup(self, instance_id: str) -> Tuple[bool, bool, bool]:
445
361
  """Check if instance setup is complete.
446
362
 
447
363
  Args:
448
364
  instance_id: EC2 instance ID
449
365
 
450
366
  Returns:
451
- Tuple[bool, bool]: (is_running, is_setup_complete)
367
+ Tuple[bool, bool, bool]: (ssm_accessible, is_running, is_setup_complete)
452
368
  """
453
369
  try:
454
370
  session = boto3.Session(profile_name=self.profile)
455
371
  ssm = session.client('ssm')
372
+ ec2 = session.client('ec2')
456
373
 
457
- # Check if instance is online in SSM
458
- self.print_status("Checking instance status in SSM...", None, 4)
459
- response = ssm.describe_instance_information(
460
- Filters=[{'Key': 'InstanceIds', 'Values': [instance_id]}]
461
- )
462
- is_running = bool(response['InstanceInformationList'])
463
-
464
- if not is_running:
465
- self.print_status("Instance is not accessible via SSM", False, 4)
466
- return False, False
467
-
468
- self.print_status("Instance is accessible via SSM", True, 4)
469
-
470
- # Check setup status using SSM command
471
- self.print_status("Checking setup status...", None, 4)
472
- response = ssm.send_command(
473
- InstanceIds=[instance_id],
474
- DocumentName='AWS-RunShellScript',
475
- Parameters={
476
- 'commands': [
477
- 'test -f /home/ec2-user/.install-done && echo "DONE" || '
478
- 'test -f /home/ec2-user/.install-running && echo "RUNNING" || '
479
- 'echo "NOT_STARTED"'
480
- ]
481
- }
482
- )
483
-
484
- command_id = response['Command']['CommandId']
374
+ # First check if instance exists and its power state
375
+ try:
376
+ response = ec2.describe_instances(InstanceIds=[instance_id])
377
+ if not response['Reservations']:
378
+ self.print_status("Instance not found", False, 4)
379
+ return False, False, False
380
+
381
+ instance = response['Reservations'][0]['Instances'][0]
382
+ state = instance['State']['Name']
383
+
384
+ if state != 'running':
385
+ self.print_status(f"Instance is {state}", False, 4)
386
+ return True, False, False
387
+ except Exception as e:
388
+ self.print_status(f"Error checking instance state: {e}", False, 4)
389
+ return False, False, False
485
390
 
486
- # Wait for command completion
487
- for _ in range(10): # 10 second timeout
488
- time.sleep(1)
489
- result = ssm.get_command_invocation(
490
- CommandId=command_id,
491
- InstanceId=instance_id
391
+ # Now check SSM connectivity
392
+ try:
393
+ self.print_status("Checking SSM connectivity...", None, 4)
394
+ response = ssm.describe_instance_information(
395
+ Filters=[{'Key': 'InstanceIds', 'Values': [instance_id]}]
492
396
  )
493
- if result['Status'] in ['Success', 'Failed']:
494
- break
495
-
496
- is_setup_complete = result['Status'] == 'Success' and result['StandardOutputContent'].strip() == 'DONE'
497
-
498
- if is_setup_complete:
499
- self.print_status("Setup is complete", True, 4)
500
- else:
501
- status = result['StandardOutputContent'].strip()
502
- self.print_status(f"Setup status: {status}", None, 4)
503
-
504
- return True, is_setup_complete
397
+ if not response['InstanceInformationList']:
398
+ self.print_status("Instance is running but not yet accessible via SSM", False, 4)
399
+ self.print_status("This is normal if the instance is still configuring", None, 4)
400
+ return True, True, False
401
+
402
+ self.print_status("SSM connection established", True, 4)
403
+
404
+ # Check setup status using SSM command
405
+ self.print_status("Checking setup status...", None, 4)
406
+ response = ssm.send_command(
407
+ InstanceIds=[instance_id],
408
+ DocumentName='AWS-RunShellScript',
409
+ Parameters={
410
+ 'commands': [
411
+ 'test -f /home/ec2-user/.install-done && echo "DONE" || '
412
+ 'test -f /home/ec2-user/.install-running && echo "RUNNING" || '
413
+ 'echo "NOT_STARTED"'
414
+ ]
415
+ }
416
+ )
417
+
418
+ command_id = response['Command']['CommandId']
419
+
420
+ # Wait for command completion
421
+ for _ in range(10): # 10 second timeout
422
+ time.sleep(1)
423
+ result = ssm.get_command_invocation(
424
+ CommandId=command_id,
425
+ InstanceId=instance_id
426
+ )
427
+ if result['Status'] in ['Success', 'Failed']:
428
+ break
429
+
430
+ is_setup_complete = result['Status'] == 'Success' and result['StandardOutputContent'].strip() == 'DONE'
431
+
432
+ if is_setup_complete:
433
+ self.print_status("Setup is complete", True, 4)
434
+ else:
435
+ status = result['StandardOutputContent'].strip()
436
+ self.print_status(f"Setup status: {status}", None, 4)
437
+
438
+ return True, True, is_setup_complete
439
+
440
+ except Exception as e:
441
+ self.print_status(f"Error checking SSM status: {e}", False, 4)
442
+ return False, True, False
505
443
 
506
444
  except Exception as e:
507
445
  self.print_status(f"\033[1;91mError:\033[0m {str(e)}", False, 4)
508
- return False, False
446
+ return False, False, False
509
447
 
510
448
  def wait_for_setup_completion(self, instance_id: str) -> bool:
511
449
  """Wait for instance setup to complete.
@@ -514,25 +452,66 @@ Host cloudx-{cloudx_env}-{hostname}
514
452
  instance_id: EC2 instance ID
515
453
 
516
454
  Returns:
517
- bool: True if setup completed successfully
455
+ bool: True if setup completed successfully or user chose to continue
518
456
  """
519
457
  self.print_header("Instance Setup Check")
520
- self.print_status(f"Checking instance {instance_id} setup status...")
458
+ self.print_status(f"Checking instance {instance_id} status...")
521
459
 
522
- is_running, is_complete = self.check_instance_setup(instance_id)
460
+ ssm_accessible, is_running, is_complete = self.check_instance_setup(instance_id)
523
461
 
524
- if not is_running:
525
- self.print_status("Instance is not running or not accessible via SSM", False, 2)
462
+ if not ssm_accessible and not is_running:
526
463
  continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
527
464
  if continue_setup:
528
465
  self.print_status("Continuing setup despite instance access issues", None, 2)
529
466
  return True
530
467
  return False
531
468
 
469
+ if not is_running:
470
+ start_instance = self.prompt("Would you like to start the instance?", "Y").lower() != 'n'
471
+ if not start_instance:
472
+ return False
473
+
474
+ try:
475
+ session = boto3.Session(profile_name=self.profile)
476
+ ec2 = session.client('ec2')
477
+ ec2.start_instances(InstanceIds=[instance_id])
478
+ self.print_status("Instance start requested. This may take a few minutes...", None, 2)
479
+
480
+ # Wait for instance to start
481
+ for _ in range(30): # 5 minute timeout
482
+ time.sleep(10)
483
+ ssm_accessible, is_running, is_complete = self.check_instance_setup(instance_id)
484
+ if is_running:
485
+ break
486
+
487
+ if not is_running:
488
+ self.print_status("Timeout waiting for instance to start", False, 2)
489
+ return False
490
+ except Exception as e:
491
+ self.print_status(f"Error starting instance: {e}", False, 2)
492
+ return False
493
+
532
494
  if is_complete:
533
495
  self.print_status("Instance setup is complete", True, 2)
534
496
  return True
535
497
 
498
+ if not ssm_accessible:
499
+ self.print_status("Waiting for SSM access...", None, 2)
500
+ # Wait for SSM access
501
+ for _ in range(30): # 5 minute timeout
502
+ time.sleep(10)
503
+ ssm_accessible, is_running, is_complete = self.check_instance_setup(instance_id)
504
+ if ssm_accessible or not is_running:
505
+ break
506
+
507
+ if not ssm_accessible:
508
+ self.print_status("Timeout waiting for SSM access", False, 2)
509
+ continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
510
+ if continue_setup:
511
+ self.print_status("Continuing setup despite SSM access issues", None, 2)
512
+ return True
513
+ return False
514
+
536
515
  wait = self.prompt("Instance setup is not complete. Would you like to wait?", "Y").lower() != 'n'
537
516
  if not wait:
538
517
  self.print_status("Skipping instance setup check", None, 2)
@@ -541,16 +520,24 @@ Host cloudx-{cloudx_env}-{hostname}
541
520
  self.print_status("Waiting for setup to complete...", None, 2)
542
521
  dots = 0
543
522
  while True:
544
- is_running, is_complete = self.check_instance_setup(instance_id)
523
+ ssm_accessible, is_running, is_complete = self.check_instance_setup(instance_id)
545
524
 
546
525
  if not is_running:
547
- self.print_status("Instance is no longer running or accessible", False, 2)
526
+ self.print_status("Instance is no longer running", False, 2)
548
527
  continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
549
528
  if continue_setup:
550
529
  self.print_status("Continuing setup despite instance issues", None, 2)
551
530
  return True
552
531
  return False
553
532
 
533
+ if not ssm_accessible:
534
+ self.print_status("Lost SSM access to instance", False, 2)
535
+ continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
536
+ if continue_setup:
537
+ self.print_status("Continuing setup despite SSM access issues", None, 2)
538
+ return True
539
+ return False
540
+
554
541
  if is_complete:
555
542
  self.print_status("Instance setup completed successfully", True, 2)
556
543
  return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cloudx-proxy
3
- Version: 0.3.6
3
+ Version: 0.3.7
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=vVN20516E2VTC9JNgtvqrQNlj5XptaB_a5z2XL8NFxg,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=V05FxFK0iuExGctDHNKg72O_aIi6fRvZrazTv3vp72U,23609
6
+ cloudx_proxy-0.3.7.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
7
+ cloudx_proxy-0.3.7.dist-info/METADATA,sha256=2TRyqsPNefMSRl552_j5r-HANYKak9VhesCigxMpxyw,14037
8
+ cloudx_proxy-0.3.7.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
9
+ cloudx_proxy-0.3.7.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
10
+ cloudx_proxy-0.3.7.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
11
+ cloudx_proxy-0.3.7.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
2
- cloudx_proxy/_version.py,sha256=IKAQ4gPrCQ2FWMXOFRqouULC2EQI1zCb4iXHsnfbmTQ,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=3Kz-VWid0EL4rIEyf30UuLEe3Rvo1Mn8rKIa5WnysFE,24378
6
- cloudx_proxy-0.3.6.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
7
- cloudx_proxy-0.3.6.dist-info/METADATA,sha256=KwikAp1MVPQDaSABlt8esDM9448EIRgMitb394SH4JM,14037
8
- cloudx_proxy-0.3.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
9
- cloudx_proxy-0.3.6.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
10
- cloudx_proxy-0.3.6.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
11
- cloudx_proxy-0.3.6.dist-info/RECORD,,