cloudx-proxy 0.1.1__py3-none-any.whl → 0.3.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 +2 -2
- cloudx_proxy/cli.py +10 -8
- cloudx_proxy/setup.py +270 -82
- {cloudx_proxy-0.1.1.dist-info → cloudx_proxy-0.3.0.dist-info}/METADATA +1 -1
- cloudx_proxy-0.3.0.dist-info/RECORD +11 -0
- cloudx_proxy-0.1.1.dist-info/RECORD +0 -11
- {cloudx_proxy-0.1.1.dist-info → cloudx_proxy-0.3.0.dist-info}/LICENSE +0 -0
- {cloudx_proxy-0.1.1.dist-info → cloudx_proxy-0.3.0.dist-info}/WHEEL +0 -0
- {cloudx_proxy-0.1.1.dist-info → cloudx_proxy-0.3.0.dist-info}/entry_points.txt +0 -0
- {cloudx_proxy-0.1.1.dist-info → cloudx_proxy-0.3.0.dist-info}/top_level.txt +0 -0
cloudx_proxy/_version.py
CHANGED
cloudx_proxy/cli.py
CHANGED
@@ -16,9 +16,9 @@ def cli():
|
|
16
16
|
@click.argument('port', type=int, default=22)
|
17
17
|
@click.option('--profile', default='vscode', help='AWS profile to use (default: vscode)')
|
18
18
|
@click.option('--region', help='AWS region (default: from profile, or eu-west-1 if not set)')
|
19
|
-
@click.option('--key
|
19
|
+
@click.option('--ssh-key', default='vscode', help='SSH key name to use (default: vscode)')
|
20
20
|
@click.option('--aws-env', help='AWS environment directory (default: ~/.aws, use name of directory in ~/.aws/aws-envs/)')
|
21
|
-
def connect(instance_id: str, port: int, profile: str, region: str,
|
21
|
+
def connect(instance_id: str, port: int, profile: str, region: str, ssh_key: str, aws_env: str):
|
22
22
|
"""Connect to an EC2 instance via SSM.
|
23
23
|
|
24
24
|
INSTANCE_ID is the EC2 instance ID to connect to (e.g., i-0123456789abcdef0)
|
@@ -34,7 +34,7 @@ def connect(instance_id: str, port: int, profile: str, region: str, key_path: st
|
|
34
34
|
port=port,
|
35
35
|
profile=profile,
|
36
36
|
region=region,
|
37
|
-
|
37
|
+
ssh_key=ssh_key,
|
38
38
|
aws_env=aws_env
|
39
39
|
)
|
40
40
|
|
@@ -65,6 +65,8 @@ def setup(profile: str, ssh_key: str, aws_env: str):
|
|
65
65
|
try:
|
66
66
|
setup = CloudXSetup(profile=profile, ssh_key=ssh_key, aws_env=aws_env)
|
67
67
|
|
68
|
+
print("\n\033[1;95m=== cloudx-proxy Setup ===\033[0m\n")
|
69
|
+
|
68
70
|
# Set up AWS profile
|
69
71
|
if not setup.setup_aws_profile():
|
70
72
|
sys.exit(1)
|
@@ -73,10 +75,10 @@ def setup(profile: str, ssh_key: str, aws_env: str):
|
|
73
75
|
if not setup.setup_ssh_key():
|
74
76
|
sys.exit(1)
|
75
77
|
|
76
|
-
# Get
|
77
|
-
cloudx_env =
|
78
|
-
instance_id =
|
79
|
-
hostname =
|
78
|
+
# Get environment and instance details
|
79
|
+
cloudx_env = setup.prompt("Enter environment", getattr(setup, 'default_env', None))
|
80
|
+
instance_id = setup.prompt("Enter EC2 instance ID (e.g., i-0123456789abcdef0)")
|
81
|
+
hostname = setup.prompt("Enter hostname for the instance")
|
80
82
|
|
81
83
|
# Set up SSH config
|
82
84
|
if not setup.setup_ssh_config(cloudx_env, instance_id, hostname):
|
@@ -86,7 +88,7 @@ def setup(profile: str, ssh_key: str, aws_env: str):
|
|
86
88
|
setup.wait_for_setup_completion(instance_id)
|
87
89
|
|
88
90
|
except Exception as e:
|
89
|
-
print(f"
|
91
|
+
print(f"\n\033[91mError: {str(e)}\033[0m", file=sys.stderr)
|
90
92
|
sys.exit(1)
|
91
93
|
|
92
94
|
if __name__ == '__main__':
|
cloudx_proxy/setup.py
CHANGED
@@ -9,7 +9,7 @@ from botocore.exceptions import ClientError
|
|
9
9
|
|
10
10
|
class CloudXSetup:
|
11
11
|
def __init__(self, profile: str = "vscode", ssh_key: str = "vscode", aws_env: str = None):
|
12
|
-
"""Initialize
|
12
|
+
"""Initialize cloudx-proxy setup.
|
13
13
|
|
14
14
|
Args:
|
15
15
|
profile: AWS profile name (default: "vscode")
|
@@ -24,13 +24,58 @@ class CloudXSetup:
|
|
24
24
|
self.ssh_config_file = self.ssh_dir / "config"
|
25
25
|
self.ssh_key_file = self.ssh_dir / f"{ssh_key}"
|
26
26
|
self.using_1password = False
|
27
|
+
self.default_env = None
|
28
|
+
|
29
|
+
def print_header(self, text: str) -> None:
|
30
|
+
"""Print a section header.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
text: The header text
|
34
|
+
"""
|
35
|
+
print(f"\n\n\033[1;94m=== {text} ===\033[0m")
|
36
|
+
|
37
|
+
def print_status(self, message: str, status: bool = None, indent: int = 0) -> None:
|
38
|
+
"""Print a status message with optional checkmark/cross.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
message: The message to print
|
42
|
+
status: True for success (✓), False for failure (✗), None for no symbol
|
43
|
+
indent: Number of spaces to indent
|
44
|
+
"""
|
45
|
+
prefix = " " * indent
|
46
|
+
if status is not None:
|
47
|
+
symbol = "✓" if status else "✗"
|
48
|
+
color = "\033[92m" if status else "\033[91m" # Green for success, red for failure
|
49
|
+
reset = "\033[0m"
|
50
|
+
print(f"{prefix}{color}{symbol}{reset} {message}")
|
51
|
+
else:
|
52
|
+
print(f"{prefix}○ {message}")
|
53
|
+
|
54
|
+
def prompt(self, message: str, default: str = None) -> str:
|
55
|
+
"""Display a colored prompt for user input.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
message: The prompt message
|
59
|
+
default: Default value (shown in brackets)
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
str: User's input or default value
|
63
|
+
"""
|
64
|
+
if default:
|
65
|
+
prompt_text = f"\033[93m{message} [{default}]: \033[0m"
|
66
|
+
else:
|
67
|
+
prompt_text = f"\033[93m{message}: \033[0m"
|
68
|
+
response = input(prompt_text)
|
69
|
+
return response if response else default
|
27
70
|
|
28
71
|
def setup_aws_profile(self) -> bool:
|
29
72
|
"""Set up AWS profile using aws configure command.
|
30
73
|
|
31
74
|
Returns:
|
32
|
-
bool: True if profile was set up successfully
|
75
|
+
bool: True if profile was set up successfully or user chose to continue
|
33
76
|
"""
|
77
|
+
self.print_status("Checking AWS profile configuration...")
|
78
|
+
|
34
79
|
try:
|
35
80
|
# Configure AWS environment if specified
|
36
81
|
if self.aws_env:
|
@@ -38,37 +83,45 @@ class CloudXSetup:
|
|
38
83
|
os.environ["AWS_CONFIG_FILE"] = os.path.join(aws_env_dir, "config")
|
39
84
|
os.environ["AWS_SHARED_CREDENTIALS_FILE"] = os.path.join(aws_env_dir, "credentials")
|
40
85
|
|
41
|
-
#
|
42
|
-
session = boto3.Session(profile_name=self.profile)
|
86
|
+
# Try to create session with profile
|
43
87
|
try:
|
44
|
-
session.
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
88
|
+
session = boto3.Session(profile_name=self.profile)
|
89
|
+
except:
|
90
|
+
# Profile doesn't exist, create it
|
91
|
+
self.print_status(f"AWS profile '{self.profile}' not found", False, 2)
|
92
|
+
self.print_status("Setting up AWS profile...", None, 2)
|
93
|
+
print("\033[96mPlease enter your AWS credentials:\033[0m")
|
94
|
+
|
95
|
+
# Use aws configure command
|
96
|
+
subprocess.run([
|
97
|
+
'aws', 'configure',
|
98
|
+
'--profile', self.profile
|
99
|
+
], check=True)
|
100
|
+
|
101
|
+
# Create new session with configured profile
|
102
|
+
session = boto3.Session(profile_name=self.profile)
|
59
103
|
|
60
104
|
# Verify the profile works
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
105
|
+
try:
|
106
|
+
identity = session.client('sts').get_caller_identity()
|
107
|
+
user_arn = identity['Arn']
|
108
|
+
|
109
|
+
# Extract environment from IAM user name
|
110
|
+
user_parts = [part for part in user_arn.split('/') if part.startswith('cloudX-')]
|
111
|
+
if user_parts:
|
112
|
+
self.default_env = user_parts[0].split('-')[1] # Extract env from cloudX-{env}-{user}
|
113
|
+
self.print_status(f"AWS profile '{self.profile}' exists and matches cloudX format", True, 2)
|
114
|
+
return True
|
115
|
+
else:
|
116
|
+
self.print_status(f"AWS profile exists but doesn't match cloudX-{{env}}-{{user}} format", False, 2)
|
117
|
+
self.print_status("Please ensure your IAM user follows the format: cloudX-{env}-{username}", None, 2)
|
118
|
+
return False
|
119
|
+
except ClientError:
|
120
|
+
self.print_status("Invalid AWS credentials", False, 2)
|
121
|
+
return False
|
69
122
|
|
70
123
|
except Exception as e:
|
71
|
-
|
124
|
+
self.print_status(f"\033[1;91mError:\033[0m {str(e)}", False, 2)
|
72
125
|
return False
|
73
126
|
|
74
127
|
def setup_ssh_key(self) -> bool:
|
@@ -77,38 +130,57 @@ class CloudXSetup:
|
|
77
130
|
Returns:
|
78
131
|
bool: True if key was set up successfully
|
79
132
|
"""
|
133
|
+
self.print_header("SSH Key Configuration")
|
134
|
+
self.print_status(f"Checking SSH key '{self.ssh_key}' configuration...")
|
135
|
+
|
80
136
|
try:
|
81
137
|
# Create .ssh/vscode directory if it doesn't exist
|
82
138
|
self.ssh_dir.mkdir(parents=True, exist_ok=True)
|
139
|
+
self.print_status("SSH directory exists", True, 2)
|
83
140
|
|
84
141
|
key_exists = self.ssh_key_file.exists() and (self.ssh_key_file.with_suffix('.pub')).exists()
|
85
142
|
|
86
143
|
if key_exists:
|
87
|
-
|
88
|
-
self.using_1password =
|
89
|
-
if
|
90
|
-
|
144
|
+
self.print_status(f"SSH key '{self.ssh_key}' exists", True, 2)
|
145
|
+
self.using_1password = self.prompt("Would you like to use 1Password SSH agent?", "N").lower() == 'y'
|
146
|
+
if self.using_1password:
|
147
|
+
self.print_status("Using 1Password SSH agent", True, 2)
|
148
|
+
else:
|
149
|
+
store_in_1password = self.prompt("Would you like to store the private key in 1Password?", "N").lower() == 'y'
|
91
150
|
if store_in_1password:
|
92
|
-
self._store_key_in_1password()
|
151
|
+
if self._store_key_in_1password():
|
152
|
+
self.print_status("Private key stored in 1Password", True, 2)
|
153
|
+
else:
|
154
|
+
self.print_status("Failed to store private key in 1Password", False, 2)
|
93
155
|
else:
|
94
|
-
|
156
|
+
self.print_status(f"Generating new SSH key '{self.ssh_key}'...", None, 2)
|
95
157
|
subprocess.run([
|
96
158
|
'ssh-keygen',
|
97
159
|
'-t', 'ed25519',
|
98
160
|
'-f', str(self.ssh_key_file),
|
99
161
|
'-N', '' # Empty passphrase
|
100
162
|
], check=True)
|
163
|
+
self.print_status("SSH key generated", True, 2)
|
101
164
|
|
102
|
-
self.using_1password =
|
103
|
-
if
|
104
|
-
|
165
|
+
self.using_1password = self.prompt("Would you like to use 1Password SSH agent?", "N").lower() == 'y'
|
166
|
+
if self.using_1password:
|
167
|
+
self.print_status("Using 1Password SSH agent", True, 2)
|
168
|
+
else:
|
169
|
+
store_in_1password = self.prompt("Would you like to store the private key in 1Password?", "N").lower() == 'y'
|
105
170
|
if store_in_1password:
|
106
|
-
self._store_key_in_1password()
|
171
|
+
if self._store_key_in_1password():
|
172
|
+
self.print_status("Private key stored in 1Password", True, 2)
|
173
|
+
else:
|
174
|
+
self.print_status("Failed to store private key in 1Password", False, 2)
|
107
175
|
|
108
176
|
return True
|
109
177
|
|
110
178
|
except Exception as e:
|
111
|
-
|
179
|
+
self.print_status(f"Error: {str(e)}", False, 2)
|
180
|
+
continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
|
181
|
+
if continue_setup:
|
182
|
+
self.print_status("Continuing setup despite SSH key issues", None, 2)
|
183
|
+
return True
|
112
184
|
return False
|
113
185
|
|
114
186
|
def _store_key_in_1password(self) -> bool:
|
@@ -123,13 +195,65 @@ class CloudXSetup:
|
|
123
195
|
subprocess.run([
|
124
196
|
'op', 'document', 'create',
|
125
197
|
str(self.ssh_key_file),
|
126
|
-
'--title', f'
|
198
|
+
'--title', f'cloudx-proxy SSH Key - {self.ssh_key}'
|
127
199
|
], check=True)
|
128
200
|
return True
|
129
201
|
except subprocess.CalledProcessError:
|
130
202
|
print("Error: 1Password CLI not installed or not signed in.")
|
131
203
|
return False
|
132
204
|
|
205
|
+
def _add_host_entry(self, cloudx_env: str, instance_id: str, hostname: str, current_config: str) -> bool:
|
206
|
+
"""Add settings to a specific host entry.
|
207
|
+
|
208
|
+
Args:
|
209
|
+
cloudx_env: CloudX environment
|
210
|
+
instance_id: EC2 instance ID
|
211
|
+
hostname: Hostname for the instance
|
212
|
+
current_config: Current SSH config content
|
213
|
+
|
214
|
+
Returns:
|
215
|
+
bool: True if settings were added successfully
|
216
|
+
"""
|
217
|
+
try:
|
218
|
+
# Build host entry with all settings
|
219
|
+
proxy_command = "uvx cloudx-proxy connect %h %p"
|
220
|
+
if self.profile != "vscode":
|
221
|
+
proxy_command += f" --profile {self.profile}"
|
222
|
+
if self.aws_env:
|
223
|
+
proxy_command += f" --aws-env {self.aws_env}"
|
224
|
+
if self.ssh_key != "vscode":
|
225
|
+
proxy_command += f" --ssh-key {self.ssh_key}"
|
226
|
+
|
227
|
+
host_entry = f"""
|
228
|
+
Host cloudx-{cloudx_env}-{hostname}
|
229
|
+
HostName {instance_id}
|
230
|
+
User ec2-user
|
231
|
+
"""
|
232
|
+
if self.using_1password:
|
233
|
+
host_entry += f""" IdentityAgent ~/.1password/agent.sock
|
234
|
+
IdentityFile {self.ssh_key_file}.pub
|
235
|
+
IdentitiesOnly yes
|
236
|
+
"""
|
237
|
+
else:
|
238
|
+
host_entry += f""" IdentityFile {self.ssh_key_file}
|
239
|
+
"""
|
240
|
+
host_entry += f""" ProxyCommand {proxy_command}
|
241
|
+
"""
|
242
|
+
|
243
|
+
# Append host entry
|
244
|
+
with open(self.ssh_config_file, 'a') as f:
|
245
|
+
f.write(host_entry)
|
246
|
+
self.print_status("Host entry added with settings", True, 2)
|
247
|
+
return True
|
248
|
+
|
249
|
+
except Exception as e:
|
250
|
+
self.print_status(f"\033[1;91mError:\033[0m {str(e)}", False, 2)
|
251
|
+
continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
|
252
|
+
if continue_setup:
|
253
|
+
self.print_status("Continuing setup despite SSH config issues", None, 2)
|
254
|
+
return True
|
255
|
+
return False
|
256
|
+
|
133
257
|
def setup_ssh_config(self, cloudx_env: str, instance_id: str, hostname: str) -> bool:
|
134
258
|
"""Set up SSH config for the instance.
|
135
259
|
|
@@ -167,55 +291,88 @@ class CloudXSetup:
|
|
167
291
|
Returns:
|
168
292
|
bool: True if config was set up successfully
|
169
293
|
"""
|
294
|
+
self.print_header("SSH Configuration")
|
295
|
+
self.print_status("Setting up SSH configuration...")
|
296
|
+
|
170
297
|
try:
|
171
|
-
# Check
|
172
|
-
need_base_config = True
|
298
|
+
# Check existing configuration
|
173
299
|
if self.ssh_config_file.exists():
|
174
300
|
current_config = self.ssh_config_file.read_text()
|
175
301
|
# Check if configuration for this environment already exists
|
176
302
|
if f"Host cloudx-{cloudx_env}-*" in current_config:
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
303
|
+
self.print_status(f"Found existing config for cloudx-{cloudx_env}-*", True, 2)
|
304
|
+
choice = self.prompt(
|
305
|
+
"Would you like to (1) override the existing config or "
|
306
|
+
"(2) add settings to the specific host entry?",
|
307
|
+
"1"
|
308
|
+
)
|
309
|
+
if choice == "2":
|
310
|
+
# Add settings to specific host entry
|
311
|
+
self.print_status("Adding settings to specific host entry", None, 2)
|
312
|
+
return self._add_host_entry(cloudx_env, instance_id, hostname, current_config)
|
313
|
+
else:
|
314
|
+
# Remove existing config for this environment
|
315
|
+
self.print_status("Removing existing configuration", None, 2)
|
316
|
+
lines = current_config.splitlines()
|
317
|
+
new_lines = []
|
318
|
+
skip = False
|
319
|
+
for line in lines:
|
320
|
+
if line.strip() == f"Host cloudx-{cloudx_env}-*":
|
321
|
+
skip = True
|
322
|
+
elif skip and line.startswith("Host "):
|
323
|
+
skip = False
|
324
|
+
if not skip:
|
325
|
+
new_lines.append(line)
|
326
|
+
current_config = "\n".join(new_lines)
|
327
|
+
with open(self.ssh_config_file, 'w') as f:
|
328
|
+
f.write(current_config)
|
329
|
+
|
330
|
+
# Create base config
|
331
|
+
self.print_status(f"Creating new config for cloudx-{cloudx_env}-*", None, 2)
|
332
|
+
# Build ProxyCommand with only non-default parameters
|
333
|
+
proxy_command = "uvx cloudx-proxy connect %h %p"
|
334
|
+
if self.profile != "vscode":
|
335
|
+
proxy_command += f" --profile {self.profile}"
|
336
|
+
if self.aws_env:
|
337
|
+
proxy_command += f" --aws-env {self.aws_env}"
|
338
|
+
if self.ssh_key != "vscode":
|
339
|
+
proxy_command += f" --ssh-key {self.ssh_key}"
|
186
340
|
|
187
|
-
|
188
|
-
|
341
|
+
# Build base configuration
|
342
|
+
base_config = f"""# cloudx-proxy SSH Configuration
|
189
343
|
Host cloudx-{cloudx_env}-*
|
190
344
|
User ec2-user
|
191
345
|
"""
|
192
|
-
|
193
|
-
|
194
|
-
|
346
|
+
# Add 1Password or standard key configuration
|
347
|
+
if self.using_1password:
|
348
|
+
base_config += f""" IdentityAgent ~/.1password/agent.sock
|
195
349
|
IdentityFile {self.ssh_key_file}.pub
|
196
350
|
IdentitiesOnly yes
|
197
351
|
"""
|
198
|
-
|
199
|
-
|
352
|
+
else:
|
353
|
+
base_config += f""" IdentityFile {self.ssh_key_file}
|
200
354
|
"""
|
201
|
-
|
202
|
-
|
355
|
+
# Add ProxyCommand
|
356
|
+
base_config += f""" ProxyCommand {proxy_command}
|
203
357
|
"""
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
358
|
+
|
359
|
+
# If file exists, append the new config, otherwise create it
|
360
|
+
if self.ssh_config_file.exists():
|
361
|
+
with open(self.ssh_config_file, 'a') as f:
|
362
|
+
f.write("\n" + base_config)
|
363
|
+
else:
|
364
|
+
self.ssh_config_file.write_text(base_config)
|
365
|
+
self.print_status("Base configuration created", True, 2)
|
211
366
|
|
212
367
|
# Add specific host entry
|
368
|
+
self.print_status(f"Adding host entry for cloudx-{cloudx_env}-{hostname}", None, 2)
|
213
369
|
host_entry = f"""
|
214
370
|
Host cloudx-{cloudx_env}-{hostname}
|
215
371
|
HostName {instance_id}
|
216
372
|
"""
|
217
373
|
with open(self.ssh_config_file, 'a') as f:
|
218
374
|
f.write(host_entry)
|
375
|
+
self.print_status("Host entry added", True, 2)
|
219
376
|
|
220
377
|
# Ensure main SSH config includes our config
|
221
378
|
main_config = Path(self.home_dir) / ".ssh" / "config"
|
@@ -226,18 +383,26 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
226
383
|
if include_line not in content:
|
227
384
|
with open(main_config, 'a') as f:
|
228
385
|
f.write(f"\n{include_line}")
|
386
|
+
self.print_status("Added include line to main SSH config", True, 2)
|
387
|
+
else:
|
388
|
+
self.print_status("Main SSH config already includes our config", True, 2)
|
229
389
|
else:
|
230
390
|
main_config.write_text(include_line)
|
391
|
+
self.print_status("Created main SSH config with include line", True, 2)
|
231
392
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
393
|
+
self.print_status("\nSSH configuration summary:", None)
|
394
|
+
self.print_status(f"Main config: {main_config}", None, 2)
|
395
|
+
self.print_status(f"cloudx-proxy config: {self.ssh_config_file}", None, 2)
|
396
|
+
self.print_status(f"Connect using: ssh cloudx-{cloudx_env}-{hostname}", None, 2)
|
236
397
|
|
237
398
|
return True
|
238
399
|
|
239
400
|
except Exception as e:
|
240
|
-
|
401
|
+
self.print_status(f"\033[1;91mError:\033[0m {str(e)}", False, 2)
|
402
|
+
continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
|
403
|
+
if continue_setup:
|
404
|
+
self.print_status("Continuing setup despite SSH config issues", None, 2)
|
405
|
+
return True
|
241
406
|
return False
|
242
407
|
|
243
408
|
def check_instance_setup(self, instance_id: str) -> Tuple[bool, bool]:
|
@@ -254,15 +419,20 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
254
419
|
ssm = session.client('ssm')
|
255
420
|
|
256
421
|
# Check if instance is online in SSM
|
422
|
+
self.print_status("Checking instance status in SSM...", None, 4)
|
257
423
|
response = ssm.describe_instance_information(
|
258
424
|
Filters=[{'Key': 'InstanceIds', 'Values': [instance_id]}]
|
259
425
|
)
|
260
426
|
is_running = bool(response['InstanceInformationList'])
|
261
427
|
|
262
428
|
if not is_running:
|
429
|
+
self.print_status("Instance is not accessible via SSM", False, 4)
|
263
430
|
return False, False
|
264
431
|
|
432
|
+
self.print_status("Instance is accessible via SSM", True, 4)
|
433
|
+
|
265
434
|
# Check setup status using SSM command
|
435
|
+
self.print_status("Checking setup status...", None, 4)
|
266
436
|
response = ssm.send_command(
|
267
437
|
InstanceIds=[instance_id],
|
268
438
|
DocumentName='AWS-RunShellScript',
|
@@ -289,10 +459,16 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
289
459
|
|
290
460
|
is_setup_complete = result['Status'] == 'Success' and result['StandardOutputContent'].strip() == 'DONE'
|
291
461
|
|
462
|
+
if is_setup_complete:
|
463
|
+
self.print_status("Setup is complete", True, 4)
|
464
|
+
else:
|
465
|
+
status = result['StandardOutputContent'].strip()
|
466
|
+
self.print_status(f"Setup status: {status}", None, 4)
|
467
|
+
|
292
468
|
return True, is_setup_complete
|
293
469
|
|
294
470
|
except Exception as e:
|
295
|
-
|
471
|
+
self.print_status(f"\033[1;91mError:\033[0m {str(e)}", False, 4)
|
296
472
|
return False, False
|
297
473
|
|
298
474
|
def wait_for_setup_completion(self, instance_id: str) -> bool:
|
@@ -304,33 +480,45 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
304
480
|
Returns:
|
305
481
|
bool: True if setup completed successfully
|
306
482
|
"""
|
307
|
-
|
483
|
+
self.print_header("Instance Setup Check")
|
484
|
+
self.print_status(f"Checking instance {instance_id} setup status...")
|
308
485
|
|
309
486
|
is_running, is_complete = self.check_instance_setup(instance_id)
|
310
487
|
|
311
488
|
if not is_running:
|
312
|
-
|
489
|
+
self.print_status("Instance is not running or not accessible via SSM", False, 2)
|
490
|
+
continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
|
491
|
+
if continue_setup:
|
492
|
+
self.print_status("Continuing setup despite instance access issues", None, 2)
|
493
|
+
return True
|
313
494
|
return False
|
314
495
|
|
315
496
|
if is_complete:
|
316
|
-
|
497
|
+
self.print_status("Instance setup is complete", True, 2)
|
317
498
|
return True
|
318
499
|
|
319
|
-
wait =
|
500
|
+
wait = self.prompt("Instance setup is not complete. Would you like to wait?", "Y").lower() != 'n'
|
320
501
|
if not wait:
|
321
|
-
|
502
|
+
self.print_status("Skipping instance setup check", None, 2)
|
503
|
+
return True
|
322
504
|
|
323
|
-
|
505
|
+
self.print_status("Waiting for setup to complete...", None, 2)
|
506
|
+
dots = 0
|
324
507
|
while True:
|
325
508
|
is_running, is_complete = self.check_instance_setup(instance_id)
|
326
509
|
|
327
510
|
if not is_running:
|
328
|
-
|
511
|
+
self.print_status("Instance is no longer running or accessible", False, 2)
|
512
|
+
continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
|
513
|
+
if continue_setup:
|
514
|
+
self.print_status("Continuing setup despite instance issues", None, 2)
|
515
|
+
return True
|
329
516
|
return False
|
330
517
|
|
331
518
|
if is_complete:
|
332
|
-
|
519
|
+
self.print_status("Instance setup completed successfully", True, 2)
|
333
520
|
return True
|
334
521
|
|
335
|
-
|
522
|
+
dots = (dots + 1) % 4
|
523
|
+
print(f"\r {'.' * dots}{' ' * (3 - dots)}", end='', flush=True)
|
336
524
|
time.sleep(10)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
|
2
|
+
cloudx_proxy/_version.py,sha256=Jk2iAU7m-7Vx9XV1TtdD9ZoJraIncDq_4_Wd-qtUotg,411
|
3
|
+
cloudx_proxy/cli.py,sha256=Ph-m8lDsdU2zZab9Y6YgBBzd_UDouBnfNrYFFx0bI_E,3426
|
4
|
+
cloudx_proxy/core.py,sha256=j6CUKdg2Ikcoi-05ceXMGA_c1aGWBhN9_JevbkLkaUY,7383
|
5
|
+
cloudx_proxy/setup.py,sha256=Y8YYMJ47fb57FAr6llQaFGuVOQ-fstYEg_Pdv5uCd-A,22486
|
6
|
+
cloudx_proxy-0.3.0.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
|
7
|
+
cloudx_proxy-0.3.0.dist-info/METADATA,sha256=A2WZ_EGsVVB-DadxbUYDy1oeUVaSoArx3IuePpdv0DQ,10216
|
8
|
+
cloudx_proxy-0.3.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
9
|
+
cloudx_proxy-0.3.0.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
|
10
|
+
cloudx_proxy-0.3.0.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
|
11
|
+
cloudx_proxy-0.3.0.dist-info/RECORD,,
|
@@ -1,11 +0,0 @@
|
|
1
|
-
cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
|
2
|
-
cloudx_proxy/_version.py,sha256=PKIMyjdUACH4-ONvtunQCnYE2UhlMfp9su83e3HXl5E,411
|
3
|
-
cloudx_proxy/cli.py,sha256=zqOIynqNr76JE6VRF7ifn3Fr0Z5C6CjnAHQgupF30BU,3374
|
4
|
-
cloudx_proxy/core.py,sha256=j6CUKdg2Ikcoi-05ceXMGA_c1aGWBhN9_JevbkLkaUY,7383
|
5
|
-
cloudx_proxy/setup.py,sha256=5GGfPgIiEWwkFEY-HnnPvT_8iD94WqjoFjJ5tGSHz8o,13157
|
6
|
-
cloudx_proxy-0.1.1.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
|
7
|
-
cloudx_proxy-0.1.1.dist-info/METADATA,sha256=tBwYeyqWuPOsREgWQylDRn6je6FZ1REbBQu5Pgk6I1s,10216
|
8
|
-
cloudx_proxy-0.1.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
9
|
-
cloudx_proxy-0.1.1.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
|
10
|
-
cloudx_proxy-0.1.1.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
|
11
|
-
cloudx_proxy-0.1.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|