Compare commits

...

20 Commits

Author SHA1 Message Date
xiaojunnuo
2942d39dfe v0.1.11 2021-01-18 22:30:04 +08:00
xiaojunnuo
df65b0509e Merge remote-tracking branch 'origin/master' 2021-01-18 22:29:36 +08:00
xiaojunnuo
fbde35483b refactor: 去掉可选链 2021-01-18 22:28:41 +08:00
xiaojunnuo
7370f8b83b docs: cron 2021-01-15 17:03:35 +08:00
xiaojunnuo
8b0ca1da2e perf: 小优化 2021-01-14 23:04:47 +08:00
xiaojunnuo
466f2b1a02 refactor: md 2021-01-12 09:44:41 +08:00
xiaojunnuo
f1d6cce88c refactor: md 2021-01-12 09:31:04 +08:00
xiaojunnuo
ad7ababb4c docs: md 2021-01-11 23:15:35 +08:00
xiaojunnuo
72fa623674 docs: md 2021-01-11 23:08:24 +08:00
xiaojunnuo
e5d117c134 docs: md 2021-01-11 23:04:23 +08:00
xiaojunnuo
f8944a1331 docs: license 2021-01-11 22:56:55 +08:00
xiaojunnuo
e850855154 docs: md 2021-01-11 22:52:56 +08:00
xiaojunnuo
06eacee90c docs: md 2021-01-11 22:49:24 +08:00
xiaojunnuo
b1e100982e refactor: md 2021-01-11 19:00:12 +08:00
xiaojunnuo
8f30158b00 refactor: 1 2021-01-08 17:17:54 +08:00
xiaojunnuo
576f7db978 refactor: 1 2021-01-08 17:14:15 +08:00
xiaojunnuo
137e043dfe refactor: 1 2021-01-08 16:59:53 +08:00
xiaojunnuo
15467fc233 v0.1.10 2021-01-08 15:57:57 +08:00
xiaojunnuo
eec0fcdcf1 refactor: 1 2021-01-08 15:57:38 +08:00
xiaojunnuo
a41dee015e fix: 修复plugins为null的问题 2021-01-08 15:56:50 +08:00
30 changed files with 2543 additions and 414 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Greper
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

196
README.md
View File

