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:
536
vultr_api/resources/instances.py
Normal file
536
vultr_api/resources/instances.py
Normal file
@@ -0,0 +1,536 @@
|
||||
"""
|
||||
Instances Resource
|
||||
|
||||
Cloud compute instance management
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
from .base import BaseResource
|
||||
|
||||
|
||||
class InstancesResource(BaseResource):
|
||||
"""
|
||||
Cloud compute instances management
|
||||
|
||||
Usage:
|
||||
# List all instances
|
||||
instances = client.instances.list()
|
||||
|
||||
# Get specific instance
|
||||
instance = client.instances.get("instance-id")
|
||||
|
||||
# Create instance
|
||||
instance = client.instances.create(
|
||||
region="ewr",
|
||||
plan="vc2-1c-1gb",
|
||||
os_id=215,
|
||||
label="my-server"
|
||||
)
|
||||
|
||||
# Delete instance
|
||||
client.instances.delete("instance-id")
|
||||
"""
|
||||
|
||||
def list(
|
||||
self,
|
||||
per_page: int = 100,
|
||||
cursor: str = None,
|
||||
tag: str = None,
|
||||
label: str = None,
|
||||
main_ip: str = None,
|
||||
region: str = None,
|
||||
) -> Dict:
|
||||
"""
|
||||
List all instances
|
||||
|
||||
Args:
|
||||
per_page: Number of items per page (max 500)
|
||||
cursor: Pagination cursor
|
||||
tag: Filter by tag
|
||||
label: Filter by label
|
||||
main_ip: Filter by main IP
|
||||
region: Filter by region
|
||||
|
||||
Returns:
|
||||
Dict with 'instances' list and 'meta' pagination info
|
||||
"""
|
||||
params = {"per_page": per_page}
|
||||
if cursor:
|
||||
params["cursor"] = cursor
|
||||
if tag:
|
||||
params["tag"] = tag
|
||||
if label:
|
||||
params["label"] = label
|
||||
if main_ip:
|
||||
params["main_ip"] = main_ip
|
||||
if region:
|
||||
params["region"] = region
|
||||
|
||||
return self.client.get("instances", params=params)
|
||||
|
||||
def list_all(self, **kwargs) -> List[Dict]:
|
||||
"""
|
||||
List all instances (auto-paginate)
|
||||
|
||||
Returns:
|
||||
List of all instances
|
||||
"""
|
||||
return self.client.paginate("instances", "instances", params=kwargs)
|
||||
|
||||
def get(self, instance_id: str) -> Dict:
|
||||
"""
|
||||
Get instance details
|
||||
|
||||
Args:
|
||||
instance_id: Instance ID
|
||||
|
||||
Returns:
|
||||
Instance details
|
||||
"""
|
||||
response = self.client.get(f"instances/{instance_id}")
|
||||
return response.get("instance", {})
|
||||
|
||||
def create(
|
||||
self,
|
||||
region: str,
|
||||
plan: str,
|
||||
os_id: int = None,
|
||||
iso_id: str = None,
|
||||
snapshot_id: str = None,
|
||||
app_id: int = None,
|
||||
image_id: str = None,
|
||||
ipxe_chain_url: str = None,
|
||||
script_id: str = None,
|
||||
enable_ipv6: bool = False,
|
||||
disable_public_ipv4: bool = False,
|
||||
attach_private_network: List[str] = None,
|
||||
attach_vpc: List[str] = None,
|
||||
attach_vpc2: List[str] = None,
|
||||
label: str = None,
|
||||
sshkey_id: List[str] = None,
|
||||
backups: str = None,
|
||||
ddos_protection: bool = False,
|
||||
activation_email: bool = False,
|
||||
hostname: str = None,
|
||||
tag: str = None,
|
||||
tags: List[str] = None,
|
||||
firewall_group_id: str = None,
|
||||
reserved_ipv4: str = None,
|
||||
user_data: str = None,
|
||||
) -> Dict:
|
||||
"""
|
||||
Create a new instance
|
||||
|
||||
Args:
|
||||
region: Region ID (e.g., "ewr", "lax", "nrt")
|
||||
plan: Plan ID (e.g., "vc2-1c-1gb")
|
||||
os_id: Operating System ID
|
||||
iso_id: ISO ID for custom install
|
||||
snapshot_id: Snapshot ID to restore from
|
||||
app_id: Application ID
|
||||
image_id: Custom image ID
|
||||
ipxe_chain_url: iPXE chain URL
|
||||
script_id: Startup script ID
|
||||
enable_ipv6: Enable IPv6
|
||||
disable_public_ipv4: Disable public IPv4
|
||||
attach_private_network: Private network IDs to attach
|
||||
attach_vpc: VPC IDs to attach
|
||||
attach_vpc2: VPC 2.0 IDs to attach
|
||||
label: Instance label
|
||||
sshkey_id: SSH key IDs to add
|
||||
backups: Backup schedule ("enabled" or "disabled")
|
||||
ddos_protection: Enable DDoS protection
|
||||
activation_email: Send activation email
|
||||
hostname: Server hostname
|
||||
tag: Deprecated, use tags
|
||||
tags: Tags for the instance
|
||||
firewall_group_id: Firewall group ID
|
||||
reserved_ipv4: Reserved IPv4 to assign
|
||||
user_data: Cloud-init user data (base64)
|
||||
|
||||
Returns:
|
||||
Created instance details
|
||||
"""
|
||||
data = {"region": region, "plan": plan}
|
||||
|
||||
# Add optional parameters
|
||||
if os_id is not None:
|
||||
data["os_id"] = os_id
|
||||
if iso_id:
|
||||
data["iso_id"] = iso_id
|
||||
if snapshot_id:
|
||||
data["snapshot_id"] = snapshot_id
|
||||
if app_id is not None:
|
||||
data["app_id"] = app_id
|
||||
if image_id:
|
||||
data["image_id"] = image_id
|
||||
if ipxe_chain_url:
|
||||
data["ipxe_chain_url"] = ipxe_chain_url
|
||||
if script_id:
|
||||
data["script_id"] = script_id
|
||||
if enable_ipv6:
|
||||
data["enable_ipv6"] = enable_ipv6
|
||||
if disable_public_ipv4:
|
||||
data["disable_public_ipv4"] = disable_public_ipv4
|
||||
if attach_private_network:
|
||||
data["attach_private_network"] = attach_private_network
|
||||
if attach_vpc:
|
||||
data["attach_vpc"] = attach_vpc
|
||||
if attach_vpc2:
|
||||
data["attach_vpc2"] = attach_vpc2
|
||||
if label:
|
||||
data["label"] = label
|
||||
if sshkey_id:
|
||||
data["sshkey_id"] = sshkey_id
|
||||
if backups:
|
||||
data["backups"] = backups
|
||||
if ddos_protection:
|
||||
data["ddos_protection"] = ddos_protection
|
||||
if activation_email:
|
||||
data["activation_email"] = activation_email
|
||||
if hostname:
|
||||
data["hostname"] = hostname
|
||||
if tag:
|
||||
data["tag"] = tag
|
||||
if tags:
|
||||
data["tags"] = tags
|
||||
if firewall_group_id:
|
||||
data["firewall_group_id"] = firewall_group_id
|
||||
if reserved_ipv4:
|
||||
data["reserved_ipv4"] = reserved_ipv4
|
||||
if user_data:
|
||||
data["user_data"] = user_data
|
||||
|
||||
response = self.client.post("instances", data)
|
||||
return response.get("instance", {})
|
||||
|
||||
def update(
|
||||
self,
|
||||
instance_id: str,
|
||||
app_id: int = None,
|
||||
image_id: str = None,
|
||||
backups: str = None,
|
||||
firewall_group_id: str = None,
|
||||
enable_ipv6: bool = None,
|
||||
os_id: int = None,
|
||||
user_data: str = None,
|
||||
tag: str = None,
|
||||
tags: List[str] = None,
|
||||
label: str = None,
|
||||
ddos_protection: bool = None,
|
||||
) -> Dict:
|
||||
"""
|
||||
Update an instance
|
||||
|
||||
Args:
|
||||
instance_id: Instance ID to update
|
||||
(other args same as create)
|
||||
|
||||
Returns:
|
||||
Updated instance details
|
||||
"""
|
||||
data = {}
|
||||
|
||||
if app_id is not None:
|
||||
data["app_id"] = app_id
|
||||
if image_id is not None:
|
||||
data["image_id"] = image_id
|
||||
if backups is not None:
|
||||
data["backups"] = backups
|
||||
if firewall_group_id is not None:
|
||||
data["firewall_group_id"] = firewall_group_id
|
||||
if enable_ipv6 is not None:
|
||||
data["enable_ipv6"] = enable_ipv6
|
||||
if os_id is not None:
|
||||
data["os_id"] = os_id
|
||||
if user_data is not None:
|
||||
data["user_data"] = user_data
|
||||
if tag is not None:
|
||||
data["tag"] = tag
|
||||
if tags is not None:
|
||||
data["tags"] = tags
|
||||
if label is not None:
|
||||
data["label"] = label
|
||||
if ddos_protection is not None:
|
||||
data["ddos_protection"] = ddos_protection
|
||||
|
||||
response = self.client.patch(f"instances/{instance_id}", data)
|
||||
return response.get("instance", {})
|
||||
|
||||
def delete(self, instance_id: str) -> None:
|
||||
"""
|
||||
Delete an instance
|
||||
|
||||
Args:
|
||||
instance_id: Instance ID to delete
|
||||
"""
|
||||
self.client.delete(f"instances/{instance_id}")
|
||||
|
||||
def start(self, instance_id: str) -> None:
|
||||
"""Start an instance"""
|
||||
self.client.post(f"instances/{instance_id}/start")
|
||||
|
||||
def stop(self, instance_id: str) -> None:
|
||||
"""Stop an instance (alias for halt)"""
|
||||
self.halt(instance_id)
|
||||
|
||||
def halt(self, instance_id: str) -> None:
|
||||
"""Halt an instance"""
|
||||
self.client.post(f"instances/{instance_id}/halt")
|
||||
|
||||
def reboot(self, instance_id: str) -> None:
|
||||
"""Reboot an instance"""
|
||||
self.client.post(f"instances/{instance_id}/reboot")
|
||||
|
||||
def reinstall(self, instance_id: str, hostname: str = None) -> Dict:
|
||||
"""
|
||||
Reinstall an instance
|
||||
|
||||
Args:
|
||||
instance_id: Instance ID
|
||||
hostname: New hostname (optional)
|
||||
|
||||
Returns:
|
||||
Instance details
|
||||
"""
|
||||
data = {}
|
||||
if hostname:
|
||||
data["hostname"] = hostname
|
||||
|
||||
response = self.client.post(f"instances/{instance_id}/reinstall", data)
|
||||
return response.get("instance", {})
|
||||
|
||||
def get_bandwidth(self, instance_id: str) -> Dict:
|
||||
"""Get instance bandwidth usage"""
|
||||
return self.client.get(f"instances/{instance_id}/bandwidth")
|
||||
|
||||
def get_neighbors(self, instance_id: str) -> Dict:
|
||||
"""Get instance neighbors (shared host)"""
|
||||
return self.client.get(f"instances/{instance_id}/neighbors")
|
||||
|
||||
def get_user_data(self, instance_id: str) -> Dict:
|
||||
"""Get instance user data"""
|
||||
return self.client.get(f"instances/{instance_id}/user-data")
|
||||
|
||||
def get_upgrades(self, instance_id: str, upgrade_type: str = None) -> Dict:
|
||||
"""
|
||||
Get available upgrades
|
||||
|
||||
Args:
|
||||
instance_id: Instance ID
|
||||
upgrade_type: Filter by type ("all", "applications", "os", "plans")
|
||||
|
||||
Returns:
|
||||
Available upgrades
|
||||
"""
|
||||
params = {}
|
||||
if upgrade_type:
|
||||
params["type"] = upgrade_type
|
||||
return self.client.get(f"instances/{instance_id}/upgrades", params=params)
|
||||
|
||||
# IPv4 management
|
||||
def list_ipv4(self, instance_id: str) -> List[Dict]:
|
||||
"""List IPv4 addresses"""
|
||||
response = self.client.get(f"instances/{instance_id}/ipv4")
|
||||
return response.get("ipv4s", [])
|
||||
|
||||
def create_ipv4(self, instance_id: str, reboot: bool = True) -> Dict:
|
||||
"""
|
||||
Create additional IPv4
|
||||
|
||||
Args:
|
||||
instance_id: Instance ID
|
||||
reboot: Reboot instance to apply (default True)
|
||||
|
||||
Returns:
|
||||
New IPv4 info
|
||||
"""
|
||||
response = self.client.post(
|
||||
f"instances/{instance_id}/ipv4",
|
||||
{"reboot": reboot}
|
||||
)
|
||||
return response.get("ipv4", {})
|
||||
|
||||
def delete_ipv4(self, instance_id: str, ipv4: str) -> None:
|
||||
"""Delete an IPv4 address"""
|
||||
self.client.delete(f"instances/{instance_id}/ipv4/{ipv4}")
|
||||
|
||||
def create_ipv4_reverse(
|
||||
self,
|
||||
instance_id: str,
|
||||
ip: str,
|
||||
reverse: str
|
||||
) -> None:
|
||||
"""Set IPv4 reverse DNS"""
|
||||
self.client.post(
|
||||
f"instances/{instance_id}/ipv4/reverse",
|
||||
{"ip": ip, "reverse": reverse}
|
||||
)
|
||||
|
||||
def set_default_ipv4_reverse(self, instance_id: str, ip: str) -> None:
|
||||
"""Set default reverse DNS for IPv4"""
|
||||
self.client.post(
|
||||
f"instances/{instance_id}/ipv4/reverse/default",
|
||||
{"ip": ip}
|
||||
)
|
||||
|
||||
# IPv6 management
|
||||
def list_ipv6(self, instance_id: str) -> List[Dict]:
|
||||
"""List IPv6 addresses"""
|
||||
response = self.client.get(f"instances/{instance_id}/ipv6")
|
||||
return response.get("ipv6s", [])
|
||||
|
||||
def create_ipv6_reverse(
|
||||
self,
|
||||
instance_id: str,
|
||||
ip: str,
|
||||
reverse: str
|
||||
) -> None:
|
||||
"""Set IPv6 reverse DNS"""
|
||||
self.client.post(
|
||||
f"instances/{instance_id}/ipv6/reverse",
|
||||
{"ip": ip, "reverse": reverse}
|
||||
)
|
||||
|
||||
def list_ipv6_reverse(self, instance_id: str) -> List[Dict]:
|
||||
"""List IPv6 reverse DNS entries"""
|
||||
response = self.client.get(f"instances/{instance_id}/ipv6/reverse")
|
||||
return response.get("reverse_ipv6s", [])
|
||||
|
||||
def delete_ipv6_reverse(self, instance_id: str, ipv6: str) -> None:
|
||||
"""Delete IPv6 reverse DNS"""
|
||||
self.client.delete(f"instances/{instance_id}/ipv6/reverse/{ipv6}")
|
||||
|
||||
# Backup management
|
||||
def get_backup_schedule(self, instance_id: str) -> Dict:
|
||||
"""Get backup schedule"""
|
||||
return self.client.get(f"instances/{instance_id}/backup-schedule")
|
||||
|
||||
def set_backup_schedule(
|
||||
self,
|
||||
instance_id: str,
|
||||
backup_type: str,
|
||||
hour: int = None,
|
||||
dow: int = None,
|
||||
dom: int = None,
|
||||
) -> None:
|
||||
"""
|
||||
Set backup schedule
|
||||
|
||||
Args:
|
||||
instance_id: Instance ID
|
||||
backup_type: Schedule type ("daily", "weekly", "monthly", "daily_alt_even", "daily_alt_odd")
|
||||
hour: Hour of day (0-23)
|
||||
dow: Day of week (1-7, for weekly)
|
||||
dom: Day of month (1-28, for monthly)
|
||||
"""
|
||||
data = {"type": backup_type}
|
||||
if hour is not None:
|
||||
data["hour"] = hour
|
||||
if dow is not None:
|
||||
data["dow"] = dow
|
||||
if dom is not None:
|
||||
data["dom"] = dom
|
||||
|
||||
self.client.post(f"instances/{instance_id}/backup-schedule", data)
|
||||
|
||||
def restore_backup(self, instance_id: str, backup_id: str) -> Dict:
|
||||
"""
|
||||
Restore instance from backup
|
||||
|
||||
Args:
|
||||
instance_id: Instance ID
|
||||
backup_id: Backup ID to restore
|
||||
|
||||
Returns:
|
||||
Restore status
|
||||
"""
|
||||
return self.client.post(
|
||||
f"instances/{instance_id}/restore",
|
||||
{"backup_id": backup_id}
|
||||
)
|
||||
|
||||
def restore_snapshot(self, instance_id: str, snapshot_id: str) -> Dict:
|
||||
"""
|
||||
Restore instance from snapshot
|
||||
|
||||
Args:
|
||||
instance_id: Instance ID
|
||||
snapshot_id: Snapshot ID to restore
|
||||
|
||||
Returns:
|
||||
Restore status
|
||||
"""
|
||||
return self.client.post(
|
||||
f"instances/{instance_id}/restore",
|
||||
{"snapshot_id": snapshot_id}
|
||||
)
|
||||
|
||||
# VPC management
|
||||
def list_vpcs(self, instance_id: str) -> List[Dict]:
|
||||
"""List VPCs attached to instance"""
|
||||
response = self.client.get(f"instances/{instance_id}/vpcs")
|
||||
return response.get("vpcs", [])
|
||||
|
||||
def attach_vpc(self, instance_id: str, vpc_id: str) -> None:
|
||||
"""Attach VPC to instance"""
|
||||
self.client.post(
|
||||
f"instances/{instance_id}/vpcs/attach",
|
||||
{"vpc_id": vpc_id}
|
||||
)
|
||||
|
||||
def detach_vpc(self, instance_id: str, vpc_id: str) -> None:
|
||||
"""Detach VPC from instance"""
|
||||
self.client.post(
|
||||
f"instances/{instance_id}/vpcs/detach",
|
||||
{"vpc_id": vpc_id}
|
||||
)
|
||||
|
||||
# VPC 2.0 management
|
||||
def list_vpc2(self, instance_id: str) -> List[Dict]:
|
||||
"""List VPC 2.0 networks attached to instance"""
|
||||
response = self.client.get(f"instances/{instance_id}/vpc2")
|
||||
return response.get("vpcs", [])
|
||||
|
||||
def attach_vpc2(
|
||||
self,
|
||||
instance_id: str,
|
||||
vpc_id: str,
|
||||
ip_address: str = None
|
||||
) -> None:
|
||||
"""
|
||||
Attach VPC 2.0 to instance
|
||||
|
||||
Args:
|
||||
instance_id: Instance ID
|
||||
vpc_id: VPC 2.0 ID
|
||||
ip_address: Specific IP to assign (optional)
|
||||
"""
|
||||
data = {"vpc_id": vpc_id}
|
||||
if ip_address:
|
||||
data["ip_address"] = ip_address
|
||||
|
||||
self.client.post(f"instances/{instance_id}/vpc2/attach", data)
|
||||
|
||||
def detach_vpc2(self, instance_id: str, vpc_id: str) -> None:
|
||||
"""Detach VPC 2.0 from instance"""
|
||||
self.client.post(
|
||||
f"instances/{instance_id}/vpc2/detach",
|
||||
{"vpc_id": vpc_id}
|
||||
)
|
||||
|
||||
# ISO management
|
||||
def attach_iso(self, instance_id: str, iso_id: str) -> Dict:
|
||||
"""Attach ISO to instance"""
|
||||
return self.client.post(
|
||||
f"instances/{instance_id}/iso/attach",
|
||||
{"iso_id": iso_id}
|
||||
)
|
||||
|
||||
def detach_iso(self, instance_id: str) -> Dict:
|
||||
"""Detach ISO from instance"""
|
||||
return self.client.post(f"instances/{instance_id}/iso/detach")
|
||||
|
||||
def get_iso_status(self, instance_id: str) -> Dict:
|
||||
"""Get ISO attach status"""
|
||||
return self.client.get(f"instances/{instance_id}/iso")
|
||||
Reference in New Issue
Block a user