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:
HWANG BYUNGHA
2026-01-22 01:08:17 +09:00
commit 184054c6c1
48 changed files with 6058 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
"""
Reserved IPs Resource
Reserved IP address management
"""
from typing import Dict, List
from .base import BaseResource
class ReservedIPsResource(BaseResource):
"""
Reserved IP address management
Usage:
# List reserved IPs
ips = client.reserved_ips.list()
# Create reserved IP
ip = client.reserved_ips.create(region="ewr", ip_type="v4")
# Attach to instance
client.reserved_ips.attach(reserved_ip="id", instance_id="instance-id")
"""
def list(self, per_page: int = 100, cursor: str = None) -> Dict:
"""
List reserved IPs
Returns:
Dict with 'reserved_ips' list and 'meta' pagination
"""
params = {"per_page": per_page}
if cursor:
params["cursor"] = cursor
return self.client.get("reserved-ips", params=params)
def list_all(self) -> List[Dict]:
"""List all reserved IPs (auto-paginate)"""
return self.client.paginate("reserved-ips", "reserved_ips")
def get(self, reserved_ip: str) -> Dict:
"""
Get reserved IP details
Args:
reserved_ip: Reserved IP ID
Returns:
Reserved IP details
"""
response = self.client.get(f"reserved-ips/{reserved_ip}")
return response.get("reserved_ip", {})
def create(
self,
region: str,
ip_type: str,
label: str = None
) -> Dict:
"""
Create a reserved IP
Args:
region: Region ID
ip_type: IP type ("v4" or "v6")
label: Label
Returns:
Created reserved IP details
"""
data = {"region": region, "ip_type": ip_type}
if label:
data["label"] = label
response = self.client.post("reserved-ips", data)
return response.get("reserved_ip", {})
def update(self, reserved_ip: str, label: str) -> None:
"""
Update reserved IP label
Args:
reserved_ip: Reserved IP ID
label: New label
"""
self.client.patch(f"reserved-ips/{reserved_ip}", {"label": label})
def delete(self, reserved_ip: str) -> None:
"""
Delete a reserved IP
Args:
reserved_ip: Reserved IP ID to delete
"""
self.client.delete(f"reserved-ips/{reserved_ip}")
def attach(self, reserved_ip: str, instance_id: str) -> None:
"""
Attach reserved IP to instance
Args:
reserved_ip: Reserved IP ID
instance_id: Instance ID to attach to
"""
self.client.post(f"reserved-ips/{reserved_ip}/attach", {
"instance_id": instance_id
})
def detach(self, reserved_ip: str) -> None:
"""
Detach reserved IP from instance
Args:
reserved_ip: Reserved IP ID
"""
self.client.post(f"reserved-ips/{reserved_ip}/detach")
def convert(self, ip_address: str, label: str = None) -> Dict:
"""
Convert instance IP to reserved IP
Args:
ip_address: IP address to convert
label: Label for reserved IP
Returns:
Created reserved IP details
"""
data = {"ip_address": ip_address}
if label:
data["label"] = label
response = self.client.post("reserved-ips/convert", data)
return response.get("reserved_ip", {})