@@ -0,0 +1,196 @@
# CertD
CertD 是一个帮助你全自动申请和部署SSL证书的工具。
后缀D取自linux守护进程的命名风格意为证书守护进程。
## 特性
本项目不仅支持证书申请过程自动化,还可以自动化部署证书,让你的证书永不过期。
* 全自动申请证书
* 全自动部署证书(目前支持服务器上传部署、阿里云、腾讯云等)
* 可与CI/DI工具结合使用
## 免费证书申请说明
* 本项目ssl证书提供商为letencrypt
* 申请过程遵循acme协议
* 需要验证域名所有权一般有两种方式目前本项目仅支持dns-01
* http-01 在网站根目录下放置一份txt文件
* dns-01 需要给域名添加txt解析记录泛域名只能用这种方式
* 证书续期:
* 实际上acme并没有续期概念。
* 我们所说的续期,其实就是按照全套流程重新申请一份新证书。
* 免费证书过期时间90天以后可能还会缩短所以自动化部署必不可少
## 快速开始
本案例演示如何配置自动申请证书并部署到阿里云CDN然后快要到期前自动更新证书并重新部署
1. 环境准备
安装[nodejs](https://nodejs.org/zh-cn/)
2. 创建任务项目
```
mkdir certd-run # 项目名称可以任意命名
cd certd-run -y
npm install @certd/executor -s --production
```
3. 创建index.js
参数配置分几个部分
args: 运行时参数
accessProviders: 授权提供者提供dns验证与部署任务的授权
cert: 证书申请的配置
deploy 证书部署流程
```js
import { Executor } from '@certd/executor'
const options = {
args: { // 运行时参数
forceDeploy: true,
},
accessProviders: { //授权提供者
aliyun: { // 阿里云accessKey用于dns验证和上传证书到阿里云并部署到cdn
providerType: 'aliyun',
accessKeyId: 'Your accessKeyId',
accessKeySecret: 'Your accessKeySecret'
},
},
cert: { //免费证书申请配置
domains: [ //可以在一张证书上绑定多个域名前提是他们的验证方式要一样目前仅支持dns验证
'*.yourdomain.com',
'*.test.yourdomain.com',
'yourdomain.com'
],
email: 'Your email',
dnsProvider: 'aliyun', //上方accessProviders里面配置的
csrInfo: { //证书csr信息
country: 'CN',
state: 'GuangDong',
locality: 'ShengZhen',
organization: 'Your company Org.',
organizationUnit: 'IT Department',
emailAddress: 'Your email'
}
},
deploy: [ //部署流程配置,数组,可以配置多条流程
{
deployName: '流程1-部署到阿里云CDN',
tasks: [ //流程任务,一个流程下可以包含多个部署任务,并且将按顺序执行
{ //任务1
taskName: '上传到阿里云', //任务名称
type: 'uploadCertToAliyun', //任务插件名称
props: { //任务插件参数
accessProvider: 'aliyun'
}
},
{ // 任务2
taskName: '部署证书到CDN',
type: 'deployCertToAliyunCDN', //任务插件名称
props:{
domainName: 'your cdn domain 全称', //cdn域名全称
certName: 'certd自动部署',//证书名称前缀
accessProvider: 'aliyun'
}
}
]
}
]
}
const executor = new Executor()
await executor.run(options)
```
4. 运行
```
node index.js
```
5. 执行效果
生成的证书默认会存储在 `${home}/.certd/${email}/certs/${domain}/current`目录下
```
[2021-01-08T16:15:04.681] [INFO] certd - 任务完成
[2021-01-08T16:15:04.681] [INFO] certd - ---------------------------任务结果总览--------------------------
[2021-01-08T16:15:04.682] [INFO] certd - 【更新证书】--------------------------------------- [success]
证书申请成功
[2021-01-08T16:15:04.682] [INFO] certd - 【流程1-部署到阿里云CDN】---------------------------- [success] 执行成功
[2021-01-08T16:15:04.682] [INFO] certd - └【上传到阿里云】-------------------------------- [success] 执行成功
[2021-01-08T16:15:04.682] [INFO] certd - └【部署证书到CDN】------------------------------- [success] 执行成功
```
6. 证书续期
实际上没有证书续期的概念,只有重新生成一份新的证书,然后重新部署证书
所以每天定时运行即可当证书过期日前20天时会重新申请新的证书然后执行部署任务。
7. 其他说明
证书的部署任务执行后会记录执行结果,已经成功过的不会重复执行
所以当你临时需要将证书部署到其他地方时,直接追加部署任务,然后再次运行即可
## CI/DI集成与自动续期重新部署
集成前将以上代码提交到内网git仓库或者私有git仓库由于包含敏感信息不要提交到公开git仓库
### jenkins任务
1. 创建任务
选择构建自由风格的任务
2. 配置git
配置cert-run的git地址
3. 构建触发器
配置 `H 3 * * *` 每天凌晨3点-4点执行一次
4. 构建环境
勾选 `Provide Node & npm bin/ folder to PATH`提供nodejs运行环境
如果没有此选项需要jenkins安装`nodejs`插件
5. 构建
执行shell
```
npm install --production #执行过一次之后,就可以注释掉,加快执行速度
npm run post
```
6. 构建后操作
邮件通知
配置你的邮箱地址,可以在执行失败时收到邮件通知。
## API
先列个提纲,待完善
参数示例参考https://gitee.com/certd/certd/blob/master/test/options.js
### 授权提供者
用于dns验证接口调用
#### aliyun
#### dnspod
### deploy插件
部署任务插件
#### 阿里云
##### 上传到阿里云
type = uploadCertToAliyun
##### 部署到阿里云DNS
type = deployCertToAliyunCDN
##### 部署到阿里云CLB
type = deployCertToAliyunCLB
#### 腾讯云
##### 上传到腾讯云
type = uploadCertToTencent
##### 部署到腾讯云DNS
type = deployCertToTencentDNS
##### 部署到腾讯云CLB
type = deployCertToTencentCLB
##### 部署到腾讯云TKE-ingress
type = deployCertToTencentTKEIngress
### 更多部署插件
等你来提需求

View File

@@ -2,5 +2,5 @@
"packages": [
"packages/*"
],
"version": "0.1.9"
"version": "0.1.11"
}

View File

