cloudx-proxy 0.3.6__tar.gz → 0.3.8__tar.gz

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.
Files changed (22) hide show
  1. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/CHANGELOG.md +14 -0
  2. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/PKG-INFO +1 -1
  3. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/cloudx_proxy/_version.py +2 -2
  4. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/cloudx_proxy/setup.py +130 -142
  5. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/cloudx_proxy.egg-info/PKG-INFO +1 -1
  6. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/.github/workflows/release.yml +0 -0
  7. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/.gitignore +0 -0
  8. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/.releaserc +0 -0
  9. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/CONTRIBUTING.md +0 -0
  10. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/LICENSE +0 -0
  11. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/README.md +0 -0
  12. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/cloudx_proxy/__init__.py +0 -0
  13. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/cloudx_proxy/cli.py +0 -0
  14. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/cloudx_proxy/core.py +0 -0
  15. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/cloudx_proxy.egg-info/SOURCES.txt +0 -0
  16. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/cloudx_proxy.egg-info/dependency_links.txt +0 -0
  17. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/cloudx_proxy.egg-info/entry_points.txt +0 -0
  18. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/cloudx_proxy.egg-info/requires.txt +0 -0
  19. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/cloudx_proxy.egg-info/top_level.txt +0 -0
  20. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/package.json +0 -0
  21. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/pyproject.toml +0 -0
  22. {cloudx_proxy-0.3.6 → cloudx_proxy-0.3.8}/setup.cfg +0 -0
@@ -1,3 +1,17 @@
1
+ ## [0.3.8](https://github.com/easytocloud/cloudX-proxy/compare/v0.3.7...v0.3.8) (2025-02-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * profile use for ec2 operations ([3b0c23f](https://github.com/easytocloud/cloudX-proxy/commit/3b0c23f6e6fd2b782ed3e17a8606133435d8f676))
7
+
8
+ ## [0.3.7](https://github.com/easytocloud/cloudX-proxy/compare/v0.3.6...v0.3.7) (2025-02-11)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * removed 1Password support and improved instance monitoring ([c01009a](https://github.com/easytocloud/cloudX-proxy/commit/c01009afb6d90f43768dece909351a8c3ca597ce))
14
+
1
15
  ## [0.3.6](https://github.com/easytocloud/cloudX-proxy/compare/v0.3.5...v0.3.6) (2025-02-11)
2
16
 
3
17
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cloudx-proxy
3
- Version: 0.3.6
3
+ Version: 0.3.8
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
@@ -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.8'
16
+ __version_tuple__ = version_tuple = (0, 3, 8)
@@ -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,94 @@ 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
+ # Use the provided profile for EC2 operations
377
+ response = session.client('ec2').describe_instances(InstanceIds=[instance_id])
378
+ if not response['Reservations']:
379
+ self.print_status("Instance not found", False, 4)
380
+ return False, False, False
381
+
382
+ instance = response['Reservations'][0]['Instances'][0]
383
+ state = instance['State']['Name']
384
+
385
+ if state != 'running':
386
+ self.print_status(f"Instance is {state}", False, 4)
387
+ return True, False, False
388
+ except Exception as e:
389
+ self.print_status(f"Error checking instance state: {e}", False, 4)
390
+ return False, False, False
485
391
 
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
392
+ # Now check SSM connectivity
393
+ try:
394
+ self.print_status("Checking SSM connectivity...", None, 4)
395
+ response = ssm.describe_instance_information(
396
+ Filters=[{'Key': 'InstanceIds', 'Values': [instance_id]}]
492
397
  )
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
398
+ if not response['InstanceInformationList']:
399
+ self.print_status("Instance is running but not yet accessible via SSM", False, 4)
400
+ self.print_status("This is normal if the instance is still configuring", None, 4)
401
+ return True, True, False
402
+
403
+ self.print_status("SSM connection established", True, 4)
404
+
405
+ # Check setup status using SSM command
406
+ self.print_status("Checking setup status...", None, 4)
407
+ response = ssm.send_command(
408
+ InstanceIds=[instance_id],
409
+ DocumentName='AWS-RunShellScript',
410
+ Parameters={
411
+ 'commands': [
412
+ 'test -f /home/ec2-user/.install-done && echo "DONE" || '
413
+ 'test -f /home/ec2-user/.install-running && echo "RUNNING" || '
414
+ 'echo "NOT_STARTED"'
415
+ ]
416
+ }
417
+ )
418
+
419
+ command_id = response['Command']['CommandId']
420
+
421
+ # Wait for command completion
422
+ for _ in range(10): # 10 second timeout
423
+ time.sleep(1)
424
+ result = ssm.get_command_invocation(
425
+ CommandId=command_id,
426
+ InstanceId=instance_id
427
+ )
428
+ if result['Status'] in ['Success', 'Failed']:
429
+ break
430
+
431
+ is_setup_complete = result['Status'] == 'Success' and result['StandardOutputContent'].strip() == 'DONE'
432
+
433
+ if is_setup_complete:
434
+ self.print_status("Setup is complete", True, 4)
435
+ else:
436
+ status = result['StandardOutputContent'].strip()
437
+ self.print_status(f"Setup status: {status}", None, 4)
438
+
439
+ return True, True, is_setup_complete
440
+
441
+ except Exception as e:
442
+ self.print_status(f"Error checking SSM status: {e}", False, 4)
443
+ return False, True, False
505
444
 
