From 4f702b833a49950fcf73a794dfee0c9abb3119d8 Mon Sep 17 00:00:00 2001 From: cnbugs Date: Sat, 18 Jul 2026 19:39:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=2018=20=E4=B8=AA=20K?= =?UTF-8?q?8s=20=E8=B5=84=E6=BA=90=E9=85=8D=E7=BD=AE=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 工作负载: Pod, StatefulSet, DaemonSet, Job, CronJob 服务发现: Ingress 存储管理: PersistentVolume, PersistentVolumeClaim, StorageClass 配置与密钥: ConfigMap, Secret 安全与权限: ServiceAccount, Role, ClusterRole, RoleBinding, ClusterRoleBinding, NetworkPolicy 集群管理: Namespace 总计支持 26 个组件,覆盖 K8s 核心资源全谱 --- README.md | 62 ++++-- src/schemas/index.js | 83 ++++++- src/schemas/k8s-clusterrole.js | 140 ++++++++++++ src/schemas/k8s-clusterrolebinding.js | 143 ++++++++++++ src/schemas/k8s-configmap.js | 160 ++++++++++++++ src/schemas/k8s-cronjob.js | 196 +++++++++++++++++ src/schemas/k8s-daemonset.js | 247 +++++++++++++++++++++ src/schemas/k8s-ingress.js | 230 +++++++++++++++++++ src/schemas/k8s-job.js | 186 ++++++++++++++++ src/schemas/k8s-namespace.js | 103 +++++++++ src/schemas/k8s-networkpolicy.js | 229 +++++++++++++++++++ src/schemas/k8s-pod.js | 304 ++++++++++++++++++++++++++ src/schemas/k8s-pv.js | 198 +++++++++++++++++ src/schemas/k8s-pvc.js | 134 ++++++++++++ src/schemas/k8s-role.js | 146 +++++++++++++ src/schemas/k8s-rolebinding.js | 154 +++++++++++++ src/schemas/k8s-secret.js | 166 ++++++++++++++ src/schemas/k8s-serviceaccount.js | 122 +++++++++++ src/schemas/k8s-statefulset.js | 297 +++++++++++++++++++++++++ src/schemas/k8s-storageclass.js | 170 ++++++++++++++ 20 files changed, 3455 insertions(+), 15 deletions(-) create mode 100644 src/schemas/k8s-clusterrole.js create mode 100644 src/schemas/k8s-clusterrolebinding.js create mode 100644 src/schemas/k8s-configmap.js create mode 100644 src/schemas/k8s-cronjob.js create mode 100644 src/schemas/k8s-daemonset.js create mode 100644 src/schemas/k8s-ingress.js create mode 100644 src/schemas/k8s-job.js create mode 100644 src/schemas/k8s-namespace.js create mode 100644 src/schemas/k8s-networkpolicy.js create mode 100644 src/schemas/k8s-pod.js create mode 100644 src/schemas/k8s-pv.js create mode 100644 src/schemas/k8s-pvc.js create mode 100644 src/schemas/k8s-role.js create mode 100644 src/schemas/k8s-rolebinding.js create mode 100644 src/schemas/k8s-secret.js create mode 100644 src/schemas/k8s-serviceaccount.js create mode 100644 src/schemas/k8s-statefulset.js create mode 100644 src/schemas/k8s-storageclass.js diff --git a/README.md b/README.md index cd08e03..a797915 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ --- -## 🧩 一期支持组件 +## 🧩 支持组件(26 个) | 分类 | 组件 | 输出格式 | 核心配置项 | |------|------|----------|------------| @@ -45,10 +45,28 @@ | 缓存 | **Redis** | `.conf` | 单机/哨兵/集群模式、端口、内存限制、RDB/AOF 持久化、密码 | | 数据库 | **MySQL** | `.cnf` | 版本(5.7/8.0)、Buffer Pool 自动计算、字符集、慢查询、主从复制 | | 数据库 | **MongoDB** | `.conf` | 端口、WiredTiger 引擎、认证、TLS、副本集、Oplog、压缩算法 | -| 云原生 | **K8s Deployment** | `.yaml` | 镜像、副本数、资源限制、健康检查、滚动更新策略 | -| 云原生 | **K8s Service** | `.yaml` | ClusterIP/NodePort/LoadBalancer、端口映射、会话保持 | -| 自动化运维 | **Ansible** | `.cfg` | SSH 连接、Pipelining、Become 提权、Forks 并发、日志、事实缓存 | -| 自动化运维 | **Ansible Playbook** | `.yaml` | 变量、包管理、服务管理、文件复制、模板渲染、Shell 命令、Handlers | +| K8S 工作负载 | **Pod** | `.yaml` | 容器镜像、端口、资源限制、环境变量、重启策略 | +| K8S 工作负载 | **Deployment** | `.yaml` | 镜像、副本数、资源限制、健康检查、滚动更新策略 | +| K8S 工作负载 | **StatefulSet** | `.yaml` | 有状态应用、稳定网络标识、PVC 模板、滚动更新 | +| K8S 工作负载 | **DaemonSet** | `.yaml` | 节点级守护进程、主机网络、容忍度配置 | +| K8S 工作负载 | **Job** | `.yaml` | 一次性任务、完成策略、重试限制、TTL 清理 | +| K8S 工作负载 | **CronJob** | `.yaml` | Cron 表达式、并发策略、历史保留、暂停开关 | +| K8S 服务发现 | **Service** | `.yaml` | ClusterIP/NodePort/LoadBalancer、端口映射、会话保持 | +| K8S 服务发现 | **Ingress** | `.yaml` | 主机路由、路径匹配、TLS 终止、IngressClass | +| K8S 存储 | **PersistentVolume** | `.yaml` | 容量、访问模式、回收策略、HostPath/NFS | +| K8S 存储 | **PersistentVolumeClaim** | `.yaml` | 存储请求、StorageClass、访问模式 | +| K8S 存储 | **StorageClass** | `.yaml` | Provisioner、回收策略、延迟绑定、卷扩展 | +| K8S 配置与密钥 | **ConfigMap** | `.yaml` | 键值对配置、挂载为卷或环境变量 | +| K8S 配置与密钥 | **Secret** | `.yaml` | Opaque/TLS/Docker Registry、Base64 编码 | +| K8S 安全与权限 | **ServiceAccount** | `.yaml` | 自动挂载 Token、镜像拉取密钥 | +| K8S 安全与权限 | **Role** | `.yaml` | 命名空间级 RBAC、资源+动词组合 | +| K8S 安全与权限 | **ClusterRole** | `.yaml` | 集群级 RBAC、跨命名空间权限 | +| K8S 安全与权限 | **RoleBinding** | `.yaml` | 将 Role 绑定到 User/Group/ServiceAccount | +| K8S 安全与权限 | **ClusterRoleBinding** | `.yaml` | 将 ClusterRole 绑定到集群级主体 | +| K8S 安全与权限 | **NetworkPolicy** | `.yaml` | 入站/出站流量控制、Pod 选择器、CIDR 规则 | +| K8S 集群管理 | **Namespace** | `.yaml` | 命名空间创建、标签管理 | +| 自动化运维 | **Ansible** | `.cfg` | SSH 连接、Pipelining、Become 提权、Forks、事实缓存 | +| 自动化运维 | **Ansible Playbook** | `.yaml` | 变量、包管理、服务管理、文件复制、模板渲染、Handlers | --- @@ -105,14 +123,32 @@ conf-template/ │ │ └── CodePreview.vue # 右侧 Monaco Editor 代码预览 │ └── schemas/ │ ├── index.js # Schema 注册中心 + 工具函数 -│ ├── nginx.js # NGINX Schema 定义 + 模板生成 -│ ├── redis.js # Redis Schema 定义 + 模板生成 -│ ├── mysql.js # MySQL Schema 定义 + 模板生成 -│ ├── mongodb.js # MongoDB Schema 定义 + 模板生成 -│ ├── k8s-deployment.js # K8s Deployment Schema + YAML 生成 -│ ├── k8s-service.js # K8s Service Schema + YAML 生成 -│ ├── ansible.js # Ansible Schema + 配置生成 -│ └── ansible-playbook.js # Ansible Playbook Schema + YAML 生成 +│ ├── nginx.js # NGINX +│ ├── redis.js # Redis +│ ├── mysql.js # MySQL +│ ├── mongodb.js # MongoDB +│ ├── k8s-pod.js # K8s Pod +│ ├── k8s-deployment.js # K8s Deployment +│ ├── k8s-statefulset.js # K8s StatefulSet +│ ├── k8s-daemonset.js # K8s DaemonSet +│ ├── k8s-job.js # K8s Job +│ ├── k8s-cronjob.js # K8s CronJob +│ ├── k8s-service.js # K8s Service +│ ├── k8s-ingress.js # K8s Ingress +│ ├── k8s-pv.js # K8s PersistentVolume +│ ├── k8s-pvc.js # K8s PersistentVolumeClaim +│ ├── k8s-storageclass.js # K8s StorageClass +│ ├── k8s-configmap.js # K8s ConfigMap +│ ├── k8s-secret.js # K8s Secret +│ ├── k8s-serviceaccount.js # K8s ServiceAccount +│ ├── k8s-role.js # K8s Role +│ ├── k8s-clusterrole.js # K8s ClusterRole +│ ├── k8s-rolebinding.js # K8s RoleBinding +│ ├── k8s-clusterrolebinding.js # K8s ClusterRoleBinding +│ ├── k8s-networkpolicy.js # K8s NetworkPolicy +│ ├── k8s-namespace.js # K8s Namespace +│ ├── ansible.js # Ansible +│ └── ansible-playbook.js # Ansible Playbook └── dist/ # 生产构建产物(gitignore) ``` diff --git a/src/schemas/index.js b/src/schemas/index.js index 7f0f0c9..e88eabf 100644 --- a/src/schemas/index.js +++ b/src/schemas/index.js @@ -6,6 +6,24 @@ import { k8sDeploymentSchema, generateK8sDeploymentYaml } from './k8s-deployment import { k8sServiceSchema, generateK8sServiceYaml } from './k8s-service' import { ansibleSchema, generateAnsibleCfg } from './ansible' import { ansiblePlaybookSchema, generateAnsiblePlaybookYaml } from './ansible-playbook' +import { k8sPodSchema, generateK8sPodYaml } from './k8s-pod' +import { k8sStatefulSetSchema, generateK8sStatefulSetYaml } from './k8s-statefulset' +import { k8sDaemonSetSchema, generateK8sDaemonSetYaml } from './k8s-daemonset' +import { k8sJobSchema, generateK8sJobYaml } from './k8s-job' +import { k8sCronJobSchema, generateK8sCronJobYaml } from './k8s-cronjob' +import { k8sIngressSchema, generateK8sIngressYaml } from './k8s-ingress' +import { k8sPvSchema, generateK8sPvYaml } from './k8s-pv' +import { k8sPvcSchema, generateK8sPvcYaml } from './k8s-pvc' +import { k8sStorageClassSchema, generateK8sStorageClassYaml } from './k8s-storageclass' +import { k8sConfigMapSchema, generateK8sConfigMapYaml } from './k8s-configmap' +import { k8sSecretSchema, generateK8sSecretYaml } from './k8s-secret' +import { k8sServiceAccountSchema, generateK8sServiceAccountYaml } from './k8s-serviceaccount' +import { k8sRoleSchema, generateK8sRoleYaml } from './k8s-role' +import { k8sClusterRoleSchema, generateK8sClusterRoleYaml } from './k8s-clusterrole' +import { k8sRoleBindingSchema, generateK8sRoleBindingYaml } from './k8s-rolebinding' +import { k8sClusterRoleBindingSchema, generateK8sClusterRoleBindingYaml } from './k8s-clusterrolebinding' +import { k8sNetworkPolicySchema, generateK8sNetworkPolicyYaml } from './k8s-networkpolicy' +import { k8sNamespaceSchema, generateK8sNamespaceYaml } from './k8s-namespace' export const schemas = { nginx: nginxSchema, @@ -16,6 +34,24 @@ export const schemas = { 'k8s-service': k8sServiceSchema, ansible: ansibleSchema, 'ansible-playbook': ansiblePlaybookSchema, + 'k8s-pod': k8sPodSchema, + 'k8s-statefulset': k8sStatefulSetSchema, + 'k8s-daemonset': k8sDaemonSetSchema, + 'k8s-job': k8sJobSchema, + 'k8s-cronjob': k8sCronJobSchema, + 'k8s-ingress': k8sIngressSchema, + 'k8s-pv': k8sPvSchema, + 'k8s-pvc': k8sPvcSchema, + 'k8s-storageclass': k8sStorageClassSchema, + 'k8s-configmap': k8sConfigMapSchema, + 'k8s-secret': k8sSecretSchema, + 'k8s-serviceaccount': k8sServiceAccountSchema, + 'k8s-role': k8sRoleSchema, + 'k8s-clusterrole': k8sClusterRoleSchema, + 'k8s-rolebinding': k8sRoleBindingSchema, + 'k8s-clusterrolebinding': k8sClusterRoleBindingSchema, + 'k8s-networkpolicy': k8sNetworkPolicySchema, + 'k8s-namespace': k8sNamespaceSchema, } export const generators = { @@ -27,6 +63,24 @@ export const generators = { 'k8s-service': generateK8sServiceYaml, ansible: generateAnsibleCfg, 'ansible-playbook': generateAnsiblePlaybookYaml, + 'k8s-pod': generateK8sPodYaml, + 'k8s-statefulset': generateK8sStatefulSetYaml, + 'k8s-daemonset': generateK8sDaemonSetYaml, + 'k8s-job': generateK8sJobYaml, + 'k8s-cronjob': generateK8sCronJobYaml, + 'k8s-ingress': generateK8sIngressYaml, + 'k8s-pv': generateK8sPvYaml, + 'k8s-pvc': generateK8sPvcYaml, + 'k8s-storageclass': generateK8sStorageClassYaml, + 'k8s-configmap': generateK8sConfigMapYaml, + 'k8s-secret': generateK8sSecretYaml, + 'k8s-serviceaccount': generateK8sServiceAccountYaml, + 'k8s-role': generateK8sRoleYaml, + 'k8s-clusterrole': generateK8sClusterRoleYaml, + 'k8s-rolebinding': generateK8sRoleBindingYaml, + 'k8s-clusterrolebinding': generateK8sClusterRoleBindingYaml, + 'k8s-networkpolicy': generateK8sNetworkPolicyYaml, + 'k8s-namespace': generateK8sNamespaceYaml, } export const categories = [ @@ -46,9 +100,34 @@ export const categories = [ items: ['mysql', 'mongodb'], }, { - name: '云原生', + name: 'K8S 工作负载', icon: 'Box', - items: ['k8s-deployment', 'k8s-service'], + items: ['k8s-pod', 'k8s-deployment', 'k8s-statefulset', 'k8s-daemonset', 'k8s-job', 'k8s-cronjob'], + }, + { + name: 'K8S 服务发现', + icon: 'Connection', + items: ['k8s-service', 'k8s-ingress'], + }, + { + name: 'K8S 存储', + icon: 'Files', + items: ['k8s-pv', 'k8s-pvc', 'k8s-storageclass'], + }, + { + name: 'K8S 配置与密钥', + icon: 'Document', + items: ['k8s-configmap', 'k8s-secret'], + }, + { + name: 'K8S 安全与权限', + icon: 'Lock', + items: ['k8s-serviceaccount', 'k8s-role', 'k8s-clusterrole', 'k8s-rolebinding', 'k8s-clusterrolebinding', 'k8s-networkpolicy'], + }, + { + name: 'K8S 集群管理', + icon: 'Grid', + items: ['k8s-namespace'], }, { name: '自动化运维', diff --git a/src/schemas/k8s-clusterrole.js b/src/schemas/k8s-clusterrole.js new file mode 100644 index 0000000..10f7bb3 --- /dev/null +++ b/src/schemas/k8s-clusterrole.js @@ -0,0 +1,140 @@ +// K8s ClusterRole Schema - 由 ConfTemplate 生成 +// 生成时间: 自动生成 + +export const k8sClusterRoleSchema = { + id: 'k8s-clusterrole', + name: 'K8s ClusterRole', + icon: 'ShieldCheck', + category: '云原生', + description: 'Kubernetes 集群级 RBAC 角色', + format: 'yaml', + fileName: 'clusterrole.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'ClusterRole 名称', + type: 'text', + placeholder: 'my-cluster-role', + default: 'my-cluster-role', + required: true, + tip: '将用作 ClusterRole 的 metadata.name', + }, + ], + }, + { + title: '权限规则', + fields: [ + { + key: 'resources', + label: '资源类型', + type: 'select', + multiple: true, + options: [ + { label: 'Pods', value: 'pods' }, + { label: 'Services', value: 'services' }, + { label: 'ConfigMaps', value: 'configmaps' }, + { label: 'Deployments', value: 'deployments' }, + { label: 'Secrets', value: 'secrets' }, + { label: 'Nodes', value: 'nodes' }, + { label: 'Namespaces', value: 'namespaces' }, + { label: 'All (*)', value: '*' }, + ], + default: ['pods'], + tip: '可多选,选择要授权的资源类型', + }, + { + key: 'verbs', + label: '操作动词', + type: 'select', + multiple: true, + options: [ + { label: 'Get (读取)', value: 'get' }, + { label: 'List (列表)', value: 'list' }, + { label: 'Watch (监听)', value: 'watch' }, + { label: 'Create (创建)', value: 'create' }, + { label: 'Update (更新)', value: 'update' }, + { label: 'Delete (删除)', value: 'delete' }, + { label: 'Patch (补丁)', value: 'patch' }, + { label: 'All (*)', value: '*' }, + ], + default: ['get', 'list', 'watch'], + tip: '可多选,选择允许的操作', + }, + { + key: 'apiGroups', + label: 'API 组', + type: 'text', + placeholder: '"" (核心组留空)', + default: '', + tip: 'API 组,核心资源(如 pods, services)留空', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sClusterRoleYaml(config) { + const resources = Array.isArray(config.resources) ? config.resources : [config.resources || 'pods'] + const verbs = Array.isArray(config.verbs) ? config.verbs : [config.verbs || 'get'] + const apiGroups = config.apiGroups !== undefined && config.apiGroups !== '' + ? config.apiGroups.split(',').map(g => g.trim()) + : [''] + + const rule = { + apiGroups, + resources, + verbs, + } + + const clusterRole = { + apiVersion: 'rbac.authorization.k8s.io/v1', + kind: 'ClusterRole', + metadata: { + name: config.name, + }, + rules: [rule], + } + + const header = `# K8s ClusterRole - 由 ConfTemplate 生成 +# 生成时间: ${new Date().toLocaleString('zh-CN')} +# 部署命令: kubectl apply -f ${config.fileName || 'clusterrole.yaml'} +` + + return header + '\n' + toYaml(clusterRole) +} diff --git a/src/schemas/k8s-clusterrolebinding.js b/src/schemas/k8s-clusterrolebinding.js new file mode 100644 index 0000000..9232f25 --- /dev/null +++ b/src/schemas/k8s-clusterrolebinding.js @@ -0,0 +1,143 @@ +// K8s ClusterRoleBinding Schema - 由 ConfTemplate 生成 +// 生成时间: 自动生成 + +export const k8sClusterRoleBindingSchema = { + id: 'k8s-clusterrolebinding', + name: 'K8s ClusterRoleBinding', + icon: 'Link2', + category: '云原生', + description: 'Kubernetes 集群级角色绑定,将 ClusterRole 授予用户或服务账户', + format: 'yaml', + fileName: 'clusterrolebinding.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'ClusterRoleBinding 名称', + type: 'text', + placeholder: 'my-clusterrolebinding', + default: 'my-clusterrolebinding', + required: true, + tip: '将用作 ClusterRoleBinding 的 metadata.name', + }, + ], + }, + { + title: '角色引用', + fields: [ + { + key: 'clusterRoleName', + label: 'ClusterRole 名称', + type: 'text', + placeholder: 'my-cluster-role', + default: '', + required: true, + tip: '要绑定的 ClusterRole 名称', + }, + ], + }, + { + title: '主体配置', + fields: [ + { + key: 'subjectKind', + label: '主体类型', + type: 'select', + options: [ + { label: 'User (用户)', value: 'User' }, + { label: 'Group (用户组)', value: 'Group' }, + { label: 'ServiceAccount (服务账户)', value: 'ServiceAccount' }, + ], + default: 'ServiceAccount', + tip: '要授权的主体类型', + }, + { + key: 'subjectName', + label: '主体名称', + type: 'text', + placeholder: 'my-serviceaccount', + default: '', + required: true, + tip: '用户、组或 ServiceAccount 的名称', + }, + { + key: 'subjectNamespace', + label: '主体命名空间', + type: 'text', + placeholder: '仅 ServiceAccount 需要', + default: '', + tip: '仅当主体类型为 ServiceAccount 时需要填写', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sClusterRoleBindingYaml(config) { + const subject = { + kind: config.subjectKind || 'ServiceAccount', + name: config.subjectName, + } + if (config.subjectNamespace && config.subjectKind === 'ServiceAccount') { + subject.namespace = config.subjectNamespace + } + if (config.subjectKind === 'User' || config.subjectKind === 'Group') { + subject.apiGroup = 'rbac.authorization.k8s.io' + } + + const clusterRoleBinding = { + apiVersion: 'rbac.authorization.k8s.io/v1', + kind: 'ClusterRoleBinding', + metadata: { + name: config.name, + }, + roleRef: { + apiGroup: 'rbac.authorization.k8s.io', + kind: 'ClusterRole', + name: config.clusterRoleName, + }, + subjects: [subject], + } + + const header = `# K8s ClusterRoleBinding - 由 ConfTemplate 生成 +# 生成时间: ${new Date().toLocaleString('zh-CN')} +# 部署命令: kubectl apply -f ${config.fileName || 'clusterrolebinding.yaml'} +` + + return header + '\n' + toYaml(clusterRoleBinding) +} diff --git a/src/schemas/k8s-configmap.js b/src/schemas/k8s-configmap.js new file mode 100644 index 0000000..b373659 --- /dev/null +++ b/src/schemas/k8s-configmap.js @@ -0,0 +1,160 @@ +// K8s ConfigMap Schema - 由 ConfTemplate 生成 +// 生成时间: 自动生成 + +export const k8sConfigMapSchema = { + id: 'k8s-configmap', + name: 'K8s ConfigMap', + icon: 'Settings', + category: '云原生', + description: 'Kubernetes 配置映射,存储键值对配置数据', + format: 'yaml', + fileName: 'configmap.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'ConfigMap 名称', + type: 'text', + placeholder: 'my-config', + default: 'my-config', + required: true, + tip: '将用作 ConfigMap 的 metadata.name', + }, + { + key: 'namespace', + label: '命名空间', + type: 'text', + placeholder: 'default', + default: 'default', + }, + ], + }, + { + title: '数据配置', + fields: [ + { + key: 'dataType', + label: '数据类型', + type: 'select', + options: [ + { label: '字面量 (Literal)', value: 'literal' }, + { label: '环境变量注入 (EnvFrom)', value: 'envFrom' }, + { label: '文件挂载 (File)', value: 'file' }, + ], + default: 'literal', + tip: '决定 ConfigMap 数据的使用方式', + }, + { + key: 'dataKey', + label: '数据键名', + type: 'text', + placeholder: 'app.conf', + default: 'app.conf', + required: true, + tip: 'ConfigMap 中的键名,如配置文件名或环境变量名', + }, + { + key: 'dataValue', + label: '数据值', + type: 'textarea', + placeholder: 'key1=value1\nkey2=value2', + default: 'key1=value1\nkey2=value2', + required: true, + tip: '键值对,每行一个,格式 key=value', + }, + ], + }, + { + title: '挂载配置', + fields: [ + { + key: 'mountAsVolume', + label: '作为 Volume 挂载', + type: 'switch', + default: true, + tip: '是否将 ConfigMap 挂载为容器内的文件', + }, + { + key: 'mountPath', + label: '挂载路径', + type: 'text', + placeholder: '/etc/config', + default: '/etc/config', + dependsOn: { key: 'mountAsVolume', value: true }, + tip: '容器内挂载目录路径', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sConfigMapYaml(config) { + const data = {} + if (config.dataValue) { + const lines = config.dataValue.split('\n').filter(line => line.trim()) + for (const line of lines) { + const eqIdx = line.indexOf('=') + if (eqIdx > 0) { + const k = line.substring(0, eqIdx).trim() + const v = line.substring(eqIdx + 1).trim() + data[k] = v + } else { + data[config.dataKey || 'data'] = line.trim() + } + } + } else { + data[config.dataKey || 'data'] = '' + } + + const configmap = { + apiVersion: 'v1', + kind: 'ConfigMap', + metadata: { + name: config.name, + namespace: config.namespace, + }, + data, + } + + const header = `# K8s ConfigMap - 由 ConfTemplate 生成 +# 生成时间: ${new Date().toLocaleString('zh-CN')} +# 部署命令: kubectl apply -f ${config.fileName || 'configmap.yaml'} +` + + return header + '\n' + toYaml(configmap) +} diff --git a/src/schemas/k8s-cronjob.js b/src/schemas/k8s-cronjob.js new file mode 100644 index 0000000..bca9a47 --- /dev/null +++ b/src/schemas/k8s-cronjob.js @@ -0,0 +1,196 @@ +export const k8sCronJobSchema = { + id: 'k8s-cronjob', + name: 'K8s CronJob', + icon: 'Box', + category: 'K8S 工作负载', + description: 'Kubernetes CronJob — 定时调度批处理任务', + format: 'yaml', + fileName: 'cronjob.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'CronJob 名称', + type: 'text', + placeholder: 'my-cronjob', + default: 'my-cronjob', + required: true, + }, + { + key: 'namespace', + label: '命名空间', + type: 'text', + placeholder: 'default', + default: 'default', + }, + { + key: 'image', + label: '容器镜像', + type: 'text', + placeholder: 'busybox:latest', + default: 'busybox:latest', + required: true, + }, + { + key: 'command', + label: 'Command', + type: 'text', + placeholder: 'echo hello', + default: 'echo hello', + required: true, + tip: '容器启动命令', + }, + ], + }, + { + title: '调度配置', + fields: [ + { + key: 'schedule', + label: 'Cron 表达式', + type: 'text', + placeholder: '0 * * * *', + default: '0 * * * *', + required: true, + tip: '标准 cron 格式: 分 时 日 月 周', + }, + { + key: 'concurrencyPolicy', + label: '并发策略', + type: 'select', + options: [ + { label: 'Allow - 允许并发', value: 'Allow' }, + { label: 'Replace - 替换上一个', value: 'Replace' }, + { label: 'Forbid - 禁止并发', value: 'Forbid' }, + ], + default: 'Allow', + }, + { + key: 'suspend', + label: '暂停调度', + type: 'switch', + default: false, + tip: '暂停后不再创建新的 Job', + }, + ], + }, + { + title: '历史与清理', + fields: [ + { + key: 'successfulJobsHistoryLimit', + label: '成功 Job 保留数', + type: 'number', + min: 0, + max: 10, + default: 3, + }, + { + key: 'failedJobsHistoryLimit', + label: '失败 Job 保留数', + type: 'number', + min: 0, + max: 10, + default: 1, + }, + ], + }, + { + title: 'Job 配置', + fields: [ + { + key: 'restartPolicy', + label: '重启策略', + type: 'select', + options: [ + { label: 'OnFailure (推荐)', value: 'OnFailure' }, + { label: 'Never', value: 'Never' }, + ], + default: 'OnFailure', + }, + { + key: 'backoffLimit', + label: '重试次数上限', + type: 'number', + min: 0, + max: 100, + default: 6, + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sCronJobYaml(config) { + const container = { + name: config.name, + image: config.image, + command: config.command.split(' '), + } + + const cronjob = { + apiVersion: 'batch/v1', + kind: 'CronJob', + metadata: { + name: config.name, + namespace: config.namespace, + }, + spec: { + schedule: config.schedule, + concurrencyPolicy: config.concurrencyPolicy, + successfulJobsHistoryLimit: config.successfulJobsHistoryLimit, + failedJobsHistoryLimit: config.failedJobsHistoryLimit, + suspend: config.suspend || undefined, + jobTemplate: { + spec: { + backoffLimit: config.backoffLimit, + template: { + spec: { + containers: [container], + restartPolicy: config.restartPolicy, + }, + }, + }, + }, + }, + } + + const header = `# K8s CronJob - 由 ConfTemplate 生成\n# 生成时间: ${new Date().toLocaleString('zh-CN')}\n# 部署命令: kubectl apply -f ${config.fileName || 'cronjob.yaml'}\n` + + return header + '\n' + toYaml(cronjob) +} diff --git a/src/schemas/k8s-daemonset.js b/src/schemas/k8s-daemonset.js new file mode 100644 index 0000000..452c2de --- /dev/null +++ b/src/schemas/k8s-daemonset.js @@ -0,0 +1,247 @@ +export const k8sDaemonSetSchema = { + id: 'k8s-daemonset', + name: 'K8s DaemonSet', + icon: 'Box', + category: 'K8S 工作负载', + description: 'Kubernetes 守护进程集 — 在每个节点上运行一个 Pod 副本', + format: 'yaml', + fileName: 'daemonset.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'DaemonSet 名称', + type: 'text', + placeholder: 'my-daemonset', + default: 'my-daemonset', + required: true, + }, + { + key: 'namespace', + label: '命名空间', + type: 'text', + placeholder: 'default', + default: 'default', + }, + { + key: 'appName', + label: '应用名称', + type: 'text', + placeholder: 'my-app', + default: 'my-app', + required: true, + }, + { + key: 'image', + label: '容器镜像', + type: 'text', + placeholder: 'fluentd:latest', + default: 'fluentd:latest', + required: true, + }, + { + key: 'containerPort', + label: '容器端口', + type: 'number', + min: 1, + max: 65535, + default: 8080, + }, + ], + }, + { + title: '资源限制', + fields: [ + { + key: 'cpuRequest', + label: 'CPU Requests', + type: 'select', + options: [ + { label: '50m', value: '50m' }, + { label: '100m', value: '100m' }, + { label: '200m', value: '200m' }, + { label: '500m', value: '500m' }, + { label: '1', value: '1' }, + ], + default: '100m', + }, + { + key: 'cpuLimit', + label: 'CPU Limits', + type: 'select', + options: [ + { label: '200m', value: '200m' }, + { label: '500m', value: '500m' }, + { label: '1', value: '1' }, + { label: '2', value: '2' }, + ], + default: '500m', + }, + { + key: 'memoryRequest', + label: 'Memory Requests', + type: 'select', + options: [ + { label: '64Mi', value: '64Mi' }, + { label: '128Mi', value: '128Mi' }, + { label: '256Mi', value: '256Mi' }, + { label: '512Mi', value: '512Mi' }, + ], + default: '128Mi', + }, + { + key: 'memoryLimit', + label: 'Memory Limits', + type: 'select', + options: [ + { label: '128Mi', value: '128Mi' }, + { label: '256Mi', value: '256Mi' }, + { label: '512Mi', value: '512Mi' }, + { label: '1Gi', value: '1Gi' }, + ], + default: '256Mi', + }, + ], + }, + { + title: '节点调度', + fields: [ + { + key: 'hostNetwork', + label: '使用主机网络', + type: 'switch', + default: false, + tip: 'Pod 将使用宿主机网络命名空间', + }, + { + key: 'tolerationsEnabled', + label: '添加 Toleration', + type: 'switch', + default: false, + tip: '允许在有污点的节点上运行', + }, + { + key: 'tolerationKey', + label: 'Toleration Key', + type: 'text', + placeholder: 'node-role.kubernetes.io/master', + default: '', + dependsOn: { key: 'tolerationsEnabled', value: true }, + }, + { + key: 'tolerationValue', + label: 'Toleration Value', + type: 'text', + placeholder: '', + default: '', + dependsOn: { key: 'tolerationsEnabled', value: true }, + }, + ], + }, + { + title: '更新策略', + fields: [ + { + key: 'updateStrategy', + label: '更新策略', + type: 'select', + options: [ + { label: 'RollingUpdate - 滚动更新 (推荐)', value: 'RollingUpdate' }, + { label: 'OnDelete - 手动删除触发', value: 'OnDelete' }, + ], + default: 'RollingUpdate', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sDaemonSetYaml(config) { + const labels = { app: config.appName } + + const container = { + name: config.appName, + image: config.image, + ports: [{ containerPort: config.containerPort, name: 'http' }], + resources: { + requests: { cpu: config.cpuRequest, memory: config.memoryRequest }, + limits: { cpu: config.cpuLimit, memory: config.memoryLimit }, + }, + } + + const podSpec = { + containers: [container], + } + + if (config.hostNetwork) { + podSpec.hostNetwork = true + } + + if (config.tolerationsEnabled && config.tolerationKey) { + const toleration = { key: config.tolerationKey, operator: 'Exists', effect: 'NoSchedule' } + if (config.tolerationValue) { + toleration.operator = 'Equal' + toleration.value = config.tolerationValue + } + podSpec.tolerations = [toleration] + } + + const daemonset = { + apiVersion: 'apps/v1', + kind: 'DaemonSet', + metadata: { + name: config.name, + namespace: config.namespace, + labels, + }, + spec: { + selector: { matchLabels: { app: config.appName } }, + updateStrategy: config.updateStrategy === 'RollingUpdate' + ? { type: 'RollingUpdate', rollingUpdate: { maxUnavailable: 1 } } + : { type: 'OnDelete' }, + template: { + metadata: { labels }, + spec: podSpec, + }, + }, + } + + const header = `# K8s DaemonSet - 由 ConfTemplate 生成\n# 生成时间: ${new Date().toLocaleString('zh-CN')}\n# 部署命令: kubectl apply -f ${config.fileName || 'daemonset.yaml'}\n` + + return header + '\n' + toYaml(daemonset) +} diff --git a/src/schemas/k8s-ingress.js b/src/schemas/k8s-ingress.js new file mode 100644 index 0000000..da03c17 --- /dev/null +++ b/src/schemas/k8s-ingress.js @@ -0,0 +1,230 @@ +// 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 toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${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) +} diff --git a/src/schemas/k8s-job.js b/src/schemas/k8s-job.js new file mode 100644 index 0000000..434c00c --- /dev/null +++ b/src/schemas/k8s-job.js @@ -0,0 +1,186 @@ +export const k8sJobSchema = { + id: 'k8s-job', + name: 'K8s Job', + icon: 'Box', + category: 'K8S 工作负载', + description: 'Kubernetes Job — 运行一次性批处理任务', + format: 'yaml', + fileName: 'job.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'Job 名称', + type: 'text', + placeholder: 'my-job', + default: 'my-job', + required: true, + }, + { + key: 'namespace', + label: '命名空间', + type: 'text', + placeholder: 'default', + default: 'default', + }, + { + key: 'image', + label: '容器镜像', + type: 'text', + placeholder: 'busybox:latest', + default: 'busybox:latest', + required: true, + }, + { + key: 'command', + label: 'Command', + type: 'text', + placeholder: 'echo hello', + default: 'echo hello', + required: true, + tip: '容器启动命令', + }, + ], + }, + { + title: '执行控制', + fields: [ + { + key: 'backoffLimit', + label: '重试次数上限', + type: 'number', + min: 0, + max: 100, + default: 6, + tip: '失败后最多重试次数', + }, + { + key: 'completions', + label: '完成数量', + type: 'number', + min: 1, + max: 100, + default: 1, + tip: '需要成功完成的 Pod 数', + }, + { + key: 'parallelism', + label: '并行度', + type: 'number', + min: 1, + max: 100, + default: 1, + tip: '同时运行的 Pod 数', + }, + { + key: 'activeDeadlineSeconds', + label: '超时时间 (秒)', + type: 'number', + min: 0, + max: 86400, + default: 0, + tip: '0 表示不限制超时', + }, + ], + }, + { + title: '重启与清理', + fields: [ + { + key: 'restartPolicy', + label: '重启策略', + type: 'select', + options: [ + { label: 'Never (推荐)', value: 'Never' }, + { label: 'OnFailure', value: 'OnFailure' }, + ], + default: 'Never', + }, + { + key: 'ttlSecondsAfterFinished', + label: '自动清理 (秒)', + type: 'number', + min: 0, + max: 86400, + default: 0, + tip: '完成后自动删除 Job,0 表示保留', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sJobYaml(config) { + const container = { + name: config.name, + image: config.image, + command: config.command.split(' '), + } + + const job = { + apiVersion: 'batch/v1', + kind: 'Job', + metadata: { + name: config.name, + namespace: config.namespace, + }, + spec: { + backoffLimit: config.backoffLimit, + completions: config.completions, + parallelism: config.parallelism, + template: { + spec: { + containers: [container], + restartPolicy: config.restartPolicy, + }, + }, + }, + } + + if (config.activeDeadlineSeconds > 0) { + job.spec.activeDeadlineSeconds = config.activeDeadlineSeconds + } + + if (config.ttlSecondsAfterFinished > 0) { + job.spec.ttlSecondsAfterFinished = config.ttlSecondsAfterFinished + } + + const header = `# K8s Job - 由 ConfTemplate 生成\n# 生成时间: ${new Date().toLocaleString('zh-CN')}\n# 部署命令: kubectl apply -f ${config.fileName || 'job.yaml'}\n` + + return header + '\n' + toYaml(job) +} diff --git a/src/schemas/k8s-namespace.js b/src/schemas/k8s-namespace.js new file mode 100644 index 0000000..2c27613 --- /dev/null +++ b/src/schemas/k8s-namespace.js @@ -0,0 +1,103 @@ +// K8s Namespace Schema - 由 ConfTemplate 生成 +// 生成时间: 自动生成 + +export const k8sNamespaceSchema = { + id: 'k8s-namespace', + name: 'K8s Namespace', + icon: 'Layers', + category: '云原生', + description: 'Kubernetes 命名空间,用于资源隔离和组织', + format: 'yaml', + fileName: 'namespace.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'Namespace 名称', + type: 'text', + placeholder: 'my-namespace', + default: '', + required: true, + tip: '命名空间名称,必须符合 DNS 命名规范', + }, + ], + }, + { + title: '标签配置', + fields: [ + { + key: 'labelKey', + label: '标签键', + type: 'text', + placeholder: 'app', + default: 'app', + tip: '为命名空间添加标签键', + }, + { + key: 'labelValue', + label: '标签值', + type: 'text', + placeholder: 'my-project', + default: 'my-project', + tip: '标签值', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sNamespaceYaml(config) { + const ns = { + apiVersion: 'v1', + kind: 'Namespace', + metadata: { + name: config.name, + }, + } + + if (config.labelKey && config.labelValue) { + ns.metadata.labels = { [config.labelKey]: config.labelValue } + } + + const header = `# K8s Namespace - 由 ConfTemplate 生成 +# 生成时间: ${new Date().toLocaleString('zh-CN')} +# 部署命令: kubectl apply -f ${config.fileName || 'namespace.yaml'} +` + + return header + '\n' + toYaml(ns) +} diff --git a/src/schemas/k8s-networkpolicy.js b/src/schemas/k8s-networkpolicy.js new file mode 100644 index 0000000..3ae69a0 --- /dev/null +++ b/src/schemas/k8s-networkpolicy.js @@ -0,0 +1,229 @@ +// K8s NetworkPolicy Schema - 由 ConfTemplate 生成 +// 生成时间: 自动生成 + +export const k8sNetworkPolicySchema = { + id: 'k8s-networkpolicy', + name: 'K8s NetworkPolicy', + icon: 'ShieldAlert', + category: '云原生', + description: 'Kubernetes 网络策略,控制 Pod 的入站和出站流量', + format: 'yaml', + fileName: 'networkpolicy.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'NetworkPolicy 名称', + type: 'text', + placeholder: 'my-network-policy', + default: 'my-network-policy', + required: true, + tip: '将用作 NetworkPolicy 的 metadata.name', + }, + { + key: 'namespace', + label: '命名空间', + type: 'text', + placeholder: 'default', + default: 'default', + }, + ], + }, + { + title: 'Pod 选择器', + fields: [ + { + key: 'podSelectorLabelKey', + label: '选择器标签键', + type: 'text', + placeholder: 'app', + default: 'app', + tip: '用于匹配 Pod 的标签键', + }, + { + key: 'podSelectorLabelValue', + label: '选择器标签值', + type: 'text', + placeholder: 'myapp', + default: 'myapp', + tip: '用于匹配 Pod 的标签值', + }, + ], + }, + { + title: '策略类型', + fields: [ + { + key: 'policyTypes', + label: '策略类型', + type: 'select', + options: [ + { label: '仅入站 (Ingress)', value: 'Ingress' }, + { label: '仅出站 (Egress)', value: 'Egress' }, + { label: '双向 (Ingress + Egress)', value: 'Both' }, + ], + default: 'Both', + tip: '控制入站、出站或双向流量', + }, + ], + }, + { + title: '入站规则 (Ingress)', + fields: [ + { + key: 'ingressFrom', + label: '允许来源', + type: 'text', + placeholder: '10.0.0.0/8 或 namespace label', + default: '', + tip: 'IP CIDR 或命名空间标签,留空则允许所有来源', + }, + { + key: 'ingressPorts', + label: '允许端口', + type: 'text', + placeholder: '80,443', + default: '', + tip: '逗号分隔的端口号列表', + }, + ], + }, + { + title: '出站规则 (Egress)', + fields: [ + { + key: 'egressTo', + label: '允许目标', + type: 'text', + placeholder: '10.0.0.0/8 或 namespace label', + default: '', + tip: 'IP CIDR 或命名空间标签,留空则允许所有目标', + }, + { + key: 'egressPorts', + label: '允许端口', + type: 'text', + placeholder: '53,443', + default: '', + tip: '逗号分隔的端口号列表', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sNetworkPolicyYaml(config) { + const labelKey = config.podSelectorLabelKey || 'app' + const labelValue = config.podSelectorLabelValue || 'myapp' + + const np = { + apiVersion: 'networking.k8s.io/v1', + kind: 'NetworkPolicy', + metadata: { + name: config.name, + namespace: config.namespace, + }, + spec: { + podSelector: { + matchLabels: { [labelKey]: labelValue }, + }, + policyTypes: [], + }, + } + + // Determine policy types + if (config.policyTypes === 'Both') { + np.spec.policyTypes = ['Ingress', 'Egress'] + } else { + np.spec.policyTypes = [config.policyTypes] + } + + // Ingress rules + if (np.spec.policyTypes.includes('Ingress')) { + const ingressRule = {} + if (config.ingressFrom) { + const from = config.ingressFrom.split(',').map(f => f.trim()).filter(Boolean) + ingressRule.from = from.map(f => { + if (f.includes('/')) { + return { ipBlock: { cidr: f } } + } + return { namespaceSelector: { matchLabels: { name: f } } } + }) + } + if (config.ingressPorts) { + const ports = config.ingressPorts.split(',').map(p => parseInt(p.trim())).filter(p => !isNaN(p)) + if (ports.length > 0) { + ingressRule.ports = ports.map(p => ({ port: p, protocol: 'TCP' })) + } + } + if (Object.keys(ingressRule).length > 0) { + np.spec.ingress = [ingressRule] + } + } + + // Egress rules + if (np.spec.policyTypes.includes('Egress')) { + const egressRule = {} + if (config.egressTo) { + const to = config.egressTo.split(',').map(t => t.trim()).filter(Boolean) + egressRule.to = to.map(t => { + if (t.includes('/')) { + return { ipBlock: { cidr: t } } + } + return { namespaceSelector: { matchLabels: { name: t } } } + }) + } + if (config.egressPorts) { + const ports = config.egressPorts.split(',').map(p => parseInt(p.trim())).filter(p => !isNaN(p)) + if (ports.length > 0) { + egressRule.ports = ports.map(p => ({ port: p, protocol: 'TCP' })) + } + } + if (Object.keys(egressRule).length > 0) { + np.spec.egress = [egressRule] + } + } + + const header = `# K8s NetworkPolicy - 由 ConfTemplate 生成 +# 生成时间: ${new Date().toLocaleString('zh-CN')} +# 部署命令: kubectl apply -f ${config.fileName || 'networkpolicy.yaml'} +# 注意: 需要 CNI 插件支持 NetworkPolicy (如 Calico, Cilium) +` + + return header + '\n' + toYaml(np) +} diff --git a/src/schemas/k8s-pod.js b/src/schemas/k8s-pod.js new file mode 100644 index 0000000..7e47592 --- /dev/null +++ b/src/schemas/k8s-pod.js @@ -0,0 +1,304 @@ +export const k8sPodSchema = { + id: 'k8s-pod', + name: 'K8s Pod', + icon: 'Box', + category: 'K8S 工作负载', + description: 'Kubernetes Pod — 最小可部署单元', + format: 'yaml', + fileName: 'pod.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'Pod 名称', + type: 'text', + placeholder: 'my-pod', + default: 'my-pod', + required: true, + }, + { + key: 'namespace', + label: '命名空间', + type: 'text', + placeholder: 'default', + default: 'default', + }, + { + key: 'containerName', + label: '容器名称', + type: 'text', + placeholder: 'main', + default: 'main', + required: true, + }, + { + key: 'image', + label: '容器镜像', + type: 'text', + placeholder: 'nginx:1.24', + default: 'nginx:1.24', + required: true, + }, + { + key: 'imagePullPolicy', + label: '镜像拉取策略', + type: 'select', + options: [ + { label: 'IfNotPresent (推荐)', value: 'IfNotPresent' }, + { label: 'Always', value: 'Always' }, + { label: 'Never', value: 'Never' }, + ], + default: 'IfNotPresent', + }, + ], + }, + { + title: '容器端口', + fields: [ + { + key: 'containerPort', + label: '容器端口', + type: 'number', + min: 1, + max: 65535, + default: 80, + }, + { + key: 'protocol', + label: '协议', + type: 'select', + options: [ + { label: 'TCP', value: 'TCP' }, + { label: 'UDP', value: 'UDP' }, + ], + default: 'TCP', + }, + ], + }, + { + title: '启动命令 (可选)', + fields: [ + { + key: 'command', + label: 'Command', + type: 'text', + placeholder: '/bin/sh', + default: '', + tip: '容器 ENTRYPOINT,留空则使用镜像默认值', + }, + { + key: 'args', + label: 'Args', + type: 'text', + placeholder: '-c, echo hello', + default: '', + tip: '逗号分隔多个参数', + }, + ], + }, + { + title: '环境变量 (可选)', + fields: [ + { + key: 'envVars', + label: '环境变量', + type: 'keyvalue', + default: [], + tip: '键值对,将注入为容器环境变量', + }, + ], + }, + { + title: '资源限制', + fields: [ + { + key: 'cpuRequest', + label: 'CPU Requests', + type: 'select', + options: [ + { label: '50m', value: '50m' }, + { label: '100m', value: '100m' }, + { label: '200m', value: '200m' }, + { label: '500m', value: '500m' }, + { label: '1', value: '1' }, + { label: '2', value: '2' }, + ], + default: '100m', + }, + { + key: 'cpuLimit', + label: 'CPU Limits', + type: 'select', + options: [ + { label: '200m', value: '200m' }, + { label: '500m', value: '500m' }, + { label: '1', value: '1' }, + { label: '2', value: '2' }, + { label: '4', value: '4' }, + ], + default: '500m', + }, + { + key: 'memoryRequest', + label: 'Memory Requests', + type: 'select', + options: [ + { label: '64Mi', value: '64Mi' }, + { label: '128Mi', value: '128Mi' }, + { label: '256Mi', value: '256Mi' }, + { label: '512Mi', value: '512Mi' }, + { label: '1Gi', value: '1Gi' }, + ], + default: '128Mi', + }, + { + key: 'memoryLimit', + label: 'Memory Limits', + type: 'select', + options: [ + { label: '128Mi', value: '128Mi' }, + { label: '256Mi', value: '256Mi' }, + { label: '512Mi', value: '512Mi' }, + { label: '1Gi', value: '1Gi' }, + { label: '2Gi', value: '2Gi' }, + ], + default: '256Mi', + }, + ], + }, + { + title: '重启与调度', + fields: [ + { + key: 'restartPolicy', + label: '重启策略', + type: 'select', + options: [ + { label: 'Always (默认)', value: 'Always' }, + { label: 'OnFailure', value: 'OnFailure' }, + { label: 'Never', value: 'Never' }, + ], + default: 'Always', + }, + { + key: 'nodeName', + label: '指定节点', + type: 'text', + placeholder: 'node-1', + default: '', + tip: '留空则由调度器自动分配', + }, + ], + }, + { + title: '标签', + fields: [ + { + key: 'appLabel', + label: 'app 标签值', + type: 'text', + default: '', + tip: '留空则使用 Pod 名称', + }, + { + key: 'versionLabel', + label: 'version 标签', + type: 'text', + placeholder: 'v1', + default: 'v1', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sPodYaml(config) { + const labels = { + app: config.appLabel || config.name, + } + if (config.versionLabel) { + labels.version = config.versionLabel + } + + const container = { + name: config.containerName, + image: config.image, + imagePullPolicy: config.imagePullPolicy, + ports: [{ + containerPort: config.containerPort, + protocol: config.protocol, + }], + resources: { + requests: { cpu: config.cpuRequest, memory: config.memoryRequest }, + limits: { cpu: config.cpuLimit, memory: config.memoryLimit }, + }, + } + + if (config.command) { + container.command = config.command.split(',').map(s => s.trim()) + } + if (config.args) { + container.args = config.args.split(',').map(s => s.trim()) + } + if (config.envVars && config.envVars.length > 0) { + container.env = config.envVars.map(e => ({ name: e.key, value: e.value })) + } + + const podSpec = { + containers: [container], + restartPolicy: config.restartPolicy, + } + if (config.nodeName) { + podSpec.nodeName = config.nodeName + } + + const pod = { + apiVersion: 'v1', + kind: 'Pod', + metadata: { + name: config.name, + namespace: config.namespace, + labels, + }, + spec: podSpec, + } + + const header = `# K8s Pod - 由 ConfTemplate 生成\n# 生成时间: ${new Date().toLocaleString('zh-CN')}\n# 部署命令: kubectl apply -f ${config.fileName || 'pod.yaml'}\n` + + return header + '\n' + toYaml(pod) +} diff --git a/src/schemas/k8s-pv.js b/src/schemas/k8s-pv.js new file mode 100644 index 0000000..3fb5823 --- /dev/null +++ b/src/schemas/k8s-pv.js @@ -0,0 +1,198 @@ +// K8s PersistentVolume schema + YAML generator for ConfTemplate +// Generated: 2026-07-18 + +export const k8sPvSchema = { + id: 'k8s-pv', + name: 'K8s PersistentVolume', + icon: 'Database', + category: '云原生', + description: 'Kubernetes 持久化存储卷 (PV)', + format: 'yaml', + fileName: 'pv.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'PV 名称', + type: 'text', + placeholder: 'my-pv', + default: 'my-pv', + required: true, + tip: 'PersistentVolume 资源名称', + }, + { + key: 'capacity', + label: '存储容量', + type: 'text', + placeholder: '10Gi', + default: '10Gi', + required: true, + tip: '例如 10Gi, 100Mi', + }, + { + 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: 'persistentVolumeReclaimPolicy', + label: '回收策略', + type: 'select', + options: [ + { label: 'Retain', value: 'Retain' }, + { label: 'Recycle', value: 'Recycle' }, + { label: 'Delete', value: 'Delete' }, + ], + default: 'Retain', + tip: 'PV 释放后的处理方式', + }, + { + key: 'volumeMode', + label: '卷模式', + type: 'select', + options: [ + { label: 'Filesystem', value: 'Filesystem' }, + { label: 'Block', value: 'Block' }, + ], + default: 'Filesystem', + }, + ], + }, + { + title: 'HostPath 配置', + fields: [ + { + key: 'hostPathEnabled', + label: '启用 HostPath', + type: 'switch', + default: false, + tip: '使用节点本地路径作为存储', + }, + { + key: 'hostPath', + label: 'HostPath 路径', + type: 'text', + placeholder: '/tmp/pv', + default: '/tmp/pv', + dependsOn: 'hostPathEnabled', + }, + ], + }, + { + title: 'NFS 配置', + fields: [ + { + key: 'nfsEnabled', + label: '启用 NFS', + type: 'switch', + default: false, + tip: '使用 NFS 共享存储', + }, + { + key: 'nfsServer', + label: 'NFS 服务器地址', + type: 'text', + placeholder: '192.168.1.100', + default: '', + dependsOn: 'nfsEnabled', + }, + { + key: 'nfsPath', + label: 'NFS 路径', + type: 'text', + placeholder: '/exports/data', + default: '', + dependsOn: 'nfsEnabled', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sPvYaml(config) { + const spec = { + capacity: { + storage: config.capacity, + }, + accessModes: [config.accessModes], + persistentVolumeReclaimPolicy: config.persistentVolumeReclaimPolicy, + storageClassName: config.storageClassName, + volumeMode: config.volumeMode, + } + + if (config.hostPathEnabled) { + spec.hostPath = { + path: config.hostPath, + } + } else if (config.nfsEnabled) { + spec.nfs = { + server: config.nfsServer, + path: config.nfsPath, + } + } + + const pv = { + apiVersion: 'v1', + kind: 'PersistentVolume', + metadata: { + name: config.name, + }, + spec, + } + + const header = `# K8s PersistentVolume - 由 ConfTemplate 生成 +# 生成时间: ${new Date().toLocaleString('zh-CN')} +# 部署命令: kubectl apply -f ${config.fileName || 'pv.yaml'} +` + + return header + '\n' + toYaml(pv) +} diff --git a/src/schemas/k8s-pvc.js b/src/schemas/k8s-pvc.js new file mode 100644 index 0000000..b7f6363 --- /dev/null +++ b/src/schemas/k8s-pvc.js @@ -0,0 +1,134 @@ +// 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 toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${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) +} diff --git a/src/schemas/k8s-role.js b/src/schemas/k8s-role.js new file mode 100644 index 0000000..c975bc8 --- /dev/null +++ b/src/schemas/k8s-role.js @@ -0,0 +1,146 @@ +// K8s Role Schema - 由 ConfTemplate 生成 +// 生成时间: 自动生成 + +export const k8sRoleSchema = { + id: 'k8s-role', + name: 'K8s Role', + icon: 'Shield', + category: '云原生', + description: 'Kubernetes 命名空间级 RBAC 角色', + format: 'yaml', + fileName: 'role.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'Role 名称', + type: 'text', + placeholder: 'my-role', + default: 'my-role', + required: true, + tip: '将用作 Role 的 metadata.name', + }, + { + key: 'namespace', + label: '命名空间', + type: 'text', + placeholder: 'default', + default: 'default', + }, + ], + }, + { + title: '权限规则', + fields: [ + { + key: 'resources', + label: '资源类型', + type: 'select', + multiple: true, + options: [ + { label: 'Pods', value: 'pods' }, + { label: 'Services', value: 'services' }, + { label: 'ConfigMaps', value: 'configmaps' }, + { label: 'Deployments', value: 'deployments' }, + { label: 'Secrets', value: 'secrets' }, + { label: 'All (*)', value: '*' }, + ], + default: ['pods'], + tip: '可多选,选择要授权的资源类型', + }, + { + key: 'verbs', + label: '操作动词', + type: 'select', + multiple: true, + options: [ + { label: 'Get (读取)', value: 'get' }, + { label: 'List (列表)', value: 'list' }, + { label: 'Watch (监听)', value: 'watch' }, + { label: 'Create (创建)', value: 'create' }, + { label: 'Update (更新)', value: 'update' }, + { label: 'Delete (删除)', value: 'delete' }, + { label: 'Patch (补丁)', value: 'patch' }, + { label: 'All (*)', value: '*' }, + ], + default: ['get', 'list', 'watch'], + tip: '可多选,选择允许的操作', + }, + { + key: 'apiGroups', + label: 'API 组', + type: 'text', + placeholder: '"" (核心组留空)', + default: '', + tip: 'API 组,核心资源(如 pods, services)留空', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sRoleYaml(config) { + const resources = Array.isArray(config.resources) ? config.resources : [config.resources || 'pods'] + const verbs = Array.isArray(config.verbs) ? config.verbs : [config.verbs || 'get'] + const apiGroups = config.apiGroups !== undefined && config.apiGroups !== '' + ? config.apiGroups.split(',').map(g => g.trim()) + : [''] + + const rule = { + apiGroups, + resources, + verbs, + } + + const role = { + apiVersion: 'rbac.authorization.k8s.io/v1', + kind: 'Role', + metadata: { + name: config.name, + namespace: config.namespace, + }, + rules: [rule], + } + + const header = `# K8s Role - 由 ConfTemplate 生成 +# 生成时间: ${new Date().toLocaleString('zh-CN')} +# 部署命令: kubectl apply -f ${config.fileName || 'role.yaml'} +` + + return header + '\n' + toYaml(role) +} diff --git a/src/schemas/k8s-rolebinding.js b/src/schemas/k8s-rolebinding.js new file mode 100644 index 0000000..f58021e --- /dev/null +++ b/src/schemas/k8s-rolebinding.js @@ -0,0 +1,154 @@ +// K8s RoleBinding Schema - 由 ConfTemplate 生成 +// 生成时间: 自动生成 + +export const k8sRoleBindingSchema = { + id: 'k8s-rolebinding', + name: 'K8s RoleBinding', + icon: 'Link', + category: '云原生', + description: 'Kubernetes 命名空间级角色绑定,将 Role 授予用户或服务账户', + format: 'yaml', + fileName: 'rolebinding.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'RoleBinding 名称', + type: 'text', + placeholder: 'my-rolebinding', + default: 'my-rolebinding', + required: true, + tip: '将用作 RoleBinding 的 metadata.name', + }, + { + key: 'namespace', + label: '命名空间', + type: 'text', + placeholder: 'default', + default: 'default', + }, + ], + }, + { + title: '角色引用', + fields: [ + { + key: 'roleName', + label: 'Role 名称', + type: 'text', + placeholder: 'my-role', + default: '', + required: true, + tip: '要绑定的 Role 名称', + }, + ], + }, + { + title: '主体配置', + fields: [ + { + key: 'subjectKind', + label: '主体类型', + type: 'select', + options: [ + { label: 'User (用户)', value: 'User' }, + { label: 'Group (用户组)', value: 'Group' }, + { label: 'ServiceAccount (服务账户)', value: 'ServiceAccount' }, + ], + default: 'ServiceAccount', + tip: '要授权的主体类型', + }, + { + key: 'subjectName', + label: '主体名称', + type: 'text', + placeholder: 'my-serviceaccount', + default: '', + required: true, + tip: '用户、组或 ServiceAccount 的名称', + }, + { + key: 'subjectNamespace', + label: '主体命名空间', + type: 'text', + placeholder: '仅 ServiceAccount 需要', + default: '', + tip: '仅当主体类型为 ServiceAccount 时需要填写', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sRoleBindingYaml(config) { + const subject = { + kind: config.subjectKind || 'ServiceAccount', + name: config.subjectName, + } + if (config.subjectNamespace && config.subjectKind === 'ServiceAccount') { + subject.namespace = config.subjectNamespace + } + if (config.subjectKind === 'User') { + subject.apiGroup = 'rbac.authorization.k8s.io' + } + if (config.subjectKind === 'Group') { + subject.apiGroup = 'rbac.authorization.k8s.io' + } + + const roleBinding = { + apiVersion: 'rbac.authorization.k8s.io/v1', + kind: 'RoleBinding', + metadata: { + name: config.name, + namespace: config.namespace, + }, + roleRef: { + apiGroup: 'rbac.authorization.k8s.io', + kind: 'Role', + name: config.roleName, + }, + subjects: [subject], + } + + const header = `# K8s RoleBinding - 由 ConfTemplate 生成 +# 生成时间: ${new Date().toLocaleString('zh-CN')} +# 部署命令: kubectl apply -f ${config.fileName || 'rolebinding.yaml'} +` + + return header + '\n' + toYaml(roleBinding) +} diff --git a/src/schemas/k8s-secret.js b/src/schemas/k8s-secret.js new file mode 100644 index 0000000..ae25a84 --- /dev/null +++ b/src/schemas/k8s-secret.js @@ -0,0 +1,166 @@ +// K8s Secret Schema - 由 ConfTemplate 生成 +// 生成时间: 自动生成 + +export const k8sSecretSchema = { + id: 'k8s-secret', + name: 'K8s Secret', + icon: 'Lock', + category: '云原生', + description: 'Kubernetes Secret,存储敏感数据如密码、证书等', + format: 'yaml', + fileName: 'secret.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'Secret 名称', + type: 'text', + placeholder: 'my-secret', + default: 'my-secret', + required: true, + tip: '将用作 Secret 的 metadata.name', + }, + { + key: 'namespace', + label: '命名空间', + type: 'text', + placeholder: 'default', + default: 'default', + }, + { + key: 'secretType', + label: 'Secret 类型', + type: 'select', + options: [ + { label: 'Opaque (通用)', value: 'Opaque' }, + { label: 'Docker Registry (dockerconfigjson)', value: 'kubernetes.io/dockerconfigjson' }, + { label: 'TLS 证书', value: 'kubernetes.io/tls' }, + { label: 'Basic Auth (基础认证)', value: 'kubernetes.io/basic-auth' }, + ], + default: 'Opaque', + tip: '不同类型对应不同的数据结构', + }, + ], + }, + { + title: '数据配置', + fields: [ + { + key: 'dataKey', + label: '数据键名', + type: 'text', + placeholder: 'username', + default: 'username', + required: true, + tip: 'Secret 中的键名', + }, + { + key: 'dataValue', + label: '数据值', + type: 'text', + placeholder: 'base64 encoded or plain text', + default: '', + required: true, + tip: '如果开启 Base64 编码,输入明文即可', + }, + { + key: 'encodeBase64', + label: 'Base64 编码', + type: 'switch', + default: true, + tip: '自动将数据值进行 Base64 编码', + }, + ], + }, + { + title: '挂载配置', + fields: [ + { + key: 'mountAsVolume', + label: '作为 Volume 挂载', + type: 'switch', + default: true, + tip: '是否将 Secret 挂载为容器内的文件', + }, + { + key: 'mountPath', + label: '挂载路径', + type: 'text', + placeholder: '/etc/secrets', + default: '/etc/secrets', + dependsOn: { key: 'mountAsVolume', value: true }, + tip: '容器内挂载目录路径', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sSecretYaml(config) { + const data = {} + let value = config.dataValue || '' + + if (config.encodeBase64) { + try { + value = btoa(unescape(encodeURIComponent(value))) + } catch { + // fallback: use value as-is + } + } + + data[config.dataKey || 'data'] = value + + const secret = { + apiVersion: 'v1', + kind: 'Secret', + metadata: { + name: config.name, + namespace: config.namespace, + }, + type: config.secretType || 'Opaque', + data, + } + + const header = `# K8s Secret - 由 ConfTemplate 生成 +# 生成时间: ${new Date().toLocaleString('zh-CN')} +# 部署命令: kubectl apply -f ${config.fileName || 'secret.yaml'} +# 注意: 生产环境建议使用 kubectl create secret 命令或 Sealed Secrets +` + + return header + '\n' + toYaml(secret) +} diff --git a/src/schemas/k8s-serviceaccount.js b/src/schemas/k8s-serviceaccount.js new file mode 100644 index 0000000..ceee96f --- /dev/null +++ b/src/schemas/k8s-serviceaccount.js @@ -0,0 +1,122 @@ +// K8s ServiceAccount Schema - 由 ConfTemplate 生成 +// 生成时间: 自动生成 + +export const k8sServiceAccountSchema = { + id: 'k8s-serviceaccount', + name: 'K8s ServiceAccount', + icon: 'User', + category: '云原生', + description: 'Kubernetes 服务账户,用于 Pod 的身份认证', + format: 'yaml', + fileName: 'serviceaccount.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'ServiceAccount 名称', + type: 'text', + placeholder: 'my-serviceaccount', + default: 'my-serviceaccount', + required: true, + tip: '将用作 ServiceAccount 的 metadata.name', + }, + { + key: 'namespace', + label: '命名空间', + type: 'text', + placeholder: 'default', + default: 'default', + }, + ], + }, + { + title: '账户配置', + fields: [ + { + key: 'automountServiceAccountToken', + label: '自动挂载 Token', + type: 'switch', + default: true, + tip: '自动将 API Token 挂载到使用此 ServiceAccount 的 Pod', + }, + { + key: 'enableImagePullSecrets', + label: '配置镜像拉取 Secret', + type: 'switch', + default: false, + tip: '为此 ServiceAccount 配置私有镜像仓库认证', + }, + { + key: 'imagePullSecretName', + label: '镜像拉取 Secret 名称', + type: 'text', + placeholder: 'my-registry-secret', + default: '', + dependsOn: { key: 'enableImagePullSecrets', value: true }, + tip: '已存在的 docker-registry Secret 名称', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sServiceAccountYaml(config) { + const sa = { + apiVersion: 'v1', + kind: 'ServiceAccount', + metadata: { + name: config.name, + namespace: config.namespace, + }, + } + + if (config.automountServiceAccountToken === false) { + sa.automountServiceAccountToken = false + } + + if (config.enableImagePullSecrets && config.imagePullSecretName) { + sa.imagePullSecrets = [{ name: config.imagePullSecretName }] + } + + const header = `# K8s ServiceAccount - 由 ConfTemplate 生成 +# 生成时间: ${new Date().toLocaleString('zh-CN')} +# 部署命令: kubectl apply -f ${config.fileName || 'serviceaccount.yaml'} +` + + return header + '\n' + toYaml(sa) +} diff --git a/src/schemas/k8s-statefulset.js b/src/schemas/k8s-statefulset.js new file mode 100644 index 0000000..b9d5d7d --- /dev/null +++ b/src/schemas/k8s-statefulset.js @@ -0,0 +1,297 @@ +export const k8sStatefulSetSchema = { + id: 'k8s-statefulset', + name: 'K8s StatefulSet', + icon: 'Box', + category: 'K8S 工作负载', + description: 'Kubernetes 有状态应用部署控制器,支持稳定网络标识和持久存储', + format: 'yaml', + fileName: 'statefulset.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'StatefulSet 名称', + type: 'text', + placeholder: 'my-statefulset', + default: 'my-statefulset', + required: true, + }, + { + key: 'namespace', + label: '命名空间', + type: 'text', + placeholder: 'default', + default: 'default', + }, + { + key: 'appName', + label: '应用名称', + type: 'text', + placeholder: 'my-app', + default: 'my-app', + required: true, + tip: '用作 selector 和 label 的 app 值', + }, + { + key: 'image', + label: '容器镜像', + type: 'text', + placeholder: 'nginx:1.24', + default: 'nginx:1.24', + required: true, + }, + { + key: 'replicas', + label: '副本数', + type: 'number', + min: 1, + max: 100, + default: 3, + }, + ], + }, + { + title: '网络与服务', + fields: [ + { + key: 'containerPort', + label: '容器端口', + type: 'number', + min: 1, + max: 65535, + default: 80, + }, + { + key: 'serviceName', + label: 'Headless Service 名称', + type: 'text', + placeholder: 'my-service', + default: 'my-service', + required: true, + tip: '必须已存在的 Headless Service', + }, + ], + }, + { + title: '持久存储', + fields: [ + { + key: 'volumeClaimEnabled', + label: '启用 PVC', + type: 'switch', + default: true, + tip: '为每个 Pod 创建独立的 PersistentVolumeClaim', + }, + { + key: 'storageClassName', + label: 'StorageClass', + type: 'text', + placeholder: 'standard', + default: 'standard', + dependsOn: { key: 'volumeClaimEnabled', value: true }, + }, + { + key: 'storageSize', + label: '存储大小', + type: 'text', + placeholder: '10Gi', + default: '10Gi', + dependsOn: { key: 'volumeClaimEnabled', value: true }, + }, + { + key: 'pvcAccessMode', + label: '访问模式', + type: 'select', + options: [ + { label: 'ReadWriteOnce', value: 'ReadWriteOnce' }, + { label: 'ReadWriteMany', value: 'ReadWriteMany' }, + { label: 'ReadOnlyMany', value: 'ReadOnlyMany' }, + ], + default: 'ReadWriteOnce', + dependsOn: { key: 'volumeClaimEnabled', value: true }, + }, + ], + }, + { + title: '资源限制', + fields: [ + { + key: 'cpuRequest', + label: 'CPU Requests', + type: 'select', + options: [ + { label: '50m', value: '50m' }, + { label: '100m', value: '100m' }, + { label: '200m', value: '200m' }, + { label: '500m', value: '500m' }, + { label: '1', value: '1' }, + { label: '2', value: '2' }, + ], + default: '100m', + }, + { + key: 'cpuLimit', + label: 'CPU Limits', + type: 'select', + options: [ + { label: '200m', value: '200m' }, + { label: '500m', value: '500m' }, + { label: '1', value: '1' }, + { label: '2', value: '2' }, + { label: '4', value: '4' }, + ], + default: '500m', + }, + { + key: 'memoryRequest', + label: 'Memory Requests', + type: 'select', + options: [ + { label: '64Mi', value: '64Mi' }, + { label: '128Mi', value: '128Mi' }, + { label: '256Mi', value: '256Mi' }, + { label: '512Mi', value: '512Mi' }, + { label: '1Gi', value: '1Gi' }, + ], + default: '128Mi', + }, + { + key: 'memoryLimit', + label: 'Memory Limits', + type: 'select', + options: [ + { label: '128Mi', value: '128Mi' }, + { label: '256Mi', value: '256Mi' }, + { label: '512Mi', value: '512Mi' }, + { label: '1Gi', value: '1Gi' }, + { label: '2Gi', value: '2Gi' }, + ], + default: '256Mi', + }, + ], + }, + { + title: '更新策略', + fields: [ + { + key: 'updateStrategy', + label: '更新策略', + type: 'select', + options: [ + { label: 'RollingUpdate - 滚动更新 (推荐)', value: 'RollingUpdate' }, + { label: 'OnDelete - 手动删除触发', value: 'OnDelete' }, + ], + default: 'RollingUpdate', + }, + { + key: 'partition', + label: 'Partition', + type: 'number', + min: 0, + max: 100, + default: 0, + tip: '仅更新序号 >= partition 的 Pod', + dependsOn: { key: 'updateStrategy', value: 'RollingUpdate' }, + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sStatefulSetYaml(config) { + const labels = { app: config.appName } + + const container = { + name: config.appName, + image: config.image, + ports: [{ containerPort: config.containerPort, name: 'http' }], + resources: { + requests: { cpu: config.cpuRequest, memory: config.memoryRequest }, + limits: { cpu: config.cpuLimit, memory: config.memoryLimit }, + }, + } + + const volumeMounts = [] + const volumeClaimTemplates = [] + + if (config.volumeClaimEnabled) { + volumeMounts.push({ name: 'data', mountPath: '/data' }) + volumeClaimTemplates.push({ + metadata: { name: 'data' }, + spec: { + accessModes: [config.pvcAccessMode], + storageClassName: config.storageClassName, + resources: { requests: { storage: config.storageSize } }, + }, + }) + } + + if (volumeMounts.length > 0) { + container.volumeMounts = volumeMounts + } + + const statefulset = { + apiVersion: 'apps/v1', + kind: 'StatefulSet', + metadata: { + name: config.name, + namespace: config.namespace, + labels, + }, + spec: { + replicas: config.replicas, + serviceName: config.serviceName, + selector: { matchLabels: { app: config.appName } }, + updateStrategy: config.updateStrategy === 'RollingUpdate' + ? { type: 'RollingUpdate', rollingUpdate: { partition: config.partition } } + : { type: 'OnDelete' }, + template: { + metadata: { labels }, + spec: { containers: [container] }, + }, + }, + } + + if (volumeClaimTemplates.length > 0) { + statefulset.spec.volumeClaimTemplates = volumeClaimTemplates + } + + const header = `# K8s StatefulSet - 由 ConfTemplate 生成\n# 生成时间: ${new Date().toLocaleString('zh-CN')}\n# 部署命令: kubectl apply -f ${config.fileName || 'statefulset.yaml'}\n` + + return header + '\n' + toYaml(statefulset) +} diff --git a/src/schemas/k8s-storageclass.js b/src/schemas/k8s-storageclass.js new file mode 100644 index 0000000..10c3e84 --- /dev/null +++ b/src/schemas/k8s-storageclass.js @@ -0,0 +1,170 @@ +// K8s StorageClass schema + YAML generator for ConfTemplate +// Generated: 2026-07-18 + +export const k8sStorageClassSchema = { + id: 'k8s-storageclass', + name: 'K8s StorageClass', + icon: 'Layers', + category: '云原生', + description: 'Kubernetes 存储类 (StorageClass)', + format: 'yaml', + fileName: 'storageclass.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'name', + label: 'StorageClass 名称', + type: 'text', + placeholder: 'my-storageclass', + default: 'my-storageclass', + required: true, + tip: 'StorageClass 资源名称', + }, + { + key: 'provisioner', + label: 'Provisioner', + type: 'select', + options: [ + { label: 'kubernetes.io/aws-ebs', value: 'kubernetes.io/aws-ebs' }, + { label: 'kubernetes.io/gce-pd', value: 'kubernetes.io/gce-pd' }, + { label: 'kubernetes.io/no-provisioner', value: 'kubernetes.io/no-provisioner' }, + { label: 'rancher.io/local-path', value: 'rancher.io/local-path' }, + ], + default: 'kubernetes.io/aws-ebs', + required: true, + tip: '存储卷供应器', + }, + { + key: 'reclaimPolicy', + label: '回收策略', + type: 'select', + options: [ + { label: 'Delete', value: 'Delete' }, + { label: 'Retain', value: 'Retain' }, + ], + default: 'Delete', + }, + { + key: 'volumeBindingMode', + label: '卷绑定模式', + type: 'select', + options: [ + { label: 'Immediate', value: 'Immediate' }, + { label: 'WaitForFirstConsumer', value: 'WaitForFirstConsumer' }, + ], + default: 'Immediate', + tip: 'Immediate 立即绑定,WaitForFirstConsumer 延迟到 Pod 调度时', + }, + { + key: 'allowVolumeExpansion', + label: '允许卷扩展', + type: 'switch', + default: true, + tip: '允许 PVC 扩容', + }, + ], + }, + { + title: 'Provisioner 参数', + fields: [ + { + key: 'paramType', + label: '卷类型', + type: 'select', + options: [ + { label: 'gp2', value: 'gp2' }, + { label: 'premium-rs', value: 'premium-rs' }, + ], + default: 'gp2', + tip: '存储卷类型 (AWS gp2 / GCE premium-rs)', + }, + { + key: 'paramFsType', + label: '文件系统类型', + type: 'select', + options: [ + { label: 'ext4', value: 'ext4' }, + { label: 'xfs', value: 'xfs' }, + ], + default: 'ext4', + }, + { + key: 'paramIopsPerGB', + label: 'IOPS per GB', + type: 'text', + placeholder: '(可选)', + default: '', + tip: '每 GB 的 IOPS 配置,留空则不设置', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sStorageClassYaml(config) { + const parameters = { + type: config.paramType, + fsType: config.paramFsType, + } + if (config.paramIopsPerGB) { + parameters.iopsPerGB = config.paramIopsPerGB + } + + const storageClass = { + apiVersion: 'storage.k8s.io/v1', + kind: 'StorageClass', + metadata: { + name: config.name, + annotations: { + 'storageclass.kubernetes.io/is-default-class': 'false', + }, + }, + provisioner: config.provisioner, + reclaimPolicy: config.reclaimPolicy, + volumeBindingMode: config.volumeBindingMode, + allowVolumeExpansion: config.allowVolumeExpansion, + parameters, + } + + const header = `# K8s StorageClass - 由 ConfTemplate 生成 +# 生成时间: ${new Date().toLocaleString('zh-CN')} +# 部署命令: kubectl apply -f ${config.fileName || 'storageclass.yaml'} +` + + return header + '\n' + toYaml(storageClass) +}