fix(k8s): 可视化编辑器 YAML 解析失败(managedFields 清理)
- 先解析原始 YAML 再对象级剥离,避免 cleanYAML 破坏结构 - managedFields 从 METADATA_LINE_KEYS 移除,改整块删除含数组项 - YamlViewer 同步使用 parseEditableManifest
This commit is contained in:
1 parent
21d40b186a
commit
5fad2db0c6
101 files changed
+195
-146
No files matched your search
@@ -13,7 +13,7 @@ import { yaml } from '@codemirror/lang-yaml';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { cleanYAML } from './k8s-yaml-utils';
|
||||
import { createEmptyManifest } from './resource-create-templates';
|
||||
import { parseManifest, dumpManifest, prepareManifestForSave } from './k8s-manifest-utils';
|
||||
import { parseManifest, parseEditableManifest, dumpManifest, prepareManifestForSave } from './k8s-manifest-utils';
|
||||
|
||||
interface ResourceVisualEditorProps {
|
||||
clusterCode: string;
|
||||
@@ -46,10 +46,10 @@ export function ResourceVisualEditor({ clusterCode, namespace, resource, name, o
|
||||
return;
|
||||
}
|
||||
const text = await kubernetesApi.getResourceYaml(clusterCode, namespace, resource, name);
|
||||
const cleaned = cleanYAML(text);
|
||||
setRawYaml(text);
|
||||
setEditYaml(cleaned);
|
||||
const parsed = await parseManifest(cleaned);
|
||||
// 先解析原始 YAML,再用对象级剥离;cleanYAML 会破坏 managedFields 导致解析失败 // Logan.Li
|
||||
const { manifest: parsed, yaml: editableYaml } = await parseEditableManifest(text);
|
||||
setEditYaml(editableYaml);
|
||||
setManifest(parsed);
|
||||
if (!parsed) {
|
||||
setMode('yaml');
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { kubernetesApi } from '@/api';
|
||||
import { cleanYAML } from './k8s-yaml-utils';
|
||||
import { parseEditableManifest } from './k8s-manifest-utils';
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import { yaml } from '@codemirror/lang-yaml';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
@@ -40,10 +41,16 @@ export function YamlViewer({ clusterCode, namespace, resource, name, onClose, on
|
||||
// autoEdit 时隐式启用清理(编辑入口必须有清理,否则 status/managedFields 会被 K8s 拒绝)
|
||||
const effectiveClean = cleanBeforeEdit || autoEdit;
|
||||
|
||||
const toEditableYaml = async (text: string) => {
|
||||
if (!effectiveClean) return text;
|
||||
const { yaml } = await parseEditableManifest(text);
|
||||
return yaml;
|
||||
};
|
||||
|
||||
const loadYaml = async () => {
|
||||
if (initialYaml) {
|
||||
setRawYaml(initialYaml);
|
||||
setEditYaml(effectiveClean ? cleanYAML(initialYaml) : initialYaml);
|
||||
setEditYaml(await toEditableYaml(initialYaml));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
@@ -51,7 +58,7 @@ export function YamlViewer({ clusterCode, namespace, resource, name, onClose, on
|
||||
try {
|
||||
const text = await kubernetesApi.getResourceYaml(clusterCode, namespace, resource, name);
|
||||
setRawYaml(text);
|
||||
setEditYaml(effectiveClean ? cleanYAML(text) : text);
|
||||
setEditYaml(await toEditableYaml(text));
|
||||
} catch (e) {
|
||||
toast({ title: '获取YAML失败', description: (e as Record<string, any>).message, variant: 'destructive' });
|
||||
const err = `# Error: ${e.message}`;
|
||||
@@ -60,9 +67,8 @@ export function YamlViewer({ clusterCode, namespace, resource, name, onClose, on
|
||||
} finally { setLoading(false); }
|
||||
};
|
||||
|
||||
const enterEdit = () => {
|
||||
const cleaned = effectiveClean ? cleanYAML(rawYaml) : rawYaml;
|
||||
setEditYaml(cleaned);
|
||||
const enterEdit = async () => {
|
||||
setEditYaml(await toEditableYaml(rawYaml));
|
||||
setEditing(true);
|
||||
if (effectiveClean) {
|
||||
toast({ title: '已清理只读字段', description: '已剥离 status / managedFields / uid 等' });
|
||||
|
||||
@@ -1,6 +1,37 @@
|
||||
// K8s manifest 解析/序列化共用工具 // Logan.Li
|
||||
import { cleanYAML } from './k8s-yaml-utils';
|
||||
|
||||
const STRIP_ANNOTATIONS = new Set([
|
||||
'kubectl.kubernetes.io/last-applied-configuration',
|
||||
'deployment.kubernetes.io/revision',
|
||||
'kubectl.kubernetes.io/restartedAt',
|
||||
]);
|
||||
|
||||
/** 对象级剥离运行时字段(比 cleanYAML 文本处理更可靠)// Logan.Li */
|
||||
export function stripRuntimeFields(manifest: Record<string, any>): Record<string, any> {
|
||||
const cloned = JSON.parse(JSON.stringify(manifest));
|
||||
delete cloned.status;
|
||||
if (cloned.metadata) {
|
||||
delete cloned.metadata.managedFields;
|
||||
delete cloned.metadata.resourceVersion;
|
||||
delete cloned.metadata.uid;
|
||||
delete cloned.metadata.creationTimestamp;
|
||||
delete cloned.metadata.generation;
|
||||
delete cloned.metadata.selfLink;
|
||||
if (cloned.metadata.annotations) {
|
||||
for (const k of Object.keys(cloned.metadata.annotations)) {
|
||||
if (STRIP_ANNOTATIONS.has(k)) delete cloned.metadata.annotations[k];
|
||||
}
|
||||
if (Object.keys(cloned.metadata.annotations).length === 0) delete cloned.metadata.annotations;
|
||||
}
|
||||
if (cloned.metadata.labels) {
|
||||
delete cloned.metadata.labels['pod-template-hash'];
|
||||
if (Object.keys(cloned.metadata.labels).length === 0) delete cloned.metadata.labels;
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
|
||||
export async function parseManifest(text: string): Promise<Record<string, any> | null> {
|
||||
if (!text?.trim()) return null;
|
||||
try { return JSON.parse(text); } catch {}
|
||||
@@ -16,17 +47,19 @@ export async function dumpManifest(manifest: Record<string, any>): Promise<strin
|
||||
return jsyaml.dump(manifest, { lineWidth: 120, noRefs: true });
|
||||
}
|
||||
|
||||
/** 原始 YAML → 可编辑 manifest + 精简 YAML(可视化/编辑共用)// Logan.Li */
|
||||
export async function parseEditableManifest(text: string): Promise<{
|
||||
manifest: Record<string, any> | null;
|
||||
yaml: string;
|
||||
}> {
|
||||
const parsed = await parseManifest(text);
|
||||
if (!parsed) return { manifest: null, yaml: text };
|
||||
const stripped = stripRuntimeFields(parsed);
|
||||
return { manifest: stripped, yaml: await dumpManifest(stripped) };
|
||||
}
|
||||
|
||||
/** 保存前剥离 status 等噪音字段 // Logan.Li */
|
||||
export async function prepareManifestForSave(manifest: Record<string, any>): Promise<string> {
|
||||
const cloned = JSON.parse(JSON.stringify(manifest));
|
||||
delete cloned.status;
|
||||
if (cloned.metadata) {
|
||||
delete cloned.metadata.managedFields;
|
||||
delete cloned.metadata.resourceVersion;
|
||||
delete cloned.metadata.uid;
|
||||
delete cloned.metadata.creationTimestamp;
|
||||
delete cloned.metadata.generation;
|
||||
}
|
||||
const yaml = await dumpManifest(cloned);
|
||||
const yaml = await dumpManifest(stripRuntimeFields(manifest));
|
||||
return cleanYAML(yaml);
|
||||
}
|
||||
@@ -22,7 +22,8 @@ export function cleanYAML(yaml: string): string {
|
||||
// ===== 移除配置 =====
|
||||
const METADATA_LINE_KEYS = new Set([
|
||||
'uid', 'resourceVersion', 'creationTimestamp', 'generation',
|
||||
'selfLink', 'clusterName', 'managedFields', 'generateName',
|
||||
'selfLink', 'clusterName', 'generateName',
|
||||
// managedFields 走 METADATA_BLOCK_KEYS 整块删除(含同级数组项)// Logan.Li
|
||||
]);
|
||||
const METADATA_BLOCK_KEYS = new Set(['managedFields', 'ownerReferences']);
|
||||
|
||||
@@ -185,8 +186,17 @@ export function cleanYAML(yaml: string): string {
|
||||
}
|
||||
|
||||
// metadata 块字段移除(managedFields/ownerReferences)
|
||||
// K8s 数组项与 key 同级缩进(如 managedFields:\n - apiVersion),需一并跳过 // Logan.Li
|
||||
if (indent === metaIndent && key && METADATA_BLOCK_KEYS.has(key)) {
|
||||
skipBlock(metaIndent);
|
||||
i++;
|
||||
while (i < lines.length) {
|
||||
const l = lines[i];
|
||||
const t = l.trimStart();
|
||||
if (t === '' || t.startsWith('#')) { i++; continue; }
|
||||
const ind = getIndent(l);
|
||||
if (ind > metaIndent || (ind === metaIndent && t.startsWith('-'))) { i++; continue; }
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user