Compare commits

..

11 Commits

Author SHA1 Message Date
xiaojunnuo
6b29972399 chore: 修复可选链操作符和DNS管理插件问题
修复多处可选链操作符访问问题,避免潜在的空指针异常
优化DNS管理插件,移除重复的id字段并修正域名匹配逻辑
添加getDomainListPage方法以支持分页查询域名列表
2026-04-03 00:32:00 +08:00
xiaojunnuo
0fcd3c09fd Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-04-03 00:14:14 +08:00
xiaojunnuo
af503442b8 perf(plugin-dnsmgr): 添加彩虹DNS插件支持
实现彩虹DNS管理系统的插件集成,包括DNS记录创建、查询和删除功能
2026-04-03 00:14:08 +08:00
xiaojunnuo
c875971b71 perf: 优化腾讯云CLB插件支持选择证书id 2026-04-02 23:27:10 +08:00
xiaojunnuo
d1a65922d7 fix: 修复某些情况下报无法修改通知的问题 2026-04-02 16:28:14 +08:00
xiaojunnuo
6ef34f95d5 Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-04-02 14:51:54 +08:00
xiaojunnuo
8b79022179 chore: 1 2026-04-02 09:05:13 +08:00
xiaojunnuo
21aec77e5c perf(spaceship): 新增Spaceship DNS插件和授权模块
添加Spaceship DNS提供商插件和授权模块,支持域名解析管理
更新相关文档和技能说明,优化错误处理和日志记录
移除调试日志,更新README项目列表
2026-04-02 00:10:28 +08:00
xiaojunnuo
74c5259af8 build: release 2026-04-01 00:29:52 +08:00
xiaojunnuo
a3e7d4414d build: publish 2026-03-31 23:59:12 +08:00
xiaojunnuo
986d32eb81 build: trigger build image 2026-03-31 23:59:00 +08:00
31 changed files with 610 additions and 62 deletions

View File

@@ -163,6 +163,16 @@ async doRequest(req: { action: string, data?: any }) {
}
```
--- 开发技巧:实现统一的 API 请求封装
**好处:**
- **代码复用**:避免在每个 API 方法中重复编写相同的 header 设置和错误处理逻辑
- **错误处理一致**:统一捕获和处理各种错误情况,确保错误信息格式统一
- **日志记录完善**:集中记录详细的错误信息,便于调试和问题排查
- **接口调用简化**:调用方只需关注业务逻辑,无需关心底层请求细节
- **易于维护**:统一修改 API 调用方式时,只需修改一处代码
## 注意事项
1. **插件命名**:插件名称应简洁明了,反映其功能。
@@ -170,9 +180,12 @@ async doRequest(req: { action: string, data?: any }) {
3. **日志输出**:必须使用 `this.ctx.logger` 输出日志,而不是 `console`
4. **错误处理**API 调用失败时应抛出明确的错误信息。
5. **测试方法**:实现 `onTestRequest` 方法,以便用户可以测试授权是否正常。
6. **统一接口调用方法**:封装统一的 API 请求方法,避免在每个 API 方法调用中重复编写错误处理逻辑。
## 完整示例
### 示例 1: 通用授权插件
```typescript
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
import { DomainRecord } from '@certd/plugin-lib';

View File

@@ -6,9 +6,8 @@ Access存储用户的第三放应用的授权数据比如用户名密码
Task 部署任务插件它继承AbstractTaskPlugin类被流水线调用execute方法将证书部署到对应的应用上
DnsProvider: DNS提供商插件它用于在ACME申请证书时给域名添加txt解析记录。
在开始工作前,请阅读并加载.trae/skills下面的技能根据skills进行相应的插件开发
当开发过程中遇到问题需要参考plugins目录下的其他插件或者用户提醒你更好的做法时你需要总结经验更新相应的skills让skills越来越完善能够在以后得新插件开发中具备指导意义。
一般调用的api接口文档会比较复杂你不知道接口是什么时请务必询问用户让用户提供API接口文档
完成开发后无需测试,通知用户自己去测试
注意事项:
1、使用技能在开始工作前请阅读并加载.trae/skills下面的技能根据skills进行相应的插件开发
2、迭代技能当开发过程用户提醒你更好的做法时你需要总结经验更新相应的skills让skills越来越完善能够在以后得新插件开发中具备指导意义。
3、一般调用的api接口文档会比较复杂你不知道接口是什么时请务必询问用户让用户提供API接口文档
4、完成开发后无需测试通知用户自己去测试

