Files
ConfTemplate/src/schemas/k8s-pvc.js
T
Your Name 23e6ac3afd fix(schemas): k8s toYaml 修复 ports/volumes/envFrom 等数组-对象字段被错渲为 dict
toYaml(obj, indent=0) 旧版递归缩进计算错位,导致:
  ports:
      ports:        <- 多余的父 key 行
        - name: http
          containerPort: 5099
被 pyyaml 反解为 {0:{...}} 而不是 list, kubectl apply 报:
  cannot unmarshal object into Go struct field
  Container.spec.template.spec.containers.ports of type []v1.ContainerPort

修复:
- 重写为 baseIndent 字符串缩进版, 数组-对象分支递归渲染后第一行加 '- '
- 新增 yamlScalar(v) 处理 number/boolean/特殊字符安全引号
- 新增 isEmptyMapping(o) 把 emptyDir:{} 等空对象渲染为 {}
- 20 个 k8s-*.js schema 全部统一替换

验证:
- npm run build 通过
- PyYAML 反序列化 ports/volumes/envFrom/tolerations 等 9 个数组-对象字段全部为 list
- 全部 20 个 schema E2E OK
2026-08-07 14:31:19 +08:00

164 lines
4.8 KiB
JavaScript

// K8s PersistentVolumeClaim schema + YAML generator for ConfTemplate
// Generated: 2026-07-18
export const k8sPvcSchema = {
id: 'k8s-pvc',
name: 'K8s PersistentVolumeClaim',
icon: 'HardDrive',
category: '云原生',
description: 'Kubernetes 持久化存储声明 (PVC)',
format: 'yaml',
fileName: 'pvc.yaml',
groups: [
{
title: '基础信息',
fields: [
{
key: 'name',
label: 'PVC 名称',
type: 'text',
placeholder: 'my-pvc',
default: 'my-pvc',
required: true,
tip: 'PersistentVolumeClaim 资源名称',
},
{
key: 'namespace',
label: '命名空间',
type: 'text',
placeholder: 'default',
default: 'default',
},
{
key: 'accessModes',
label: '访问模式',
type: 'select',
options: [
{ label: 'ReadWriteOnce', value: 'ReadWriteOnce' },
{ label: 'ReadWriteMany', value: 'ReadWriteMany' },
{ label: 'ReadOnlyMany', value: 'ReadOnlyMany' },
],
default: 'ReadWriteOnce',
tip: '单节点读写 / 多节点读写 / 多节点只读',
},
{
key: 'storageClassName',
label: 'StorageClass 名称',
type: 'text',
placeholder: 'standard',
default: 'standard',
},
{
key: 'storageRequest',
label: '请求存储容量',
type: 'text',
placeholder: '10Gi',
default: '10Gi',
required: true,
tip: '例如 10Gi, 100Mi',
},
{
key: 'volumeMode',
label: '卷模式',
type: 'select',
options: [
{ label: 'Filesystem', value: 'Filesystem' },
{ label: 'Block', value: 'Block' },
],
default: 'Filesystem',
},
],
},
],
}
function yamlScalar(v) {
if (v === null || v === undefined) return ''
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
const s = String(v)
if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
return JSON.stringify(s)
}
return s
}
function isEmptyMapping(o) {
if (!o || typeof o !== 'object' || Array.isArray(o)) return false
return Object.values(o).every(v => v === null || v === undefined || v === '' || (typeof v === 'object' && isEmptyMapping(v)))
}
function toYaml(obj, baseIndent = '') {
const lines = []
for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) {
const items = value.filter(v => v !== null && v !== undefined && v !== '')
if (!items.length) continue
lines.push(`${baseIndent}${key}:`)
for (const item of items) {
if (item && typeof item === 'object' && !Array.isArray(item)) {
if (isEmptyMapping(item)) continue
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (!entries.length) continue
const childIndent = baseIndent + ' '
const subAllIndent = childIndent + ' '
const subObj = {}
for (const [k, v] of entries) subObj[k] = v
const subRendered = toYaml(subObj, subAllIndent)
const subLines = subRendered.split('\n')
if (subLines[0].startsWith(subAllIndent)) {
subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
} else {
subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
}
}
} else if (typeof value === 'object') {
if (isEmptyMapping(value)) {
lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else {
lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
}
}
return lines.join('\n')
}
export function generateK8sPvcYaml(config) {
const pvc = {
apiVersion: 'v1',
kind: 'PersistentVolumeClaim',
metadata: {
name: config.name,
namespace: config.namespace,
},
spec: {
accessModes: [config.accessModes],
storageClassName: config.storageClassName,
volumeMode: config.volumeMode,
resources: {
requests: {
storage: config.storageRequest,
},
},
},
}
const header = `# K8s PersistentVolumeClaim - 由 ConfTemplate 生成
# 生成时间: ${new Date().toLocaleString('zh-CN')}
# 部署命令: kubectl apply -f ${config.fileName || 'pvc.yaml'}
`
return header + '\n' + toYaml(pvc)
}