request-vm-on-golem 0.1.46__tar.gz → 0.1.48__tar.gz

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.
Files changed (26) hide show
  1. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/PKG-INFO +1 -1
  2. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/pyproject.toml +1 -1
  3. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/cli/commands.py +42 -6
  4. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/payments/blockchain_service.py +12 -3
  5. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/README.md +0 -0
  6. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/__init__.py +0 -0
  7. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/api/main.py +0 -0
  8. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/cli/__init__.py +0 -0
  9. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/config.py +0 -0
  10. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/data/deployments/l2.json +0 -0
  11. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/db/__init__.py +0 -0
  12. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/db/sqlite.py +0 -0
  13. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/errors.py +0 -0
  14. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/provider/__init__.py +0 -0
  15. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/provider/client.py +0 -0
  16. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/run.py +0 -0
  17. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/security/faucet.py +0 -0
  18. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/services/__init__.py +0 -0
  19. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/services/database_service.py +0 -0
  20. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/services/provider_service.py +0 -0
  21. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/services/ssh_service.py +0 -0
  22. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/services/vm_service.py +0 -0
  23. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/ssh/__init__.py +0 -0
  24. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/ssh/manager.py +0 -0
  25. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/utils/logging.py +0 -0
  26. {request_vm_on_golem-0.1.46 → request_vm_on_golem-0.1.48}/requestor/utils/spinner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: request-vm-on-golem
3
- Version: 0.1.46
3
+ Version: 0.1.48
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,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "request-vm-on-golem"
3
- version = "0.1.46"
3
+ version = "0.1.48"
4
4
  description = "VM on Golem Requestor CLI - Create and manage virtual machines on the Golem Network"
5
5
  authors = ["Phillip Jensen <phillip+vm-on-golem@golemgrid.com>"]
6
6
  readme = "README.md"
@@ -186,10 +186,11 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
186
186
  @click.option('--memory', type=int, required=True, help='Memory in GB')
187
187
  @click.option('--storage', type=int, required=True, help='Disk in GB')
188
188
  @click.option('--stream-id', type=int, default=None, help='Optional StreamPayment stream id to fund this VM')
189
+ @click.option('--hours', type=int, default=1, help='If no stream-id is provided and payments are enabled, open a stream with this many hours of deposit (default 1)')
189
190
  @click.option('--yes', is_flag=True, help='Do not prompt for confirmation')
190
191
  @click.option('--network', type=click.Choice(['testnet', 'mainnet']), default=None, help='Override network for discovery during creation')
191
192
  @async_command
192
- async def create_vm(name: str, provider_id: str, cpu: int, memory: int, storage: int, stream_id: int | None, yes: bool, network: Optional[str] = None):
193
+ async def create_vm(name: str, provider_id: str, cpu: int, memory: int, storage: int, stream_id: int | None, hours: int, yes: bool, network: Optional[str] = None):
193
194
  """Create a new VM on a specific provider."""
194
195
  try:
195
196
  if network:
