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

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

View File

@@ -29,7 +29,7 @@ class UpdateRecordRequest(BaseModel):
priority: Optional[int] = None
@router.get("/domains")
@router.get("")
async def list_domains(
per_page: int = Query(25, le=500),
cursor: Optional[str] = None,
@@ -39,44 +39,44 @@ async def list_domains(
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)):
"""List all DNS domains (auto-paginated)"""
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)):
"""Get DNS domain details"""
return client.dns.get_domain(domain)
@router.post("/domains")
@router.post("")
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}")
@router.delete("/{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")
@router.get("/{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")
@router.get("/{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")
@router.get("/{domain}/records")
async def list_records(
domain: str,
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)
@router.get("/domains/{domain}/records/all")
@router.get("/{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}")
@router.get("/{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")
@router.post("/{domain}/records")
async def create_record(domain: str, req: CreateRecordRequest, client: VultrClient = Depends(get_client)):
"""Create a DNS 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)):
"""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}")
@router.delete("/{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")
@router.post("/{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")
@router.post("/{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")
@router.post("/{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")
@router.post("/{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")
@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)):
"""Create an MX record"""
return client.dns.create_mx_record(domain, name, data, priority, ttl)

View File

@@ -23,7 +23,7 @@ class CreateRuleRequest(BaseModel):
notes: Optional[str] = None
@router.get("/groups")
@router.get("")
async def list_groups(
per_page: int = Query(25, le=500),
cursor: Optional[str] = None,
@@ -33,38 +33,38 @@ async def list_groups(
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)):
"""List all firewall groups (auto-paginated)"""
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)):
"""Get firewall group details"""
return client.firewall.get_group(group_id)
@router.post("/groups")
@router.post("")
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}")
@router.patch("/{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}")
@router.delete("/{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")
@router.get("/{group_id}/rules")
async def list_rules(
group_id: str,
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)
@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)):
"""List all firewall rules (auto-paginated)"""
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)):
"""Get a firewall rule"""
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)):
"""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}")
@router.delete("/{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")
@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)):
"""Allow SSH (port 22) from 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)):
"""Allow HTTP (port 80) from 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)):
"""Allow HTTPS (port 443) from 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)):
"""Allow ICMP ping from 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 typing import Optional, List
from typing import Optional
from pydantic import BaseModel
from vultr_api import VultrClient
@@ -20,18 +20,6 @@ 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),
@@ -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)):
"""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)

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)