Initial commit: Vultr API v2 Python wrapper with FastAPI server
- vultr_api/: Python library wrapping Vultr API v2 - 17 resource modules (instances, dns, firewall, vpc, etc.) - Pagination support, error handling - server/: FastAPI REST server - All API endpoints exposed via HTTP - X-API-Key header authentication - Swagger docs at /docs - Podman quadlet config for systemd deployment Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
18
server/Dockerfile
Normal file
18
server/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
RUN pip install --no-cache-dir fastapi uvicorn requests pydantic
|
||||
|
||||
# Copy vultr_api library
|
||||
COPY vultr_api/ /app/vultr_api/
|
||||
|
||||
# Copy server code
|
||||
COPY server/ /app/
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8000
|
||||
|
||||
# Run server
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
20
server/deps.py
Normal file
20
server/deps.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""Dependencies for FastAPI"""
|
||||
import os
|
||||
from fastapi import HTTPException, Security
|
||||
from fastapi.security import APIKeyHeader
|
||||
|
||||
from vultr_api import VultrClient
|
||||
|
||||
API_KEY_HEADER = APIKeyHeader(name="X-API-Key", auto_error=False)
|
||||
VULTR_API_KEY = os.environ.get("VULTR_API_KEY")
|
||||
|
||||
|
||||
def get_client(api_key: str = Security(API_KEY_HEADER)) -> VultrClient:
|
||||
"""Get VultrClient instance with API key from header or environment"""
|
||||
key = api_key or VULTR_API_KEY
|
||||
if not key:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="API key required. Set X-API-Key header or VULTR_API_KEY env var"
|
||||
)
|
||||
return VultrClient(api_key=key)
|
||||
58
server/main.py
Normal file
58
server/main.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""
|
||||
Vultr API REST Server
|
||||
FastAPI wrapper for vultr-api library
|
||||
"""
|
||||
from fastapi import FastAPI
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from routers import (
|
||||
account, instances, dns, firewall, ssh_keys,
|
||||
startup_scripts, snapshots, block_storage, reserved_ips,
|
||||
vpc, load_balancers, bare_metal, backups, plans, regions, os_api
|
||||
)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Application lifespan handler"""
|
||||
print("Vultr API Server starting...")
|
||||
yield
|
||||
print("Vultr API Server shutting down...")
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="Vultr API Server",
|
||||
description="REST API server wrapping Vultr API v2",
|
||||
version="1.0.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
# Include routers
|
||||
app.include_router(account.router, prefix="/account", tags=["Account"])
|
||||
app.include_router(instances.router, prefix="/instances", tags=["Instances"])
|
||||
app.include_router(dns.router, prefix="/dns", tags=["DNS"])
|
||||
app.include_router(firewall.router, prefix="/firewall", tags=["Firewall"])
|
||||
app.include_router(ssh_keys.router, prefix="/ssh-keys", tags=["SSH Keys"])
|
||||
app.include_router(startup_scripts.router, prefix="/startup-scripts", tags=["Startup Scripts"])
|
||||
app.include_router(snapshots.router, prefix="/snapshots", tags=["Snapshots"])
|
||||
app.include_router(block_storage.router, prefix="/block-storage", tags=["Block Storage"])
|
||||
app.include_router(reserved_ips.router, prefix="/reserved-ips", tags=["Reserved IPs"])
|
||||
app.include_router(vpc.router, prefix="/vpc", tags=["VPC"])
|
||||
app.include_router(load_balancers.router, prefix="/load-balancers", tags=["Load Balancers"])
|
||||
app.include_router(bare_metal.router, prefix="/bare-metal", tags=["Bare Metal"])
|
||||
app.include_router(backups.router, prefix="/backups", tags=["Backups"])
|
||||
app.include_router(plans.router, prefix="/plans", tags=["Plans"])
|
||||
app.include_router(regions.router, prefix="/regions", tags=["Regions"])
|
||||
app.include_router(os_api.router, prefix="/os", tags=["Operating Systems"])
|
||||
|
||||
|
||||
@app.get("/", tags=["Health"])
|
||||
async def root():
|
||||
"""Health check endpoint"""
|
||||
return {"status": "ok", "service": "vultr-api-server"}
|
||||
|
||||
|
||||
@app.get("/health", tags=["Health"])
|
||||
async def health():
|
||||
"""Health check endpoint"""
|
||||
return {"status": "healthy"}
|
||||
38
server/routers/__init__.py
Normal file
38
server/routers/__init__.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""API Routers"""
|
||||
from . import (
|
||||
account,
|
||||
instances,
|
||||
dns,
|
||||
firewall,
|
||||
ssh_keys,
|
||||
startup_scripts,
|
||||
snapshots,
|
||||
block_storage,
|
||||
reserved_ips,
|
||||
vpc,
|
||||
load_balancers,
|
||||
bare_metal,
|
||||
backups,
|
||||
plans,
|
||||
regions,
|
||||
os_api,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"account",
|
||||
"instances",
|
||||
"dns",
|
||||
"firewall",
|
||||
"ssh_keys",
|
||||
"startup_scripts",
|
||||
"snapshots",
|
||||
"block_storage",
|
||||
"reserved_ips",
|
||||
"vpc",
|
||||
"load_balancers",
|
||||
"bare_metal",
|
||||
"backups",
|
||||
"plans",
|
||||
"regions",
|
||||
"os_api",
|
||||
]
|
||||
38
server/routers/account.py
Normal file
38
server/routers/account.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""Account router"""
|
||||
from fastapi import APIRouter, Depends
|
||||
from typing import List
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def get_account(client: VultrClient = Depends(get_client)):
|
||||
"""Get account information"""
|
||||
return client.account.get()
|
||||
|
||||
|
||||
@router.get("/acl")
|
||||
async def get_acl(client: VultrClient = Depends(get_client)):
|
||||
"""Get account ACL (IP access control list)"""
|
||||
return client.account.get_acl()
|
||||
|
||||
|
||||
@router.put("/acl")
|
||||
async def set_acl(acls: List[str], client: VultrClient = Depends(get_client)):
|
||||
"""Set account ACL"""
|
||||
return client.account.set_acl(acls)
|
||||
|
||||
|
||||
@router.post("/acl")
|
||||
async def add_acl(ip: str, client: VultrClient = Depends(get_client)):
|
||||
"""Add IP to ACL"""
|
||||
return client.account.add_acl(ip)
|
||||
|
||||
|
||||
@router.delete("/acl/{ip}")
|
||||
async def remove_acl(ip: str, client: VultrClient = Depends(get_client)):
|
||||
"""Remove IP from ACL"""
|
||||
return client.account.remove_acl(ip)
|
||||
34
server/routers/backups.py
Normal file
34
server/routers/backups.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""Backups router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_backups(
|
||||
instance_id: Optional[str] = None,
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all backups"""
|
||||
return client.backups.list(instance_id=instance_id, per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
async def list_all_backups(
|
||||
instance_id: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all backups (auto-paginated)"""
|
||||
return {"backups": client.backups.list_all(instance_id=instance_id)}
|
||||
|
||||
|
||||
@router.get("/{backup_id}")
|
||||
async def get_backup(backup_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get backup details"""
|
||||
return client.backups.get(backup_id)
|
||||
118
server/routers/bare_metal.py
Normal file
118
server/routers/bare_metal.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""Bare Metal router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class CreateBareMetalRequest(BaseModel):
|
||||
region: str
|
||||
plan: str
|
||||
os_id: Optional[int] = None
|
||||
snapshot_id: Optional[str] = None
|
||||
app_id: Optional[int] = None
|
||||
image_id: Optional[str] = None
|
||||
script_id: Optional[str] = None
|
||||
ssh_key_ids: Optional[List[str]] = None
|
||||
enable_ipv6: Optional[bool] = None
|
||||
hostname: Optional[str] = None
|
||||
label: Optional[str] = None
|
||||
tags: Optional[List[str]] = None
|
||||
|
||||
|
||||
class UpdateBareMetalRequest(BaseModel):
|
||||
label: Optional[str] = None
|
||||
tags: Optional[List[str]] = None
|
||||
enable_ipv6: Optional[bool] = None
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_bare_metal(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all bare metal servers"""
|
||||
return client.bare_metal.list(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
async def list_all_bare_metal(client: VultrClient = Depends(get_client)):
|
||||
"""List all bare metal servers (auto-paginated)"""
|
||||
return {"bare_metals": client.bare_metal.list_all()}
|
||||
|
||||
|
||||
@router.get("/{baremetal_id}")
|
||||
async def get_bare_metal(baremetal_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get bare metal server details"""
|
||||
return client.bare_metal.get(baremetal_id)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_bare_metal(req: CreateBareMetalRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Create a bare metal server"""
|
||||
return client.bare_metal.create(**req.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
@router.patch("/{baremetal_id}")
|
||||
async def update_bare_metal(baremetal_id: str, req: UpdateBareMetalRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Update a bare metal server"""
|
||||
return client.bare_metal.update(baremetal_id, **req.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
@router.delete("/{baremetal_id}")
|
||||
async def delete_bare_metal(baremetal_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Delete a bare metal server"""
|
||||
return client.bare_metal.delete(baremetal_id)
|
||||
|
||||
|
||||
@router.post("/{baremetal_id}/start")
|
||||
async def start_bare_metal(baremetal_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Start a bare metal server"""
|
||||
return client.bare_metal.start(baremetal_id)
|
||||
|
||||
|
||||
@router.post("/{baremetal_id}/halt")
|
||||
async def halt_bare_metal(baremetal_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Halt a bare metal server"""
|
||||
return client.bare_metal.halt(baremetal_id)
|
||||
|
||||
|
||||
@router.post("/{baremetal_id}/reboot")
|
||||
async def reboot_bare_metal(baremetal_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Reboot a bare metal server"""
|
||||
return client.bare_metal.reboot(baremetal_id)
|
||||
|
||||
|
||||
@router.post("/{baremetal_id}/reinstall")
|
||||
async def reinstall_bare_metal(baremetal_id: str, hostname: Optional[str] = None, client: VultrClient = Depends(get_client)):
|
||||
"""Reinstall a bare metal server"""
|
||||
return client.bare_metal.reinstall(baremetal_id, hostname=hostname)
|
||||
|
||||
|
||||
@router.get("/{baremetal_id}/ipv4")
|
||||
async def list_ipv4(baremetal_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""List IPv4 addresses for a bare metal server"""
|
||||
return client.bare_metal.list_ipv4(baremetal_id)
|
||||
|
||||
|
||||
@router.get("/{baremetal_id}/ipv6")
|
||||
async def list_ipv6(baremetal_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""List IPv6 addresses for a bare metal server"""
|
||||
return client.bare_metal.list_ipv6(baremetal_id)
|
||||
|
||||
|
||||
@router.get("/{baremetal_id}/bandwidth")
|
||||
async def get_bandwidth(baremetal_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get bandwidth usage for a bare metal server"""
|
||||
return client.bare_metal.bandwidth(baremetal_id)
|
||||
|
||||
|
||||
@router.get("/{baremetal_id}/user-data")
|
||||
async def get_user_data(baremetal_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get user data for a bare metal server"""
|
||||
return client.bare_metal.get_user_data(baremetal_id)
|
||||
78
server/routers/block_storage.py
Normal file
78
server/routers/block_storage.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""Block Storage router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class CreateBlockStorageRequest(BaseModel):
|
||||
region: str
|
||||
size_gb: int
|
||||
label: Optional[str] = None
|
||||
block_type: Optional[str] = None
|
||||
|
||||
|
||||
class UpdateBlockStorageRequest(BaseModel):
|
||||
size_gb: Optional[int] = None
|
||||
label: Optional[str] = None
|
||||
|
||||
|
||||
class AttachBlockStorageRequest(BaseModel):
|
||||
instance_id: str
|
||||
live: Optional[bool] = True
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_block_storage(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all block storage volumes"""
|
||||
return client.block_storage.list(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
async def list_all_block_storage(client: VultrClient = Depends(get_client)):
|
||||
"""List all block storage volumes (auto-paginated)"""
|
||||
return {"blocks": client.block_storage.list_all()}
|
||||
|
||||
|
||||
@router.get("/{block_id}")
|
||||
async def get_block_storage(block_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get block storage details"""
|
||||
return client.block_storage.get(block_id)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_block_storage(req: CreateBlockStorageRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Create a block storage volume"""
|
||||
return client.block_storage.create(**req.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
@router.patch("/{block_id}")
|
||||
async def update_block_storage(block_id: str, req: UpdateBlockStorageRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Update a block storage volume"""
|
||||
return client.block_storage.update(block_id, **req.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
@router.delete("/{block_id}")
|
||||
async def delete_block_storage(block_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Delete a block storage volume"""
|
||||
return client.block_storage.delete(block_id)
|
||||
|
||||
|
||||
@router.post("/{block_id}/attach")
|
||||
async def attach_block_storage(block_id: str, req: AttachBlockStorageRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Attach block storage to an instance"""
|
||||
return client.block_storage.attach(block_id, instance_id=req.instance_id, live=req.live)
|
||||
|
||||
|
||||
@router.post("/{block_id}/detach")
|
||||
async def detach_block_storage(block_id: str, live: bool = True, client: VultrClient = Depends(get_client)):
|
||||
"""Detach block storage from an instance"""
|
||||
return client.block_storage.detach(block_id, live=live)
|
||||
155
server/routers/dns.py
Normal file
155
server/routers/dns.py
Normal file
@@ -0,0 +1,155 @@
|
||||
"""DNS router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class CreateDomainRequest(BaseModel):
|
||||
domain: str
|
||||
ip: Optional[str] = None
|
||||
|
||||
|
||||
class CreateRecordRequest(BaseModel):
|
||||
type: str
|
||||
name: str
|
||||
data: str
|
||||
ttl: Optional[int] = 300
|
||||
priority: Optional[int] = None
|
||||
|
||||
|
||||
class UpdateRecordRequest(BaseModel):
|
||||
name: Optional[str] = None
|
||||
data: Optional[str] = None
|
||||
ttl: Optional[int] = None
|
||||
priority: Optional[int] = None
|
||||
|
||||
|
||||
@router.get("/domains")
|
||||
async def list_domains(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all DNS domains"""
|
||||
return client.dns.list_domains(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/domains/all")
|
||||
async def list_all_domains(client: VultrClient = Depends(get_client)):
|
||||
"""List all DNS domains (auto-paginated)"""
|
||||
return {"domains": client.dns.list_all_domains()}
|
||||
|
||||
|
||||
@router.get("/domains/{domain}")
|
||||
async def get_domain(domain: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get DNS domain details"""
|
||||
return client.dns.get_domain(domain)
|
||||
|
||||
|
||||
@router.post("/domains")
|
||||
async def create_domain(req: CreateDomainRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Create a DNS domain"""
|
||||
return client.dns.create_domain(req.domain, ip=req.ip)
|
||||
|
||||
|
||||
@router.delete("/domains/{domain}")
|
||||
async def delete_domain(domain: str, client: VultrClient = Depends(get_client)):
|
||||
"""Delete a DNS domain"""
|
||||
return client.dns.delete_domain(domain)
|
||||
|
||||
|
||||
@router.get("/domains/{domain}/soa")
|
||||
async def get_soa(domain: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get SOA record for a domain"""
|
||||
return client.dns.get_soa(domain)
|
||||
|
||||
|
||||
@router.get("/domains/{domain}/dnssec")
|
||||
async def get_dnssec(domain: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get DNSSEC info for a domain"""
|
||||
return client.dns.get_dnssec(domain)
|
||||
|
||||
|
||||
# Records
|
||||
@router.get("/domains/{domain}/records")
|
||||
async def list_records(
|
||||
domain: str,
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List DNS records for a domain"""
|
||||
return client.dns.list_records(domain, per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/domains/{domain}/records/all")
|
||||
async def list_all_records(domain: str, client: VultrClient = Depends(get_client)):
|
||||
"""List all DNS records (auto-paginated)"""
|
||||
return {"records": client.dns.list_all_records(domain)}
|
||||
|
||||
|
||||
@router.get("/domains/{domain}/records/{record_id}")
|
||||
async def get_record(domain: str, record_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get a DNS record"""
|
||||
return client.dns.get_record(domain, record_id)
|
||||
|
||||
|
||||
@router.post("/domains/{domain}/records")
|
||||
async def create_record(domain: str, req: CreateRecordRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Create a DNS record"""
|
||||
return client.dns.create_record(
|
||||
domain,
|
||||
type=req.type,
|
||||
name=req.name,
|
||||
data=req.data,
|
||||
ttl=req.ttl,
|
||||
priority=req.priority
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/domains/{domain}/records/{record_id}")
|
||||
async def update_record(domain: str, record_id: str, req: UpdateRecordRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Update a DNS record"""
|
||||
return client.dns.update_record(domain, record_id, **req.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
@router.delete("/domains/{domain}/records/{record_id}")
|
||||
async def delete_record(domain: str, record_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Delete a DNS record"""
|
||||
return client.dns.delete_record(domain, record_id)
|
||||
|
||||
|
||||
# Convenience endpoints
|
||||
@router.post("/domains/{domain}/records/a")
|
||||
async def create_a_record(domain: str, name: str, ip: str, ttl: int = 300, client: VultrClient = Depends(get_client)):
|
||||
"""Create an A record"""
|
||||
return client.dns.create_a_record(domain, name, ip, ttl)
|
||||
|
||||
|
||||
@router.post("/domains/{domain}/records/aaaa")
|
||||
async def create_aaaa_record(domain: str, name: str, ip: str, ttl: int = 300, client: VultrClient = Depends(get_client)):
|
||||
"""Create an AAAA record"""
|
||||
return client.dns.create_aaaa_record(domain, name, ip, ttl)
|
||||
|
||||
|
||||
@router.post("/domains/{domain}/records/cname")
|
||||
async def create_cname_record(domain: str, name: str, target: str, ttl: int = 300, client: VultrClient = Depends(get_client)):
|
||||
"""Create a CNAME record"""
|
||||
return client.dns.create_cname_record(domain, name, target, ttl)
|
||||
|
||||
|
||||
@router.post("/domains/{domain}/records/txt")
|
||||
async def create_txt_record(domain: str, name: str, data: str, ttl: int = 300, client: VultrClient = Depends(get_client)):
|
||||
"""Create a TXT record"""
|
||||
return client.dns.create_txt_record(domain, name, data, ttl)
|
||||
|
||||
|
||||
@router.post("/domains/{domain}/records/mx")
|
||||
async def create_mx_record(domain: str, name: str, data: str, priority: int = 10, ttl: int = 300, client: VultrClient = Depends(get_client)):
|
||||
"""Create an MX record"""
|
||||
return client.dns.create_mx_record(domain, name, data, priority, ttl)
|
||||
124
server/routers/firewall.py
Normal file
124
server/routers/firewall.py
Normal file
@@ -0,0 +1,124 @@
|
||||
"""Firewall router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class CreateGroupRequest(BaseModel):
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class CreateRuleRequest(BaseModel):
|
||||
ip_type: str # v4 or v6
|
||||
protocol: str # tcp, udp, icmp, gre, esp, ah
|
||||
subnet: str
|
||||
subnet_size: int
|
||||
port: Optional[str] = None
|
||||
source: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
@router.get("/groups")
|
||||
async def list_groups(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all firewall groups"""
|
||||
return client.firewall.list_groups(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/groups/all")
|
||||
async def list_all_groups(client: VultrClient = Depends(get_client)):
|
||||
"""List all firewall groups (auto-paginated)"""
|
||||
return {"firewall_groups": client.firewall.list_all_groups()}
|
||||
|
||||
|
||||
@router.get("/groups/{group_id}")
|
||||
async def get_group(group_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get firewall group details"""
|
||||
return client.firewall.get_group(group_id)
|
||||
|
||||
|
||||
@router.post("/groups")
|
||||
async def create_group(req: CreateGroupRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Create a firewall group"""
|
||||
return client.firewall.create_group(description=req.description)
|
||||
|
||||
|
||||
@router.patch("/groups/{group_id}")
|
||||
async def update_group(group_id: str, description: str, client: VultrClient = Depends(get_client)):
|
||||
"""Update a firewall group"""
|
||||
return client.firewall.update_group(group_id, description=description)
|
||||
|
||||
|
||||
@router.delete("/groups/{group_id}")
|
||||
async def delete_group(group_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Delete a firewall group"""
|
||||
return client.firewall.delete_group(group_id)
|
||||
|
||||
|
||||
# Rules
|
||||
@router.get("/groups/{group_id}/rules")
|
||||
async def list_rules(
|
||||
group_id: str,
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List firewall rules for a group"""
|
||||
return client.firewall.list_rules(group_id, per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/groups/{group_id}/rules/all")
|
||||
async def list_all_rules(group_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""List all firewall rules (auto-paginated)"""
|
||||
return {"firewall_rules": client.firewall.list_all_rules(group_id)}
|
||||
|
||||
|
||||
@router.get("/groups/{group_id}/rules/{rule_id}")
|
||||
async def get_rule(group_id: str, rule_id: int, client: VultrClient = Depends(get_client)):
|
||||
"""Get a firewall rule"""
|
||||
return client.firewall.get_rule(group_id, rule_id)
|
||||
|
||||
|
||||
@router.post("/groups/{group_id}/rules")
|
||||
async def create_rule(group_id: str, req: CreateRuleRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Create a firewall rule"""
|
||||
return client.firewall.create_rule(group_id, **req.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
@router.delete("/groups/{group_id}/rules/{rule_id}")
|
||||
async def delete_rule(group_id: str, rule_id: int, client: VultrClient = Depends(get_client)):
|
||||
"""Delete a firewall rule"""
|
||||
return client.firewall.delete_rule(group_id, rule_id)
|
||||
|
||||
|
||||
# Convenience endpoints
|
||||
@router.post("/groups/{group_id}/rules/allow-ssh")
|
||||
async def allow_ssh(group_id: str, source: str = "0.0.0.0/0", client: VultrClient = Depends(get_client)):
|
||||
"""Allow SSH (port 22) from source"""
|
||||
return client.firewall.allow_ssh(group_id, source=source)
|
||||
|
||||
|
||||
@router.post("/groups/{group_id}/rules/allow-http")
|
||||
async def allow_http(group_id: str, source: str = "0.0.0.0/0", client: VultrClient = Depends(get_client)):
|
||||
"""Allow HTTP (port 80) from source"""
|
||||
return client.firewall.allow_http(group_id, source=source)
|
||||
|
||||
|
||||
@router.post("/groups/{group_id}/rules/allow-https")
|
||||
async def allow_https(group_id: str, source: str = "0.0.0.0/0", client: VultrClient = Depends(get_client)):
|
||||
"""Allow HTTPS (port 443) from source"""
|
||||
return client.firewall.allow_https(group_id, source=source)
|
||||
|
||||
|
||||
@router.post("/groups/{group_id}/rules/allow-ping")
|
||||
async def allow_ping(group_id: str, source: str = "0.0.0.0/0", client: VultrClient = Depends(get_client)):
|
||||
"""Allow ICMP ping from source"""
|
||||
return client.firewall.allow_ping(group_id, source=source)
|
||||
151
server/routers/instances.py
Normal file
151
server/routers/instances.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""Instances router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class CreateInstanceRequest(BaseModel):
|
||||
region: str
|
||||
plan: str
|
||||
os_id: Optional[int] = None
|
||||
iso_id: Optional[str] = None
|
||||
snapshot_id: Optional[str] = None
|
||||
app_id: Optional[int] = None
|
||||
image_id: Optional[str] = None
|
||||
script_id: Optional[str] = None
|
||||
ssh_key_ids: Optional[List[str]] = None
|
||||
backups: Optional[str] = None
|
||||
enable_ipv6: Optional[bool] = None
|
||||
hostname: Optional[str] = None
|
||||
label: Optional[str] = None
|
||||
tags: Optional[List[str]] = None
|
||||
firewall_group_id: Optional[str] = None
|
||||
vpc_id: Optional[str] = None
|
||||
|
||||
|
||||
class UpdateInstanceRequest(BaseModel):
|
||||
label: Optional[str] = None
|
||||
tags: Optional[List[str]] = None
|
||||
firewall_group_id: Optional[str] = None
|
||||
enable_ipv6: Optional[bool] = None
|
||||
backups: Optional[str] = None
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_instances(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all instances"""
|
||||
return client.instances.list(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
async def list_all_instances(client: VultrClient = Depends(get_client)):
|
||||
"""List all instances (auto-paginated)"""
|
||||
return {"instances": client.instances.list_all()}
|
||||
|
||||
|
||||
@router.get("/{instance_id}")
|
||||
async def get_instance(instance_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get instance details"""
|
||||
return client.instances.get(instance_id)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_instance(req: CreateInstanceRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Create a new instance"""
|
||||
return client.instances.create(**req.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
@router.patch("/{instance_id}")
|
||||
async def update_instance(instance_id: str, req: UpdateInstanceRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Update an instance"""
|
||||
return client.instances.update(instance_id, **req.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
@router.delete("/{instance_id}")
|
||||
async def delete_instance(instance_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Delete an instance"""
|
||||
return client.instances.delete(instance_id)
|
||||
|
||||
|
||||
@router.post("/{instance_id}/start")
|
||||
async def start_instance(instance_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Start an instance"""
|
||||
return client.instances.start(instance_id)
|
||||
|
||||
|
||||
@router.post("/{instance_id}/stop")
|
||||
async def stop_instance(instance_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Stop an instance (halt)"""
|
||||
return client.instances.halt(instance_id)
|
||||
|
||||
|
||||
@router.post("/{instance_id}/reboot")
|
||||
async def reboot_instance(instance_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Reboot an instance"""
|
||||
return client.instances.reboot(instance_id)
|
||||
|
||||
|
||||
@router.post("/{instance_id}/reinstall")
|
||||
async def reinstall_instance(instance_id: str, hostname: Optional[str] = None, client: VultrClient = Depends(get_client)):
|
||||
"""Reinstall an instance"""
|
||||
return client.instances.reinstall(instance_id, hostname=hostname)
|
||||
|
||||
|
||||
@router.get("/{instance_id}/ipv4")
|
||||
async def list_ipv4(instance_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""List IPv4 addresses for an instance"""
|
||||
return client.instances.list_ipv4(instance_id)
|
||||
|
||||
|
||||
@router.get("/{instance_id}/ipv6")
|
||||
async def list_ipv6(instance_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""List IPv6 addresses for an instance"""
|
||||
return client.instances.list_ipv6(instance_id)
|
||||
|
||||
|
||||
@router.get("/{instance_id}/bandwidth")
|
||||
async def get_bandwidth(instance_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get bandwidth usage for an instance"""
|
||||
return client.instances.bandwidth(instance_id)
|
||||
|
||||
|
||||
@router.get("/{instance_id}/neighbors")
|
||||
async def get_neighbors(instance_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get instance neighbors"""
|
||||
return client.instances.neighbors(instance_id)
|
||||
|
||||
|
||||
@router.get("/{instance_id}/user-data")
|
||||
async def get_user_data(instance_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get instance user data"""
|
||||
return client.instances.get_user_data(instance_id)
|
||||
|
||||
|
||||
@router.post("/{instance_id}/backup")
|
||||
async def create_backup(instance_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Create a backup of an instance"""
|
||||
return client.instances.create_backup(instance_id)
|
||||
|
||||
|
||||
@router.post("/{instance_id}/restore")
|
||||
async def restore_instance(
|
||||
instance_id: str,
|
||||
backup_id: Optional[str] = None,
|
||||
snapshot_id: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""Restore an instance from backup or snapshot"""
|
||||
if backup_id:
|
||||
return client.instances.restore_backup(instance_id, backup_id)
|
||||
elif snapshot_id:
|
||||
return client.instances.restore_snapshot(instance_id, snapshot_id)
|
||||
return {"error": "backup_id or snapshot_id required"}
|
||||
108
server/routers/load_balancers.py
Normal file
108
server/routers/load_balancers.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""Load Balancers router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class ForwardingRule(BaseModel):
|
||||
frontend_protocol: str
|
||||
frontend_port: int
|
||||
backend_protocol: str
|
||||
backend_port: int
|
||||
|
||||
|
||||
class HealthCheck(BaseModel):
|
||||
protocol: str
|
||||
port: int
|
||||
path: Optional[str] = None
|
||||
check_interval: Optional[int] = None
|
||||
response_timeout: Optional[int] = None
|
||||
unhealthy_threshold: Optional[int] = None
|
||||
healthy_threshold: Optional[int] = None
|
||||
|
||||
|
||||
class CreateLoadBalancerRequest(BaseModel):
|
||||
region: str
|
||||
label: Optional[str] = None
|
||||
balancing_algorithm: Optional[str] = None # roundrobin or leastconn
|
||||
forwarding_rules: Optional[List[ForwardingRule]] = None
|
||||
health_check: Optional[HealthCheck] = None
|
||||
instances: Optional[List[str]] = None
|
||||
ssl_redirect: Optional[bool] = None
|
||||
proxy_protocol: Optional[bool] = None
|
||||
|
||||
|
||||
class UpdateLoadBalancerRequest(BaseModel):
|
||||
label: Optional[str] = None
|
||||
balancing_algorithm: Optional[str] = None
|
||||
ssl_redirect: Optional[bool] = None
|
||||
proxy_protocol: Optional[bool] = None
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_load_balancers(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all load balancers"""
|
||||
return client.load_balancers.list(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
async def list_all_load_balancers(client: VultrClient = Depends(get_client)):
|
||||
"""List all load balancers (auto-paginated)"""
|
||||
return {"load_balancers": client.load_balancers.list_all()}
|
||||
|
||||
|
||||
@router.get("/{lb_id}")
|
||||
async def get_load_balancer(lb_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get load balancer details"""
|
||||
return client.load_balancers.get(lb_id)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_load_balancer(req: CreateLoadBalancerRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Create a load balancer"""
|
||||
data = req.model_dump(exclude_none=True)
|
||||
if 'forwarding_rules' in data:
|
||||
data['forwarding_rules'] = [r for r in data['forwarding_rules']]
|
||||
if 'health_check' in data:
|
||||
data['health_check'] = dict(data['health_check'])
|
||||
return client.load_balancers.create(**data)
|
||||
|
||||
|
||||
@router.patch("/{lb_id}")
|
||||
async def update_load_balancer(lb_id: str, req: UpdateLoadBalancerRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Update a load balancer"""
|
||||
return client.load_balancers.update(lb_id, **req.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
@router.delete("/{lb_id}")
|
||||
async def delete_load_balancer(lb_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Delete a load balancer"""
|
||||
return client.load_balancers.delete(lb_id)
|
||||
|
||||
|
||||
# Forwarding rules
|
||||
@router.get("/{lb_id}/forwarding-rules")
|
||||
async def list_forwarding_rules(lb_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""List forwarding rules for a load balancer"""
|
||||
return client.load_balancers.list_forwarding_rules(lb_id)
|
||||
|
||||
|
||||
@router.post("/{lb_id}/forwarding-rules")
|
||||
async def create_forwarding_rule(lb_id: str, req: ForwardingRule, client: VultrClient = Depends(get_client)):
|
||||
"""Create a forwarding rule"""
|
||||
return client.load_balancers.create_forwarding_rule(lb_id, **req.model_dump())
|
||||
|
||||
|
||||
@router.delete("/{lb_id}/forwarding-rules/{rule_id}")
|
||||
async def delete_forwarding_rule(lb_id: str, rule_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Delete a forwarding rule"""
|
||||
return client.load_balancers.delete_forwarding_rule(lb_id, rule_id)
|
||||
24
server/routers/os_api.py
Normal file
24
server/routers/os_api.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""Operating Systems router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_os(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all operating systems"""
|
||||
return client.os.list(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
async def list_all_os(client: VultrClient = Depends(get_client)):
|
||||
"""List all operating systems (auto-paginated)"""
|
||||
return {"os": client.os.list_all()}
|
||||
44
server/routers/plans.py
Normal file
44
server/routers/plans.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Plans router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_plans(
|
||||
plan_type: Optional[str] = None, # vc2, vhf, vdc, etc.
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all cloud compute plans"""
|
||||
return client.plans.list(plan_type=plan_type, per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
async def list_all_plans(
|
||||
plan_type: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all cloud compute plans (auto-paginated)"""
|
||||
return {"plans": client.plans.list_all(plan_type=plan_type)}
|
||||
|
||||
|
||||
@router.get("/bare-metal")
|
||||
async def list_bare_metal_plans(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all bare metal plans"""
|
||||
return client.plans.list_bare_metal(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/bare-metal/all")
|
||||
async def list_all_bare_metal_plans(client: VultrClient = Depends(get_client)):
|
||||
"""List all bare metal plans (auto-paginated)"""
|
||||
return {"plans_metal": client.plans.list_all_bare_metal()}
|
||||
34
server/routers/regions.py
Normal file
34
server/routers/regions.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""Regions router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_regions(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all regions"""
|
||||
return client.regions.list(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
async def list_all_regions(client: VultrClient = Depends(get_client)):
|
||||
"""List all regions (auto-paginated)"""
|
||||
return {"regions": client.regions.list_all()}
|
||||
|
||||
|
||||
@router.get("/{region_id}/availability")
|
||||
async def list_region_availability(
|
||||
region_id: str,
|
||||
plan_type: Optional[str] = None, # vc2, vhf, vdc, etc.
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List available plans in a region"""
|
||||
return client.regions.list_availability(region_id, plan_type=plan_type)
|
||||
86
server/routers/reserved_ips.py
Normal file
86
server/routers/reserved_ips.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""Reserved IPs router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class CreateReservedIPRequest(BaseModel):
|
||||
region: str
|
||||
ip_type: str = "v4" # v4 or v6
|
||||
label: Optional[str] = None
|
||||
|
||||
|
||||
class UpdateReservedIPRequest(BaseModel):
|
||||
label: str
|
||||
|
||||
|
||||
class AttachReservedIPRequest(BaseModel):
|
||||
instance_id: str
|
||||
|
||||
|
||||
class ConvertReservedIPRequest(BaseModel):
|
||||
ip_address: str
|
||||
label: Optional[str] = None
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_reserved_ips(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all reserved IPs"""
|
||||
return client.reserved_ips.list(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
async def list_all_reserved_ips(client: VultrClient = Depends(get_client)):
|
||||
"""List all reserved IPs (auto-paginated)"""
|
||||
return {"reserved_ips": client.reserved_ips.list_all()}
|
||||
|
||||
|
||||
@router.get("/{reserved_ip}")
|
||||
async def get_reserved_ip(reserved_ip: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get reserved IP details"""
|
||||
return client.reserved_ips.get(reserved_ip)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_reserved_ip(req: CreateReservedIPRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Create a reserved IP"""
|
||||
return client.reserved_ips.create(**req.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
@router.patch("/{reserved_ip}")
|
||||
async def update_reserved_ip(reserved_ip: str, req: UpdateReservedIPRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Update a reserved IP"""
|
||||
return client.reserved_ips.update(reserved_ip, label=req.label)
|
||||
|
||||
|
||||
@router.delete("/{reserved_ip}")
|
||||
async def delete_reserved_ip(reserved_ip: str, client: VultrClient = Depends(get_client)):
|
||||
"""Delete a reserved IP"""
|
||||
return client.reserved_ips.delete(reserved_ip)
|
||||
|
||||
|
||||
@router.post("/{reserved_ip}/attach")
|
||||
async def attach_reserved_ip(reserved_ip: str, req: AttachReservedIPRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Attach reserved IP to an instance"""
|
||||
return client.reserved_ips.attach(reserved_ip, instance_id=req.instance_id)
|
||||
|
||||
|
||||
@router.post("/{reserved_ip}/detach")
|
||||
async def detach_reserved_ip(reserved_ip: str, client: VultrClient = Depends(get_client)):
|
||||
"""Detach reserved IP from an instance"""
|
||||
return client.reserved_ips.detach(reserved_ip)
|
||||
|
||||
|
||||
@router.post("/convert")
|
||||
async def convert_to_reserved_ip(req: ConvertReservedIPRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Convert an instance IP to a reserved IP"""
|
||||
return client.reserved_ips.convert(ip_address=req.ip_address, label=req.label)
|
||||
59
server/routers/snapshots.py
Normal file
59
server/routers/snapshots.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""Snapshots router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class CreateSnapshotRequest(BaseModel):
|
||||
instance_id: str
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class CreateSnapshotFromURLRequest(BaseModel):
|
||||
url: str
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_snapshots(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all snapshots"""
|
||||
return client.snapshots.list(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
async def list_all_snapshots(client: VultrClient = Depends(get_client)):
|
||||
"""List all snapshots (auto-paginated)"""
|
||||
return {"snapshots": client.snapshots.list_all()}
|
||||
|
||||
|
||||
@router.get("/{snapshot_id}")
|
||||
async def get_snapshot(snapshot_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get snapshot details"""
|
||||
return client.snapshots.get(snapshot_id)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_snapshot(req: CreateSnapshotRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Create a snapshot from an instance"""
|
||||
return client.snapshots.create(instance_id=req.instance_id, description=req.description)
|
||||
|
||||
|
||||
@router.post("/from-url")
|
||||
async def create_snapshot_from_url(req: CreateSnapshotFromURLRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Create a snapshot from a URL"""
|
||||
return client.snapshots.create_from_url(url=req.url, description=req.description)
|
||||
|
||||
|
||||
@router.delete("/{snapshot_id}")
|
||||
async def delete_snapshot(snapshot_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Delete a snapshot"""
|
||||
return client.snapshots.delete(snapshot_id)
|
||||
59
server/routers/ssh_keys.py
Normal file
59
server/routers/ssh_keys.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""SSH Keys router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class CreateSSHKeyRequest(BaseModel):
|
||||
name: str
|
||||
ssh_key: str
|
||||
|
||||
|
||||
class UpdateSSHKeyRequest(BaseModel):
|
||||
name: Optional[str] = None
|
||||
ssh_key: Optional[str] = None
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_ssh_keys(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all SSH keys"""
|
||||
return client.ssh_keys.list(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
async def list_all_ssh_keys(client: VultrClient = Depends(get_client)):
|
||||
"""List all SSH keys (auto-paginated)"""
|
||||
return {"ssh_keys": client.ssh_keys.list_all()}
|
||||
|
||||
|
||||
@router.get("/{ssh_key_id}")
|
||||
async def get_ssh_key(ssh_key_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get SSH key details"""
|
||||
return client.ssh_keys.get(ssh_key_id)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_ssh_key(req: CreateSSHKeyRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Create an SSH key"""
|
||||
return client.ssh_keys.create(name=req.name, ssh_key=req.ssh_key)
|
||||
|
||||
|
||||
@router.patch("/{ssh_key_id}")
|
||||
async def update_ssh_key(ssh_key_id: str, req: UpdateSSHKeyRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Update an SSH key"""
|
||||
return client.ssh_keys.update(ssh_key_id, **req.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
@router.delete("/{ssh_key_id}")
|
||||
async def delete_ssh_key(ssh_key_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Delete an SSH key"""
|
||||
return client.ssh_keys.delete(ssh_key_id)
|
||||
61
server/routers/startup_scripts.py
Normal file
61
server/routers/startup_scripts.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Startup Scripts router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class CreateScriptRequest(BaseModel):
|
||||
name: str
|
||||
script: str
|
||||
type: Optional[str] = "boot" # boot or pxe
|
||||
|
||||
|
||||
class UpdateScriptRequest(BaseModel):
|
||||
name: Optional[str] = None
|
||||
script: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_startup_scripts(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all startup scripts"""
|
||||
return client.startup_scripts.list(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
async def list_all_startup_scripts(client: VultrClient = Depends(get_client)):
|
||||
"""List all startup scripts (auto-paginated)"""
|
||||
return {"startup_scripts": client.startup_scripts.list_all()}
|
||||
|
||||
|
||||
@router.get("/{script_id}")
|
||||
async def get_startup_script(script_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get startup script details"""
|
||||
return client.startup_scripts.get(script_id)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_startup_script(req: CreateScriptRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Create a startup script"""
|
||||
return client.startup_scripts.create(name=req.name, script=req.script, type=req.type)
|
||||
|
||||
|
||||
@router.patch("/{script_id}")
|
||||
async def update_startup_script(script_id: str, req: UpdateScriptRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Update a startup script"""
|
||||
return client.startup_scripts.update(script_id, **req.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
@router.delete("/{script_id}")
|
||||
async def delete_startup_script(script_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Delete a startup script"""
|
||||
return client.startup_scripts.delete(script_id)
|
||||
136
server/routers/vpc.py
Normal file
136
server/routers/vpc.py
Normal file
@@ -0,0 +1,136 @@
|
||||
"""VPC router"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel
|
||||
|
||||
from vultr_api import VultrClient
|
||||
from deps import get_client
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class CreateVPCRequest(BaseModel):
|
||||
region: str
|
||||
description: Optional[str] = None
|
||||
v4_subnet: Optional[str] = None
|
||||
v4_subnet_mask: Optional[int] = None
|
||||
|
||||
|
||||
class UpdateVPCRequest(BaseModel):
|
||||
description: str
|
||||
|
||||
|
||||
class CreateVPC2Request(BaseModel):
|
||||
region: str
|
||||
description: Optional[str] = None
|
||||
ip_block: Optional[str] = None
|
||||
prefix_length: Optional[int] = None
|
||||
|
||||
|
||||
class AttachVPC2Request(BaseModel):
|
||||
nodes: List[dict] # [{"id": "instance_id", "ip_address": "10.0.0.1"}, ...]
|
||||
|
||||
|
||||
# VPC 1.0
|
||||
@router.get("")
|
||||
async def list_vpcs(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all VPCs"""
|
||||
return client.vpc.list(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
async def list_all_vpcs(client: VultrClient = Depends(get_client)):
|
||||
"""List all VPCs (auto-paginated)"""
|
||||
return {"vpcs": client.vpc.list_all()}
|
||||
|
||||
|
||||
@router.get("/{vpc_id}")
|
||||
async def get_vpc(vpc_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get VPC details"""
|
||||
return client.vpc.get(vpc_id)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_vpc(req: CreateVPCRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Create a VPC"""
|
||||
return client.vpc.create(**req.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
@router.patch("/{vpc_id}")
|
||||
async def update_vpc(vpc_id: str, req: UpdateVPCRequest, client: VultrClient = Depends(get_client)):
|
||||
"""Update a VPC"""
|
||||
return client.vpc.update(vpc_id, description=req.description)
|
||||
|
||||
|
||||
@router.delete("/{vpc_id}")
|
||||
async def delete_vpc(vpc_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Delete a VPC"""
|
||||
return client.vpc.delete(vpc_id)
|
||||
|
||||
|
||||
# VPC 2.0
|
||||
@router.get("/v2/list")
|
||||
async def list_vpc2s(
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List all VPC 2.0 networks"""
|
||||
return client.vpc.list_vpc2(per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.get("/v2/all")
|
||||
async def list_all_vpc2s(client: VultrClient = Depends(get_client)):
|
||||
"""List all VPC 2.0 networks (auto-paginated)"""
|
||||
return {"vpcs": client.vpc.list_all_vpc2()}
|
||||
|
||||
|
||||
@router.get("/v2/{vpc_id}")
|
||||
async def get_vpc2(vpc_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Get VPC 2.0 details"""
|
||||
return client.vpc.get_vpc2(vpc_id)
|
||||
|
||||
|
||||
@router.post("/v2")
|
||||
async def create_vpc2(req: CreateVPC2Request, client: VultrClient = Depends(get_client)):
|
||||
"""Create a VPC 2.0 network"""
|
||||
return client.vpc.create_vpc2(**req.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
@router.patch("/v2/{vpc_id}")
|
||||
async def update_vpc2(vpc_id: str, description: str, client: VultrClient = Depends(get_client)):
|
||||
"""Update a VPC 2.0 network"""
|
||||
return client.vpc.update_vpc2(vpc_id, description=description)
|
||||
|
||||
|
||||
@router.delete("/v2/{vpc_id}")
|
||||
async def delete_vpc2(vpc_id: str, client: VultrClient = Depends(get_client)):
|
||||
"""Delete a VPC 2.0 network"""
|
||||
return client.vpc.delete_vpc2(vpc_id)
|
||||
|
||||
|
||||
@router.get("/v2/{vpc_id}/nodes")
|
||||
async def list_vpc2_nodes(
|
||||
vpc_id: str,
|
||||
per_page: int = Query(25, le=500),
|
||||
cursor: Optional[str] = None,
|
||||
client: VultrClient = Depends(get_client)
|
||||
):
|
||||
"""List nodes attached to a VPC 2.0"""
|
||||
return client.vpc.list_vpc2_nodes(vpc_id, per_page=per_page, cursor=cursor)
|
||||
|
||||
|
||||
@router.post("/v2/{vpc_id}/nodes/attach")
|
||||
async def attach_vpc2_nodes(vpc_id: str, req: AttachVPC2Request, client: VultrClient = Depends(get_client)):
|
||||
"""Attach nodes to a VPC 2.0"""
|
||||
return client.vpc.attach_vpc2_nodes(vpc_id, nodes=req.nodes)
|
||||
|
||||
|
||||
@router.post("/v2/{vpc_id}/nodes/detach")
|
||||
async def detach_vpc2_nodes(vpc_id: str, req: AttachVPC2Request, client: VultrClient = Depends(get_client)):
|
||||
"""Detach nodes from a VPC 2.0"""
|
||||
return client.vpc.detach_vpc2_nodes(vpc_id, nodes=req.nodes)
|
||||
Reference in New Issue
Block a user