506
445
  except Exception as e:
507
446
  self.print_status(f"\033[1;91mError:\033[0m {str(e)}", False, 4)
508
- return False, False
447
+ return False, False, False
509
448
 
510
449
  def wait_for_setup_completion(self, instance_id: str) -> bool:
511
450
  """Wait for instance setup to complete.
@@ -514,25 +453,66 @@ Host cloudx-{cloudx_env}-{hostname}
514
453
  instance_id: EC2 instance ID
515
454
 
516
455
  Returns:
517
- bool: True if setup completed successfully
456
+ bool: True if setup completed successfully or user chose to continue
518
457
  """
519
458
  self.print_header("Instance Setup Check")
520
- self.print_status(f"Checking instance {instance_id} setup status...")
459
+ self.print_status(f"Checking instance {instance_id} status...")
521
460
 
522
- is_running, is_complete = self.check_instance_setup(instance_id)
461
+ ssm_accessible, is_running, is_complete = self.check_instance_setup(instance_id)
523
462
 
524
- if not is_running:
525
- self.print_status("Instance is not running or not accessible via SSM", False, 2)
463
+ if not ssm_accessible and not is_running:
526
464
  continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
527
465
  if continue_setup:
528
466
  self.print_status("Continuing setup despite instance access issues", None, 2)
529
467
  return True
530
468
  return False
531
469
 
470
+ if not is_running:
471
+ start_instance = self.prompt("Would you like to start the instance?", "Y").lower() != 'n'
472
+ if not start_instance:
473
+ return False
474
+
475
+ try:
476
+ # Use the provided profile for EC2 operations
477
+ session = boto3.Session(profile_name=self.profile)
478
+ session.client('ec2').start_instances(InstanceIds=[instance_id])
479
+ self.print_status("Instance start requested. This may take a few minutes...", None, 2)
480
+
481
+ # Wait for instance to start
482
+ for _ in range(30): # 5 minute timeout
483
+ time.sleep(10)
484
+ ssm_accessible, is_running, is_complete = self.check_instance_setup(instance_id)
485
+ if is_running:
486
+ break
487
+
488
+ if not is_running:
489
+ self.print_status("Timeout waiting for instance to start", False, 2)
490
+ return False
491
+ except Exception as e:
492
+ self.print_status(f"Error starting instance: {e}", False, 2)
493
+ return False
494
+
532
495
  if is_complete:
533
496
  self.print_status("Instance setup is complete", True, 2)
534
497
  return True
535
498
 
499
+ if not ssm_accessible:
500
+ self.print_status("Waiting for SSM access...", None, 2)
501
+ # Wait for SSM access
502
+ for _ in range(30): # 5 minute timeout
503
+ time.sleep(10)
504
+ ssm_accessible, is_running, is_complete = self.check_instance_setup(instance_id)
505
+ if ssm_accessible or not is_running:
506
+ break
507
+
508
+ if not ssm_accessible:
509
+ self.print_status("Timeout waiting for SSM access", False, 2)
510
+ continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
511
+ if continue_setup:
512
+ self.print_status("Continuing setup despite SSM access issues", None, 2)
513
+ return True
514
+ return False
515
+
536
516
  wait = self.prompt("Instance setup is not complete. Would you like to wait?", "Y").lower() != 'n'
537
517
  if not wait:
538
518
  self.print_status("Skipping instance setup check", None, 2)
@@ -541,16 +521,24 @@ Host cloudx-{cloudx_env}-{hostname}
541
521
  self.print_status("Waiting for setup to complete...", None, 2)
542
522
  dots = 0
543
523
  while True:
544
- is_running, is_complete = self.check_instance_setup(instance_id)
524
+ ssm_accessible, is_running, is_complete = self.check_instance_setup(instance_id)
545
525
 
546
526
  if not is_running:
547
- self.print_status("Instance is no longer running or accessible", False, 2)
527
+ self.print_status("Instance is no longer running", False, 2)
548
528
  continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
549
529
  if continue_setup:
550
530
  self.print_status("Continuing setup despite instance issues", None, 2)
551
531
  return True
552
532
  return False
553
533
 
534
+ if not ssm_accessible:
535
+ self.print_status("Lost SSM access to instance", False, 2)
536
+ continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
537
+ if continue_setup:
538
+ self.print_status("Continuing setup despite SSM access issues", None, 2)
539
+ return True
540
+ return False
541
+
554
542
  if is_complete:
555
543
  self.print_status("Instance setup completed successfully", True, 2)
556
544
  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.8
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes