mirror of
https://github.com/certd/certd.git
synced 2026-04-03 14:10:54 +08:00
perf: 支持部署证书到百度CCE
This commit is contained in:
@@ -14,7 +14,8 @@
|
|||||||
"build3": "rollup -c",
|
"build3": "rollup -c",
|
||||||
"build2": "vue-tsc --noEmit && vite build",
|
"build2": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"pub": "npm publish"
|
"pub": "npm publish",
|
||||||
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.39.7",
|
"@certd/basic": "^1.39.7",
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ export class K8sClient {
|
|||||||
const yml = loadYaml<KubernetesObject>(manifest);
|
const yml = loadYaml<KubernetesObject>(manifest);
|
||||||
const client = this.getKubeClient();
|
const client = this.getKubeClient();
|
||||||
try {
|
try {
|
||||||
|
this.logger.info("apply yaml:", yml);
|
||||||
await client.create(yml);
|
await client.create(yml);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error("apply error", e.response?.body);
|
|
||||||
if (e.response?.body?.reason === "AlreadyExists") {
|
if (e.response?.body?.reason === "AlreadyExists") {
|
||||||
//patch
|
//patch
|
||||||
this.logger.info("patch existing resource: ", yml.metadata?.name);
|
this.logger.info("patch existing resource: ", yml.metadata?.name);
|
||||||
@@ -70,13 +70,26 @@ export class K8sClient {
|
|||||||
yml.metadata = {};
|
yml.metadata = {};
|
||||||
}
|
}
|
||||||
yml.metadata.resourceVersion = existing.body.metadata.resourceVersion;
|
yml.metadata.resourceVersion = existing.body.metadata.resourceVersion;
|
||||||
await client.patch(yml);
|
const res = await client.patch(yml);
|
||||||
return;
|
return res?.body;
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async applyPatch(manifest: string) {
|
||||||
|
const yml = loadYaml<KubernetesObject>(manifest);
|
||||||
|
const client = this.getKubeClient();
|
||||||
|
this.logger.info("patch yaml:", yml);
|
||||||
|
const existing = await client.read(yml as any);
|
||||||
|
if (!yml.metadata) {
|
||||||
|
yml.metadata = {};
|
||||||
|
}
|
||||||
|
yml.metadata.resourceVersion = existing.body.metadata.resourceVersion;
|
||||||
|
const res = await client.patch(yml);
|
||||||
|
return res?.body;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param localRecords { [domain]:{ip:'xxx.xx.xxx'} }
|
* @param localRecords { [domain]:{ip:'xxx.xx.xxx'} }
|
||||||
@@ -112,6 +125,7 @@ export class K8sClient {
|
|||||||
*/
|
*/
|
||||||
async createSecret(opts: { namespace: string; body: V1Secret }) {
|
async createSecret(opts: { namespace: string; body: V1Secret }) {
|
||||||
const namespace = opts.namespace || "default";
|
const namespace = opts.namespace || "default";
|
||||||
|
this.logger.info("create secret:", opts.body.metadata);
|
||||||
const created = await this.client.createNamespacedSecret(namespace, opts.body);
|
const created = await this.client.createNamespacedSecret(namespace, opts.body);
|
||||||
this.logger.info("new secrets:", opts.body.metadata);
|
this.logger.info("new secrets:", opts.body.metadata);
|
||||||
return created.body;
|
return created.body;
|
||||||
@@ -152,6 +166,8 @@ export class K8sClient {
|
|||||||
this.logger.info(`secret ${secretName} 已创建`);
|
this.logger.info(`secret ${secretName} 已创建`);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new Error(`secret ${secretName} 不存在`);
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from "./plugin-deploy-to-cdn.js";
|
export * from "./plugin-deploy-to-cdn.js";
|
||||||
export * from "./plugin-deploy-to-blb.js";
|
export * from "./plugin-deploy-to-blb.js";
|
||||||
export * from "./plugin-upload-to-baidu.js";
|
export * from "./plugin-upload-to-baidu.js";
|
||||||
|
export * from "./plugin-deploy-to-cce.js";
|
||||||
|
|||||||
@@ -0,0 +1,245 @@
|
|||||||
|
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||||
|
import { utils } from "@certd/basic";
|
||||||
|
|
||||||
|
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||||
|
import { BaiduAccess } from "../access.js";
|
||||||
|
import { BaiduYunClient } from "../client.js";
|
||||||
|
|
||||||
|
@IsTaskPlugin({
|
||||||
|
name: "DeployCertToBaiduCce",
|
||||||
|
title: "百度云-部署到CCE",
|
||||||
|
icon: "ant-design:cloud-outlined",
|
||||||
|
desc: "部署到百度云CCE集群Ingress等通过Secret管理证书的应用",
|
||||||
|
group: pluginGroups.baidu.key,
|
||||||
|
needPlus: true,
|
||||||
|
input: {},
|
||||||
|
output: {},
|
||||||
|
default: {
|
||||||
|
strategy: {
|
||||||
|
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class DeployCertToBaiduCcePlugin extends AbstractTaskPlugin {
|
||||||
|
@TaskInput({
|
||||||
|
title: "域名证书",
|
||||||
|
helper: "请选择前置任务输出的域名证书",
|
||||||
|
component: {
|
||||||
|
name: "output-selector",
|
||||||
|
from: [...CertApplyPluginNames],
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
cert!: CertInfo;
|
||||||
|
@TaskInput({
|
||||||
|
title: "Access授权",
|
||||||
|
helper: "百度云授权AccessKey、SecretKey",
|
||||||
|
component: {
|
||||||
|
name: "access-selector",
|
||||||
|
type: "baidu",
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
accessId!: string;
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: "大区",
|
||||||
|
component: {
|
||||||
|
name: "a-auto-complete",
|
||||||
|
vModel: "value",
|
||||||
|
options: [
|
||||||
|
{ value: "bj", label: "北京" },
|
||||||
|
{ value: "gz", label: "广州" },
|
||||||
|
{ value: "su", label: "苏州" },
|
||||||
|
{ value: "bd", label: "保定" },
|
||||||
|
{ value: "fwh", label: "武汉" },
|
||||||
|
{ value: "hkg", label: "香港" },
|
||||||
|
{ value: "yq", label: "阳泉" },
|
||||||
|
{ value: "cd", label: "成都" },
|
||||||
|
{ value: "nj", label: "南京" },
|
||||||
|
],
|
||||||
|
placeholder: "集群所属大区",
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
regionId!: string;
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: "集群id",
|
||||||
|
component: {
|
||||||
|
placeholder: "集群id",
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
clusterId!: string;
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: "保密字典Id",
|
||||||
|
component: {
|
||||||
|
placeholder: "保密字典Id",
|
||||||
|
},
|
||||||
|
helper: "原本存储证书的secret的name",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
secretName!: string | string[];
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: "命名空间",
|
||||||
|
value: "default",
|
||||||
|
component: {
|
||||||
|
placeholder: "命名空间",
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
namespace = "default";
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: "Kubeconfig类型",
|
||||||
|
value: "public",
|
||||||
|
component: {
|
||||||
|
name: "a-auto-complete",
|
||||||
|
vModel: "value",
|
||||||
|
options: [
|
||||||
|
{ value: "vpc", label: "VPC私网IP (BLB VPCIP)" },
|
||||||
|
{ value: "public", label: "公网IP (BLB EIP)" },
|
||||||
|
],
|
||||||
|
placeholder: "选择集群连接端点类型",
|
||||||
|
},
|
||||||
|
helper: "VPC类型使用私网IP连接,需要certd运行在同一网络环境;public类型使用公网IP连接",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
kubeconfigType!: string;
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: "忽略证书校验",
|
||||||
|
required: false,
|
||||||
|
helper: "是否忽略证书校验",
|
||||||
|
component: {
|
||||||
|
name: "a-switch",
|
||||||
|
vModel: "checked",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
skipTLSVerify!: boolean;
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: "Secret自动创建",
|
||||||
|
helper: "如果Secret不存在,则创建,百度云的自动创建secret有问题",
|
||||||
|
value: false,
|
||||||
|
component: {
|
||||||
|
name: "a-switch",
|
||||||
|
vModel: "checked",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
createOnNotFound: boolean;
|
||||||
|
|
||||||
|
K8sClient: any;
|
||||||
|
async onInstance() {
|
||||||
|
const sdk = await import("@certd/lib-k8s");
|
||||||
|
this.K8sClient = sdk.K8sClient;
|
||||||
|
}
|
||||||
|
async execute(): Promise<void> {
|
||||||
|
this.logger.info("开始部署证书到百度云CCE");
|
||||||
|
const { regionId, clusterId, kubeconfigType, cert } = this;
|
||||||
|
const access = (await this.getAccess(this.accessId)) as BaiduAccess;
|
||||||
|
const client = new BaiduYunClient({
|
||||||
|
access,
|
||||||
|
logger: this.logger,
|
||||||
|
http: this.ctx.http,
|
||||||
|
});
|
||||||
|
const kubeConfigStr = await this.getKubeConfig(client, clusterId, regionId, kubeconfigType);
|
||||||
|
|
||||||
|
this.logger.info("kubeconfig已成功获取");
|
||||||
|
const k8sClient = new this.K8sClient({
|
||||||
|
kubeConfigStr,
|
||||||
|
logger: this.logger,
|
||||||
|
skipTLSVerify: this.skipTLSVerify,
|
||||||
|
});
|
||||||
|
await this.patchCertSecret({ cert, k8sClient });
|
||||||
|
|
||||||
|
await utils.sleep(5000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.restartIngress({ k8sClient });
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.warn(`重启ingress失败:${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async restartIngress(options: { k8sClient: any }) {
|
||||||
|
const { k8sClient } = options;
|
||||||
|
const { namespace } = this;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
metadata: {
|
||||||
|
labels: {
|
||||||
|
certd: this.appendTimeSuffix("certd"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const ingressList = await k8sClient.getIngressList({ namespace });
|
||||||
|
this.logger.info("ingressList:", JSON.stringify(ingressList));
|
||||||
|
if (!ingressList || !ingressList.items) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ingressNames = ingressList.items
|
||||||
|
.filter((item: any) => {
|
||||||
|
if (!item.spec.tls) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const tls of item.spec.tls) {
|
||||||
|
if (tls.secretName === this.secretName) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
.map((item: any) => {
|
||||||
|
return item.metadata.name;
|
||||||
|
});
|
||||||
|
for (const ingress of ingressNames) {
|
||||||
|
await k8sClient.patchIngress({ namespace, ingressName: ingress, body, createOnNotFound: this.createOnNotFound });
|
||||||
|
this.logger.info(`ingress已重启:${ingress}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async patchCertSecret(options: { cert: CertInfo; k8sClient: any }) {
|
||||||
|
const { cert, k8sClient } = options;
|
||||||
|
const crt = cert.crt;
|
||||||
|
const key = cert.key;
|
||||||
|
const crtBase64 = Buffer.from(crt).toString("base64");
|
||||||
|
const keyBase64 = Buffer.from(key).toString("base64");
|
||||||
|
|
||||||
|
const { namespace, secretName } = this;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
data: {
|
||||||
|
"tls.crt": crtBase64,
|
||||||
|
"tls.key": keyBase64,
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
labels: {
|
||||||
|
certd: this.appendTimeSuffix("certd"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let secretNames: any = secretName;
|
||||||
|
if (typeof secretName === "string") {
|
||||||
|
secretNames = [secretName];
|
||||||
|
}
|
||||||
|
for (const secret of secretNames) {
|
||||||
|
await k8sClient.patchSecret({ namespace, secretName: secret, body ,createOnNotFound: this.createOnNotFound});
|
||||||
|
this.logger.info(`cert secret已更新: ${secret}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getKubeConfig(client: BaiduYunClient, clusterId: string, regionId: string, kubeconfigType: string) {
|
||||||
|
const res = await client.doRequest({
|
||||||
|
host: `cce.${regionId}.baidubce.com`,
|
||||||
|
uri: `/v2/kubeconfig/${clusterId}/${kubeconfigType}`,
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
return res.kubeConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new DeployCertToBaiduCcePlugin();
|
||||||
@@ -62,6 +62,21 @@ export class K8sApplyPlugin extends AbstractPlusTaskPlugin {
|
|||||||
// })
|
// })
|
||||||
// namespace!: string;
|
// namespace!: string;
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: "应用策略",
|
||||||
|
helper: "选择使用apply(创建或更新)还是patch(补丁更新)",
|
||||||
|
component: {
|
||||||
|
name: "a-select",
|
||||||
|
options: [
|
||||||
|
{ label: "apply(创建)", value: "apply" },
|
||||||
|
{ label: "patch(更新)", value: "patch" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
value: "apply",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
strategy!: string;
|
||||||
|
|
||||||
@TaskInput({
|
@TaskInput({
|
||||||
title: "yaml",
|
title: "yaml",
|
||||||
required: true,
|
required: true,
|
||||||
@@ -112,8 +127,13 @@ export class K8sApplyPlugin extends AbstractPlusTaskPlugin {
|
|||||||
try {
|
try {
|
||||||
// this.logger.info("apply yaml:", compiledYaml);
|
// this.logger.info("apply yaml:", compiledYaml);
|
||||||
// this.logger.info("apply yamlDoc:", JSON.stringify(doc));
|
// this.logger.info("apply yamlDoc:", JSON.stringify(doc));
|
||||||
const res = await client.apply(compiledYaml);
|
if (this.strategy === "apply") {
|
||||||
this.logger.info("apply result:", res);
|
await client.apply(compiledYaml);
|
||||||
|
this.logger.info("apply success");
|
||||||
|
} else {
|
||||||
|
await client.applyPatch(compiledYaml);
|
||||||
|
this.logger.info("patch success");
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.response?.body) {
|
if (e.response?.body) {
|
||||||
throw new Error(JSON.stringify(e.response.body));
|
throw new Error(JSON.stringify(e.response.body));
|
||||||
|
|||||||
Reference in New Issue
Block a user