cloudx-proxy 0.3.5__tar.gz → 0.3.7__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.5 → cloudx_proxy-0.3.7}/CHANGELOG.md +14 -0
  2. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/PKG-INFO +1 -1
  3. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/cloudx_proxy/_version.py +2 -2
  4. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/cloudx_proxy/setup.py +129 -154
  5. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/cloudx_proxy.egg-info/PKG-INFO +1 -1
  6. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/.github/workflows/release.yml +0 -0
  7. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/.gitignore +0 -0
  8. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/.releaserc +0 -0
  9. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/CONTRIBUTING.md +0 -0
  10. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/LICENSE +0 -0
  11. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/README.md +0 -0
  12. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/cloudx_proxy/__init__.py +0 -0
  13. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/cloudx_proxy/cli.py +0 -0
  14. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/cloudx_proxy/core.py +0 -0
  15. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/cloudx_proxy.egg-info/SOURCES.txt +0 -0
  16. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/cloudx_proxy.egg-info/dependency_links.txt +0 -0
  17. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/cloudx_proxy.egg-info/entry_points.txt +0 -0
  18. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/cloudx_proxy.egg-info/requires.txt +0 -0
  19. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/cloudx_proxy.egg-info/top_level.txt +0 -0
  20. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/package.json +0 -0
  21. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/pyproject.toml +0 -0
  22. {cloudx_proxy-0.3.5 → cloudx_proxy-0.3.7}/setup.cfg +0 -0
@@ -1,3 +1,17 @@
1
+ ## [0.3.7](https://github.com/easytocloud/cloudX-proxy/compare/v0.3.6...v0.3.7) (2025-02-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * removed 1Password support and improved instance monitoring ([c01009a](https://github.com/easytocloud/cloudX-proxy/commit/c01009afb6d90f43768dece909351a8c3ca597ce))
7
+
8
+ ## [0.3.6](https://github.com/easytocloud/cloudX-proxy/compare/v0.3.5...v0.3.6) (2025-02-11)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * 1Password integration repaired ([b8fd8eb](https://github.com/easytocloud/cloudX-proxy/commit/b8fd8eb445e2ccae107b01c796bc5bec1a9fa1d3))
14
+
1
15
  ## [0.3.5](https://github.com/easytocloud/cloudX-proxy/compare/v0.3.4...v0.3.5) (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.5
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
@@ -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.5'
16
- __version_tuple__ = version_tuple = (0, 3, 5)
15
+ __version__ = version = '0.3.7'
16
+ __version_tuple__ = version_tuple = (0, 3, 7)
@@ -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,56 +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 and configure SSH agent.
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
- print("Adding SSH key to 1Password SSH agent...")
209
- try:
210
- # First try to add to SSH agent
211
- subprocess.run([
212
- 'op', 'ssh-add',
213
- '--name', f'cloudx-proxy-{self.ssh_key}',
214
- str(self.ssh_key_file)
215
- ], check=True)
216
-
217
- # Read private key content before removing
218
- with open(self.ssh_key_file, 'r') as f:
219
- private_key = f.read()
220
-
221
- # Store private key in 1Password as document for backup
222
- subprocess.run([
223
- 'op', 'document', 'create',
224
- '--title', f'cloudx-proxy SSH Key - {self.ssh_key}',
225
- ], input=private_key.encode(), check=True)
226
-
227
- # Remove private key file but keep public key
228
- os.remove(self.ssh_key_file)
229
- self.print_status("Private key added to 1Password SSH agent and removed from disk", True, 2)
230
- self.print_status("Backup copy stored in 1Password documents", True, 2)
231
- return True
232
- except subprocess.CalledProcessError as e:
233
- self.print_status(f"Failed to add key to SSH agent: {e}", False, 2)
234
- return False
235
- except subprocess.CalledProcessError:
236
- print("Error: 1Password CLI not installed or not signed in.")
237
- return False
238
-
239
166
  def _add_host_entry(self, cloudx_env: str, instance_id: str, hostname: str, current_config: str) -> bool:
240
167
  """Add settings to a specific host entry.
241
168
 
@@ -263,18 +190,7 @@ Host cloudx-{cloudx_env}-{hostname}
263
190
  HostName {instance_id}
264
191
  User ec2-user
265
192
  """
266
- if self.using_1password and platform.system() != 'Windows':
267
- # Use platform-specific agent socket path
268
- if platform.system() == 'Darwin': # macOS
269
- agent_sock = "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
270
- else: # Linux
271
- agent_sock = "~/.1password/agent.sock"
272
- host_entry += f""" IdentityAgent {agent_sock}
273
- IdentityFile {self.ssh_key_file}.pub
274
- IdentitiesOnly yes
275
- """
276
- else:
277
- host_entry += f""" IdentityFile {self.ssh_key_file}
193
+ host_entry += f""" IdentityFile {self.ssh_key_file}
278
194
  IdentitiesOnly yes
279
195
  """
280
196
  host_entry += f""" ProxyCommand {proxy_command}
@@ -384,19 +300,7 @@ Host cloudx-{cloudx_env}-*
384
300
  User ec2-user
385
301
  """
386
302
  # Add key configuration
387
- if self.using_1password and platform.system() != 'Windows':
388
- # Use platform-specific agent socket path
389
- if platform.system() == 'Darwin': # macOS
390
- agent_sock = "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
391
- else: # Linux
392
- agent_sock = "~/.1password/agent.sock"
393
- base_config += f""" IdentityAgent {agent_sock}
394
- IdentityFile {self.ssh_key_file}.pub
395
- IdentitiesOnly yes
396
-
397
- """
398
- else:
399
- base_config += f""" IdentityFile {self.ssh_key_file}
303
+ base_config += f""" IdentityFile {self.ssh_key_file}
400
304
  IdentitiesOnly yes
401
305
 
402
306
  """
