cloudx-proxy 0.2.0__py3-none-any.whl → 0.3.1__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/core.py +6 -7
- cloudx_proxy/setup.py +191 -83
- {cloudx_proxy-0.2.0.dist-info → cloudx_proxy-0.3.1.dist-info}/METADATA +1 -1
- cloudx_proxy-0.3.1.dist-info/RECORD +11 -0
- cloudx_proxy-0.2.0.dist-info/RECORD +0 -11
- {cloudx_proxy-0.2.0.dist-info → cloudx_proxy-0.3.1.dist-info}/LICENSE +0 -0
- {cloudx_proxy-0.2.0.dist-info → cloudx_proxy-0.3.1.dist-info}/WHEEL +0 -0
- {cloudx_proxy-0.2.0.dist-info → cloudx_proxy-0.3.1.dist-info}/entry_points.txt +0 -0
- {cloudx_proxy-0.2.0.dist-info → cloudx_proxy-0.3.1.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/core.py
CHANGED
@@ -7,7 +7,7 @@ from botocore.exceptions import ClientError
|
|
7
7
|
|
8
8
|
class CloudXClient:
|
9
9
|
def __init__(self, instance_id: str, port: int = 22, profile: str = "vscode",
|
10
|
-
region: str = None,
|
10
|
+
region: str = None, ssh_key: str = "vscode", aws_env: str = None):
|
11
11
|
"""Initialize CloudX client for SSH tunneling via AWS SSM.
|
12
12
|
|
13
13
|
Args:
|
@@ -15,7 +15,7 @@ class CloudXClient:
|
|
15
15
|
port: SSH port number (default: 22)
|
16
16
|
profile: AWS profile to use (default: "vscode")
|
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
20
|
"""
|
21
21
|
self.instance_id = instance_id
|
@@ -39,10 +39,9 @@ class CloudXClient:
|
|
39
39
|
self.ec2 = self.session.client('ec2')
|
40
40
|
self.ec2_connect = self.session.client('ec2-instance-connect')
|
41
41
|
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
self.public_key_path = Path(public_key_path)
|
42
|
+
# Set up SSH key path
|
43
|
+
self.ssh_dir = os.path.expanduser("~/.ssh/vscode")
|
44
|
+
self.ssh_key = os.path.join(self.ssh_dir, f"{ssh_key}.pub")
|
46
45
|
|
47
46
|
def log(self, message: str) -> None:
|
48
47
|
"""Log message to stderr to avoid interfering with SSH connection."""
|
@@ -88,7 +87,7 @@ class CloudXClient:
|
|
88
87
|
def push_ssh_key(self) -> bool:
|
89
88
|
"""Push SSH public key to instance via EC2 Instance Connect."""
|
90
89
|
try:
|
91
|
-
with open(self.
|
90
|
+
with open(self.ssh_key) as f:
|
92
91
|
public_key = f.read()
|
93
92
|
|
94
93
|
self.ec2_connect.send_ssh_public_key(
|
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,6 +24,15 @@ 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")
|
27
36
|
|
28
37
|
def print_status(self, message: str, status: bool = None, indent: int = 0) -> None:
|
29
38
|
"""Print a status message with optional checkmark/cross.
|
@@ -42,6 +51,23 @@ class CloudXSetup:
|
|
42
51
|
else:
|
43
52
|
print(f"{prefix}○ {message}")
|
44
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
|
70
|
+
|
45
71
|
def setup_aws_profile(self) -> bool:
|
46
72
|
"""Set up AWS profile using aws configure command.
|
47
73
|
|
@@ -57,54 +83,45 @@ class CloudXSetup:
|
|
57
83
|
os.environ["AWS_CONFIG_FILE"] = os.path.join(aws_env_dir, "config")
|
58
84
|
os.environ["AWS_SHARED_CREDENTIALS_FILE"] = os.path.join(aws_env_dir, "credentials")
|
59
85
|
|
60
|
-
#
|
61
|
-
|
86
|
+
# Try to create session with profile
|
87
|
+
try:
|
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)
|
103
|
+
|
104
|
+
# Verify the profile works
|
62
105
|
try:
|
63
106
|
identity = session.client('sts').get_caller_identity()
|
64
107
|
user_arn = identity['Arn']
|
65
108
|
|
66
|
-
|
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}
|
67
113
|
self.print_status(f"AWS profile '{self.profile}' exists and matches cloudX format", True, 2)
|
114
|
+
return True
|
68
115
|
else:
|
69
|
-
self.print_status(f"AWS profile
|
70
|
-
|
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
|
71
119
|
except ClientError:
|
72
|
-
self.print_status(
|
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
|
79
|
-
|
80
|
-
# Profile doesn't exist or is invalid, set it up
|
81
|
-
self.print_status("Setting up AWS profile...", None, 2)
|
82
|
-
print("Please enter your AWS credentials:")
|
83
|
-
|
84
|
-
# Use aws configure command
|
85
|
-
subprocess.run([
|
86
|
-
'aws', 'configure',
|
87
|
-
'--profile', self.profile
|
88
|
-
], check=True)
|
89
|
-
|
90
|
-
# Verify the profile works
|
91
|
-
session = boto3.Session(profile_name=self.profile)
|
92
|
-
identity = session.client('sts').get_caller_identity()
|
93
|
-
user_arn = identity['Arn']
|
94
|
-
|
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)
|
99
|
-
|
100
|
-
return True
|
120
|
+
self.print_status("Invalid AWS credentials", False, 2)
|
121
|
+
return False
|
101
122
|
|
102
123
|
except Exception as e:
|
103
|
-
self.print_status(f"
|
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
|
124
|
+
self.print_status(f"\033[1;91mError:\033[0m {str(e)}", False, 2)
|
108
125
|
return False
|
109
126
|
|
110
127
|
def setup_ssh_key(self) -> bool:
|
@@ -113,7 +130,8 @@ class CloudXSetup:
|
|
113
130
|
Returns:
|
114
131
|
bool: True if key was set up successfully
|
115
132
|
"""
|
116
|
-
self.
|
133
|
+
self.print_header("SSH Key Configuration")
|
134
|
+
self.print_status(f"Checking SSH key '{self.ssh_key}' configuration...")
|
117
135
|
|
118
136
|
try:
|
119
137
|
# Create .ssh/vscode directory if it doesn't exist
|
@@ -124,11 +142,11 @@ class CloudXSetup:
|
|
124
142
|
|
125
143
|
if key_exists:
|
126
144
|
self.print_status(f"SSH key '{self.ssh_key}' exists", True, 2)
|
127
|
-
self.using_1password =
|
145
|
+
self.using_1password = self.prompt("Would you like to use 1Password SSH agent?", "N").lower() == 'y'
|
128
146
|
if self.using_1password:
|
129
147
|
self.print_status("Using 1Password SSH agent", True, 2)
|
130
148
|
else:
|
131
|
-
store_in_1password =
|
149
|
+
store_in_1password = self.prompt("Would you like to store the private key in 1Password?", "N").lower() == 'y'
|
132
150
|
if store_in_1password:
|
133
151
|
if self._store_key_in_1password():
|
134
152
|
self.print_status("Private key stored in 1Password", True, 2)
|
@@ -144,11 +162,11 @@ class CloudXSetup:
|
|
144
162
|
], check=True)
|
145
163
|
self.print_status("SSH key generated", True, 2)
|
146
164
|
|
147
|
-
self.using_1password =
|
165
|
+
self.using_1password = self.prompt("Would you like to use 1Password SSH agent?", "N").lower() == 'y'
|
148
166
|
if self.using_1password:
|
149
167
|
self.print_status("Using 1Password SSH agent", True, 2)
|
150
168
|
else:
|
151
|
-
store_in_1password =
|
169
|
+
store_in_1password = self.prompt("Would you like to store the private key in 1Password?", "N").lower() == 'y'
|
152
170
|
if store_in_1password:
|
153
171
|
if self._store_key_in_1password():
|
154
172
|
self.print_status("Private key stored in 1Password", True, 2)
|
@@ -159,7 +177,7 @@ class CloudXSetup:
|
|
159
177
|
|
160
178
|
except Exception as e:
|
161
179
|
self.print_status(f"Error: {str(e)}", False, 2)
|
162
|
-
continue_setup =
|
180
|
+
continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
|
163
181
|
if continue_setup:
|
164
182
|
self.print_status("Continuing setup despite SSH key issues", None, 2)
|
165
183
|
return True
|
@@ -177,13 +195,65 @@ class CloudXSetup:
|
|
177
195
|
subprocess.run([
|
178
196
|
'op', 'document', 'create',
|
179
197
|
str(self.ssh_key_file),
|
180
|
-
'--title', f'
|
198
|
+
'--title', f'cloudx-proxy SSH Key - {self.ssh_key}'
|
181
199
|
], check=True)
|
182
200
|
return True
|
183
201
|
except subprocess.CalledProcessError:
|
184
202
|
print("Error: 1Password CLI not installed or not signed in.")
|
185
203
|
return False
|
186
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
|
+
|
187
257
|
def setup_ssh_config(self, cloudx_env: str, instance_id: str, hostname: str) -> bool:
|
188
258
|
"""Set up SSH config for the instance.
|
189
259
|
|
@@ -221,52 +291,78 @@ class CloudXSetup:
|
|
221
291
|
Returns:
|
222
292
|
bool: True if config was set up successfully
|
223
293
|
"""
|
294
|
+
self.print_header("SSH Configuration")
|
224
295
|
self.print_status("Setting up SSH configuration...")
|
225
296
|
|
226
297
|
try:
|
227
|
-
# Check
|
228
|
-
need_base_config = True
|
298
|
+
# Check existing configuration
|
229
299
|
if self.ssh_config_file.exists():
|
230
300
|
current_config = self.ssh_config_file.read_text()
|
231
301
|
# Check if configuration for this environment already exists
|
232
302
|
if f"Host cloudx-{cloudx_env}-*" in current_config:
|
233
|
-
need_base_config = False
|
234
303
|
self.print_status(f"Found existing config for cloudx-{cloudx_env}-*", True, 2)
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
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}"
|
244
340
|
|
245
|
-
|
246
|
-
|
341
|
+
# Build base configuration
|
342
|
+
base_config = f"""# cloudx-proxy SSH Configuration
|
247
343
|
Host cloudx-{cloudx_env}-*
|
248
344
|
User ec2-user
|
249
345
|
"""
|
250
|
-
|
251
|
-
|
252
|
-
|
346
|
+
# Add 1Password or standard key configuration
|
347
|
+
if self.using_1password:
|
348
|
+
base_config += f""" IdentityAgent ~/.1password/agent.sock
|
253
349
|
IdentityFile {self.ssh_key_file}.pub
|
254
350
|
IdentitiesOnly yes
|
255
351
|
"""
|
256
|
-
|
257
|
-
|
352
|
+
else:
|
353
|
+
base_config += f""" IdentityFile {self.ssh_key_file}
|
258
354
|
"""
|
259
|
-
|
260
|
-
|
355
|
+
# Add ProxyCommand
|
356
|
+
base_config += f""" ProxyCommand {proxy_command}
|
261
357
|
"""
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
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)
|
270
366
|
|
271
367
|
# Add specific host entry
|
272
368
|
self.print_status(f"Adding host entry for cloudx-{cloudx_env}-{hostname}", None, 2)
|
@@ -296,14 +392,14 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
296
392
|
|
297
393
|
self.print_status("\nSSH configuration summary:", None)
|
298
394
|
self.print_status(f"Main config: {main_config}", None, 2)
|
299
|
-
self.print_status(f"
|
395
|
+
self.print_status(f"cloudx-proxy config: {self.ssh_config_file}", None, 2)
|
300
396
|
self.print_status(f"Connect using: ssh cloudx-{cloudx_env}-{hostname}", None, 2)
|
301
397
|
|
302
398
|
return True
|
303
399
|
|
304
400
|
except Exception as e:
|
305
|
-
self.print_status(f"
|
306
|
-
continue_setup =
|
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'
|
307
403
|
if continue_setup:
|
308
404
|
self.print_status("Continuing setup despite SSH config issues", None, 2)
|
309
405
|
return True
|
@@ -323,15 +419,20 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
323
419
|
ssm = session.client('ssm')
|
324
420
|
|
325
421
|
# Check if instance is online in SSM
|
422
|
+
self.print_status("Checking instance status in SSM...", None, 4)
|
326
423
|
response = ssm.describe_instance_information(
|
327
424
|
Filters=[{'Key': 'InstanceIds', 'Values': [instance_id]}]
|
328
425
|
)
|
329
426
|
is_running = bool(response['InstanceInformationList'])
|
330
427
|
|
331
428
|
if not is_running:
|
429
|
+
self.print_status("Instance is not accessible via SSM", False, 4)
|
332
430
|
return False, False
|
333
431
|
|
432
|
+
self.print_status("Instance is accessible via SSM", True, 4)
|
433
|
+
|
334
434
|
# Check setup status using SSM command
|
435
|
+
self.print_status("Checking setup status...", None, 4)
|
335
436
|
response = ssm.send_command(
|
336
437
|
InstanceIds=[instance_id],
|
337
438
|
DocumentName='AWS-RunShellScript',
|
@@ -358,10 +459,16 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
358
459
|
|
359
460
|
is_setup_complete = result['Status'] == 'Success' and result['StandardOutputContent'].strip() == 'DONE'
|
360
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
|
+
|
361
468
|
return True, is_setup_complete
|
362
469
|
|
363
470
|
except Exception as e:
|
364
|
-
|
471
|
+
self.print_status(f"\033[1;91mError:\033[0m {str(e)}", False, 4)
|
365
472
|
return False, False
|
366
473
|
|
367
474
|
def wait_for_setup_completion(self, instance_id: str) -> bool:
|
@@ -373,13 +480,14 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
373
480
|
Returns:
|
374
481
|
bool: True if setup completed successfully
|
375
482
|
"""
|
483
|
+
self.print_header("Instance Setup Check")
|
376
484
|
self.print_status(f"Checking instance {instance_id} setup status...")
|
377
485
|
|
378
486
|
is_running, is_complete = self.check_instance_setup(instance_id)
|
379
487
|
|
380
488
|
if not is_running:
|
381
489
|
self.print_status("Instance is not running or not accessible via SSM", False, 2)
|
382
|
-
continue_setup =
|
490
|
+
continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
|
383
491
|
if continue_setup:
|
384
492
|
self.print_status("Continuing setup despite instance access issues", None, 2)
|
385
493
|
return True
|
@@ -389,7 +497,7 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
389
497
|
self.print_status("Instance setup is complete", True, 2)
|
390
498
|
return True
|
391
499
|
|
392
|
-
wait =
|
500
|
+
wait = self.prompt("Instance setup is not complete. Would you like to wait?", "Y").lower() != 'n'
|
393
501
|
if not wait:
|
394
502
|
self.print_status("Skipping instance setup check", None, 2)
|
395
503
|
return True
|
@@ -401,7 +509,7 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
401
509
|
|
402
510
|
if not is_running:
|
403
511
|
self.print_status("Instance is no longer running or accessible", False, 2)
|
404
|
-
continue_setup =
|
512
|
+
continue_setup = self.prompt("Would you like to continue anyway?", "Y").lower() != 'n'
|
405
513
|
if continue_setup:
|
406
514
|
self.print_status("Continuing setup despite instance issues", None, 2)
|
407
515
|
return True
|
@@ -0,0 +1,11 @@
|
|
1
|
+
cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
|
2
|
+
cloudx_proxy/_version.py,sha256=HzPz9rq3s1AiZXregKlqKaJJ2wGMtvH_a3V9la9CnpM,411
|
3
|
+
cloudx_proxy/cli.py,sha256=Ph-m8lDsdU2zZab9Y6YgBBzd_UDouBnfNrYFFx0bI_E,3426
|
4
|
+
cloudx_proxy/core.py,sha256=WjKoqMmmnt6e_4JMeq4gTka75JAvQcMUs9r9XUBLmFE,7289
|
5
|
+
cloudx_proxy/setup.py,sha256=Y8YYMJ47fb57FAr6llQaFGuVOQ-fstYEg_Pdv5uCd-A,22486
|
6
|
+
cloudx_proxy-0.3.1.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
|
7
|
+
cloudx_proxy-0.3.1.dist-info/METADATA,sha256=9IDcysHjzmeyFMs0ODmS6gfC98rXcCyj-Ug2RsJBxyI,10216
|
8
|
+
cloudx_proxy-0.3.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
9
|
+
cloudx_proxy-0.3.1.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
|
10
|
+
cloudx_proxy-0.3.1.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
|
11
|
+
cloudx_proxy-0.3.1.dist-info/RECORD,,
|
@@ -1,11 +0,0 @@
|
|
1
|
-
cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
|
2
|
-
cloudx_proxy/_version.py,sha256=H-qsvrxCpdhaQzyddR-yajEqI71hPxLa4KxzpP3uS1g,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=rGpH02DxM-UOT0_fY47BLyOD6IobmGvs_jOCgtGENqc,18009
|
6
|
-
cloudx_proxy-0.2.0.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
|
7
|
-
cloudx_proxy-0.2.0.dist-info/METADATA,sha256=vXyuzU2tmrQBhTRjlBFXcRYqLrfDv0AAymMz5nAvZsU,10216
|
8
|
-
cloudx_proxy-0.2.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
9
|
-
cloudx_proxy-0.2.0.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
|
10
|
-
cloudx_proxy-0.2.0.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
|
11
|
-
cloudx_proxy-0.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|