View File

@@ -126,6 +126,8 @@ if (isDev()) {
## 完整示例
### 示例:通用 DNS Provider
```typescript
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { DemoAccess } from './access.js';

View File

@@ -211,3 +211,4 @@ https://certd.handfree.work/
| --------- |--------- |----------- |
| [fast-crud](https://gitee.com/fast-crud/fast-crud/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/fast-crud/fast-crud?logo=github"/> | 基于vue3的crud快速开发框架 |
| [dev-sidecar](https://github.com/docmirror/dev-sidecar/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/docmirror/dev-sidecar?logo=github"/> | 直连访问github工具无需FQ解决github无法访问的问题 |
| [winsvc-manager](https://github.com/greper/winsvc-manager/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/greper/winsvc-manager?logo=github"/> | 可视化包装应用成为一个Windows服务使其后台运行 |

View File

@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Bug Fixes
* 修复某些情况下报没有匹配到任何校验方式的bug ([fe02ce7](https://github.com/certd/certd/commit/fe02ce7b64cf23c4dc4c30daccd5330059a35e9a))
* 修复上传头像退出登录的bug ([6eb20a1](https://github.com/certd/certd/commit/6eb20a1f2e31d984d9135edbf39c97cdd15621f9))
### Performance Improvements
* 阿里云CDN部署支持根据证书域名自动匹配部署 ([a68301e](https://github.com/certd/certd/commit/a68301e4dcea8b7391ad751aa57555d566297ad9))
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
* 支持部署证书到百度CCE ([a19ea74](https://github.com/certd/certd/commit/a19ea7489c01cdbf795fb51f804bd6d00389f604))
* dcdn自动匹配部署支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
### Bug Fixes

View File

@@ -70,5 +70,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -47,5 +47,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -271,7 +271,7 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
}
const originalRequest = error.config || {};
logger.info(`config`, originalRequest);
// logger.info(`config`, originalRequest);
const retry = originalRequest.retry || {};
if (retry.status && retry.status.includes(status)) {
if (retry.max > 0 && retry.count < retry.max) {

View File

@@ -45,5 +45,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -24,5 +24,5 @@
"prettier": "^2.8.8",
"tslib": "^2.8.1"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -31,5 +31,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -56,5 +56,5 @@
"fetch"
]
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -33,5 +33,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -64,5 +64,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -46,5 +46,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -38,5 +38,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -57,5 +57,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -21,7 +21,8 @@ import { defineComponent, reactive, ref, watch, inject } from "vue";
import CertAccessModal from "./access/index.vue";
import { createAccessApi } from "../api";
import { message } from "ant-design-vue";
import { useUserStore } from "/@/store/user";
import { useProjectStore } from "/@/store/project";
export default defineComponent({
name: "AccessSelector",
components: { CertAccessModal },
@@ -71,11 +72,27 @@ export default defineComponent({
emitValue(null);
}
const userStore = useUserStore();
const projectStore = useProjectStore();
async function emitValue(value) {
if (pipeline && pipeline?.value && target?.value && pipeline.value.userId !== target.value.userId) {
message.error("对不起,您不能修改他人流水线的授权");
return;
const userId = userStore.userInfo.id;
const isEnterprice = projectStore.isEnterprise;
if (pipeline?.value) {
if (isEnterprice) {
const projectId = projectStore.currentProjectId;
if (pipeline?.value?.projectId !== projectId) {
message.error(`对不起,您不能修改其他项目流水线的授权`);
return;
}
} else {
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error(`对不起,您不能修改他人流水线的授权`);
return;
}
}
}
if (value == null) {
selectedId.value = "";
target.value = null;

View File

@@ -48,6 +48,7 @@ import createCrudOptions from "../crud";
import { addonProvide } from "../common";
import { useUserStore } from "/@/store/user";
import { useI18n } from "/src/locales";
import { useProjectStore } from "/@/store/project";
const { t } = useI18n();
@@ -127,13 +128,24 @@ function clear() {
}
const userStore = useUserStore();
const projectStore = useProjectStore();
async function emitValue(value: any) {
// target.value = optionsDictRef.dataMap[value];
const userId = userStore.userInfo.id;
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error(`对不起,您不能修改他人流水线的${props.addonType}设置`);
return;
if (pipeline.value) {
const userId = userStore.userInfo.id;
const isEnterprice = projectStore.isEnterprise;
if (isEnterprice) {
const projectId = projectStore.currentProjectId;
if (pipeline?.value?.projectId !== projectId) {
message.error(`对不起,您不能修改其他项目流水线的${props.addonType}设置`);
return;
}
} else {
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error(`对不起,您不能修改他人流水线的${props.addonType}设置`);
return;
}
}
}
emit("change", value);
emit("update:modelValue", value);

View File

@@ -42,6 +42,7 @@ import createCrudOptions from "../crud";
import { notificationProvide } from "/@/views/certd/notification/common";
import { useUserStore } from "/@/store/user";
import { useI18n } from "/src/locales";
import { useProjectStore } from "/@/store/project";
const { t } = useI18n();
@@ -127,13 +128,23 @@ function clear() {
}
const userStore = useUserStore();
const projectStore = useProjectStore();
async function emitValue(value: any) {
// target.value = optionsDictRef.dataMap[value];
const userId = userStore.userInfo.id;
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error("对不起,您不能修改他人流水线的通知");
return;
const isEnterprice = projectStore.isEnterprise;
if (isEnterprice) {
const projectId = projectStore.currentProjectId;
if (pipeline?.value?.projectId !== projectId) {
message.error("对不起,您不能修改其他项目流水线的通知");
return;
}
} else {
if (pipeline?.value?.userId !== userId) {
message.error("对不起,您不能修改他人流水线的通知");
return;
}
}
emit("change", value);
emit("update:modelValue", value);

View File

@@ -44,4 +44,5 @@
// export * from './plugin-lib/index.js'
// export * from './plugin-plus/index.js'
// export * from './plugin-cert/index.js'
// export * from './plugin-zenlayer/index.js'
// export * from './plugin-zenlayer/index.js'
export * from './plugin-dnsmgr/index.js'

View File

@@ -156,7 +156,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
if(maxDays < 2){
maxDays = 2;
}
this.logger.warn(`为避免每次运行都更新证书,更新天数自动减半,调整为${maxDays}`);
this.logger.warn(`为避免每次运行都更新证书,更新天数自动减半(即证书最大时长${totalDays}天减半),调整为${maxDays}`);
}
return {

View File

@@ -0,0 +1,144 @@
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
import { DomainRecord } from '@certd/plugin-lib';
@IsAccess({
name: 'dnsmgr',
title: '彩虹DNS',
icon: 'clarity:plugin-line',
desc: '彩虹DNS管理系统授权',
})
export class DnsmgrAccess extends BaseAccess {
@AccessInput({
title: '系统地址',
component: {
name: "a-input",
allowClear: true,
placeholder: 'https://dnsmgr.example.com',
},
required: true,
})
endpoint = '';
@AccessInput({
title: '用户ID',
component: {
name: "a-input",
allowClear: true,
placeholder: '123456',
},
required: true,
})
uid = '';
@AccessInput({
title: 'API密钥',
required: true,
encrypt: true,
})
key = '';
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
},
helper: "点击测试接口是否正常"
})
testRequest = true;
async onTestRequest() {
await this.GetDomainList({});
return "ok";
}
async GetDomainList(req: PageSearch): Promise<PageRes<DomainRecord>> {
this.ctx.logger.info(`获取域名列表req:${JSON.stringify(req)}`);
const pager = new Pager(req);
const resp = await this.doRequest({
url: '/api/domain',
data: {
offset: pager.getOffset(),
limit: pager.pageSize,
kw: req.searchKey,
},
});
const total = resp?.total || 0;
let list = resp?.rows?.map((item: any) => {
return {
domain: item.name,
...item,
};
});
return {
total,
list,
};
}
async createDnsRecord(domainId: string, record: string, value: string, type: string, domain: string) {
this.ctx.logger.info(`创建DNS记录${record} ${type} ${value}`);
const resp = await this.doRequest({
url: `/api/record/add/${domainId}`,
data: {
name: record.replace(`.${domain}`, ''),
type,
value,
line: 'default',
ttl: 600,
},
});
return resp;
}
async getDnsRecords(domainId: string, type?: string, name?: string, value?: string) {
this.ctx.logger.info(`获取DNS记录列表domainId=${domainId}, type=${type}, name=${name}`);
const resp = await this.doRequest({
url: `/api/record/data/${domainId}`,
data: {
type,
subdomain: name,
value,
},
});
return resp;
}
async deleteDnsRecord(domainId: string, recordId: string) {
this.ctx.logger.info(`删除DNS记录domainId=${domainId}, recordId=${recordId}`);
const resp = await this.doRequest({
url: `/api/record/delete/${domainId}`,
data: {
recordid: recordId,
},
});
return resp;
}
async doRequest(req: { url: string; data?: any }) {
const timestamp = Math.floor(Date.now() / 1000);
const sign = this.ctx.utils.hash.md5(`${this.uid}${timestamp}${this.key}`);
const url = `${this.endpoint}${req.url}`;
const res = await this.ctx.http.request({
url,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
data: {
uid: this.uid,
timestamp,
sign,
...req.data,
},
});
if (res.code !== undefined && res.code !== 0) {
throw new Error(res.msg || '请求失败');
}
return res;
}
}

View File

@@ -0,0 +1,71 @@
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { DnsmgrAccess } from './access.js';
import { PageRes, PageSearch } from '@certd/pipeline';
type DnsmgrRecord = {
domainId: string;
name: string;
value: string;
};
@IsDnsProvider({
name: 'dnsmgr',
title: '彩虹DNS',
desc: '彩虹DNS管理系统',
icon: 'clarity:plugin-line',
accessType: 'dnsmgr',
order: 99,
})
export class DnsmgrDnsProvider extends AbstractDnsProvider<DnsmgrRecord> {
access!: DnsmgrAccess;
async onInstance() {
this.access = this.ctx.access as DnsmgrAccess;
this.logger.debug('access', this.access);
}
async createRecord(options: CreateRecordOptions): Promise<any> {
const { fullRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
const domainList = await this.access.GetDomainList({ searchKey: domain });
const domainInfo = domainList.list?.find((item: any) => item.name === domain);
if (!domainInfo) {
throw new Error(`未找到域名:${domain}`);
}
const name = fullRecord.replace(`.${domain}`, '');
const res = await this.access.createDnsRecord(domainInfo.id, fullRecord, value, type, domain);
return { domainId: domainInfo.id, name, value,res };
}
async removeRecord(options: RemoveRecordOptions<DnsmgrRecord>): Promise<void> {
const { fullRecord, value } = options.recordReq;
const record = options.recordRes;
this.logger.info('删除域名解析:', fullRecord, value, record);
if (record && record.domainId) {
const records = await this.access.getDnsRecords(record.domainId, 'TXT', record.name, record.value);
if (records && records.rows && records.rows.length > 0) {
const recordToDelete = records.rows[0];
await this.access.deleteDnsRecord(record.domainId, recordToDelete.RecordId);
}
}
this.logger.info('删除域名解析成功:', fullRecord, value);
}
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
const res = await this.access.GetDomainList(req);
res.list = res.list?.map((item: any) => {
return {
id: item.id,
domain: item.name,
};
});
return res;
}
}
new DnsmgrDnsProvider();

View File

@@ -0,0 +1,2 @@
export * from './access.js';
export * from './dns-provider.js';

View File

@@ -0,0 +1,148 @@
import { IsAccess, AccessInput, BaseAccess, PageSearch } from "@certd/pipeline";
@IsAccess({
name: "spaceship",
title: "Spaceship.com 授权",
icon: "clarity:plugin-line",
desc: "Spaceship.com API 授权插件"
})
export class SpaceshipAccess extends BaseAccess {
@AccessInput({
title: "API Key",
component: {
placeholder: "请输入 API Key"
},
required: true,
encrypt: true,
helper: "前往 [获取 API Key](https://www.spaceship.com/application/api-manager/)"
})
apiKey = "";
@AccessInput({
title: "API Secret",
component: {
name: "a-input-password",
vModel: "value",
placeholder: "请输入 API Secret"
},
required: true,
encrypt: true
})
apiSecret = "";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
},
helper: "测试 API 连接是否正常"
})
testRequest = true;
async onTestRequest() {
await this.GetDomainList({});
return "ok";
}
async doRequest(options: {
url: string;
method: 'GET' | 'POST' | 'DELETE';
params?: any;
data?: any;
}) {
const headers = {
"X-Api-Key": this.apiKey,
"X-Api-Secret": this.apiSecret
};
try {
const res = await this.ctx.http.request({
url: options.url,
method: options.method,
headers,
params: options.params,
data: options.data
});
return res;
} catch (error: any) {
const errorMsg = [];
const status = error.status || error.response?.status;
if (error.response) {
const headers = error.response.headers;
const data = error.response.data;
errorMsg.push(`API 请求失败: ${status}`);
if (headers['spaceship-error-code']) {
errorMsg.push(`错误代码: ${headers['spaceship-error-code']}`);
}
if (headers['spaceship-operation-id']) {
errorMsg.push(`操作ID: ${headers['spaceship-operation-id']}`);
}
if (data && data.detail) {
errorMsg.push(`错误详情: ${data.detail}`);
}
this.ctx.logger.error(`Spaceship API 错误: ${errorMsg.join(' | ')}`);
} else if (error.request) {
errorMsg.push(`请求发送失败: ${error.message}`);
this.ctx.logger.error(`Spaceship API 请求发送失败: ${error.message}`);
} else {
errorMsg.push(`请求配置错误: ${error.message}`);
this.ctx.logger.error(`Spaceship API 请求配置错误: ${error.message}`);
}
const error2 = new Error(errorMsg.join(' | '));
//@ts-ignore
error2.status = status;
throw error2;
}
}
async GetDomainList(req: PageSearch) {
const take = req.pageSize || 100;
const skip = ((req.pageNo || 1) - 1) * take;
const res = await this.doRequest({
url: "https://spaceship.dev/api/v1/domains",
method: "GET",
params: {
take,
skip
}
});
return {
total: res.total || 0,
list: res.items || []
};
}
async getDomainInfo(domain: string) {
try {
const res = await this.doRequest({
url: `https://spaceship.dev/api/v1/domains/${domain}`,
method: "GET"
});
return res;
} catch (error: any) {
if (error.status === 404) {
throw new Error(`域名 ${domain} 不存在于当前账号中`);
}
throw error;
}
}
getCacheKey() {
const hashStr = this.apiKey + this.apiSecret;
const hashCode = this.ctx.utils.hash.sha256(hashStr);
return `spaceship-${hashCode}`;
}
}
new SpaceshipAccess();

