diff --git a/src/k8s-client.ts b/src/k8s-client.ts index 2ef7f35..14d023d 100644 --- a/src/k8s-client.ts +++ b/src/k8s-client.ts @@ -1,5 +1,12 @@ +/** + * K8S 客户端 - 通过 HTTP API 代理调用 K8S + * @kubernetes/client-node 仅用于类型定义 + */ + +import axios from 'axios'; import * as k8s from '@kubernetes/client-node'; +// 导出类型 export type V1Deployment = k8s.V1Deployment; export type V1DeploymentList = k8s.V1DeploymentList; export type V1Ingress = k8s.V1Ingress; @@ -8,131 +15,303 @@ export type V1Pod = k8s.V1Pod; export type V1Service = k8s.V1Service; export type V1ServicePort = k8s.V1ServicePort; +// 构建 URL 路径 +const buildUrl = (baseUrl: string, path: string, params?: Record) => { + let url = baseUrl.replace(/\/+$/, '') + '/api/v1/kubernetes/' + path.replace(/^\/+/, ''); + if (params) { + for (const [key, value] of Object.entries(params)) { + url = url.replace(`:${key}`, value); + } + } + return url; +}; + +// 构建查询字符串 +const buildQuery = (query?: Record) => { + if (!query) return ''; + const params = new URLSearchParams(); + for (const [key, value] of Object.entries(query)) { + if (value !== undefined) { + params.set(key, String(value)); + } + } + return params.toString() ? '?' + params.toString() : ''; +}; + +// HTTP 请求封装 +const request = async ( + baseUrl: string, + token: string, + method: string, + path: string, + args?: { + param?: Record; + query?: Record; + json?: unknown; + } +) => { + let url = buildUrl(baseUrl, path, args?.param); + url += buildQuery(args?.query); + + const response = await axios.request({ + url, + method, + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + data: args?.json ? JSON.stringify(args.json) : undefined, + validateStatus: () => true, // 不抛出 HTTP 错误 + }); + + return response; +}; + +/** + * K8S 客户端类 + */ export class K8sClient { - private kubeConfig: k8s.KubeConfig; + private baseUrl: string; + private token: string; constructor(baseUrl: string, token: string) { - this.kubeConfig = new k8s.KubeConfig(); - this.kubeConfig.loadFromOptions({ - clusters: [{ name: 'cluster', server: baseUrl, skipTLSVerify: true }], - users: [{ name: 'user', authProvider: { name: 'token', config: { token } } }], - contexts: [{ name: 'ctx', cluster: 'cluster', user: 'user' }], - currentContext: 'ctx', - }); + this.baseUrl = baseUrl; + this.token = token; } - private getCoreV1Api() { - return this.kubeConfig.makeApiClient(k8s.CoreV1Api); - } + // ========== Pod 操作 ========== - private getAppsV1Api() { - return this.kubeConfig.makeApiClient(k8s.AppsV1Api); - } - - private getNetworkingV1Api() { - return this.kubeConfig.makeApiClient(k8s.NetworkingV1Api); - } - - async getPods(args: { param: { namespace?: string }; query?: { labelSelector?: string; fieldSelector?: string; limit?: number; continue?: string } }): Promise<{ pods: k8s.V1Pod[] }> { + async getPods(args: { + param: { namespace?: string }; + query?: { labelSelector?: string; fieldSelector?: string; limit?: number; continue?: string }; + }): Promise<{ pods: V1Pod[] }> { try { - const ns = args.param.namespace || 'default'; - const res = await this.getCoreV1Api().listNamespacedPod({ namespace: ns, labelSelector: args.query?.labelSelector, fieldSelector: args.query?.fieldSelector, limit: args.query?.limit, _continue: args.query?.continue }); - return { pods: res.items || [] }; - } catch { return { pods: [] }; } + const namespace = args.param.namespace || 'default'; + const res = await request(this.baseUrl, this.token, 'GET', 'pods/:namespace', { + param: { namespace }, + query: args.query, + }); + if (res.status !== 200) { + throw new Error(res.data?.message || '获取 Pods 失败'); + } + return { pods: res.data?.pods || [] }; + } catch (error) { + console.error('获取 Pods 失败:', error); + return { pods: [] }; + } } - async getPod(args: { param: { namespace: string; podName: string } }): Promise { - return this.getCoreV1Api().readNamespacedPod({ name: args.param.podName, namespace: args.param.namespace }); + async getPod(args: { param: { namespace: string; podName: string } }): Promise { + const res = await request(this.baseUrl, this.token, 'GET', 'pods/:namespace/:podName', { + param: { namespace: args.param.namespace, podName: args.param.podName }, + }); + if (res.status !== 200) { + throw new Error(res.data?.message || '获取 Pod 失败'); + } + return res.data; } async createPod(args: { json: { namespace: string; pod: V1Pod } }): Promise<{ success: boolean; message: string; pod?: V1Pod }> { try { - const res = await this.getCoreV1Api().createNamespacedPod({ namespace: args.json.namespace, body: args.json.pod }); - return { success: true, message: 'Pod 创建成功', pod: res }; - } catch (e) { return { success: false, message: `创建 Pod 失败: ${e}` }; } + const res = await request(this.baseUrl, this.token, 'POST', 'pods', { json: args.json }); + if (res.status !== 200) { + return { success: false, message: res.data?.message || '创建 Pod 失败' }; + } + return { success: true, message: 'Pod 创建成功', pod: res.data }; + } catch (error) { + return { success: false, message: `创建 Pod 失败: ${error}` }; + } } async deletePod(args: { param: { namespace: string; podName: string } }): Promise<{ success: boolean; message: string }> { try { - await this.getCoreV1Api().deleteNamespacedPod({ name: args.param.podName, namespace: args.param.namespace }); + const res = await request(this.baseUrl, this.token, 'DELETE', 'pods/:namespace/:podName', { + param: { namespace: args.param.namespace, podName: args.param.podName }, + }); + if (res.status !== 200) { + return { success: false, message: res.data?.message || '删除 Pod 失败' }; + } return { success: true, message: 'Pod 删除成功' }; - } catch (e) { return { success: false, message: `删除 Pod 失败: ${e}` }; } + } catch (error) { + return { success: false, message: `删除 Pod 失败: ${error}` }; + } } - async getDeployments(args: { param: { namespace: string }; query?: { limit?: number; continue?: string } }): Promise { - try { - const res = await this.getAppsV1Api().listNamespacedDeployment({ namespace: args.param.namespace, limit: args.query?.limit, _continue: args.query?.continue }); - return { items: res.items || [], metadata: res.metadata || {} }; - } catch { return { items: [], metadata: {} }; } - } + // ========== Deployment 操作 ========== async getDeployment(args: { param: { namespace: string; name: string } }): Promise { - return this.getAppsV1Api().readNamespacedDeployment({ name: args.param.name, namespace: args.param.namespace }); + const res = await request(this.baseUrl, this.token, 'GET', 'deployments/:namespace/:name', { + param: { namespace: args.param.namespace, name: args.param.name }, + }); + if (res.status !== 200) { + const error = new Error(res.data?.message || `Deployment ${args.param.name} not found`); + (error as any).code = res.status; + throw error; + } + return res.data; } - async createDeployment(args: { json: { namespace: string; deployment: V1Deployment } }): Promise<{ success: boolean; message: string; deployment?: V1Deployment }> { + async getDeployments(args: { + param: { namespace: string }; + query?: { limit?: number; continue?: string }; + }): Promise { try { - const res = await this.getAppsV1Api().createNamespacedDeployment({ namespace: args.json.namespace, body: args.json.deployment }); - return { success: true, message: 'Deployment 创建成功', deployment: res }; - } catch (e) { return { success: false, message: `创建 Deployment 失败: ${e}` }; } + const res = await request(this.baseUrl, this.token, 'GET', 'deployments/:namespace', { + param: { namespace: args.param.namespace }, + query: args.query, + }); + if (res.status !== 200) { + return { items: [], metadata: {} }; + } + return { items: res.data?.items || [], metadata: res.data?.metadata || {} }; + } catch (error) { + console.error('获取 Deployments 失败:', error); + return { items: [], metadata: {} }; + } } - async deleteDeployment(args: { param: { namespace: string; name: string } }): Promise<{ success: boolean; message: string }> { + async createDeployment(args: { + json: { namespace: string; deployment: V1Deployment }; + }): Promise<{ success: boolean; message: string; deployment?: V1Deployment }> { try { - await this.getAppsV1Api().deleteNamespacedDeployment({ name: args.param.name, namespace: args.param.namespace }); + const res = await request(this.baseUrl, this.token, 'POST', 'deployments', { json: args.json }); + if (res.status !== 200) { + return { success: false, message: res.data?.message || '创建 Deployment 失败' }; + } + return { success: true, message: 'Deployment 创建成功', deployment: res.data }; + } catch (error) { + return { success: false, message: `创建 Deployment 失败: ${error}` }; + } + } + + async deleteDeployment(args: { + param: { namespace: string; name: string }; + }): Promise<{ success: boolean; message: string }> { + try { + const res = await request(this.baseUrl, this.token, 'DELETE', 'deployments/:namespace/:name', { + param: { namespace: args.param.namespace, name: args.param.name }, + }); + if (res.status !== 200) { + return { success: false, message: res.data?.message || '删除 Deployment 失败' }; + } return { success: true, message: 'Deployment 删除成功' }; - } catch (e) { return { success: false, message: `删除 Deployment 失败: ${e}` }; } + } catch (error) { + return { success: false, message: `删除 Deployment 失败: ${error}` }; + } } - async restartDeployment(args: { param: { namespace: string; name: string } }): Promise<{ success: boolean; message: string }> { + async restartDeployment(args: { + param: { namespace: string; name: string }; + }): Promise<{ success: boolean; message: string }> { try { - const pods = await this.getPods({ param: { namespace: args.param.namespace }, query: { labelSelector: `app=${args.param.name}` } }); + const pods = await this.getPods({ + param: { namespace: args.param.namespace }, + query: { labelSelector: `app=${args.param.name}` }, + }); for (const pod of pods.pods) { - if (pod.metadata?.name) await this.deletePod({ param: { namespace: args.param.namespace, podName: pod.metadata.name } }); + if (pod.metadata?.name) { + await this.deletePod({ param: { namespace: args.param.namespace, podName: pod.metadata.name } }); + } } return { success: true, message: 'Deployment 重启成功' }; - } catch (e) { return { success: false, message: `重启 Deployment 失败: ${e}` }; } + } catch (error) { + return { success: false, message: `重启 Deployment 失败: ${error}` }; + } } - async createService(args: { json: { namespace: string; service: V1Service } }): Promise<{ success: boolean; message: string; service?: V1Service }> { + // ========== Service 操作 ========== + + async createService(args: { + json: { namespace: string; service: V1Service }; + }): Promise<{ success: boolean; message: string; service?: V1Service }> { try { - const res = await this.getCoreV1Api().createNamespacedService({ namespace: args.json.namespace, body: args.json.service }); - return { success: true, message: 'Service 创建成功', service: res }; - } catch (e) { return { success: false, message: `创建 Service 失败: ${e}` }; } + const res = await request(this.baseUrl, this.token, 'POST', 'services', { json: args.json }); + if (res.status !== 200) { + return { success: false, message: res.data?.message || '创建 Service 失败' }; + } + return { success: true, message: 'Service 创建成功', service: res.data }; + } catch (error) { + return { success: false, message: `创建 Service 失败: ${error}` }; + } } - async deleteService(args: { param: { namespace: string; serviceName: string } }): Promise<{ success: boolean; message: string }> { + async deleteService(args: { + param: { namespace: string; serviceName: string }; + }): Promise<{ success: boolean; message: string }> { try { - await this.getCoreV1Api().deleteNamespacedService({ name: args.param.serviceName, namespace: args.param.namespace }); + const res = await request(this.baseUrl, this.token, 'DELETE', 'services/:namespace/:serviceName', { + param: { namespace: args.param.namespace, serviceName: args.param.serviceName }, + }); + if (res.status !== 200) { + return { success: false, message: res.data?.message || '删除 Service 失败' }; + } return { success: true, message: 'Service 删除成功' }; - } catch (e) { return { success: false, message: `删除 Service 失败: ${e}` }; } + } catch (error) { + return { success: false, message: `删除 Service 失败: ${error}` }; + } } - async getService(args: { param: { namespace: string; serviceName: string } }): Promise<{ service?: V1Service }> { + async getService(args: { + param: { namespace: string; serviceName: string }; + }): Promise<{ service?: V1Service }> { try { - const res = await this.getCoreV1Api().readNamespacedService({ name: args.param.serviceName, namespace: args.param.namespace }); - return { service: res }; - } catch { return {}; } + const res = await request(this.baseUrl, this.token, 'GET', 'services/:namespace/:serviceName', { + param: { namespace: args.param.namespace, serviceName: args.param.serviceName }, + }); + if (res.status !== 200) { + return {}; + } + return { service: res.data }; + } catch { + return {}; + } } - async createIngress(args: { json: { namespace: string; ingress: V1Ingress } }): Promise<{ success: boolean; message: string; ingress?: V1Ingress }> { + // ========== Ingress 操作 ========== + + async createIngress(args: { + json: { namespace: string; ingress: V1Ingress }; + }): Promise<{ success: boolean; message: string; ingress?: V1Ingress }> { try { - const res = await this.getNetworkingV1Api().createNamespacedIngress({ namespace: args.json.namespace, body: args.json.ingress }); - return { success: true, message: 'Ingress 创建成功', ingress: res }; - } catch (e) { return { success: false, message: `创建 Ingress 失败: ${e}` }; } + const res = await request(this.baseUrl, this.token, 'POST', 'ingresses', { json: args.json }); + if (res.status !== 200) { + return { success: false, message: res.data?.message || '创建 Ingress 失败' }; + } + return { success: true, message: 'Ingress 创建成功', ingress: res.data }; + } catch (error) { + return { success: false, message: `创建 Ingress 失败: ${error}` }; + } } - async deleteIngress(args: { param: { namespace: string; ingressName: string } }): Promise<{ success: boolean; message: string }> { + async deleteIngress(args: { + param: { namespace: string; ingressName: string }; + }): Promise<{ success: boolean; message: string }> { try { - await this.getNetworkingV1Api().deleteNamespacedIngress({ name: args.param.ingressName, namespace: args.param.namespace }); + const res = await request(this.baseUrl, this.token, 'DELETE', 'ingresses/:namespace/:ingressName', { + param: { namespace: args.param.namespace, ingressName: args.param.ingressName }, + }); + if (res.status !== 200) { + return { success: false, message: res.data?.message || '删除 Ingress 失败' }; + } return { success: true, message: 'Ingress 删除成功' }; - } catch (e) { return { success: false, message: `删除 Ingress 失败: ${e}` }; } + } catch (error) { + return { success: false, message: `删除 Ingress 失败: ${error}` }; + } } - async getIngress(args: { param: { namespace: string; ingressName: string } }): Promise<{ ingress?: V1Ingress }> { + async getIngress(args: { + param: { namespace: string; ingressName: string }; + }): Promise<{ ingress?: V1Ingress }> { try { - const res = await this.getNetworkingV1Api().readNamespacedIngress({ name: args.param.ingressName, namespace: args.param.namespace }); - return { ingress: res }; - } catch { return {}; } + const res = await request(this.baseUrl, this.token, 'GET', 'ingresses/:namespace/:ingressName', { + param: { namespace: args.param.namespace, ingressName: args.param.ingressName }, + }); + if (res.status !== 200) { + return {}; + } + return { ingress: res.data }; + } catch { + return {}; + } } }