23e6ac3afd
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
260 lines
7.1 KiB
JavaScript
260 lines
7.1 KiB
JavaScript
// K8s Ingress schema + YAML generator for ConfTemplate
|
|
// Generated: 2026-07-18
|
|
|
|
export const k8sIngressSchema = {
|
|
id: 'k8s-ingress',
|
|
name: 'K8s Ingress',
|
|
icon: 'Globe',
|
|
category: '云原生',
|
|
description: 'Kubernetes Ingress 网络入口资源',
|
|
format: 'yaml',
|
|
fileName: 'ingress.yaml',
|
|
groups: [
|
|
{
|
|
title: '基础信息',
|
|
fields: [
|
|
{
|
|
key: 'name',
|
|
label: 'Ingress 名称',
|
|
type: 'text',
|
|
placeholder: 'my-ingress',
|
|
default: 'my-ingress',
|
|
required: true,
|
|
tip: '将用作 Ingress 资源名称',
|
|
},
|
|
{
|
|
key: 'namespace',
|
|
label: '命名空间',
|
|
type: 'text',
|
|
placeholder: 'default',
|
|
default: 'default',
|
|
},
|
|
{
|
|
key: 'ingressClassName',
|
|
label: 'Ingress Class',
|
|
type: 'select',
|
|
options: [
|
|
{ label: 'nginx', value: 'nginx' },
|
|
{ label: 'traefik', value: 'traefik' },
|
|
{ label: '(不指定)', value: '' },
|
|
],
|
|
default: 'nginx',
|
|
tip: '选择 Ingress 控制器类型',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: '路由规则',
|
|
fields: [
|
|
{
|
|
key: 'host',
|
|
label: '域名',
|
|
type: 'text',
|
|
placeholder: 'example.com',
|
|
default: 'example.com',
|
|
required: true,
|
|
tip: '匹配的主机名',
|
|
},
|
|
{
|
|
key: 'path',
|
|
label: '路径',
|
|
type: 'text',
|
|
placeholder: '/',
|
|
default: '/',
|
|
},
|
|
{
|
|
key: 'pathType',
|
|
label: '路径类型',
|
|
type: 'select',
|
|
options: [
|
|
{ label: 'Prefix', value: 'Prefix' },
|
|
{ label: 'Exact', value: 'Exact' },
|
|
{ label: 'ImplementationSpecific', value: 'ImplementationSpecific' },
|
|
],
|
|
default: 'Prefix',
|
|
},
|
|
{
|
|
key: 'backendServiceName',
|
|
label: '后端 Service 名称',
|
|
type: 'text',
|
|
placeholder: 'my-service',
|
|
default: '',
|
|
required: true,
|
|
tip: '流量转发到的 Service 名称',
|
|
},
|
|
{
|
|
key: 'backendServicePort',
|
|
label: '后端 Service 端口',
|
|
type: 'number',
|
|
min: 1,
|
|
max: 65535,
|
|
default: 80,
|
|
required: true,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: 'TLS 配置',
|
|
fields: [
|
|
{
|
|
key: 'enableTls',
|
|
label: '启用 TLS',
|
|
type: 'switch',
|
|
default: false,
|
|
tip: '开启 HTTPS 终止',
|
|
},
|
|
{
|
|
key: 'tlsSecretName',
|
|
label: 'TLS Secret 名称',
|
|
type: 'text',
|
|
placeholder: 'my-tls-secret',
|
|
default: '',
|
|
dependsOn: 'enableTls',
|
|
tip: '包含 TLS 证书的 Secret 名称',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: 'Annotations',
|
|
fields: [
|
|
{
|
|
key: 'enableCors',
|
|
label: '启用 CORS',
|
|
type: 'switch',
|
|
default: false,
|
|
tip: '添加 nginx.ingress.kubernetes.io/enable-cors 注解',
|
|
},
|
|
{
|
|
key: 'rewriteTarget',
|
|
label: '启用路径重写',
|
|
type: 'switch',
|
|
default: false,
|
|
tip: '添加 nginx.ingress.kubernetes.io/rewrite-target 注解',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
|
|
|
|
|
|
|
|
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 generateK8sIngressYaml(config) {
|
|
const annotations = {}
|
|
if (config.enableCors) {
|
|
annotations['nginx.ingress.kubernetes.io/enable-cors'] = 'true'
|
|
}
|
|
if (config.rewriteTarget) {
|
|
annotations['nginx.ingress.kubernetes.io/rewrite-target'] = '/'
|
|
}
|
|
|
|
const ingress = {
|
|
apiVersion: 'networking.k8s.io/v1',
|
|
kind: 'Ingress',
|
|
metadata: {
|
|
name: config.name,
|
|
namespace: config.namespace,
|
|
annotations: Object.keys(annotations).length > 0 ? annotations : undefined,
|
|
},
|
|
spec: {
|
|
ingressClassName: config.ingressClassName || undefined,
|
|
rules: [
|
|
{
|
|
host: config.host,
|
|
http: {
|
|
paths: [
|
|
{
|
|
path: config.path,
|
|
pathType: config.pathType,
|
|
backend: {
|
|
service: {
|
|
name: config.backendServiceName,
|
|
port: {
|
|
number: Number(config.backendServicePort),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
}
|
|
|
|
if (config.enableTls && config.tlsSecretName) {
|
|
ingress.spec.tls = [
|
|
{
|
|
hosts: [config.host],
|
|
secretName: config.tlsSecretName,
|
|
},
|
|
]
|
|
}
|
|
|
|
const header = `# K8s Ingress - 由 ConfTemplate 生成
|
|
# 生成时间: ${new Date().toLocaleString('zh-CN')}
|
|
# 部署命令: kubectl apply -f ${config.fileName || 'ingress.yaml'}
|
|
`
|
|
|
|
return header + '\n' + toYaml(ingress)
|
|
}
|