@@ -6,10 +6,9 @@
"lerna": "^3.18.4"
},
"scripts": {
"submodule": "git submodule update --init --recursive"
},
"license": "MIT",
"dependencies": {
"lodash": "^4.17.20"
"lodash-es": "^4.17.20"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/api",
"version": "0.1.7",
"version": "0.1.11",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,28 +1,25 @@
{
"name": "@certd/api",
"version": "0.1.7",
"description": "",
"main": "./src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"type": "module",
"author": "Greper",
"license": "MIT",
"dependencies": {
"axios": "^0.21.1",
"dayjs": "^1.9.7",
"lodash-es": "^4.17.20",
"log4js": "^6.3.0",
"qs": "^6.9.4"
},
"devDependencies": {
"chai": "^4.2.0",
"eslint": "^7.15.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"mocha": "^8.2.1"
}
"name": "@certd/api",
"version": "0.1.11",
"description": "",
"main": "./src/index.js",
"type": "module",
"author": "Greper",
"license": "MIT",
"dependencies": {
"axios": "^0.21.1",
"dayjs": "^1.9.7",
"lodash-es": "^4.17.20",
"log4js": "^6.3.0",
"qs": "^6.9.4"
},
"devDependencies": {
"chai": "^4.2.0",
"eslint": "^7.15.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"mocha": "^8.2.1"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/certd",
"version": "0.1.7",
"version": "0.1.11",
"description": "",
"main": "./src/index.js",
"scripts": {
@@ -11,8 +11,8 @@
"license": "MIT",
"dependencies": {
"@certd/acme-client": "^0.1.6",
"@certd/api": "^0.1.7",
"@certd/providers": "^0.1.7",
"@certd/api": "^0.1.11",
"@certd/providers": "^0.1.11",
"dayjs": "^1.9.7",
"lodash-es": "^4.17.20",
"node-forge": "^0.10.0"

View File

@@ -2,16 +2,30 @@ import { util, Store } from '@certd/api'
import { AcmeService } from './acme.js'
import { FileStore } from './store/file-store.js'
import { CertStore } from './store/cert-store.js'
import { DnsProviderFactory } from '@certd/providers'
import dayjs from 'dayjs'
import forge from 'node-forge'
import DefaultProviders from '@certd/providers'
import _ from 'lodash-es'
const logger = util.logger
const AccessProviderClasses = {}
function install (providerClass) {
AccessProviderClasses[providerClass.name()] = providerClass
}
logger.info('use')
_.forEach(DefaultProviders, item => {
logger.info('use:', item.name())
install(item)
})
export class Certd {
static use (providerClass) {
install(providerClass)
}
constructor (options) {
this.options = options
this.email = options.cert.email
this.domains = options.cert.domains
this.domain = this.getMainDomain(options.cert.domains)
if (!(options.store instanceof Store)) {
this.store = new FileStore(options.store || {})
@@ -19,33 +33,11 @@ export class Certd {
this.certStore = new CertStore({
store: this.store,
email: options.cert.email,
domain: this.domain
domains: this.domains
})
this.acme = new AcmeService(this.store)
}
getMainDomain (domains) {
if (domains == null) {
return null
}
if (typeof domains === 'string') {
return domains
}
if (domains.length > 0) {
return domains[0]
}
}
//
// buildDomainFileName (domains) {
// const domain = this.getMainDomain(domains)
// return domain.replace(/\*/g, '_')
// }
//
// buildCertDir (email, domains) {
// const domainFileName = this.buildDomainFileName(domains)
// return path.join(email, '/certs/', domainFileName)
// }
async certApply () {
let oldCert
try {
@@ -97,7 +89,7 @@ export class Certd {
createDnsProvider (options) {
const accessProviders = options.accessProviders
const providerOptions = accessProviders[options.cert.dnsProvider]
return DnsProviderFactory.createByType(providerOptions.providerType, providerOptions)
return this.createProviderByType(providerOptions.providerType, providerOptions)
}
async writeCert (cert) {
@@ -114,7 +106,7 @@ export class Certd {
return null
}
const { detail, expires } = this.getCrtDetail(cert.crt)
const domain = this.getMainDomain(this.options.cert.domains)
const domain = this.certStore.getMainDomain(this.options.cert.domains)
return {
...cert, detail, expires, domain, domains: this.domains, email: this.email
}
@@ -144,4 +136,13 @@ export class Certd {
leftDays
}
}
createProviderByType (type, options) {
try {
const Provider = AccessProviderClasses[type]
return new Provider(options)
} catch (e) {
throw new Error('暂不支持此dnsProvider,请先use该provider' + type, e)
}
}
}

View File

@@ -1,29 +1,48 @@
import dayjs from 'dayjs'
import crypto from 'crypto'
function md5 (content) {
return crypto.createHash('md5').update(content).digest('hex')
}
export class CertStore {
constructor ({ store, email, domain }) {
constructor ({ store, email, domains }) {
this.store = store
this.email = email
this.domain = domain
this.domains = domains
this.domain = this.getMainDomain(this.domains)
this.safetyDomain = this.getSafetyDomain(this.domain)
this.domainDir = this.safetyDomain + '-' + md5(this.getDomainStr(this.domains))
this.certsRootPath = this.store.buildKey(this.email, 'certs')
this.currentRootPath = this.store.buildKey(this.certsRootPath, this.safetyDomain, 'current')
this.currentRootPath = this.store.buildKey(this.certsRootPath, this.domainDir, 'current')
}
// getAccountConfig () {
// return this.store.get(this.accountConfigKey)
// }
//
// setAccountConfig (email, account) {
// return this.store.set(this.accountConfigKey, account)
// }
getMainDomain (domains) {
if (domains == null) {
return null
}
if (typeof domains === 'string') {
return domains
}
if (domains.length > 0) {
return domains[0]
}
}
getDomainStr (domains) {
if (domains == null) {
return null
}
if (typeof domains === 'string') {
return domains
}
return domains.join(',')
}
buildNewCertRootPath (dir) {
if (dir == null) {
dir = dayjs().format('YYYY.MM.DD.HHmmss')
}
return this.store.buildKey(this.certsRootPath, this.safetyDomain, dir)
return this.store.buildKey(this.certsRootPath, this.domainDir, dir)
}
formatCert (pem) {

View File

@@ -68,7 +68,7 @@ describe('Certd', function () {
const certd = new Certd(options)
const currentRootPath = certd.certStore.currentRootPath
console.log('rootDir', currentRootPath)
expect(currentRootPath).match(/xiaojunnuo@qq.com\\certs\\_.docmirror.club\\current/)
expect(currentRootPath).match(/xiaojunnuo@qq.com\\certs\\_.docmirror.club-\w+\\current/)
})
it('#writeAndReadCert', async function () {
const options = createOptions()

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/executor",
"version": "0.1.9",
"version": "0.1.11",
"description": "",
"main": "src/index.js",
"scripts": {
@@ -10,9 +10,9 @@
},
"type": "module",
"dependencies": {
"@certd/api": "^0.1.7",
"@certd/certd": "^0.1.7",
"@certd/plugins": "^0.1.9",
"@certd/api": "^0.1.11",
"@certd/certd": "^0.1.11",
"@certd/plugins": "^0.1.11",
"dayjs": "^1.9.7",
"lodash-es": "^4.17.20"
},

View File

@@ -11,7 +11,8 @@ function createDefaultOptions () {
args: {
forceCert: false,
forceDeploy: true,
forceRedeploy: false
forceRedeploy: false,
doNotThrowError: false // 部署流程执行有错误时,不抛异常,此时整个任务执行完毕后,可以返回结果,你可以在返回结果中处理
}
}
}
@@ -21,10 +22,25 @@ export class Executor {
this.trace = new Trace()
}
use (plugin) {
useProviders (providers) {
if (providers) {
_.forEach(item => {
Certd.use(item)
})
}
}
useProvider (provider) {
Certd.use(provider)
}
usePlugin (plugin) {
if (plugin == null) {
return
}
if (this.plugins == null) {
this.plugins = {}
}
this.plugins[plugin.name] = plugin
if (plugin.define) {
const define = plugin.define()
@@ -34,9 +50,9 @@ export class Executor {
usePlugins (plugins) {
if (plugins) {
for (const plugin of plugins) {
this.use(plugin)
}
_.forEach(plugins, item => {
this.usePlugin(item)
})
}
}
@@ -54,7 +70,7 @@ export class Executor {
async doRun (options) {
// 申请证书
logger.info('任务开始')
const certd = new Certd(options)
const certd = new Certd(options, this.providers)
const cert = await this.runCertd(certd)
if (cert == null) {
throw new Error('申请证书失败')
@@ -64,7 +80,7 @@ export class Executor {
logger.info('----------------------')
if (!cert.isNew) {
// 如果没有更新
if (!options.args?.forceDeploy && !options.args?.forceRedeploy) {
if (!options.args.forceDeploy && !options.args.forceRedeploy) {
// 且不需要强制运行deploy
logger.info('证书无更新,无需重新部署')
logger.info('任务完成')
@@ -81,24 +97,23 @@ export class Executor {
context.certIsNew = !!cert.isNew
const trace = new Trace(context)
const resultTrace = trace.getInstance({ type: 'result' })
// 运行部署任务
try {
await this.runDeploys({ options, cert, context, trace })
} finally {
await certd.certStore.setCurrentFile('context.json', JSON.stringify(context))
}
logger.info('任务完成')
trace.print()
const result = trace.get({ })
const result = resultTrace.get({ })
const returnData = {
cert,
context,
result
}
if (result.status === 'error') {
const err = new Error(result.remark)
err.data = returnData
if (result.status === 'error' && options.args.doNotThrowError === false) {
throw new Error(result.remark)
}
return returnData
}
@@ -119,10 +134,12 @@ export class Executor {
for (const deploy of options.deploy) {
const deployName = deploy.deployName
logger.info(`------------【${deployName}】-----------`)
const deployTrace = trace.getInstance({ type: 'deploy', deployName })
if (deploy.disabled === true) {
logger.info('此流程已被禁用,跳过')
logger.info('')
trace.set({ deployName, value: { current: 'skip', status: 'disabled', remark: '流程禁用' } })
deployTrace.set({ value: { current: 'skip', status: 'disabled', remark: '流程禁用' } })
continue
}
try {
@@ -131,13 +148,14 @@ export class Executor {
context[deployName] = {}
}
const taskContext = context[deployName]
// 开始执行任务列表
await this.runTask({ options, cert, task, context: taskContext, deploy, trace })
}
trace.set({ deployName, value: { status: 'success', remark: '执行成功' } })
deployTrace.set({ value: { status: 'success', remark: '执行成功' } })
} catch (e) {
trace.set({ deployName, value: { status: 'error', remark: '执行失败:' + e.message } })
trace.set({ value: { status: 'error', remark: deployName + '执行失败:' + e.message } })
deployTrace.set({ value: { status: 'error', remark: '执行失败:' + e.message } })
trace.set({ type: 'result', value: { status: 'error', remark: deployName + '执行失败:' + e.message } })
logger.error('流程执行失败', e)
}
@@ -158,18 +176,20 @@ export class Executor {
if (Plugin instanceof Function) {
instance = new Plugin({ accessProviders: options.accessProviders })
}
const traceStatus = trace.get({ deployName: deploy.deployName, taskName: taskName })
if (traceStatus?.status === 'success' && !options?.args?.forceRedeploy) {
const taskTrace = trace.getInstance({ type: 'deploy', deployName, taskName })
const traceStatus = taskTrace.get({})
if (traceStatus && traceStatus.status === 'success' && !options.args.forceRedeploy) {
logger.info(`----【${taskName}】已经执行完成,跳过此任务`)
trace.set({ deployName, taskName, value: { current: 'skip', status: 'success', remark: '已执行成功过,本次跳过' } })
taskTrace.set({ value: { current: 'skip', status: 'success', remark: '已执行成功过,本次跳过' } })
return
}
logger.info(`----【${taskName}】开始执行`)
try {
// 执行任务
await instance.execute({ cert, props: task.props, context })
trace.set({ deployName, taskName, value: { current: 'success', status: 'success', remark: '执行成功', time: dayjs().format() } })
taskTrace.set({ value: { current: 'success', status: 'success', remark: '执行成功', time: dayjs().format() } })
} catch (e) {
trace.set({ deployName, taskName, value: { current: 'error', status: 'error', remark: e.message, time: dayjs().format() } })
taskTrace.set({ value: { current: 'error', status: 'error', remark: e.message, time: dayjs().format() } })
throw e
}
logger.info(`----任务【${taskName}】执行完成`)

View File

@@ -6,19 +6,30 @@ export class Trace {
this.context = context
}
set ({ deployName, taskName, prop, value }) {
const key = this.buildTraceKey({ deployName, taskName, prop })
getInstance ({ type, deployName, taskName }) {
return {
get: ({ prop }) => {
return this.get({ type, deployName, taskName, prop })
},
set: ({ prop, value }) => {
this.set({ type, deployName, taskName, prop, value })
}
}
}
set ({ type, deployName, taskName, prop, value }) {
const key = this.buildTraceKey({ type, deployName, taskName, prop })
const oldValue = _.get(this.context, key) || {}
_.merge(oldValue, value)
_.set(this.context, key, oldValue)
}
get ({ deployName, taskName, prop }) {
return _.get(this.context, this.buildTraceKey({ deployName, taskName, prop }))
get ({ type, deployName, taskName, prop }) {
return _.get(this.context, this.buildTraceKey({ type, deployName, taskName, prop }))
}
buildTraceKey ({ deployName, taskName, prop }) {
let key = '__trace__'
buildTraceKey ({ type = 'default', deployName, taskName, prop }) {
let key = '__trace__.' + type
if (deployName) {
key += '.'
key += deployName.replace(/\./g, '_')
@@ -46,7 +57,7 @@ export class Trace {
} else {
this.printTraceLine({ current: 'skip', remark: '还未到过期时间,跳过' }, '更新证书')
}
const trace = this.get({ })
const trace = this.get({ type: 'deploy' })
// logger.info('trace', trace)
for (const deployName in trace) {
if (trace[deployName] == null) {
@@ -64,11 +75,12 @@ export class Trace {
}
}
}
const result = this.get({ type: 'result' })
this.printTraceLine(result, 'result', '')
const mainContext = {}
_.merge(mainContext, context)
delete mainContext.__trace__
logger.info('context:', JSON.stringify(mainContext))
logger.info('context', JSON.stringify(mainContext))
}
printTraceLine (traceStatus, name, prefix = '') {

View File

@@ -5,7 +5,7 @@ const { expect } = pkg
describe('AutoDeploy', function () {
it('#run', async function () {
this.timeout(20000)
this.timeout(120000)
const options = createOptions()
const executor = new Executor()
const ret = await executor.run(options)
@@ -13,15 +13,18 @@ describe('AutoDeploy', function () {
expect(ret.cert).ok
})
it('#forceCert', async function () {
this.timeout(20000)
this.timeout(120000)
const executor = new Executor()
const options = createOptions()
const ret = await executor.run(options, { forceCert: true, forceDeploy: false })
options.args.forceCert = true
options.args.forceDeploy = true
const ret = await executor.run(options)
expect(ret).ok
expect(ret.cert).ok
})
it('#forceDeploy', async function () {
this.timeout(20000)
this.timeout(120000)
const executor = new Executor()
const options = createOptions()
const ret = await executor.run(options, { forceCert: false, forceDeploy: true, forceRedeploy: true })

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/plugins",
"version": "0.1.9",
"version": "0.1.11",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -131,34 +131,6 @@
"to-fast-properties": "^2.0.0"
}
},
"@certd/acme-client": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/@certd/acme-client/-/acme-client-0.1.6.tgz",
"integrity": "sha512-XffzB/QHRj61gUHXor1B8R2TVt7HnklJpjAbwQi8vHjBGloU8I3occJxIunoh1AShhc4wsxnc+h/D5yRIUp17A==",
"requires": {
"axios": "0.21.1",
"backo2": "^1.0.0",
"bluebird": "^3.5.0",
"debug": "^4.1.1",
"log4js": "^6.3.0",
"node-forge": "^0.10.0"
},
"dependencies": {
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"@certd/api": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/@certd/api/-/api-0.1.7.tgz",
@@ -171,30 +143,6 @@
"qs": "^6.9.4"
}
},
"@certd/certd": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/@certd/certd/-/certd-0.1.7.tgz",
"integrity": "sha512-h+YWlcXzdQk3dbhK7u0guwyzotKRtsIA9zTaHdViWlMDlT9/oO9QflFoo8x8wA1Xx/Vd90APT6EEBnizgOXxsQ==",
"requires": {
"@certd/acme-client": "^0.1.6",
"@certd/api": "^0.1.7",
"@certd/providers": "^0.1.7",
"dayjs": "^1.9.7",
"lodash-es": "^4.17.20",
"node-forge": "^0.10.0"
}
},
"@certd/providers": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/@certd/providers/-/providers-0.1.7.tgz",
"integrity": "sha512-ACiFNhRBVWB5Nyui3RTuAX0oTVkuWi0zopO1qMzbA/2iOrtFAoNs3I5DSLpZxS3d1erKcOq+Lx7Rm0rZxiL6wg==",
"requires": {
"@alicloud/pop-core": "^1.7.10",
"@certd/api": "^0.1.7",
"lodash-es": "^4.17.20",
"tencentcloud-sdk-nodejs": "^4.0.44"
}
},
"@eslint/eslintrc": {
"version": "0.2.2",
"resolved": "https://registry.npm.taobao.org/@eslint/eslintrc/download/@eslint/eslintrc-0.2.2.tgz?cache=0&sync_timestamp=1607145629875&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40eslint%2Feslintrc%2Fdownload%2F%40eslint%2Feslintrc-0.2.2.tgz",
@@ -561,11 +509,6 @@
"resolve": "^1.12.0"
}
},
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.0.tgz",
@@ -595,11 +538,6 @@
"integrity": "sha1-MPpAyef+B9vIlWeM0ocCTeokHdk=",
"dev": true
},
"bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz?cache=0&sync_timestamp=1601898189928&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrace-expansion%2Fdownload%2Fbrace-expansion-1.1.11.tgz",
@@ -2428,11 +2366,6 @@
"resolved": "https://registry.npm.taobao.org/node-fetch/download/node-fetch-2.6.1.tgz?cache=0&sync_timestamp=1599309179354&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-fetch%2Fdownload%2Fnode-fetch-2.6.1.tgz",
"integrity": "sha1-BFvTI2Mfdu0uK1VXM5RBa2OaAFI="
},
"node-forge": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA=="
},
"normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npm.taobao.org/normalize-package-data/download/normalize-package-data-2.5.0.tgz?cache=0&sync_timestamp=1602547447569&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnormalize-package-data%2Fdownload%2Fnormalize-package-data-2.5.0.tgz",

View File

@@ -1,31 +1,27 @@
{
"name": "@certd/plugins",
"version": "0.1.9",
"description": "",
"main": "./src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"type": "module",
"dependencies": {
"@alicloud/pop-core": "^1.7.10",
"@certd/api": "^0.1.7",
"@certd/certd": "^0.1.7",
"dayjs": "^1.9.7",
"kubernetes-client": "^9.0.0",
"lodash-es": "^4.17.20",
"ssh2": "^0.8.9",
"tencentcloud-sdk-nodejs": "^4.0.44"
},
"devDependencies": {
"chai": "^4.2.0",
"eslint": "^7.15.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"mocha": "^8.2.1"
},
"author": "Greper",
"license": "MIT"
"name": "@certd/plugins",
"version": "0.1.11",
"description": "",
"main": "./src/index.js",
"type": "module",
"dependencies": {
"@alicloud/pop-core": "^1.7.10",
"@certd/api": "^0.1.11",
"dayjs": "^1.9.7",
"kubernetes-client": "^9.0.0",
"lodash-es": "^4.17.20",
"ssh2": "^0.8.9",
"tencentcloud-sdk-nodejs": "^4.0.44"
},
"devDependencies": {
"chai": "^4.2.0",
"eslint": "^7.15.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"mocha": "^8.2.1"
},
"author": "Greper",
"license": "MIT"
}

View File

@@ -9,11 +9,11 @@ import { DeployCertToTencentCLB } from './tencent/deploy-to-clb/index.js'
import { DeployCertToTencentTKEIngress } from './tencent/deploy-to-tke-ingress/index.js'
export default [
export default {
UploadCertToAliyun,
DeployCertToAliyunCDN,
UploadCertToTencent,
DeployCertToTencentTKEIngress,
DeployCertToTencentCDN,
DeployCertToTencentCLB
]
}

View File

@@ -46,7 +46,7 @@ export class K8sClient {
* @returns secretsList
*/
async getSecret (opts) {
const namespace = opts?.namespace || 'default'
const namespace = opts.namespace || 'default'
const secrets = await this.client.api.v1.namespaces(namespace).secrets.get()
return secrets
}
@@ -57,7 +57,7 @@ export class K8sClient {
* @returns {Promise<*>}
*/
async createSecret (opts) {
const namespace = opts?.namespace || 'default'
const namespace = opts.namespace || 'default'
const created = await this.client.api.v1.namespaces(namespace).secrets.post({
body: opts.body
})
@@ -66,8 +66,8 @@ export class K8sClient {
}
async updateSecret (opts) {
const namespace = opts?.namespace || 'default'
const secretName = opts?.secretName
const namespace = opts.namespace || 'default'
const secretName = opts.secretName
if (secretName == null) {
throw new Error('secretName 不能为空')
}
@@ -77,8 +77,8 @@ export class K8sClient {
}
async patchSecret (opts) {
const namespace = opts?.namespace || 'default'
const secretName = opts?.secretName
const namespace = opts.namespace || 'default'
const secretName = opts.secretName
if (secretName == null) {
throw new Error('secretName 不能为空')
}
@@ -88,8 +88,8 @@ export class K8sClient {
}
async getIngress (opts) {
const namespace = opts?.namespace || 'default'
const ingressName = opts?.ingressName
const namespace = opts.namespace || 'default'
const ingressName = opts.ingressName
if (!ingressName) {
throw new Error('ingressName 不能为空')
}
@@ -97,8 +97,8 @@ export class K8sClient {
}
async patchIngress (opts) {
const namespace = opts?.namespace || 'default'
const ingressName = opts?.ingressName
const namespace = opts.namespace || 'default'
const ingressName = opts.ingressName
if (!ingressName) {
throw new Error('ingressName 不能为空')
}

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/providers",
"version": "0.1.7",
"version": "0.1.11",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/providers",
"version": "0.1.7",
"version": "0.1.11",
"description": "",
"main": "./src/index.js",
"scripts": {
@@ -11,7 +11,7 @@
"license": "MIT",
"dependencies": {
"@alicloud/pop-core": "^1.7.10",
"@certd/api": "^0.1.7",
"@certd/api": "^0.1.11",
"lodash-es": "^4.17.20",
"tencentcloud-sdk-nodejs": "^4.0.44"
},

View File

@@ -85,7 +85,9 @@ export class AliyunDnsProvider extends AbstractDnsProvider {
this.logger.info('添加域名解析成功:', value, value, ret.RecordId)
return ret.RecordId
} catch (e) {
// e.code === 'DomainRecordDuplicate'
if (e.code === 'DomainRecordDuplicate') {
return
}
this.logger.info('添加域名解析出错', e)
throw e
}

View File

@@ -1,12 +0,0 @@
import dnsProviders from './index.js'
export class DnsProviderFactory {
static createByType (type, options) {
try {
const Provider = dnsProviders[type]
return new Provider(options)
} catch (e) {
throw new Error('暂不支持此dnsProvider' + type, e)
}
}
}

View File

@@ -28,7 +28,7 @@ export class DnspodDnsProvider extends AbstractDnsProvider {
_.merge(config, options)
const ret = await request(config)
if (ret?.status?.code !== '1') {
if (!ret || !ret.status || ret.status.code !== '1') {
throw new Error('请求失败:' + ret.status.message + ',api=' + config.url)
}
return ret

View File

@@ -1,6 +0,0 @@
import { AliyunDnsProvider } from './impl/aliyun.js'
import { DnspodDnsProvider } from './impl/dnspod.js'
export default {
[AliyunDnsProvider.name()]: AliyunDnsProvider,
[DnspodDnsProvider.name()]: DnspodDnsProvider
}

View File

@@ -1,4 +1,7 @@
import dnsProviders from './dns-provider/index.js'
export { DnsProviderFactory } from './dns-provider/dns-provider-factory.js'
import { AliyunDnsProvider } from './dns-provider/aliyun.js'
import { DnspodDnsProvider } from './dns-provider/dnspod.js'
export default dnsProviders
export default {
AliyunDnsProvider,
DnspodDnsProvider
}

View File

@@ -1,7 +1,6 @@
import pkg from 'chai'
import AliyunDnsProvider from '../../src/dns-provider/impl/aliyun.js'
import AliyunDnsProvider from '../../src/dns-provider/aliyun.js'
import { createOptions } from '../../../../test/options.js'
import { Certd } from '../../src/index.js'
const { expect } = pkg
describe('AliyunDnsProvider', function () {
it('#getDomainList', async function () {

View File

@@ -1,5 +1,5 @@
import pkg from 'chai'
import DnspodDnsProvider from '../../src/dns-provider/impl/dnspod.js'
import DnspodDnsProvider from '../../src/dns-provider/dnspod.js'
import { Certd } from '../../src/index.js'
import { createOptions } from '../../../../test/options.js'
const { expect } = pkg

View File

@@ -1,11 +1,11 @@
import _ from 'lodash'
import _ from 'lodash-es'
import optionsPrivate from './options.private.js'
const defaultOptions = {
args: {
forceCert: false, // 强制更新证书
skipCert: false, // 是否跳过证书申请环节
forceDeploy: false,
test: true
forceDeploy: true,
test: false
},
accessProviders: {
aliyun: {