Skip to main content
Glama

Hi-GCloud

by su-record
exec.ts7.6 kB
import { exec } from 'child_process'; import { promisify } from 'util'; import { readConfig } from './config.js'; const execAsync = promisify(exec); // Cache for discovered gcloud path let cachedGcloudPath: string | null = null; export interface ExecResult { stdout: string; stderr: string; } export interface GcloudError { type: 'NOT_INSTALLED' | 'NOT_AUTHENTICATED' | 'NO_PROJECT' | 'PERMISSION_DENIED' | 'UNKNOWN'; message: string; suggestion: string; } /** * Execute a gcloud command */ export async function executeGcloud(command: string, timeout = 30000): Promise<ExecResult> { // Use cached gcloud path if available, otherwise find it const gcloudPath = cachedGcloudPath || await findGcloudPath() || 'gcloud'; try { const result = await execAsync(`${gcloudPath} ${command}`, { timeout, maxBuffer: 10 * 1024 * 1024, // 10MB buffer }); return { stdout: result.stdout, stderr: result.stderr, }; } catch (error: any) { const errorMessage = error.message || error.stderr || ''; throw parseGcloudError(errorMessage); } } /** * Parse gcloud error messages into structured errors */ function parseGcloudError(errorMessage: string): GcloudError { const lowerError = errorMessage.toLowerCase(); if (lowerError.includes('command not found') || lowerError.includes('not recognized')) { return { type: 'NOT_INSTALLED', message: 'gcloud CLI가 설치되지 않았습니다.', suggestion: 'https://cloud.google.com/sdk/docs/install 에서 Google Cloud SDK를 설치해주세요.', }; } if (lowerError.includes('not logged in') || lowerError.includes('authentication') || lowerError.includes('credentials')) { return { type: 'NOT_AUTHENTICATED', message: 'GCP 인증이 필요합니다.', suggestion: '`gcloud auth login` 명령어를 실행해주세요.', }; } if (lowerError.includes('project') && (lowerError.includes('not set') || lowerError.includes('not specified'))) { return { type: 'NO_PROJECT', message: '프로젝트가 설정되지 않았습니다.', suggestion: 'project_id를 지정하거나 `gcloud config set project PROJECT_ID` 명령어를 실행해주세요.', }; } if (lowerError.includes('permission denied') || lowerError.includes('403') || lowerError.includes('access denied')) { return { type: 'PERMISSION_DENIED', message: '권한이 없습니다.', suggestion: '필요한 IAM 역할이 부여되었는지 확인해주세요.', }; } return { type: 'UNKNOWN', message: errorMessage, suggestion: '오류 메시지를 확인하고 다시 시도해주세요.', }; } /** * Find gcloud CLI path (with caching) */ async function findGcloudPath(): Promise<string | null> { // Return cached path if available if (cachedGcloudPath) { return cachedGcloudPath; } // Common gcloud installation paths const commonPaths = [ 'gcloud', '/usr/local/bin/gcloud', '/opt/homebrew/bin/gcloud', `${process.env.HOME}/google-cloud-sdk/bin/gcloud`, '/usr/bin/gcloud', '/snap/bin/gcloud', ]; for (const gcloudPath of commonPaths) { try { await execAsync(`${gcloudPath} --version`, { timeout: 2000 }); cachedGcloudPath = gcloudPath; return gcloudPath; } catch { // Try next path } } return null; } /** * Check if gcloud CLI is installed and authenticated */ export async function checkGcloudAuth(): Promise<{ authenticated: boolean; project?: string; account?: string; error?: GcloudError }> { const gcloudPath = await findGcloudPath(); if (!gcloudPath) { return { authenticated: false, error: { type: 'NOT_INSTALLED', message: 'gcloud CLI가 설치되지 않았습니다.', suggestion: 'https://cloud.google.com/sdk/docs/install 에서 Google Cloud SDK를 설치해주세요.', }, }; } try { // Check authentication status const authResult = await execAsync(`${gcloudPath} auth list --format="value(account)" --filter="status:ACTIVE"`, { timeout: 10000 }); const account = authResult.stdout.trim(); if (!account) { return { authenticated: false, error: { type: 'NOT_AUTHENTICATED', message: 'GCP 인증이 필요합니다.', suggestion: '`gcloud auth login` 명령어를 실행해주세요.', }, }; } // Get current project const projectResult = await execAsync(`${gcloudPath} config get-value project`, { timeout: 5000 }); const project = projectResult.stdout.trim(); return { authenticated: true, account, project: project || undefined, }; } catch (error: any) { return { authenticated: false, error: parseGcloudError(error.message || ''), }; } } /** * Get current project ID (priority: parameter > .hi-gcloud.json > gcloud config) */ export async function getProjectId(providedProjectId?: string): Promise<string> { // 1. Parameter takes highest priority if (providedProjectId) { return providedProjectId; } // 2. Check .hi-gcloud.json config const config = await readConfig(); if (config?.project_id) { return config.project_id; } // 3. Fall back to gcloud config const gcloudPath = cachedGcloudPath || await findGcloudPath() || 'gcloud'; try { const result = await execAsync(`${gcloudPath} config get-value project`, { timeout: 5000 }); const project = result.stdout.trim(); if (!project || project === '(unset)') { throw { type: 'NO_PROJECT', message: '프로젝트가 설정되지 않았습니다.', suggestion: '.hi-gcloud.json 파일을 생성하거나 `gcloud config set project PROJECT_ID` 명령어를 실행해주세요.', } as GcloudError; } return project; } catch (error: any) { if (error.type) { throw error; } throw parseGcloudError(error.message || ''); } } /** * Get region (priority: parameter > .hi-gcloud.json > gcloud config) */ export async function getRegion(providedRegion?: string): Promise<string | undefined> { // 1. Parameter takes highest priority if (providedRegion) { return providedRegion; } // 2. Check .hi-gcloud.json config const config = await readConfig(); if (config?.region) { return config.region; } // 3. Fall back to gcloud config const gcloudPath = cachedGcloudPath || await findGcloudPath() || 'gcloud'; try { const result = await execAsync(`${gcloudPath} config get-value compute/region`, { timeout: 5000 }); const region = result.stdout.trim(); return region && region !== '(unset)' ? region : undefined; } catch { return undefined; } } /** * Parse time range string to timestamp */ export function parseTimeRange(timeRange: string): string { const now = new Date(); const match = timeRange.match(/^(\d+)([hmd])$/); if (!match) { // Default to 1 hour const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000); return oneHourAgo.toISOString(); } const value = parseInt(match[1], 10); const unit = match[2]; let milliseconds: number; switch (unit) { case 'h': milliseconds = value * 60 * 60 * 1000; break; case 'd': milliseconds = value * 24 * 60 * 60 * 1000; break; case 'm': milliseconds = value * 60 * 1000; break; default: milliseconds = 60 * 60 * 1000; // 1 hour default } const timestamp = new Date(now.getTime() - milliseconds); return timestamp.toISOString(); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/su-record/hi-gcloud'

If you have feedback or need assistance with the MCP directory API, please join our Discord server