cloudx-proxy 0.8.4__py3-none-any.whl → 0.9.0__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
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.8.4'
32
- __version_tuple__ = version_tuple = (0, 8, 4)
31
+ __version__ = version = '0.9.0'
32
+ __version_tuple__ = version_tuple = (0, 9, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
cloudx_proxy/cli.py CHANGED
@@ -31,7 +31,8 @@ Main commands:
31
31
  @click.option('--ssh-key', default='vscode', help='SSH key name to use (default: vscode)')
32
32
  @click.option('--ssh-config', help='SSH config file to use (default: ~/.ssh/vscode/config)')
33
33
  @click.option('--aws-env', help='AWS environment directory (default: ~/.aws, use name of directory in ~/.aws/aws-envs/)')
34
- def connect(instance_id: str, port: int, profile: str, region: str, ssh_key: str, ssh_config: str, aws_env: str):
34
+ @click.option('--dry-run', is_flag=True, help='Preview connection workflow without executing')
35
+ def connect(instance_id: str, port: int, profile: str, region: str, ssh_key: str, ssh_config: str, aws_env: str, dry_run: bool):
35
36
  """Connect to an EC2 instance via SSM.
36
37
 
37
38
  INSTANCE_ID is the EC2 instance ID to connect to (e.g., i-0123456789abcdef0)
@@ -52,7 +53,8 @@ def connect(instance_id: str, port: int, profile: str, region: str, ssh_key: str
52
53
  region=region,
53
54
  ssh_key=ssh_key,
54
55
  ssh_config=ssh_config,
55
- aws_env=aws_env
56
+ aws_env=aws_env,
57
+ dry_run=dry_run
56
58
  )
57
59
 
58
60
  client.log(f"cloudx-proxy@{__version__} Connecting to instance {instance_id} on port {port}...")
@@ -73,8 +75,9 @@ def connect(instance_id: str, port: int, profile: str, region: str, ssh_key: str
73
75
  @click.option('--instance', help='EC2 instance ID to set up connection for')
74
76
  @click.option('--hostname', help='Hostname to use for SSH configuration')
75
77
  @click.option('--yes', 'non_interactive', is_flag=True, help='Non-interactive mode, use default values for all prompts')
78
+ @click.option('--dry-run', is_flag=True, help='Preview setup changes without executing')
76
79
  def setup(profile: str, ssh_key: str, ssh_config: str, aws_env: str, use_1password: str,
77
- instance: str, hostname: str, non_interactive: bool):
80
+ instance: str, hostname: str, non_interactive: bool, dry_run: bool):
78
81
  """Set up AWS profile, SSH keys, and configuration for CloudX.
79
82
 
80
83
  \b
@@ -103,10 +106,14 @@ def setup(profile: str, ssh_key: str, ssh_config: str, aws_env: str, use_1passwo
103
106
  aws_env=aws_env,
104
107
  use_1password=use_1password,
105
108
  instance_id=instance,
106
- non_interactive=non_interactive
109
+ non_interactive=non_interactive,
110
+ dry_run=dry_run
107
111
  )
108
112
 
109
- print("\n\033[1;95m=== cloudx-proxy Setup ===\033[0m\n")
113
+ if dry_run:
114
+ print("\n\033[1;95m=== cloudx-proxy Setup (DRY RUN) ===\033[0m\n")
115
+ else:
116
+ print("\n\033[1;95m=== cloudx-proxy Setup ===\033[0m\n")
110
117
 
111
118
  # Set up AWS profile
112
119
  if not setup.setup_aws_profile():
@@ -147,7 +154,8 @@ def setup(profile: str, ssh_key: str, ssh_config: str, aws_env: str, use_1passwo
147
154
  @click.option('--ssh-config', help='SSH config file to use (default: ~/.ssh/vscode/config)')
148
155
  @click.option('--environment', help='Filter hosts by environment (e.g., dev, prod)')
149
156
  @click.option('--detailed', is_flag=True, help='Show detailed information including instance IDs')
150
- def list(ssh_config: str, environment: str, detailed: bool):
157
+ @click.option('--dry-run', is_flag=True, help='Preview list output format')
158
+ def list(ssh_config: str, environment: str, detailed: bool, dry_run: bool):
151
159
  """List configured cloudx-proxy SSH hosts.
152
160
 
153
161
  This command parses the SSH configuration file and displays all configured cloudx-proxy hosts.
@@ -168,6 +176,16 @@ def list(ssh_config: str, environment: str, detailed: bool):
168
176
  else:
169
177
  config_file = Path(os.path.expanduser("~/.ssh/vscode/config"))
170
178
 
179
+ if dry_run:
180
+ print(f"\n\033[1;95m=== cloudx-proxy List (DRY RUN) ===\033[0m\n")
181
+ print(f"[DRY RUN] Would read SSH config from: {config_file}")
182
+ if environment:
183
+ print(f"[DRY RUN] Would filter hosts by environment: {environment}")
184
+ if detailed:
185
+ print(f"[DRY RUN] Would show detailed information including instance IDs")
186
+ print(f"[DRY RUN] Would parse SSH configuration and display grouped hosts")
187
+ return
188
+
171
189
  if not config_file.exists():
172
190
  print(f"SSH config file not found: {config_file}")
173
191
  print("Run 'cloudx-proxy setup' to create a configuration.")
cloudx_proxy/core.py CHANGED
@@ -7,7 +7,7 @@ from botocore.exceptions import ClientError
7
7
 
8
8
  class CloudXProxy:
9
9
  def __init__(self, instance_id: str, port: int = 22, profile: str = "vscode",
10
- region: str = None, ssh_key: str = "vscode", ssh_config: str = None, aws_env: str = None):
10
+ region: str = None, ssh_key: str = "vscode", ssh_config: str = None, aws_env: str = None, dry_run: bool = False):
11
11
  """Initialize CloudX client for SSH tunneling via AWS SSM.
12
12
 
13
13
  Args:
@@ -17,10 +17,12 @@ class CloudXProxy:
17
17
  region: AWS region (default: from profile)
18
18
  ssh_key: SSH key name to use (default: "vscode")
19
19
  aws_env: AWS environment directory (default: None, uses ~/.aws)
20
+ dry_run: Preview mode, show what would be done without executing (default: False)
20
21
  """
21
22
  self.instance_id = instance_id
22
23
  self.port = port
23
24
  self.profile = profile
25
+ self.dry_run = dry_run
24
26
 
25
27
  # Configure AWS environment
26
28
  if aws_env:
@@ -28,17 +30,25 @@ class CloudXProxy:
28
30
  os.environ["AWS_CONFIG_FILE"] = os.path.join(aws_env_dir, "config")
29
31
  os.environ["AWS_SHARED_CREDENTIALS_FILE"] = os.path.join(aws_env_dir, "credentials")
30
32
 
31
- # Set up AWS session with eu-west-1 as default region
32
- if not region:
33
- # Try to get region from profile first
34
- session = boto3.Session(profile_name=profile)
35
- region = session.region_name or 'eu-west-1'
36
-
37
- self.session = boto3.Session(profile_name=profile, region_name=region)
38
- self.ssm = self.session.client('ssm')
39
- self.ec2 = self.session.client('ec2')
40
- self.ec2_connect = self.session.client('ec2-instance-connect')
41
-
33
+ # Set up AWS session with eu-west-1 as default region (skip in dry-run mode)
34
+ if not self.dry_run:
35
+ if not region:
36
+ # Try to get region from profile first
37
+ session = boto3.Session(profile_name=profile)
38
+ region = session.region_name or 'eu-west-1'
39
+
40
+ self.session = boto3.Session(profile_name=profile, region_name=region)
41
+ self.ssm = self.session.client('ssm')
42
+ self.ec2 = self.session.client('ec2')
43
+ self.ec2_connect = self.session.client('ec2-instance-connect')
44
+ else:
45
+ self.session = None
46
+ self.ssm = None
47
+ self.ec2 = None
48
+ self.ec2_connect = None
49
+ if not region:
50
+ region = 'eu-west-1' # Default for dry-run display
51
+ self.region = region
42
52
  # Set up SSH configuration and key paths
43
53
  if ssh_config:
44
54
  self.ssh_config_file = os.path.expanduser(ssh_config)
@@ -55,6 +65,9 @@ class CloudXProxy:
55
65
 
56
66
  def get_instance_status(self) -> str:
57
67
  """Check if instance is online in SSM."""
68
+ if self.dry_run:
69
+ return 'Online' # Simulate online status for dry-run
70
+
58
71
  try:
59
72
  response = self.ssm.describe_instance_information(
60
73
  Filters=[{'Key': 'InstanceIds', 'Values': [self.instance_id]}]
@@ -67,6 +80,10 @@ class CloudXProxy:
67
80
 
68
81
  def start_instance(self) -> bool:
69
82
  """Start the EC2 instance if it's stopped."""
83
+ if self.dry_run:
84
+ self.log(f"[DRY RUN] Would start EC2 instance: {self.instance_id}")
85
+ return True
86
+
70
87
  try:
71
88
  self.ec2.start_instances(InstanceIds=[self.instance_id])
72
89
  return True
@@ -84,6 +101,10 @@ class CloudXProxy:
84
101
  Returns:
85
102
  bool: True if instance came online, False if timeout
86
103
  """
104
+ if self.dry_run:
105
+ self.log(f"[DRY RUN] Would wait for instance to come online (max {max_attempts * delay} seconds)")
106
+ return True
107
+
87
108
  for _ in range(max_attempts):
88
109
  if self.get_instance_status() == 'Online':
89
110
  return True
@@ -96,6 +117,14 @@ class CloudXProxy:
96
117
  Determines which SSH key to use (regular key or 1Password-managed key),
97
118
  then pushes the correct public key to the instance.
98
119
  """
120
+ if self.dry_run:
121
+ key_path = self.ssh_key
122
+ if not key_path.endswith('.pub'):
123
+ key_path += '.pub'
124
+ self.log(f"[DRY RUN] Would push SSH public key: {key_path}")
125
+ self.log(f"[DRY RUN] Would send key to instance {self.instance_id} as ec2-user")
126
+ return True
127
+
99
128
  try:
100
129
  # Check if file exists with .pub extension (could be a non-1Password key)
101
130
  # or if the .pub extension is already part of self.ssh_key (because of 1Password integration)
@@ -129,6 +158,12 @@ class CloudXProxy:
129
158
  2. Only use stderr for logging
130
159
  3. Let the session manager plugin handle the actual data transfer
131
160
  """
161
+ if self.dry_run:
162
+ region = self.region or 'eu-west-1' # Use initialized region or default
163
+ self.log(f"[DRY RUN] Would start SSM session with SSH port forwarding")
164
+ self.log(f"[DRY RUN] Would run: aws ssm start-session --target {self.instance_id} --document-name AWS-StartSSHSession --parameters portNumber={self.port} --profile {self.profile} --region {region}")
165
+ return
166
+
132
167
  import subprocess
133
168
  import platform
134
169
 
@@ -185,6 +220,15 @@ class CloudXProxy:
185
220
  3. Push SSH key
186
221
  4. Start SSM session
187
222
  """
223
+ if self.dry_run:
224
+ self.log(f"[DRY RUN] Connection workflow preview:")
225
+ self.log(f"[DRY RUN] Would check instance status: {self.instance_id}")
226
+ self.log(f"[DRY RUN] Would start instance if stopped")
227
+ self.log(f"[DRY RUN] Would wait for instance to come online")
228
+ self.log(f"[DRY RUN] Would push SSH key to instance")
229
+ self.log(f"[DRY RUN] Would start SSM session with port forwarding 22 -> localhost:22")
230
+ return True
231
+
188
232
  status = self.get_instance_status()
189
233
 
190
234
  if status != 'Online':
cloudx_proxy/setup.py CHANGED
@@ -15,7 +15,7 @@ class CloudXSetup:
15
15
 
16
16
  def __init__(self, profile: str = "vscode", ssh_key: str = "vscode", ssh_config: str = None,
17
17
  aws_env: str = None, use_1password: str = None, instance_id: str = None,
18
- non_interactive: bool = False):
18
+ non_interactive: bool = False, dry_run: bool = False):
19
19
  """Initialize cloudx-proxy setup.
20
20
 
21
21
  Args:
@@ -26,6 +26,7 @@ class CloudXSetup:
26
26
  use_1password: Use 1Password SSH agent for authentication. Can be True/False or a vault name (default: None)
27
27
  instance_id: EC2 instance ID to set up connection for (optional)
28
28
  non_interactive: Non-interactive mode, use defaults for all prompts (default: False)
29
+ dry_run: Preview mode, show what would be done without executing (default: False)
29
30
  """
30
31
  self.profile = profile
31
32
  self.ssh_key = ssh_key
@@ -43,6 +44,7 @@ class CloudXSetup:
43
44
  self.op_vault = use_1password
44
45
  self.instance_id = instance_id
45
46
  self.non_interactive = non_interactive
47
+ self.dry_run = dry_run
46
48
  self.home_dir = str(Path.home())
47
49
  self.onepassword_agent_sock = Path(self.home_dir) / ".1password" / "agent.sock"
48
50
 
@@ -134,6 +136,13 @@ class CloudXSetup:
134
136
  Returns:
135
137
  bool: True if profile was set up successfully or user chose to continue
136
138
  """
139
+ if self.dry_run:
140
+ self.print_status(f"[DRY RUN] Would check AWS profile configuration for '{self.profile}'")
141
+ if self.aws_env:
142
+ self.print_status(f"[DRY RUN] Would configure AWS environment: {self.aws_env}", None, 2)
143
+ self.print_status(f"[DRY RUN] Would verify AWS credentials and extract cloudX environment", None, 2)
144
+ return True
145
+
137
146
  self.print_status("Checking AWS profile configuration...")
138
147
 
139
148
  try:
@@ -362,6 +371,16 @@ class CloudXSetup:
362
371
  """
363
372
  self.print_header("SSH Key Configuration")
364
373
 
374
+ if self.dry_run:
375
+ self.print_status(f"[DRY RUN] Would check SSH key '{self.ssh_key}' configuration")
376
+ if self.use_1password:
377
+ self.print_status(f"[DRY RUN] Would use 1Password SSH agent for authentication", None, 2)
378
+ self.print_status(f"[DRY RUN] Would create or find SSH key in vault: {self.op_vault}", None, 2)
379
+ else:
380
+ self.print_status(f"[DRY RUN] Would create SSH key pair at: {self.ssh_key_file}", None, 2)
381
+ self.print_status(f"[DRY RUN] Would set proper file permissions", None, 2)
382
+ return True
383
+
365
384
  # Check 1Password integration if requested
366
385
  if self.use_1password:
367
386
  op_available = self._check_1password_availability()
@@ -790,6 +809,15 @@ Host cloudx-{cloudx_env}-{hostname}
790
809
  bool: True if config was set up successfully
791
810
  """
792
811
  self.print_header("SSH Configuration")
812
+
813
+ if self.dry_run:
814
+ self.print_status(f"[DRY RUN] Would set up SSH configuration with three-tier approach")
815
+ self.print_status(f"[DRY RUN] Would create generic pattern: cloudx-*", None, 2)
816
+ self.print_status(f"[DRY RUN] Would create environment pattern: cloudx-{cloudx_env}-*", None, 2)
817
+ self.print_status(f"[DRY RUN] Would create host entry: cloudx-{cloudx_env}-{hostname} -> {instance_id}", None, 2)
818
+ self.print_status(f"[DRY RUN] Would write configuration to: {self.ssh_config_file}", None, 2)
819
+ return True
820
+
793
821
  self.print_status("Setting up SSH configuration with three-tier approach...")
794
822
 
795
823
  try:
@@ -953,6 +981,12 @@ Host cloudx-{cloudx_env}-{hostname}
953
981
  """
954
982
  self.print_header("Instance Access Check")
955
983
 
984
+ if self.dry_run:
985
+ self.print_status(f"[DRY RUN] Would check instance accessibility for: {hostname}")
986
+ self.print_status(f"[DRY RUN] Would test connection to instance: {instance_id}", None, 2)
987
+ self.print_status(f"[DRY RUN] Would wait up to 5 minutes for SSH access if needed", None, 2)
988
+ return True
989
+
956
990
  if self.check_instance_setup(instance_id, hostname, cloudx_env):
957
991
  return True
958
992
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudx-proxy
3
- Version: 0.8.4
3
+ Version: 0.9.0
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,13 @@
1
+ cloudx_proxy/_1password.py,sha256=uxyCfVvO1eQrOfYRojst_LN2DV4fIwxM5moaQTn3wQY,5853
2
+ cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
3
+ cloudx_proxy/_version.py,sha256=TvxBYkx8Rz_Q1S3JFp831BRT8Wo0Yxt6TJMtgZKenTo,704
4
+ cloudx_proxy/cli.py,sha256=DYUjhmgaUcTQlJ2FcbgoLtPJMCbETBIn0tSMDybgC0s,10932
5
+ cloudx_proxy/core.py,sha256=f5Wrz68nZAy0QnEw3V0OAHfc0vNPX8xfAPtY8zFIL0k,10340
6
+ cloudx_proxy/setup.py,sha256=vVM5ZVEIxudjPdZKpjXG0JKPXzgMbN4ztbkV-5XoIu0,44927
7
+ cloudx_proxy-0.9.0.dist-info/licenses/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
8
+ cloudx_proxy-0.9.0.dist-info/licenses/NOTICE,sha256=JH0TVxH8Nkt3_CkL2-MEiJfVGvWYZ52H5uUxEq9p7rg,3223
9
+ cloudx_proxy-0.9.0.dist-info/METADATA,sha256=F4AmOHuQ271ymlzXMT59OLXJUrLyN84eG17I1j3LxzM,29053
10
+ cloudx_proxy-0.9.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ cloudx_proxy-0.9.0.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
12
+ cloudx_proxy-0.9.0.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
13
+ cloudx_proxy-0.9.0.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- cloudx_proxy/_1password.py,sha256=uxyCfVvO1eQrOfYRojst_LN2DV4fIwxM5moaQTn3wQY,5853
2
- cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
3
- cloudx_proxy/_version.py,sha256=6jY56wQpNbuVX1aDDCpFgsBq74u7lloIJcrJSefeVec,704
4
- cloudx_proxy/cli.py,sha256=vvPxNaixGdc_hirQrHgjxrmbiFtX-ugYoHGgR0zq79w,9934
5
- cloudx_proxy/core.py,sha256=RF3bX5MQiokRKjYEPnfWdKywGdtoVUvV2xZqm9uOl1g,8135
6
- cloudx_proxy/setup.py,sha256=CmUYr9EhtLoEpKU9jMEgxXtVMYlZTfPAJ-eQ59nr_bI,42785
7
- cloudx_proxy-0.8.4.dist-info/licenses/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
8
- cloudx_proxy-0.8.4.dist-info/licenses/NOTICE,sha256=JH0TVxH8Nkt3_CkL2-MEiJfVGvWYZ52H5uUxEq9p7rg,3223
9
- cloudx_proxy-0.8.4.dist-info/METADATA,sha256=5y1v_qUKTRAdn3gvgM-ARGAWDkkxb4MOXW9fHu0MgI4,29053
10
- cloudx_proxy-0.8.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
- cloudx_proxy-0.8.4.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
12
- cloudx_proxy-0.8.4.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
13
- cloudx_proxy-0.8.4.dist-info/RECORD,,