- Add server recommendation integration (SERVER_RECOMMEND worker) - Implement KV-based session management for multi-step ordering - Add Linode/Vultr API clients for server provisioning - Add server-tool for Function Calling support refactor: major code reorganization (Phase 1-3) - Remove 443 lines of deprecated callback handlers - Extract handlers to separate files (message-handler, callback-handler) - Extract cloud-spec-service, server-recommend-service - Centralize constants (OS_IMAGES, REGION_FLAGS, NUM_EMOJIS) - webhook.ts reduced from 1,951 to 30 lines Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
241 lines
5.8 KiB
TypeScript
241 lines
5.8 KiB
TypeScript
/**
|
|
* 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<VultrInstance> {
|
|
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<VultrInstance> {
|
|
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<VultrRegion[]> {
|
|
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',
|
|
}
|
|
);
|
|
}
|