mirror of https://github.com/certd/certd
perf: 支持部署到金山云CDN
parent
9e1e4eeec2
commit
dfa74a69f7
|
@ -11,7 +11,6 @@ export type PageSearch = {
|
||||||
// sortOrder?: "asc" | "desc";
|
// sortOrder?: "asc" | "desc";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type PageRes = {
|
export type PageRes = {
|
||||||
pageNo?: number;
|
pageNo?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
|
|
|
@ -41,7 +41,7 @@ const option = ref({
|
||||||
center: ["60%", "50%"],
|
center: ["60%", "50%"],
|
||||||
name: "状态",
|
name: "状态",
|
||||||
type: "pie",
|
type: "pie",
|
||||||
radius: "80%",
|
radius: ["30%", "70%"],
|
||||||
avoidLabelOverlap: false,
|
avoidLabelOverlap: false,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
|
|
|
@ -31,3 +31,4 @@ export * from './plugin-namesilo/index.js'
|
||||||
export * from './plugin-proxmox/index.js'
|
export * from './plugin-proxmox/index.js'
|
||||||
export * from './plugin-wangsu/index.js'
|
export * from './plugin-wangsu/index.js'
|
||||||
export * from './plugin-admin/index.js'
|
export * from './plugin-admin/index.js'
|
||||||
|
export * from './plugin-ksyun/index.js'
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
import {AccessInput, BaseAccess, IsAccess} from "@certd/pipeline";
|
||||||
|
import {KsyunClient} from './client.js'
|
||||||
|
import {CertInfo} from "@certd/plugin-cert";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
@IsAccess({
|
||||||
|
name: "ksyun",
|
||||||
|
title: "金山云授权",
|
||||||
|
desc: "",
|
||||||
|
icon: "svg:icon-ksyun"
|
||||||
|
})
|
||||||
|
export class KsyunAccess extends BaseAccess {
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: 'AccessKeyID',
|
||||||
|
component: {
|
||||||
|
placeholder: 'AccessKeyID',
|
||||||
|
},
|
||||||
|
helper: "[获取密钥](https://uc.console.ksyun.com/pro/iam/#/set/keyManage)",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
accessKeyId = '';
|
||||||
|
@AccessInput({
|
||||||
|
title: 'AccessKeySecret',
|
||||||
|
component: {
|
||||||
|
placeholder: 'AccessKeySecret',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
encrypt: true,
|
||||||
|
})
|
||||||
|
accessKeySecret = '';
|
||||||
|
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: "测试",
|
||||||
|
component: {
|
||||||
|
name: "api-test",
|
||||||
|
action: "TestRequest"
|
||||||
|
},
|
||||||
|
helper: "点击测试接口是否正常"
|
||||||
|
})
|
||||||
|
testRequest = true;
|
||||||
|
|
||||||
|
async onTestRequest() {
|
||||||
|
const client = await this.getCdnClient()
|
||||||
|
await this.getCertList({client})
|
||||||
|
return "ok"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getCertList(opts?:{client:KsyunClient,pageNo?:number;pageSize?:number}) {
|
||||||
|
const res = await opts.client.doRequest({
|
||||||
|
action: "GetCertificates",
|
||||||
|
version: "2016-09-01",
|
||||||
|
method:"POST",
|
||||||
|
url:"/2016-09-01/cert/GetCertificates",
|
||||||
|
data:{
|
||||||
|
PageNum:opts?.pageNo || 1,
|
||||||
|
PageSize: opts?.pageSize || 30
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.ctx.logger.info(res)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CertificateId 是 string 证书对应的唯一ID
|
||||||
|
* CertificateName 是 String 安全证书名称
|
||||||
|
* ServerCertificate 是 String 域名对应的安全证书内容
|
||||||
|
* PrivateKey
|
||||||
|
* @param opts
|
||||||
|
*/
|
||||||
|
async updateCert(opts:{
|
||||||
|
client:KsyunClient,
|
||||||
|
certId:string,
|
||||||
|
certName:string,
|
||||||
|
cert:CertInfo
|
||||||
|
}){
|
||||||
|
const res = await opts.client.doRequest({
|
||||||
|
action: "SetCertificate",
|
||||||
|
version: "2016-09-01",
|
||||||
|
method:"POST",
|
||||||
|
url:"/2016-09-01/cert/SetCertificate",
|
||||||
|
data:{
|
||||||
|
CertificateId: parseInt(opts.certId),
|
||||||
|
CertificateName: opts.certName,
|
||||||
|
ServerCertificate: opts.cert.crt,
|
||||||
|
PrivateKey: opts.cert.key
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.ctx.logger.info(res)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCdnClient() {
|
||||||
|
return new KsyunClient({
|
||||||
|
accessKeyId: this.accessKeyId,
|
||||||
|
secretAccessKey: this.accessKeySecret,
|
||||||
|
region: 'cn-beijing-6',
|
||||||
|
service: 'cdn',
|
||||||
|
endpoint: 'cdn.api.ksyun.com',
|
||||||
|
logger: this.ctx.logger,
|
||||||
|
http: this.ctx.http
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
new KsyunAccess();
|
|
@ -0,0 +1,351 @@
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import querystring from 'querystring'
|
||||||
|
import {HttpClient, HttpRequestConfig, ILogger} from "@certd/basic";
|
||||||
|
|
||||||
|
export class KsyunClient {
|
||||||
|
|
||||||
|
accessKeyId: string;
|
||||||
|
secretAccessKey: string;
|
||||||
|
region: string;
|
||||||
|
service: string;
|
||||||
|
endpoint: string;
|
||||||
|
logger: ILogger;
|
||||||
|
http: HttpClient
|
||||||
|
constructor(opts:{accessKeyId:string; secretAccessKey:string; region?:string; service :string;endpoint :string,logger:ILogger,http:HttpClient}) {
|
||||||
|
this.accessKeyId = opts.accessKeyId;
|
||||||
|
this.secretAccessKey = opts.secretAccessKey;
|
||||||
|
this.region = opts.region || 'cn-beijing-6';
|
||||||
|
this.service = opts.service;
|
||||||
|
this.endpoint =opts.endpoint
|
||||||
|
this.logger = opts.logger;
|
||||||
|
this.http = opts.http;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async doRequest(opts: {action:string;version:string} &HttpRequestConfig){
|
||||||
|
const config = this.signRequest({
|
||||||
|
method: opts.method || 'GET',
|
||||||
|
url: opts.url || '/2016-09-01/domain/GetCdnDomains',
|
||||||
|
baseURL: `https://${this.endpoint}`,
|
||||||
|
params: opts.params,
|
||||||
|
headers: {
|
||||||
|
'X-Action': opts.action,
|
||||||
|
'X-Version': opts.version
|
||||||
|
},
|
||||||
|
data: opts.data
|
||||||
|
});
|
||||||
|
|
||||||
|
try{
|
||||||
|
return await this.http.request(config)
|
||||||
|
}catch (e) {
|
||||||
|
if (e.response?.data?.Error?.Message){
|
||||||
|
throw new Error(e.response?.data?.Error?.Message)
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名请求
|
||||||
|
* @param {Object} config Axios 请求配置
|
||||||
|
* @returns {Object} 签名后的请求配置
|
||||||
|
*/
|
||||||
|
signRequest(config) {
|
||||||
|
// 确保有必要的配置
|
||||||
|
if (!this.accessKeyId || !this.secretAccessKey) {
|
||||||
|
throw new Error('AccessKeyId and SecretAccessKey are required');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置默认值
|
||||||
|
config.method = config.method || 'GET';
|
||||||
|
config.headers = config.headers || {};
|
||||||
|
|
||||||
|
// 获取当前时间并设置 X-Amz-Date
|
||||||
|
const requestDate = this.getRequestDate();
|
||||||
|
config.headers['x-amz-date'] = requestDate;
|
||||||
|
|
||||||
|
// 处理不同的请求方法
|
||||||
|
let canonicalQueryString = '';
|
||||||
|
let hashedPayload = this.hashPayload(config.data || '');
|
||||||
|
|
||||||
|
if (config.method.toUpperCase() === 'GET') {
|
||||||
|
// GET 请求 - 参数在 URL 中
|
||||||
|
const urlParts = config.url.split('?');
|
||||||
|
const path = urlParts[0];
|
||||||
|
const query = urlParts[1] || '';
|
||||||
|
|
||||||
|
// 合并现有查询参数和额外参数
|
||||||
|
const queryParams = {
|
||||||
|
...querystring.parse(query),
|
||||||
|
...(config.params || {})
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成规范查询字符串
|
||||||
|
canonicalQueryString = this.createCanonicalQueryString(queryParams);
|
||||||
|
config.url = `${path}?${canonicalQueryString}`;
|
||||||
|
config.params = {}; // 清空 params,因为已经合并到 URL 中
|
||||||
|
} else {
|
||||||
|
// POST/PUT 等请求 - 参数在 body 中
|
||||||
|
canonicalQueryString = '';
|
||||||
|
if (config.data && typeof config.data === 'object') {
|
||||||
|
// 如果 data 是对象,转换为 JSON 字符串
|
||||||
|
config.data = JSON.stringify(config.data);
|
||||||
|
hashedPayload = this.hashPayload(config.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成规范请求
|
||||||
|
const canonicalRequest = this.createCanonicalRequest(
|
||||||
|
config.method,
|
||||||
|
config.url,
|
||||||
|
canonicalQueryString,
|
||||||
|
config.headers,
|
||||||
|
hashedPayload
|
||||||
|
);
|
||||||
|
|
||||||
|
// 生成签名字符串
|
||||||
|
const credentialScope = this.createCredentialScope(requestDate);
|
||||||
|
const stringToSign = this.createStringToSign(requestDate, credentialScope, canonicalRequest);
|
||||||
|
|
||||||
|
// 计算签名
|
||||||
|
const signature = this.calculateSignature(requestDate, stringToSign);
|
||||||
|
|
||||||
|
// 生成 Authorization 头
|
||||||
|
const signedHeaders = this.getSignedHeaders(config.headers);
|
||||||
|
const authorizationHeader = this.createAuthorizationHeader(
|
||||||
|
credentialScope,
|
||||||
|
signedHeaders,
|
||||||
|
signature
|
||||||
|
);
|
||||||
|
|
||||||
|
// 添加 Authorization 头
|
||||||
|
config.headers.Authorization = authorizationHeader;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前时间 (格式: YYYYMMDD'T'HHMMSS'Z')
|
||||||
|
* @returns {string} 格式化后的时间字符串
|
||||||
|
*/
|
||||||
|
getRequestDate() {
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getUTCFullYear();
|
||||||
|
const month = String(now.getUTCMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(now.getUTCDate()).padStart(2, '0');
|
||||||
|
const hours = String(now.getUTCHours()).padStart(2, '0');
|
||||||
|
const minutes = String(now.getUTCMinutes()).padStart(2, '0');
|
||||||
|
const seconds = String(now.getUTCSeconds()).padStart(2, '0');
|
||||||
|
|
||||||
|
return `${year}${month}${day}T${hours}${minutes}${seconds}Z`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 哈希 payload
|
||||||
|
* @param {string} payload 请求体内容
|
||||||
|
* @returns {string} 哈希后的16进制字符串
|
||||||
|
*/
|
||||||
|
hashPayload(payload) {
|
||||||
|
if (typeof payload !== 'string') {
|
||||||
|
payload = '';
|
||||||
|
}
|
||||||
|
return crypto.createHash('sha256').update(payload).digest('hex').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建规范查询字符串
|
||||||
|
* @param {Object} params 查询参数对象
|
||||||
|
* @returns {string} 规范化的查询字符串
|
||||||
|
*/
|
||||||
|
createCanonicalQueryString(params) {
|
||||||
|
// 对参数名和值进行 URI 编码
|
||||||
|
const encodedParams = {};
|
||||||
|
for (const key in params) {
|
||||||
|
if (params.hasOwnProperty(key)) {
|
||||||
|
const encodedKey = this.uriEncode(key);
|
||||||
|
const encodedValue = this.uriEncode(params[key].toString());
|
||||||
|
encodedParams[encodedKey] = encodedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按 ASCII 顺序排序
|
||||||
|
const sortedKeys = Object.keys(encodedParams).sort();
|
||||||
|
|
||||||
|
// 构建查询字符串
|
||||||
|
return sortedKeys.map(key => `${key}=${encodedParams[key]}`).join('&');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URI 编码 (符合 AWS 规范)
|
||||||
|
* @param {string} str 要编码的字符串
|
||||||
|
* @returns {string} 编码后的字符串
|
||||||
|
*/
|
||||||
|
uriEncode(str) {
|
||||||
|
return encodeURIComponent(str)
|
||||||
|
.replace(/[^A-Za-z0-9\-_.~]/g, c =>
|
||||||
|
'%' + c.charCodeAt(0).toString(16).toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建规范请求
|
||||||
|
* @param {string} method HTTP 方法
|
||||||
|
* @param {string} url 请求 URL
|
||||||
|
* @param {string} queryString 查询字符串
|
||||||
|
* @param {Object} headers 请求头
|
||||||
|
* @param {string} hashedPayload 哈希后的 payload
|
||||||
|
* @returns {string} 规范化的请求字符串
|
||||||
|
*/
|
||||||
|
createCanonicalRequest(method, url, queryString, headers, hashedPayload) {
|
||||||
|
// 获取规范 URI
|
||||||
|
const urlObj = new URL(url, 'http://dummy.com'); // 使用虚拟基础 URL 来解析路径
|
||||||
|
const canonicalUri = this.uriEncodePath(urlObj.pathname) || '/';
|
||||||
|
|
||||||
|
// 获取规范 headers 和 signed headers
|
||||||
|
const { canonicalHeaders, signedHeaders } = this.createCanonicalHeaders(headers);
|
||||||
|
|
||||||
|
return [
|
||||||
|
method.toUpperCase(),
|
||||||
|
canonicalUri,
|
||||||
|
queryString,
|
||||||
|
canonicalHeaders,
|
||||||
|
signedHeaders,
|
||||||
|
hashedPayload
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URI 编码路径部分
|
||||||
|
* @param {string} path 路径
|
||||||
|
* @returns {string} 编码后的路径
|
||||||
|
*/
|
||||||
|
uriEncodePath(path) {
|
||||||
|
// 分割路径为各个部分,分别编码
|
||||||
|
return path.split('/').map(part => this.uriEncode(part)).join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建规范 headers 和 signed headers
|
||||||
|
* @param {Object} headers 原始请求头
|
||||||
|
* @returns {Object} { canonicalHeaders: string, signedHeaders: string }
|
||||||
|
*/
|
||||||
|
createCanonicalHeaders(headers) {
|
||||||
|
// 处理 headers
|
||||||
|
const headerMap:any = {};
|
||||||
|
|
||||||
|
// 标准化 headers
|
||||||
|
for (const key in headers) {
|
||||||
|
if (headers.hasOwnProperty(key)) {
|
||||||
|
const lowerKey = key.toLowerCase();
|
||||||
|
let value = headers[key]
|
||||||
|
if (value) {
|
||||||
|
value = value.toString().replace(/\s+/g, ' ').trim();
|
||||||
|
headerMap[lowerKey] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保 host 和 x-amz-date 存在
|
||||||
|
if (!headerMap.host) {
|
||||||
|
const url = headers.host ||this.endpoint || 'cdn.api.ksyun.com'; // 默认值
|
||||||
|
headerMap.host = url.replace(/^https?:\/\//, '').split('/')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按 header 名称排序
|
||||||
|
const sortedHeaderNames = Object.keys(headerMap).sort();
|
||||||
|
|
||||||
|
// 构建规范 headers
|
||||||
|
let canonicalHeaders = '';
|
||||||
|
for (const name of sortedHeaderNames) {
|
||||||
|
canonicalHeaders += `${name}:${headerMap[name]}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建 signed headers
|
||||||
|
const signedHeaders = sortedHeaderNames.join(';');
|
||||||
|
|
||||||
|
return { canonicalHeaders, signedHeaders };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 signed headers
|
||||||
|
* @param {Object} headers 请求头
|
||||||
|
* @returns {string} signed headers 字符串
|
||||||
|
*/
|
||||||
|
getSignedHeaders(headers) {
|
||||||
|
const { signedHeaders } = this.createCanonicalHeaders(headers);
|
||||||
|
return signedHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建信任状范围
|
||||||
|
* @param {string} requestDate 请求日期 (YYYYMMDDTHHMMSSZ)
|
||||||
|
* @returns {string} 信任状范围字符串
|
||||||
|
*/
|
||||||
|
createCredentialScope(requestDate) {
|
||||||
|
const date = requestDate.split('T')[0];
|
||||||
|
return `${date}/${this.region}/${this.service}/aws4_request`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建签名字符串
|
||||||
|
* @param {string} requestDate 请求日期
|
||||||
|
* @param {string} credentialScope 信任状范围
|
||||||
|
* @param {string} canonicalRequest 规范请求
|
||||||
|
* @returns {string} 签名字符串
|
||||||
|
*/
|
||||||
|
createStringToSign(requestDate, credentialScope, canonicalRequest) {
|
||||||
|
const algorithm = 'AWS4-HMAC-SHA256';
|
||||||
|
const hashedCanonicalRequest = crypto.createHash('sha256')
|
||||||
|
.update(canonicalRequest)
|
||||||
|
.digest('hex')
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
return [
|
||||||
|
algorithm,
|
||||||
|
requestDate,
|
||||||
|
credentialScope,
|
||||||
|
hashedCanonicalRequest
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算签名
|
||||||
|
* @param {string} requestDate 请求日期
|
||||||
|
* @param {string} stringToSign 签名字符串
|
||||||
|
* @returns {string} 签名值
|
||||||
|
*/
|
||||||
|
calculateSignature(requestDate, stringToSign) {
|
||||||
|
const date = requestDate.split('T')[0];
|
||||||
|
const kDate = this.hmac(`AWS4${this.secretAccessKey}`, date);
|
||||||
|
const kRegion = this.hmac(kDate, this.region);
|
||||||
|
const kService = this.hmac(kRegion, this.service);
|
||||||
|
const kSigning = this.hmac(kService, 'aws4_request');
|
||||||
|
|
||||||
|
return this.hmac(kSigning, stringToSign, 'hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HMAC-SHA256 计算
|
||||||
|
* @param {string|Buffer} key 密钥
|
||||||
|
* @param {string} data 数据
|
||||||
|
* @param {string} [encoding] 输出编码
|
||||||
|
* @returns {string|Buffer} HMAC 结果
|
||||||
|
*/
|
||||||
|
hmac(key, data, encoding = null) {
|
||||||
|
const hmac = crypto.createHmac('sha256', key);
|
||||||
|
hmac.update(data);
|
||||||
|
return encoding ? hmac.digest(encoding) : hmac.digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Authorization 头
|
||||||
|
* @param {string} credentialScope 信任状范围
|
||||||
|
* @param {string} signedHeaders signed headers
|
||||||
|
* @param {string} signature 签名值
|
||||||
|
* @returns {string} Authorization 头值
|
||||||
|
*/
|
||||||
|
createAuthorizationHeader(credentialScope, signedHeaders, signature) {
|
||||||
|
return `AWS4-HMAC-SHA256 Credential=${this.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./plugins/index.js";
|
||||||
|
export * from "./access.js";
|
|
@ -0,0 +1 @@
|
||||||
|
import "./plugin-refresh-cert.js"
|
|
@ -0,0 +1,133 @@
|
||||||
|
import {
|
||||||
|
AbstractTaskPlugin,
|
||||||
|
IsTaskPlugin,
|
||||||
|
Pager,
|
||||||
|
PageSearch,
|
||||||
|
pluginGroups,
|
||||||
|
RunStrategy,
|
||||||
|
TaskInput
|
||||||
|
} from "@certd/pipeline";
|
||||||
|
import {CertApplyPluginNames, CertInfo, CertReader} from "@certd/plugin-cert";
|
||||||
|
import {createCertDomainGetterInputDefine, createRemoteSelectInputDefine} from "@certd/plugin-lib";
|
||||||
|
import {KsyunAccess} from "../access.js";
|
||||||
|
|
||||||
|
@IsTaskPlugin({
|
||||||
|
//命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名
|
||||||
|
name: "KsyunRefreshCert",
|
||||||
|
title: "金山云-更新CDN证书",
|
||||||
|
desc: "金山云自动更新CDN证书",
|
||||||
|
icon: "svg:icon-lucky",
|
||||||
|
//插件分组
|
||||||
|
group: pluginGroups.cdn.key,
|
||||||
|
needPlus: false,
|
||||||
|
default: {
|
||||||
|
//默认值配置照抄即可
|
||||||
|
strategy: {
|
||||||
|
runStrategy: RunStrategy.SkipWhenSucceed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
//类名规范,跟上面插件名称(name)一致
|
||||||
|
export class KsyunRefreshCDNCert extends AbstractTaskPlugin {
|
||||||
|
//证书选择,此项必须要有
|
||||||
|
@TaskInput({
|
||||||
|
title: "域名证书",
|
||||||
|
helper: "请选择前置任务输出的域名证书",
|
||||||
|
component: {
|
||||||
|
name: "output-selector",
|
||||||
|
from: [...CertApplyPluginNames]
|
||||||
|
}
|
||||||
|
// required: true, // 必填
|
||||||
|
})
|
||||||
|
cert!: CertInfo;
|
||||||
|
|
||||||
|
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
||||||
|
certDomains!: string[];
|
||||||
|
|
||||||
|
//授权选择框
|
||||||
|
@TaskInput({
|
||||||
|
title: "金山云授权",
|
||||||
|
component: {
|
||||||
|
name: "access-selector",
|
||||||
|
type: "ksyun" //固定授权类型
|
||||||
|
},
|
||||||
|
required: true //必填
|
||||||
|
})
|
||||||
|
accessId!: string;
|
||||||
|
//
|
||||||
|
|
||||||
|
@TaskInput(
|
||||||
|
createRemoteSelectInputDefine({
|
||||||
|
title: "证书Id",
|
||||||
|
helper: "要更新的金山云CDN证书id,如果这里没有,请先给cdn域名手动绑定一次证书",
|
||||||
|
action: KsyunRefreshCDNCert.prototype.onGetCertList.name,
|
||||||
|
pager: false,
|
||||||
|
search: false
|
||||||
|
})
|
||||||
|
)
|
||||||
|
certList!: string[];
|
||||||
|
|
||||||
|
//插件实例化时执行的方法
|
||||||
|
async onInstance() {
|
||||||
|
}
|
||||||
|
|
||||||
|
//插件执行方法
|
||||||
|
async execute(): Promise<void> {
|
||||||
|
const access = await this.getAccess<KsyunAccess>(this.accessId);
|
||||||
|
|
||||||
|
const certReader = new CertReader(this.cert)
|
||||||
|
const certName = certReader.buildCertName()
|
||||||
|
const client = await access.getCdnClient();
|
||||||
|
for (const item of this.certList) {
|
||||||
|
this.logger.info(`----------- 开始更新证书:${item}`);
|
||||||
|
await access.updateCert({
|
||||||
|
client,
|
||||||
|
certId: item,
|
||||||
|
certName,
|
||||||
|
cert: this.cert
|
||||||
|
});
|
||||||
|
this.logger.info(`----------- 更新证书${item}成功`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info("部署完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
async onGetCertList(data: PageSearch = {}) {
|
||||||
|
const access = await this.getAccess<KsyunAccess>(this.accessId);
|
||||||
|
|
||||||
|
const client = await access.getCdnClient();
|
||||||
|
const pager = new Pager(data)
|
||||||
|
const res = await access.getCertList({client,
|
||||||
|
pageNo: pager.pageNo ,
|
||||||
|
pageSize: pager.pageSize
|
||||||
|
})
|
||||||
|
const list = res.Certificates
|
||||||
|
if (!list || list.length === 0) {
|
||||||
|
throw new Error("没有找到证书,请先在控制台手动上传一次证书");
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = res.TotalCount
|
||||||
|
|
||||||
|
/**
|
||||||
|
* certificate-id
|
||||||
|
* name
|
||||||
|
* dns-names
|
||||||
|
*/
|
||||||
|
const options = list.map((item: any) => {
|
||||||
|
return {
|
||||||
|
label: `${item.CertificateName}<${item.CertificateId}-${item.ConfigDomainNames}>`,
|
||||||
|
value: item.CertificateId,
|
||||||
|
domain: item.ConfigDomainNames
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
list: this.ctx.utils.options.buildGroupOptions(options, this.certDomains),
|
||||||
|
total: total,
|
||||||
|
pageNo: pager.pageNo,
|
||||||
|
pageSize: pager.pageSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//实例化一下,注册插件
|
||||||
|
new KsyunRefreshCDNCert();
|
Loading…
Reference in New Issue