request-vm-on-golem 0.1.53__py3-none-any.whl → 0.1.55__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.53.dist-info → request_vm_on_golem-0.1.55.dist-info}/METADATA +1 -1
- {request_vm_on_golem-0.1.53.dist-info → request_vm_on_golem-0.1.55.dist-info}/RECORD +12 -12
- requestor/cli/commands.py +60 -5
- requestor/config.py +3 -1
- requestor/provider/client.py +19 -2
- requestor/run.py +10 -0
- requestor/services/provider_service.py +17 -4
- requestor/services/vm_service.py +39 -5
- requestor/utils/logging.py +15 -4
- requestor/utils/spinner.py +12 -11
- {request_vm_on_golem-0.1.53.dist-info → request_vm_on_golem-0.1.55.dist-info}/WHEEL +0 -0
- {request_vm_on_golem-0.1.53.dist-info → request_vm_on_golem-0.1.55.dist-info}/entry_points.txt +0 -0
@@ -1,8 +1,8 @@
|
|
1
1
|
requestor/__init__.py,sha256=OqSUAh1uZBMx7GW0MoSMg967PVdmT8XdPJx3QYjwkak,116
|
2
2
|
requestor/api/main.py,sha256=CTnaM7KyBtDwVlyclYbNDy-nGi5_xt9GTcGusRasDVY,2493
|
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=Obr8Z1ZX3f6WThooIInlD7HqMl_pJmOqawzazLmhke0,51253
|
5
|
+
requestor/config.py,sha256=FMSRKdyo3nEdn62CRonnwrw4Hsy4VGcuvvfg60aORsI,12809
|
6
6
|
requestor/data/deployments/l2.json,sha256=XTNN2C5LkBfp4YbDKdUKfWMdp1fKnfv8D3TgcwVWxtQ,249
|
7
7
|
requestor/db/__init__.py,sha256=Gm5DfWls6uvCZZ3HGGnyRHswbUQdeA5OGN8yPwH0hc8,88
|
8
8
|
requestor/db/sqlite.py,sha256=l5pWbx2qlHuar1N_a0B9tVnmumLJY1w5rp3yZ7jmsC0,4146
|
@@ -10,19 +10,19 @@ requestor/errors.py,sha256=wVpHBuYgQx5pTe_SamugfK-k768noikY1RxvPOjQGko,665
|
|
10
10
|
requestor/payments/blockchain_service.py,sha256=CACvZH2ZstutX7f0L_PXl8K_V5WlIkxNYIaeJuhP5I0,7500
|
11
11
|
requestor/payments/monitor.py,sha256=JtSnh2plFf-f8sJU-bkOpadhoK_R82_ULwkDRmBYSbc,6012
|
12
12
|
requestor/provider/__init__.py,sha256=fmW23aYUVciF8-gmBZkG-PLhn22upmcDzdPfAOLHG6g,103
|
13
|
-
requestor/provider/client.py,sha256=
|
14
|
-
requestor/run.py,sha256=
|
13
|
+
requestor/provider/client.py,sha256=bWj4sNQ8w4F2sSRcMlHbWVjeBLAflDhHxy0BNMqzj8s,5021
|
14
|
+
requestor/run.py,sha256=sR9GgylQWbYPc60wRr3rUpemlNrWqIPNFIJ8WCz6YwE,2120
|
15
15
|
requestor/security/faucet.py,sha256=XF_13b66SKAaY0-40hNRcSgC8AZA4mD5gyXl3qaBLpQ,2320
|
16
16
|
requestor/services/__init__.py,sha256=1qSn_6RMn0KB0A7LCnY2IW6_tC3HBQsdfkFeV-h94eM,172
|
17
17
|
requestor/services/database_service.py,sha256=GlSrzzzd7PSYQJNup00sxkB-B2PMr1__04K8k5QSWvs,2996
|
18
|
-
requestor/services/provider_service.py,sha256=
|
18
|
+
requestor/services/provider_service.py,sha256=XMZtdkrpEKiGg0iFW3v_cf3zu7BZcYp1wjaphxo2SCU,16250
|
19
19
|
requestor/services/ssh_service.py,sha256=tcOCtk2SlB9Uuv-P2ghR22e7BJ9kigQh5b4zSGdPFns,4280
|
20
|
-
requestor/services/vm_service.py,sha256=
|
20
|
+
requestor/services/vm_service.py,sha256=e05OgMZwz3NcCiIwmz4EnB6joMK6S3G_RT3VajD92lw,9438
|
21
21
|
requestor/ssh/__init__.py,sha256=hNgSqJ5s1_AwwxVRyFjUqh_LTBpI4Hmzq0F-f_wXN9g,119
|
22
22
|
requestor/ssh/manager.py,sha256=3jQtbbK7CVC2yD1zCO88jGXh2fBcuv3CzWEqDLuaQVk,9758
|
23
|
-
requestor/utils/logging.py,sha256=
|
24
|
-
requestor/utils/spinner.py,sha256=
|
25
|
-
request_vm_on_golem-0.1.
|
26
|
-
request_vm_on_golem-0.1.
|
27
|
-
request_vm_on_golem-0.1.
|
28
|
-
request_vm_on_golem-0.1.
|
23
|
+
requestor/utils/logging.py,sha256=lgAswzYvO9M0EOET0cFZvuAsGI4lInh_wln_6bI-fJk,4281
|
24
|
+
requestor/utils/spinner.py,sha256=X0jfPfs5ricglTS4_XmacrM2Z1DDHR7zGk2KqYZDpXg,2541
|
25
|
+
request_vm_on_golem-0.1.55.dist-info/METADATA,sha256=wepR8dMN4ehyEx4zmQjjTl3fuXs_OdGGiBCZPn3ZZ8I,15780
|
26
|
+
request_vm_on_golem-0.1.55.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
27
|
+
request_vm_on_golem-0.1.55.dist-info/entry_points.txt,sha256=Z-skRNpJ8aZcIl_En9mEm1ygkp9FKy0bzQoL3zO52-0,44
|
28
|
+
request_vm_on_golem-0.1.55.dist-info/RECORD,,
|
requestor/cli/commands.py
CHANGED
@@ -128,6 +128,7 @@ def vm():
|
|
128
128
|
@click.option('--memory', type=int, help='Minimum memory (GB) required')
|
129
129
|
@click.option('--storage', type=int, help='Minimum disk (GB) required')
|
130
130
|
@click.option('--country', help='Preferred provider country')
|
131
|
+
@click.option('--platform', help='Preferred platform/arch (e.g., x86_64, arm64)')
|
131
132
|
@click.option('--driver', type=click.Choice(['central', 'golem-base']), default=None, help='Discovery driver to use')
|
132
133
|
@click.option('--payments-network', type=str, default=None, help='Filter by payments network profile (default: current config)')
|
133
134
|
@click.option('--all-payments', is_flag=True, help='Do not filter by payments network (show all)')
|
@@ -135,13 +136,15 @@ def vm():
|
|
135
136
|
@click.option('--network', type=click.Choice(['testnet', 'mainnet']), default=None,
|
136
137
|
help='Override network filter for this command')
|
137
138
|
@async_command
|
138
|
-
async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Optional[int], country: Optional[str], driver: Optional[str], payments_network: Optional[str] = None, all_payments: bool = False, as_json: bool = False, network: Optional[str] = None):
|
139
|
+
async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Optional[int], country: Optional[str], platform: Optional[str], driver: Optional[str], payments_network: Optional[str] = None, all_payments: bool = False, as_json: bool = False, network: Optional[str] = None):
|
139
140
|
"""List available providers matching requirements."""
|
140
141
|
try:
|
142
|
+
if as_json:
|
143
|
+
os.environ["GOLEM_SILENCE_LOGS"] = "1"
|
141
144
|
if network:
|
142
145
|
config.network = network
|
143
146
|
# Log search criteria if any
|
144
|
-
if any([cpu, memory, storage, country]):
|
147
|
+
if any([cpu, memory, storage, country, platform]):
|
145
148
|
logger.command("🔍 Searching for providers with criteria:")
|
146
149
|
if cpu:
|
147
150
|
logger.detail(f"CPU Cores: {cpu}+")
|
@@ -151,6 +154,8 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
|
|
151
154
|
logger.detail(f"Disk: {storage}GB+")
|
152
155
|
if country:
|
153
156
|
logger.detail(f"Country: {country}")
|
157
|
+
if platform:
|
158
|
+
logger.detail(f"Platform: {platform}")
|
154
159
|
|
155
160
|
# Determine the discovery driver being used
|
156
161
|
discovery_driver = driver or config.discovery_driver
|
@@ -169,6 +174,7 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
|
|
169
174
|
memory=memory,
|
170
175
|
storage=storage,
|
171
176
|
country=country,
|
177
|
+
platform=platform,
|
172
178
|
driver=driver,
|
173
179
|
payments_network=eff_pn,
|
174
180
|
include_all_payments=bool(all_payments),
|
@@ -176,12 +182,15 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
|
|
176
182
|
except TypeError:
|
177
183
|
# Backward compatibility with older/dummy service stubs in tests
|
178
184
|
providers = await provider_service.find_providers(
|
179
|
-
cpu=cpu, memory=memory, storage=storage, country=country, driver=driver
|
185
|
+
cpu=cpu, memory=memory, storage=storage, country=country, platform=platform, driver=driver
|
180
186
|
)
|
181
187
|
|
182
188
|
if not providers:
|
183
189
|
logger.warning("No providers found matching criteria")
|
184
|
-
|
190
|
+
result = {"providers": []}
|
191
|
+
if as_json:
|
192
|
+
click.echo(json.dumps(result, indent=2))
|
193
|
+
return result
|
185
194
|
|
186
195
|
# If JSON requested and full spec provided, include estimates per provider
|
187
196
|
if as_json and cpu and memory and storage:
|
@@ -216,6 +225,12 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
|
|
216
225
|
except Exception as e:
|
217
226
|
logger.error(f"Failed to list providers: {str(e)}")
|
218
227
|
raise click.Abort()
|
228
|
+
finally:
|
229
|
+
if as_json:
|
230
|
+
try:
|
231
|
+
del os.environ["GOLEM_SILENCE_LOGS"]
|
232
|
+
except Exception:
|
233
|
+
pass
|
219
234
|
|
220
235
|
|
221
236
|
@vm.command(name='create')
|
@@ -385,6 +400,8 @@ def vm_stream():
|
|
385
400
|
async def stream_list(as_json: bool):
|
386
401
|
"""List payment stream status for all known VMs."""
|
387
402
|
try:
|
403
|
+
if as_json:
|
404
|
+
os.environ["GOLEM_SILENCE_LOGS"] = "1"
|
388
405
|
vms = await db_service.list_vms()
|
389
406
|
if not vms:
|
390
407
|
logger.warning("No VMs found in local database")
|
@@ -461,6 +478,12 @@ async def stream_list(as_json: bool):
|
|
461
478
|
except Exception as e:
|
462
479
|
logger.error(f"Failed to list streams: {e}")
|
463
480
|
raise click.Abort()
|
481
|
+
finally:
|
482
|
+
if as_json:
|
483
|
+
try:
|
484
|
+
del os.environ["GOLEM_SILENCE_LOGS"]
|
485
|
+
except Exception:
|
486
|
+
pass
|
464
487
|
|
465
488
|
|
466
489
|
@vm_stream.command('open')
|
@@ -550,6 +573,8 @@ async def stream_topup(stream_id: int, glm: float | None, hours: int | None):
|
|
550
573
|
async def stream_status(name: str, as_json: bool):
|
551
574
|
"""Show the payment stream status for a VM by name."""
|
552
575
|
try:
|
576
|
+
if as_json:
|
577
|
+
os.environ["GOLEM_SILENCE_LOGS"] = "1"
|
553
578
|
# Resolve VM and provider
|
554
579
|
vm = await db_service.get_vm(name)
|
555
580
|
if not vm:
|
@@ -580,6 +605,12 @@ async def stream_status(name: str, as_json: bool):
|
|
580
605
|
except Exception as e:
|
581
606
|
logger.error(f"Failed to fetch stream status: {e}")
|
582
607
|
raise click.Abort()
|
608
|
+
finally:
|
609
|
+
if as_json:
|
610
|
+
try:
|
611
|
+
del os.environ["GOLEM_SILENCE_LOGS"]
|
612
|
+
except Exception:
|
613
|
+
pass
|
583
614
|
|
584
615
|
|
585
616
|
@vm_stream.command('inspect')
|
@@ -589,6 +620,8 @@ async def stream_status(name: str, as_json: bool):
|
|
589
620
|
async def stream_inspect(stream_id: int, as_json: bool):
|
590
621
|
"""Inspect a stream directly on-chain (no provider required)."""
|
591
622
|
try:
|
623
|
+
if as_json:
|
624
|
+
os.environ["GOLEM_SILENCE_LOGS"] = "1"
|
592
625
|
from web3 import Web3
|
593
626
|
from golem_streaming_abi import STREAM_PAYMENT_ABI
|
594
627
|
w3 = Web3(Web3.HTTPProvider(config.polygon_rpc_url))
|
@@ -632,6 +665,12 @@ async def stream_inspect(stream_id: int, as_json: bool):
|
|
632
665
|
except Exception as e:
|
633
666
|
logger.error(f"Failed to inspect stream: {e}")
|
634
667
|
raise click.Abort()
|
668
|
+
finally:
|
669
|
+
if as_json:
|
670
|
+
try:
|
671
|
+
del os.environ["GOLEM_SILENCE_LOGS"]
|
672
|
+
except Exception:
|
673
|
+
pass
|
635
674
|
|
636
675
|
|
637
676
|
@cli.group()
|
@@ -726,6 +765,8 @@ def connect_vm(name: str):
|
|
726
765
|
async def info_vm(name: str, as_json: bool):
|
727
766
|
"""Show information about a VM."""
|
728
767
|
try:
|
768
|
+
if as_json:
|
769
|
+
os.environ["GOLEM_SILENCE_LOGS"] = "1"
|
729
770
|
logger.command(f"ℹ️ Getting info for VM '{name}'")
|
730
771
|
|
731
772
|
# Initialize VM service
|
@@ -767,6 +808,12 @@ async def info_vm(name: str, as_json: bool):
|
|
767
808
|
except Exception as e:
|
768
809
|
logger.error(f"Failed to get VM info: {str(e)}")
|
769
810
|
raise click.Abort()
|
811
|
+
finally:
|
812
|
+
if as_json:
|
813
|
+
try:
|
814
|
+
del os.environ["GOLEM_SILENCE_LOGS"]
|
815
|
+
except Exception:
|
816
|
+
pass
|
770
817
|
|
771
818
|
|
772
819
|
@vm.command(name='destroy')
|
@@ -1054,6 +1101,8 @@ def run_api_server(host: str, port: int, reload: bool):
|
|
1054
1101
|
async def list_vms(as_json: bool):
|
1055
1102
|
"""List all VMs."""
|
1056
1103
|
try:
|
1104
|
+
if as_json:
|
1105
|
+
os.environ["GOLEM_SILENCE_LOGS"] = "1"
|
1057
1106
|
logger.command("📋 Listing your VMs")
|
1058
1107
|
logger.process("Fetching VM details")
|
1059
1108
|
|
@@ -1086,7 +1135,6 @@ async def list_vms(as_json: bool):
|
|
1086
1135
|
tablefmt="grid"
|
1087
1136
|
))
|
1088
1137
|
click.echo("\n" + "─" * 60)
|
1089
|
-
|
1090
1138
|
return result
|
1091
1139
|
|
1092
1140
|
except Exception as e:
|
@@ -1096,6 +1144,13 @@ async def list_vms(as_json: bool):
|
|
1096
1144
|
logger.error(f"Failed to list VMs: {error_msg}")
|
1097
1145
|
raise click.Abort()
|
1098
1146
|
|
1147
|
+
finally:
|
1148
|
+
if as_json:
|
1149
|
+
try:
|
1150
|
+
del os.environ["GOLEM_SILENCE_LOGS"]
|
1151
|
+
except Exception:
|
1152
|
+
pass
|
1153
|
+
|
1099
1154
|
|
1100
1155
|
def main():
|
1101
1156
|
"""Entry point for the CLI."""
|
requestor/config.py
CHANGED
@@ -4,6 +4,7 @@ import os
|
|
4
4
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
5
5
|
from pydantic import Field, field_validator, ValidationInfo
|
6
6
|
import os
|
7
|
+
import sys
|
7
8
|
|
8
9
|
|
9
10
|
def ensure_config() -> None:
|
@@ -33,7 +34,8 @@ def ensure_config() -> None:
|
|
33
34
|
created = True
|
34
35
|
|
35
36
|
if created:
|
36
|
-
|
37
|
+
# Write to stderr so stdout stays clean for JSON outputs
|
38
|
+
print("Using default settings – run with --help to customize", file=sys.stderr)
|
37
39
|
|
38
40
|
|
39
41
|
ensure_config()
|
requestor/provider/client.py
CHANGED
@@ -24,7 +24,7 @@ class ProviderClient:
|
|
24
24
|
ssh_key: str,
|
25
25
|
stream_id: int | None = None,
|
26
26
|
) -> Dict:
|
27
|
-
"""Create a VM on the provider."""
|
27
|
+
"""Create a VM on the provider (async job semantics)."""
|
28
28
|
payload = {
|
29
29
|
"name": name,
|
30
30
|
"resources": {
|
@@ -37,12 +37,29 @@ class ProviderClient:
|
|
37
37
|
if stream_id is not None:
|
38
38
|
payload["stream_id"] = int(stream_id)
|
39
39
|
async with self.session.post(
|
40
|
-
f"{self.provider_url}/api/v1/vms",
|
40
|
+
f"{self.provider_url}/api/v1/vms?async=true",
|
41
41
|
json=payload
|
42
42
|
) as response:
|
43
43
|
if not response.ok:
|
44
44
|
error_text = await response.text()
|
45
45
|
raise Exception(f"Failed to create VM: {error_text}")
|
46
|
+
data = await response.json()
|
47
|
+
# Normalize: support both old (VMInfo) and new (job) responses
|
48
|
+
# New shape: { job_id, vm_id, status }
|
49
|
+
# Old shape: { id, ... }
|
50
|
+
if isinstance(data, dict) and "job_id" in data:
|
51
|
+
return data
|
52
|
+
# Fallback: synthesize a job-like envelope from immediate VM info
|
53
|
+
vm_id = data.get("id") or data.get("name") or name
|
54
|
+
return {"job_id": "", "vm_id": vm_id, "status": data.get("status", "ready"), "_vm": data}
|
55
|
+
|
56
|
+
async def get_vm_info(self, vm_id: str) -> Dict:
|
57
|
+
async with self.session.get(
|
58
|
+
f"{self.provider_url}/api/v1/vms/{vm_id}"
|
59
|
+
) as response:
|
60
|
+
if not response.ok:
|
61
|
+
error_text = await response.text()
|
62
|
+
raise Exception(f"Failed to get VM info: {error_text}")
|
46
63
|
return await response.json()
|
47
64
|
|
48
65
|
async def get_provider_info(self) -> Dict:
|
requestor/run.py
CHANGED
@@ -4,6 +4,16 @@ import sys
|
|
4
4
|
from pathlib import Path
|
5
5
|
from dotenv import load_dotenv
|
6
6
|
|
7
|
+
if "--json" in sys.argv:
|
8
|
+
os.environ["GOLEM_SILENCE_LOGS"] = "1"
|
9
|
+
try:
|
10
|
+
import logging as _logging
|
11
|
+
_logging.getLogger().setLevel(_logging.CRITICAL)
|
12
|
+
_logging.getLogger('rlp').setLevel(_logging.CRITICAL)
|
13
|
+
_logging.getLogger('rlp.codec').setLevel(_logging.CRITICAL)
|
14
|
+
except Exception:
|
15
|
+
pass
|
16
|
+
|
7
17
|
from requestor.utils.logging import setup_logger
|
8
18
|
|
9
19
|
# Configure logging with debug mode from environment variable
|
@@ -66,6 +66,7 @@ class ProviderService:
|
|
66
66
|
memory: Optional[int] = None,
|
67
67
|
storage: Optional[int] = None,
|
68
68
|
country: Optional[str] = None,
|
69
|
+
platform: Optional[str] = None,
|
69
70
|
driver: Optional[str] = None,
|
70
71
|
payments_network: Optional[str] = None,
|
71
72
|
include_all_payments: bool = False,
|
@@ -82,12 +83,12 @@ class ProviderService:
|
|
82
83
|
private_key=private_key_bytes,
|
83
84
|
)
|
84
85
|
return await self._find_providers_golem_base(
|
85
|
-
cpu, memory, storage, country,
|
86
|
+
cpu, memory, storage, country, platform,
|
86
87
|
payments_network=payments_network,
|
87
88
|
include_all_payments=include_all_payments,
|
88
89
|
)
|
89
90
|
else:
|
90
|
-
return await self._find_providers_central(cpu, memory, storage, country)
|
91
|
+
return await self._find_providers_central(cpu, memory, storage, country, platform)
|
91
92
|
|
92
93
|
async def _find_providers_golem_base(
|
93
94
|
self,
|
@@ -95,6 +96,7 @@ class ProviderService:
|
|
95
96
|
memory: Optional[int] = None,
|
96
97
|
storage: Optional[int] = None,
|
97
98
|
country: Optional[str] = None,
|
99
|
+
platform: Optional[str] = None,
|
98
100
|
payments_network: Optional[str] = None,
|
99
101
|
include_all_payments: bool = False,
|
100
102
|
) -> List[Dict]:
|
@@ -124,6 +126,8 @@ class ProviderService:
|
|
124
126
|
query += f' && golem_storage>={storage}'
|
125
127
|
if country:
|
126
128
|
query += f' && golem_country="{country}"'
|
129
|
+
if platform:
|
130
|
+
query += f' && golem_platform="{platform}"'
|
127
131
|
|
128
132
|
results = await self.golem_base_client.query_entities(query)
|
129
133
|
|
@@ -142,6 +146,7 @@ class ProviderService:
|
|
142
146
|
'provider_name': annotations.get('golem_provider_name'),
|
143
147
|
'ip_address': annotations.get('golem_ip_address'),
|
144
148
|
'country': annotations.get('golem_country'),
|
149
|
+
'platform': annotations.get('golem_platform') or None,
|
145
150
|
'payments_network': annotations.get('golem_payments_network'),
|
146
151
|
'resources': {
|
147
152
|
'cpu': int(annotations.get('golem_cpu', 0)),
|
@@ -171,7 +176,8 @@ class ProviderService:
|
|
171
176
|
cpu: Optional[int] = None,
|
172
177
|
memory: Optional[int] = None,
|
173
178
|
storage: Optional[int] = None,
|
174
|
-
country: Optional[str] = None
|
179
|
+
country: Optional[str] = None,
|
180
|
+
platform: Optional[str] = None
|
175
181
|
) -> List[Dict]:
|
176
182
|
"""Find providers using the central discovery service."""
|
177
183
|
try:
|
@@ -181,7 +187,8 @@ class ProviderService:
|
|
181
187
|
'cpu': cpu,
|
182
188
|
'memory': memory,
|
183
189
|
'storage': storage,
|
184
|
-
'country': country
|
190
|
+
'country': country,
|
191
|
+
'platform': platform,
|
185
192
|
}.items() if v is not None
|
186
193
|
}
|
187
194
|
|
@@ -328,6 +335,7 @@ class ProviderService:
|
|
328
335
|
usd_storage if usd_storage is not None else '—',
|
329
336
|
est_usd,
|
330
337
|
est_glm,
|
338
|
+
(provider.get('platform') or '—'),
|
331
339
|
updated_at_str
|
332
340
|
]
|
333
341
|
|
@@ -358,6 +366,10 @@ class ProviderService:
|
|
358
366
|
# Format location info
|
359
367
|
row[3] = style(f"🌍 {row[3]}", fg="green", bold=True)
|
360
368
|
|
369
|
+
# Platform column: dim label
|
370
|
+
if row[12] != '—':
|
371
|
+
row[12] = style(f"{row[12]}", fg="white")
|
372
|
+
|
361
373
|
return row
|
362
374
|
|
363
375
|
@property
|
@@ -376,5 +388,6 @@ class ProviderService:
|
|
376
388
|
"USD/GB Disk/mo",
|
377
389
|
"Est. $/mo",
|
378
390
|
"Est. GLM/mo",
|
391
|
+
"Platform",
|
379
392
|
"Updated"
|
380
393
|
]
|
requestor/services/vm_service.py
CHANGED
@@ -39,8 +39,8 @@ class VMService:
|
|
39
39
|
if existing_vm:
|
40
40
|
raise VMError(f"VM with name '{name}' already exists")
|
41
41
|
|
42
|
-
# Create VM on provider
|
43
|
-
|
42
|
+
# Create VM on provider (returns job envelope)
|
43
|
+
job = await self.provider_client.create_vm(
|
44
44
|
name=name,
|
45
45
|
cpu=cpu,
|
46
46
|
memory=memory,
|
@@ -49,8 +49,41 @@ class VMService:
|
|
49
49
|
stream_id=stream_id
|
50
50
|
)
|
51
51
|
|
52
|
-
|
53
|
-
|
52
|
+
vm_id = job.get('vm_id') or name
|
53
|
+
|
54
|
+
# Save initial record with 'creating' status (no port yet)
|
55
|
+
await self.db.save_vm(
|
56
|
+
name=name,
|
57
|
+
provider_ip=provider_ip,
|
58
|
+
vm_id=vm_id,
|
59
|
+
config={
|
60
|
+
'cpu': cpu,
|
61
|
+
'memory': memory,
|
62
|
+
'storage': storage,
|
63
|
+
**({"stream_id": stream_id} if stream_id is not None else {}),
|
64
|
+
},
|
65
|
+
status='creating'
|
66
|
+
)
|
67
|
+
|
68
|
+
# Poll provider until VM is ready, then fetch access info
|
69
|
+
import asyncio as _asyncio
|
70
|
+
deadline = _asyncio.get_event_loop().time() + 600.0 # 10 minutes max
|
71
|
+
last_status = 'creating'
|
72
|
+
while _asyncio.get_event_loop().time() < deadline:
|
73
|
+
try:
|
74
|
+
info = await self.provider_client.get_vm_info(vm_id)
|
75
|
+
last_status = (info.get('status') or '').lower() or last_status
|
76
|
+
if last_status == 'running':
|
77
|
+
break
|
78
|
+
except Exception:
|
79
|
+
# Best-effort: ignore transient errors during startup
|
80
|
+
pass
|
81
|
+
await _asyncio.sleep(2.0)
|
82
|
+
if last_status != 'running':
|
83
|
+
raise VMError(f"VM did not become ready in time (status={last_status})")
|
84
|
+
|
85
|
+
# Get VM access info (ssh port)
|
86
|
+
access_info = await self.provider_client.get_vm_access(vm_id)
|
54
87
|
|
55
88
|
# Preserve any provided stream_id; do not auto-create streams here
|
56
89
|
# Stream creation should be explicit via CLI `vm stream open` command.
|
@@ -67,7 +100,8 @@ class VMService:
|
|
67
100
|
name=name,
|
68
101
|
provider_ip=provider_ip,
|
69
102
|
vm_id=access_info['vm_id'],
|
70
|
-
config=config
|
103
|
+
config=config,
|
104
|
+
status='running'
|
71
105
|
)
|
72
106
|
|
73
107
|
return {
|
requestor/utils/logging.py
CHANGED
@@ -61,6 +61,8 @@ def setup_logger(name: Optional[str] = None) -> logging.Logger:
|
|
61
61
|
|
62
62
|
# Check DEBUG environment variable
|
63
63
|
debug = os.getenv('DEBUG', '').lower() in ('1', 'true', 'yes')
|
64
|
+
# Global silence switch for JSON/machine outputs
|
65
|
+
silence = os.getenv('GOLEM_SILENCE_LOGS', '').lower() in ('1', 'true', 'yes')
|
64
66
|
|
65
67
|
# Prevent duplicate logs by removing root handlers
|
66
68
|
root = logging.getLogger()
|
@@ -86,13 +88,22 @@ def setup_logger(name: Optional[str] = None) -> logging.Logger:
|
|
86
88
|
style='%'
|
87
89
|
)
|
88
90
|
fancy_handler.setFormatter(fancy_formatter)
|
89
|
-
|
90
|
-
|
91
|
-
|
91
|
+
# Suppress DEBUG unless DEBUG=1; suppress everything if silence
|
92
|
+
def _filter(record: logging.LogRecord) -> bool:
|
93
|
+
if silence:
|
94
|
+
return False
|
95
|
+
return (record.levelno != DEBUG) or debug
|
96
|
+
fancy_handler.addFilter(_filter)
|
92
97
|
logger.addHandler(fancy_handler)
|
93
98
|
logger.propagate = False # Prevent propagation to avoid duplicates
|
94
99
|
|
95
|
-
if
|
100
|
+
if silence:
|
101
|
+
logger.setLevel(CRITICAL)
|
102
|
+
# Silence common libraries and root logger
|
103
|
+
logging.getLogger().setLevel(CRITICAL)
|
104
|
+
logging.getLogger('asyncio').setLevel(CRITICAL)
|
105
|
+
logging.getLogger('aiosqlite').setLevel(CRITICAL)
|
106
|
+
elif debug:
|
96
107
|
logger.setLevel(DEBUG)
|
97
108
|
# Enable debug logging for other libraries
|
98
109
|
logging.getLogger('asyncio').setLevel(DEBUG)
|
requestor/utils/spinner.py
CHANGED
@@ -11,26 +11,27 @@ class Spinner:
|
|
11
11
|
self.busy = False
|
12
12
|
self.spinner_visible = False
|
13
13
|
self.message = message
|
14
|
-
|
14
|
+
# Use stderr so stdout can remain machine-readable (e.g., --json outputs)
|
15
|
+
sys.stderr.write('\033[?25l') # Hide cursor
|
15
16
|
|
16
17
|
def write_next(self):
|
17
18
|
"""Write the next spinner frame."""
|
18
19
|
with self._screen_lock:
|
19
20
|
if not self.spinner_visible:
|
20
|
-
sys.
|
21
|
+
sys.stderr.write(f"\r{next(self.spinner)} {self.message}")
|
21
22
|
self.spinner_visible = True
|
22
|
-
sys.
|
23
|
+
sys.stderr.flush()
|
23
24
|
|
24
25
|
def remove_spinner(self, cleanup=False):
|
25
26
|
"""Remove the spinner from the terminal."""
|
26
27
|
with self._screen_lock:
|
27
28
|
if self.spinner_visible:
|
28
|
-
sys.
|
29
|
-
sys.
|
30
|
-
sys.
|
29
|
+
sys.stderr.write('\r')
|
30
|
+
sys.stderr.write(' ' * (len(self.message) + 2))
|
31
|
+
sys.stderr.write('\r')
|
31
32
|
if cleanup:
|
32
|
-
sys.
|
33
|
-
sys.
|
33
|
+
sys.stderr.write('\033[?25h') # Show cursor
|
34
|
+
sys.stderr.flush()
|
34
35
|
self.spinner_visible = False
|
35
36
|
|
36
37
|
def spinner_task(self):
|
@@ -56,11 +57,11 @@ class Spinner:
|
|
56
57
|
self.remove_spinner(cleanup=True)
|
57
58
|
if exc_type is None:
|
58
59
|
# Show checkmark on success
|
59
|
-
sys.
|
60
|
+
sys.stderr.write(f"\r✓ {self.message}\n")
|
60
61
|
else:
|
61
62
|
# Show X on failure
|
62
|
-
sys.
|
63
|
-
sys.
|
63
|
+
sys.stderr.write(f"\r✗ {self.message}\n")
|
64
|
+
sys.stderr.flush()
|
64
65
|
|
65
66
|
def step(message):
|
66
67
|
"""Decorator to add a spinning progress indicator to a function."""
|
File without changes
|
{request_vm_on_golem-0.1.53.dist-info → request_vm_on_golem-0.1.55.dist-info}/entry_points.txt
RENAMED
File without changes
|