cloudx-proxy 0.4.3__tar.gz → 0.4.4__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.
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/CHANGELOG.md +7 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/PKG-INFO +35 -20
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/README.md +34 -19
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy/_version.py +2 -2
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy/cli.py +2 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy/setup.py +265 -122
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy.egg-info/PKG-INFO +35 -20
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/.github/workflows/release.yml +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/.gitignore +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/.releaserc +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/CONTRIBUTING.md +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/LICENSE +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy/_1password.py +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy/__init__.py +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy/core.py +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy.egg-info/SOURCES.txt +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy.egg-info/dependency_links.txt +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy.egg-info/entry_points.txt +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy.egg-info/requires.txt +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/cloudx_proxy.egg-info/top_level.txt +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/package.json +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/pyproject.toml +0 -0
- {cloudx_proxy-0.4.3 → cloudx_proxy-0.4.4}/setup.cfg +0 -0
@@ -1,3 +1,10 @@
|
|
1
|
+
## [0.4.4](https://github.com/easytocloud/cloudX-proxy/compare/v0.4.3...v0.4.4) (2025-03-07)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* improved generated ssh config file for multiple cloudx instances ([11c28d1](https://github.com/easytocloud/cloudX-proxy/commit/11c28d1534bddfbbd7d108a2a961aa21166e899a))
|
7
|
+
|
1
8
|
## [0.4.3](https://github.com/easytocloud/cloudX-proxy/compare/v0.4.2...v0.4.3) (2025-03-06)
|
2
9
|
|
3
10
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: cloudx-proxy
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.4
|
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
|
@@ -158,22 +158,30 @@ The setup command configures SSH to use cloudX-proxy as a ProxyCommand, enabling
|
|
158
158
|
uvx cloudx-proxy setup --profile myprofile --ssh-key mykey
|
159
159
|
```
|
160
160
|
|
161
|
-
Will create a configuration like this:
|
161
|
+
Will create a three-tier configuration structure like this:
|
162
162
|
|
163
163
|
```
|
164
|
-
#
|
165
|
-
#
|
166
|
-
|
164
|
+
# Generic configuration (shared by all environments)
|
165
|
+
# Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
|
166
|
+
# Configuration type: generic
|
167
|
+
Host cloudx-*
|
167
168
|
User ec2-user
|
168
|
-
IdentityFile ~/.ssh/vscode/mykey
|
169
|
-
IdentitiesOnly yes
|
170
|
-
ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
|
171
169
|
TCPKeepAlive yes
|
172
170
|
ControlMaster auto
|
173
171
|
ControlPath ~/.ssh/control/%r@%h:%p
|
174
172
|
ControlPersist 4h
|
175
173
|
|
176
|
-
#
|
174
|
+
# Environment configuration (specific to a single environment)
|
175
|
+
# Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
|
176
|
+
# Configuration type: environment
|
177
|
+
Host cloudx-dev-*
|
178
|
+
IdentityFile ~/.ssh/vscode/mykey
|
179
|
+
IdentitiesOnly yes
|
180
|
+
ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
|
181
|
+
|
182
|
+
# Host configuration (specific to a single instance)
|
183
|
+
# Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
|
184
|
+
# Configuration type: host
|
177
185
|
Host cloudx-dev-myserver
|
178
186
|
HostName i-0123456789abcdef0
|
179
187
|
```
|
@@ -191,23 +199,30 @@ In these examples, ssh will use cloudx-proxy to connect to AWS with the `myprofi
|
|
191
199
|
VSCode will be able to connect to the instance using the same SSH configuration.
|
192
200
|
|
193
201
|
### SSH Configuration Details
|
194
|
-
The setup command creates
|
202
|
+
The setup command creates a hierarchical three-tier SSH configuration structure:
|
195
203
|
|
196
|
-
1.
|
197
|
-
- User
|
198
|
-
- 1Password SSH agent integration if selected
|
199
|
-
- ProxyCommand with appropriate parameters
|
200
|
-
- SSH multiplexing for better performance
|
204
|
+
1. Generic configuration (cloudx-*) containing common settings shared across all environments:
|
205
|
+
- User settings (ec2-user)
|
201
206
|
- TCP keepalive for connection stability
|
207
|
+
- SSH multiplexing for better performance (ControlMaster, ControlPath, ControlPersist)
|
202
208
|
|
203
|
-
2.
|
204
|
-
-
|
205
|
-
-
|
206
|
-
- Inherits all
|
209
|
+
2. Environment-specific configuration (cloudx-{env}-*) with:
|
210
|
+
- Authentication settings (IdentityFile, IdentityAgent for 1Password)
|
211
|
+
- ProxyCommand with environment-specific parameters
|
212
|
+
- Inherits all settings from the generic configuration
|
213
|
+
|
214
|
+
3. Host-specific entries (cloudx-{env}-hostname) with:
|
215
|
+
- Instance ID (HostName directive)
|
216
|
+
- Inherits all settings from both generic and environment configurations
|
217
|
+
|
218
|
+
Each configuration tier is clearly marked with a timestamp and version information comment, making it easy to track when and how configurations were created.
|
207
219
|
|
208
220
|
When adding new instances to an existing environment, you can choose to:
|
221
|
+
- Keep the existing environment configuration if it's compatible
|
209
222
|
- Override the environment configuration with new settings
|
210
|
-
- Add
|
223
|
+
- Add host-specific settings only
|
224
|
+
|
225
|
+
This three-tier structure offers better maintainability by reducing duplication and making it clear which settings apply broadly and which are specific to an environment or host.
|
211
226
|
|
212
227
|
### VSCode Configuration
|
213
228
|
|
@@ -108,22 +108,30 @@ The setup command configures SSH to use cloudX-proxy as a ProxyCommand, enabling
|
|
108
108
|
uvx cloudx-proxy setup --profile myprofile --ssh-key mykey
|
109
109
|
```
|
110
110
|
|
111
|
-
Will create a configuration like this:
|
111
|
+
Will create a three-tier configuration structure like this:
|
112
112
|
|
113
113
|
```
|
114
|
-
#
|
115
|
-
#
|
116
|
-
|
114
|
+
# Generic configuration (shared by all environments)
|
115
|
+
# Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
|
116
|
+
# Configuration type: generic
|
117
|
+
Host cloudx-*
|
117
118
|
User ec2-user
|
118
|
-
IdentityFile ~/.ssh/vscode/mykey
|
119
|
-
IdentitiesOnly yes
|
120
|
-
ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
|
121
119
|
TCPKeepAlive yes
|
122
120
|
ControlMaster auto
|
123
121
|
ControlPath ~/.ssh/control/%r@%h:%p
|
124
122
|
ControlPersist 4h
|
125
123
|
|
126
|
-
#
|
124
|
+
# Environment configuration (specific to a single environment)
|
125
|
+
# Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
|
126
|
+
# Configuration type: environment
|
127
|
+
Host cloudx-dev-*
|
128
|
+
IdentityFile ~/.ssh/vscode/mykey
|
129
|
+
IdentitiesOnly yes
|
130
|
+
ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
|
131
|
+
|
132
|
+
# Host configuration (specific to a single instance)
|
133
|
+
# Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
|
134
|
+
# Configuration type: host
|
127
135
|
Host cloudx-dev-myserver
|
128
136
|
HostName i-0123456789abcdef0
|
129
137
|
```
|
@@ -141,23 +149,30 @@ In these examples, ssh will use cloudx-proxy to connect to AWS with the `myprofi
|
|
141
149
|
VSCode will be able to connect to the instance using the same SSH configuration.
|
142
150
|
|
143
151
|
### SSH Configuration Details
|
144
|
-
The setup command creates
|
152
|
+
The setup command creates a hierarchical three-tier SSH configuration structure:
|
145
153
|
|
146
|
-
1.
|
147
|
-
- User
|
148
|
-
- 1Password SSH agent integration if selected
|
149
|
-
- ProxyCommand with appropriate parameters
|
150
|
-
- SSH multiplexing for better performance
|
154
|
+
1. Generic configuration (cloudx-*) containing common settings shared across all environments:
|
155
|
+
- User settings (ec2-user)
|
151
156
|
- TCP keepalive for connection stability
|
157
|
+
- SSH multiplexing for better performance (ControlMaster, ControlPath, ControlPersist)
|
152
158
|
|
153
|
-
2.
|
154
|
-
-
|
155
|
-
-
|
156
|
-
- Inherits all
|
159
|
+
2. Environment-specific configuration (cloudx-{env}-*) with:
|
160
|
+
- Authentication settings (IdentityFile, IdentityAgent for 1Password)
|
161
|
+
- ProxyCommand with environment-specific parameters
|
162
|
+
- Inherits all settings from the generic configuration
|
163
|
+
|
164
|
+
3. Host-specific entries (cloudx-{env}-hostname) with:
|
165
|
+
- Instance ID (HostName directive)
|
166
|
+
- Inherits all settings from both generic and environment configurations
|
167
|
+
|
168
|
+
Each configuration tier is clearly marked with a timestamp and version information comment, making it easy to track when and how configurations were created.
|
157
169
|
|
158
170
|
When adding new instances to an existing environment, you can choose to:
|
171
|
+
- Keep the existing environment configuration if it's compatible
|
159
172
|
- Override the environment configuration with new settings
|
160
|
-
- Add
|
173
|
+
- Add host-specific settings only
|
174
|
+
|
175
|
+
This three-tier structure offers better maintainability by reducing duplication and making it clear which settings apply broadly and which are specific to an environment or host.
|
161
176
|
|
162
177
|
### VSCode Configuration
|
163
178
|
|
@@ -346,6 +346,18 @@ class CloudXSetup:
|
|
346
346
|
return True
|
347
347
|
return False
|
348
348
|
|
349
|
+
def _get_version(self) -> str:
|
350
|
+
"""Get the current version of the cloudx-proxy package.
|
351
|
+
|
352
|
+
Returns:
|
353
|
+
str: Version string
|
354
|
+
"""
|
355
|
+
try:
|
356
|
+
from . import __version__
|
357
|
+
return __version__
|
358
|
+
except (ImportError, AttributeError):
|
359
|
+
return "unknown"
|
360
|
+
|
349
361
|
def _build_proxy_command(self) -> str:
|
350
362
|
"""Build the ProxyCommand with appropriate parameters.
|
351
363
|
|
@@ -383,24 +395,31 @@ class CloudXSetup:
|
|
383
395
|
IdentitiesOnly yes
|
384
396
|
"""
|
385
397
|
|
386
|
-
def
|
387
|
-
"""
|
398
|
+
def _get_timestamp(self) -> str:
|
399
|
+
"""Get a formatted timestamp for configuration comments.
|
388
400
|
|
389
|
-
Args:
|
390
|
-
cloudx_env: CloudX environment
|
391
|
-
|
392
401
|
Returns:
|
393
|
-
str:
|
402
|
+
str: Formatted timestamp
|
394
403
|
"""
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
host_entry += self._build_auth_config()
|
404
|
+
from datetime import datetime
|
405
|
+
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
406
|
+
|
407
|
+
def _build_generic_config(self) -> str:
|
408
|
+
"""Build a generic configuration block with common settings for all environments.
|
401
409
|
|
402
|
-
|
403
|
-
|
410
|
+
Returns:
|
411
|
+
str: Generic configuration block
|
412
|
+
"""
|
413
|
+
version = self._get_version()
|
414
|
+
timestamp = self._get_timestamp()
|
415
|
+
|
416
|
+
# Start with metadata comment
|
417
|
+
config = f"""
|
418
|
+
# Created by cloudx-proxy v{version} on {timestamp}
|
419
|
+
# Configuration type: generic
|
420
|
+
Host cloudx-*
|
421
|
+
User ec2-user
|
422
|
+
TCPKeepAlive yes
|
404
423
|
"""
|
405
424
|
|
406
425
|
# Add SSH multiplexing configuration
|
@@ -409,16 +428,42 @@ Host cloudx-{cloudx_env}-*
|
|
409
428
|
# Use forward slashes for Windows as well, SSH client will handle conversion
|
410
429
|
control_path = "~/.ssh/control/%r@%h:%p"
|
411
430
|
|
412
|
-
|
413
|
-
ControlMaster auto
|
431
|
+
config += f""" ControlMaster auto
|
414
432
|
ControlPath {control_path}
|
415
433
|
ControlPersist 4h
|
416
434
|
"""
|
417
435
|
|
418
|
-
return
|
436
|
+
return config
|
437
|
+
|
438
|
+
def _build_environment_config(self, cloudx_env: str) -> str:
|
439
|
+
"""Build an environment-specific configuration block.
|
440
|
+
|
441
|
+
Args:
|
442
|
+
cloudx_env: CloudX environment
|
443
|
+
|
444
|
+
Returns:
|
445
|
+
str: Environment configuration block
|
446
|
+
"""
|
447
|
+
version = self._get_version()
|
448
|
+
timestamp = self._get_timestamp()
|
449
|
+
|
450
|
+
# Start with metadata comment
|
451
|
+
config = f"""
|
452
|
+
# Created by cloudx-proxy v{version} on {timestamp}
|
453
|
+
# Configuration type: environment
|
454
|
+
Host cloudx-{cloudx_env}-*
|
455
|
+
"""
|
456
|
+
# Add authentication configuration
|
457
|
+
config += self._build_auth_config()
|
458
|
+
|
459
|
+
# Add ProxyCommand
|
460
|
+
config += f""" ProxyCommand {self._build_proxy_command()}
|
461
|
+
"""
|
462
|
+
|
463
|
+
return config
|
419
464
|
|
420
465
|
def _build_host_config(self, cloudx_env: str, hostname: str, instance_id: str) -> str:
|
421
|
-
"""Build a
|
466
|
+
"""Build a host-specific configuration block.
|
422
467
|
|
423
468
|
Args:
|
424
469
|
cloudx_env: CloudX environment
|
@@ -426,13 +471,62 @@ Host cloudx-{cloudx_env}-*
|
|
426
471
|
instance_id: EC2 instance ID
|
427
472
|
|
428
473
|
Returns:
|
429
|
-
str:
|
474
|
+
str: Host configuration block
|
430
475
|
"""
|
431
|
-
|
476
|
+
version = self._get_version()
|
477
|
+
timestamp = self._get_timestamp()
|
478
|
+
|
479
|
+
# Start with metadata comment
|
480
|
+
config = f"""
|
481
|
+
# Created by cloudx-proxy v{version} on {timestamp}
|
482
|
+
# Configuration type: host
|
432
483
|
Host cloudx-{cloudx_env}-{hostname}
|
433
484
|
HostName {instance_id}
|
434
485
|
"""
|
435
486
|
|
487
|
+
return config
|
488
|
+
|
489
|
+
def _check_config_exists(self, pattern: str, current_config: str) -> bool:
|
490
|
+
"""Check if a configuration pattern exists in the current config.
|
491
|
+
|
492
|
+
Args:
|
493
|
+
pattern: Host pattern to look for (e.g., 'cloudx-*', 'cloudx-dev-*')
|
494
|
+
current_config: Current SSH config content
|
495
|
+
|
496
|
+
Returns:
|
497
|
+
bool: True if pattern exists in configuration
|
498
|
+
"""
|
499
|
+
return f"Host {pattern}" in current_config
|
500
|
+
|
501
|
+
def _extract_host_config(self, pattern: str, current_config: str) -> Tuple[str, str]:
|
502
|
+
"""Extract a host configuration block from the current config.
|
503
|
+
|
504
|
+
Args:
|
505
|
+
pattern: Host pattern to extract (e.g., 'cloudx-*', 'cloudx-dev-*')
|
506
|
+
current_config: Current SSH config content
|
507
|
+
|
508
|
+
Returns:
|
509
|
+
Tuple[str, str]: Extracted host configuration, remaining configuration
|
510
|
+
"""
|
511
|
+
lines = current_config.splitlines()
|
512
|
+
host_config_lines = []
|
513
|
+
remaining_lines = []
|
514
|
+
in_host_block = False
|
515
|
+
|
516
|
+
for line in lines:
|
517
|
+
if line.strip() == f"Host {pattern}":
|
518
|
+
in_host_block = True
|
519
|
+
host_config_lines.append(line)
|
520
|
+
elif in_host_block and line.strip().startswith("Host "):
|
521
|
+
in_host_block = False
|
522
|
+
remaining_lines.append(line)
|
523
|
+
elif in_host_block:
|
524
|
+
host_config_lines.append(line)
|
525
|
+
else:
|
526
|
+
remaining_lines.append(line)
|
527
|
+
|
528
|
+
return "\n".join(host_config_lines), "\n".join(remaining_lines)
|
529
|
+
|
436
530
|
def _add_host_entry(self, cloudx_env: str, instance_id: str, hostname: str, current_config: str) -> bool:
|
437
531
|
"""Add settings to a specific host entry.
|
438
532
|
|
@@ -446,13 +540,29 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
446
540
|
bool: True if settings were added successfully
|
447
541
|
"""
|
448
542
|
try:
|
449
|
-
#
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
543
|
+
# Check if host entry already exists
|
544
|
+
host_pattern = f"cloudx-{cloudx_env}-{hostname}"
|
545
|
+
if self._check_config_exists(host_pattern, current_config):
|
546
|
+
# Extract existing host configuration
|
547
|
+
host_config, remaining_config = self._extract_host_config(host_pattern, current_config)
|
548
|
+
|
549
|
+
# Update host configuration
|
550
|
+
host_config = self._build_host_config(cloudx_env, hostname, instance_id)
|
551
|
+
|
552
|
+
# Write updated config
|
553
|
+
with open(self.ssh_config_file, 'w') as f:
|
554
|
+
f.write(remaining_config)
|
555
|
+
f.write(host_config)
|
556
|
+
|
557
|
+
self.print_status(f"Updated existing host entry for {host_pattern}", True, 2)
|
558
|
+
else:
|
559
|
+
# Generate new host entry
|
560
|
+
host_entry = self._build_host_config(cloudx_env, hostname, instance_id)
|
561
|
+
|
562
|
+
# Append host entry
|
563
|
+
with open(self.ssh_config_file, 'a') as f:
|
564
|
+
f.write(host_entry)
|
565
|
+
self.print_status(f"Added new host entry for {host_pattern}", True, 2)
|
456
566
|
|
457
567
|
# Set proper permissions on the config file
|
458
568
|
if platform.system() != 'Windows':
|
@@ -469,6 +579,81 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
469
579
|
self.print_status("Continuing setup despite SSH config issues", None, 2)
|
470
580
|
return True
|
471
581
|
return False
|
582
|
+
|
583
|
+
def _check_and_create_generic_config(self, current_config: str) -> Tuple[bool, str]:
|
584
|
+
"""Check if generic configuration exists and create it if needed.
|
585
|
+
|
586
|
+
Args:
|
587
|
+
current_config: Current SSH config content
|
588
|
+
|
589
|
+
Returns:
|
590
|
+
Tuple[bool, str]: Success flag, Updated configuration
|
591
|
+
"""
|
592
|
+
if self._check_config_exists("cloudx-*", current_config):
|
593
|
+
self.print_status("Found existing generic config for cloudx-*", True, 2)
|
594
|
+
return True, current_config
|
595
|
+
|
596
|
+
self.print_status("Creating generic config for cloudx-*", None, 2)
|
597
|
+
generic_config = self._build_generic_config()
|
598
|
+
|
599
|
+
# Append generic config to current config
|
600
|
+
updated_config = current_config
|
601
|
+
if updated_config and not updated_config.endswith('\n'):
|
602
|
+
updated_config += '\n'
|
603
|
+
updated_config += generic_config
|
604
|
+
|
605
|
+
return True, updated_config
|
606
|
+
|
607
|
+
def _check_and_create_environment_config(self, cloudx_env: str, current_config: str) -> Tuple[bool, str]:
|
608
|
+
"""Check if environment configuration exists and create it if needed.
|
609
|
+
|
610
|
+
Args:
|
611
|
+
cloudx_env: CloudX environment
|
612
|
+
current_config: Current SSH config content
|
613
|
+
|
614
|
+
Returns:
|
615
|
+
Tuple[bool, str]: Success flag, Updated configuration
|
616
|
+
"""
|
617
|
+
if self._check_config_exists(f"cloudx-{cloudx_env}-*", current_config):
|
618
|
+
self.print_status(f"Found existing config for cloudx-{cloudx_env}-*", True, 2)
|
619
|
+
|
620
|
+
# Option to override if needed
|
621
|
+
choice = self.prompt(
|
622
|
+
"Would you like to \n"
|
623
|
+
" 1: override the existing environment config\n"
|
624
|
+
" 2: keep existing environment config?\n"
|
625
|
+
"Select an option",
|
626
|
+
"2"
|
627
|
+
)
|
628
|
+
|
629
|
+
if choice == "1":
|
630
|
+
# Remove existing config for this environment
|
631
|
+
self.print_status("Removing existing environment configuration", None, 2)
|
632
|
+
env_config, remaining_config = self._extract_host_config(f"cloudx-{cloudx_env}-*", current_config)
|
633
|
+
|
634
|
+
# Create new environment config
|
635
|
+
env_config = self._build_environment_config(cloudx_env)
|
636
|
+
|
637
|
+
# Append new environment config to remaining config
|
638
|
+
updated_config = remaining_config
|
639
|
+
if updated_config and not updated_config.endswith('\n'):
|
640
|
+
updated_config += '\n'
|
641
|
+
updated_config += env_config
|
642
|
+
|
643
|
+
return True, updated_config
|
644
|
+
|
645
|
+
return True, current_config
|
646
|
+
|
647
|
+
self.print_status(f"Creating environment config for cloudx-{cloudx_env}-*", None, 2)
|
648
|
+
env_config = self._build_environment_config(cloudx_env)
|
649
|
+
|
650
|
+
# Append environment config to current config
|
651
|
+
updated_config = current_config
|
652
|
+
if updated_config and not updated_config.endswith('\n'):
|
653
|
+
updated_config += '\n'
|
654
|
+
updated_config += env_config
|
655
|
+
|
656
|
+
return True, updated_config
|
472
657
|
|
473
658
|
def _ensure_control_dir(self) -> bool:
|
474
659
|
"""Create SSH control directory with proper permissions.
|
@@ -496,38 +681,21 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
496
681
|
return False
|
497
682
|
|
498
683
|
def setup_ssh_config(self, cloudx_env: str, instance_id: str, hostname: str) -> bool:
|
499
|
-
"""Set up SSH config for the instance.
|
500
|
-
|
501
|
-
This method
|
502
|
-
1.
|
503
|
-
|
504
|
-
-
|
505
|
-
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
```
|
515
|
-
# Base environment config (created only once per environment)
|
516
|
-
Host cloudx-{env}-*
|
517
|
-
User ec2-user
|
518
|
-
IdentityAgent ~/.1password/agent.sock # If using 1Password
|
519
|
-
IdentityFile ~/.ssh/vscode/key.pub # .pub for 1Password, no .pub otherwise
|
520
|
-
IdentitiesOnly yes # If using 1Password
|
521
|
-
TCPKeepAlive yes
|
522
|
-
ControlMaster auto
|
523
|
-
ControlPath ~/.ssh/control/%r@%h:%p
|
524
|
-
ControlPersist 4h
|
525
|
-
ProxyCommand uvx cloudx-proxy connect %h %p --profile profile --aws-env env
|
526
|
-
|
527
|
-
# Host entries (added for each instance)
|
528
|
-
Host cloudx-{env}-hostname
|
529
|
-
HostName i-1234567890
|
530
|
-
```
|
684
|
+
"""Set up SSH config for the instance using a three-tier configuration approach.
|
685
|
+
|
686
|
+
This method implements a hierarchical SSH configuration with three levels:
|
687
|
+
1. Generic (cloudx-*): Common settings for all environments
|
688
|
+
- User settings
|
689
|
+
- TCP keepalive
|
690
|
+
- SSH multiplexing configuration
|
691
|
+
|
692
|
+
2. Environment (cloudx-{env}-*): Environment-specific settings
|
693
|
+
- Authentication configuration (identity settings)
|
694
|
+
- ProxyCommand with environment-specific parameters
|
695
|
+
|
696
|
+
3. Host (cloudx-{env}-hostname): Instance-specific settings
|
697
|
+
- HostName (instance ID)
|
698
|
+
- Optional overrides for incompatible settings
|
531
699
|
|
532
700
|
Args:
|
533
701
|
cloudx_env: CloudX environment (e.g., dev, prod)
|
@@ -538,80 +706,46 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
538
706
|
bool: True if config was set up successfully
|
539
707
|
"""
|
540
708
|
self.print_header("SSH Configuration")
|
541
|
-
self.print_status("Setting up SSH configuration...")
|
709
|
+
self.print_status("Setting up SSH configuration with three-tier approach...")
|
542
710
|
|
543
711
|
try:
|
544
|
-
# Check existing configuration
|
545
|
-
if self.ssh_config_file.exists():
|
546
|
-
current_config = self.ssh_config_file.read_text()
|
547
|
-
# Check if configuration for this environment already exists
|
548
|
-
if f"Host cloudx-{cloudx_env}-*" in current_config:
|
549
|
-
self.print_status(f"Found existing config for cloudx-{cloudx_env}-*", True, 2)
|
550
|
-
choice = self.prompt(
|
551
|
-
"Would you like to \n"
|
552
|
-
" 1: override the existing config\n"
|
553
|
-
" 2: add settings to the specific host entry?\n"
|
554
|
-
"Select an option",
|
555
|
-
"1"
|
556
|
-
)
|
557
|
-
if choice == "2":
|
558
|
-
# Add settings to specific host entry
|
559
|
-
self.print_status("Adding settings to specific host entry", None, 2)
|
560
|
-
return self._add_host_entry(cloudx_env, instance_id, hostname, current_config)
|
561
|
-
else:
|
562
|
-
# Remove existing config for this environment
|
563
|
-
self.print_status("Removing existing configuration", None, 2)
|
564
|
-
lines = current_config.splitlines()
|
565
|
-
new_lines = []
|
566
|
-
skip = False
|
567
|
-
for line in lines:
|
568
|
-
if line.strip() == f"Host cloudx-{cloudx_env}-*":
|
569
|
-
skip = True
|
570
|
-
elif skip and line.startswith("Host "):
|
571
|
-
skip = False
|
572
|
-
if not skip:
|
573
|
-
new_lines.append(line)
|
574
|
-
current_config = "\n".join(new_lines)
|
575
|
-
with open(self.ssh_config_file, 'w') as f:
|
576
|
-
f.write(current_config)
|
577
|
-
|
578
|
-
# Create base config
|
579
|
-
self.print_status(f"Creating new config for cloudx-{cloudx_env}-*", None, 2)
|
580
|
-
|
581
712
|
# Ensure control directory exists with proper permissions
|
582
713
|
if not self._ensure_control_dir():
|
583
714
|
return False
|
584
715
|
|
585
|
-
#
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
"""
|
716
|
+
# Initialize or read current configuration
|
717
|
+
current_config = ""
|
718
|
+
if self.ssh_config_file.exists():
|
719
|
+
current_config = self.ssh_config_file.read_text()
|
590
720
|
|
591
|
-
#
|
592
|
-
|
721
|
+
# 1. Check and create generic config (highest level)
|
722
|
+
self.print_status("Checking generic configuration...", None, 2)
|
723
|
+
success, current_config = self._check_and_create_generic_config(current_config)
|
724
|
+
if not success:
|
725
|
+
return False
|
593
726
|
|
594
|
-
#
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
727
|
+
# 2. Check and create environment config
|
728
|
+
self.print_status("Checking environment configuration...", None, 2)
|
729
|
+
success, current_config = self._check_and_create_environment_config(cloudx_env, current_config)
|
730
|
+
if not success:
|
731
|
+
return False
|
732
|
+
|
733
|
+
# Write the updated config with generic and environment tiers
|
734
|
+
self.ssh_config_file.parent.mkdir(parents=True, exist_ok=True)
|
735
|
+
self.ssh_config_file.write_text(current_config)
|
736
|
+
self.print_status("Generic and environment configurations created", True, 2)
|
601
737
|
|
602
738
|
# Set proper permissions on the config file
|
603
739
|
if platform.system() != 'Windows':
|
604
740
|
import stat
|
605
|
-
self.ssh_config_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions
|
741
|
+
self.ssh_config_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions
|
606
742
|
self.print_status("Set config file permissions to 600", True, 2)
|
607
|
-
|
608
|
-
# Add
|
609
|
-
self.print_status(f"Adding host entry for cloudx-{cloudx_env}-{hostname}", None, 2)
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
self.print_status("Host entry added", True, 2)
|
614
|
-
|
743
|
+
|
744
|
+
# 3. Add or update host entry (lowest level)
|
745
|
+
self.print_status(f"Adding/updating host entry for cloudx-{cloudx_env}-{hostname}", None, 2)
|
746
|
+
if not self._add_host_entry(cloudx_env, instance_id, hostname, current_config):
|
747
|
+
return False
|
748
|
+
|
615
749
|
# Handle system SSH config integration
|
616
750
|
system_config_path = Path(self.home_dir) / ".ssh" / "config"
|
617
751
|
|
@@ -622,8 +756,17 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
622
756
|
self.print_status(f"Created SSH directory: {ssh_parent_dir}", True, 2)
|
623
757
|
self._set_directory_permissions(ssh_parent_dir)
|
624
758
|
|
625
|
-
#
|
626
|
-
|
759
|
+
# Handle system config integration
|
760
|
+
same_file = False
|
761
|
+
if self.ssh_config_file.exists() and system_config_path.exists():
|
762
|
+
try:
|
763
|
+
same_file = self.ssh_config_file.samefile(system_config_path)
|
764
|
+
except:
|
765
|
+
same_file = str(self.ssh_config_file) == str(system_config_path)
|
766
|
+
else:
|
767
|
+
same_file = str(self.ssh_config_file) == str(system_config_path)
|
768
|
+
|
769
|
+
if same_file:
|
627
770
|
self.print_status("Using system SSH config directly, no Include needed", True, 2)
|
628
771
|
else:
|
629
772
|
# Otherwise, make sure the system config includes our config file
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: cloudx-proxy
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.4
|
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
|
@@ -158,22 +158,30 @@ The setup command configures SSH to use cloudX-proxy as a ProxyCommand, enabling
|
|
158
158
|
uvx cloudx-proxy setup --profile myprofile --ssh-key mykey
|
159
159
|
```
|
160
160
|
|
161
|
-
Will create a configuration like this:
|
161
|
+
Will create a three-tier configuration structure like this:
|
162
162
|
|
163
163
|
```
|
164
|
-
#
|
165
|
-
#
|
166
|
-
|
164
|
+
# Generic configuration (shared by all environments)
|
165
|
+
# Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
|
166
|
+
# Configuration type: generic
|
167
|
+
Host cloudx-*
|
167
168
|
User ec2-user
|
168
|
-
IdentityFile ~/.ssh/vscode/mykey
|
169
|
-
IdentitiesOnly yes
|
170
|
-
ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
|
171
169
|
TCPKeepAlive yes
|
172
170
|
ControlMaster auto
|
173
171
|
ControlPath ~/.ssh/control/%r@%h:%p
|
174
172
|
ControlPersist 4h
|
175
173
|
|
176
|
-
#
|
174
|
+
# Environment configuration (specific to a single environment)
|
175
|
+
# Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
|
176
|
+
# Configuration type: environment
|
177
|
+
Host cloudx-dev-*
|
178
|
+
IdentityFile ~/.ssh/vscode/mykey
|
179
|
+
IdentitiesOnly yes
|
180
|
+
ProxyCommand uvx cloudx-proxy connect %h %p --profile myprofile --ssh-key mykey
|
181
|
+
|
182
|
+
# Host configuration (specific to a single instance)
|
183
|
+
# Created by cloudx-proxy v1.0.0 on 2025-03-07 09:05:23
|
184
|
+
# Configuration type: host
|
177
185
|
Host cloudx-dev-myserver
|
178
186
|
HostName i-0123456789abcdef0
|
179
187
|
```
|
@@ -191,23 +199,30 @@ In these examples, ssh will use cloudx-proxy to connect to AWS with the `myprofi
|
|
191
199
|
VSCode will be able to connect to the instance using the same SSH configuration.
|
192
200
|
|
193
201
|
### SSH Configuration Details
|
194
|
-
The setup command creates
|
202
|
+
The setup command creates a hierarchical three-tier SSH configuration structure:
|
195
203
|
|
196
|
-
1.
|
197
|
-
- User
|
198
|
-
- 1Password SSH agent integration if selected
|
199
|
-
- ProxyCommand with appropriate parameters
|
200
|
-
- SSH multiplexing for better performance
|
204
|
+
1. Generic configuration (cloudx-*) containing common settings shared across all environments:
|
205
|
+
- User settings (ec2-user)
|
201
206
|
- TCP keepalive for connection stability
|
207
|
+
- SSH multiplexing for better performance (ControlMaster, ControlPath, ControlPersist)
|
202
208
|
|
203
|
-
2.
|
204
|
-
-
|
205
|
-
-
|
206
|
-
- Inherits all
|
209
|
+
2. Environment-specific configuration (cloudx-{env}-*) with:
|
210
|
+
- Authentication settings (IdentityFile, IdentityAgent for 1Password)
|
211
|
+
- ProxyCommand with environment-specific parameters
|
212
|
+
- Inherits all settings from the generic configuration
|
213
|
+
|
214
|
+
3. Host-specific entries (cloudx-{env}-hostname) with:
|
215
|
+
- Instance ID (HostName directive)
|
216
|
+
- Inherits all settings from both generic and environment configurations
|
217
|
+
|
218
|
+
Each configuration tier is clearly marked with a timestamp and version information comment, making it easy to track when and how configurations were created.
|
207
219
|
|
208
220
|
When adding new instances to an existing environment, you can choose to:
|
221
|
+
- Keep the existing environment configuration if it's compatible
|
209
222
|
- Override the environment configuration with new settings
|
210
|
-
- Add
|
223
|
+
- Add host-specific settings only
|
224
|
+
|
225
|
+
This three-tier structure offers better maintainability by reducing duplication and making it clear which settings apply broadly and which are specific to an environment or host.
|
211
226
|
|
212
227
|
### VSCode Configuration
|
213
228
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|