@@ -227,10 +228,7 @@ async def create_vm(name: str, provider_id: str, cpu: int, memory: int, storage:
227
228
  if est_glm != '—':
228
229
  price_str += f" (~{est_glm} GLM/mo)"
229
230
  click.echo(click.style(f" 💵 Estimated Monthly Cost: {price_str}", fg='yellow', bold=True))
230
- if not yes:
231
- if not click.confirm("Proceed with VM creation?", default=True):
232
- logger.warning("Creation cancelled by user")
233
- return
231
+ # For streamlined UX, proceed without an interactive confirmation
234
232
 
235
233
  # Setup SSH
236
234
  ssh_service = SSHService(config.ssh_key_dir)
@@ -239,8 +237,46 @@ async def create_vm(name: str, provider_id: str, cpu: int, memory: int, storage:
239
237
  # Initialize VM service
240
238
  provider_url = config.get_provider_url(provider_ip)
241
239
  async with ProviderClient(provider_url) as client:
240
+ # Fetch provider info if available (for preferred contract addresses); proceed regardless
241
+ info = None
242
+ try:
243
+ info = await client.get_provider_info()
244
+ except Exception:
245
+ info = None
246
+ # Always auto-open a stream when none provided (assume streaming required by default)
247
+ if stream_id is None:
248
+ # Compute rate from provider pricing
249
+ est = provider_service.compute_estimate(provider, (cpu, memory, storage))
250
+ if not est or est.get('glm_per_month') is None:
251
+ raise RequestorError('Provider requires streaming but does not advertise GLM pricing; cannot compute ratePerSecond')
252
+ glm_month = est['glm_per_month']
253
+ glm_per_second = float(glm_month) / (730.0 * 3600.0)
254
+ rate_per_second_wei = int(glm_per_second * (10**18))
255
+ deposit_wei = rate_per_second_wei * int(hours) * 3600
256
+ # Auto-fund via faucet if needed (testnets), then create stream
257
+ try:
258
+ from eth_account import Account
259
+ from ..security.faucet import L2FaucetService
260
+ acct = Account.from_key(config.ethereum_private_key)
261
+ faucet = L2FaucetService(config)
262
+ await faucet.request_funds(acct.address)
263
+ except Exception:
264
+ # Non-fatal; stream creation may still succeed if already funded
265
+ pass
266
+ # Open stream on-chain
267
+ from ..payments.blockchain_service import StreamPaymentClient, StreamPaymentConfig
268
+ spc = StreamPaymentConfig(
269
+ rpc_url=config.polygon_rpc_url,
270
+ contract_address=(info.get('stream_payment_address') if info else None) or config.stream_payment_address,
271
+ glm_token_address=(info.get('glm_token_address') if info else None) or config.glm_token_address,
272
+ private_key=config.ethereum_private_key,
273
+ )
274
+ sp_client = StreamPaymentClient(spc)
275
+ recipient = (info.get('provider_id') if info else None) or provider_id
276
+ stream_id = sp_client.create_stream(recipient, int(deposit_wei), int(rate_per_second_wei))
277
+ logger.success(f"Opened stream id={stream_id} (hours={hours})")
278
+
242
279
  vm_service = VMService(db_service, ssh_service, client)
243
-
244
280
  # Create VM
245
281
  vm = await vm_service.create_vm(
246
282
  name=name,
@@ -66,7 +66,10 @@ class StreamPaymentClient:
66
66
  # In production, sign and send raw; in tests, Account may be a dummy without signer
67
67
  if hasattr(self.account, "sign_transaction"):
68
68
  signed = self.account.sign_transaction(tx)
69
- tx_hash = self.web3.eth.send_raw_transaction(signed.rawTransaction)
69
+ raw = getattr(signed, "rawTransaction", None) or getattr(signed, "raw_transaction", None)
70
+ if raw is None:
71
+ raise RuntimeError("sign_transaction did not return raw transaction bytes")
72
+ tx_hash = self.web3.eth.send_raw_transaction(raw)
70
73
  else:
71
74
  tx_hash = self.web3.eth.send_transaction(tx)
72
75
  receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash)
@@ -105,7 +108,10 @@ class StreamPaymentClient:
105
108
  }
106
109
  tx = fn.build_transaction(base)
107
110
  signed = self.account.sign_transaction(tx)
108
- tx_hash = self.web3.eth.send_raw_transaction(signed.rawTransaction)
111
+ raw = getattr(signed, "rawTransaction", None) or getattr(signed, "raw_transaction", None)
112
+ if raw is None:
113
+ raise RuntimeError("sign_transaction did not return raw transaction bytes")
114
+ tx_hash = self.web3.eth.send_raw_transaction(raw)
109
115
  receipt = self.web3.eth.wait_for_transaction_receipt(tx_hash)
110
116
  tx_receipt = {"transactionHash": tx_hash.hex(), "status": receipt.status, "logs": receipt.logs}
111
117
  else:
@@ -156,6 +162,9 @@ class StreamPaymentClient:
156
162
  }
157
163
  tx = fn.build_transaction(base)
158
164
  signed = self.account.sign_transaction(tx)
159
- tx_hash = self.web3.eth.send_raw_transaction(signed.rawTransaction)
165
+ raw = getattr(signed, "rawTransaction", None) or getattr(signed, "raw_transaction", None)
166
+ if raw is None:
167
+ raise RuntimeError("sign_transaction did not return raw transaction bytes")
168
+ tx_hash = self.web3.eth.send_raw_transaction(raw)
160
169
  self.web3.eth.wait_for_transaction_receipt(tx_hash)
161
170
  return tx_hash.hex()