golem-vm-provider 0.1.16__py3-none-any.whl → 0.1.17__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.
- {golem_vm_provider-0.1.16.dist-info → golem_vm_provider-0.1.17.dist-info}/METADATA +1 -1
- {golem_vm_provider-0.1.16.dist-info → golem_vm_provider-0.1.17.dist-info}/RECORD +6 -6
- provider/config.py +54 -8
- provider/vm/cloud_init.py +43 -4
- {golem_vm_provider-0.1.16.dist-info → golem_vm_provider-0.1.17.dist-info}/WHEEL +0 -0
- {golem_vm_provider-0.1.16.dist-info → golem_vm_provider-0.1.17.dist-info}/entry_points.txt +0 -0
@@ -2,7 +2,7 @@ provider/__init__.py,sha256=HO1fkPpZqPO3z8O8-eVIyx8xXSMIVuTR_b1YF0RtXOg,45
|
|
2
2
|
provider/api/__init__.py,sha256=ssX1ugDqEPt8Fn04IymgmG-Ev8PiXLsCSaiZVvHQnec,344
|
3
3
|
provider/api/models.py,sha256=JOzoNf1oE5N97UqTN5xuIrTkqn2tCHqPDaIzGA3jUyo,3513
|
4
4
|
provider/api/routes.py,sha256=P27RQvNqFWn6PacRwr1PaVz-yv5KAWsp9KeORejkXSI,6452
|
5
|
-
provider/config.py,sha256=
|
5
|
+
provider/config.py,sha256=btSyU6cB6z8o9nHWpNEOBDxKqsEha1yStM-6of3Np0s,13237
|
6
6
|
provider/discovery/__init__.py,sha256=VR3NRoQtZRH5Vs8FG7jnGLR7p7wn7XeZdLaBb3t8e1g,123
|
7
7
|
provider/discovery/advertiser.py,sha256=yv7RbRf1K43qOLAEa2Olj9hhN8etl2qsBuoHok0xoVs,6784
|
8
8
|
provider/discovery/resource_tracker.py,sha256=8dYhJxoe_jLRwisHoA0jr575YhUKmLIqSXfW88KshcQ,6000
|
@@ -14,13 +14,13 @@ provider/utils/logging.py,sha256=C_elr0sJROHKQgErYpHJQvfujgh0k4Zf2gg8ZKfrmVk,259
|
|
14
14
|
provider/utils/port_display.py,sha256=5d_604Eo-82dqx_yV2ZScq7bKQ8IsXacc-yXC_KAz3A,11031
|
15
15
|
provider/utils/retry.py,sha256=ekP2ucaSJNN-lBcrIvyHa4QYPKNITMl1a5V1X6BBvsw,1560
|
16
16
|
provider/vm/__init__.py,sha256=JGs50tUmzOR1rQ_w4fMY_3XWylmiA1G7KKWZkVw51mY,501
|
17
|
-
provider/vm/cloud_init.py,sha256=
|
17
|
+
provider/vm/cloud_init.py,sha256=E5dDH7dqStRcJNDfbarBBe83-c9N63W8B5ycIrHI8eU,4627
|
18
18
|
provider/vm/models.py,sha256=zkfvP5Z50SPDNajwZTt9NTDIMRQIsZLvSOsuirHEcJM,6256
|
19
19
|
provider/vm/multipass.py,sha256=FOcsfcJ-NrgBg_fvq_CKOKsQ0xOmk7Z34KXi3ag_Vl8,16603
|
20
20
|
provider/vm/name_mapper.py,sha256=MrshNeJ4Dw-WBsyiIVcn9N5xyOxaBKX4Yqhyh_m5IFg,4103
|
21
21
|
provider/vm/port_manager.py,sha256=d03uwU76vx6LgADMN8ffBT9t400XQ3vtYlXr6cLIFN0,9831
|
22
22
|
provider/vm/proxy_manager.py,sha256=cu0FPPbeCc3CR6NRE_CnLjiRg7xVdSFUylVUOL1g1sI,10154
|
23
|
-
golem_vm_provider-0.1.
|
24
|
-
golem_vm_provider-0.1.
|
25
|
-
golem_vm_provider-0.1.
|
26
|
-
golem_vm_provider-0.1.
|
23
|
+
golem_vm_provider-0.1.17.dist-info/METADATA,sha256=Lu9EpHlgFmOQP56l89vLyowyM0j48TI5BTGfT_EQxtQ,10594
|
24
|
+
golem_vm_provider-0.1.17.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
25
|
+
golem_vm_provider-0.1.17.dist-info/entry_points.txt,sha256=E4rCWo_Do_2zCG_GewNuftfVlHF_8b_OvioZre0dfeA,54
|
26
|
+
golem_vm_provider-0.1.17.dist-info/RECORD,,
|
provider/config.py
CHANGED
@@ -57,26 +57,72 @@ class Settings(BaseSettings):
|
|
57
57
|
VM_DATA_DIR: str = ""
|
58
58
|
SSH_KEY_DIR: str = ""
|
59
59
|
CLOUD_INIT_DIR: str = ""
|
60
|
+
CLOUD_INIT_FALLBACK_DIR: str = "" # Will be set to a temp directory if needed
|
60
61
|
|
61
62
|
@validator("CLOUD_INIT_DIR", pre=True)
|
62
63
|
def resolve_cloud_init_dir(cls, v: str) -> str:
|
63
64
|
"""Resolve and create cloud-init directory path."""
|
64
|
-
|
65
|
-
|
66
|
-
|
65
|
+
import platform
|
66
|
+
import tempfile
|
67
|
+
|
68
|
+
def verify_dir_permissions(path: Path) -> bool:
|
69
|
+
"""Verify directory has correct permissions and is accessible."""
|
70
|
+
try:
|
71
|
+
# Create test file
|
72
|
+
test_file = path / "permission_test"
|
73
|
+
test_file.write_text("test")
|
74
|
+
test_file.unlink()
|
75
|
+
return True
|
76
|
+
except Exception:
|
77
|
+
return False
|
78
|
+
|
79
|
+
if v:
|
67
80
|
path = Path(v)
|
68
81
|
if not path.is_absolute():
|
69
82
|
path = Path.home() / path
|
70
|
-
|
83
|
+
else:
|
84
|
+
system = platform.system().lower()
|
85
|
+
# Try OS-specific paths first
|
86
|
+
if system == "linux" and Path("/snap/bin/multipass").exists():
|
87
|
+
# Linux with snap
|
88
|
+
path = Path("/var/snap/multipass/common/cloud-init")
|
89
|
+
elif system == "linux":
|
90
|
+
# Linux without snap
|
91
|
+
path = Path("/var/lib/multipass/cloud-init")
|
92
|
+
elif system == "darwin":
|
93
|
+
# macOS
|
94
|
+
path = Path("/Library/Application Support/multipass/cloud-init")
|
95
|
+
elif system == "windows":
|
96
|
+
# Windows
|
97
|
+
path = Path(os.path.expandvars("%ProgramData%\\Multipass\\cloud-init"))
|
98
|
+
else:
|
99
|
+
path = Path.home() / ".golem" / "provider" / "cloud-init"
|
100
|
+
|
71
101
|
try:
|
102
|
+
# Try to create and verify the directory
|
72
103
|
path.mkdir(parents=True, exist_ok=True)
|
73
|
-
|
74
|
-
|
104
|
+
if platform.system().lower() != "windows":
|
105
|
+
path.chmod(0o755) # Readable and executable by owner and others, writable by owner
|
106
|
+
|
107
|
+
if verify_dir_permissions(path):
|
108
|
+
logger.debug(f"Created cloud-init directory at {path}")
|
109
|
+
return str(path)
|
110
|
+
|
111
|
+
# If verification fails, fall back to temp directory
|
112
|
+
fallback_path = Path(tempfile.gettempdir()) / "golem" / "cloud-init"
|
113
|
+
fallback_path.mkdir(parents=True, exist_ok=True)
|
114
|
+
if platform.system().lower() != "windows":
|
115
|
+
fallback_path.chmod(0o755)
|
116
|
+
|
117
|
+
if verify_dir_permissions(fallback_path):
|
118
|
+
logger.warning(f"Using fallback cloud-init directory at {fallback_path}")
|
119
|
+
return str(fallback_path)
|
120
|
+
|
121
|
+
raise ValueError("Could not create a writable cloud-init directory")
|
122
|
+
|
75
123
|
except Exception as e:
|
76
124
|
logger.error(f"Failed to create cloud-init directory at {path}: {e}")
|
77
125
|
raise ValueError(f"Failed to create cloud-init directory: {e}")
|
78
|
-
|
79
|
-
return str(path)
|
80
126
|
|
81
127
|
@validator("VM_DATA_DIR", pre=True)
|
82
128
|
def resolve_vm_data_dir(cls, v: str) -> str:
|
provider/vm/cloud_init.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import yaml
|
2
2
|
import os
|
3
|
+
import subprocess
|
3
4
|
from datetime import datetime
|
4
5
|
from pathlib import Path
|
5
6
|
from typing import Dict, Optional, Tuple
|
@@ -9,6 +10,29 @@ from ..utils.logging import setup_logger
|
|
9
10
|
|
10
11
|
logger = setup_logger(__name__)
|
11
12
|
|
13
|
+
def validate_cloud_init(content: str) -> bool:
|
14
|
+
"""Validate cloud-init configuration content.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
content: YAML content to validate
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
True if valid, False otherwise
|
21
|
+
"""
|
22
|
+
try:
|
23
|
+
# First validate YAML syntax
|
24
|
+
yaml.safe_load(content)
|
25
|
+
|
26
|
+
# Check for required #cloud-config header
|
27
|
+
if not content.startswith("#cloud-config\n"):
|
28
|
+
logger.error("Cloud-init config missing #cloud-config header")
|
29
|
+
return False
|
30
|
+
|
31
|
+
return True
|
32
|
+
except Exception as e:
|
33
|
+
logger.error(f"Cloud-init validation failed: {e}")
|
34
|
+
return False
|
35
|
+
|
12
36
|
def generate_cloud_init(
|
13
37
|
hostname: str,
|
14
38
|
ssh_key: str,
|
@@ -32,10 +56,15 @@ def generate_cloud_init(
|
|
32
56
|
|
33
57
|
logger.info(f"Generating cloud-init configuration {config_id}")
|
34
58
|
try:
|
59
|
+
# Start with required #cloud-config header
|
60
|
+
yaml_content = "#cloud-config\n"
|
61
|
+
|
35
62
|
config = {
|
63
|
+
"version": 1,
|
36
64
|
"hostname": hostname,
|
37
65
|
"package_update": True,
|
38
66
|
"package_upgrade": True,
|
67
|
+
"preserve_hostname": False,
|
39
68
|
"ssh_authorized_keys": [ssh_key],
|
40
69
|
"users": [{
|
41
70
|
"name": "root",
|
@@ -60,16 +89,21 @@ def generate_cloud_init(
|
|
60
89
|
if runcmd:
|
61
90
|
config["runcmd"].extend(runcmd)
|
62
91
|
|
63
|
-
#
|
64
|
-
yaml_content
|
65
|
-
yaml.
|
92
|
+
# Add config to YAML content with document markers
|
93
|
+
yaml_content += "---\n"
|
94
|
+
yaml_content += yaml.safe_dump(config, default_flow_style=False, sort_keys=False)
|
95
|
+
|
96
|
+
# Validate the configuration
|
97
|
+
if not validate_cloud_init(yaml_content):
|
98
|
+
raise Exception("Cloud-init configuration validation failed")
|
66
99
|
|
67
100
|
# Write to file in our managed directory
|
68
101
|
with open(config_path, 'w') as f:
|
69
102
|
f.write(yaml_content)
|
70
103
|
|
71
104
|
# Set proper permissions
|
72
|
-
|
105
|
+
if os.name != 'nt': # Skip on Windows
|
106
|
+
config_path.chmod(0o644) # World readable but only owner writable
|
73
107
|
|
74
108
|
logger.debug(f"Cloud-init configuration written to {config_path}")
|
75
109
|
logger.debug(f"Cloud-init configuration content:\n{yaml_content}")
|
@@ -82,6 +116,11 @@ def generate_cloud_init(
|
|
82
116
|
# Don't cleanup on error - keep file for debugging
|
83
117
|
if config_path.exists():
|
84
118
|
logger.info(f"Failed config preserved at {config_path} for debugging")
|
119
|
+
# Log the file contents for debugging
|
120
|
+
try:
|
121
|
+
logger.debug(f"Failed config contents:\n{config_path.read_text()}")
|
122
|
+
except Exception as read_error:
|
123
|
+
logger.error(f"Could not read failed config: {read_error}")
|
85
124
|
raise Exception(error_msg)
|
86
125
|
|
87
126
|
def cleanup_cloud_init(path: str, config_id: str) -> None:
|
File without changes
|
File without changes
|