request-vm-on-golem 0.1.52__tar.gz → 0.1.54__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 (27) hide show
  1. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/PKG-INFO +7 -1
  2. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/README.md +6 -0
  3. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/pyproject.toml +1 -1
  4. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/cli/commands.py +81 -2
  5. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/config.py +3 -1
  6. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/run.py +10 -0
  7. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/utils/logging.py +15 -4
  8. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/utils/spinner.py +12 -11
  9. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/__init__.py +0 -0
  10. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/api/main.py +0 -0
  11. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/cli/__init__.py +0 -0
  12. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/data/deployments/l2.json +0 -0
  13. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/db/__init__.py +0 -0
  14. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/db/sqlite.py +0 -0
  15. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/errors.py +0 -0
  16. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/payments/blockchain_service.py +0 -0
  17. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/payments/monitor.py +0 -0
  18. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/provider/__init__.py +0 -0
  19. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/provider/client.py +0 -0
  20. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/security/faucet.py +0 -0
  21. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/services/__init__.py +0 -0
  22. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/services/database_service.py +0 -0
  23. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/services/provider_service.py +0 -0
  24. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/services/ssh_service.py +0 -0
  25. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/services/vm_service.py +0 -0
  26. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/ssh/__init__.py +0 -0
  27. {request_vm_on_golem-0.1.52 → request_vm_on_golem-0.1.54}/requestor/ssh/manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: request-vm-on-golem
3
- Version: 0.1.52
3
+ Version: 0.1.54
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
@@ -69,6 +69,12 @@ golem vm create my-vm --provider-id 0xProvider --cpu 2 --memory 4 --storage 20
69
69
  golem vm ssh my-vm
70
70
  ```
71
71
 
72
+ Check your installed version and whether an update is available:
73
+
74
+ ```bash
75
+ golem version
76
+ ```
77
+
72
78
  5) Stop or destroy when done:
73
79
 
74
80
  ```bash
@@ -28,6 +28,12 @@ golem vm create my-vm --provider-id 0xProvider --cpu 2 --memory 4 --storage 20
28
28
  golem vm ssh my-vm
29
29
  ```
30
30
 
31
+ Check your installed version and whether an update is available:
32
+
33
+ ```bash
34
+ golem version
35
+ ```
36
+
31
37
  5) Stop or destroy when done:
32
38
 
33
39
  ```bash
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "request-vm-on-golem"
3
- version = "0.1.52"
3
+ version = "0.1.54"
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"
@@ -5,6 +5,7 @@ import json
5
5
  from typing import Optional
6
6
  from pathlib import Path
7
7
  import subprocess
8
+ import os
8
9
  import aiohttp
9
10
  from tabulate import tabulate
10
11
  import uvicorn
@@ -89,6 +90,33 @@ def cli(network: str | None):
89
90
  pass
90
91
 
91
92
 
93
+ @cli.command(name="version")
94
+ def version_cmd():
95
+ """Show installed and latest versions from PyPI."""
96
+ pkg = "request-vm-on-golem"
97
+ try:
98
+ current = metadata.version(pkg)
99
+ except Exception:
100
+ current = "unknown"
101
+ latest = None
102
+ # Avoid network during pytest
103
+ if not os.environ.get("PYTEST_CURRENT_TEST"):
104
+ try:
105
+ import json as _json
106
+ from urllib.request import urlopen
107
+ with urlopen(f"https://pypi.org/pypi/{pkg}/json", timeout=5) as resp:
108
+ data = _json.loads(resp.read().decode("utf-8"))
109
+ latest = data.get("info", {}).get("version")
110
+ except Exception:
111
+ latest = None
112
+
113
+ if latest and latest != current:
114
+ click.echo(f"Requestor CLI: {current} (update available: {latest})")
115
+ click.echo("Update: pip install -U request-vm-on-golem")
116
+ else:
117
+ click.echo(f"Requestor CLI: {current} (up-to-date)" if latest else f"Requestor CLI: {current}")
118
+
119
+
92
120
  @cli.group()
