request-vm-on-golem 0.1.54__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: request-vm-on-golem
3
- Version: 0.1.54
3
+ Version: 0.1.55
4
4
  Summary: VM on Golem Requestor CLI - Create and manage virtual machines on the Golem Network
5
5
  Keywords: golem,vm,cloud,decentralized,cli
6
6
  Author: Phillip Jensen
@@ -1,7 +1,7 @@
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=fKZFsW4d60VYF5wxZnMxZLc0Tqod087P_NjfCQyDfRI,50998
4
+ requestor/cli/commands.py,sha256=Obr8Z1ZX3f6WThooIInlD7HqMl_pJmOqawzazLmhke0,51253
5
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
@@ -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=pfJymufYR13W4kfykHZSVvs6ikRUE5AdHp0W0DB17AE,4130
13
+ requestor/provider/client.py,sha256=bWj4sNQ8w4F2sSRcMlHbWVjeBLAflDhHxy0BNMqzj8s,5021
14
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=UjCg6YOwhkclJ227gXfI5uCTinhBv6Jjfsgxq0x0ESo,15701
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=1EUypRbCykdQTVJf4gYiNzkZNk66T3df32Vc51HkSMI,7983
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
23
  requestor/utils/logging.py,sha256=lgAswzYvO9M0EOET0cFZvuAsGI4lInh_wln_6bI-fJk,4281
24
24
  requestor/utils/spinner.py,sha256=X0jfPfs5ricglTS4_XmacrM2Z1DDHR7zGk2KqYZDpXg,2541
25
- request_vm_on_golem-0.1.54.dist-info/METADATA,sha256=STyVVfE84_1K_a-HlKflEM-hZHQJhYo-vQm7vcwtbRQ,15780
26
- request_vm_on_golem-0.1.54.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
27
- request_vm_on_golem-0.1.54.dist-info/entry_points.txt,sha256=Z-skRNpJ8aZcIl_En9mEm1ygkp9FKy0bzQoL3zO52-0,44
28
- request_vm_on_golem-0.1.54.dist-info/RECORD,,
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,7 +136,7 @@ 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:
141
142
  if as_json:
@@ -143,7 +144,7 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
143
144
  if network:
144
145
  config.network = network
145
146
  # Log search criteria if any
146
- if any([cpu, memory, storage, country]):
147
+ if any([cpu, memory, storage, country, platform]):
147
148
  logger.command("🔍 Searching for providers with criteria:")
148
149
  if cpu:
149
150
  logger.detail(f"CPU Cores: {cpu}+")
@@ -153,6 +154,8 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
153
154
  logger.detail(f"Disk: {storage}GB+")
154
155
  if country:
155
156
  logger.detail(f"Country: {country}")
157
+ if platform:
158
+ logger.detail(f"Platform: {platform}")
156
159
 
157
160
  # Determine the discovery driver being used
158
161
  discovery_driver = driver or config.discovery_driver
@@ -171,6 +174,7 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
171
174
  memory=memory,
172
175
  storage=storage,
173
176
  country=country,
177
+ platform=platform,
174
178
  driver=driver,
175
179
  payments_network=eff_pn,
176
180
  include_all_payments=bool(all_payments),
@@ -178,7 +182,7 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
178
182
  except TypeError:
179
183
  # Backward compatibility with older/dummy service stubs in tests
180
184
  providers = await provider_service.find_providers(
181
- cpu=cpu, memory=memory, storage=storage, country=country, driver=driver
185
+ cpu=cpu, memory=memory, storage=storage, country=country, platform=platform, driver=driver
182
186
  )
183
187
 
184
188
  if not providers:
@@ -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:
@@ -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
  ]
@@ -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
- vm = await self.provider_client.create_vm(
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
- # Get VM access info
53
- access_info = await self.provider_client.get_vm_access(vm['id'])
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 {