cloudx-proxy 0.3.15__py3-none-any.whl → 0.4.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 -2
- cloudx_proxy/core.py +14 -2
- cloudx_proxy/setup.py +222 -48
- {cloudx_proxy-0.3.15.dist-info → cloudx_proxy-0.4.1.dist-info}/METADATA +1 -1
- cloudx_proxy-0.4.1.dist-info/RECORD +11 -0
- cloudx_proxy-0.3.15.dist-info/RECORD +0 -11
- {cloudx_proxy-0.3.15.dist-info → cloudx_proxy-0.4.1.dist-info}/LICENSE +0 -0
- {cloudx_proxy-0.3.15.dist-info → cloudx_proxy-0.4.1.dist-info}/WHEEL +0 -0
- {cloudx_proxy-0.3.15.dist-info → cloudx_proxy-0.4.1.dist-info}/entry_points.txt +0 -0
- {cloudx_proxy-0.3.15.dist-info → cloudx_proxy-0.4.1.dist-info}/top_level.txt +0 -0
cloudx_proxy/_version.py
CHANGED
cloudx_proxy/cli.py
CHANGED
@@ -52,7 +52,8 @@ def connect(instance_id: str, port: int, profile: str, region: str, ssh_key: str
|
|
52
52
|
@click.option('--ssh-key', default='vscode', help='SSH key name to use (default: vscode)')
|
53
53
|
@click.option('--ssh-config', help='SSH config file to use (default: ~/.ssh/vscode/config)')
|
54
54
|
@click.option('--aws-env', help='AWS environment directory (default: ~/.aws, use name of directory in ~/.aws/aws-envs/)')
|
55
|
-
|
55
|
+
@click.option('--1password', 'use_1password', is_flag=True, help='Use 1Password SSH agent for SSH authentication')
|
56
|
+
def setup(profile: str, ssh_key: str, ssh_config: str, aws_env: str, use_1password: bool):
|
56
57
|
"""Set up AWS profile, SSH keys, and configuration for CloudX.
|
57
58
|
|
58
59
|
This command will:
|
@@ -65,9 +66,16 @@ def setup(profile: str, ssh_key: str, ssh_config: str, aws_env: str):
|
|
65
66
|
cloudx-proxy setup
|
66
67
|
cloudx-proxy setup --profile myprofile --ssh-key mykey
|
67
68
|
cloudx-proxy setup --ssh-config ~/.ssh/cloudx/config
|
69
|
+
cloudx-proxy setup --1password
|
68
70
|
"""
|
69
71
|
try:
|
70
|
-
setup = CloudXSetup(
|
72
|
+
setup = CloudXSetup(
|
73
|
+
profile=profile,
|
74
|
+
ssh_key=ssh_key,
|
75
|
+
ssh_config=ssh_config,
|
76
|
+
aws_env=aws_env,
|
77
|
+
use_1password=use_1password
|
78
|
+
)
|
71
79
|
|
72
80
|
print("\n\033[1;95m=== cloudx-proxy Setup ===\033[0m\n")
|
73
81
|
|
cloudx_proxy/core.py
CHANGED
@@ -91,9 +91,21 @@ class CloudXProxy:
|
|
91
91
|
return False
|
92
92
|
|
93
93
|
def push_ssh_key(self) -> bool:
|
94
|
-
"""Push SSH public key to instance via EC2 Instance Connect.
|
94
|
+
"""Push SSH public key to instance via EC2 Instance Connect.
|
95
|
+
|
96
|
+
Determines which SSH key to use (regular key or 1Password-managed key),
|
97
|
+
then pushes the correct public key to the instance.
|
98
|
+
"""
|
95
99
|
try:
|
96
|
-
with
|
100
|
+
# Check if file exists with .pub extension (could be a non-1Password key)
|
101
|
+
# or if the .pub extension is already part of self.ssh_key (because of 1Password integration)
|
102
|
+
key_path = self.ssh_key
|
103
|
+
if not key_path.endswith('.pub'):
|
104
|
+
key_path += '.pub'
|
105
|
+
|
106
|
+
self.log(f"Using public key: {key_path}")
|
107
|
+
|
108
|
+
with open(key_path) as f:
|
97
109
|
public_key = f.read()
|
98
110
|
|
99
111
|
self.ec2_connect.send_ssh_public_key(
|
cloudx_proxy/setup.py
CHANGED
@@ -7,9 +7,11 @@ from pathlib import Path
|
|
7
7
|
from typing import Optional, Tuple
|
8
8
|
import boto3
|
9
9
|
from botocore.exceptions import ClientError
|
10
|
+
from ._1password import check_1password_cli, check_ssh_agent, list_ssh_keys, create_ssh_key, get_vaults, save_public_key
|
10
11
|
|
11
12
|
class CloudXSetup:
|
12
|
-
def __init__(self, profile: str = "vscode", ssh_key: str = "vscode", ssh_config: str = None,
|
13
|
+
def __init__(self, profile: str = "vscode", ssh_key: str = "vscode", ssh_config: str = None,
|
14
|
+
aws_env: str = None, use_1password: bool = False):
|
13
15
|
"""Initialize cloudx-proxy setup.
|
14
16
|
|
15
17
|
Args:
|
@@ -17,11 +19,14 @@ class CloudXSetup:
|
|
17
19
|
ssh_key: SSH key name (default: "vscode")
|
18
20
|
ssh_config: SSH config file path (default: None, uses ~/.ssh/vscode/config)
|
19
21
|
aws_env: AWS environment directory (default: None)
|
22
|
+
use_1password: Use 1Password SSH agent for authentication (default: False)
|
20
23
|
"""
|
21
24
|
self.profile = profile
|
22
25
|
self.ssh_key = ssh_key
|
23
26
|
self.aws_env = aws_env
|
27
|
+
self.use_1password = use_1password
|
24
28
|
self.home_dir = str(Path.home())
|
29
|
+
self.onepassword_agent_sock = Path(self.home_dir) / ".1password" / "agent.sock"
|
25
30
|
|
26
31
|
# Set up ssh config paths based on provided config or default
|
27
32
|
if ssh_config:
|
@@ -151,6 +156,124 @@ class CloudXSetup:
|
|
151
156
|
self.print_status(f"\033[1;91mError:\033[0m {str(e)}", False, 2)
|
152
157
|
return False
|
153
158
|
|
159
|
+
def _check_1password_availability(self) -> bool:
|
160
|
+
"""Check if 1Password CLI and SSH agent are available.
|
161
|
+
|
162
|
+
Returns:
|
163
|
+
bool: True if 1Password is available and configured
|
164
|
+
"""
|
165
|
+
if not self.use_1password:
|
166
|
+
return False
|
167
|
+
|
168
|
+
self.print_status("Checking 1Password availability...")
|
169
|
+
|
170
|
+
# Use our helper function to check 1Password CLI
|
171
|
+
installed, authenticated, version = check_1password_cli()
|
172
|
+
|
173
|
+
if not installed:
|
174
|
+
self.print_status("1Password CLI not found. Please install it from https://1password.com/downloads/command-line/", False, 2)
|
175
|
+
return False
|
176
|
+
|
177
|
+
self.print_status(f"1Password CLI {version} installed", True, 2)
|
178
|
+
|
179
|
+
if not authenticated:
|
180
|
+
self.print_status("1Password CLI is not authenticated. Run 'op signin' first.", False, 2)
|
181
|
+
return False
|
182
|
+
|
183
|
+
self.print_status("1Password CLI is authenticated", True, 2)
|
184
|
+
|
185
|
+
# Check if 1Password SSH agent is running
|
186
|
+
agent_running = check_ssh_agent(str(self.onepassword_agent_sock))
|
187
|
+
|
188
|
+
if not agent_running:
|
189
|
+
self.print_status("1Password SSH agent is not running", False, 2)
|
190
|
+
self.print_status("Please ensure 1Password SSH agent is enabled in 1Password settings", None, 2)
|
191
|
+
return False
|
192
|
+
|
193
|
+
self.print_status("1Password SSH agent is running", True, 2)
|
194
|
+
return True
|
195
|
+
|
196
|
+
def _create_1password_key(self) -> bool:
|
197
|
+
"""Create a new SSH key in 1Password.
|
198
|
+
|
199
|
+
Returns:
|
200
|
+
bool: True if successful
|
201
|
+
"""
|
202
|
+
try:
|
203
|
+
# Get vaults to determine where to store the key
|
204
|
+
vaults = get_vaults()
|
205
|
+
if not vaults:
|
206
|
+
self.print_status("No 1Password vaults found", False, 2)
|
207
|
+
return False
|
208
|
+
|
209
|
+
# Display available vaults
|
210
|
+
self.print_status("Creating a new SSH key in 1Password", None, 2)
|
211
|
+
print("\n\033[96mAvailable 1Password vaults:\033[0m")
|
212
|
+
for i, vault in enumerate(vaults):
|
213
|
+
print(f" {i+1}. {vault['name']}")
|
214
|
+
|
215
|
+
# Let user select vault
|
216
|
+
vault_num = self.prompt("Select vault number to store SSH key", "1")
|
217
|
+
try:
|
218
|
+
vault_idx = int(vault_num) - 1
|
219
|
+
if vault_idx < 0 or vault_idx >= len(vaults):
|
220
|
+
self.print_status("Invalid vault number", False, 2)
|
221
|
+
return False
|
222
|
+
selected_vault = vaults[vault_idx]['id']
|
223
|
+
except ValueError:
|
224
|
+
self.print_status("Invalid input", False, 2)
|
225
|
+
return False
|
226
|
+
|
227
|
+
# Create a title for the 1Password item
|
228
|
+
ssh_key_title = f"cloudX SSH Key - {self.ssh_key}"
|
229
|
+
|
230
|
+
# Check if a key with this title already exists in 1Password
|
231
|
+
ssh_keys = list_ssh_keys()
|
232
|
+
existing_key = next((key for key in ssh_keys if key['title'] == ssh_key_title), None)
|
233
|
+
|
234
|
+
if existing_key:
|
235
|
+
self.print_status(f"SSH key '{ssh_key_title}' already exists in 1Password", True, 2)
|
236
|
+
# Get the public key
|
237
|
+
result = subprocess.run(
|
238
|
+
['op', 'item', 'get', existing_key['id'], '--fields', 'public key'],
|
239
|
+
capture_output=True,
|
240
|
+
text=True,
|
241
|
+
check=False
|
242
|
+
)
|
243
|
+
|
244
|
+
if result.returncode == 0:
|
245
|
+
public_key = result.stdout.strip()
|
246
|
+
# Save it to the expected location
|
247
|
+
if save_public_key(public_key, f"{self.ssh_key_file}.pub"):
|
248
|
+
self.print_status(f"Saved existing public key to {self.ssh_key_file}.pub", True, 2)
|
249
|
+
return True
|
250
|
+
else:
|
251
|
+
# Create a new SSH key in 1Password
|
252
|
+
self.print_status(f"Creating new SSH key '{ssh_key_title}' in 1Password...", None, 2)
|
253
|
+
success, public_key, item_id = create_ssh_key(ssh_key_title, selected_vault)
|
254
|
+
|
255
|
+
if not success:
|
256
|
+
self.print_status("Failed to create SSH key in 1Password", False, 2)
|
257
|
+
return False
|
258
|
+
|
259
|
+
self.print_status("SSH key created successfully in 1Password", True, 2)
|
260
|
+
|
261
|
+
# Save the public key to the expected location
|
262
|
+
if save_public_key(public_key, f"{self.ssh_key_file}.pub"):
|
263
|
+
self.print_status(f"Saved public key to {self.ssh_key_file}.pub", True, 2)
|
264
|
+
return True
|
265
|
+
else:
|
266
|
+
self.print_status(f"Failed to save public key to {self.ssh_key_file}.pub", False, 2)
|
267
|
+
return False
|
268
|
+
|
269
|
+
# Remind user to enable the key in 1Password SSH agent
|
270
|
+
self.print_status("\033[93mImportant: Make sure the key is enabled in 1Password's SSH agent settings\033[0m", None, 2)
|
271
|
+
return True
|
272
|
+
|
273
|
+
except Exception as e:
|
274
|
+
self.print_status(f"Error creating key in 1Password: {str(e)}", False, 2)
|
275
|
+
return False
|
276
|
+
|
154
277
|
def setup_ssh_key(self) -> bool:
|
155
278
|
"""Set up SSH key pair.
|
156
279
|
|
@@ -158,6 +281,21 @@ class CloudXSetup:
|
|
158
281
|
bool: True if key was set up successfully
|
159
282
|
"""
|
160
283
|
self.print_header("SSH Key Configuration")
|
284
|
+
|
285
|
+
# Check 1Password integration if requested
|
286
|
+
if self.use_1password:
|
287
|
+
op_available = self._check_1password_availability()
|
288
|
+
if op_available:
|
289
|
+
self.print_status("Using 1Password SSH agent for authentication", True, 2)
|
290
|
+
|
291
|
+
# Always prefer to create keys in 1Password
|
292
|
+
return self._create_1password_key()
|
293
|
+
else:
|
294
|
+
proceed = self.prompt("1Password integration not available. Continue with standard SSH key setup?", "Y").lower() != "n"
|
295
|
+
if not proceed:
|
296
|
+
return False
|
297
|
+
self.use_1password = False # Fallback to standard setup
|
298
|
+
|
161
299
|
self.print_status(f"Checking SSH key '{self.ssh_key}' configuration...")
|
162
300
|
|
163
301
|
try:
|
@@ -196,6 +334,8 @@ class CloudXSetup:
|
|
196
334
|
self.ssh_key_file.with_suffix('.pub').chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IROTH | stat.S_IRGRP) # 644 permissions
|
197
335
|
self.print_status("Set key file permissions", True, 2)
|
198
336
|
|
337
|
+
# Standard key generation successful
|
338
|
+
self.print_status("Key generated successfully", True, 2)
|
199
339
|
return True
|
200
340
|
|
201
341
|
except Exception as e:
|
@@ -206,6 +346,75 @@ class CloudXSetup:
|
|
206
346
|
return True
|
207
347
|
return False
|
208
348
|
|
349
|
+
def _build_proxy_command(self) -> str:
|
350
|
+
"""Build the ProxyCommand with appropriate parameters.
|
351
|
+
|
352
|
+
Returns:
|
353
|
+
str: The complete ProxyCommand string
|
354
|
+
"""
|
355
|
+
proxy_command = "uvx cloudx-proxy connect %h %p"
|
356
|
+
if self.profile != "vscode":
|
357
|
+
proxy_command += f" --profile {self.profile}"
|
358
|
+
if self.aws_env:
|
359
|
+
proxy_command += f" --aws-env {self.aws_env}"
|
360
|
+
if self.ssh_key != "vscode":
|
361
|
+
proxy_command += f" --ssh-key {self.ssh_key}"
|
362
|
+
|
363
|
+
return proxy_command
|
364
|
+
|
365
|
+
def _build_auth_config(self) -> str:
|
366
|
+
"""Build the authentication configuration block.
|
367
|
+
|
368
|
+
Returns:
|
369
|
+
str: SSH config authentication section
|
370
|
+
"""
|
371
|
+
if self.use_1password:
|
372
|
+
# When using 1Password:
|
373
|
+
# 1. Set IdentityAgent to the 1Password socket
|
374
|
+
# 2. Set IdentityFile to the PUBLIC key (.pub) to limit key search
|
375
|
+
# 3. Set IdentitiesOnly to yes to avoid using ssh-agent keys
|
376
|
+
return f""" IdentityAgent {self.onepassword_agent_sock}
|
377
|
+
IdentityFile {self.ssh_key_file}.pub
|
378
|
+
IdentitiesOnly yes
|
379
|
+
"""
|
380
|
+
else:
|
381
|
+
# Standard SSH key configuration
|
382
|
+
return f""" IdentityFile {self.ssh_key_file}
|
383
|
+
IdentitiesOnly yes
|
384
|
+
"""
|
385
|
+
|
386
|
+
def _build_host_config(self, cloudx_env: str, hostname: str, instance_id: str, include_proxy: bool = True) -> str:
|
387
|
+
"""Build a host configuration block.
|
388
|
+
|
389
|
+
Args:
|
390
|
+
cloudx_env: CloudX environment
|
391
|
+
hostname: Hostname for the instance
|
392
|
+
instance_id: EC2 instance ID (None for wildcard entries)
|
393
|
+
include_proxy: Whether to include the ProxyCommand (default: True)
|
394
|
+
|
395
|
+
Returns:
|
396
|
+
str: Complete host configuration block
|
397
|
+
"""
|
398
|
+
host_pattern = hostname if hostname else "*"
|
399
|
+
host_entry = f"""
|
400
|
+
Host cloudx-{cloudx_env}-{host_pattern}
|
401
|
+
"""
|
402
|
+
# Add HostName only for specific hosts, not for wildcard entries
|
403
|
+
if instance_id:
|
404
|
+
host_entry += f""" HostName {instance_id}
|
405
|
+
"""
|
406
|
+
host_entry += """ User ec2-user
|
407
|
+
"""
|
408
|
+
# Add authentication configuration
|
409
|
+
host_entry += self._build_auth_config()
|
410
|
+
|
411
|
+
# Add proxy command if requested
|
412
|
+
if include_proxy:
|
413
|
+
host_entry += f""" ProxyCommand {self._build_proxy_command()}
|
414
|
+
"""
|
415
|
+
|
416
|
+
return host_entry
|
417
|
+
|
209
418
|
def _add_host_entry(self, cloudx_env: str, instance_id: str, hostname: str, current_config: str) -> bool:
|
210
419
|
"""Add settings to a specific host entry.
|
211
420
|
|
@@ -219,25 +428,8 @@ class CloudXSetup:
|
|
219
428
|
bool: True if settings were added successfully
|
220
429
|
"""
|
221
430
|
try:
|
222
|
-
#
|
223
|
-
|
224
|
-
if self.profile != "vscode":
|
225
|
-
proxy_command += f" --profile {self.profile}"
|
226
|
-
if self.aws_env:
|
227
|
-
proxy_command += f" --aws-env {self.aws_env}"
|
228
|
-
if self.ssh_key != "vscode":
|
229
|
-
proxy_command += f" --ssh-key {self.ssh_key}"
|
230
|
-
|
231
|
-
host_entry = f"""
|
232
|
-
Host cloudx-{cloudx_env}-{hostname}
|
233
|
-
HostName {instance_id}
|
234
|
-
User ec2-user
|
235
|
-
"""
|
236
|
-
host_entry += f""" IdentityFile {self.ssh_key_file}
|
237
|
-
IdentitiesOnly yes
|
238
|
-
"""
|
239
|
-
host_entry += f""" ProxyCommand {proxy_command}
|
240
|
-
"""
|
431
|
+
# Generate the host entry using the consolidated helper method
|
432
|
+
host_entry = self._build_host_config(cloudx_env, hostname, instance_id)
|
241
433
|
|
242
434
|
# Append host entry
|
243
435
|
with open(self.ssh_config_file, 'a') as f:
|
@@ -367,30 +559,18 @@ Host cloudx-{cloudx_env}-{hostname}
|
|
367
559
|
|
368
560
|
# Create base config
|
369
561
|
self.print_status(f"Creating new config for cloudx-{cloudx_env}-*", None, 2)
|
370
|
-
|
371
|
-
# We don't need to include ssh_config here as SSH will handle that through the config
|
372
|
-
proxy_command = "uvx cloudx-proxy connect %h %p"
|
373
|
-
if self.profile != "vscode":
|
374
|
-
proxy_command += f" --profile {self.profile}"
|
375
|
-
if self.aws_env:
|
376
|
-
proxy_command += f" --aws-env {self.aws_env}"
|
377
|
-
if self.ssh_key != "vscode":
|
378
|
-
proxy_command += f" --ssh-key {self.ssh_key}"
|
379
|
-
|
562
|
+
|
380
563
|
# Ensure control directory exists with proper permissions
|
381
564
|
if not self._ensure_control_dir():
|
382
565
|
return False
|
383
566
|
|
384
|
-
# Build base configuration
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
IdentitiesOnly yes
|
392
|
-
|
393
|
-
"""
|
567
|
+
# Build base configuration with wildcard hostname pattern
|
568
|
+
# Start with a header comment
|
569
|
+
base_config = "# cloudx-proxy SSH Configuration\n"
|
570
|
+
|
571
|
+
# Add base host pattern with wildcard
|
572
|
+
base_config += self._build_host_config(cloudx_env, None, None, include_proxy=True)
|
573
|
+
|
394
574
|
# Add SSH multiplexing configuration
|
395
575
|
control_path = "~/.ssh/control/%r@%h:%p"
|
396
576
|
if platform.system() == 'Windows':
|
@@ -402,9 +582,6 @@ Host cloudx-{cloudx_env}-*
|
|
402
582
|
ControlPath {control_path}
|
403
583
|
ControlPersist 4h
|
404
584
|
|
405
|
-
"""
|
406
|
-
# Add ProxyCommand
|
407
|
-
base_config += f""" ProxyCommand {proxy_command}
|
408
585
|
"""
|
409
586
|
|
410
587
|
# If file exists, append the new config, otherwise create it
|
@@ -421,12 +598,9 @@ Host cloudx-{cloudx_env}-*
|
|
421
598
|
self.ssh_config_file.chmod(stat.S_IRUSR | stat.S_IWUSR) # 600 permissions (owner read/write)
|
422
599
|
self.print_status("Set config file permissions to 600", True, 2)
|
423
600
|
|
424
|
-
# Add specific host entry
|
601
|
+
# Add specific host entry using the consolidated helper method
|
425
602
|
self.print_status(f"Adding host entry for cloudx-{cloudx_env}-{hostname}", None, 2)
|
426
|
-
host_entry =
|
427
|
-
Host cloudx-{cloudx_env}-{hostname}
|
428
|
-
HostName {instance_id}
|
429
|
-
"""
|
603
|
+
host_entry = self._build_host_config(cloudx_env, hostname, instance_id, include_proxy=False)
|
430
604
|
with open(self.ssh_config_file, 'a') as f:
|
431
605
|
f.write(host_entry)
|
432
606
|
self.print_status("Host entry added", True, 2)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
|
2
|
+
cloudx_proxy/_version.py,sha256=yF2DwGUoQKNnLhAbpZX8kCQKjw77EZzhRk7_OTftets,511
|
3
|
+
cloudx_proxy/cli.py,sha256=kdrZydxL94BJrv6NnjIcceRqhoonBzMIx4vfm1Wl7qc,4104
|
4
|
+
cloudx_proxy/core.py,sha256=RF3bX5MQiokRKjYEPnfWdKywGdtoVUvV2xZqm9uOl1g,8135
|
5
|
+
cloudx_proxy/setup.py,sha256=jvv7ibJQ8svyjYYeVKwGa70L7RV2W7yS7JXEvKed3wI,33339
|
6
|
+
cloudx_proxy-0.4.1.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
|
7
|
+
cloudx_proxy-0.4.1.dist-info/METADATA,sha256=wFL6lV-shLLfI6cn8H5ZItIF944-TrX67LJC0Ml-muc,14037
|
8
|
+
cloudx_proxy-0.4.1.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
9
|
+
cloudx_proxy-0.4.1.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
|
10
|
+
cloudx_proxy-0.4.1.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
|
11
|
+
cloudx_proxy-0.4.1.dist-info/RECORD,,
|
@@ -1,11 +0,0 @@
|
|
1
|
-
cloudx_proxy/__init__.py,sha256=ZZ2O_m9OFJm18AxMSuYJt4UjSuSqyJlYRaZMoets498,61
|
2
|
-
cloudx_proxy/_version.py,sha256=HSn5cGZLA_vnXREa9sRtSYCA5Ii9CJlQbi1YMzsfUGM,513
|
3
|
-
cloudx_proxy/cli.py,sha256=tP-Ftf2VNKtaiu26x5nDKCMZOPKoa4XCNDd5-okoE4s,3827
|
4
|
-
cloudx_proxy/core.py,sha256=iHloywyiDcWRXzFxgX0TdcOPHujW2u83WUMllk4m9Es,7588
|
5
|
-
cloudx_proxy/setup.py,sha256=zZMMGAAQMqXlfVJ8_M4f5-SEcYqsFQ82kQv18ikdta0,25409
|
6
|
-
cloudx_proxy-0.3.15.dist-info/LICENSE,sha256=i7P2OR4zsJYsMWcCUDe_B9ZfGi9bU0K5I2nKfDrW_N8,1068
|
7
|
-
cloudx_proxy-0.3.15.dist-info/METADATA,sha256=qOT0s-RuqhXnU69PHxWELyeBKNgluZyHpO-R6O5d2q8,14038
|
8
|
-
cloudx_proxy-0.3.15.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
9
|
-
cloudx_proxy-0.3.15.dist-info/entry_points.txt,sha256=HGt743N2lVlKd7O1qWq3C0aEHyS5PjPnxzDHh7hwtSg,54
|
10
|
-
cloudx_proxy-0.3.15.dist-info/top_level.txt,sha256=2wtEote1db21j-VvkCJFfT-dLlauuG5indjggYh3xDg,13
|
11
|
-
cloudx_proxy-0.3.15.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|