93
121
  def vm():
94
122
  """VM management commands"""
@@ -110,6 +138,8 @@ def vm():
110
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):
111
139
  """List available providers matching requirements."""
112
140
  try:
141
+ if as_json:
142
+ os.environ["GOLEM_SILENCE_LOGS"] = "1"
113
143
  if network:
114
144
  config.network = network
115
145
  # Log search criteria if any
@@ -153,7 +183,10 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
153
183
 
154
184
  if not providers:
155
185
  logger.warning("No providers found matching criteria")
156
- return {"providers": []}
186
+ result = {"providers": []}
187
+ if as_json:
188
+ click.echo(json.dumps(result, indent=2))
189
+ return result
157
190
 
158
191
  # If JSON requested and full spec provided, include estimates per provider
159
192
  if as_json and cpu and memory and storage:
@@ -188,6 +221,12 @@ async def list_providers(cpu: Optional[int], memory: Optional[int], storage: Opt
188
221
  except Exception as e:
189
222
  logger.error(f"Failed to list providers: {str(e)}")
190
223
  raise click.Abort()
224
+ finally:
225
+ if as_json:
226
+ try:
227
+ del os.environ["GOLEM_SILENCE_LOGS"]
228
+ except Exception:
229
+ pass
191
230
 
192
231
 
193
232
  @vm.command(name='create')
@@ -357,6 +396,8 @@ def vm_stream():
357
396
  async def stream_list(as_json: bool):
358
397
  """List payment stream status for all known VMs."""
359
398
  try:
399
+ if as_json:
400
+ os.environ["GOLEM_SILENCE_LOGS"] = "1"
360
401
  vms = await db_service.list_vms()
361
402
  if not vms:
362
403
  logger.warning("No VMs found in local database")
@@ -433,6 +474,12 @@ async def stream_list(as_json: bool):
433
474
  except Exception as e:
434
475
  logger.error(f"Failed to list streams: {e}")
435
476
  raise click.Abort()
477
+ finally:
478
+ if as_json:
479
+ try:
480
+ del os.environ["GOLEM_SILENCE_LOGS"]
481
+ except Exception:
482
+ pass
436
483
 
437
484
 
438
485
  @vm_stream.command('open')
@@ -522,6 +569,8 @@ async def stream_topup(stream_id: int, glm: float | None, hours: int | None):
522
569
  async def stream_status(name: str, as_json: bool):
523
570
  """Show the payment stream status for a VM by name."""
524
571
  try:
572
+ if as_json:
573
+ os.environ["GOLEM_SILENCE_LOGS"] = "1"
525
574
  # Resolve VM and provider
526
575
  vm = await db_service.get_vm(name)
527
576
  if not vm:
@@ -552,6 +601,12 @@ async def stream_status(name: str, as_json: bool):
552
601
  except Exception as e:
553
602
  logger.error(f"Failed to fetch stream status: {e}")
554
603
  raise click.Abort()
604
+ finally:
605
+ if as_json:
606
+ try:
607
+ del os.environ["GOLEM_SILENCE_LOGS"]
608
+ except Exception:
609
+ pass
555
610
 
556
611
 
557
612
  @vm_stream.command('inspect')
@@ -561,6 +616,8 @@ async def stream_status(name: str, as_json: bool):
561
616
  async def stream_inspect(stream_id: int, as_json: bool):
562
617
  """Inspect a stream directly on-chain (no provider required)."""
563
618
  try:
619
+ if as_json:
620
+ os.environ["GOLEM_SILENCE_LOGS"] = "1"
564
621
  from web3 import Web3
565
622
  from golem_streaming_abi import STREAM_PAYMENT_ABI
566
623
  w3 = Web3(Web3.HTTPProvider(config.polygon_rpc_url))
@@ -604,6 +661,12 @@ async def stream_inspect(stream_id: int, as_json: bool):
604
661
  except Exception as e:
605
662
  logger.error(f"Failed to inspect stream: {e}")
606
663
  raise click.Abort()
664
+ finally:
665
+ if as_json:
666
+ try:
667
+ del os.environ["GOLEM_SILENCE_LOGS"]
668
+ except Exception:
669
+ pass
607
670
 
608
671
 
609
672
  @cli.group()
@@ -698,6 +761,8 @@ def connect_vm(name: str):
698
761
  async def info_vm(name: str, as_json: bool):
699
762
  """Show information about a VM."""
700
763
  try:
764
+ if as_json:
765
+ os.environ["GOLEM_SILENCE_LOGS"] = "1"
701
766
  logger.command(f"ℹ️ Getting info for VM '{name}'")
702
767
 
703
768
  # Initialize VM service
@@ -739,6 +804,12 @@ async def info_vm(name: str, as_json: bool):
739
804
  except Exception as e:
740
805
  logger.error(f"Failed to get VM info: {str(e)}")
741
806
  raise click.Abort()
807
+ finally:
808
+ if as_json:
809
+ try:
810
+ del os.environ["GOLEM_SILENCE_LOGS"]
811
+ except Exception:
812
+ pass
742
813
 
743
814
 
744
815
  @vm.command(name='destroy')
@@ -1026,6 +1097,8 @@ def run_api_server(host: str, port: int, reload: bool):
1026
1097
  async def list_vms(as_json: bool):
1027
1098
  """List all VMs."""
1028
1099
  try:
1100
+ if as_json:
1101
+ os.environ["GOLEM_SILENCE_LOGS"] = "1"
1029
1102
  logger.command("📋 Listing your VMs")
1030
1103
  logger.process("Fetching VM details")
1031
1104
 
@@ -1058,7 +1131,6 @@ async def list_vms(as_json: bool):
1058
1131
  tablefmt="grid"
1059
1132
  ))
