feat: 新增 Dockerfile 配置模板,总计 60 个组件
Dockerfile 支持: - 基础镜像 (FROM) + 多阶段构建 + 平台架构 - 元数据 LABEL / 构建参数 ARG - 环境变量 ENV / 工作目录 WORKDIR - 文件复制 COPY + chown/chmod + ADD (自动解压/URL) - 依赖安装: apk/apt/yum/dnf 包管理器 + 应用依赖 + 构建命令 - 端口 EXPOSE / 数据卷 VOLUME - 用户 USER (非 root 运行) - 健康检查 HEALTHCHECK (interval/timeout/retries/start-period) - 入口 ENTRYPOINT / CMD (exec/shell 格式) - 停止信号 STOPSIGNAL / Shell 指令
This commit is contained in:
@@ -60,7 +60,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
<el-text size="small" type="info">v1.0.0 · 纯前端生成 · 59 个组件</el-text>
|
<el-text size="small" type="info">v1.0.0 · 纯前端生成 · 60 个组件</el-text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,364 @@
|
|||||||
|
export const dockerfileSchema = {
|
||||||
|
id: 'dockerfile',
|
||||||
|
name: 'Dockerfile',
|
||||||
|
icon: 'Box',
|
||||||
|
category: '容器化',
|
||||||
|
description: 'Docker 容器镜像构建文件',
|
||||||
|
format: 'dockerfile',
|
||||||
|
fileName: 'Dockerfile',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
title: '基础镜像',
|
||||||
|
fields: [
|
||||||
|
{ key: 'baseImage', label: '基础镜像', type: 'text', placeholder: 'nginx:1.24-alpine', default: 'nginx:1.24-alpine', required: true, tip: 'FROM 指令' },
|
||||||
|
{ key: 'fromPlatform', label: '平台架构', type: 'select', options: [
|
||||||
|
{ label: '不限制', value: '' },
|
||||||
|
{ label: 'linux/amd64', value: 'linux/amd64' },
|
||||||
|
{ label: 'linux/arm64', value: 'linux/arm64' },
|
||||||
|
], default: '', tip: '多架构构建时指定' },
|
||||||
|
{ key: 'enableMultiStage', label: '多阶段构建', type: 'switch', default: false, tip: '分离构建和运行阶段' },
|
||||||
|
{ key: 'builderImage', label: '构建阶段镜像', type: 'text', placeholder: 'node:18-alpine', default: 'node:18-alpine', dependsOn: { key: 'enableMultiStage', value: true } },
|
||||||
|
{ key: 'builderAs', label: '构建阶段别名', type: 'text', default: 'builder', dependsOn: { key: 'enableMultiStage', value: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '元数据 (LABEL)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableLabels', label: '添加标签', type: 'switch', default: false },
|
||||||
|
{ key: 'labelMaintainer', label: '维护者', type: 'text', placeholder: 'team@example.com', default: 'team@example.com', dependsOn: { key: 'enableLabels', value: true } },
|
||||||
|
{ key: 'labelVersion', label: '版本', type: 'text', placeholder: '1.0.0', default: '1.0.0', dependsOn: { key: 'enableLabels', value: true } },
|
||||||
|
{ key: 'labelDescription', label: '描述', type: 'text', placeholder: 'My application', default: '', dependsOn: { key: 'enableLabels', value: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '构建参数 (ARG)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableArgs', label: '定义构建参数', type: 'switch', default: false, tip: '仅在 docker build --build-arg 时生效' },
|
||||||
|
{ key: 'args', label: '参数列表 (KEY=VALUE)', type: 'text', placeholder: 'APP_VERSION=1.0.0\nNODE_ENV=production', default: 'APP_VERSION=1.0.0', dependsOn: { key: 'enableArgs', value: true }, tip: '每行一个,格式 KEY 或 KEY=默认值' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '环境变量 (ENV)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableEnv', label: '设置环境变量', type: 'switch', default: false },
|
||||||
|
{ key: 'envVars', label: '环境变量 (KEY=VALUE)', type: 'text', placeholder: 'NODE_ENV=production\nAPP_PORT=8080', default: 'NODE_ENV=production\nAPP_PORT=8080', dependsOn: { key: 'enableEnv', value: true }, tip: '每行一个,格式 KEY=VALUE' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '工作目录与用户',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableWorkdir', label: '设置工作目录', type: 'switch', default: true },
|
||||||
|
{ key: 'workdir', label: '工作目录', type: 'text', default: '/app', dependsOn: { key: 'enableWorkdir', value: true } },
|
||||||
|
{ key: 'enableUser', label: '切换运行用户', type: 'switch', default: false, tip: '生产环境建议使用非 root 用户' },
|
||||||
|
{ key: 'userName', label: '用户名或 UID', type: 'text', placeholder: '1001:1001', default: '1001:1001', dependsOn: { key: 'enableUser', value: true } },
|
||||||
|
{ key: 'enableUseradd', label: '创建用户', type: 'switch', default: false, dependsOn: { key: 'enableUser', value: true }, tip: '在 RUN 中创建用户' },
|
||||||
|
{ key: 'createUserName', label: '用户名', type: 'text', default: 'appuser', dependsOn: { key: 'enableUseradd', value: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '文件复制',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableCopy', label: '复制文件', type: 'switch', default: true },
|
||||||
|
{ key: 'copySrc', label: '源路径', type: 'text', placeholder: '. 或 ./dist', default: '.', dependsOn: { key: 'enableCopy', value: true } },
|
||||||
|
{ key: 'copyDest', label: '目标路径', type: 'text', placeholder: '/app', default: '/app', dependsOn: { key: 'enableCopy', value: true } },
|
||||||
|
{ key: 'enableCopyChown', label: '设置文件所有者', type: 'switch', default: false, dependsOn: { key: 'enableCopy', value: true } },
|
||||||
|
{ key: 'copyChown', label: '所有者', type: 'text', placeholder: '1001:1001', default: '1001:1001', dependsOn: { key: 'enableCopyChown', value: true } },
|
||||||
|
{ key: 'enableCopyChmod', label: '设置文件权限', type: 'switch', default: false, dependsOn: { key: 'enableCopy', value: true } },
|
||||||
|
{ key: 'copyChmod', label: '权限', type: 'text', placeholder: '755', default: '755', dependsOn: { key: 'enableCopyChmod', value: true } },
|
||||||
|
{ key: 'enableAdd', label: '使用 ADD (自动解压/URL)', type: 'switch', default: false, tip: '需要解压 tar 或下载远程文件时使用' },
|
||||||
|
{ key: 'addSrc', label: 'ADD 源', type: 'text', placeholder: 'https://example.com/app.tar.gz', default: '', dependsOn: { key: 'enableAdd', value: true } },
|
||||||
|
{ key: 'addDest', label: 'ADD 目标', type: 'text', default: '/app/', dependsOn: { key: 'enableAdd', value: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '依赖安装 (RUN)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'packageManager', label: '包管理器', type: 'select', options: [
|
||||||
|
{ label: '不安装系统包', value: 'none' },
|
||||||
|
{ label: 'apk (Alpine)', value: 'apk' },
|
||||||
|
{ label: 'apt (Debian/Ubuntu)', value: 'apt' },
|
||||||
|
{ label: 'yum (CentOS/RHEL)', value: 'yum' },
|
||||||
|
{ label: 'dnf (Fedora)', value: 'dnf' },
|
||||||
|
], default: 'none' },
|
||||||
|
{ key: 'systemPackages', label: '系统包 (空格分隔)', type: 'text', placeholder: 'curl wget tzdata', default: 'curl ca-certificates tzdata', dependsOn: { key: 'packageManager', valueNotIn: ['none'] } },
|
||||||
|
{ key: 'enableAppInstall', label: '安装应用依赖', type: 'switch', default: false, tip: 'npm install / pip install / go build 等' },
|
||||||
|
{ key: 'appInstallCmd', label: '安装命令', type: 'text', placeholder: 'npm ci --only=production', default: 'npm ci --only=production', dependsOn: { key: 'enableAppInstall', value: true } },
|
||||||
|
{ key: 'enableBuildCmd', label: '构建命令', type: 'switch', default: false, tip: 'npm run build / go build / mvn package 等' },
|
||||||
|
{ key: 'buildCmd', label: '构建命令', type: 'text', placeholder: 'npm run build', default: 'npm run build', dependsOn: { key: 'enableBuildCmd', value: true } },
|
||||||
|
{ key: 'enableCleanup', label: '清理缓存', type: 'switch', default: true, dependsOn: { key: 'packageManager', valueNotIn: ['none'] }, tip: '减小镜像体积' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '端口与网络',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableExpose', label: '暴露端口', type: 'switch', default: true },
|
||||||
|
{ key: 'exposePorts', label: '端口列表', type: 'text', placeholder: '80 443', default: '80', dependsOn: { key: 'enableExpose', value: true }, tip: '空格分隔多个端口' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '健康检查 (HEALTHCHECK)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableHealthcheck', label: '添加健康检查', type: 'switch', default: false },
|
||||||
|
{ key: 'healthcheckType', label: '检查方式', type: 'select', options: [
|
||||||
|
{ label: 'CMD (自定义命令)', value: 'CMD' },
|
||||||
|
{ label: 'NONE (禁用继承的健康检查)', value: 'NONE' },
|
||||||
|
], default: 'CMD', dependsOn: { key: 'enableHealthcheck', value: true } },
|
||||||
|
{ key: 'healthcheckInterval', label: '检查间隔', type: 'text', default: '30s', dependsOn: { key: 'healthcheckType', value: 'CMD' } },
|
||||||
|
{ key: 'healthcheckTimeout', label: '超时时间', type: 'text', default: '10s', dependsOn: { key: 'healthcheckType', value: 'CMD' } },
|
||||||
|
{ key: 'healthcheckRetries', label: '重试次数', type: 'number', min: 1, max: 10, default: 3, dependsOn: { key: 'healthcheckType', value: 'CMD' } },
|
||||||
|
{ key: 'healthcheckStartPeriod', label: '启动等待期', type: 'text', default: '5s', dependsOn: { key: 'healthcheckType', value: 'CMD' } },
|
||||||
|
{ key: 'healthcheckCmd', label: '检查命令', type: 'text', placeholder: 'curl -f http://localhost/ || exit 1', default: 'curl -f http://localhost:80/ || exit 1', dependsOn: { key: 'healthcheckType', value: 'CMD' }, required: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '挂载点与数据卷',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableVolume', label: '声明数据卷', type: 'switch', default: false },
|
||||||
|
{ key: 'volumePaths', label: '挂载路径', type: 'text', placeholder: '/data /var/log/app', default: '/data', dependsOn: { key: 'enableVolume', value: true }, tip: '空格分隔多个路径' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '入口与启动',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableEntrypoint', label: '设置 ENTRYPOINT', type: 'switch', default: false, tip: '容器启动时执行的固定命令' },
|
||||||
|
{ key: 'entrypointCmd', label: 'ENTRYPOINT 命令', type: 'text', placeholder: '["java", "-jar", "app.jar"]', default: '["node", "server.js"]', dependsOn: { key: 'enableEntrypoint', value: true }, tip: 'JSON 数组格式或 shell 格式' },
|
||||||
|
{ key: 'enableCmd', label: '设置 CMD', type: 'switch', default: true, tip: '容器启动时的默认参数' },
|
||||||
|
{ key: 'cmdType', label: 'CMD 格式', type: 'select', options: [
|
||||||
|
{ label: 'exec 格式 (推荐)', value: 'exec' },
|
||||||
|
{ label: 'shell 格式', value: 'shell' },
|
||||||
|
], default: 'exec', dependsOn: { key: 'enableCmd', value: true } },
|
||||||
|
{ key: 'cmdValue', label: 'CMD 内容', type: 'text', placeholder: '["nginx", "-g", "daemon off;"]', default: '["nginx", "-g", "daemon off;"]', dependsOn: { key: 'enableCmd', value: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '信号与停止',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableStopSignal', label: '自定义停止信号', type: 'switch', default: false, tip: 'docker stop 发送的信号' },
|
||||||
|
{ key: 'stopSignal', label: '停止信号', type: 'select', options: [
|
||||||
|
{ label: 'SIGTERM (默认)', value: 'SIGTERM' },
|
||||||
|
{ label: 'SIGQUIT', value: 'SIGQUIT' },
|
||||||
|
{ label: 'SIGINT', value: 'SIGINT' },
|
||||||
|
{ label: 'SIGUSR1', value: 'SIGUSR1' },
|
||||||
|
], default: 'SIGTERM', dependsOn: { key: 'enableStopSignal', value: true } },
|
||||||
|
{ key: 'enableShell', label: '指定 Shell', type: 'switch', default: false, tip: 'SHELL 指令,RUN 使用的 shell' },
|
||||||
|
{ key: 'shellPath', label: 'Shell 路径', type: 'text', default: '/bin/sh', dependsOn: { key: 'enableShell', value: true } },
|
||||||
|
{ key: 'shellFlag', label: 'Shell 参数', type: 'text', default: '-c', dependsOn: { key: 'enableShell', value: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateDockerfile(config) {
|
||||||
|
const lines = []
|
||||||
|
lines.push(`# Dockerfile - 由 ConfTemplate 生成`)
|
||||||
|
lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`)
|
||||||
|
lines.push(``)
|
||||||
|
|
||||||
|
// Builder stage
|
||||||
|
if (config.enableMultiStage) {
|
||||||
|
lines.push(`# ======================== 构建阶段 ========================`)
|
||||||
|
lines.push(`FROM${config.fromPlatform ? ` --platform=${config.fromPlatform}` : ''} ${config.builderImage} AS ${config.builderAs}`)
|
||||||
|
lines.push(``)
|
||||||
|
if (config.enableWorkdir) {
|
||||||
|
lines.push(`WORKDIR ${config.workdir}`)
|
||||||
|
}
|
||||||
|
lines.push(`COPY . .`)
|
||||||
|
if (config.enableAppInstall && config.appInstallCmd) {
|
||||||
|
lines.push(`RUN ${config.appInstallCmd}`)
|
||||||
|
}
|
||||||
|
if (config.enableBuildCmd && config.buildCmd) {
|
||||||
|
lines.push(`RUN ${config.buildCmd}`)
|
||||||
|
}
|
||||||
|
lines.push(``)
|
||||||
|
lines.push(`# ======================== 运行阶段 ========================`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FROM
|
||||||
|
const platformArg = config.fromPlatform ? ` --platform=${config.fromPlatform}` : ''
|
||||||
|
if (config.enableMultiStage) {
|
||||||
|
lines.push(`FROM${platformArg} ${config.baseImage}`)
|
||||||
|
} else {
|
||||||
|
lines.push(`FROM${platformArg} ${config.baseImage}`)
|
||||||
|
}
|
||||||
|
lines.push(``)
|
||||||
|
|
||||||
|
// ARG
|
||||||
|
if (config.enableArgs && config.args) {
|
||||||
|
config.args.split('\n').filter(Boolean).forEach(line => {
|
||||||
|
lines.push(`ARG ${line.trim()}`)
|
||||||
|
})
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LABEL
|
||||||
|
if (config.enableLabels) {
|
||||||
|
if (config.labelMaintainer) lines.push(`LABEL maintainer="${config.labelMaintainer}"`)
|
||||||
|
if (config.labelVersion) lines.push(`LABEL version="${config.labelVersion}"`)
|
||||||
|
if (config.labelDescription) lines.push(`LABEL description="${config.labelDescription}"`)
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ENV
|
||||||
|
if (config.enableEnv && config.envVars) {
|
||||||
|
config.envVars.split('\n').filter(Boolean).forEach(line => {
|
||||||
|
lines.push(`ENV ${line.trim()}`)
|
||||||
|
})
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WORKDIR
|
||||||
|
if (config.enableWorkdir) {
|
||||||
|
lines.push(`WORKDIR ${config.workdir}`)
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package install
|
||||||
|
if (config.packageManager && config.packageManager !== 'none') {
|
||||||
|
const pkgs = (config.systemPackages || '').trim()
|
||||||
|
if (pkgs) {
|
||||||
|
if (config.packageManager === 'apk') {
|
||||||
|
lines.push(`RUN apk add --no-cache ${pkgs}`)
|
||||||
|
} else if (config.packageManager === 'apt') {
|
||||||
|
lines.push(`RUN apt-get update && \\`)
|
||||||
|
lines.push(` apt-get install -y --no-install-recommends ${pkgs} && \\`)
|
||||||
|
if (config.enableCleanup) {
|
||||||
|
lines.push(` rm -rf /var/lib/apt/lists/*`)
|
||||||
|
}
|
||||||
|
} else if (config.packageManager === 'yum') {
|
||||||
|
lines.push(`RUN yum install -y ${pkgs} && \\`)
|
||||||
|
if (config.enableCleanup) {
|
||||||
|
lines.push(` yum clean all && rm -rf /var/cache/yum`)
|
||||||
|
}
|
||||||
|
} else if (config.packageManager === 'dnf') {
|
||||||
|
lines.push(`RUN dnf install -y ${pkgs} && \\`)
|
||||||
|
if (config.enableCleanup) {
|
||||||
|
lines.push(` dnf clean all && rm -rf /var/cache/dnf`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create user
|
||||||
|
if (config.enableUser && config.enableUseradd && config.createUserName) {
|
||||||
|
lines.push(`RUN addgroup -S ${config.createUserName} && adduser -S ${config.createUserName} -G ${config.createUserName}`)
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// COPY from builder stage
|
||||||
|
if (config.enableMultiStage) {
|
||||||
|
if (config.enableCopyChown) {
|
||||||
|
lines.push(`COPY --from=${config.builderAs} --chown=${config.copyChown} ${config.workdir || '/app'} ${config.copyDest}`)
|
||||||
|
} else {
|
||||||
|
lines.push(`COPY --from=${config.builderAs} ${config.workdir || '/app'} ${config.copyDest}`)
|
||||||
|
}
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// COPY
|
||||||
|
if (config.enableCopy && !config.enableMultiStage) {
|
||||||
|
let copyFlags = []
|
||||||
|
if (config.enableCopyChown && config.copyChown) copyFlags.push(`--chown=${config.copyChown}`)
|
||||||
|
if (config.enableCopyChmod && config.copyChmod) copyFlags.push(`--chmod=${config.copyChmod}`)
|
||||||
|
const flags = copyFlags.length ? ` ${copyFlags.join(' ')}` : ''
|
||||||
|
lines.push(`COPY${flags} ${config.copySrc} ${config.copyDest}`)
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADD
|
||||||
|
if (config.enableAdd && config.addSrc) {
|
||||||
|
lines.push(`ADD ${config.addSrc} ${config.addDest}`)
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// App install (non-multi-stage)
|
||||||
|
if (!config.enableMultiStage && config.enableAppInstall && config.appInstallCmd) {
|
||||||
|
lines.push(`RUN ${config.appInstallCmd}`)
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build cmd (non-multi-stage)
|
||||||
|
if (!config.enableMultiStage && config.enableBuildCmd && config.buildCmd) {
|
||||||
|
lines.push(`RUN ${config.buildCmd}`)
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VOLUME
|
||||||
|
if (config.enableVolume && config.volumePaths) {
|
||||||
|
const paths = config.volumePaths.split(/\s+/).filter(Boolean)
|
||||||
|
if (paths.length === 1) {
|
||||||
|
lines.push(`VOLUME ["${paths[0]}"]`)
|
||||||
|
} else {
|
||||||
|
lines.push(`VOLUME [${paths.map(p => `"${p}"`).join(', ')}]`)
|
||||||
|
}
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPOSE
|
||||||
|
if (config.enableExpose && config.exposePorts) {
|
||||||
|
lines.push(`EXPOSE ${config.exposePorts}`)
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// USER
|
||||||
|
if (config.enableUser) {
|
||||||
|
lines.push(`USER ${config.userName}`)
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HEALTHCHECK
|
||||||
|
if (config.enableHealthcheck) {
|
||||||
|
if (config.healthcheckType === 'NONE') {
|
||||||
|
lines.push(`HEALTHCHECK NONE`)
|
||||||
|
} else {
|
||||||
|
let hc = `HEALTHCHECK`
|
||||||
|
if (config.healthcheckInterval) hc += ` --interval=${config.healthcheckInterval}`
|
||||||
|
if (config.healthcheckTimeout) hc += ` --timeout=${config.healthcheckTimeout}`
|
||||||
|
if (config.healthcheckRetries) hc += ` --retries=${config.healthcheckRetries}`
|
||||||
|
if (config.healthcheckStartPeriod) hc += ` --start-period=${config.healthcheckStartPeriod}`
|
||||||
|
hc += ` CMD ${config.healthcheckCmd}`
|
||||||
|
lines.push(hc)
|
||||||
|
}
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// STOPSIGNAL
|
||||||
|
if (config.enableStopSignal) {
|
||||||
|
lines.push(`STOPSIGNAL ${config.stopSignal}`)
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHELL
|
||||||
|
if (config.enableShell) {
|
||||||
|
lines.push(`SHELL ["${config.shellPath}", "${config.shellFlag}"]`)
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ENTRYPOINT
|
||||||
|
if (config.enableEntrypoint && config.entrypointCmd) {
|
||||||
|
const val = config.entrypointCmd.trim()
|
||||||
|
if (val.startsWith('[')) {
|
||||||
|
lines.push(`ENTRYPOINT ${val}`)
|
||||||
|
} else {
|
||||||
|
lines.push(`ENTRYPOINT ["${val}"]`)
|
||||||
|
}
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CMD
|
||||||
|
if (config.enableCmd && config.cmdValue) {
|
||||||
|
const val = config.cmdValue.trim()
|
||||||
|
if (config.cmdType === 'exec') {
|
||||||
|
if (val.startsWith('[')) {
|
||||||
|
lines.push(`CMD ${val}`)
|
||||||
|
} else {
|
||||||
|
lines.push(`CMD ["${val}"]`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lines.push(`CMD ${val}`)
|
||||||
|
}
|
||||||
|
lines.push(``)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join('\n')
|
||||||
|
}
|
||||||
+75
-35
@@ -235,47 +235,87 @@ export const k8sCronJobSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
// ======================== YAML 生成器 ========================
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
function toYaml(obj, indent) {
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
indent = indent || 0
|
||||||
|
var lines = []
|
||||||
|
var prefix = ''
|
||||||
|
for (var p = 0; p < indent; p++) prefix += ' '
|
||||||
|
var keys = Object.keys(obj)
|
||||||
|
for (var ki = 0; ki < keys.length; ki++) {
|
||||||
|
var key = keys[ki]
|
||||||
|
var value = obj[key]
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
lines.push(prefix + key + ':')
|
||||||
for (const item of value) {
|
for (var ai = 0; ai < value.length; ai++) {
|
||||||
|
var item = value[ai]
|
||||||
if (typeof item === 'object' && item !== null) {
|
if (typeof item === 'object' && item !== null) {
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
lines.push(renderArrayObject(item, indent + 1))
|
||||||
if (entries.length === 0) continue
|
|
||||||
const first = entries[0]
|
|
||||||
lines.push(`${prefix} - ${first[0]}: ${typeof first[1] === 'object' ? '' : first[1]}`)
|
|
||||||
if (typeof first[1] === 'object') {
|
|
||||||
lines.push(toYaml(first[1], indent + 3))
|
|
||||||
}
|
|
||||||
for (let i = 1; i < entries.length; i++) {
|
|
||||||
const [k, v] = entries[i]
|
|
||||||
if (typeof v === 'object') {
|
|
||||||
lines.push(`${prefix} ${k}:`)
|
|
||||||
lines.push(toYaml(v, indent + 4))
|
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} ${k}: ${v}`)
|
lines.push(prefix + ' - ' + item)
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lines.push(`${prefix} - ${item}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
lines.push(prefix + key + ':')
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(toYaml(value, indent + 1))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(prefix + key + ': ' + value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderArrayObject(item, indent) {
|
||||||
|
var prefix = ''
|
||||||
|
for (var p = 0; p < indent; p++) prefix += ' '
|
||||||
|
var entries = Object.entries(item).filter(function(pair) { return pair[1] !== null && pair[1] !== undefined && pair[1] !== '' })
|
||||||
|
if (entries.length === 0) return ''
|
||||||
|
var lines = []
|
||||||
|
var first = entries[0]
|
||||||
|
var fk = first[0], fv = first[1]
|
||||||
|
if (Array.isArray(fv)) {
|
||||||
|
lines.push(prefix + '- ' + fk + ':')
|
||||||
|
for (var ai = 0; ai < fv.length; ai++) {
|
||||||
|
var arrItem = fv[ai]
|
||||||
|
if (typeof arrItem === 'object' && arrItem !== null) {
|
||||||
|
lines.push(renderArrayObject(arrItem, indent + 2))
|
||||||
|
} else {
|
||||||
|
lines.push(prefix + ' - ' + arrItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof fv === 'object' && fv !== null) {
|
||||||
|
lines.push(prefix + '- ' + fk + ':')
|
||||||
|
lines.push(toYaml(fv, indent + 2))
|
||||||
|
} else {
|
||||||
|
lines.push(prefix + '- ' + fk + ': ' + fv)
|
||||||
|
}
|
||||||
|
for (var i = 1; i < entries.length; i++) {
|
||||||
|
var k = entries[i][0], v = entries[i][1]
|
||||||
|
if (v === null || v === undefined || v === '') continue
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
lines.push(prefix + ' ' + k + ':')
|
||||||
|
for (var ai = 0; ai < v.length; ai++) {
|
||||||
|
var arrItem = v[ai]
|
||||||
|
if (typeof arrItem === 'object' && arrItem !== null) {
|
||||||
|
lines.push(renderArrayObject(arrItem, indent + 2))
|
||||||
|
} else {
|
||||||
|
lines.push(prefix + ' - ' + arrItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof v === 'object') {
|
||||||
|
lines.push(prefix + ' ' + k + ':')
|
||||||
|
lines.push(toYaml(v, indent + 2))
|
||||||
|
} else {
|
||||||
|
lines.push(prefix + ' ' + k + ': ' + v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateK8sCronJobYaml(config) {
|
export function generateK8sCronJobYaml(config) {
|
||||||
const container = {
|
var container = {
|
||||||
name: config.name,
|
name: config.name,
|
||||||
image: config.image,
|
image: config.image,
|
||||||
imagePullPolicy: config.imagePullPolicy,
|
imagePullPolicy: config.imagePullPolicy,
|
||||||
@@ -299,9 +339,9 @@ export function generateK8sCronJobYaml(config) {
|
|||||||
|
|
||||||
// 环境变量
|
// 环境变量
|
||||||
if (config.enableEnv && config.envVars) {
|
if (config.enableEnv && config.envVars) {
|
||||||
container.env = config.envVars.split('\n').filter(Boolean).map(line => {
|
container.env = config.envVars.split('\n').filter(Boolean).map(function(line) {
|
||||||
const [k, ...v] = line.split('=')
|
var idx = line.indexOf('=')
|
||||||
return { name: k.trim(), value: v.join('=').trim() }
|
return { name: line.substring(0, idx).trim(), value: line.substring(idx + 1).trim() }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (config.enableEnvFromConfigMap || config.enableEnvFromSecret) {
|
if (config.enableEnvFromConfigMap || config.enableEnvFromSecret) {
|
||||||
@@ -311,11 +351,11 @@ export function generateK8sCronJobYaml(config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 卷挂载
|
// 卷挂载
|
||||||
const volumes = []
|
var volumes = []
|
||||||
if (config.enableVolumeMount) {
|
if (config.enableVolumeMount) {
|
||||||
container.volumeMounts = [{ name: config.volumeName, mountPath: config.volumeMountPath, readOnly: config.volumeReadOnly || undefined }]
|
container.volumeMounts = [{ name: config.volumeName, mountPath: config.volumeMountPath, readOnly: config.volumeReadOnly || undefined }]
|
||||||
const vol = { name: config.volumeName }
|
var vol = { name: config.volumeName }
|
||||||
const vt = config.volumeType
|
var vt = config.volumeType
|
||||||
if (vt === 'emptyDir') vol.emptyDir = {}
|
if (vt === 'emptyDir') vol.emptyDir = {}
|
||||||
else if (vt === 'pvc') vol.persistentVolumeClaim = { claimName: config.pvcClaimName }
|
else if (vt === 'pvc') vol.persistentVolumeClaim = { claimName: config.pvcClaimName }
|
||||||
else if (vt === 'configMap') vol.configMap = { name: config.configMapName }
|
else if (vt === 'configMap') vol.configMap = { name: config.configMapName }
|
||||||
@@ -335,7 +375,7 @@ export function generateK8sCronJobYaml(config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const podSpec = {
|
var podSpec = {
|
||||||
containers: [container],
|
containers: [container],
|
||||||
restartPolicy: config.restartPolicy,
|
restartPolicy: config.restartPolicy,
|
||||||
}
|
}
|
||||||
@@ -351,7 +391,7 @@ export function generateK8sCronJobYaml(config) {
|
|||||||
podSpec.securityContext = { fsGroup: config.fsGroup }
|
podSpec.securityContext = { fsGroup: config.fsGroup }
|
||||||
}
|
}
|
||||||
|
|
||||||
const cronjob = {
|
var cronjob = {
|
||||||
apiVersion: 'batch/v1',
|
apiVersion: 'batch/v1',
|
||||||
kind: 'CronJob',
|
kind: 'CronJob',
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -377,7 +417,7 @@ export function generateK8sCronJobYaml(config) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const header = `# K8s CronJob - 由 ConfTemplate 生成\n# 生成时间: ${new Date().toLocaleString('zh-CN')}\n# 部署命令: kubectl apply -f ${config.fileName || 'cronjob.yaml'}\n`
|
var header = '# K8s CronJob - 由 ConfTemplate 生成\n# 生成时间: ' + new Date().toLocaleString('zh-CN') + '\n# 部署命令: kubectl apply -f ' + (config.fileName || 'cronjob.yaml') + '\n'
|
||||||
|
|
||||||
return header + '\n' + toYaml(cronjob)
|
return header + '\n' + toYaml(cronjob)
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-35
@@ -206,47 +206,87 @@ export const k8sJobSchema = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
// ======================== YAML 生成器 ========================
|
||||||
const lines = []
|
|
||||||
const prefix = ' '.repeat(indent)
|
function toYaml(obj, indent) {
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
indent = indent || 0
|
||||||
|
var lines = []
|
||||||
|
var prefix = ''
|
||||||
|
for (var p = 0; p < indent; p++) prefix += ' '
|
||||||
|
var keys = Object.keys(obj)
|
||||||
|
for (var ki = 0; ki < keys.length; ki++) {
|
||||||
|
var key = keys[ki]
|
||||||
|
var value = obj[key]
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
lines.push(prefix + key + ':')
|
||||||
for (const item of value) {
|
for (var ai = 0; ai < value.length; ai++) {
|
||||||
|
var item = value[ai]
|
||||||
if (typeof item === 'object' && item !== null) {
|
if (typeof item === 'object' && item !== null) {
|
||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
lines.push(renderArrayObject(item, indent + 1))
|
||||||
if (entries.length === 0) continue
|
|
||||||
const first = entries[0]
|
|
||||||
lines.push(`${prefix} - ${first[0]}: ${typeof first[1] === 'object' ? '' : first[1]}`)
|
|
||||||
if (typeof first[1] === 'object') {
|
|
||||||
lines.push(toYaml(first[1], indent + 3))
|
|
||||||
}
|
|
||||||
for (let i = 1; i < entries.length; i++) {
|
|
||||||
const [k, v] = entries[i]
|
|
||||||
if (typeof v === 'object') {
|
|
||||||
lines.push(`${prefix} ${k}:`)
|
|
||||||
lines.push(toYaml(v, indent + 4))
|
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} ${k}: ${v}`)
|
lines.push(prefix + ' - ' + item)
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lines.push(`${prefix} - ${item}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
lines.push(`${prefix}${key}:`)
|
lines.push(prefix + key + ':')
|
||||||
lines.push(toYaml(value, indent + 1))
|
lines.push(toYaml(value, indent + 1))
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(prefix + key + ': ' + value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderArrayObject(item, indent) {
|
||||||
|
var prefix = ''
|
||||||
|
for (var p = 0; p < indent; p++) prefix += ' '
|
||||||
|
var entries = Object.entries(item).filter(function(pair) { return pair[1] !== null && pair[1] !== undefined && pair[1] !== '' })
|
||||||
|
if (entries.length === 0) return ''
|
||||||
|
var lines = []
|
||||||
|
var first = entries[0]
|
||||||
|
var fk = first[0], fv = first[1]
|
||||||
|
if (Array.isArray(fv)) {
|
||||||
|
lines.push(prefix + '- ' + fk + ':')
|
||||||
|
for (var ai = 0; ai < fv.length; ai++) {
|
||||||
|
var arrItem = fv[ai]
|
||||||
|
if (typeof arrItem === 'object' && arrItem !== null) {
|
||||||
|
lines.push(renderArrayObject(arrItem, indent + 2))
|
||||||
|
} else {
|
||||||
|
lines.push(prefix + ' - ' + arrItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof fv === 'object' && fv !== null) {
|
||||||
|
lines.push(prefix + '- ' + fk + ':')
|
||||||
|
lines.push(toYaml(fv, indent + 2))
|
||||||
|
} else {
|
||||||
|
lines.push(prefix + '- ' + fk + ': ' + fv)
|
||||||
|
}
|
||||||
|
for (var i = 1; i < entries.length; i++) {
|
||||||
|
var k = entries[i][0], v = entries[i][1]
|
||||||
|
if (v === null || v === undefined || v === '') continue
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
lines.push(prefix + ' ' + k + ':')
|
||||||
|
for (var ai = 0; ai < v.length; ai++) {
|
||||||
|
var arrItem = v[ai]
|
||||||
|
if (typeof arrItem === 'object' && arrItem !== null) {
|
||||||
|
lines.push(renderArrayObject(arrItem, indent + 2))
|
||||||
|
} else {
|
||||||
|
lines.push(prefix + ' - ' + arrItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof v === 'object') {
|
||||||
|
lines.push(prefix + ' ' + k + ':')
|
||||||
|
lines.push(toYaml(v, indent + 2))
|
||||||
|
} else {
|
||||||
|
lines.push(prefix + ' ' + k + ': ' + v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateK8sJobYaml(config) {
|
export function generateK8sJobYaml(config) {
|
||||||
const container = {
|
var container = {
|
||||||
name: config.name,
|
name: config.name,
|
||||||
image: config.image,
|
image: config.image,
|
||||||
imagePullPolicy: config.imagePullPolicy,
|
imagePullPolicy: config.imagePullPolicy,
|
||||||
@@ -270,9 +310,9 @@ export function generateK8sJobYaml(config) {
|
|||||||
|
|
||||||
// 环境变量
|
// 环境变量
|
||||||
if (config.enableEnv && config.envVars) {
|
if (config.enableEnv && config.envVars) {
|
||||||
container.env = config.envVars.split('\n').filter(Boolean).map(line => {
|
container.env = config.envVars.split('\n').filter(Boolean).map(function(line) {
|
||||||
const [k, ...v] = line.split('=')
|
var idx = line.indexOf('=')
|
||||||
return { name: k.trim(), value: v.join('=').trim() }
|
return { name: line.substring(0, idx).trim(), value: line.substring(idx + 1).trim() }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (config.enableEnvFromConfigMap || config.enableEnvFromSecret) {
|
if (config.enableEnvFromConfigMap || config.enableEnvFromSecret) {
|
||||||
@@ -282,11 +322,11 @@ export function generateK8sJobYaml(config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 卷挂载
|
// 卷挂载
|
||||||
const volumes = []
|
var volumes = []
|
||||||
if (config.enableVolumeMount) {
|
if (config.enableVolumeMount) {
|
||||||
container.volumeMounts = [{ name: config.volumeName, mountPath: config.volumeMountPath, readOnly: config.volumeReadOnly || undefined }]
|
container.volumeMounts = [{ name: config.volumeName, mountPath: config.volumeMountPath, readOnly: config.volumeReadOnly || undefined }]
|
||||||
const vol = { name: config.volumeName }
|
var vol = { name: config.volumeName }
|
||||||
const vt = config.volumeType
|
var vt = config.volumeType
|
||||||
if (vt === 'emptyDir') vol.emptyDir = {}
|
if (vt === 'emptyDir') vol.emptyDir = {}
|
||||||
else if (vt === 'pvc') vol.persistentVolumeClaim = { claimName: config.pvcClaimName }
|
else if (vt === 'pvc') vol.persistentVolumeClaim = { claimName: config.pvcClaimName }
|
||||||
else if (vt === 'configMap') vol.configMap = { name: config.configMapName }
|
else if (vt === 'configMap') vol.configMap = { name: config.configMapName }
|
||||||
@@ -306,7 +346,7 @@ export function generateK8sJobYaml(config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const podSpec = {
|
var podSpec = {
|
||||||
containers: [container],
|
containers: [container],
|
||||||
restartPolicy: config.restartPolicy,
|
restartPolicy: config.restartPolicy,
|
||||||
}
|
}
|
||||||
@@ -322,7 +362,7 @@ export function generateK8sJobYaml(config) {
|
|||||||
podSpec.securityContext = { fsGroup: config.fsGroup }
|
podSpec.securityContext = { fsGroup: config.fsGroup }
|
||||||
}
|
}
|
||||||
|
|
||||||
const job = {
|
var job = {
|
||||||
apiVersion: 'batch/v1',
|
apiVersion: 'batch/v1',
|
||||||
kind: 'Job',
|
kind: 'Job',
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -341,7 +381,7 @@ export function generateK8sJobYaml(config) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const header = `# K8s Job - 由 ConfTemplate 生成\n# 生成时间: ${new Date().toLocaleString('zh-CN')}\n# 部署命令: kubectl apply -f ${config.fileName || 'job.yaml'}\n`
|
var header = '# K8s Job - 由 ConfTemplate 生成\n# 生成时间: ' + new Date().toLocaleString('zh-CN') + '\n# 部署命令: kubectl apply -f ' + (config.fileName || 'job.yaml') + '\n'
|
||||||
|
|
||||||
return header + '\n' + toYaml(job)
|
return header + '\n' + toYaml(job)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user