All checks were successful
Build and Push Backend / build (push) Successful in 5s
The @kubernetes/client-node API expects parameters as an object:
{ namespace: 'ns', body: pod } instead of positional params.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
206 lines
4.6 KiB
TypeScript
206 lines
4.6 KiB
TypeScript
/**
|
|
* Kubernetes API client utilities
|
|
*/
|
|
|
|
import * as k8s from '@kubernetes/client-node'
|
|
|
|
let k8sClient: k8s.CoreV1Api | null = null
|
|
let k8sConfig: k8s.KubeConfig | null = null
|
|
|
|
/**
|
|
* Initialize Kubernetes client
|
|
*/
|
|
export function initK8sClient() {
|
|
if (k8sClient) return k8sClient
|
|
|
|
k8sConfig = new k8s.KubeConfig()
|
|
|
|
// Check if running in cluster
|
|
const inCluster = process.env.K8S_IN_CLUSTER === 'true'
|
|
|
|
if (inCluster) {
|
|
k8sConfig.loadFromCluster()
|
|
} else {
|
|
// Load from kubeconfig file
|
|
const configPath = process.env.K8S_CONFIG_PATH || process.env.KUBECONFIG || '~/.kube/config'
|
|
k8sConfig.loadFromFile(configPath)
|
|
}
|
|
|
|
k8sClient = k8sConfig.makeApiClient(k8s.CoreV1Api)
|
|
return k8sClient
|
|
}
|
|
|
|
/**
|
|
* Get Kubernetes client
|
|
*/
|
|
export function getK8sClient(): k8s.CoreV1Api {
|
|
if (!k8sClient) {
|
|
return initK8sClient()
|
|
}
|
|
return k8sClient
|
|
}
|
|
|
|
/**
|
|
* Create pod spec for agent
|
|
*/
|
|
export function createAgentPodSpec(podName: string, userId: string): k8s.V1Pod {
|
|
return {
|
|
metadata: {
|
|
name: podName,
|
|
labels: {
|
|
app: 'claude-agent',
|
|
userId: userId,
|
|
'aiworker.io/agent': 'true',
|
|
},
|
|
},
|
|
spec: {
|
|
serviceAccountName: 'agent-sa',
|
|
imagePullSecrets: [
|
|
{
|
|
name: 'gitea-registry',
|
|
},
|
|
],
|
|
containers: [
|
|
{
|
|
name: 'agent',
|
|
image: 'git.fuq.tv/admin/aiworker-agent:latest',
|
|
imagePullPolicy: 'Always',
|
|
ports: [
|
|
{
|
|
containerPort: 7681,
|
|
name: 'terminal',
|
|
},
|
|
],
|
|
env: [
|
|
{
|
|
name: 'BACKEND_URL',
|
|
value: 'https://api.fuq.tv',
|
|
},
|
|
{
|
|
name: 'MCP_ENDPOINT',
|
|
value: 'https://api.fuq.tv/api/mcp',
|
|
},
|
|
{
|
|
name: 'GITEA_URL',
|
|
value: 'https://git.fuq.tv',
|
|
},
|
|
{
|
|
name: 'GITEA_TOKEN',
|
|
valueFrom: {
|
|
secretKeyRef: {
|
|
name: 'agent-secrets',
|
|
key: 'gitea-token',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'POD_NAME',
|
|
valueFrom: {
|
|
fieldRef: {
|
|
fieldPath: 'metadata.name',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'NAMESPACE',
|
|
valueFrom: {
|
|
fieldRef: {
|
|
fieldPath: 'metadata.namespace',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'USER_ID',
|
|
value: userId,
|
|
},
|
|
],
|
|
resources: {
|
|
requests: {
|
|
cpu: '500m',
|
|
memory: '1Gi',
|
|
},
|
|
limits: {
|
|
cpu: '2000m',
|
|
memory: '4Gi',
|
|
},
|
|
},
|
|
volumeMounts: [
|
|
{
|
|
name: 'workspace',
|
|
mountPath: '/workspace',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
volumes: [
|
|
{
|
|
name: 'workspace',
|
|
emptyDir: {},
|
|
},
|
|
],
|
|
},
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create agent pod in Kubernetes
|
|
*/
|
|
export async function createAgentPod(podName: string, userId: string): Promise<void> {
|
|
const client = getK8sClient()
|
|
const podSpec = createAgentPodSpec(podName, userId)
|
|
|
|
try {
|
|
await client.createNamespacedPod({
|
|
namespace: 'agents',
|
|
body: podSpec
|
|
})
|
|
console.log(`✅ Pod ${podName} created successfully`)
|
|
} catch (error: any) {
|
|
console.error(`❌ Failed to create pod ${podName}:`, error.message)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete agent pod from Kubernetes
|
|
*/
|
|
export async function deleteAgentPod(podName: string): Promise<void> {
|
|
const client = getK8sClient()
|
|
|
|
try {
|
|
await client.deleteNamespacedPod({
|
|
name: podName,
|
|
namespace: 'agents'
|
|
})
|
|
console.log(`✅ Pod ${podName} deleted successfully`)
|
|
} catch (error: any) {
|
|
// Ignore 404 errors (pod already deleted)
|
|
if (error.statusCode === 404 || error.response?.statusCode === 404) {
|
|
console.log(`⚠️ Pod ${podName} not found (already deleted)`)
|
|
return
|
|
}
|
|
console.error(`❌ Failed to delete pod ${podName}:`, error.message)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get pod status
|
|
*/
|
|
export async function getPodStatus(podName: string): Promise<string | null> {
|
|
const client = getK8sClient()
|
|
|
|
try {
|
|
const response = await client.readNamespacedPod({
|
|
name: podName,
|
|
namespace: 'agents'
|
|
})
|
|
return response.body.status?.phase || null
|
|
} catch (error: any) {
|
|
if (error.statusCode === 404 || error.response?.statusCode === 404) {
|
|
return null
|
|
}
|
|
throw error
|
|
}
|
|
}
|