Make API endpoints compatible with Vultr API v2 format

- Change auth from X-API-Key header to Authorization: Bearer format
- Add /v2 prefix to all endpoints to match Vultr API URL structure
- Fix router paths (dns, firewall) to avoid duplicate path segments
- Split VPC 2.0 into separate router at /v2/vpc2

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
HWANG BYUNGHA
2026-01-22 17:03:19 +09:00
parent 184054c6c1
commit b807b9d267
7 changed files with 159 additions and 136 deletions

View File

@@ -1,20 +1,33 @@
"""Dependencies for FastAPI""" """Dependencies for FastAPI"""
import os import os
from fastapi import HTTPException, Security from fastapi import HTTPException, Header
from fastapi.security import APIKeyHeader from typing import Optional
from vultr_api import VultrClient 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") VULTR_API_KEY = os.environ.get("VULTR_API_KEY")
def get_client(api_key: str = Security(API_KEY_HEADER)) -> VultrClient: def get_client(authorization: Optional[str] = Header(None)) -> VultrClient:
"""Get VultrClient instance with API key from header or environment""" """
key = api_key or VULTR_API_KEY Get VultrClient instance with API key from Authorization header or environment.
Supports Vultr API compatible format:
Authorization: Bearer YOUR_API_KEY
"""
key = None
# Parse "Authorization: Bearer API_KEY" header
if authorization and authorization.startswith("Bearer "):
key = authorization[7:] # Remove "Bearer " prefix
# Fallback to environment variable
if not key:
key = VULTR_API_KEY
if not key: if not key:
raise HTTPException( raise HTTPException(
status_code=401, status_code=401,
detail="API key required. Set X-API-Key header or VULTR_API_KEY env var" detail="API key required. Use 'Authorization: Bearer YOUR_API_KEY' header or set VULTR_API_KEY env var"
) )
return VultrClient(api_key=key) return VultrClient(api_key=key)

View File

@@ -8,7 +8,7 @@ from contextlib import asynccontextmanager
from routers import ( from routers import (
account, instances, dns, firewall, ssh_keys, account, instances, dns, firewall, ssh_keys,
startup_scripts, snapshots, block_storage, reserved_ips, startup_scripts, snapshots, block_storage, reserved_ips,
vpc, load_balancers, bare_metal, backups, plans, regions, os_api vpc, vpc2, load_balancers, bare_metal, backups, plans, regions, os_api
) )
@@ -27,23 +27,24 @@ app = FastAPI(
lifespan=lifespan, lifespan=lifespan,
) )
# Include routers # Include routers with /v2 prefix to match Vultr API
app.include_router(account.router, prefix="/account", tags=["Account"]) app.include_router(account.router, prefix="/v2/account", tags=["Account"])
app.include_router(instances.router, prefix="/instances", tags=["Instances"]) app.include_router(instances.router, prefix="/v2/instances", tags=["Instances"])
app.include_router(dns.router, prefix="/dns", tags=["DNS"]) app.include_router(dns.router, prefix="/v2/domains", tags=["DNS"])
app.include_router(firewall.router, prefix="/firewall", tags=["Firewall"]) app.include_router(firewall.router, prefix="/v2/firewalls", tags=["Firewall"])
app.include_router(ssh_keys.router, prefix="/ssh-keys", tags=["SSH Keys"]) app.include_router(ssh_keys.router, prefix="/v2/ssh-keys", tags=["SSH Keys"])
app.include_router(startup_scripts.router, prefix="/startup-scripts", tags=["Startup Scripts"]) app.include_router(startup_scripts.router, prefix="/v2/startup-scripts", tags=["Startup Scripts"])
app.include_router(snapshots.router, prefix="/snapshots", tags=["Snapshots"]) app.include_router(snapshots.router, prefix="/v2/snapshots", tags=["Snapshots"])
app.include_router(block_storage.router, prefix="/block-storage", tags=["Block Storage"]) app.include_router(block_storage.router, prefix="/v2/blocks", tags=["Block Storage"])
app.include_router(reserved_ips.router, prefix="/reserved-ips", tags=["Reserved IPs"]) app.include_router(reserved_ips.router, prefix="/v2/reserved-ips", tags=["Reserved IPs"])
app.include_router(vpc.router, prefix="/vpc", tags=["VPC"]) app.include_router(vpc.router, prefix="/v2/vpcs", tags=["VPC"])
app.include_router(load_balancers.router, prefix="/load-balancers", tags=["Load Balancers"]) app.include_router(vpc2.router, prefix="/v2/vpc2", tags=["VPC 2.0"])
app.include_router(bare_metal.router, prefix="/bare-metal", tags=["Bare Metal"]) app.include_router(load_balancers.router, prefix="/v2/load-balancers", tags=["Load Balancers"])
app.include_router(backups.router, prefix="/backups", tags=["Backups"]) app.include_router(bare_metal.router, prefix="/v2/bare-metals", tags=["Bare Metal"])
app.include_router(plans.router, prefix="/plans", tags=["Plans"]) app.include_router(backups.router, prefix="/v2/backups", tags=["Backups"])
app.include_router(regions.router, prefix="/regions", tags=["Regions"]) app.include_router(plans.router, prefix="/v2/plans", tags=["Plans"])
app.include_router(os_api.router, prefix="/os", tags=["Operating Systems"]) app.include_router(regions.router, prefix="/v2/regions", tags=["Regions"])
app.include_router(os_api.router, prefix="/v2/os", tags=["Operating Systems"])
@app.get("/", tags=["Health"]) @app.get("/", tags=["Health"])