@@ -453,71 +357,93 @@ Host cloudx-{cloudx_env}-{hostname}
453
357
  return True
454
358
  return False
455
359
 
456
- def check_instance_setup(self, instance_id: str) -> Tuple[bool, bool]:
360
+ def check_instance_setup(self, instance_id: str) -> Tuple[bool, bool, bool]:
457
361
  """Check if instance setup is complete.
458
362
 
459
363
  Args:
460
364
  instance_id: EC2 instance ID
461
365
 
462
366
  Returns:
463
- Tuple[bool, bool]: (is_running, is_setup_complete)
367
+ Tuple[bool, bool, bool]: (ssm_accessible, is_running, is_setup_complete)
464
368
  """
465
369
  try:
466
370
  session = boto3.Session(profile_name=self.profile)
467
371
  ssm = session.client('ssm')
372
+ ec2 = session.client('ec2')
468
373
 
469
- # Check if instance is online in SSM
470
- self.print_status("Checking instance status in SSM...", None, 4)
471
- response = ssm.describe_instance_information(
472
- Filters=[{'Key': 'InstanceIds', 'Values': [instance_id]}]
473
- )
474
- is_running = bool(response['InstanceInformationList'])
475
-
476
- if not is_running:
477
- self.print_status("Instance is not accessible via SSM", False, 4)
478
- return False, False
479
-
480
- self.print_status("Instance is accessible via SSM", True, 4)
481
-
482
- # Check setup status using SSM command
483
- self.print_status("Checking setup status...", None, 4)
484
- response = ssm.send_command(
485
- InstanceIds=[instance_id],
486
- DocumentName='AWS-RunShellScript',
487
- Parameters={
488
- 'commands': [
489
- 'test -f /home/ec2-user/.install-done && echo "DONE" || '
490
- 'test -f /home/ec2-user/.install-running && echo "RUNNING" || '
491
- 'echo "NOT_STARTED"'
492
- ]
493
- }
494
- )
495
-
496
- 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
497
390
 
498
- # Wait for command completion
499
- for _ in range(10): # 10 second timeout
500
- time.sleep(1)
501
- result = ssm.get_command_invocation(
502
- CommandId=command_id,
503
- 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]}]
504
396
  )
505
- if result['Status'] in ['Success', 'Failed']:
506
- break
507
-
508
- is_setup_complete = result['Status'] == 'Success' and result['StandardOutputContent'].strip() == 'DONE'
509
-
510
- if is_setup_complete:
511
- self.print_status("Setup is complete", True, 4)
512
- else:
513
- status = result['StandardOutputContent'].strip()
514
- self.print_status(f"Setup status: {status}", None, 4)
515
-
516
- 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
517
443
 
518
444
  except Exception as e:
519
445
  self.print_status(f"\033[1;91mError:\033[0m {str(e)}", False, 4)
520
- return False, False
446
+ return False, False, False
521
447
 
522
448
  def wait_for_setup_completion(self, instance_id: str) -> bool:
523
449
  """Wait for instance setup to complete.
@@ -526,25 +452,66 @@ Host cloudx-{cloudx_env}-{hostname}
526
452
  instance_id: EC2 instance ID
527
453
 
528
454
  Returns:
529
- bool: True if setup completed successfully
455
+ bool: True if setup completed successfully or user chose to continue
530
456
  """
531
457
  self.print_header("Instance Setup Check")
532
- self.print_status(f"Checking instance {instance_id} setup status...")
458
+ self.print_status(f"Checking instance {instance_id} status...")
533
459
 
534
- is_running, is_complete = self.check_instance_setup(instance_id)
460
+ ssm_accessible, is_running, is_complete = self.check_instance_setup(instance_id)
535
461
 
536
- if not is_running:
537
- self.print_status("Instance is not running or not accessible via SSM", False, 2)
462
+ if not ssm_accessible and not is_running:
538
463
  continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
539
464
  if continue_setup:
540
465
  self.print_status("Continuing setup despite instance access issues", None, 2)
541
466
  return True
542
467
  return False
543
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
+
544
494
  if is_complete:
545
495
  self.print_status("Instance setup is complete", True, 2)
546
496
  return True
547
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
+
548
515
  wait = self.prompt("Instance setup is not complete. Would you like to wait?", "Y").lower() != 'n'
549
516
  if not wait:
550
517
  self.print_status("Skipping instance setup check", None, 2)
@@ -553,16 +520,24 @@ Host cloudx-{cloudx_env}-{hostname}
553
520
  self.print_status("Waiting for setup to complete...", None, 2)
554
521
  dots = 0
555
522
  while True:
556
- is_running, is_complete = self.check_instance_setup(instance_id)
523
+ ssm_accessible, is_running, is_complete = self.check_instance_setup(instance_id)
557
524
 
558
525
  if not is_running:
559
- self.print_status("Instance is no longer running or accessible", False, 2)
526
+ self.print_status("Instance is no longer running", False, 2)
560
527
  continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
561
528
  if continue_setup:
562
529
  self.print_status("Continuing setup despite instance issues", None, 2)
563
530
  return True
564
531
  return False
565
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
+
566
541
  if is_complete:
567
542
  self.print_status("Instance setup completed successfully", True, 2)
568
543
  return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cloudx-proxy
3
- Version: 0.3.5
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes