Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion yocto/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def main() -> int:

should_deploy = maybe_build(configs)
if not should_deploy:
return
return 0

assert configs.deploy # should never happen

Expand Down
18 changes: 7 additions & 11 deletions yocto/cloud/gcp/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import time
from pathlib import Path

from google.api_core.extended_operation import ExtendedOperation
from google.cloud import compute_v1, resourcemanager_v3, storage

from yocto.cloud.azure.api import AzureApi
Expand All @@ -34,7 +35,7 @@

# Disk Operations
def wait_for_extended_operation(
operation: compute_v1.Operation,
operation: ExtendedOperation,
operation_name: str,
timeout: int = 600,
) -> None:
Expand All @@ -46,19 +47,14 @@ def wait_for_extended_operation(
operation_name: Human-readable name for logging
timeout: Maximum time to wait in seconds
"""
start_time = time.time()

while not operation.done():
if time.time() - start_time > timeout:
try:
operation.result(timeout=timeout)
except Exception as e:
if "timeout" in str(e).lower():
raise TimeoutError(
f"{operation_name} timed out after {timeout} seconds"
)

time.sleep(5)
logger.info(f"Waiting for {operation_name}...")

if operation.error:
raise RuntimeError(f"{operation_name} failed: {operation.error}")
raise RuntimeError(f"{operation_name} failed: {e}")


class GcpApi(CloudApi):
Expand Down
2 changes: 1 addition & 1 deletion yocto/config/mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def deploy_only() -> "Mode":
delete_artifact=None,
)

def to_dict(self) -> dict[str, str | bool]:
def to_dict(self) -> dict[str, bool | dict[str, str]]:
delete_kwargs = {}
if self.delete_vm:
delete_kwargs["vm"] = self.delete_vm
Expand Down
4 changes: 4 additions & 0 deletions yocto/deployment/deploy_bob.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pathlib import Path

from yocto.cloud.azure.api import AzureApi
from yocto.cloud.cloud_config import CloudProvider

# Import defaults here to avoid circular imports
from yocto.cloud.azure.defaults import (
Expand Down Expand Up @@ -133,6 +134,8 @@ def deploy_bob_vm(

# Convert to Configs object to access vm/deploy attributes
cfg = config.to_configs()
if cfg.deploy is None:
raise ValueError("Deploy config is None")
deploy_cfg = cfg.deploy

logger.info(f"Config:\n{json.dumps(cfg.to_dict(), indent=2)}")
Expand Down Expand Up @@ -357,6 +360,7 @@ def main():
try:
# Create config (similar to genesis but without domain/DNS)
config = DeploymentConfig(
cloud=CloudProvider.AZURE,
vm_name=args.name,
region=args.region or DEFAULT_REGION,
vm_size=args.vm_size or DEFAULT_VM_SIZE,
Expand Down
4 changes: 2 additions & 2 deletions yocto/deployment/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def _post_shares(
node_to_pubkey: dict[int, str],
):
genesis_file = f"{tmpdir}/genesis.toml"
genesis_toml = SummitClient.load_genesis_toml(genesis_file)
genesis_toml = SummitClient.load_genesis_toml(Path(genesis_file))
validators = genesis_toml["validators"]

for node, client in node_clients:
Expand Down Expand Up @@ -140,7 +140,7 @@ def main():

_post_shares(tmpdir, node_clients, node_to_pubkey)
for _, client in node_clients:
client.post_genesis_filepath(f"{tmpdir}/genesis.toml")
client.post_genesis_filepath(Path(f"{tmpdir}/genesis.toml"))


if __name__ == "__main__":
Expand Down
12 changes: 9 additions & 3 deletions yocto/genesis_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
class GenesisIPManager:
"""Manages persistent IP addresses for genesis nodes."""

def __init__(self, cloud_api: CloudApi, ip_rg: str):
def __init__(self, cloud_api: type[CloudApi], ip_rg: str):
self.cloud_api = cloud_api
self.ip_rg = ip_rg

Expand Down Expand Up @@ -68,8 +68,11 @@ def deploy_genesis_vm(args: DeploymentConfig) -> None:
node = args.node
cfg = args.to_configs()
deploy_cfg = cfg.deploy
if deploy_cfg is None:
raise ValueError("Deploy config is None")
print(f"Config:\n{json.dumps(cfg.to_dict(), indent=2)}")


cloud_api = get_cloud_api(deploy_cfg.vm.cloud)
genesis_ip_manager = GenesisIPManager(cloud_api, args.resource_group)

Expand Down Expand Up @@ -98,9 +101,12 @@ def deploy_genesis_vm(args: DeploymentConfig) -> None:
logger.info("Not creating machines (used --ip-only flag)")
return

image_path, measurements = maybe_build(cfg)
build_result = maybe_build(cfg)
if build_result is None:
raise ValueError("Build result is None")
image_path, measurements = build_result
deployer = Deployer(
configs=cfg.deploy,
configs=deploy_cfg,
image_path=image_path,
measurements=measurements,
home=cfg.home,
Expand Down
4 changes: 4 additions & 0 deletions yocto/image/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
- from yocto.image.measurements import ...
"""

from typing import TYPE_CHECKING
if TYPE_CHECKING:
from yocto.image import build, git, measurements

# Only export module names, not individual functions
__all__ = [
"build",
Expand Down
4 changes: 4 additions & 0 deletions yocto/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
- etc.
"""

from typing import TYPE_CHECKING
if TYPE_CHECKING:
from yocto.utils import artifact, logging_setup, metadata, parser, paths, summit_client

# Only export module names, not individual functions
__all__ = [
"artifact",
Expand Down
15 changes: 13 additions & 2 deletions yocto/utils/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,24 @@ def load_artifact_measurements(
msg = f"Could not find artifact {artifact} in {metadata_path}"
raise ValueError(msg)
image_path = BuildPaths(home).artifacts / artifact
artifact = artifacts[artifact]
artifact_data = artifacts[artifact]
if not image_path.exists():
raise FileNotFoundError(
f"Artifact {artifact} is defined in the deploy metadata, "
"but the corresponding file was not found on the machine"
)
return image_path, artifact["image"]

if not isinstance(artifact_data, dict):
raise TypeError(f"Artifact data for {artifact} is not a dict")
if "image" not in artifact_data:
raise ValueError(f"Artifact {artifact} missing 'image' key")

measurements = artifact_data["image"]
if not isinstance(measurements, dict):
raise TypeError(f"Measurements for {artifact} is not a dict")

return image_path, measurements



def get_cloud_resources(home: str, cloud: str) -> dict[str, dict]:
Expand Down