Files
aiops/frontend/src/components/kubernetes/deployment-edit/container-utils.ts
T
attacker c68aef4444 feat(alert): A2 告警引擎分布式安全 — Redis 分布式锁(SetNX)
- Cache 接口新增 SetNX 方法(go-redis 原子 / redka check-then-set 单副本安全)
- AlertEngine 改造为支持分布式锁: tryAcquireLock/releaseLock/GetEngineStatus
- 多副本部署时仅 Leader 执行告警检查, 60s TTL 防死锁, fail-open 容错
- 新增 GET /alerts/engine/status 监控端点(is_leader/instance_id/lock_holder)
- 修复 GrafanaDashboard.tsx Panel.options 类型不匹配导致构建中断
- 同步未提交的前端类型安全迁移(140+ 文件)
- ROADMAP/TDD_PLAN/PROJECT_EVALUATION 三文档同步更新

验证: go build 零错误, Docker 部署 healthy, 引擎状态端点正常,
锁竞争测试通过(非 Leader 跳过检查), 5/5 核心 API 回归通过
2026-06-26 21:13:15 +08:00

214 lines
9.0 KiB
TypeScript

// container-utils.ts — K8s container spec ↔ form 转换工具
// 作者: Logan <admin@attacker.club>
import type {
ContainerForm, ProbeDef, LifecycleHandler,
} from './types';
import type { JsonData } from '@/types/common';
// K8s container spec → form conversion // Logan.Li
export function toContainerForm(c: JsonData): ContainerForm {
const probe = (p: JsonData): ProbeDef | null => {
if (!p) return null;
let type: ProbeDef['type'] = 'none';
if (p.httpGet) type = 'httpGet';
else if (p.tcpSocket) type = 'tcpSocket';
else if (p.exec) type = 'exec';
return {
type,
httpPath: p.httpGet?.path || '',
httpPort: p.httpGet?.port ?? '',
httpHost: p.httpGet?.host || '',
tcpPort: p.tcpSocket?.port ?? '',
execCommand: Array.isArray(p.exec?.command) ? p.exec.command.join('\n') : (p.exec?.command || ''),
initialDelaySeconds: p.initialDelaySeconds,
periodSeconds: p.periodSeconds,
timeoutSeconds: p.timeoutSeconds,
successThreshold: p.successThreshold,
failureThreshold: p.failureThreshold,
};
};
const lh = (h: JsonData): LifecycleHandler => {
if (!h) return { type: 'none' };
let type: LifecycleHandler['type'] = 'none';
if (h.exec) type = 'exec';
else if (h.httpGet) type = 'httpGet';
else if (h.tcpSocket) type = 'tcpSocket';
return {
type,
execCommand: Array.isArray(h.exec?.command) ? h.exec.command.join('\n') : '',
httpPath: h.httpGet?.path || '',
httpPort: h.httpGet?.port ?? '',
tcpPort: h.tcpSocket?.port ?? '',
};
};
const sc = c.securityContext;
return {
name: c.name || '',
image: c.image || '',
imagePullPolicy: c.imagePullPolicy || '',
command: Array.isArray(c.command) ? c.command : [],
args: Array.isArray(c.args) ? c.args : [],
workingDir: c.workingDir || '',
env: (Array.isArray(c.env) ? c.env : []).map((e: JsonData) => ({ name: e.name || '', value: e.value ?? '' })),
ports: (Array.isArray(c.ports) ? c.ports : []).map((p: JsonData) => ({ name: p.name || '', containerPort: p.containerPort ?? '', protocol: p.protocol || 'TCP' })),
resources: {
requests: c.resources?.requests || {},
limits: c.resources?.limits || {},
},
livenessProbe: probe(c.livenessProbe),
readinessProbe: probe(c.readinessProbe),
startupProbe: probe(c.startupProbe),
lifecycle: c.lifecycle ? {
postStart: c.lifecycle.postStart ? lh(c.lifecycle.postStart) : undefined,
preStop: c.lifecycle.preStop ? lh(c.lifecycle.preStop) : undefined,
} : null,
securityContext: sc ? {
runAsUser: sc.runAsUser,
runAsGroup: sc.runAsGroup,
runAsNonRoot: sc.runAsNonRoot,
privileged: sc.privileged,
readOnlyRootFilesystem: sc.readOnlyRootFilesystem,
allowPrivilegeEscalation: sc.allowPrivilegeEscalation,
capabilitiesAdd: sc.capabilities?.add || [],
capabilitiesDrop: sc.capabilities?.drop || [],
} : null,
volumeMounts: (Array.isArray(c.volumeMounts) ? c.volumeMounts : []).map((m: JsonData) => ({
name: m.name || '', mountPath: m.mountPath || '', subPath: m.subPath || '', readOnly: m.readOnly || false,
})),
};
}
// form → K8s container spec // Logan.Li
export function emptyContainer(name = ''): ContainerForm {
return {
name, image: '', imagePullPolicy: 'IfNotPresent',
command: [], args: [], workingDir: '',
env: [], ports: [], resources: {},
livenessProbe: null, readinessProbe: null, startupProbe: null,
lifecycle: null, securityContext: null, volumeMounts: [],
};
}
export function probeToK8s(p: ProbeDef | null | undefined): JsonData {
if (!p || p.type === 'none' || !p.type) return undefined;
const out: JsonData = {};
if (p.type === 'httpGet') {
out.httpGet = {};
if (p.httpPath) out.httpGet.path = p.httpPath;
if (p.httpPort !== '' && p.httpPort != null) out.httpGet.port = typeof p.httpPort === 'number' ? p.httpPort : Number(p.httpPort);
if (p.httpHost) out.httpGet.host = p.httpHost;
} else if (p.type === 'tcpSocket') {
out.tcpSocket = {};
if (p.tcpPort !== '' && p.tcpPort != null) out.tcpSocket.port = typeof p.tcpPort === 'number' ? p.tcpPort : Number(p.tcpPort);
} else if (p.type === 'exec') {
const cmds = (p.execCommand || '').split('\n').map(s => s.trim()).filter(Boolean);
if (cmds.length) out.exec = { command: cmds };
}
if (p.initialDelaySeconds != null) out.initialDelaySeconds = p.initialDelaySeconds;
if (p.periodSeconds != null) out.periodSeconds = p.periodSeconds;
if (p.timeoutSeconds != null) out.timeoutSeconds = p.timeoutSeconds;
if (p.successThreshold != null) out.successThreshold = p.successThreshold;
if (p.failureThreshold != null) out.failureThreshold = p.failureThreshold;
return Object.keys(out).length > 0 ? out : undefined;
}
export function handlerToK8s(h: LifecycleHandler | undefined): JsonData {
if (!h || h.type === 'none' || !h.type) return undefined;
if (h.type === 'exec') {
const cmds = (h.execCommand || '').split('\n').map(s => s.trim()).filter(Boolean);
return cmds.length ? { exec: { command: cmds } } : undefined;
}
if (h.type === 'httpGet') {
const o: JsonData = { httpGet: {} };
if (h.httpPath) o.httpGet.path = h.httpPath;
if (h.httpPort !== '' && h.httpPort != null) o.httpGet.port = Number(h.httpPort);
return o;
}
if (h.type === 'tcpSocket') {
const o: JsonData = { tcpSocket: {} };
if (h.tcpPort !== '' && h.tcpPort != null) o.tcpSocket.port = Number(h.tcpPort);
return o;
}
return undefined;
}
export function containerToK8s(c: ContainerForm): JsonData {
const out: JsonData = { name: c.name };
if (c.image) out.image = c.image;
if (c.imagePullPolicy) out.imagePullPolicy = c.imagePullPolicy;
if (c.workingDir) out.workingDir = c.workingDir;
if (c.command.length > 0) out.command = c.command;
if (c.args.length > 0) out.args = c.args;
if (c.env.length > 0) {
out.env = c.env.filter(e => e.name.trim()).map(e => {
const o: JsonData = { name: e.name };
if (e.value != null) o.value = e.value;
return o;
});
}
if (c.ports.length > 0) {
out.ports = c.ports.filter(p => p.containerPort !== '').map(p => {
const o: JsonData = { containerPort: typeof p.containerPort === 'number' ? p.containerPort : Number(p.containerPort) };
if (p.name) o.name = p.name;
if (p.protocol) o.protocol = p.protocol;
return o;
});
}
const req = c.resources.requests;
const lim = c.resources.limits;
const hasReq = req && Object.values(req).some(v => v && v.trim());
const hasLim = lim && Object.values(lim).some(v => v && v.trim());
if (hasReq || hasLim) {
out.resources = {};
if (hasReq) {
out.resources.requests = {};
if (req!.cpu?.trim()) out.resources.requests.cpu = req!.cpu;
if (req!.memory?.trim()) out.resources.requests.memory = req!.memory;
}
if (hasLim) {
out.resources.limits = {};
if (lim!.cpu?.trim()) out.resources.limits.cpu = lim!.cpu;
if (lim!.memory?.trim()) out.resources.limits.memory = lim!.memory;
}
}
const lp = probeToK8s(c.livenessProbe); if (lp) out.livenessProbe = lp;
const rp = probeToK8s(c.readinessProbe); if (rp) out.readinessProbe = rp;
const sp = probeToK8s(c.startupProbe); if (sp) out.startupProbe = sp;
if (c.lifecycle) {
const lc: JsonData = {};
const ps = handlerToK8s(c.lifecycle.postStart); if (ps) lc.postStart = ps;
const pst = handlerToK8s(c.lifecycle.preStop); if (pst) lc.preStop = pst;
if (Object.keys(lc).length > 0) out.lifecycle = lc;
}
if (c.securityContext) {
const s = c.securityContext;
const sc: JsonData = {};
if (s.runAsUser !== '' && s.runAsUser != null) sc.runAsUser = Number(s.runAsUser);
if (s.runAsGroup !== '' && s.runAsGroup != null) sc.runAsGroup = Number(s.runAsGroup);
if (s.runAsNonRoot != null) sc.runAsNonRoot = s.runAsNonRoot;
if (s.privileged != null) sc.privileged = s.privileged;
if (s.readOnlyRootFilesystem != null) sc.readOnlyRootFilesystem = s.readOnlyRootFilesystem;
if (s.allowPrivilegeEscalation != null) sc.allowPrivilegeEscalation = s.allowPrivilegeEscalation;
const capAdd = (s.capabilitiesAdd || []).filter(Boolean);
const capDrop = (s.capabilitiesDrop || []).filter(Boolean);
if (capAdd.length || capDrop.length) {
sc.capabilities = {};
if (capAdd.length) sc.capabilities.add = capAdd;
if (capDrop.length) sc.capabilities.drop = capDrop;
}
if (Object.keys(sc).length > 0) out.securityContext = sc;
}
if (c.volumeMounts.length > 0) {
out.volumeMounts = c.volumeMounts.filter(m => m.name.trim() && m.mountPath.trim()).map(m => {
const o: JsonData = { name: m.name, mountPath: m.mountPath };
if (m.subPath) o.subPath = m.subPath;
if (m.readOnly) o.readOnly = true;
return o;
});
}
return out;
}
// String array helper (command/args editing) // Logan.Li
export function arrToText(arr: string[]): string { return arr.join('\n'); }
export function textToArr(text: string): string[] { return text.split('\n').map(s => s.trim()).filter(Boolean); }