/** * Vultr API Client * * REST API 클라이언트 for Vultr cloud provider * - Instance management (create, get) * - Region listing * - Automatic retry with exponential backoff */ import type { Env, VultrInstance, VultrCreateRequest } from '../types'; import { createLogger } from '../utils/logger'; import { retryWithBackoff } from '../utils/retry'; const logger = createLogger('vultr-api'); /** * Vultr API Base URLs */ const DEFAULT_API_BASE = 'https://api.vultr.com/v2'; /** * Vultr Region */ export interface VultrRegion { id: string; city: string; country: string; continent: string; options: string[]; } /** * Vultr API Error */ export class VultrAPIError extends Error { constructor( message: string, public readonly statusCode: number, public readonly response?: any ) { super(message); this.name = 'VultrAPIError'; } } /** * Create a Vultr instance * * @param params - Instance creation parameters * @param env - Environment variables (API key, base URL) * @returns Created instance information * @throws VultrAPIError if API call fails */ export async function createInstance( params: VultrCreateRequest, env: Env ): Promise { const apiKey = env.VULTR_API_KEY; if (!apiKey) { throw new Error('VULTR_API_KEY is not configured'); } const apiBase = env.VULTR_API_BASE || DEFAULT_API_BASE; const url = `${apiBase}/instances`; logger.info('Creating Vultr instance', { plan: params.plan, region: params.region, label: params.label, }); return retryWithBackoff( async () => { const response = await fetch(url, { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(params), }); if (!response.ok) { const errorBody = await response.json().catch(() => ({})); logger.error('Vultr API create instance failed', undefined, { status: response.status, statusText: response.statusText, error: errorBody, }); throw new VultrAPIError( `Vultr API error: ${response.status} ${response.statusText}`, response.status, errorBody ); } const responseData = await response.json() as { instance: VultrInstance }; const instance = responseData.instance; logger.info('Vultr instance created successfully', { id: instance.id, label: instance.label, main_ip: instance.main_ip, }); return instance; }, { maxRetries: 3, initialDelayMs: 1000, serviceName: 'vultr-api', } ); } /** * Get a Vultr instance by ID * * @param instanceId - Vultr instance ID * @param env - Environment variables (API key, base URL) * @returns Instance information * @throws VultrAPIError if API call fails */ export async function getInstance( instanceId: string, env: Env ): Promise { const apiKey = env.VULTR_API_KEY; if (!apiKey) { throw new Error('VULTR_API_KEY is not configured'); } const apiBase = env.VULTR_API_BASE || DEFAULT_API_BASE; const url = `${apiBase}/instances/${instanceId}`; logger.info('Getting Vultr instance', { instanceId }); return retryWithBackoff( async () => { const response = await fetch(url, { method: 'GET', headers: { 'Authorization': `Bearer ${apiKey}`, }, }); if (!response.ok) { const errorBody = await response.json().catch(() => ({})); logger.error('Vultr API get instance failed', undefined, { status: response.status, statusText: response.statusText, instanceId, error: errorBody, }); throw new VultrAPIError( `Vultr API error: ${response.status} ${response.statusText}`, response.status, errorBody ); } const responseData = await response.json() as { instance: VultrInstance }; const instance = responseData.instance; logger.info('Vultr instance retrieved successfully', { id: instance.id, label: instance.label, status: instance.status, }); return instance; }, { maxRetries: 3, initialDelayMs: 1000, serviceName: 'vultr-api', } ); } /** * Get list of available Vultr regions * * @param env - Environment variables (API key, base URL) * @returns Array of available regions * @throws VultrAPIError if API call fails */ export async function getRegions(env: Env): Promise { const apiKey = env.VULTR_API_KEY; if (!apiKey) { throw new Error('VULTR_API_KEY is not configured'); } const apiBase = env.VULTR_API_BASE || DEFAULT_API_BASE; const url = `${apiBase}/regions`; logger.info('Getting Vultr regions'); return retryWithBackoff( async () => { const response = await fetch(url, { method: 'GET', headers: { 'Authorization': `Bearer ${apiKey}`, }, }); if (!response.ok) { const errorBody = await response.json().catch(() => ({})); logger.error('Vultr API get regions failed', undefined, { status: response.status, statusText: response.statusText, error: errorBody, }); throw new VultrAPIError( `Vultr API error: ${response.status} ${response.statusText}`, response.status, errorBody ); } const responseData = await response.json() as { regions: VultrRegion[] }; const regions = responseData.regions; logger.info('Vultr regions retrieved successfully', { count: regions.length, }); return regions; }, { maxRetries: 3, initialDelayMs: 1000, serviceName: 'vultr-api', } ); }