View File

@@ -0,0 +1,95 @@
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
import { SpaceshipAccess } from "./access.js";
import { PageRes, PageSearch } from "@certd/pipeline";
export type SpaceshipRecord = {
id: string;
name: string;
type: string;
content: string;
domainId: string;
};
@IsDnsProvider({
name: "spaceship",
title: "Spaceship",
desc: "Spaceship 域名解析",
icon: "clarity:plugin-line",
accessType: "spaceship",
order: 99
})
export class SpaceshipProvider extends AbstractDnsProvider<SpaceshipRecord> {
access!: SpaceshipAccess;
async onInstance() {
this.access = this.ctx.access as SpaceshipAccess;
}
async createRecord(options: CreateRecordOptions): Promise<SpaceshipRecord> {
const { fullRecord, hostRecord, value, type, domain } = options;
this.logger.info("添加域名解析:", fullRecord, value, type, domain);
await this.access.getDomainInfo(domain);
const recordRes = await this.access.doRequest({
url: `https://spaceship.dev/api/v1/domains/${domain}/records`,
method: "POST",
data: {
force: false,
items: [
{
type: type,
value: value,
name: hostRecord,
ttl: 300
}
]
}
});
return {
id: recordRes.items[0].id,
name: hostRecord,
type: type,
content: value,
domainId: domain
};
}
async removeRecord(options: RemoveRecordOptions<SpaceshipRecord>): Promise<void> {
const recordRes = options.recordRes;
this.logger.info("删除域名解析:", recordRes);
await this.access.doRequest({
url: `https://spaceship.dev/api/v1/domains/${recordRes.domainId}/records`,
method: "DELETE",
data: {
Records: [
{
type: recordRes.type,
value: recordRes.content,
name: recordRes.name
}
]
}
});
this.logger.info("删除域名解析成功:", recordRes.name);
}
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
const res = await this.access.GetDomainList(req);
const list = res.list.map((item: any) => ({
domain: item.name,
id: item.name
}));
return {
total: res.total || 0,
list: list || []
};
}
}
new SpaceshipProvider();

