request-vm-on-golem 0.1.35__py3-none-any.whl → 0.1.37__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.
- {request_vm_on_golem-0.1.35.dist-info → request_vm_on_golem-0.1.37.dist-info}/METADATA +1 -1
- {request_vm_on_golem-0.1.35.dist-info → request_vm_on_golem-0.1.37.dist-info}/RECORD +7 -7
- requestor/cli/commands.py +105 -37
- requestor/config.py +44 -11
- requestor/run.py +21 -13
- {request_vm_on_golem-0.1.35.dist-info → request_vm_on_golem-0.1.37.dist-info}/WHEEL +0 -0
- {request_vm_on_golem-0.1.35.dist-info → request_vm_on_golem-0.1.37.dist-info}/entry_points.txt +0 -0
@@ -1,14 +1,14 @@
|
|
1
1
|
requestor/__init__.py,sha256=OqSUAh1uZBMx7GW0MoSMg967PVdmT8XdPJx3QYjwkak,116
|
2
2
|
requestor/api/main.py,sha256=7utCzFNbh5Ol-vsBWeSwT4lXeHD7zdA-GFZuS3rHMWc,2180
|
3
3
|
requestor/cli/__init__.py,sha256=e3E4oEGxmGj-STPtFkQwg_qIWhR0JAiAQdw3G1hXciU,37
|
4
|
-
requestor/cli/commands.py,sha256=
|
5
|
-
requestor/config.py,sha256=
|
4
|
+
requestor/cli/commands.py,sha256=f1XyNNVOYu9h9lQC96I-hMQoCU79Lwu_ropUWbQ2qzY,26129
|
5
|
+
requestor/config.py,sha256=O39E-Wa-ewqdC9XP5nvj3zkOs52mevvFMyQGtHaqANk,4668
|
6
6
|
requestor/db/__init__.py,sha256=Gm5DfWls6uvCZZ3HGGnyRHswbUQdeA5OGN8yPwH0hc8,88
|
7
7
|
requestor/db/sqlite.py,sha256=l5pWbx2qlHuar1N_a0B9tVnmumLJY1w5rp3yZ7jmsC0,4146
|
8
8
|
requestor/errors.py,sha256=wVpHBuYgQx5pTe_SamugfK-k768noikY1RxvPOjQGko,665
|
9
9
|
requestor/provider/__init__.py,sha256=fmW23aYUVciF8-gmBZkG-PLhn22upmcDzdPfAOLHG6g,103
|
10
10
|
requestor/provider/client.py,sha256=OUP7CoOCCtKD6DB9eqFkOXK6A2BLFdM4DWSkoulJQxg,3213
|
11
|
-
requestor/run.py,sha256=
|
11
|
+
requestor/run.py,sha256=GqOG6n34szt8Sp3SEqjRV6huWm737yCN6YnBqoiwI-U,1785
|
12
12
|
requestor/services/__init__.py,sha256=1qSn_6RMn0KB0A7LCnY2IW6_tC3HBQsdfkFeV-h94eM,172
|
13
13
|
requestor/services/database_service.py,sha256=GlSrzzzd7PSYQJNup00sxkB-B2PMr1__04K8k5QSWvs,2996
|
14
14
|
requestor/services/provider_service.py,sha256=iejw8Q-ziK3Ny0cEAD1EHejUsAqf9BwJTa7jFmei0_8,9773
|
@@ -18,7 +18,7 @@ requestor/ssh/__init__.py,sha256=hNgSqJ5s1_AwwxVRyFjUqh_LTBpI4Hmzq0F-f_wXN9g,119
|
|
18
18
|
requestor/ssh/manager.py,sha256=XhZjz7_BRPnmpu-zxqnGHLCq0b2JZ8Xr8zc1OlMNDkc,9355
|
19
19
|
requestor/utils/logging.py,sha256=oFNpO8pJboYM8Wp7g3HOU4HFyBTKypVdY15lUiz1a4I,3721
|
20
20
|
requestor/utils/spinner.py,sha256=PUHJdTD9jpUHur__01_qxXy87WFfNmjQbD_sLG-KlGo,2459
|
21
|
-
request_vm_on_golem-0.1.
|
22
|
-
request_vm_on_golem-0.1.
|
23
|
-
request_vm_on_golem-0.1.
|
24
|
-
request_vm_on_golem-0.1.
|
21
|
+
request_vm_on_golem-0.1.37.dist-info/METADATA,sha256=5MM3qjnmwNTUgCsTou03TbGAR5eKOuRZ4ZMefPHicO0,9950
|
22
|
+
request_vm_on_golem-0.1.37.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
23
|
+
request_vm_on_golem-0.1.37.dist-info/entry_points.txt,sha256=Z-skRNpJ8aZcIl_En9mEm1ygkp9FKy0bzQoL3zO52-0,44
|
24
|
+
request_vm_on_golem-0.1.37.dist-info/RECORD,,
|
requestor/cli/commands.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
"""CLI interface for VM on Golem."""
|
2
2
|
import click
|
3
3
|
import asyncio
|
4
|
+
import json
|
4
5
|
from typing import Optional
|
5
6
|
from pathlib import Path
|
6
7
|
import subprocess
|
@@ -13,7 +14,7 @@ except ImportError:
|
|
13
14
|
# Python < 3.8
|
14
15
|
import importlib_metadata as metadata
|
15
16
|
|
16
|
-
from ..config import config
|
17
|
+
from ..config import config, ensure_config
|
17
18
|
from ..provider.client import ProviderClient
|
18
19
|
from ..errors import RequestorError
|
19
20
|
from ..utils.logging import setup_logger
|
@@ -55,6 +56,7 @@ def print_version(ctx, param, value):
|
|
55
56
|
expose_value=False, is_eager=True, help="Show the version and exit.")
|
56
57
|
def cli():
|
57
58
|
"""VM on Golem management CLI"""
|
59
|
+
ensure_config()
|
58
60
|
pass
|
59
61
|
|
60
62
|
|
@@ -70,8 +72,9 @@ def vm():
|
|
70
72
|
@click.option('--storage', type=int, help='Minimum storage (GB) required')
|
71
73
|
@click.option('--country', help='Preferred provider country')
|
72
74
|
@click.option('--driver', type=click.Choice(['central', 'golem-base']), default=None, help='Discovery driver to use')
|
75
|
+
@click.option('--json', 'as_json', is_flag=True, help='Output in JSON format')
|
73
76
|
@async_command
|
74
|
-
async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Optional[int], country: Optional[str], driver: Optional[str]):
|
77
|
+
async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Optional[int], country: Optional[str], driver: Optional[str], as_json: bool):
|
75
78
|
"""List available providers matching requirements."""
|
76
79
|
try:
|
77
80
|
# Log search criteria if any
|
@@ -85,11 +88,11 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
|
|
85
88
|
logger.detail(f"Storage: {storage}GB+")
|
86
89
|
if country:
|
87
90
|
logger.detail(f"Country: {country}")
|
88
|
-
|
91
|
+
|
89
92
|
# Determine the discovery driver being used
|
90
93
|
discovery_driver = driver or config.discovery_driver
|
91
94
|
logger.process(f"Querying discovery service via {discovery_driver}")
|
92
|
-
|
95
|
+
|
93
96
|
# Initialize provider service
|
94
97
|
provider_service = ProviderService()
|
95
98
|
async with provider_service:
|
@@ -103,24 +106,31 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
|
|
103
106
|
|
104
107
|
if not providers:
|
105
108
|
logger.warning("No providers found matching criteria")
|
106
|
-
return
|
109
|
+
return {"providers": []}
|
107
110
|
|
108
|
-
|
109
|
-
headers = provider_service.provider_headers
|
110
|
-
rows = await asyncio.gather(*(provider_service.format_provider_row(p, colorize=True) for p in providers))
|
111
|
+
result = {"providers": providers}
|
111
112
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
if as_json:
|
114
|
+
click.echo(json.dumps(result, indent=2))
|
115
|
+
else:
|
116
|
+
# Format provider information using service with colors
|
117
|
+
headers = provider_service.provider_headers
|
118
|
+
rows = await asyncio.gather(*(provider_service.format_provider_row(p, colorize=True) for p in providers))
|
116
119
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
120
|
+
# Show fancy header
|
121
|
+
click.echo("\n" + "─" * 80)
|
122
|
+
click.echo(click.style(f" 🌍 Available Providers ({len(providers)} total)", fg="blue", bold=True))
|
123
|
+
click.echo("─" * 80)
|
124
|
+
|
125
|
+
# Show table with colored headers
|
126
|
+
click.echo("\n" + tabulate(
|
127
|
+
rows,
|
128
|
+
headers=[click.style(h, bold=True) for h in headers],
|
129
|
+
tablefmt="grid"
|
130
|
+
))
|
131
|
+
click.echo("\n" + "─" * 80)
|
132
|
+
|
133
|
+
return result
|
124
134
|
|
125
135
|
except Exception as e:
|
126
136
|
logger.error(f"Failed to list providers: {str(e)}")
|
@@ -274,6 +284,56 @@ async def ssh_vm(name: str):
|
|
274
284
|
raise click.Abort()
|
275
285
|
|
276
286
|
|
287
|
+
@vm.command(name='info')
|
288
|
+
@click.argument('name')
|
289
|
+
@click.option('--json', 'as_json', is_flag=True, help='Output in JSON format')
|
290
|
+
@async_command
|
291
|
+
async def info_vm(name: str, as_json: bool):
|
292
|
+
"""Show information about a VM."""
|
293
|
+
try:
|
294
|
+
logger.command(f"ℹ️ Getting info for VM '{name}'")
|
295
|
+
|
296
|
+
# Initialize VM service
|
297
|
+
ssh_service = SSHService(config.ssh_key_dir)
|
298
|
+
vm_service = VMService(db_service, ssh_service)
|
299
|
+
|
300
|
+
# Retrieve VM details
|
301
|
+
vm = await vm_service.get_vm(name)
|
302
|
+
if not vm:
|
303
|
+
raise click.BadParameter(f"VM '{name}' not found")
|
304
|
+
|
305
|
+
result = vm
|
306
|
+
|
307
|
+
if as_json:
|
308
|
+
click.echo(json.dumps(result, indent=2))
|
309
|
+
else:
|
310
|
+
headers = [
|
311
|
+
"Status",
|
312
|
+
"IP Address",
|
313
|
+
"SSH Port",
|
314
|
+
"CPU",
|
315
|
+
"Memory (GB)",
|
316
|
+
"Storage (GB)",
|
317
|
+
]
|
318
|
+
|
319
|
+
row = [
|
320
|
+
vm.get("status", "unknown"),
|
321
|
+
vm["provider_ip"],
|
322
|
+
vm["config"].get("ssh_port", "N/A"),
|
323
|
+
vm["config"]["cpu"],
|
324
|
+
vm["config"]["memory"],
|
325
|
+
vm["config"]["storage"],
|
326
|
+
]
|
327
|
+
|
328
|
+
click.echo("\n" + tabulate([row], headers=headers, tablefmt="grid"))
|
329
|
+
|
330
|
+
return result
|
331
|
+
|
332
|
+
except Exception as e:
|
333
|
+
logger.error(f"Failed to get VM info: {str(e)}")
|
334
|
+
raise click.Abort()
|
335
|
+
|
336
|
+
|
277
337
|
@vm.command(name='destroy')
|
278
338
|
@click.argument('name')
|
279
339
|
@async_command
|
@@ -520,37 +580,45 @@ def run_api_server(host: str, port: int, reload: bool):
|
|
520
580
|
|
521
581
|
|
522
582
|
@vm.command(name='list')
|
583
|
+
@click.option('--json', 'as_json', is_flag=True, help='Output in JSON format')
|
523
584
|
@async_command
|
524
|
-
async def list_vms():
|
585
|
+
async def list_vms(as_json: bool):
|
525
586
|
"""List all VMs."""
|
526
587
|
try:
|
527
588
|
logger.command("📋 Listing your VMs")
|
528
589
|
logger.process("Fetching VM details")
|
529
|
-
|
590
|
+
|
530
591
|
# Initialize VM service with temporary client (not needed for listing)
|
531
592
|
ssh_service = SSHService(config.ssh_key_dir)
|
532
593
|
vm_service = VMService(db_service, ssh_service, None)
|
533
594
|
vms = await vm_service.list_vms()
|
534
595
|
if not vms:
|
535
596
|
logger.warning("No VMs found")
|
536
|
-
return
|
597
|
+
return {"vms": []}
|
537
598
|
|
538
|
-
|
539
|
-
headers = vm_service.vm_headers
|
540
|
-
rows = [vm_service.format_vm_row(vm, colorize=True) for vm in vms]
|
599
|
+
result = {"vms": vms}
|
541
600
|
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
601
|
+
if as_json:
|
602
|
+
click.echo(json.dumps(result, indent=2))
|
603
|
+
else:
|
604
|
+
# Format VM information using service
|
605
|
+
headers = vm_service.vm_headers
|
606
|
+
rows = [vm_service.format_vm_row(vm, colorize=True) for vm in vms]
|
607
|
+
|
608
|
+
# Show fancy header
|
609
|
+
click.echo("\n" + "─" * 60)
|
610
|
+
click.echo(click.style(f" 📋 Your VMs ({len(vms)} total)", fg="blue", bold=True))
|
611
|
+
click.echo("─" * 60)
|
612
|
+
|
613
|
+
# Show table with colored status
|
614
|
+
click.echo("\n" + tabulate(
|
615
|
+
rows,
|
616
|
+
headers=[click.style(h, bold=True) for h in headers],
|
617
|
+
tablefmt="grid"
|
618
|
+
))
|
619
|
+
click.echo("\n" + "─" * 60)
|
620
|
+
|
621
|
+
return result
|
554
622
|
|
555
623
|
except Exception as e:
|
556
624
|
error_msg = str(e)
|
requestor/config.py
CHANGED
@@ -1,14 +1,46 @@
|
|
1
1
|
from pathlib import Path
|
2
2
|
from typing import Optional, Dict
|
3
3
|
import os
|
4
|
-
from pydantic_settings import BaseSettings
|
5
|
-
from pydantic import Field,
|
4
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
5
|
+
from pydantic import Field, field_validator, ValidationInfo
|
6
|
+
|
7
|
+
|
8
|
+
def ensure_config() -> None:
|
9
|
+
"""Ensure the requestor configuration directory and defaults exist."""
|
10
|
+
base_dir = Path.home() / ".golem" / "requestor"
|
11
|
+
ssh_dir = base_dir / "ssh"
|
12
|
+
env_file = base_dir / ".env"
|
13
|
+
created = False
|
14
|
+
|
15
|
+
if not base_dir.exists():
|
16
|
+
base_dir.mkdir(parents=True, exist_ok=True)
|
17
|
+
created = True
|
18
|
+
if not ssh_dir.exists():
|
19
|
+
ssh_dir.mkdir(parents=True, exist_ok=True)
|
20
|
+
created = True
|
21
|
+
|
22
|
+
if not env_file.exists():
|
23
|
+
env_file.write_text("GOLEM_REQUESTOR_ENVIRONMENT=production\n")
|
24
|
+
created = True
|
25
|
+
|
26
|
+
private_key = ssh_dir / "id_rsa"
|
27
|
+
public_key = ssh_dir / "id_rsa.pub"
|
28
|
+
if not private_key.exists():
|
29
|
+
private_key.write_text("placeholder-private-key")
|
30
|
+
private_key.chmod(0o600)
|
31
|
+
public_key.write_text("placeholder-public-key")
|
32
|
+
created = True
|
33
|
+
|
34
|
+
if created:
|
35
|
+
print("Using default settings – run with --help to customize")
|
36
|
+
|
37
|
+
|
38
|
+
ensure_config()
|
6
39
|
|
7
40
|
class RequestorConfig(BaseSettings):
|
8
41
|
"""Configuration settings for the requestor node."""
|
9
|
-
|
10
|
-
|
11
|
-
env_prefix = "GOLEM_REQUESTOR_"
|
42
|
+
|
43
|
+
model_config = SettingsConfigDict(env_prefix="GOLEM_REQUESTOR_")
|
12
44
|
|
13
45
|
# Environment
|
14
46
|
environment: str = Field(
|
@@ -36,10 +68,11 @@ class RequestorConfig(BaseSettings):
|
|
36
68
|
description="URL of the discovery service (for 'central' driver)"
|
37
69
|
)
|
38
70
|
|
39
|
-
@
|
40
|
-
|
71
|
+
@field_validator("discovery_url")
|
72
|
+
@classmethod
|
73
|
+
def set_discovery_url(cls, v: str, info: ValidationInfo) -> str:
|
41
74
|
"""Prefix discovery URL with DEVMODE if in development."""
|
42
|
-
if
|
75
|
+
if info.data.get("environment") == "development":
|
43
76
|
return f"DEVMODE-{v}"
|
44
77
|
return v
|
45
78
|
|
@@ -63,7 +96,7 @@ class RequestorConfig(BaseSettings):
|
|
63
96
|
|
64
97
|
# Base Directory
|
65
98
|
base_dir: Path = Field(
|
66
|
-
default_factory=lambda: Path.home() / ".golem",
|
99
|
+
default_factory=lambda: Path.home() / ".golem" / "requestor",
|
67
100
|
description="Base directory for all Golem requestor files"
|
68
101
|
)
|
69
102
|
|
@@ -86,10 +119,10 @@ class RequestorConfig(BaseSettings):
|
|
86
119
|
|
87
120
|
# Set dependent paths before validation
|
88
121
|
if 'ssh_key_dir' not in kwargs:
|
89
|
-
base_dir = kwargs.get('base_dir', Path.home() / ".golem")
|
122
|
+
base_dir = kwargs.get('base_dir', Path.home() / ".golem" / "requestor")
|
90
123
|
kwargs['ssh_key_dir'] = base_dir / "ssh"
|
91
124
|
if 'db_path' not in kwargs:
|
92
|
-
base_dir = kwargs.get('base_dir', Path.home() / ".golem")
|
125
|
+
base_dir = kwargs.get('base_dir', Path.home() / ".golem" / "requestor")
|
93
126
|
kwargs['db_path'] = base_dir / "vms.db"
|
94
127
|
super().__init__(**kwargs)
|
95
128
|
|
requestor/run.py
CHANGED
@@ -5,30 +5,36 @@ from pathlib import Path
|
|
5
5
|
from dotenv import load_dotenv
|
6
6
|
|
7
7
|
from requestor.utils.logging import setup_logger
|
8
|
-
from requestor.cli.commands import cli
|
9
8
|
|
10
9
|
# Configure logging with debug mode from environment variable
|
11
10
|
logger = setup_logger(__name__)
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
|
13
|
+
def get_ssh_key_dir() -> Path:
|
14
|
+
"""Return the path to the SSH key directory."""
|
15
|
+
return Path(
|
16
|
+
os.environ.get(
|
17
|
+
"GOLEM_REQUESTOR_SSH_KEY_DIR",
|
18
|
+
str(Path.home() / ".golem" / "requestor" / "ssh"),
|
19
|
+
)
|
19
20
|
)
|
20
|
-
|
21
|
+
|
22
|
+
|
23
|
+
def secure_directory(path: Path) -> bool:
|
24
|
+
"""Create the directory if needed and set strict permissions."""
|
21
25
|
try:
|
22
|
-
# Create and secure directories
|
23
|
-
path = Path(ssh_key_dir)
|
24
26
|
path.mkdir(parents=True, exist_ok=True)
|
25
|
-
path.chmod(0o700)
|
26
|
-
except Exception as e:
|
27
|
+
path.chmod(0o700)
|
28
|
+
except Exception as e: # pragma: no cover - OS-related failures
|
27
29
|
logger.error(f"Failed to create required directories: {e}")
|
28
30
|
return False
|
29
|
-
|
30
31
|
return True
|
31
32
|
|
33
|
+
|
34
|
+
def check_requirements() -> bool:
|
35
|
+
"""Check if all requirements are met."""
|
36
|
+
return secure_directory(get_ssh_key_dir())
|
37
|
+
|
32
38
|
def main():
|
33
39
|
"""Run the requestor CLI."""
|
34
40
|
try:
|
@@ -44,6 +50,8 @@ def main():
|
|
44
50
|
sys.exit(1)
|
45
51
|
|
46
52
|
# Run CLI
|
53
|
+
from requestor.cli.commands import cli
|
54
|
+
|
47
55
|
cli()
|
48
56
|
except Exception as e:
|
49
57
|
logger.error(f"Failed to start requestor CLI: {e}")
|
File without changes
|
{request_vm_on_golem-0.1.35.dist-info → request_vm_on_golem-0.1.37.dist-info}/entry_points.txt
RENAMED
File without changes
|