View File

@@ -10,6 +10,7 @@ from . import (
block_storage, block_storage,
reserved_ips, reserved_ips,
vpc, vpc,
vpc2,
load_balancers, load_balancers,
bare_metal, bare_metal,
backups, backups,
@@ -29,6 +30,7 @@ __all__ = [
"block_storage", "block_storage",
"reserved_ips", "reserved_ips",
"vpc", "vpc",
"vpc2",
"load_balancers", "load_balancers",
"bare_metal", "bare_metal",
"backups", "backups",

View File

@@ -29,7 +29,7 @@ class UpdateRecordRequest(BaseModel):
priority: Optional[int] = None priority: Optional[int] = None
@router.get("/domains") @router.get("")
async def list_domains( async def list_domains(
per_page: int = Query(25, le=500), per_page: int = Query(25, le=500),
cursor: Optional[str] = None, cursor: Optional[str] = None,
@@ -39,44 +39,44 @@ async def list_domains(
return client.dns.list_domains(per_page=per_page, cursor=cursor) return client.dns.list_domains(per_page=per_page, cursor=cursor)
@router.get("/domains/all") @router.get("/all")
async def list_all_domains(client: VultrClient = Depends(get_client)): async def list_all_domains(client: VultrClient = Depends(get_client)):
"""List all DNS domains (auto-paginated)""" """List all DNS domains (auto-paginated)"""
return {"domains": client.dns.list_all_domains()} return {"domains": client.dns.list_all_domains()}
@router.get("/domains/{domain}") @router.get("/{domain}")
async def get_domain(domain: str, client: VultrClient = Depends(get_client)): async def get_domain(domain: str, client: VultrClient = Depends(get_client)):
"""Get DNS domain details""" """Get DNS domain details"""
return client.dns.get_domain(domain) return client.dns.get_domain(domain)
@router.post("/domains") @router.post("")
async def create_domain(req: CreateDomainRequest, client: VultrClient = Depends(get_client)): async def create_domain(req: CreateDomainRequest, client: VultrClient = Depends(get_client)):
"""Create a DNS domain""" """Create a DNS domain"""
return client.dns.create_domain(req.domain, ip=req.ip) return client.dns.create_domain(req.domain, ip=req.ip)
@router.delete("/domains/{domain}") @router.delete("/{domain}")
async def delete_domain(domain: str, client: VultrClient = Depends(get_client)): async def delete_domain(domain: str, client: VultrClient = Depends(get_client)):
"""Delete a DNS domain""" """Delete a DNS domain"""
return client.dns.delete_domain(domain) return client.dns.delete_domain(domain)
@router.get("/domains/{domain}/soa") @router.get("/{domain}/soa")
async def get_soa(domain: str, client: VultrClient = Depends(get_client)): async def get_soa(domain: str, client: VultrClient = Depends(get_client)):
"""Get SOA record for a domain""" """Get SOA record for a domain"""
return client.dns.get_soa(domain) return client.dns.get_soa(domain)
@router.get("/domains/{domain}/dnssec") @router.get("/{domain}/dnssec")
async def get_dnssec(domain: str, client: VultrClient = Depends(get_client)): async def get_dnssec(domain: str, client: VultrClient = Depends(get_client)):
"""Get DNSSEC info for a domain""" """Get DNSSEC info for a domain"""
return client.dns.get_dnssec(domain) return client.dns.get_dnssec(domain)
# Records # Records
@router.get("/domains/{domain}/records") @router.get("/{domain}/records")
async def list_records( async def list_records(
domain: str, domain: str,
per_page: int = Query(25, le=500), per_page: int = Query(25, le=500),
@@ -87,19 +87,19 @@ async def list_records(
return client.dns.list_records(domain, per_page=per_page, cursor=cursor) return client.dns.list_records(domain, per_page=per_page, cursor=cursor)
@router.get("/domains/{domain}/records/all") @router.get("/{domain}/records/all")
async def list_all_records(domain: str, client: VultrClient = Depends(get_client)): async def list_all_records(domain: str, client: VultrClient = Depends(get_client)):
"""List all DNS records (auto-paginated)""" """List all DNS records (auto-paginated)"""
return {"records": client.dns.list_all_records(domain)} return {"records": client.dns.list_all_records(domain)}
@router.get("/domains/{domain}/records/{record_id}") @router.get("/{domain}/records/{record_id}")
async def get_record(domain: str, record_id: str, client: VultrClient = Depends(get_client)): async def get_record(domain: str, record_id: str, client: VultrClient = Depends(get_client)):
"""Get a DNS record""" """Get a DNS record"""
return client.dns.get_record(domain, record_id) return client.dns.get_record(domain, record_id)
@router.post("/domains/{domain}/records") @router.post("/{domain}/records")
async def create_record(domain: str, req: CreateRecordRequest, client: VultrClient = Depends(get_client)): async def create_record(domain: str, req: CreateRecordRequest, client: VultrClient = Depends(get_client)):
"""Create a DNS record""" """Create a DNS record"""
return client.dns.create_record( return client.dns.create_record(
@@ -112,44 +112,44 @@ async def create_record(domain: str, req: CreateRecordRequest, client: VultrClie
) )
@router.patch("/domains/{domain}/records/{record_id}") @router.patch("/{domain}/records/{record_id}")
async def update_record(domain: str, record_id: str, req: UpdateRecordRequest, client: VultrClient = Depends(get_client)): async def update_record(domain: str, record_id: str, req: UpdateRecordRequest, client: VultrClient = Depends(get_client)):
"""Update a DNS record""" """Update a DNS record"""
return client.dns.update_record(domain, record_id, **req.model_dump(exclude_none=True)) return client.dns.update_record(domain, record_id, **req.model_dump(exclude_none=True))
@router.delete("/domains/{domain}/records/{record_id}") @router.delete("/{domain}/records/{record_id}")
async def delete_record(domain: str, record_id: str, client: VultrClient = Depends(get_client)): async def delete_record(domain: str, record_id: str, client: VultrClient = Depends(get_client)):
"""Delete a DNS record""" """Delete a DNS record"""
return client.dns.delete_record(domain, record_id) return client.dns.delete_record(domain, record_id)
# Convenience endpoints # Convenience endpoints
@router.post("/domains/{domain}/records/a") @router.post("/{domain}/records/a")
async def create_a_record(domain: str, name: str, ip: str, ttl: int = 300, client: VultrClient = Depends(get_client)): async def create_a_record(domain: str, name: str, ip: str, ttl: int = 300, client: VultrClient = Depends(get_client)):
"""Create an A record""" """Create an A record"""
return client.dns.create_a_record(domain, name, ip, ttl) return client.dns.create_a_record(domain, name, ip, ttl)
@router.post("/domains/{domain}/records/aaaa") @router.post("/{domain}/records/aaaa")
async def create_aaaa_record(domain: str, name: str, ip: str, ttl: int = 300, client: VultrClient = Depends(get_client)): async def create_aaaa_record(domain: str, name: str, ip: str, ttl: int = 300, client: VultrClient = Depends(get_client)):
"""Create an AAAA record""" """Create an AAAA record"""
return client.dns.create_aaaa_record(domain, name, ip, ttl) return client.dns.create_aaaa_record(domain, name, ip, ttl)
@router.post("/domains/{domain}/records/cname") @router.post("/{domain}/records/cname")
async def create_cname_record(domain: str, name: str, target: str, ttl: int = 300, client: VultrClient = Depends(get_client)): async def create_cname_record(domain: str, name: str, target: str, ttl: int = 300, client: VultrClient = Depends(get_client)):
"""Create a CNAME record""" """Create a CNAME record"""
return client.dns.create_cname_record(domain, name, target, ttl) return client.dns.create_cname_record(domain, name, target, ttl)
@router.post("/domains/{domain}/records/txt") @router.post("/{domain}/records/txt")
async def create_txt_record(domain: str, name: str, data: str, ttl: int = 300, client: VultrClient = Depends(get_client)): async def create_txt_record(domain: str, name: str, data: str, ttl: int = 300, client: VultrClient = Depends(get_client)):
"""Create a TXT record""" """Create a TXT record"""
return client.dns.create_txt_record(domain, name, data, ttl) return client.dns.create_txt_record(domain, name, data, ttl)
@router.post("/domains/{domain}/records/mx") @router.post("/{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)): 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""" """Create an MX record"""
return client.dns.create_mx_record(domain, name, data, priority, ttl) return client.dns.create_mx_record(domain, name, data, priority, ttl)

View File

@@ -23,7 +23,7 @@ class CreateRuleRequest(BaseModel):
notes: Optional[str] = None notes: Optional[str] = None
@router.get("/groups") @router.get("")
async def list_groups( async def list_groups(
per_page: int = Query(25, le=500), per_page: int = Query(25, le=500),
cursor: Optional[str] = None, cursor: Optional[str] = None,
@@ -33,38 +33,38 @@ async def list_groups(
return client.firewall.list_groups(per_page=per_page, cursor=cursor) return client.firewall.list_groups(per_page=per_page, cursor=cursor)
@router.get("/groups/all") @router.get("/all")
async def list_all_groups(client: VultrClient = Depends(get_client)): async def list_all_groups(client: VultrClient = Depends(get_client)):
"""List all firewall groups (auto-paginated)""" """List all firewall groups (auto-paginated)"""
return {"firewall_groups": client.firewall.list_all_groups()} return {"firewall_groups": client.firewall.list_all_groups()}
@router.get("/groups/{group_id}") @router.get("/{group_id}")
async def get_group(group_id: str, client: VultrClient = Depends(get_client)): async def get_group(group_id: str, client: VultrClient = Depends(get_client)):
"""Get firewall group details""" """Get firewall group details"""
return client.firewall.get_group(group_id) return client.firewall.get_group(group_id)
@router.post("/groups") @router.post("")
async def create_group(req: CreateGroupRequest, client: VultrClient = Depends(get_client)): async def create_group(req: CreateGroupRequest, client: VultrClient = Depends(get_client)):
"""Create a firewall group""" """Create a firewall group"""
return client.firewall.create_group(description=req.description) return client.firewall.create_group(description=req.description)
@router.patch("/groups/{group_id}") @router.patch("/{group_id}")
async def update_group(group_id: str, description: str, client: VultrClient = Depends(get_client)): async def update_group(group_id: str, description: str, client: VultrClient = Depends(get_client)):
"""Update a firewall group""" """Update a firewall group"""
return client.firewall.update_group(group_id, description=description) return client.firewall.update_group(group_id, description=description)
@router.delete("/groups/{group_id}") @router.delete("/{group_id}")
async def delete_group(group_id: str, client: VultrClient = Depends(get_client)): async def delete_group(group_id: str, client: VultrClient = Depends(get_client)):
"""Delete a firewall group""" """Delete a firewall group"""
return client.firewall.delete_group(group_id) return client.firewall.delete_group(group_id)
# Rules # Rules
@router.get("/groups/{group_id}/rules") @router.get("/{group_id}/rules")
async def list_rules( async def list_rules(
group_id: str, group_id: str,
per_page: int = Query(25, le=500), per_page: int = Query(25, le=500),
@@ -75,50 +75,50 @@ async def list_rules(
return client.firewall.list_rules(group_id, per_page=per_page, cursor=cursor) return client.firewall.list_rules(group_id, per_page=per_page, cursor=cursor)
@router.get("/groups/{group_id}/rules/all") @router.get("/{group_id}/rules/all")
async def list_all_rules(group_id: str, client: VultrClient = Depends(get_client)): async def list_all_rules(group_id: str, client: VultrClient = Depends(get_client)):
"""List all firewall rules (auto-paginated)""" """List all firewall rules (auto-paginated)"""
return {"firewall_rules": client.firewall.list_all_rules(group_id)} return {"firewall_rules": client.firewall.list_all_rules(group_id)}
@router.get("/groups/{group_id}/rules/{rule_id}") @router.get("/{group_id}/rules/{rule_id}")
async def get_rule(group_id: str, rule_id: int, client: VultrClient = Depends(get_client)): async def get_rule(group_id: str, rule_id: int, client: VultrClient = Depends(get_client)):
"""Get a firewall rule""" """Get a firewall rule"""
return client.firewall.get_rule(group_id, rule_id) return client.firewall.get_rule(group_id, rule_id)
@router.post("/groups/{group_id}/rules") @router.post("/{group_id}/rules")
async def create_rule(group_id: str, req: CreateRuleRequest, client: VultrClient = Depends(get_client)): async def create_rule(group_id: str, req: CreateRuleRequest, client: VultrClient = Depends(get_client)):
"""Create a firewall rule""" """Create a firewall rule"""
return client.firewall.create_rule(group_id, **req.model_dump(exclude_none=True)) return client.firewall.create_rule(group_id, **req.model_dump(exclude_none=True))
@router.delete("/groups/{group_id}/rules/{rule_id}") @router.delete("/{group_id}/rules/{rule_id}")
async def delete_rule(group_id: str, rule_id: int, client: VultrClient = Depends(get_client)): async def delete_rule(group_id: str, rule_id: int, client: VultrClient = Depends(get_client)):
"""Delete a firewall rule""" """Delete a firewall rule"""
return client.firewall.delete_rule(group_id, rule_id) return client.firewall.delete_rule(group_id, rule_id)
# Convenience endpoints # Convenience endpoints
@router.post("/groups/{group_id}/rules/allow-ssh") @router.post("/{group_id}/rules/allow-ssh")
async def allow_ssh(group_id: str, source: str = "0.0.0.0/0", client: VultrClient = Depends(get_client)): 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""" """Allow SSH (port 22) from source"""
return client.firewall.allow_ssh(group_id, source=source) return client.firewall.allow_ssh(group_id, source=source)
@router.post("/groups/{group_id}/rules/allow-http") @router.post("/{group_id}/rules/allow-http")
async def allow_http(group_id: str, source: str = "0.0.0.0/0", client: VultrClient = Depends(get_client)): 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""" """Allow HTTP (port 80) from source"""
return client.firewall.allow_http(group_id, source=source) return client.firewall.allow_http(group_id, source=source)
@router.post("/groups/{group_id}/rules/allow-https") @router.post("/{group_id}/rules/allow-https")
async def allow_https(group_id: str, source: str = "0.0.0.0/0", client: VultrClient = Depends(get_client)): 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""" """Allow HTTPS (port 443) from source"""
return client.firewall.allow_https(group_id, source=source) return client.firewall.allow_https(group_id, source=source)
@router.post("/groups/{group_id}/rules/allow-ping") @router.post("/{group_id}/rules/allow-ping")
async def allow_ping(group_id: str, source: str = "0.0.0.0/0", client: VultrClient = Depends(get_client)): async def allow_ping(group_id: str, source: str = "0.0.0.0/0", client: VultrClient = Depends(get_client)):
"""Allow ICMP ping from source""" """Allow ICMP ping from source"""
return client.firewall.allow_ping(group_id, source=source) return client.firewall.allow_ping(group_id, source=source)

View File

@@ -1,6 +1,6 @@
"""VPC router""" """VPC router (VPC 1.0)"""
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from typing import Optional, List from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
from vultr_api import VultrClient from vultr_api import VultrClient
@@ -20,18 +20,6 @@ class UpdateVPCRequest(BaseModel):
description: str 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("") @router.get("")
async def list_vpcs( async def list_vpcs(
per_page: int = Query(25, le=500), per_page: int = Query(25, le=500),
@@ -70,67 +58,3 @@ async def update_vpc(vpc_id: str, req: UpdateVPCRequest, client: VultrClient = D
async def delete_vpc(vpc_id: str, client: VultrClient = Depends(get_client)): async def delete_vpc(vpc_id: str, client: VultrClient = Depends(get_client)):
"""Delete a VPC""" """Delete a VPC"""
return client.vpc.delete(vpc_id) 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)

83
server/routers/vpc2.py Normal file
View File

@@ -0,0 +1,83 @@
"""VPC 2.0 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 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"}, ...]
@router.get("")
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("/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("/{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("")
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("/{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("/{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("/{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("/{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("/{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)