View File

@@ -0,0 +1,2 @@
import "./access.js";
import "./dns-provider.js";

View File

@@ -1,7 +1,7 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import dayjs from 'dayjs';
import { TencentAccess } from '../../../plugin-lib/tencent/index.js';
import { CertApplyPluginNames} from '@certd/plugin-cert';
import { CertApplyPluginNames, CertInfo } from '@certd/plugin-cert';
@IsTaskPlugin({
name: 'DeployCertToTencentCLB',
title: '腾讯云-部署到CLB',
@@ -15,6 +15,31 @@ import { CertApplyPluginNames} from '@certd/plugin-cert';
},
})
export class DeployCertToTencentCLB extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames, 'UploadCertToTencent'],
},
required: true,
})
cert!: string | CertInfo;
@TaskInput({
title: 'Access提供者',
helper: 'access授权',
component: {
name: 'access-selector',
type: 'tencent',
},
required: true,
})
accessId!: string;
@TaskInput({
title: '大区',
component: {
@@ -46,14 +71,10 @@ export class DeployCertToTencentCLB extends AbstractTaskPlugin {
})
region!: string;
@TaskInput({
title: '证书名称前缀',
})
certName!: string;
@TaskInput({
title: '负载均衡ID',
helper: '如果没有配置则根据域名匹配负载均衡下的监听器根据域名匹配时暂时只支持前100个',
required: true,
})
loadBalancerId!: string;
@@ -78,26 +99,10 @@ export class DeployCertToTencentCLB extends AbstractTaskPlugin {
domain!: string | string[];
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames],
},
required: true,
title: '证书名称前缀',
})
cert!: any;
certName!: string;
@TaskInput({
title: 'Access提供者',
helper: 'access授权',
component: {
name: 'access-selector',
type: 'tencent',
},
required: true,
})
accessId!: string;
client: any;
async onInstance() {
@@ -234,12 +239,23 @@ export class DeployCertToTencentCLB extends AbstractTaskPlugin {
return name + '-' + dayjs().format('YYYYMMDD-HHmmss');
}
buildProps() {
const certId = this.cert as string;
const certInfo = this.cert as CertInfo;
if (typeof this.cert === 'string') {
return {
Certificate: {
CertId: certId,
},
LoadBalancerId: this.loadBalancerId,
ListenerId: this.listenerId,
};
}
return {
Certificate: {
SSLMode: 'UNIDIRECTIONAL', // 单向认证
CertName: this.appendTimeSuffix(this.certName || this.cert.domain),
CertKey: this.cert.key,
CertContent: this.cert.crt,
CertName: this.appendTimeSuffix(this.certName || "certd"),
CertKey: certInfo.key,
CertContent: certInfo.crt,
},
LoadBalancerId: this.loadBalancerId,
ListenerId: this.listenerId,

View File

@@ -1 +1 @@
01:07
23:58

View File

@@ -1 +1 @@
01:28
00:29