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
This commit is contained in:
Your Name
2026-08-07 14:31:09 +08:00
parent 70e196045c
commit 23e6ac3afd
20 changed files with 947 additions and 366 deletions
+47 -18
View File
@@ -76,36 +76,65 @@ export const k8sClusterRoleSchema = {
], ],
} }
function toYaml(obj, indent = 0) {
const lines = []
const prefix = ' '.repeat(indent)
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)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${first[1]}`) const subAllIndent = childIndent + ' '
for (let i = 1; i < entries.length; i++) { const subObj = {}
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) 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 { } else {
lines.push(`${prefix} - ${item}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
} }
+47 -18
View File
@@ -75,36 +75,65 @@ export const k8sClusterRoleBindingSchema = {
], ],
} }
function toYaml(obj, indent = 0) {
const lines = []
const prefix = ' '.repeat(indent)
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)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${first[1]}`) const subAllIndent = childIndent + ' '
for (let i = 1; i < entries.length; i++) { const subObj = {}
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) 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 { } else {
lines.push(`${prefix} - ${item}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
} }
+47 -18
View File
@@ -90,36 +90,65 @@ export const k8sConfigMapSchema = {
], ],
} }
function toYaml(obj, indent = 0) {
const lines = []
const prefix = ' '.repeat(indent)
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)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${first[1]}`) const subAllIndent = childIndent + ' '
for (let i = 1; i < entries.length; i++) { const subObj = {}
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) 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 { } else {
lines.push(`${prefix} - ${item}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
} }
+50 -18
View File
@@ -237,31 +237,63 @@ export const k8sCronJobSchema = {
// ======================== YAML 生成器 ======================== // ======================== YAML 生成器 ========================
function toYaml(obj, indent) {
indent = indent || 0
var lines = []
var prefix = '' function yamlScalar(v) {
for (var p = 0; p < indent; p++) prefix += ' ' if (v === null || v === undefined) return ''
var keys = Object.keys(obj) if (typeof v === 'number' || typeof v === 'boolean') return String(v)
for (var ki = 0; ki < keys.length; ki++) { const s = String(v)
var key = keys[ki] if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
var value = obj[key] 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 (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(prefix + key + ':') const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (var ai = 0; ai < value.length; ai++) { if (!items.length) continue
var item = value[ai] lines.push(`${baseIndent}${key}:`)
if (typeof item === 'object' && item !== null) { for (const item of items) {
lines.push(renderArrayObject(item, indent + 1)) 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 { } else {
lines.push(prefix + ' - ' + item) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(prefix + key + ':') if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(prefix + key + ': ' + value) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
+45 -22
View File
@@ -357,40 +357,63 @@ export function generateK8sDaemonSetYaml(config) {
return header + '\n' + toYaml(daemonset) return header + '\n' + toYaml(daemonset)
} }
function toYaml(obj, indent = 0) {
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 = [] const lines = []
const prefix = ' '.repeat(indent)
for (const [key, value] of Object.entries(obj)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${typeof first[1] === 'object' ? '' : first[1]}`) const subAllIndent = childIndent + ' '
if (typeof first[1] === 'object') { const subObj = {}
lines.push(toYaml(first[1], indent + 3)) for (const [k, v] of entries) subObj[k] = v
} const subRendered = toYaml(subObj, subAllIndent)
for (let i = 1; i < entries.length; i++) { const subLines = subRendered.split('\n')
const [k, v] = entries[i] if (subLines[0].startsWith(subAllIndent)) {
if (typeof v === 'object') { subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
lines.push(`${prefix} ${k}:`)
lines.push(toYaml(v, indent + 4))
} else { } else {
lines.push(`${prefix} ${k}: ${v}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
} }
lines.push(...subLines)
} else { } else {
lines.push(`${prefix} - ${item}`) lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
+64 -22
View File
@@ -433,40 +433,82 @@ export function generateK8sDeploymentYaml(config) {
return header + '\n' + toYaml(deployment) return header + '\n' + toYaml(deployment)
} }
function toYaml(obj, indent = 0) { // 序列化值为带引号的 YAML 标量字符串
// 过滤掉空值/null
function clean(obj) {
if (Array.isArray(obj)) return obj.map(clean).filter(v => v !== undefined)
if (obj && typeof obj === 'object') {
const out = {}
for (const [k, v] of Object.entries(obj)) {
if (v === null || v === undefined || v === '') continue
const cv = clean(v)
if (cv === undefined) continue
out[k] = cv
}
return Object.keys(out).length ? out : undefined
}
return obj
}
// 通用递归 YAML 序列化器:父级 + 本行缩进 + 子级缩进
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 = [] const lines = []
const prefix = ' '.repeat(indent)
for (const [key, value] of Object.entries(obj)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${typeof first[1] === 'object' ? '' : first[1]}`) const subAllIndent = childIndent + ' '
if (typeof first[1] === 'object') { const subObj = {}
lines.push(toYaml(first[1], indent + 3)) for (const [k, v] of entries) subObj[k] = v
} const subRendered = toYaml(subObj, subAllIndent)
for (let i = 1; i < entries.length; i++) { const subLines = subRendered.split('\n')
const [k, v] = entries[i] if (subLines[0].startsWith(subAllIndent)) {
if (typeof v === 'object') { subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
lines.push(`${prefix} ${k}:`)
lines.push(toYaml(v, indent + 4))
} else { } else {
lines.push(`${prefix} ${k}: ${v}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
} }
lines.push(...subLines)
} else { } else {
lines.push(`${prefix} - ${item}`) lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
+47 -18
View File
@@ -136,36 +136,65 @@ export const k8sIngressSchema = {
], ],
} }
function toYaml(obj, indent = 0) {
const lines = []
const prefix = ' '.repeat(indent)
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)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${first[1]}`) const subAllIndent = childIndent + ' '
for (let i = 1; i < entries.length; i++) { const subObj = {}
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) 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 { } else {
lines.push(`${prefix} - ${item}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
} }
+50 -18
View File
@@ -208,31 +208,63 @@ export const k8sJobSchema = {
// ======================== YAML 生成器 ======================== // ======================== YAML 生成器 ========================
function toYaml(obj, indent) {
indent = indent || 0
var lines = []
var prefix = '' function yamlScalar(v) {
for (var p = 0; p < indent; p++) prefix += ' ' if (v === null || v === undefined) return ''
var keys = Object.keys(obj) if (typeof v === 'number' || typeof v === 'boolean') return String(v)
for (var ki = 0; ki < keys.length; ki++) { const s = String(v)
var key = keys[ki] if (/[:#\n"'`]|^[\s\-]|[\s]$/.test(s) || /^(true|false|null|yes|no|on|off|~)$/i.test(s)) {
var value = obj[key] 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 (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(prefix + key + ':') const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (var ai = 0; ai < value.length; ai++) { if (!items.length) continue
var item = value[ai] lines.push(`${baseIndent}${key}:`)
if (typeof item === 'object' && item !== null) { for (const item of items) {
lines.push(renderArrayObject(item, indent + 1)) 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 { } else {
lines.push(prefix + ' - ' + item) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(prefix + key + ':') if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(prefix + key + ': ' + value) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
+47 -18
View File
@@ -48,36 +48,65 @@ export const k8sNamespaceSchema = {
], ],
} }
function toYaml(obj, indent = 0) {
const lines = []
const prefix = ' '.repeat(indent)
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)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${first[1]}`) const subAllIndent = childIndent + ' '
for (let i = 1; i < entries.length; i++) { const subObj = {}
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) 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 { } else {
lines.push(`${prefix} - ${item}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
} }
+47 -18
View File
@@ -114,36 +114,65 @@ export const k8sNetworkPolicySchema = {
], ],
} }
function toYaml(obj, indent = 0) {
const lines = []
const prefix = ' '.repeat(indent)
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)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${first[1]}`) const subAllIndent = childIndent + ' '
for (let i = 1; i < entries.length; i++) { const subObj = {}
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) 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 { } else {
lines.push(`${prefix} - ${item}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
} }
+45 -22
View File
@@ -445,40 +445,63 @@ export function generateK8sPodYaml(config) {
return header + '\n' + toYaml(pod) return header + '\n' + toYaml(pod)
} }
function toYaml(obj, indent = 0) {
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 = [] const lines = []
const prefix = ' '.repeat(indent)
for (const [key, value] of Object.entries(obj)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${typeof first[1] === 'object' ? '' : first[1]}`) const subAllIndent = childIndent + ' '
if (typeof first[1] === 'object') { const subObj = {}
lines.push(toYaml(first[1], indent + 3)) for (const [k, v] of entries) subObj[k] = v
} const subRendered = toYaml(subObj, subAllIndent)
for (let i = 1; i < entries.length; i++) { const subLines = subRendered.split('\n')
const [k, v] = entries[i] if (subLines[0].startsWith(subAllIndent)) {
if (typeof v === 'object') { subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
lines.push(`${prefix} ${k}:`)
lines.push(toYaml(v, indent + 4))
} else { } else {
lines.push(`${prefix} ${k}: ${v}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
} }
lines.push(...subLines)
} else { } else {
lines.push(`${prefix} - ${item}`) lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
+47 -18
View File
@@ -125,36 +125,65 @@ export const k8sPvSchema = {
], ],
} }
function toYaml(obj, indent = 0) {
const lines = []
const prefix = ' '.repeat(indent)
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)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${first[1]}`) const subAllIndent = childIndent + ' '
for (let i = 1; i < entries.length; i++) { const subObj = {}
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) 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 { } else {
lines.push(`${prefix} - ${item}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
} }
+47 -18
View File
@@ -72,36 +72,65 @@ export const k8sPvcSchema = {
], ],
} }
function toYaml(obj, indent = 0) {
const lines = []
const prefix = ' '.repeat(indent)
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)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${first[1]}`) const subAllIndent = childIndent + ' '
for (let i = 1; i < entries.length; i++) { const subObj = {}
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) 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 { } else {
lines.push(`${prefix} - ${item}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
} }
+47 -18
View File
@@ -81,36 +81,65 @@ export const k8sRoleSchema = {
], ],
} }
function toYaml(obj, indent = 0) {
const lines = []
const prefix = ' '.repeat(indent)
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)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${first[1]}`) const subAllIndent = childIndent + ' '
for (let i = 1; i < entries.length; i++) { const subObj = {}
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) 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 { } else {
lines.push(`${prefix} - ${item}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
} }
+47 -18
View File
@@ -82,36 +82,65 @@ export const k8sRoleBindingSchema = {
], ],
} }
function toYaml(obj, indent = 0) {
const lines = []
const prefix = ' '.repeat(indent)
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)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${first[1]}`) const subAllIndent = childIndent + ' '
for (let i = 1; i < entries.length; i++) { const subObj = {}
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) 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 { } else {
lines.push(`${prefix} - ${item}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
} }
+47 -18
View File
@@ -98,36 +98,65 @@ export const k8sSecretSchema = {
], ],
} }
function toYaml(obj, indent = 0) {
const lines = []
const prefix = ' '.repeat(indent)
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)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${first[1]}`) const subAllIndent = childIndent + ' '
for (let i = 1; i < entries.length; i++) { const subObj = {}
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) 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 { } else {
lines.push(`${prefix} - ${item}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
} }
+47 -18
View File
@@ -134,36 +134,65 @@ export const k8sServiceSchema = {
], ],
} }
function toYaml(obj, indent = 0) {
const lines = []
const prefix = ' '.repeat(indent)
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)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${first[1]}`) const subAllIndent = childIndent + ' '
for (let i = 1; i < entries.length; i++) { const subObj = {}
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) 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 { } else {
lines.push(`${prefix} - ${item}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
} }
+47 -18
View File
@@ -62,36 +62,65 @@ export const k8sServiceAccountSchema = {
], ],
} }
function toYaml(obj, indent = 0) {
const lines = []
const prefix = ' '.repeat(indent)
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)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${first[1]}`) const subAllIndent = childIndent + ' '
for (let i = 1; i < entries.length; i++) { const subObj = {}
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) 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 { } else {
lines.push(`${prefix} - ${item}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
} }
+45 -22
View File
@@ -424,40 +424,63 @@ export function generateK8sStatefulSetYaml(config) {
return header + '\n' + toYaml(statefulset) return header + '\n' + toYaml(statefulset)
} }
function toYaml(obj, indent = 0) {
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 = [] const lines = []
const prefix = ' '.repeat(indent)
for (const [key, value] of Object.entries(obj)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${typeof first[1] === 'object' ? '' : first[1]}`) const subAllIndent = childIndent + ' '
if (typeof first[1] === 'object') { const subObj = {}
lines.push(toYaml(first[1], indent + 3)) for (const [k, v] of entries) subObj[k] = v
} const subRendered = toYaml(subObj, subAllIndent)
for (let i = 1; i < entries.length; i++) { const subLines = subRendered.split('\n')
const [k, v] = entries[i] if (subLines[0].startsWith(subAllIndent)) {
if (typeof v === 'object') { subLines[0] = childIndent + '- ' + subLines[0].slice(subAllIndent.length)
lines.push(`${prefix} ${k}:`)
lines.push(toYaml(v, indent + 4))
} else { } else {
lines.push(`${prefix} ${k}: ${v}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
} }
lines.push(...subLines)
} else { } else {
lines.push(`${prefix} - ${item}`) lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
+47 -18
View File
@@ -103,36 +103,65 @@ export const k8sStorageClassSchema = {
], ],
} }
function toYaml(obj, indent = 0) {
const lines = []
const prefix = ' '.repeat(indent)
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)) { for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined || value === '') continue if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) { if (Array.isArray(value)) {
lines.push(`${prefix}${key}:`) const items = value.filter(v => v !== null && v !== undefined && v !== '')
for (const item of value) { if (!items.length) continue
if (typeof item === 'object' && item !== null) { 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 !== '') const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
if (entries.length === 0) continue if (!entries.length) continue
const first = entries[0] const childIndent = baseIndent + ' '
lines.push(`${prefix} - ${first[0]}: ${first[1]}`) const subAllIndent = childIndent + ' '
for (let i = 1; i < entries.length; i++) { const subObj = {}
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) 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 { } else {
lines.push(`${prefix} - ${item}`) subLines[0] = childIndent + '- ' + subLines[0].trimStart()
}
lines.push(...subLines)
} else {
lines.push(`${baseIndent} - ${yamlScalar(item)}`)
} }
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
lines.push(`${prefix}${key}:`) if (isEmptyMapping(value)) {
lines.push(toYaml(value, indent + 1)) lines.push(`${baseIndent}${key}: {}`)
continue
}
lines.push(`${baseIndent}${key}:`)
const childIndent = baseIndent + ' '
lines.push(toYaml(value, childIndent))
} else { } else {
lines.push(`${prefix}${key}: ${value}`) lines.push(`${baseIndent}${key}: ${yamlScalar(value)}`)
} }
} }
return lines.join('\n') return lines.join('\n')
} }