1060
1133
  click.echo("\n" + "─" * 60)
1061
-
1062
1134
  return result
1063
1135
 
1064
1136
  except Exception as e:
@@ -1068,6 +1140,13 @@ async def list_vms(as_json: bool):
1068
1140
  logger.error(f"Failed to list VMs: {error_msg}")
1069
1141
  raise click.Abort()
1070
1142
 
1143
+ finally:
1144
+ if as_json:
1145
+ try:
1146
+ del os.environ["GOLEM_SILENCE_LOGS"]
1147
+ except Exception:
1148
+ pass
1149
+
1071
1150
 
1072
1151
  def main():
1073
1152
  """Entry point for the CLI."""
@@ -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
- print("Using default settings run with --help to customize")
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()
@@ -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
@@ -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
- fancy_handler.addFilter(
90
- lambda record: record.levelno != DEBUG or debug
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 debug:
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)
@@ -11,26 +11,27 @@ class Spinner:
11
11
  self.busy = False
12
12
  self.spinner_visible = False
13
13
  self.message = message
14
- sys.stdout.write('\033[?25l') # Hide cursor
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.stdout.write(f"\r{next(self.spinner)} {self.message}")
21
+ sys.stderr.write(f"\r{next(self.spinner)} {self.message}")
21
22
  self.spinner_visible = True
22
- sys.stdout.flush()
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.stdout.write('\r')
29
- sys.stdout.write(' ' * (len(self.message) + 2))
30
- sys.stdout.write('\r')
29
+ sys.stderr.write('\r')
30
+ sys.stderr.write(' ' * (len(self.message) + 2))
31
+ sys.stderr.write('\r')
31
32
  if cleanup:
32
- sys.stdout.write('\033[?25h') # Show cursor
33
- sys.stdout.flush()
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.stdout.write(f"\r✓ {self.message}\n")
60
+ sys.stderr.write(f"\r✓ {self.message}\n")
60
61
  else:
61
62
  # Show X on failure
62
- sys.stdout.write(f"\r✗ {self.message}\n")
63
- sys.stdout.flush()
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."""