implement cli/infra update cicd

This commit is contained in:
Deeman
2025-10-12 21:00:41 +02:00
parent 790e802edd
commit 55bb84f0fa
18 changed files with 2052 additions and 60 deletions

View File

@@ -0,0 +1,48 @@
"""Cloud provider abstraction for worker management."""
from dataclasses import dataclass
from typing import Protocol
@dataclass
class Instance:
id: str
name: str
ip: str
status: str
provider: str
type: str
class ProviderModule(Protocol):
def create_instance(
name: str,
instance_type: str,
ssh_key: str,
location: str | None = None,
) -> Instance: ...
def destroy_instance(instance_id: str) -> None: ...
def list_instances(label: str | None = None) -> list[Instance]: ...
def get_instance(name: str) -> Instance | None: ...
def wait_for_ssh(ip: str, timeout: int = 300) -> bool: ...
def get_provider(provider_name: str) -> ProviderModule:
if provider_name == "hetzner":
from materia.providers import hetzner
return hetzner
elif provider_name == "ovh":
from materia.providers import ovh
return ovh
elif provider_name == "scaleway":
from materia.providers import scaleway
return scaleway
elif provider_name == "oracle":
from materia.providers import oracle
return oracle
else:
raise ValueError(f"Unknown provider: {provider_name}")

View File

@@ -0,0 +1,122 @@
"""Hetzner Cloud provider implementation."""
import time
import socket
from hcloud import Client
from hcloud.images import Image
from hcloud.server_types import ServerType
from materia.providers import Instance
from materia.secrets import get_secret
def _get_client() -> Client:
token = get_secret("HETZNER_TOKEN")
if not token:
raise ValueError("HETZNER_TOKEN not found in secrets")
return Client(token=token)
def create_instance(
name: str,
instance_type: str,
ssh_key: str,
location: str | None = None,
) -> Instance:
client = _get_client()
# Get or create SSH key
ssh_keys = client.ssh_keys.get_all(name="materia-key")
if ssh_keys:
hcloud_key = ssh_keys[0]
else:
hcloud_key = client.ssh_keys.create(name="materia-key", public_key=ssh_key)
server_type = ServerType(name=instance_type)
image = Image(name="ubuntu-24.04")
location_obj = location or "nbg1"
response = client.servers.create(
name=name,
server_type=server_type,
image=image,
ssh_keys=[hcloud_key],
location=location_obj,
labels={"managed_by": "materia"},
)
server = response.server
server.wait_until_status_is("running")
return Instance(
id=str(server.id),
name=server.name,
ip=server.public_net.ipv4.ip,
status=server.status,
provider="hetzner",
type=instance_type,
)
def destroy_instance(instance_id: str) -> None:
client = _get_client()
server = client.servers.get_by_id(int(instance_id))
if server:
server.delete()
def list_instances(label: str | None = None) -> list[Instance]:
client = _get_client()
label_selector = {"managed_by": "materia"}
if label:
label_selector["pipeline"] = label
servers = client.servers.get_all(label_selector=label_selector)
return [
Instance(
id=str(server.id),
name=server.name,
ip=server.public_net.ipv4.ip,
status=server.status,
provider="hetzner",
type=server.server_type.name,
)
for server in servers
]
def get_instance(name: str) -> Instance | None:
client = _get_client()
servers = client.servers.get_all(name=name)
if not servers:
return None
server = servers[0]
return Instance(
id=str(server.id),
name=server.name,
ip=server.public_net.ipv4.ip,
status=server.status,
provider="hetzner",
type=server.server_type.name,
)
def wait_for_ssh(ip: str, timeout: int = 300) -> bool:
start = time.time()
while time.time() - start < timeout:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
result = sock.connect_ex((ip, 22))
sock.close()
if result == 0:
time.sleep(10)
return True
except Exception:
pass
time.sleep(5)
return False

View File

@@ -0,0 +1,28 @@
"""Oracle Cloud provider implementation."""
from materia.providers import Instance
def create_instance(
name: str,
instance_type: str,
ssh_key: str,
location: str | None = None,
) -> Instance:
raise NotImplementedError("Oracle Cloud provider not yet implemented")
def destroy_instance(instance_id: str) -> None:
raise NotImplementedError("Oracle Cloud provider not yet implemented")
def list_instances(label: str | None = None) -> list[Instance]:
raise NotImplementedError("Oracle Cloud provider not yet implemented")
def get_instance(name: str) -> Instance | None:
raise NotImplementedError("Oracle Cloud provider not yet implemented")
def wait_for_ssh(ip: str, timeout: int = 300) -> bool:
raise NotImplementedError("Oracle Cloud provider not yet implemented")

View File

@@ -0,0 +1,28 @@
"""OVH Cloud provider implementation."""
from materia.providers import Instance
def create_instance(
name: str,
instance_type: str,
ssh_key: str,
location: str | None = None,
) -> Instance:
raise NotImplementedError("OVH provider not yet implemented")
def destroy_instance(instance_id: str) -> None:
raise NotImplementedError("OVH provider not yet implemented")
def list_instances(label: str | None = None) -> list[Instance]:
raise NotImplementedError("OVH provider not yet implemented")
def get_instance(name: str) -> Instance | None:
raise NotImplementedError("OVH provider not yet implemented")
def wait_for_ssh(ip: str, timeout: int = 300) -> bool:
raise NotImplementedError("OVH provider not yet implemented")

View File

@@ -0,0 +1,28 @@
"""Scaleway provider implementation."""
from materia.providers import Instance
def create_instance(
name: str,
instance_type: str,
ssh_key: str,
location: str | None = None,
) -> Instance:
raise NotImplementedError("Scaleway provider not yet implemented")
def destroy_instance(instance_id: str) -> None:
raise NotImplementedError("Scaleway provider not yet implemented")
def list_instances(label: str | None = None) -> list[Instance]:
raise NotImplementedError("Scaleway provider not yet implemented")
def get_instance(name: str) -> Instance | None:
raise NotImplementedError("Scaleway provider not yet implemented")
def wait_for_ssh(ip: str, timeout: int = 300) -> bool:
raise NotImplementedError("Scaleway provider not yet implemented")