cloudx-proxy 0.1.1__tar.gz → 0.2.0__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.1.1 → cloudx_proxy-0.2.0}/CHANGELOG.md +7 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/PKG-INFO +1 -1
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/cloudx_proxy/_version.py +2 -2
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/cloudx_proxy/setup.py +108 -28
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/cloudx_proxy.egg-info/PKG-INFO +1 -1
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/.github/workflows/release.yml +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/.gitignore +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/.releaserc +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/CONTRIBUTING.md +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/LICENSE +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/README.md +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/cloudx_proxy/__init__.py +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/cloudx_proxy/cli.py +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/cloudx_proxy/core.py +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/cloudx_proxy.egg-info/SOURCES.txt +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/cloudx_proxy.egg-info/dependency_links.txt +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/cloudx_proxy.egg-info/entry_points.txt +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/cloudx_proxy.egg-info/requires.txt +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/cloudx_proxy.egg-info/top_level.txt +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/package.json +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/pyproject.toml +0 -0
- {cloudx_proxy-0.1.1 → cloudx_proxy-0.2.0}/setup.cfg +0 -0
@@ -1,3 +1,10 @@
|
|
1
|
+
# [0.2.0](https://github.com/easytocloud/cloudX-proxy/compare/v0.1.1...v0.2.0) (2025-02-09)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* add setup checklist and make all steps optional ([46016b8](https://github.com/easytocloud/cloudX-proxy/commit/46016b8fd7f1a1ae42fb34a7ff35365279883ab0))
|
7
|
+
|
1
8
|
## [0.1.1](https://github.com/easytocloud/cloudX-proxy/compare/v0.1.0...v0.1.1) (2025-02-09)
|
2
9
|
|
3
10
|
# Changelog
|
@@ -25,12 +25,31 @@ class CloudXSetup:
|
|
25
25
|
self.ssh_key_file = self.ssh_dir / f"{ssh_key}"
|
26
26
|
self.using_1password = False
|
27
27
|
|
28
|
+
def print_status(self, message: str, status: bool = None, indent: int = 0) -> None:
|
29
|
+
"""Print a status message with optional checkmark/cross.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
message: The message to print
|
33
|
+
status: True for success (✓), False for failure (✗), None for no symbol
|
34
|
+
indent: Number of spaces to indent
|
35
|
+
"""
|
36
|
+
prefix = " " * indent
|
37
|
+
if status is not None:
|
38
|
+
symbol = "✓" if status else "✗"
|
39
|
+
color = "\033[92m" if status else "\033[91m" # Green for success, red for failure
|
40
|
+
reset = "\033[0m"
|
41
|
+
print(f"{prefix}{color}{symbol}{reset} {message}")
|
42
|
+
else:
|
43
|
+
print(f"{prefix}○ {message}")
|
44
|
+
|
28
45
|
def setup_aws_profile(self) -> bool:
|
29
46
|
"""Set up AWS profile using aws configure command.
|
30
47
|
|
31
48
|
Returns:
|
32
|
-
bool: True if profile was set up successfully
|
49
|
+
bool: True if profile was set up successfully or user chose to continue
|
33
50
|
"""
|
51
|
+
self.print_status("Checking AWS profile configuration...")
|
52
|
+
|
34
53
|
try:
|
35
54
|
# Configure AWS environment if specified
|
36
55
|
if self.aws_env:
|
@@ -41,14 +60,25 @@ class CloudXSetup:
|
|
41
60
|
# Check if profile exists
|
42
61
|
session = boto3.Session(profile_name=self.profile)
|
43
62
|
try:
|
44
|
-
session.client('sts').get_caller_identity()
|
45
|
-
|
63
|
+
identity = session.client('sts').get_caller_identity()
|
64
|
+
user_arn = identity['Arn']
|
65
|
+
|
66
|
+
if any(part.startswith('cloudX-') for part in user_arn.split('/')):
|
67
|
+
self.print_status(f"AWS profile '{self.profile}' exists and matches cloudX format", True, 2)
|
68
|
+
else:
|
69
|
+
self.print_status(f"AWS profile '{self.profile}' exists but doesn't match cloudX-{{env}}-{{user}} format", False, 2)
|
46
70
|
return True
|
47
71
|
except ClientError:
|
48
|
-
|
72
|
+
self.print_status(f"AWS profile '{self.profile}' not found or invalid", False, 2)
|
73
|
+
|
74
|
+
# Ask user if they want to set up the profile
|
75
|
+
setup_profile = input(f"Would you like to set up AWS profile '{self.profile}'? (Y/n): ").lower() != 'n'
|
76
|
+
if not setup_profile:
|
77
|
+
self.print_status("Skipping AWS profile setup", None, 2)
|
78
|
+
return True
|
49
79
|
|
50
80
|
# Profile doesn't exist or is invalid, set it up
|
51
|
-
|
81
|
+
self.print_status("Setting up AWS profile...", None, 2)
|
52
82
|
print("Please enter your AWS credentials:")
|
53
83
|
|
54
84
|
# Use aws configure command
|
@@ -62,13 +92,19 @@ class CloudXSetup:
|
|
62
92
|
identity = session.client('sts').get_caller_identity()
|
63
93
|
user_arn = identity['Arn']
|
64
94
|
|
65
|
-
if
|
66
|
-
|
95
|
+
if any(part.startswith('cloudX-') for part in user_arn.split('/')):
|
96
|
+
self.print_status("AWS profile setup complete and matches cloudX format", True, 2)
|
97
|
+
else:
|
98
|
+
self.print_status("AWS profile setup complete but doesn't match cloudX-{env}-{user} format", False, 2)
|
67
99
|
|
68
100
|
return True
|
69
101
|
|
70
102
|
except Exception as e:
|
71
|
-
|
103
|
+
self.print_status(f"Error: {str(e)}", False, 2)
|
104
|
+
continue_setup = input("Would you like to continue anyway? (Y/n): ").lower() != 'n'
|
105
|
+
if continue_setup:
|
106
|
+
self.print_status("Continuing setup despite AWS profile issues", None, 2)
|
107
|
+
return True
|
72
108
|
return False
|
73
109
|
|
74
110
|
def setup_ssh_key(self) -> bool:
|
@@ -77,38 +113,56 @@ class CloudXSetup:
|
|
77
113
|
Returns:
|
78
114
|
bool: True if key was set up successfully
|
79
115
|
"""
|
116
|
+
self.print_status("Checking SSH key configuration...")
|
117
|
+
|
80
118
|
try:
|
81
119
|
# Create .ssh/vscode directory if it doesn't exist
|
82
120
|
self.ssh_dir.mkdir(parents=True, exist_ok=True)
|
121
|
+
self.print_status("SSH directory exists", True, 2)
|
83
122
|
|
84
123
|
key_exists = self.ssh_key_file.exists() and (self.ssh_key_file.with_suffix('.pub')).exists()
|
85
124
|
|
86
125
|
if key_exists:
|
87
|
-
|
126
|
+
self.print_status(f"SSH key '{self.ssh_key}' exists", True, 2)
|
88
127
|
self.using_1password = input("Would you like to use 1Password SSH agent? (y/N): ").lower() == 'y'
|
89
|
-
if
|
128
|
+
if self.using_1password:
|
129
|
+
self.print_status("Using 1Password SSH agent", True, 2)
|
130
|
+
else:
|
90
131
|
store_in_1password = input("Would you like to store the private key in 1Password? (y/N): ").lower() == 'y'
|
91
132
|
if store_in_1password:
|
92
|
-
self._store_key_in_1password()
|
133
|
+
if self._store_key_in_1password():
|
134
|
+
self.print_status("Private key stored in 1Password", True, 2)
|
135
|
+
else:
|
136
|
+
self.print_status("Failed to store private key in 1Password", False, 2)
|
93
137
|
else:
|
94
|
-
|
138
|
+
self.print_status(f"Generating new SSH key '{self.ssh_key}'...", None, 2)
|
95
139
|
subprocess.run([
|
96
140
|
'ssh-keygen',
|
97
141
|
'-t', 'ed25519',
|
98
142
|
'-f', str(self.ssh_key_file),
|
99
143
|
'-N', '' # Empty passphrase
|
100
144
|
], check=True)
|
145
|
+
self.print_status("SSH key generated", True, 2)
|
101
146
|
|
102
147
|
self.using_1password = input("Would you like to use 1Password SSH agent? (y/N): ").lower() == 'y'
|
103
|
-
if
|
148
|
+
if self.using_1password:
|
149
|
+
self.print_status("Using 1Password SSH agent", True, 2)
|
150
|
+
else:
|
104
151
|
store_in_1password = input("Would you like to store the private key in 1Password? (y/N): ").lower() == 'y'
|
105
152
|
if store_in_1password:
|
106
|
-
self._store_key_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)
|
107
157
|
|
108
158
|
return True
|
109
159
|
|
110
160
|
except Exception as e:
|
111
|
-
|
161
|
+
self.print_status(f"Error: {str(e)}", False, 2)
|
162
|
+
continue_setup = input("Would you like to continue anyway? (Y/n): ").lower() != 'n'
|
163
|
+
if continue_setup:
|
164
|
+
self.print_status("Continuing setup despite SSH key issues", None, 2)
|
165
|
+
return True
|
112
166
|
return False
|
113
167
|
|
114
168
|
def _store_key_in_1password(self) -> bool:
|
@@ -167,6 +221,8 @@ class CloudXSetup:
|
|
167
221
|
Returns:
|
168
222
|
bool: True if config was set up successfully
|
169
223
|
"""
|
224
|
+
self.print_status("Setting up SSH configuration...")
|
225
|
+
|
170
226
|
try:
|
171
227
|
# Check if we need to create base config
|
172
228
|
need_base_config = True
|
@@ -175,8 +231,10 @@ class CloudXSetup:
|
|
175
231
|
# Check if configuration for this environment already exists
|
176
232
|
if f"Host cloudx-{cloudx_env}-*" in current_config:
|
177
233
|
need_base_config = False
|
234
|
+
self.print_status(f"Found existing config for cloudx-{cloudx_env}-*", True, 2)
|
178
235
|
|
179
236
|
if need_base_config:
|
237
|
+
self.print_status(f"Creating new config for cloudx-{cloudx_env}-*", None, 2)
|
180
238
|
# Build ProxyCommand with all necessary parameters
|
181
239
|
proxy_command = f"uvx cloudx-proxy connect %h %p --profile {self.profile}"
|
182
240
|
if self.aws_env:
|
@@ -208,14 +266,17 @@ Host cloudx-{cloudx_env}-*
|
|
208
266
|
f.write("\n" + base_config)
|
209
267
|
else:
|
210
268
|
self.ssh_config_file.write_text(base_config)
|
269
|
+
self.print_status("Base configuration created", True, 2)
|
211
270
|
|
212
271
|
# Add specific host entry
|
272
|
+
self.print_status(f"Adding host entry for cloudx-{cloudx_env}-{hostname}", None, 2)
|
213
273
|
host_entry = f"""
|
214
274
|
Host cloudx-{cloudx_env}-{hostname}
|
215
275
|
HostName {instance_id}
|
216
276
|
"""
|
217
277
|
with open(self.ssh_config_file, 'a') as f:
|
218
278
|
f.write(host_entry)
|
279
|
+
self.print_status("Host entry added", True, 2)
|
219
280
|
|
220
281
|
# Ensure main SSH config includes our config
|
221
282
|
main_config = Path(self.home_dir) / ".ssh" / "config"
|
@@ -226,18 +287,26 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
226
287
|
if include_line not in content:
|
227
288
|
with open(main_config, 'a') as f:
|
228
289
|
f.write(f"\n{include_line}")
|
290
|
+
self.print_status("Added include line to main SSH config", True, 2)
|
291
|
+
else:
|
292
|
+
self.print_status("Main SSH config already includes our config", True, 2)
|
229
293
|
else:
|
230
294
|
main_config.write_text(include_line)
|
295
|
+
self.print_status("Created main SSH config with include line", True, 2)
|
231
296
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
297
|
+
self.print_status("\nSSH configuration summary:", None)
|
298
|
+
self.print_status(f"Main config: {main_config}", None, 2)
|
299
|
+
self.print_status(f"CloudX config: {self.ssh_config_file}", None, 2)
|
300
|
+
self.print_status(f"Connect using: ssh cloudx-{cloudx_env}-{hostname}", None, 2)
|
236
301
|
|
237
302
|
return True
|
238
303
|
|
239
304
|
except Exception as e:
|
240
|
-
|
305
|
+
self.print_status(f"Error: {str(e)}", False, 2)
|
306
|
+
continue_setup = input("Would you like to continue anyway? (Y/n): ").lower() != 'n'
|
307
|
+
if continue_setup:
|
308
|
+
self.print_status("Continuing setup despite SSH config issues", None, 2)
|
309
|
+
return True
|
241
310
|
return False
|
242
311
|
|
243
312
|
def check_instance_setup(self, instance_id: str) -> Tuple[bool, bool]:
|
@@ -304,33 +373,44 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
304
373
|
Returns:
|
305
374
|
bool: True if setup completed successfully
|
306
375
|
"""
|
307
|
-
|
376
|
+
self.print_status(f"Checking instance {instance_id} setup status...")
|
308
377
|
|
309
378
|
is_running, is_complete = self.check_instance_setup(instance_id)
|
310
379
|
|
311
380
|
if not is_running:
|
312
|
-
|
381
|
+
self.print_status("Instance is not running or not accessible via SSM", False, 2)
|
382
|
+
continue_setup = input("Would you like to continue anyway? (Y/n): ").lower() != 'n'
|
383
|
+
if continue_setup:
|
384
|
+
self.print_status("Continuing setup despite instance access issues", None, 2)
|
385
|
+
return True
|
313
386
|
return False
|
314
387
|
|
315
388
|
if is_complete:
|
316
|
-
|
389
|
+
self.print_status("Instance setup is complete", True, 2)
|
317
390
|
return True
|
318
391
|
|
319
392
|
wait = input("Instance setup is not complete. Would you like to wait? (Y/n): ").lower() != 'n'
|
320
393
|
if not wait:
|
321
|
-
|
394
|
+
self.print_status("Skipping instance setup check", None, 2)
|
395
|
+
return True
|
322
396
|
|
323
|
-
|
397
|
+
self.print_status("Waiting for setup to complete...", None, 2)
|
398
|
+
dots = 0
|
324
399
|
while True:
|
325
400
|
is_running, is_complete = self.check_instance_setup(instance_id)
|
326
401
|
|
327
402
|
if not is_running:
|
328
|
-
|
403
|
+
self.print_status("Instance is no longer running or accessible", False, 2)
|
404
|
+
continue_setup = input("Would you like to continue anyway? (Y/n): ").lower() != 'n'
|
405
|
+
if continue_setup:
|
406
|
+
self.print_status("Continuing setup despite instance issues", None, 2)
|
407
|
+
return True
|
329
408
|
return False
|
330
409
|
|
331
410
|
if is_complete:
|
332
|
-
|
411
|
+
self.print_status("Instance setup completed successfully", True, 2)
|
333
412
|
return True
|
334
413
|
|
335
|
-
|
414
|
+
dots = (dots + 1) % 4
|
415
|
+
print(f"\r {'.' * dots}{' ' * (3 - dots)}", end='', flush=True)
|
336
416
|
time.sleep(10)
|
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
|
File without changes
|