implement cli/infra update cicd
This commit is contained in:
48
src/materia/providers/__init__.py
Normal file
48
src/materia/providers/__init__.py
Normal 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}")
|
||||
122
src/materia/providers/hetzner.py
Normal file
122
src/materia/providers/hetzner.py
Normal 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
|
||||
28
src/materia/providers/oracle.py
Normal file
28
src/materia/providers/oracle.py
Normal 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")
|
||||
28
src/materia/providers/ovh.py
Normal file
28
src/materia/providers/ovh.py
Normal 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")
|
||||
28
src/materia/providers/scaleway.py
Normal file
28
src/materia/providers/scaleway.py
Normal 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")
|
||||
Reference in New Issue
Block a user