mirror of
https://github.com/certd/certd.git
synced 2026-04-14 20:40:53 +08:00
Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef1896fdcf | ||
|
|
7438cea8d1 | ||
|
|
6950d779e0 | ||
|
|
4c31db427a | ||
|
|
540981e071 | ||
|
|
ed8b9cd5e9 | ||
|
|
3dea83ce31 | ||
|
|
cfb46a7c2b | ||
|
|
dd2f44bfe9 | ||
|
|
09d0e79bcd | ||
|
|
a9c1ca5c97 | ||
|
|
1af6fb3a58 | ||
|
|
a38004c1ed | ||
|
|
5a355b276e | ||
|
|
a52721bd7e | ||
|
|
6df399fe02 | ||
|
|
6d8ede9575 | ||
|
|
7145aa60ca | ||
|
|
b1181c0f9a | ||
|
|
bace2a7c25 | ||
|
|
a70b4373de | ||
|
|
b7c12e6d91 | ||
|
|
6a88dd476e | ||
|
|
5fbd774266 | ||
|
|
bdec010d2e | ||
|
|
05a00b7b78 | ||
|
|
eaf23c3034 | ||
|
|
276a8b35e5 | ||
|
|
466d659f6e | ||
|
|
84e26381b5 | ||
|
|
469b5a5f69 | ||
|
|
ad77ebd2f9 | ||
|
|
b75543c3bc | ||
|
|
0677275742 | ||
|
|
0c3724e0ad | ||
|
|
803083d23c | ||
|
|
f4f8067a12 | ||
|
|
caa9f084d6 | ||
|
|
81407b65d1 | ||
|
|
8a24293fd7 | ||
|
|
8f1886a585 | ||
|
|
0a64e5fa67 | ||
|
|
7a70603971 | ||
|
|
0d5e00e744 | ||
|
|
91ba1433af | ||
|
|
12e56d14f2 | ||
|
|
7326119f52 | ||
|
|
136983cf14 | ||
|
|
105a1b80ae | ||
|
|
b8000ca533 | ||
|
|
c3e374e6e5 | ||
|
|
a9b6e87249 | ||
|
|
61de5422bf | ||
|
|
f96697f619 | ||
|
|
b4560d6370 | ||
|
|
a7bcde8d82 | ||
|
|
34bb4d54c2 | ||
|
|
e0116a1a03 | ||
|
|
12fec7939d | ||
|
|
ff8e02cceb | ||
|
|
8122bed97f | ||
|
|
991c3dbb76 | ||
|
|
399c23623d | ||
|
|
2232f21b48 | ||
|
|
e41c084381 | ||
|
|
520b27e0dc | ||
|
|
ace7e0247a | ||
|
|
9ae414b1c6 | ||
|
|
cb8c8186f1 | ||
|
|
82f86d9556 | ||
|
|
cfb1034450 | ||
|
|
2a07442a85 | ||
|
|
68c1eff81d | ||
|
|
baec15dfc6 | ||
|
|
6eb9817296 | ||
|
|
b9d5d33aaa | ||
|
|
560519894c | ||
|
|
9f434b0968 | ||
|
|
07066dde87 | ||
|
|
074c8f7cd0 | ||
|
|
e9df2355f4 | ||
|
|
45547d6f94 | ||
|
|
4a421d5b14 | ||
|
|
7b9825eb40 | ||
|
|
5cde165f0b | ||
|
|
305824ff1a | ||
|
|
86ddb72227 | ||
|
|
cca33478e4 | ||
|
|
a8f41d3c48 | ||
|
|
a25a15ca6e | ||
|
|
a39dac4dbd | ||
|
|
eab0c3be60 | ||
|
|
b4ee3d0dfc | ||
|
|
2f03e18c59 | ||
|
|
232cd7215e | ||
|
|
86b1e9959b | ||
|
|
fd130f86fd | ||
|
|
2669f509e1 | ||
|
|
d3619ad60f | ||
|
|
c26417d769 |
26
.gitignore
vendored
26
.gitignore
vendored
@@ -1,11 +1,29 @@
|
|||||||
# IntelliJ project files
|
# IntelliJ project files
|
||||||
|
.vscode/
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
yarn.lock
|
||||||
|
package-lock.json
|
||||||
|
/.idea/
|
||||||
|
**/dist
|
||||||
.idea
|
.idea
|
||||||
*.iml
|
*.iml
|
||||||
out
|
out
|
||||||
gen
|
gen
|
||||||
node_modules/
|
|
||||||
/test/*.private.*
|
/test/*.private.*
|
||||||
/other/node-acme-client/.idea/
|
|
||||||
/*.log
|
/*.log
|
||||||
/other/certd-run
|
|
||||||
/other/node-acme-client
|
/packages/ui/*/.idea
|
||||||
|
|
||||||
|
/packages/ui/*/node_modules
|
||||||
|
|
||||||
|
/packages/*/node_modules
|
||||||
|
/packages/ui/certd-server/tmp/
|
||||||
|
/packages/ui/certd-ui/dist/
|
||||||
|
/other
|
||||||
|
/dev-sidecar-test
|
||||||
|
/packages/core/certd/yarn.lock
|
||||||
|
/packages/test
|
||||||
|
/test/own
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "packages/base/node-acme-client"]
|
||||||
|
path = packages/base/node-acme-client
|
||||||
|
url = https://github.com/certd/node-acme-client.git
|
||||||
85
README.md
85
README.md
@@ -26,88 +26,31 @@ CertD 是一个帮助你全自动申请和部署SSL证书的工具。
|
|||||||
## 快速开始
|
## 快速开始
|
||||||
本案例演示,如何配置自动申请证书,并部署到阿里云CDN,然后快要到期前自动更新证书并重新部署
|
本案例演示,如何配置自动申请证书,并部署到阿里云CDN,然后快要到期前自动更新证书并重新部署
|
||||||
|
|
||||||
|
|
||||||
1. 环境准备
|
1. 环境准备
|
||||||
安装[nodejs](https://nodejs.org/zh-cn/)
|
安装[nodejs](https://nodejs.org/zh-cn/)
|
||||||
|
|
||||||
2. 创建任务项目
|
|
||||||
```
|
|
||||||
mkdir certd-run # 项目名称可以任意命名
|
|
||||||
cd certd-run -y
|
|
||||||
npm install @certd/executor -s --production
|
|
||||||
```
|
|
||||||
|
|
||||||
3. 创建index.js
|
2. 生成node项目
|
||||||
|
|
||||||
参数配置分几个部分
|
通过ui生成: https://certd.docmirror.cn/
|
||||||
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()
|
配置好之后,点击导出按钮,导出一个node项目包
|
||||||
await executor.run(options)
|
|
||||||
```
|
|
||||||
|
|
||||||
4. 运行
|
4. 运行
|
||||||
|
将导出的压缩包解压,然后执行如下命令,即可开始申请证书并部署
|
||||||
```
|
```
|
||||||
node index.js
|
npm install
|
||||||
|
npm run certd
|
||||||
```
|
```
|
||||||
5. 执行效果
|
5. 执行效果
|
||||||
生成的证书默认会存储在 `${home}/.certd/${email}/certs/${domain}/current`目录下
|
生成的证书默认会存储在 `${home}/.certd/${email}/certs/${domain}/current`目录下
|
||||||
@@ -129,7 +72,7 @@ node index.js
|
|||||||
所以当你临时需要将证书部署到其他地方时,直接追加部署任务,然后再次运行即可
|
所以当你临时需要将证书部署到其他地方时,直接追加部署任务,然后再次运行即可
|
||||||
|
|
||||||
## CI/DI集成与自动续期重新部署
|
## CI/DI集成与自动续期重新部署
|
||||||
集成前,将以上代码提交到内网git仓库,或者私有git仓库(由于包含敏感信息,不要提交到公开git仓库)
|
集成前,将以上导出的node项目提交到内网git仓库,或者私有git仓库(由于包含敏感信息,不要提交到公开git仓库)
|
||||||
|
|
||||||
### jenkins任务
|
### jenkins任务
|
||||||
1. 创建任务
|
1. 创建任务
|
||||||
|
|||||||
BIN
doc/step1.png
Normal file
BIN
doc/step1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
doc/step2.png
Normal file
BIN
doc/step2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
BIN
doc/step3.png
Normal file
BIN
doc/step3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
BIN
doc/tasks.png
Normal file
BIN
doc/tasks.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/base/*",
|
||||||
|
"packages/core/*",
|
||||||
|
"packages/plugins/*"
|
||||||
],
|
],
|
||||||
"version": "0.1.11"
|
"version": "0.3.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
"lerna": "^3.18.4"
|
"lerna": "^3.18.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"start": "lerna bootstrap --hoist",
|
||||||
|
"i-all": "lerna link && lerna exec npm install "
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
2468
packages/api/package-lock.json
generated
2468
packages/api/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
|||||||
export { AbstractDnsProvider } from './dns-provider/index.js'
|
|
||||||
export { Store } from './store/store.js'
|
|
||||||
export { util } from './utils/index.js'
|
|
||||||
export { AbstractPlugin } from './plugin/index.js'
|
|
||||||
2944
packages/certd/package-lock.json
generated
2944
packages/certd/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
packages/core/api/.eslintrc
Normal file
17
packages/core/api/.eslintrc
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "standard",
|
||||||
|
"env": {
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2020
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.test.js", "*.spec.js"],
|
||||||
|
"rules": {
|
||||||
|
"no-unused-expressions": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
7
packages/core/api/.gitignore
vendored
Normal file
7
packages/core/api/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.vscode/
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
yarn.lock
|
||||||
|
package-lock.json
|
||||||
|
/.idea/
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/api",
|
"name": "@certd/api",
|
||||||
"version": "0.1.11",
|
"version": "0.3.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "./src/index.js",
|
"main": "src/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "Greper",
|
"author": "Greper",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,5 +21,6 @@
|
|||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"mocha": "^8.2.1"
|
"mocha": "^8.2.1"
|
||||||
}
|
},
|
||||||
|
"gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
|
||||||
}
|
}
|
||||||
2
packages/core/api/src/access-provider/index.js
Normal file
2
packages/core/api/src/access-provider/index.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import { Registry } from '../registry/registry.js'
|
||||||
|
export const accessProviderRegistry = new Registry()
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import _ from 'lodash-es'
|
import _ from 'lodash-es'
|
||||||
import logger from '../utils/util.log.js'
|
import logger from '../utils/util.log.js'
|
||||||
|
import commonUtil from '../utils/util.common.js'
|
||||||
export class AbstractDnsProvider {
|
export class AbstractDnsProvider {
|
||||||
constructor () {
|
constructor ({ accessProviders }) {
|
||||||
this.logger = logger
|
this.logger = logger
|
||||||
|
this.accessProviders = commonUtil.arrayToMap(accessProviders)
|
||||||
}
|
}
|
||||||
|
|
||||||
async createRecord ({ fullRecord, type, value }) {
|
async createRecord ({ fullRecord, type, value }) {
|
||||||
@@ -31,4 +33,15 @@ export class AbstractDnsProvider {
|
|||||||
}
|
}
|
||||||
return domain
|
return domain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAccessProvider (accessProvider, accessProviders = this.accessProviders) {
|
||||||
|
let access = accessProvider
|
||||||
|
if (typeof accessProvider === 'string' && accessProviders) {
|
||||||
|
access = accessProviders[accessProvider]
|
||||||
|
}
|
||||||
|
if (access == null) {
|
||||||
|
throw new Error(`accessProvider :${accessProvider}不存在`)
|
||||||
|
}
|
||||||
|
return access
|
||||||
|
}
|
||||||
}
|
}
|
||||||
4
packages/core/api/src/dns-provider/index.js
Normal file
4
packages/core/api/src/dns-provider/index.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { Registry } from '../registry/registry.js'
|
||||||
|
export { AbstractDnsProvider } from './abstract-dns-provider.js'
|
||||||
|
|
||||||
|
export const dnsProviderRegistry = new Registry()
|
||||||
6
packages/core/api/src/index.js
Normal file
6
packages/core/api/src/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export * from './dns-provider/index.js'
|
||||||
|
export * from './plugin/index.js'
|
||||||
|
export * from './access-provider/index.js'
|
||||||
|
export { Store } from './store/store.js'
|
||||||
|
export { util } from './utils/index.js'
|
||||||
|
// module.createRequireFromPath()
|
||||||
@@ -2,10 +2,15 @@ import fs from 'fs'
|
|||||||
import logger from '../utils/util.log.js'
|
import logger from '../utils/util.log.js'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import Sleep from '../utils/util.sleep.js'
|
import Sleep from '../utils/util.sleep.js'
|
||||||
|
import commonUtil from '../utils/util.common.js'
|
||||||
export class AbstractPlugin {
|
export class AbstractPlugin {
|
||||||
constructor ({ accessProviders }) {
|
constructor (options) {
|
||||||
|
if (options == null) {
|
||||||
|
throw new Error('插件安装失败:参数不允许为空')
|
||||||
|
}
|
||||||
|
const { accessProviders } = options
|
||||||
this.logger = logger
|
this.logger = logger
|
||||||
this.accessProviders = accessProviders
|
this.accessProviders = commonUtil.arrayToMap(accessProviders)
|
||||||
}
|
}
|
||||||
|
|
||||||
appendTimeSuffix (name) {
|
appendTimeSuffix (name) {
|
||||||
@@ -52,7 +57,7 @@ export class AbstractPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 回退,如有必要
|
* 回退,用于单元测试
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
async rollback (options) {
|
async rollback (options) {
|
||||||
@@ -60,10 +65,14 @@ export class AbstractPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAccessProvider (accessProvider, accessProviders = this.accessProviders) {
|
getAccessProvider (accessProvider, accessProviders = this.accessProviders) {
|
||||||
|
let access = accessProvider
|
||||||
if (typeof accessProvider === 'string' && accessProviders) {
|
if (typeof accessProvider === 'string' && accessProviders) {
|
||||||
accessProvider = accessProviders[accessProvider]
|
access = accessProviders[accessProvider]
|
||||||
}
|
}
|
||||||
return accessProvider
|
if (access == null) {
|
||||||
|
throw new Error(`accessProvider :${accessProvider}不存在`)
|
||||||
|
}
|
||||||
|
return access
|
||||||
}
|
}
|
||||||
|
|
||||||
async sleep (time) {
|
async sleep (time) {
|
||||||
3
packages/core/api/src/plugin/index.js
Normal file
3
packages/core/api/src/plugin/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { Registry } from '../registry/registry.js'
|
||||||
|
export { AbstractPlugin } from './abstract-plugin.js'
|
||||||
|
export const pluginRegistry = new Registry()
|
||||||
46
packages/core/api/src/registry/registry.js
Normal file
46
packages/core/api/src/registry/registry.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
export class Registry {
|
||||||
|
constructor () {
|
||||||
|
this.collection = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
install (target) {
|
||||||
|
if (target == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.collection == null) {
|
||||||
|
this.collection = {}
|
||||||
|
}
|
||||||
|
let defineName = target.define ? target.define().name : null
|
||||||
|
if (defineName == null) {
|
||||||
|
defineName = target.name
|
||||||
|
}
|
||||||
|
|
||||||
|
this.register(defineName, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
register (key, value) {
|
||||||
|
if (!key || value == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.collection[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
get (name) {
|
||||||
|
if (!name) {
|
||||||
|
throw new Error('插件名称不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.collection) {
|
||||||
|
this.collection = {}
|
||||||
|
}
|
||||||
|
const plugin = this.collection[name]
|
||||||
|
if (!plugin) {
|
||||||
|
throw new Error(`插件${name}还未注册`)
|
||||||
|
}
|
||||||
|
return plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
getCollection () {
|
||||||
|
return this.collection
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import logger from './util.log.js'
|
|||||||
import path from './util.path.js'
|
import path from './util.path.js'
|
||||||
import { request } from './util.request.js'
|
import { request } from './util.request.js'
|
||||||
import sleep from './util.sleep.js'
|
import sleep from './util.sleep.js'
|
||||||
|
import common from './util.common.js'
|
||||||
export const util = {
|
export const util = {
|
||||||
logger, path, request, sleep
|
logger, path, request, sleep, common
|
||||||
}
|
}
|
||||||
33
packages/core/api/src/utils/util.common.js
Normal file
33
packages/core/api/src/utils/util.common.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import _ from 'lodash-es'
|
||||||
|
export default {
|
||||||
|
arrayToMap (array) {
|
||||||
|
if (!array) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
if (!_.isArray(array)) {
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
const map = {}
|
||||||
|
for (const item of array) {
|
||||||
|
if (item.key) {
|
||||||
|
map[item.key] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
},
|
||||||
|
mapToArray (map) {
|
||||||
|
if (!map) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (_.isArray(map)) {
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
const array = []
|
||||||
|
for (const key in map) {
|
||||||
|
const item = map[key]
|
||||||
|
item.key = key
|
||||||
|
array.push(item)
|
||||||
|
}
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
}
|
||||||
7
packages/core/certd/.gitignore
vendored
Normal file
7
packages/core/certd/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.vscode/
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
yarn.lock
|
||||||
|
package-lock.json
|
||||||
|
/.idea/
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/certd",
|
"name": "@certd/certd",
|
||||||
"version": "0.1.11",
|
"version": "0.3.1",
|
||||||
"description": "",
|
"description": "a ssl cert keeper",
|
||||||
"main": "./src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \\\"Error: no test specified\\\" && exit 1"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "Greper",
|
"author": "Greper",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^0.1.6",
|
"@certd/acme-client": "^0.3.1",
|
||||||
"@certd/api": "^0.1.11",
|
"@certd/api": "^0.3.1",
|
||||||
"@certd/providers": "^0.1.11",
|
|
||||||
"dayjs": "^1.9.7",
|
"dayjs": "^1.9.7",
|
||||||
"lodash-es": "^4.17.20",
|
"lodash-es": "^4.17.20",
|
||||||
"node-forge": "^0.10.0"
|
"node-forge": "^0.10.0"
|
||||||
@@ -25,5 +24,6 @@
|
|||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"mocha": "^8.2.1"
|
"mocha": "^8.2.1"
|
||||||
}
|
},
|
||||||
|
"gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,9 @@ const logger = util.logger
|
|||||||
export class AcmeService {
|
export class AcmeService {
|
||||||
constructor (store) {
|
constructor (store) {
|
||||||
this.store = store
|
this.store = store
|
||||||
|
acme.setLogger((text) => {
|
||||||
|
logger.info(text)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAccountConfig (email) {
|
async getAccountConfig (email) {
|
||||||
@@ -1,27 +1,12 @@
|
|||||||
import { util, Store } from '@certd/api'
|
import { util, Store, dnsProviderRegistry } from '@certd/api'
|
||||||
import { AcmeService } from './acme.js'
|
import { AcmeService } from './acme.js'
|
||||||
import { FileStore } from './store/file-store.js'
|
import { FileStore } from './store/file-store.js'
|
||||||
import { CertStore } from './store/cert-store.js'
|
import { CertStore } from './store/cert-store.js'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import forge from 'node-forge'
|
import forge from 'node-forge'
|
||||||
import DefaultProviders from '@certd/providers'
|
|
||||||
import _ from 'lodash-es'
|
|
||||||
const logger = util.logger
|
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 {
|
export class Certd {
|
||||||
static use (providerClass) {
|
|
||||||
install(providerClass)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor (options) {
|
constructor (options) {
|
||||||
this.options = options
|
this.options = options
|
||||||
this.email = options.cert.email
|
this.email = options.cert.email
|
||||||
@@ -87,16 +72,14 @@ export class Certd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createDnsProvider (options) {
|
createDnsProvider (options) {
|
||||||
const accessProviders = options.accessProviders
|
return this.createProviderByType(options.cert.dnsProvider, options.accessProviders)
|
||||||
const providerOptions = accessProviders[options.cert.dnsProvider]
|
|
||||||
return this.createProviderByType(providerOptions.providerType, providerOptions)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async writeCert (cert) {
|
async writeCert (cert) {
|
||||||
const newPath = await this.certStore.writeCert(cert)
|
const newPath = await this.certStore.writeCert(cert)
|
||||||
return {
|
return {
|
||||||
realPath: this.certStore.store.getActualKey(newPath),
|
realPath: this.certStore.store.getActualKey(newPath),
|
||||||
currentPath: this.certStore.store.getActualKey(this.certStore.currentRootPath)
|
currentPath: this.certStore.store.getActualKey(this.certStore.currentMarkPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,12 +120,12 @@ export class Certd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createProviderByType (type, options) {
|
createProviderByType (props, accessProviders) {
|
||||||
try {
|
const { type } = props
|
||||||
const Provider = AccessProviderClasses[type]
|
const Provider = dnsProviderRegistry.get(type)
|
||||||
return new Provider(options)
|
if (Provider == null) {
|
||||||
} catch (e) {
|
throw new Error('暂不支持此dnsProvider,请先注册该provider:' + type)
|
||||||
throw new Error('暂不支持此dnsProvider,请先use该provider:' + type, e)
|
|
||||||
}
|
}
|
||||||
|
return new Provider({ accessProviders, props })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
function md5 (content) {
|
function md5 (content) {
|
||||||
return crypto.createHash('md5').update(content).digest('hex')
|
return crypto.createHash('md5').update(content).digest('hex')
|
||||||
}
|
}
|
||||||
@@ -11,9 +12,10 @@ export class CertStore {
|
|||||||
this.domain = this.getMainDomain(this.domains)
|
this.domain = this.getMainDomain(this.domains)
|
||||||
this.safetyDomain = this.getSafetyDomain(this.domain)
|
this.safetyDomain = this.getSafetyDomain(this.domain)
|
||||||
this.domainDir = this.safetyDomain + '-' + md5(this.getDomainStr(this.domains))
|
this.domainDir = this.safetyDomain + '-' + md5(this.getDomainStr(this.domains))
|
||||||
|
// this.domainDir = this.safetyDomain
|
||||||
this.certsRootPath = this.store.buildKey(this.email, 'certs')
|
this.certsRootPath = this.store.buildKey(this.email, 'certs')
|
||||||
|
|
||||||
this.currentRootPath = this.store.buildKey(this.certsRootPath, this.domainDir, 'current')
|
this.currentMarkPath = this.store.buildKey(this.certsRootPath, this.domainDir, 'current.json')
|
||||||
}
|
}
|
||||||
|
|
||||||
getMainDomain (domains) {
|
getMainDomain (domains) {
|
||||||
@@ -62,15 +64,19 @@ export class CertStore {
|
|||||||
await this.store.set(priKey, this.formatCert(cert.key.toString()))
|
await this.store.set(priKey, this.formatCert(cert.key.toString()))
|
||||||
await this.store.set(csrKey, cert.csr.toString())
|
await this.store.set(csrKey, cert.csr.toString())
|
||||||
|
|
||||||
await this.store.link(newDir, this.currentRootPath)
|
await this.store.set(this.currentMarkPath, JSON.stringify({ latest: newDir }))
|
||||||
|
|
||||||
return newDir
|
return newDir
|
||||||
}
|
}
|
||||||
|
|
||||||
async readCert (dir) {
|
async readCert (dir) {
|
||||||
if (dir == null) {
|
if (dir == null) {
|
||||||
dir = this.currentRootPath
|
dir = await this.getCurrentDir()
|
||||||
}
|
}
|
||||||
|
if (dir == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const crtKey = this.buildKey(dir, this.safetyDomain + '.crt')
|
const crtKey = this.buildKey(dir, this.safetyDomain + '.crt')
|
||||||
const priKey = this.buildKey(dir, this.safetyDomain + '.key')
|
const priKey = this.buildKey(dir, this.safetyDomain + '.key')
|
||||||
const csrKey = this.buildKey(dir, this.safetyDomain + '.csr')
|
const csrKey = this.buildKey(dir, this.safetyDomain + '.csr')
|
||||||
@@ -99,13 +105,23 @@ export class CertStore {
|
|||||||
return domain.replace(/\*/g, '_')
|
return domain.replace(/\*/g, '_')
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentFile (file) {
|
async getCurrentDir () {
|
||||||
const key = this.buildKey(this.currentRootPath, file)
|
const current = await this.store.get(this.currentMarkPath)
|
||||||
|
if (current == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return JSON.parse(current).latest
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCurrentFile (file) {
|
||||||
|
const currentDir = await this.getCurrentDir()
|
||||||
|
const key = this.buildKey(currentDir, file)
|
||||||
return this.store.get(key)
|
return this.store.get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentFile (file, value) {
|
async setCurrentFile (file, value) {
|
||||||
const key = this.buildKey(this.currentRootPath, file)
|
const currentDir = await this.getCurrentDir()
|
||||||
|
const key = this.buildKey(currentDir, file)
|
||||||
return this.store.set(key, value)
|
return this.store.set(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import chai from 'chai'
|
import chai from 'chai'
|
||||||
import { Certd } from '../src/index.js'
|
import { Certd } from '../src/index.js'
|
||||||
import { createOptions } from '../../../test/options.js'
|
import { createOptions } from '../../../../test/options.js'
|
||||||
const { expect } = chai
|
const { expect } = chai
|
||||||
const fakeCrt = `-----BEGIN CERTIFICATE-----
|
const fakeCrt = `-----BEGIN CERTIFICATE-----
|
||||||
MIIFSTCCBDGgAwIBAgITAPoZZk/LhVIyXoic2NnJyxubezANBgkqhkiG9w0BAQsF
|
MIIFSTCCBDGgAwIBAgITAPoZZk/LhVIyXoic2NnJyxubezANBgkqhkiG9w0BAQsF
|
||||||
@@ -66,9 +66,9 @@ describe('Certd', function () {
|
|||||||
options.cert.email = 'xiaojunnuo@qq.com'
|
options.cert.email = 'xiaojunnuo@qq.com'
|
||||||
options.cert.domains = ['*.docmirror.club']
|
options.cert.domains = ['*.docmirror.club']
|
||||||
const certd = new Certd(options)
|
const certd = new Certd(options)
|
||||||
const currentRootPath = certd.certStore.currentRootPath
|
const currentRootPath = certd.certStore.currentMarkPath
|
||||||
console.log('rootDir', currentRootPath)
|
console.log('rootDir', currentRootPath)
|
||||||
expect(currentRootPath).match(/xiaojunnuo@qq.com\\certs\\_.docmirror.club-\w+\\current/)
|
expect(currentRootPath).match(/xiaojunnuo@qq.com\\certs\\_.docmirror.club-\w*\\current.json/)
|
||||||
})
|
})
|
||||||
it('#writeAndReadCert', async function () {
|
it('#writeAndReadCert', async function () {
|
||||||
const options = createOptions()
|
const options = createOptions()
|
||||||
@@ -83,6 +83,6 @@ describe('Certd', function () {
|
|||||||
expect(cert.key).to.be.ok
|
expect(cert.key).to.be.ok
|
||||||
expect(cert.detail).to.be.ok
|
expect(cert.detail).to.be.ok
|
||||||
expect(cert.expires).to.be.ok
|
expect(cert.expires).to.be.ok
|
||||||
console.log('expires:', cert.expires)
|
console.log('cert:', JSON.stringify(cert))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
7
packages/core/executor/.gitignore
vendored
Normal file
7
packages/core/executor/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.vscode/
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
yarn.lock
|
||||||
|
package-lock.json
|
||||||
|
/.idea/
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/executor",
|
"name": "@certd/executor",
|
||||||
"version": "0.1.11",
|
"version": "0.3.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \\\"Error: no test specified\\\" && exit 1",
|
||||||
"build": "webpack --config webpack.config.cjs ",
|
"build": "webpack --config webpack.config.cjs ",
|
||||||
"rollup": "rollup --config rollup.config.js"
|
"rollup": "rollup --config rollup.config.js"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/api": "^0.1.11",
|
"@certd/api": "^0.3.1",
|
||||||
"@certd/certd": "^0.1.11",
|
"@certd/certd": "^0.3.1",
|
||||||
"@certd/plugins": "^0.1.11",
|
|
||||||
"dayjs": "^1.9.7",
|
"dayjs": "^1.9.7",
|
||||||
"lodash-es": "^4.17.20"
|
"lodash-es": "^4.17.20"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@certd/plugin-aliyun": "^0.3.1",
|
||||||
|
"@certd/plugin-host": "^0.3.1",
|
||||||
|
"@certd/plugin-tencent": "^0.3.1",
|
||||||
"@rollup/plugin-commonjs": "^17.0.0",
|
"@rollup/plugin-commonjs": "^17.0.0",
|
||||||
"@rollup/plugin-json": "^4.1.0",
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^11.0.1",
|
"@rollup/plugin-node-resolve": "^11.0.1",
|
||||||
@@ -32,5 +34,6 @@
|
|||||||
},
|
},
|
||||||
"author": "Greper",
|
"author": "Greper",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"sideEffects": false
|
"sideEffects": false,
|
||||||
|
"gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Certd } from '@certd/certd'
|
import { Certd } from '@certd/certd'
|
||||||
import DefaultPlugins from '@certd/plugins'
|
import { pluginRegistry, util } from '@certd/api'
|
||||||
import { util } from '@certd/api'
|
|
||||||
import _ from 'lodash-es'
|
import _ from 'lodash-es'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { Trace } from './trace.js'
|
import { Trace } from './trace.js'
|
||||||
@@ -18,59 +17,38 @@ function createDefaultOptions () {
|
|||||||
}
|
}
|
||||||
export class Executor {
|
export class Executor {
|
||||||
constructor () {
|
constructor () {
|
||||||
this.usePlugins(DefaultPlugins)
|
|
||||||
this.trace = new Trace()
|
this.trace = new Trace()
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
|
||||||
this.plugins[define.name] = plugin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
usePlugins (plugins) {
|
|
||||||
if (plugins) {
|
|
||||||
_.forEach(plugins, item => {
|
|
||||||
this.usePlugin(item)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async run (options) {
|
async run (options) {
|
||||||
logger.info('------------------- Cert-D ---------------------')
|
logger.info('------------------- Cert-D ---------------------')
|
||||||
try {
|
try {
|
||||||
|
this.transfer(options)
|
||||||
options = _.merge(createDefaultOptions(), options)
|
options = _.merge(createDefaultOptions(), options)
|
||||||
return await this.doRun(options)
|
return await this.doRun(options)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('任务执行出错:', e)
|
logger.error('任务执行出错', e)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transfer (options) {
|
||||||
|
const providers = options.accessProviders
|
||||||
|
if (_.isArray(providers)) {
|
||||||
|
const map = {}
|
||||||
|
for (const provider of providers) {
|
||||||
|
if (provider.key) {
|
||||||
|
map[provider.key] = provider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options.accessProviders = map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async doRun (options) {
|
async doRun (options) {
|
||||||
// 申请证书
|
// 申请证书
|
||||||
logger.info('任务开始')
|
logger.info('任务开始')
|
||||||
const certd = new Certd(options, this.providers)
|
const certd = new Certd(options)
|
||||||
const cert = await this.runCertd(certd)
|
const cert = await this.runCertd(certd)
|
||||||
if (cert == null) {
|
if (cert == null) {
|
||||||
throw new Error('申请证书失败')
|
throw new Error('申请证书失败')
|
||||||
@@ -80,8 +58,11 @@ export class Executor {
|
|||||||
logger.info('----------------------')
|
logger.info('----------------------')
|
||||||
if (!cert.isNew) {
|
if (!cert.isNew) {
|
||||||
// 如果没有更新
|
// 如果没有更新
|
||||||
if (!options.args.forceDeploy && !options.args.forceRedeploy) {
|
if (options.args.forceRedeploy) {
|
||||||
// 且不需要强制运行deploy
|
// 强制重新部署,清空保存的状态
|
||||||
|
await certd.certStore.setCurrentFile('context.json', '{}')
|
||||||
|
} else if (!options.args.forceDeploy) {
|
||||||
|
// 且不需要强制deploy
|
||||||
logger.info('证书无更新,无需重新部署')
|
logger.info('证书无更新,无需重新部署')
|
||||||
logger.info('任务完成')
|
logger.info('任务完成')
|
||||||
return { cert }
|
return { cert }
|
||||||
@@ -107,15 +88,16 @@ export class Executor {
|
|||||||
logger.info('任务完成')
|
logger.info('任务完成')
|
||||||
trace.print()
|
trace.print()
|
||||||
const result = resultTrace.get({ })
|
const result = resultTrace.get({ })
|
||||||
const returnData = {
|
if (result) {
|
||||||
|
if (result.status === 'error' && options.args.doNotThrowError === false) {
|
||||||
|
throw new Error(result.remark)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
cert,
|
cert,
|
||||||
context,
|
context,
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
if (result.status === 'error' && options.args.doNotThrowError === false) {
|
|
||||||
throw new Error(result.remark)
|
|
||||||
}
|
|
||||||
return returnData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async runCertd (certd) {
|
async runCertd (certd) {
|
||||||
@@ -140,6 +122,7 @@ export class Executor {
|
|||||||
logger.info('此流程已被禁用,跳过')
|
logger.info('此流程已被禁用,跳过')
|
||||||
logger.info('')
|
logger.info('')
|
||||||
deployTrace.set({ value: { current: 'skip', status: 'disabled', remark: '流程禁用' } })
|
deployTrace.set({ value: { current: 'skip', status: 'disabled', remark: '流程禁用' } })
|
||||||
|
deployTrace.set({ tasks: null })
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -153,6 +136,7 @@ export class Executor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deployTrace.set({ value: { status: 'success', remark: '执行成功' } })
|
deployTrace.set({ value: { status: 'success', remark: '执行成功' } })
|
||||||
|
trace.set({ type: 'result', value: { status: 'success', remark: '执行成功' } })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
deployTrace.set({ value: { status: 'error', remark: '执行失败:' + e.message } })
|
deployTrace.set({ value: { status: 'error', remark: '执行失败:' + e.message } })
|
||||||
trace.set({ type: 'result', value: { status: 'error', remark: deployName + '执行失败:' + e.message } })
|
trace.set({ type: 'result', value: { status: 'error', remark: deployName + '执行失败:' + e.message } })
|
||||||
@@ -165,7 +149,7 @@ export class Executor {
|
|||||||
|
|
||||||
async runTask ({ options, task, cert, context, deploy, trace }) {
|
async runTask ({ options, task, cert, context, deploy, trace }) {
|
||||||
const taskType = task.type
|
const taskType = task.type
|
||||||
const Plugin = this.plugins[taskType]
|
const Plugin = pluginRegistry.get(taskType)
|
||||||
const deployName = deploy.deployName
|
const deployName = deploy.deployName
|
||||||
const taskName = task.taskName
|
const taskName = task.taskName
|
||||||
if (Plugin == null) {
|
if (Plugin == null) {
|
||||||
@@ -76,7 +76,9 @@ export class Trace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const result = this.get({ type: 'result' })
|
const result = this.get({ type: 'result' })
|
||||||
this.printTraceLine(result, 'result', '')
|
if (result) {
|
||||||
|
this.printTraceLine(result, 'result', '')
|
||||||
|
}
|
||||||
const mainContext = {}
|
const mainContext = {}
|
||||||
_.merge(mainContext, context)
|
_.merge(mainContext, context)
|
||||||
delete mainContext.__trace__
|
delete mainContext.__trace__
|
||||||
@@ -1,8 +1,16 @@
|
|||||||
import pkg from 'chai'
|
import pkg from 'chai'
|
||||||
import { Executor } from '../src/index.js'
|
import { Executor } from '../src/index.js'
|
||||||
import { createOptions } from '../../../test/options.js'
|
import { createOptions } from '../../../../test/options.js'
|
||||||
|
import PluginAliyun from '@certd/plugin-aliyun'
|
||||||
|
import PluginTencent from '@certd/plugin-tencent'
|
||||||
|
import PluginHost from '@certd/plugin-host'
|
||||||
const { expect } = pkg
|
const { expect } = pkg
|
||||||
|
|
||||||
|
// 安装默认插件和授权提供者
|
||||||
|
PluginAliyun.install()
|
||||||
|
PluginTencent.install()
|
||||||
|
PluginHost.install()
|
||||||
|
|
||||||
describe('AutoDeploy', function () {
|
describe('AutoDeploy', function () {
|
||||||
it('#run', async function () {
|
it('#run', async function () {
|
||||||
this.timeout(120000)
|
this.timeout(120000)
|
||||||
3920
packages/executor/package-lock.json
generated
3920
packages/executor/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3690
packages/plugins/package-lock.json
generated
3690
packages/plugins/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
7
packages/plugins/plugin-aliyun/.gitignore
vendored
Normal file
7
packages/plugins/plugin-aliyun/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.vscode/
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
yarn.lock
|
||||||
|
package-lock.json
|
||||||
|
/.idea/
|
||||||
29
packages/plugins/plugin-aliyun/package.json
Normal file
29
packages/plugins/plugin-aliyun/package.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "@certd/plugin-aliyun",
|
||||||
|
"version": "0.3.1",
|
||||||
|
"description": "",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@alicloud/cs20151215": "^3.0.3",
|
||||||
|
"@alicloud/openapi-client": "^0.4.0",
|
||||||
|
"@alicloud/pop-core": "^1.7.10",
|
||||||
|
"@certd/api": "^0.3.1",
|
||||||
|
"dayjs": "^1.9.7",
|
||||||
|
"lodash-es": "^4.17.20"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@certd/certd": "^0.3.1",
|
||||||
|
"@certd/plugin-common": "^0.3.1",
|
||||||
|
"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",
|
||||||
|
"gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
export class AliyunAccessProvider {
|
||||||
|
static define () {
|
||||||
|
return {
|
||||||
|
name: 'aliyun',
|
||||||
|
label: '阿里云',
|
||||||
|
desc: '',
|
||||||
|
input: {
|
||||||
|
accessKeyId: {
|
||||||
|
type: String,
|
||||||
|
component: {
|
||||||
|
placeholder: 'accessKeyId',
|
||||||
|
rules: [{ required: true, message: '必填项' }]
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
accessKeySecret: {
|
||||||
|
type: String,
|
||||||
|
component: {
|
||||||
|
placeholder: 'accessKeySecret',
|
||||||
|
rules: [{ required: true, message: '必填项' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,20 +2,41 @@ import { AbstractDnsProvider } from '@certd/api'
|
|||||||
import Core from '@alicloud/pop-core'
|
import Core from '@alicloud/pop-core'
|
||||||
import _ from 'lodash-es'
|
import _ from 'lodash-es'
|
||||||
export class AliyunDnsProvider extends AbstractDnsProvider {
|
export class AliyunDnsProvider extends AbstractDnsProvider {
|
||||||
constructor (dnsProviderConfig) {
|
static define () {
|
||||||
super()
|
return {
|
||||||
|
name: 'aliyun',
|
||||||
|
label: '阿里云',
|
||||||
|
desc: '',
|
||||||
|
input: {
|
||||||
|
accessProvider: {
|
||||||
|
label: '授权',
|
||||||
|
type: [String, Object],
|
||||||
|
desc: '需要aliyun类型的授权',
|
||||||
|
component: {
|
||||||
|
name: 'access-provider-selector',
|
||||||
|
filter: 'aliyun'
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (args) {
|
||||||
|
super(args)
|
||||||
|
const { props } = args
|
||||||
|
const accessProvider = this.getAccessProvider(props.accessProvider)
|
||||||
this.client = new Core({
|
this.client = new Core({
|
||||||
accessKeyId: dnsProviderConfig.accessKeyId,
|
accessKeyId: accessProvider.accessKeyId,
|
||||||
accessKeySecret: dnsProviderConfig.accessKeySecret,
|
accessKeySecret: accessProvider.accessKeySecret,
|
||||||
endpoint: 'https://alidns.aliyuncs.com',
|
endpoint: 'https://alidns.aliyuncs.com',
|
||||||
apiVersion: '2015-01-09'
|
apiVersion: '2015-01-09'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static name () {
|
|
||||||
return 'aliyun'
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDomainList () {
|
async getDomainList () {
|
||||||
const params = {
|
const params = {
|
||||||
RegionId: 'cn-hangzhou'
|
RegionId: 'cn-hangzhou'
|
||||||
25
packages/plugins/plugin-aliyun/src/index.js
Normal file
25
packages/plugins/plugin-aliyun/src/index.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import _ from 'lodash-es'
|
||||||
|
|
||||||
|
import { AliyunDnsProvider } from './dns-providers/aliyun.js'
|
||||||
|
import { AliyunAccessProvider } from './access-providers/aliyun.js'
|
||||||
|
import { UploadCertToAliyun } from './plugins/upload-to-aliyun/index.js'
|
||||||
|
import { DeployCertToAliyunCDN } from './plugins/deploy-to-cdn/index.js'
|
||||||
|
import { DeployCertToAliyunAckIngress } from './plugins/deploy-to-ack-ingress/index.js'
|
||||||
|
|
||||||
|
import { pluginRegistry, accessProviderRegistry, dnsProviderRegistry } from '@certd/api'
|
||||||
|
|
||||||
|
export const Plugins = {
|
||||||
|
UploadCertToAliyun,
|
||||||
|
DeployCertToAliyunCDN,
|
||||||
|
DeployCertToAliyunAckIngress
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
install () {
|
||||||
|
_.forEach(Plugins, item => {
|
||||||
|
pluginRegistry.install(item)
|
||||||
|
})
|
||||||
|
|
||||||
|
accessProviderRegistry.install(AliyunAccessProvider)
|
||||||
|
dnsProviderRegistry.install(AliyunDnsProvider)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
import { AbstractAliyunPlugin } from '../abstract-aliyun.js'
|
||||||
|
import Core from '@alicloud/pop-core'
|
||||||
|
import { K8sClient } from '@certd/plugin-common'
|
||||||
|
const ROAClient = Core.ROAClient
|
||||||
|
|
||||||
|
const define = {
|
||||||
|
name: 'deployCertToAliyunAckIngress',
|
||||||
|
label: '部署到阿里云AckIngress',
|
||||||
|
input: {
|
||||||
|
clusterId: {
|
||||||
|
label: '集群id',
|
||||||
|
component: {
|
||||||
|
placeholder: '集群id'
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretName: {
|
||||||
|
label: '保密字典Id',
|
||||||
|
component: {
|
||||||
|
placeholder: '保密字典Id'
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
regionId: {
|
||||||
|
label: '大区',
|
||||||
|
value: 'cn-shanghai',
|
||||||
|
component: {
|
||||||
|
placeholder: '集群所属大区'
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
namespace: {
|
||||||
|
label: '命名空间',
|
||||||
|
value: 'default',
|
||||||
|
component: {
|
||||||
|
placeholder: '命名空间'
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
ingressName: {
|
||||||
|
label: 'ingress名称',
|
||||||
|
value: '',
|
||||||
|
component: {
|
||||||
|
placeholder: 'ingress名称'
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
helper: '可以传入一个数组'
|
||||||
|
},
|
||||||
|
ingressClass: {
|
||||||
|
label: 'ingress类型',
|
||||||
|
value: 'nginx',
|
||||||
|
component: {
|
||||||
|
placeholder: '暂时只支持nginx类型'
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isPrivateIpAddress: {
|
||||||
|
label: '是否私网ip',
|
||||||
|
value: false,
|
||||||
|
component: {
|
||||||
|
placeholder: '集群连接端点是否是私网ip'
|
||||||
|
},
|
||||||
|
helper: '如果您当前certd运行在同一个私网下,可以选择是。',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
accessProvider: {
|
||||||
|
label: 'Access提供者',
|
||||||
|
type: [String, Object],
|
||||||
|
desc: 'access授权',
|
||||||
|
component: {
|
||||||
|
name: 'access-provider-selector',
|
||||||
|
filter: 'aliyun'
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DeployCertToAliyunAckIngress extends AbstractAliyunPlugin {
|
||||||
|
static define () {
|
||||||
|
return define
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute ({ cert, props, context }) {
|
||||||
|
const accessProvider = this.getAccessProvider(props.accessProvider)
|
||||||
|
const client = this.getClient(accessProvider, props.regionId)
|
||||||
|
|
||||||
|
const kubeConfigStr = await this.getKubeConfig(client, props.clusterId, props.isPrivateIpAddress)
|
||||||
|
|
||||||
|
this.logger.info('kubeconfig已成功获取')
|
||||||
|
const k8sClient = new K8sClient(kubeConfigStr)
|
||||||
|
const ingressType = props.ingressClass || 'qcloud'
|
||||||
|
if (ingressType === 'qcloud') {
|
||||||
|
throw new Error('暂未实现')
|
||||||
|
// await this.patchQcloudCertSecret({ k8sClient, props, context })
|
||||||
|
} else {
|
||||||
|
await this.patchNginxCertSecret({ cert, k8sClient, props, context })
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.sleep(3000) // 停留2秒,等待secret部署完成
|
||||||
|
// await this.restartIngress({ k8sClient, props })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async restartIngress ({ k8sClient, props }) {
|
||||||
|
const { namespace } = props
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
metadata: {
|
||||||
|
labels: {
|
||||||
|
certd: this.appendTimeSuffix('certd')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const ingressList = await k8sClient.getIngressList({ namespace })
|
||||||
|
console.log('ingressList:', ingressList)
|
||||||
|
if (!ingressList || !ingressList.body || !ingressList.body.items) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const ingressNames = ingressList.body.items.filter(item => {
|
||||||
|
if (!item.spec.tls) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for (const tls of item.spec.tls) {
|
||||||
|
if (tls.secretName === props.secretName) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}).map(item => {
|
||||||
|
return item.metadata.name
|
||||||
|
})
|
||||||
|
for (const ingress of ingressNames) {
|
||||||
|
await k8sClient.patchIngress({ namespace, ingressName: ingress, body })
|
||||||
|
this.logger.info(`ingress已重启:${ingress}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async patchNginxCertSecret ({ cert, k8sClient, props, context }) {
|
||||||
|
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 } = props
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
data: {
|
||||||
|
'tls.crt': crtBase64,
|
||||||
|
'tls.key': keyBase64
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
labels: {
|
||||||
|
certd: this.appendTimeSuffix('certd')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let secretNames = secretName
|
||||||
|
if (typeof secretName === 'string') {
|
||||||
|
secretNames = [secretName]
|
||||||
|
}
|
||||||
|
for (const secret of secretNames) {
|
||||||
|
await k8sClient.patchSecret({ namespace, secretName: secret, body })
|
||||||
|
this.logger.info(`CertSecret已更新:${secret}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getClient (aliyunProvider, regionId) {
|
||||||
|
return new ROAClient({
|
||||||
|
accessKeyId: aliyunProvider.accessKeyId,
|
||||||
|
accessKeySecret: aliyunProvider.accessKeySecret,
|
||||||
|
endpoint: `https://cs.${regionId}.aliyuncs.com`,
|
||||||
|
apiVersion: '2015-12-15'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async getKubeConfig (client, clusterId, isPrivateIpAddress = false) {
|
||||||
|
const httpMethod = 'GET'
|
||||||
|
const uriPath = `/k8s/${clusterId}/user_config`
|
||||||
|
const queries = {
|
||||||
|
PrivateIpAddress: isPrivateIpAddress
|
||||||
|
}
|
||||||
|
const body = '{}'
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
const requestOption = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await client.request(httpMethod, uriPath, queries, body, headers, requestOption)
|
||||||
|
return res.config
|
||||||
|
} catch (e) {
|
||||||
|
console.error('请求出错:', e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { AbstractAliyunPlugin } from '../abstract-aliyun.js'
|
||||||
|
import Core from '@alicloud/pop-core'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
|
const define = {
|
||||||
|
name: 'deployCertToAliyunCDN',
|
||||||
|
label: '部署到阿里云CDN',
|
||||||
|
input: {
|
||||||
|
domainName: {
|
||||||
|
label: 'cdn加速域名',
|
||||||
|
component: {
|
||||||
|
placeholder: 'cdn加速域名'
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
certName: {
|
||||||
|
label: '证书名称',
|
||||||
|
component: {
|
||||||
|
placeholder: '上传后将以此名称作为前缀'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
from: {
|
||||||
|
default: 'upload',
|
||||||
|
label: '证书来源',
|
||||||
|
required: true,
|
||||||
|
component: {
|
||||||
|
required: true,
|
||||||
|
placeholder: '证书来源',
|
||||||
|
name: 'a-select',
|
||||||
|
options: [
|
||||||
|
{ value: 'upload', label: '直接上传' },
|
||||||
|
{ value: 'cas', label: '从证书库', title: '需要uploadCertToAliyun作为前置任务' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
desc: '如果选择‘从证书库’类型,则需要以《上传证书到阿里云》作为前置任务'
|
||||||
|
|
||||||
|
},
|
||||||
|
// serverCertificateStatus: {
|
||||||
|
// label: '启用https',
|
||||||
|
// options: [
|
||||||
|
// { value: 'on', label: '开启HTTPS,并更新证书' },
|
||||||
|
// { value: 'auto', label: '若HTTPS开启则更新,未开启不更新' }
|
||||||
|
// ],
|
||||||
|
// required:true
|
||||||
|
// },
|
||||||
|
accessProvider: {
|
||||||
|
label: 'Access提供者',
|
||||||
|
type: [String, Object],
|
||||||
|
desc: 'access授权',
|
||||||
|
component: {
|
||||||
|
name: 'access-provider-selector',
|
||||||
|
filter: 'aliyun'
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DeployCertToAliyunCDN extends AbstractAliyunPlugin {
|
||||||
|
static define () {
|
||||||
|
return define
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute ({ cert, props, context }) {
|
||||||
|
const accessProvider = this.getAccessProvider(props.accessProvider)
|
||||||
|
const client = this.getClient(accessProvider)
|
||||||
|
const params = this.buildParams(props, context, cert)
|
||||||
|
await this.doRequest(client, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
getClient (aliyunProvider) {
|
||||||
|
return new Core({
|
||||||
|
accessKeyId: aliyunProvider.accessKeyId,
|
||||||
|
accessKeySecret: aliyunProvider.accessKeySecret,
|
||||||
|
endpoint: 'https://cdn.aliyuncs.com',
|
||||||
|
apiVersion: '2018-05-10'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
buildParams (args, context, cert) {
|
||||||
|
const { certName, from, domainName } = args
|
||||||
|
const CertName = certName + '-' + dayjs().format('YYYYMMDDHHmmss')
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
RegionId: 'cn-hangzhou',
|
||||||
|
DomainName: domainName,
|
||||||
|
ServerCertificateStatus: 'on',
|
||||||
|
CertName: CertName,
|
||||||
|
CertType: from,
|
||||||
|
ServerCertificate: cert.crt,
|
||||||
|
PrivateKey: cert.key
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
async doRequest (client, params) {
|
||||||
|
const requestOption = {
|
||||||
|
method: 'POST'
|
||||||
|
}
|
||||||
|
const ret = await client.request('SetDomainServerCertificate', params, requestOption)
|
||||||
|
this.checkRet(ret)
|
||||||
|
this.logger.info('设置cdn证书成功:', ret.RequestId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +1,41 @@
|
|||||||
import Core from '@alicloud/pop-core'
|
import Core from '@alicloud/pop-core'
|
||||||
import { AbstractAliyunPlugin } from '../abstract-aliyun.js'
|
import { AbstractAliyunPlugin } from '../abstract-aliyun.js'
|
||||||
export class UploadCertToAliyun extends AbstractAliyunPlugin {
|
|
||||||
/**
|
const define = {
|
||||||
* 插件定义
|
name: 'uploadCertToAliyun',
|
||||||
* 名称
|
label: '上传证书到阿里云',
|
||||||
* 入参
|
input: {
|
||||||
* 出参
|
name: {
|
||||||
*/
|
label: '证书名称',
|
||||||
static define () {
|
desc: '证书上传后将以此参数作为名称前缀'
|
||||||
return {
|
},
|
||||||
name: 'uploadCertToAliyun',
|
regionId: {
|
||||||
label: '上传证书到阿里云',
|
label: '大区',
|
||||||
input: {
|
default: 'cn-hangzhou',
|
||||||
name: {
|
required: true
|
||||||
label: '证书名称'
|
},
|
||||||
},
|
accessProvider: {
|
||||||
regionId: {
|
label: 'Access提供者',
|
||||||
label: '大区',
|
type: [String, Object],
|
||||||
value: 'cn-hangzhou'
|
desc: 'access授权',
|
||||||
},
|
component: {
|
||||||
accessProvider: {
|
name: 'access-provider-selector',
|
||||||
label: 'Access提供者',
|
filter: 'aliyun'
|
||||||
type: [String, Object],
|
|
||||||
desc: 'AccessProviders的key 或 一个包含accessKeyId与accessKeySecret的对象',
|
|
||||||
options: 'accessProviders[type=aliyun]'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
output: {
|
required: true
|
||||||
aliyunCertId: {
|
|
||||||
type: String,
|
|
||||||
desc: '上传成功后的阿里云CertId'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
aliyunCertId: {
|
||||||
|
type: String,
|
||||||
|
desc: '上传成功后的阿里云CertId'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UploadCertToAliyun extends AbstractAliyunPlugin {
|
||||||
|
static define () {
|
||||||
|
return define
|
||||||
}
|
}
|
||||||
|
|
||||||
getClient (aliyunProvider) {
|
getClient (aliyunProvider) {
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import pkg from 'chai'
|
import pkg from 'chai'
|
||||||
import { createOptions } from '../../../../test/options.js'
|
import { createOptions } from '../../../../../test/options.js'
|
||||||
import { Certd } from '../../src/index.js'
|
import { Certd } from '@certd/certd'
|
||||||
|
import PluginAliyun from '../../src/index.js'
|
||||||
|
|
||||||
|
// 安装默认插件和授权提供者
|
||||||
|
PluginAliyun.install()
|
||||||
const { expect } = pkg
|
const { expect } = pkg
|
||||||
describe('AliyunDnsProvider', function () {
|
describe('AliyunDnsProvider', function () {
|
||||||
it('#申请证书-aliyun', async function () {
|
it('#申请证书-aliyun', async function () {
|
||||||
@@ -1,27 +1,33 @@
|
|||||||
import pkg from 'chai'
|
import pkg from 'chai'
|
||||||
import AliyunDnsProvider from '../../src/dns-provider/aliyun.js'
|
import { AliyunDnsProvider } from '../../src/dns-providers/aliyun.js'
|
||||||
import { createOptions } from '../../../../test/options.js'
|
import { createOptions } from '../../../../../test/options.js'
|
||||||
const { expect } = pkg
|
const { expect } = pkg
|
||||||
|
|
||||||
|
export function getPluginOptions () {
|
||||||
|
const options = createOptions()
|
||||||
|
return { accessProviders: options.accessProviders, props: options.cert.dnsProvider }
|
||||||
|
}
|
||||||
|
|
||||||
describe('AliyunDnsProvider', function () {
|
describe('AliyunDnsProvider', function () {
|
||||||
it('#getDomainList', async function () {
|
it('#getDomainList', async function () {
|
||||||
const options = createOptions()
|
const options = getPluginOptions()
|
||||||
const aliyunDnsProvider = new AliyunDnsProvider(options.accessProviders.aliyun)
|
const aliyunDnsProvider = new AliyunDnsProvider(options)
|
||||||
const domainList = await aliyunDnsProvider.getDomainList()
|
const domainList = await aliyunDnsProvider.getDomainList()
|
||||||
console.log('domainList', domainList)
|
console.log('domainList', domainList)
|
||||||
expect(domainList.length).gt(0)
|
expect(domainList.length).gt(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('#getRecords', async function () {
|
it('#getRecords', async function () {
|
||||||
const options = createOptions()
|
const options = getPluginOptions()
|
||||||
const aliyunDnsProvider = new AliyunDnsProvider(options.accessProviders.aliyun)
|
const aliyunDnsProvider = new AliyunDnsProvider(options)
|
||||||
const recordList = await aliyunDnsProvider.getRecords('docmirror.cn', '*')
|
const recordList = await aliyunDnsProvider.getRecords('docmirror.cn', '*')
|
||||||
console.log('recordList', recordList)
|
console.log('recordList', recordList)
|
||||||
expect(recordList.length).gt(0)
|
expect(recordList.length).gt(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('#createAndRemoveRecord', async function () {
|
it('#createAndRemoveRecord', async function () {
|
||||||
const options = createOptions()
|
const options = getPluginOptions()
|
||||||
const aliyunDnsProvider = new AliyunDnsProvider(options.accessProviders.aliyun)
|
const aliyunDnsProvider = new AliyunDnsProvider(options)
|
||||||
const record = await aliyunDnsProvider.createRecord({ fullRecord: '___certd___.__test__.docmirror.cn', type: 'TXT', value: 'aaaa' })
|
const record = await aliyunDnsProvider.createRecord({ fullRecord: '___certd___.__test__.docmirror.cn', type: 'TXT', value: 'aaaa' })
|
||||||
console.log('recordId', record)
|
console.log('recordId', record)
|
||||||
expect(record != null).ok
|
expect(record != null).ok
|
||||||
42
packages/plugins/plugin-aliyun/test/options.js
Normal file
42
packages/plugins/plugin-aliyun/test/options.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import _ from 'lodash-es'
|
||||||
|
import optionsPrivate from '../../../test/options.private.mjs'
|
||||||
|
const defaultOptions = {
|
||||||
|
version: '1.0.0',
|
||||||
|
args: {
|
||||||
|
directory: 'test',
|
||||||
|
dry: false
|
||||||
|
},
|
||||||
|
accessProviders: {
|
||||||
|
aliyun: {
|
||||||
|
providerType: 'aliyun',
|
||||||
|
accessKeyId: '',
|
||||||
|
accessKeySecret: ''
|
||||||
|
},
|
||||||
|
myLinux: {
|
||||||
|
providerType: 'SSH',
|
||||||
|
username: 'xxx',
|
||||||
|
password: 'xxx',
|
||||||
|
host: '1111.com',
|
||||||
|
port: 22,
|
||||||
|
publicKey: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cert: {
|
||||||
|
domains: ['*.docmirror.club', 'docmirror.club'],
|
||||||
|
email: 'xiaojunnuo@qq.com',
|
||||||
|
dnsProvider: { type: 'aliyun', accessProvider: 'aliyun' },
|
||||||
|
certProvider: 'letsencrypt',
|
||||||
|
csrInfo: {
|
||||||
|
country: 'CN',
|
||||||
|
state: 'GuangDong',
|
||||||
|
locality: 'ShengZhen',
|
||||||
|
organization: 'CertD Org.',
|
||||||
|
organizationUnit: 'IT Department',
|
||||||
|
emailAddress: 'xiaojunnuo@qq.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_.merge(defaultOptions, optionsPrivate)
|
||||||
|
|
||||||
|
export default defaultOptions
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import pkg from 'chai'
|
||||||
|
import { DeployCertToAliyunAckIngress } from '../../src/plugins/deploy-to-ack-ingress/index.js'
|
||||||
|
import { Certd } from '@certd/certd'
|
||||||
|
import { createOptions } from '../../../../../test/options.js'
|
||||||
|
import { K8sClient } from '@certd/plugin-common'
|
||||||
|
|
||||||
|
const { expect } = pkg
|
||||||
|
|
||||||
|
async function getOptions () {
|
||||||
|
const options = createOptions()
|
||||||
|
options.args.test = false
|
||||||
|
options.cert.email = 'xiaojunnuo@qq.com'
|
||||||
|
options.cert.domains = ['*.docmirror.cn']
|
||||||
|
const certd = new Certd(options)
|
||||||
|
const cert = await certd.readCurrentCert()
|
||||||
|
const context = {}
|
||||||
|
const deployOpts = {
|
||||||
|
accessProviders: options.accessProviders,
|
||||||
|
cert,
|
||||||
|
props: {
|
||||||
|
accessProvider: 'aliyun-yonsz-prod',
|
||||||
|
regionId: 'cn-shanghai',
|
||||||
|
clusterId: 'c9e107ca518314f70973636965037fc00',
|
||||||
|
secretName: 'default-ingress-secret1638601684896',
|
||||||
|
namespace: 'default',
|
||||||
|
ingressClass: 'nginx'
|
||||||
|
},
|
||||||
|
context
|
||||||
|
}
|
||||||
|
return { options, deployOpts }
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DeployCertToAliyunAckIngressNginx', function () {
|
||||||
|
it('#getAliyunSecrets', async function () {
|
||||||
|
this.timeout(50000)
|
||||||
|
const { options, deployOpts } = await getOptions()
|
||||||
|
const plugin = new DeployCertToAliyunAckIngress(options)
|
||||||
|
const ackClient = plugin.getClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.regionId)
|
||||||
|
const kubeConfig = await plugin.getKubeConfig(ackClient, deployOpts.props.clusterId, false)
|
||||||
|
|
||||||
|
const k8sClient = new K8sClient(kubeConfig)
|
||||||
|
const secrets = await k8sClient.getSecret({ namespace: 'default' })
|
||||||
|
|
||||||
|
console.log('secrets:', secrets)
|
||||||
|
})
|
||||||
|
it('#getAliyunIngreses', async function () {
|
||||||
|
this.timeout(50000)
|
||||||
|
const { options, deployOpts } = await getOptions()
|
||||||
|
const plugin = new DeployCertToAliyunAckIngress(options)
|
||||||
|
const ackClient = plugin.getClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.regionId)
|
||||||
|
const kubeConfig = await plugin.getKubeConfig(ackClient, deployOpts.props.clusterId, false)
|
||||||
|
|
||||||
|
const k8sClient = new K8sClient(kubeConfig)
|
||||||
|
const list = await k8sClient.getIngressList({ namespace: 'default' })
|
||||||
|
|
||||||
|
console.log('list:', list)
|
||||||
|
})
|
||||||
|
it('#execute', async function () {
|
||||||
|
this.timeout(5000)
|
||||||
|
|
||||||
|
const { options, deployOpts } = await getOptions()
|
||||||
|
const plugin = new DeployCertToAliyunAckIngress(options)
|
||||||
|
|
||||||
|
const ret = await plugin.doExecute(deployOpts)
|
||||||
|
console.log('success', ret)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
import pkg from 'chai'
|
import pkg from 'chai'
|
||||||
import { DeployCertToAliyunCDN } from '../../src/aliyun/deploy-to-cdn/index.js'
|
import { DeployCertToAliyunCDN } from '../../src/plugins/deploy-to-cdn/index.js'
|
||||||
import { Certd } from '@certd/certd'
|
import { Certd } from '@certd/certd'
|
||||||
import createOptions from '../../../../test/options.js'
|
import { createOptions } from '../../../../../test/options.js'
|
||||||
const { expect } = pkg
|
const { expect } = pkg
|
||||||
|
|
||||||
describe('DeployToAliyunCDN', function () {
|
describe('DeployToAliyunCDN', function () {
|
||||||
it('#execute', async function () {
|
it('#execute', async function () {
|
||||||
this.timeout(5000)
|
this.timeout(5000)
|
||||||
const options = createOptions()
|
const options = createOptions()
|
||||||
const plugin = new DeployCertToAliyunCDN()
|
const plugin = new DeployCertToAliyunCDN(options)
|
||||||
options.cert.domains = ['*.docmirror.cn', 'docmirror.cn']
|
options.cert.domains = ['*.docmirror.cn', 'docmirror.cn']
|
||||||
const certd = new Certd(options)
|
const certd = new Certd(options)
|
||||||
const cert = await certd.readCurrentCert()
|
const cert = await certd.readCurrentCert()
|
||||||
const ret = await plugin.doExecute({
|
const ret = await plugin.doExecute({
|
||||||
accessProviders: options.accessProviders,
|
|
||||||
cert,
|
cert,
|
||||||
props: { domainName: 'certd-cdn-upload.docmirror.cn', certName: 'certd部署测试', certType: 'cas', accessProvider: 'aliyun' }
|
props: { domainName: 'certd-cdn-upload.docmirror.cn', certName: 'certd部署测试', from: 'cas', accessProvider: 'aliyun' }
|
||||||
})
|
})
|
||||||
console.log('context:', context, ret)
|
console.log('context:', context, ret)
|
||||||
})
|
})
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import pkg from 'chai'
|
import pkg from 'chai'
|
||||||
import { UploadCertToAliyun } from '../../src/aliyun/upload-to-aliyun/index.js'
|
import { UploadCertToAliyun } from '../../src/plugins/upload-to-aliyun/index.js'
|
||||||
import { Certd } from '@certd/certd'
|
import { Certd } from '@certd/certd'
|
||||||
import { createOptions } from '../../../../test/options.js'
|
import { createOptions } from '../../../../../test/options.js'
|
||||||
const { expect } = pkg
|
const { expect } = pkg
|
||||||
describe('PluginUploadToAliyun', function () {
|
describe('PluginUploadToAliyun', function () {
|
||||||
it('#execute', async function () {
|
it('#execute', async function () {
|
||||||
@@ -9,12 +9,11 @@ describe('PluginUploadToAliyun', function () {
|
|||||||
const options = createOptions()
|
const options = createOptions()
|
||||||
options.cert.email = 'xiaojunnuo@qq.com'
|
options.cert.email = 'xiaojunnuo@qq.com'
|
||||||
options.cert.domains = ['_.docmirror.cn']
|
options.cert.domains = ['_.docmirror.cn']
|
||||||
const plugin = new UploadCertToAliyun()
|
const plugin = new UploadCertToAliyun(options)
|
||||||
const certd = new Certd(options)
|
const certd = new Certd(options)
|
||||||
const cert = await certd.readCurrentCert()
|
const cert = await certd.readCurrentCert()
|
||||||
const context = {}
|
const context = {}
|
||||||
const deployOpts = {
|
const deployOpts = {
|
||||||
accessProviders: options.accessProviders,
|
|
||||||
cert,
|
cert,
|
||||||
props: { accessProvider: 'aliyun' },
|
props: { accessProvider: 'aliyun' },
|
||||||
context
|
context
|
||||||
@@ -22,7 +21,7 @@ describe('PluginUploadToAliyun', function () {
|
|||||||
await plugin.doExecute(deployOpts)
|
await plugin.doExecute(deployOpts)
|
||||||
console.log('context:', context)
|
console.log('context:', context)
|
||||||
|
|
||||||
// await plugin.sleep(1000)
|
// await plugin.sleep(1000)
|
||||||
// await plugin.rollback(deployOpts)
|
// await plugin.rollback(deployOpts)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
7
packages/plugins/plugin-common/.gitignore
vendored
Normal file
7
packages/plugins/plugin-common/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.vscode/
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
yarn.lock
|
||||||
|
package-lock.json
|
||||||
|
/.idea/
|
||||||
23
packages/plugins/plugin-common/package.json
Normal file
23
packages/plugins/plugin-common/package.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "@certd/plugin-common",
|
||||||
|
"version": "0.3.1",
|
||||||
|
"description": "",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"kubernetes-client": "^9.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@certd/certd": "^0.3.1",
|
||||||
|
"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",
|
||||||
|
"gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
|
||||||
|
}
|
||||||
1
packages/plugins/plugin-common/src/index.js
Normal file
1
packages/plugins/plugin-common/src/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { K8sClient } from './lib/k8s.client.js'
|
||||||
@@ -45,7 +45,7 @@ export class K8sClient {
|
|||||||
* @param opts = {namespace:default}
|
* @param opts = {namespace:default}
|
||||||
* @returns secretsList
|
* @returns secretsList
|
||||||
*/
|
*/
|
||||||
async getSecret (opts) {
|
async getSecret (opts = {}) {
|
||||||
const namespace = opts.namespace || 'default'
|
const namespace = opts.namespace || 'default'
|
||||||
const secrets = await this.client.api.v1.namespaces(namespace).secrets.get()
|
const secrets = await this.client.api.v1.namespaces(namespace).secrets.get()
|
||||||
return secrets
|
return secrets
|
||||||
@@ -87,6 +87,11 @@ export class K8sClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getIngressList (opts) {
|
||||||
|
const namespace = opts.namespace || 'default'
|
||||||
|
return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses.get()
|
||||||
|
}
|
||||||
|
|
||||||
async getIngress (opts) {
|
async getIngress (opts) {
|
||||||
const namespace = opts.namespace || 'default'
|
const namespace = opts.namespace || 'default'
|
||||||
const ingressName = opts.ingressName
|
const ingressName = opts.ingressName
|
||||||
7
packages/plugins/plugin-host/.gitignore
vendored
Normal file
7
packages/plugins/plugin-host/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.vscode/
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
yarn.lock
|
||||||
|
package-lock.json
|
||||||
|
/.idea/
|
||||||
26
packages/plugins/plugin-host/package.json
Normal file
26
packages/plugins/plugin-host/package.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "@certd/plugin-host",
|
||||||
|
"version": "0.3.1",
|
||||||
|
"description": "",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@certd/api": "^0.3.1",
|
||||||
|
"dayjs": "^1.9.7",
|
||||||
|
"lodash-es": "^4.17.20",
|
||||||
|
"ssh2": "^0.8.9"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@certd/certd": "^0.3.1",
|
||||||
|
"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",
|
||||||
|
"gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
|
||||||
|
}
|
||||||
26
packages/plugins/plugin-host/src/access-providers/ssh.js
Normal file
26
packages/plugins/plugin-host/src/access-providers/ssh.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
export class SSHAccessProvider {
|
||||||
|
static define () {
|
||||||
|
return {
|
||||||
|
name: 'ssh',
|
||||||
|
label: '主机',
|
||||||
|
desc: '',
|
||||||
|
input: {
|
||||||
|
host: { required: true },
|
||||||
|
port: {
|
||||||
|
label: '端口',
|
||||||
|
type: Number,
|
||||||
|
default: '22',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
default: 'root',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
password: { desc: '登录密码' },
|
||||||
|
privateKey: {
|
||||||
|
desc: '密钥,密码或此项必填一项'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
packages/plugins/plugin-host/src/index.js
Normal file
22
packages/plugins/plugin-host/src/index.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import _ from 'lodash-es'
|
||||||
|
|
||||||
|
import { SSHAccessProvider } from './access-providers/ssh.js'
|
||||||
|
|
||||||
|
import { UploadCertToHost } from './plugins/upload-to-host/index.js'
|
||||||
|
import { HostShellExecute } from './plugins/host-shell-execute/index.js'
|
||||||
|
|
||||||
|
import { pluginRegistry, accessProviderRegistry } from '@certd/api'
|
||||||
|
|
||||||
|
export const DefaultPlugins = {
|
||||||
|
UploadCertToHost,
|
||||||
|
HostShellExecute
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
install () {
|
||||||
|
_.forEach(DefaultPlugins, item => {
|
||||||
|
pluginRegistry.install(item)
|
||||||
|
})
|
||||||
|
|
||||||
|
accessProviderRegistry.install(SSHAccessProvider)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,14 +12,21 @@ export class HostShellExecute extends AbstractHostPlugin {
|
|||||||
name: 'hostShellExecute',
|
name: 'hostShellExecute',
|
||||||
label: '执行远程主机脚本命令',
|
label: '执行远程主机脚本命令',
|
||||||
input: {
|
input: {
|
||||||
script: {
|
|
||||||
label: 'shell脚本命令'
|
|
||||||
},
|
|
||||||
accessProvider: {
|
accessProvider: {
|
||||||
label: '主机登录配置',
|
label: '主机登录配置',
|
||||||
type: [String, Object],
|
type: [String, Object],
|
||||||
desc: 'AccessProviders的key 或 一个包含用户名密码的对象',
|
desc: '登录',
|
||||||
options: 'accessProviders[type=ssh]'
|
component: {
|
||||||
|
name: 'access-provider-selector',
|
||||||
|
filter: 'ssh'
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
script: {
|
||||||
|
label: 'shell脚本命令',
|
||||||
|
component: {
|
||||||
|
name: 'a-textarea'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
@@ -32,7 +39,7 @@ export class HostShellExecute extends AbstractHostPlugin {
|
|||||||
const { script, accessProvider } = props
|
const { script, accessProvider } = props
|
||||||
const connectConf = this.getAccessProvider(accessProvider)
|
const connectConf = this.getAccessProvider(accessProvider)
|
||||||
const sshClient = new SshClient()
|
const sshClient = new SshClient()
|
||||||
const ret = await sshClient.shell({
|
const ret = await sshClient.exec({
|
||||||
connectConf,
|
connectConf,
|
||||||
script
|
script
|
||||||
})
|
})
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import ssh2 from 'ssh2'
|
import ssh2 from 'ssh2'
|
||||||
import logger from '../utils/util.log.js'
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { util } from '@certd/api'
|
||||||
|
import _ from 'lodash-es'
|
||||||
|
const logger = util.logger
|
||||||
export class SshClient {
|
export class SshClient {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -13,7 +15,7 @@ export class SshClient {
|
|||||||
}
|
}
|
||||||
* @param transports
|
* @param transports
|
||||||
*/
|
*/
|
||||||
uploadFiles ({ connectConf, transports }) {
|
uploadFiles ({ connectConf, transports, sudo = false }) {
|
||||||
const conn = new ssh2.Client()
|
const conn = new ssh2.Client()
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -27,7 +29,8 @@ export class SshClient {
|
|||||||
try {
|
try {
|
||||||
for (const transport of transports) {
|
for (const transport of transports) {
|
||||||
logger.info('上传文件:', JSON.stringify(transport))
|
logger.info('上传文件:', JSON.stringify(transport))
|
||||||
await this.exec({ conn, cmd: 'mkdir ' + path.dirname(transport.remotePath) })
|
sudo = sudo ? 'sudo' : ''
|
||||||
|
await this.exec({ connectConf, script: `${sudo} mkdir -p ${path.dirname(transport.remotePath)} ` })
|
||||||
await this.fastPut({ sftp, ...transport })
|
await this.fastPut({ sftp, ...transport })
|
||||||
}
|
}
|
||||||
resolve()
|
resolve()
|
||||||
@@ -41,6 +44,43 @@ export class SshClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exec ({ connectConf, script }) {
|
||||||
|
if (_.isArray(script)) {
|
||||||
|
script = script.join('\n')
|
||||||
|
}
|
||||||
|
console.log('执行命令:', script)
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.connect({
|
||||||
|
connectConf,
|
||||||
|
onReady: (conn) => {
|
||||||
|
conn.exec(script, (err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let data = null
|
||||||
|
stream.on('close', (code, signal) => {
|
||||||
|
console.log(`[${connectConf.host}][close]:code:${code}`)
|
||||||
|
data = data ? data.toString() : null
|
||||||
|
if (code === 0) {
|
||||||
|
resolve(data)
|
||||||
|
} else {
|
||||||
|
reject(new Error(data))
|
||||||
|
}
|
||||||
|
conn.end()
|
||||||
|
}).on('data', (ret) => {
|
||||||
|
console.log(`[${connectConf.host}][info]: ` + ret)
|
||||||
|
data = ret
|
||||||
|
}).stderr.on('data', (err) => {
|
||||||
|
console.log(`[${connectConf.host}][error]: ` + err)
|
||||||
|
data = err
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
shell ({ connectConf, script }) {
|
shell ({ connectConf, script }) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.connect({
|
this.connect({
|
||||||
@@ -87,24 +127,4 @@ export class SshClient {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exec ({ conn, cmd }) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
conn.exec(cmd, (err, stream) => {
|
|
||||||
if (err) {
|
|
||||||
logger.error('执行命令出错', err)
|
|
||||||
reject(err)
|
|
||||||
// return conn.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.on('close', (code, signal) => {
|
|
||||||
// logger.info('Stream :: close :: code: ' + code + ', signal: ' + signal)
|
|
||||||
// conn.end()
|
|
||||||
resolve()
|
|
||||||
}).on('data', (data) => {
|
|
||||||
logger.info('data', data.toString())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -13,16 +13,23 @@ export class UploadCertToHost extends AbstractHostPlugin {
|
|||||||
label: '上传证书到主机',
|
label: '上传证书到主机',
|
||||||
input: {
|
input: {
|
||||||
crtPath: {
|
crtPath: {
|
||||||
label: '证书路径'
|
label: '证书保存路径'
|
||||||
},
|
},
|
||||||
keyPath: {
|
keyPath: {
|
||||||
label: '私钥路径'
|
label: '私钥保存路径'
|
||||||
},
|
},
|
||||||
accessProvider: {
|
accessProvider: {
|
||||||
label: '主机登录配置',
|
label: '主机登录配置',
|
||||||
type: [String, Object],
|
type: [String, Object],
|
||||||
desc: 'AccessProviders的key 或 一个包含用户名密码的对象',
|
desc: 'access授权',
|
||||||
options: 'accessProviders[type=ssh]'
|
component: {
|
||||||
|
name: 'access-provider-selector',
|
||||||
|
filter: 'ssh'
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
sudo: {
|
||||||
|
label: '是否sudo'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import pkg from 'chai'
|
||||||
|
import { HostShellExecute } from '../../src/plugins/host-shell-execute/index.js'
|
||||||
|
import { Certd } from '@certd/certd'
|
||||||
|
import { createOptions } from '../../../../../test/options.js'
|
||||||
|
const { expect } = pkg
|
||||||
|
describe('HostShellExecute', function () {
|
||||||
|
it('#execute', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
const options = createOptions()
|
||||||
|
options.args = { test: false }
|
||||||
|
options.cert.email = 'xiaojunnuo@qq.com'
|
||||||
|
options.cert.domains = ['*.docmirror.cn']
|
||||||
|
const plugin = new HostShellExecute(options)
|
||||||
|
const certd = new Certd(options)
|
||||||
|
const cert = await certd.readCurrentCert()
|
||||||
|
const context = {}
|
||||||
|
const uploadOpts = {
|
||||||
|
cert,
|
||||||
|
props: { script: ['ls ', 'ls '], accessProvider: 'aliyun-ssh' },
|
||||||
|
context
|
||||||
|
}
|
||||||
|
const ret = await plugin.doExecute(uploadOpts)
|
||||||
|
expect(ret).ok
|
||||||
|
console.log('-----' + JSON.stringify(ret))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('#execute-hk-restart-docker', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
const options = createOptions()
|
||||||
|
const plugin = new HostShellExecute(options)
|
||||||
|
const uploadOpts = {
|
||||||
|
props: { script: ['cd /home/ubuntu/deloy/nginx-proxy\nsudo docker-compose build\nsudo docker-compose up -d\n'], accessProvider: 'aliyun-ssh-hk' },
|
||||||
|
context: {}
|
||||||
|
}
|
||||||
|
const ret = await plugin.doExecute(uploadOpts)
|
||||||
|
expect(ret).ok
|
||||||
|
console.log('-----' + JSON.stringify(ret))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('#execute-publicKey-login', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
const options = createOptions()
|
||||||
|
const plugin = new HostShellExecute(options)
|
||||||
|
const shellOpts = {
|
||||||
|
props: { script: ['ls'], accessProvider: 'tencent-ssh-base01' },
|
||||||
|
context: {}
|
||||||
|
}
|
||||||
|
const ret = await plugin.doExecute(shellOpts)
|
||||||
|
expect(ret).ok
|
||||||
|
console.log('-----' + JSON.stringify(ret))
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import pkg from 'chai'
|
||||||
|
import { UploadCertToHost } from '../../src/plugins/upload-to-host/index.js'
|
||||||
|
import { Certd } from '@certd/certd'
|
||||||
|
import { createOptions } from '../../../../../test/options.js'
|
||||||
|
const { expect } = pkg
|
||||||
|
describe('PluginUploadToHost', function () {
|
||||||
|
it('#execute', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
const options = createOptions()
|
||||||
|
options.args = { test: false }
|
||||||
|
options.cert.email = 'xiaojunnuo@qq.com'
|
||||||
|
options.cert.domains = ['*.docmirror.cn']
|
||||||
|
const plugin = new UploadCertToHost(options)
|
||||||
|
const certd = new Certd(options)
|
||||||
|
const cert = await certd.readCurrentCert()
|
||||||
|
const context = {}
|
||||||
|
const uploadOpts = {
|
||||||
|
cert,
|
||||||
|
props: { crtPath: '/root/certd/test/test.crt', keyPath: '/root/certd/test/test.key', accessProvider: 'aliyun-ssh' },
|
||||||
|
context
|
||||||
|
}
|
||||||
|
await plugin.doExecute(uploadOpts)
|
||||||
|
console.log('context:', context)
|
||||||
|
|
||||||
|
await plugin.doRollback(uploadOpts)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('#execute-to-ubantu', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
const options = createOptions()
|
||||||
|
options.args = { test: false }
|
||||||
|
options.cert.email = 'xiaojunnuo@qq.com'
|
||||||
|
options.cert.domains = ['*.docmirror.cn']
|
||||||
|
const plugin = new UploadCertToHost(options)
|
||||||
|
const certd = new Certd(options)
|
||||||
|
const cert = await certd.readCurrentCert()
|
||||||
|
const context = {}
|
||||||
|
const uploadOpts = {
|
||||||
|
cert,
|
||||||
|
props: { crtPath: '/home/ubuntu/deloy/nginx-proxy/ssl/test.crt', keyPath: '/home/ubuntu/deloy/nginx-proxy/ssl/test.key', accessProvider: 'aliyun-ssh-hk' },
|
||||||
|
context
|
||||||
|
}
|
||||||
|
await plugin.doExecute(uploadOpts)
|
||||||
|
console.log('context:', context)
|
||||||
|
|
||||||
|
await plugin.doRollback(uploadOpts)
|
||||||
|
})
|
||||||
|
})
|
||||||
14
packages/plugins/plugin-tencent/.eslintrc
Normal file
14
packages/plugins/plugin-tencent/.eslintrc
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "standard",
|
||||||
|
"env": {
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.test.js", "*.spec.js"],
|
||||||
|
"rules": {
|
||||||
|
"no-unused-expressions": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
7
packages/plugins/plugin-tencent/.gitignore
vendored
Normal file
7
packages/plugins/plugin-tencent/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.vscode/
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
yarn.lock
|
||||||
|
package-lock.json
|
||||||
|
/.idea/
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugins",
|
"name": "@certd/plugin-tencent",
|
||||||
"version": "0.1.11",
|
"version": "0.3.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "./src/index.js",
|
"main": "src/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alicloud/pop-core": "^1.7.10",
|
"@certd/api": "^0.3.1",
|
||||||
"@certd/api": "^0.1.11",
|
"@certd/plugin-common": "^0.3.1",
|
||||||
"dayjs": "^1.9.7",
|
"dayjs": "^1.9.7",
|
||||||
"kubernetes-client": "^9.0.0",
|
"kubernetes-client": "^9.0.0",
|
||||||
"lodash-es": "^4.17.20",
|
"lodash-es": "^4.17.20",
|
||||||
"ssh2": "^0.8.9",
|
|
||||||
"tencentcloud-sdk-nodejs": "^4.0.44"
|
"tencentcloud-sdk-nodejs": "^4.0.44"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@certd/certd": "^0.3.1",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
"eslint": "^7.15.0",
|
"eslint": "^7.15.0",
|
||||||
"eslint-config-standard": "^16.0.2",
|
"eslint-config-standard": "^16.0.2",
|
||||||
@@ -23,5 +23,6 @@
|
|||||||
"mocha": "^8.2.1"
|
"mocha": "^8.2.1"
|
||||||
},
|
},
|
||||||
"author": "Greper",
|
"author": "Greper",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
export class DnspodAccessProvider {
|
||||||
|
static define () {
|
||||||
|
return {
|
||||||
|
name: 'dnspod',
|
||||||
|
label: 'dnspod',
|
||||||
|
desc: '腾讯云的域名解析接口已迁移到dnspod',
|
||||||
|
input: {
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
component: {
|
||||||
|
placeholder: 'dnspod接口账户id',
|
||||||
|
rules: [{ required: true, message: '该项必填' }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
token: {
|
||||||
|
type: String,
|
||||||
|
label: 'token',
|
||||||
|
component: {
|
||||||
|
placeholder: '开放接口token',
|
||||||
|
rules: [{ required: true, message: '该项必填' }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
export class TencentAccessProvider {
|
||||||
|
static define () {
|
||||||
|
return {
|
||||||
|
name: 'tencent',
|
||||||
|
label: '腾讯云',
|
||||||
|
input: {
|
||||||
|
secretId: {
|
||||||
|
type: String,
|
||||||
|
label: 'secretId',
|
||||||
|
component: {
|
||||||
|
placeholder: 'secretId',
|
||||||
|
rules: [{ required: true, message: '该项必填' }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
secretKey: {
|
||||||
|
type: String,
|
||||||
|
label: 'secretKey',
|
||||||
|
component: {
|
||||||
|
placeholder: 'secretKey',
|
||||||
|
rules: [{ required: true, message: '该项必填' }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,19 +2,34 @@ import { AbstractDnsProvider, util } from '@certd/api'
|
|||||||
import _ from 'lodash-es'
|
import _ from 'lodash-es'
|
||||||
const request = util.request
|
const request = util.request
|
||||||
export class DnspodDnsProvider extends AbstractDnsProvider {
|
export class DnspodDnsProvider extends AbstractDnsProvider {
|
||||||
static name () {
|
static define () {
|
||||||
return 'dnspod'
|
return {
|
||||||
}
|
name: 'dnspod',
|
||||||
|
label: 'dnspod(腾讯云)',
|
||||||
constructor (dnsProviderConfig) {
|
desc: '腾讯云的域名解析接口已迁移到dnspod',
|
||||||
super()
|
input: {
|
||||||
if (!dnsProviderConfig.id || !dnsProviderConfig.token) {
|
accessProvider: {
|
||||||
throw new Error('请正确配置dnspod的 id 和 token')
|
label: '授权',
|
||||||
|
type: [String, Object],
|
||||||
|
desc: '需要dnspod类型的授权',
|
||||||
|
component: {
|
||||||
|
name: 'access-provider-selector',
|
||||||
|
filter: 'dnspod'
|
||||||
|
},
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.loginToken = dnsProviderConfig.id + ',' + dnsProviderConfig.token
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async doRequest (options) {
|
constructor (args) {
|
||||||
|
super(args)
|
||||||
|
const { props } = args
|
||||||
|
const accessProvider = this.getAccessProvider(props.accessProvider)
|
||||||
|
this.loginToken = accessProvider.id + ',' + accessProvider.token
|
||||||
|
}
|
||||||
|
|
||||||
|
async doRequest (options, successCodes = []) {
|
||||||
const config = {
|
const config = {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
formData: {
|
formData: {
|
||||||
@@ -28,8 +43,11 @@ export class DnspodDnsProvider extends AbstractDnsProvider {
|
|||||||
_.merge(config, options)
|
_.merge(config, options)
|
||||||
|
|
||||||
const ret = await request(config)
|
const ret = await request(config)
|
||||||
if (!ret || !ret.status || ret.status.code !== '1') {
|
if (!ret || !ret.status) {
|
||||||
throw new Error('请求失败:' + ret.status.message + ',api=' + config.url)
|
const code = ret.status.code
|
||||||
|
if (code !== '1' || !successCodes.includes(code)) {
|
||||||
|
throw new Error('请求失败:' + ret.status.message + ',api=' + config.url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@@ -58,7 +76,7 @@ export class DnspodDnsProvider extends AbstractDnsProvider {
|
|||||||
value: value,
|
value: value,
|
||||||
mx: 1
|
mx: 1
|
||||||
}
|
}
|
||||||
})
|
}, ['104'])// 104错误码为记录已存在,无需再次添加
|
||||||
this.logger.info('添加域名解析成功:', fullRecord, value, JSON.stringify(ret.record))
|
this.logger.info('添加域名解析成功:', fullRecord, value, JSON.stringify(ret.record))
|
||||||
return ret.record
|
return ret.record
|
||||||
}
|
}
|
||||||
34
packages/plugins/plugin-tencent/src/index.js
Normal file
34
packages/plugins/plugin-tencent/src/index.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import _ from 'lodash-es'
|
||||||
|
|
||||||
|
import { TencentAccessProvider } from './access-providers/tencent.js'
|
||||||
|
import { DnspodAccessProvider } from './access-providers/dnspod.js'
|
||||||
|
import { DnspodDnsProvider } from './dns-providers/dnspod.js'
|
||||||
|
|
||||||
|
import { UploadCertToTencent } from './plugins/upload-to-tencent/index.js'
|
||||||
|
|
||||||
|
import { DeployCertToTencentCDN } from './plugins/deploy-to-cdn/index.js'
|
||||||
|
|
||||||
|
import { DeployCertToTencentCLB } from './plugins/deploy-to-clb/index.js'
|
||||||
|
|
||||||
|
import { DeployCertToTencentTKEIngress } from './plugins/deploy-to-tke-ingress/index.js'
|
||||||
|
|
||||||
|
import { pluginRegistry, accessProviderRegistry, dnsProviderRegistry } from '@certd/api'
|
||||||
|
|
||||||
|
export const DefaultPlugins = {
|
||||||
|
UploadCertToTencent,
|
||||||
|
DeployCertToTencentTKEIngress,
|
||||||
|
DeployCertToTencentCDN,
|
||||||
|
DeployCertToTencentCLB
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
install () {
|
||||||
|
_.forEach(DefaultPlugins, item => {
|
||||||
|
pluginRegistry.install(item)
|
||||||
|
})
|
||||||
|
|
||||||
|
accessProviderRegistry.install(TencentAccessProvider)
|
||||||
|
accessProviderRegistry.install(DnspodAccessProvider)
|
||||||
|
|
||||||
|
dnsProviderRegistry.install(DnspodDnsProvider)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AbstractTencentPlugin } from '../../tencent/abstract-tencent.js'
|
import { AbstractTencentPlugin } from '../abstract-tencent.js'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import tencentcloud from 'tencentcloud-sdk-nodejs'
|
import tencentcloud from 'tencentcloud-sdk-nodejs'
|
||||||
|
|
||||||
@@ -19,35 +19,35 @@ export class DeployCertToTencentCDN extends AbstractTencentPlugin {
|
|||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
certName: {
|
certName: {
|
||||||
label: '证书名称'
|
label: '证书名称',
|
||||||
|
desc: '证书上传后将以此参数作为名称前缀'
|
||||||
},
|
},
|
||||||
certType: {
|
certType: {
|
||||||
value: 'upload',
|
default: 'upload',
|
||||||
label: '证书来源',
|
label: '证书来源',
|
||||||
options: [
|
options: [
|
||||||
{ value: 'upload', label: '直接上传' },
|
{ value: 'upload', label: '直接上传' },
|
||||||
{ value: 'cloud', label: '从证书库', desc: '需要uploadCertToTencent作为前置任务' }
|
{ value: 'cloud', label: '从证书库', desc: '需要uploadCertToTencent作为前置任务' }
|
||||||
],
|
],
|
||||||
|
desc: '如果选择‘从证书库’类型,则需要以《上传证书到腾讯云》作为前置任务',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
// serverCertificateStatus: {
|
|
||||||
// label: '启用https',
|
|
||||||
// options: [
|
|
||||||
// { value: 'on', label: '开启HTTPS,并更新证书' },
|
|
||||||
// { value: 'auto', label: '若HTTPS开启则更新,未开启不更新' }
|
|
||||||
// ],
|
|
||||||
// required:true
|
|
||||||
// },
|
|
||||||
accessProvider: {
|
accessProvider: {
|
||||||
label: 'Access提供者',
|
label: 'Access提供者',
|
||||||
type: [String, Object],
|
type: [String, Object],
|
||||||
desc: 'AccessProviders的key 或 一个包含accessKeyId与accessKeySecret的对象',
|
desc: 'access 授权',
|
||||||
options: 'accessProviders[type=aliyun]',
|
component: {
|
||||||
|
name: 'access-provider-selector',
|
||||||
|
filter: 'tencent'
|
||||||
|
},
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
|
tencentCertId: {
|
||||||
|
type: String,
|
||||||
|
desc: '证书来源选择上传时,将返回此id'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AbstractTencentPlugin } from '../../tencent/abstract-tencent.js'
|
import { AbstractTencentPlugin } from '../abstract-tencent.js'
|
||||||
import tencentcloud from 'tencentcloud-sdk-nodejs'
|
import tencentcloud from 'tencentcloud-sdk-nodejs'
|
||||||
export class DeployCertToTencentCLB extends AbstractTencentPlugin {
|
export class DeployCertToTencentCLB extends AbstractTencentPlugin {
|
||||||
/**
|
/**
|
||||||
@@ -15,16 +15,19 @@ export class DeployCertToTencentCLB extends AbstractTencentPlugin {
|
|||||||
input: {
|
input: {
|
||||||
region: {
|
region: {
|
||||||
label: '大区',
|
label: '大区',
|
||||||
value: 'ap-guangzhou'
|
default: 'ap-guangzhou',
|
||||||
|
required: true
|
||||||
},
|
},
|
||||||
domain: {
|
domain: {
|
||||||
label: '域名',
|
label: '域名',
|
||||||
type: [String, Array],
|
type: [String, Array],
|
||||||
|
required: true,
|
||||||
desc: '要更新的支持https的负载均衡的域名'
|
desc: '要更新的支持https的负载均衡的域名'
|
||||||
},
|
},
|
||||||
loadBalancerId: {
|
loadBalancerId: {
|
||||||
label: '负载均衡ID',
|
label: '负载均衡ID',
|
||||||
desc: '如果没有配置,则根据域名匹配负载均衡下的监听器(根据域名匹配时暂时只支持前100个)'
|
desc: '如果没有配置,则根据域名匹配负载均衡下的监听器(根据域名匹配时暂时只支持前100个)',
|
||||||
|
required: true
|
||||||
},
|
},
|
||||||
listenerId: {
|
listenerId: {
|
||||||
label: '监听器ID',
|
label: '监听器ID',
|
||||||
@@ -37,8 +40,11 @@ export class DeployCertToTencentCLB extends AbstractTencentPlugin {
|
|||||||
accessProvider: {
|
accessProvider: {
|
||||||
label: 'Access提供者',
|
label: 'Access提供者',
|
||||||
type: [String, Object],
|
type: [String, Object],
|
||||||
desc: 'AccessProviders的key 或 一个包含accessKeyId与accessKeySecret的对象',
|
desc: 'access授权',
|
||||||
options: 'accessProviders[type=tencent]',
|
component: {
|
||||||
|
name: 'access-provider-selector',
|
||||||
|
filter: 'tencent'
|
||||||
|
},
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { AbstractTencentPlugin } from '../../tencent/abstract-tencent.js'
|
import { AbstractTencentPlugin } from '../abstract-tencent.js'
|
||||||
import tencentcloud from 'tencentcloud-sdk-nodejs'
|
import tencentcloud from 'tencentcloud-sdk-nodejs'
|
||||||
import { K8sClient } from '../../utils/util.k8s.client.js'
|
import { K8sClient } from '@certd/plugin-common'
|
||||||
export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
|
export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
|
||||||
/**
|
/**
|
||||||
* 插件定义
|
* 插件定义
|
||||||
@@ -16,27 +16,36 @@ export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
|
|||||||
input: {
|
input: {
|
||||||
region: {
|
region: {
|
||||||
label: '大区',
|
label: '大区',
|
||||||
value: 'ap-guangzhou'
|
default: 'ap-guangzhou',
|
||||||
|
required: true
|
||||||
},
|
},
|
||||||
clusterId: {
|
clusterId: {
|
||||||
label: '集群ID',
|
label: '集群ID',
|
||||||
required: true,
|
required: true,
|
||||||
desc: '例如:cls-6lbj1vee'
|
desc: '例如:cls-6lbj1vee',
|
||||||
|
request: true
|
||||||
},
|
},
|
||||||
namespace: {
|
namespace: {
|
||||||
label: '集群的namespace',
|
label: '集群namespace',
|
||||||
value: 'default'
|
default: 'default',
|
||||||
|
required: true
|
||||||
},
|
},
|
||||||
secreteName: {
|
secreteName: {
|
||||||
type: [String, Array],
|
type: [String, Array],
|
||||||
label: '证书的secret名称',
|
label: '证书的secret名称',
|
||||||
desc: '支持多个(传入数组)'
|
desc: '支持多个(传入数组)',
|
||||||
|
required: true
|
||||||
},
|
},
|
||||||
ingressName: {
|
ingressName: {
|
||||||
type: [String, Array],
|
type: [String, Array],
|
||||||
label: 'ingress名称',
|
label: 'ingress名称',
|
||||||
desc: '支持多个(传入数组)'
|
desc: '支持多个(传入数组)'
|
||||||
},
|
},
|
||||||
|
ingressClass: {
|
||||||
|
type: String,
|
||||||
|
label: 'ingress类型',
|
||||||
|
desc: '可选 qcloud / nginx'
|
||||||
|
},
|
||||||
clusterIp: {
|
clusterIp: {
|
||||||
type: String,
|
type: String,
|
||||||
label: '集群内网ip',
|
label: '集群内网ip',
|
||||||
@@ -44,20 +53,19 @@ export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
|
|||||||
},
|
},
|
||||||
clusterDomain: {
|
clusterDomain: {
|
||||||
type: String,
|
type: String,
|
||||||
label: '集群域名,可不填,默认为:[clusterId].ccs.tencent-cloud.com'
|
label: '集群域名',
|
||||||
|
desc: '可不填,默认为:[clusterId].ccs.tencent-cloud.com'
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* AccessProvider的key,或者一个包含access的具体的对象
|
* AccessProvider的key,或者一个包含access的具体的对象
|
||||||
*/
|
*/
|
||||||
accessProvider: {
|
accessProvider: {
|
||||||
label: 'Access提供者',
|
label: 'Access授权',
|
||||||
type: [String, Object],
|
type: [String, Object],
|
||||||
desc: '请选择access提供者',
|
desc: 'access授权',
|
||||||
component: {
|
component: {
|
||||||
name: 'accessProviderSelect',
|
name: 'access-provider-selector',
|
||||||
props: {
|
filter: 'tencent'
|
||||||
filterType: 'tencent'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
@@ -83,7 +91,13 @@ export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
|
|||||||
// 修改内网解析ip地址
|
// 修改内网解析ip地址
|
||||||
k8sClient.setLookup({ [clusterDomain]: { ip: props.clusterIp } })
|
k8sClient.setLookup({ [clusterDomain]: { ip: props.clusterIp } })
|
||||||
}
|
}
|
||||||
await this.patchCertSecret({ k8sClient, props, context })
|
const ingressType = props.ingressClass || 'qcloud'
|
||||||
|
if (ingressType === 'qcloud') {
|
||||||
|
await this.patchQcloudCertSecret({ k8sClient, props, context })
|
||||||
|
} else {
|
||||||
|
await this.patchNginxCertSecret({ cert, k8sClient, props, context })
|
||||||
|
}
|
||||||
|
|
||||||
await this.sleep(2000) // 停留2秒,等待secret部署完成
|
await this.sleep(2000) // 停留2秒,等待secret部署完成
|
||||||
await this.restartIngress({ k8sClient, props })
|
await this.restartIngress({ k8sClient, props })
|
||||||
return true
|
return true
|
||||||
@@ -118,7 +132,7 @@ export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
|
|||||||
return ret.Kubeconfig
|
return ret.Kubeconfig
|
||||||
}
|
}
|
||||||
|
|
||||||
async patchCertSecret ({ k8sClient, props, context }) {
|
async patchQcloudCertSecret ({ k8sClient, props, context }) {
|
||||||
const { tencentCertId } = context
|
const { tencentCertId } = context
|
||||||
if (tencentCertId == null) {
|
if (tencentCertId == null) {
|
||||||
throw new Error('请先将【上传证书到腾讯云】作为前置任务')
|
throw new Error('请先将【上传证书到腾讯云】作为前置任务')
|
||||||
@@ -148,6 +162,35 @@ export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async patchNginxCertSecret ({ cert, k8sClient, props, context }) {
|
||||||
|
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 } = props
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
data: {
|
||||||
|
'tls.crt': crtBase64,
|
||||||
|
'tls.key': keyBase64
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
labels: {
|
||||||
|
certd: this.appendTimeSuffix('certd')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let secretNames = secretName
|
||||||
|
if (typeof secretName === 'string') {
|
||||||
|
secretNames = [secretName]
|
||||||
|
}
|
||||||
|
for (const secret of secretNames) {
|
||||||
|
await k8sClient.patchSecret({ namespace, secretName: secret, body })
|
||||||
|
this.logger.info(`CertSecret已更新:${secret}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async restartIngress ({ k8sClient, props }) {
|
async restartIngress ({ k8sClient, props }) {
|
||||||
const { namespace, ingressName } = props
|
const { namespace, ingressName } = props
|
||||||
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import dayjs from 'dayjs'
|
|
||||||
import tencentcloud from 'tencentcloud-sdk-nodejs'
|
import tencentcloud from 'tencentcloud-sdk-nodejs'
|
||||||
import { AbstractTencentPlugin } from '../abstract-tencent.js'
|
import { AbstractTencentPlugin } from '../abstract-tencent.js'
|
||||||
|
|
||||||
@@ -18,10 +17,14 @@ export class UploadCertToTencent extends AbstractTencentPlugin {
|
|||||||
label: '证书名称'
|
label: '证书名称'
|
||||||
},
|
},
|
||||||
accessProvider: {
|
accessProvider: {
|
||||||
label: 'Access提供者',
|
label: 'Access授权',
|
||||||
type: [String, Object],
|
type: [String, Object],
|
||||||
desc: 'AccessProviders的key 或 一个包含accessKeyId与accessKeySecret的对象',
|
desc: 'access授权',
|
||||||
options: 'accessProviders[type=tencent]'
|
component: {
|
||||||
|
name: 'access-provider-selector',
|
||||||
|
filter: 'tencent'
|
||||||
|
},
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
@@ -1,13 +1,20 @@
|
|||||||
import pkg from 'chai'
|
import pkg from 'chai'
|
||||||
import { Certd } from '../../src/index.js'
|
import PluginTencent from '../../src/index.js'
|
||||||
import { createOptions } from '../../../../test/options.js'
|
import { createOptions } from '../../../../../test/options.js'
|
||||||
|
import { Certd } from '@certd/certd'
|
||||||
const { expect } = pkg
|
const { expect } = pkg
|
||||||
|
|
||||||
|
// 安装默认插件和授权提供者
|
||||||
|
PluginTencent.install()
|
||||||
describe('DnspodDnsProvider', function () {
|
describe('DnspodDnsProvider', function () {
|
||||||
it('#申请证书', async function () {
|
it('#申请证书', async function () {
|
||||||
this.timeout(300000)
|
this.timeout(300000)
|
||||||
const options = createOptions()
|
const options = createOptions()
|
||||||
options.cert.domains = ['*.certd.xyz', '*.test.certd.xyz', '*.base.certd.xyz', 'certd.xyz']
|
options.cert.domains = ['*.certd.xyz', '*.test.certd.xyz', '*.base.certd.xyz', 'certd.xyz']
|
||||||
options.cert.dnsProvider = 'dnspod'
|
options.cert.dnsProvider = {
|
||||||
|
type: 'dnspod',
|
||||||
|
accessProvider: 'dnspod'
|
||||||
|
}
|
||||||
options.args = { forceCert: true }
|
options.args = { forceCert: true }
|
||||||
const certd = new Certd(options)
|
const certd = new Certd(options)
|
||||||
const cert = await certd.certApply()
|
const cert = await certd.certApply()
|
||||||
@@ -1,20 +1,31 @@
|
|||||||
import pkg from 'chai'
|
import pkg from 'chai'
|
||||||
import DnspodDnsProvider from '../../src/dns-provider/dnspod.js'
|
import { DnspodDnsProvider } from '../../src/dns-providers/dnspod.js'
|
||||||
import { Certd } from '../../src/index.js'
|
import { createOptions, getDnsProviderOptions } from '../../../../../test/options.js'
|
||||||
import { createOptions } from '../../../../test/options.js'
|
|
||||||
const { expect } = pkg
|
const { expect } = pkg
|
||||||
describe('DnspodDnsProvider', function () {
|
describe('DnspodDnsProvider', function () {
|
||||||
it('#getDomainList', async function () {
|
it('#getDomainList', async function () {
|
||||||
const options = createOptions()
|
let options = createOptions()
|
||||||
const dnsProvider = new DnspodDnsProvider(options.accessProviders.dnspod)
|
options.cert.dnsProvider = {
|
||||||
|
type: 'dnspod',
|
||||||
|
accessProvider: 'dnspod'
|
||||||
|
}
|
||||||
|
options = getDnsProviderOptions(options)
|
||||||
|
|
||||||
|
const dnsProvider = new DnspodDnsProvider(options)
|
||||||
const domainList = await dnsProvider.getDomainList()
|
const domainList = await dnsProvider.getDomainList()
|
||||||
console.log('domainList', domainList)
|
console.log('domainList', domainList)
|
||||||
expect(domainList.length).gt(0)
|
expect(domainList.length).gt(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('#createRecord&removeRecord', async function () {
|
it('#createRecord&removeRecord', async function () {
|
||||||
const options = createOptions()
|
let options = createOptions()
|
||||||
const dnsProvider = new DnspodDnsProvider(options.accessProviders.dnspod)
|
options.cert.dnsProvider = {
|
||||||
|
type: 'dnspod',
|
||||||
|
accessProvider: 'dnspod'
|
||||||
|
}
|
||||||
|
options = getDnsProviderOptions(options)
|
||||||
|
|
||||||
|
const dnsProvider = new DnspodDnsProvider(options)
|
||||||
const record = await dnsProvider.createRecord({ fullRecord: '___certd___.__test__.certd.xyz', type: 'TXT', value: 'aaaa' })
|
const record = await dnsProvider.createRecord({ fullRecord: '___certd___.__test__.certd.xyz', type: 'TXT', value: 'aaaa' })
|
||||||
console.log('recordId', record.id)
|
console.log('recordId', record.id)
|
||||||
expect(record.id != null).ok
|
expect(record.id != null).ok
|
||||||
@@ -1,35 +1,33 @@
|
|||||||
import pkg from 'chai'
|
import pkg from 'chai'
|
||||||
import { DeployCertToTencentCDN } from '../../src/tencent/deploy-to-cdn/index.js'
|
import { DeployCertToTencentCDN } from '../../src/plugins/deploy-to-cdn/index.js'
|
||||||
import { Certd } from '@certd/certd'
|
import { Certd } from '@certd/certd'
|
||||||
import { UploadCertToTencent } from '../../src/tencent/upload-to-tencent/index.js'
|
import { UploadCertToTencent } from '../../src/plugins/upload-to-tencent/index.js'
|
||||||
import { createOptions } from '../../../../test/options.js'
|
import { createOptions } from '../../../../../test/options.js'
|
||||||
const { expect } = pkg
|
const { expect } = pkg
|
||||||
describe('DeployToTencentCDN', function () {
|
describe('DeployToTencentCDN', function () {
|
||||||
it('#execute-from-store', async function () {
|
it('#execute-from-store', async function () {
|
||||||
const options = createOptions()
|
const options = createOptions()
|
||||||
options.args.test = false
|
options.args.test = false
|
||||||
const certd = new Certd(options)
|
const certd = new Certd(options)
|
||||||
const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn'])
|
const cert = await certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn'])
|
||||||
const context = {}
|
const context = {}
|
||||||
const uploadPlugin = new UploadCertToTencent()
|
const uploadPlugin = new UploadCertToTencent(options)
|
||||||
const uploadOptions = {
|
const uploadOptions = {
|
||||||
accessProviders: options.accessProviders,
|
|
||||||
cert,
|
cert,
|
||||||
props: { name: 'certd部署测试', accessProvider: 'tencent' },
|
props: { name: 'certd部署测试', accessProvider: 'tencent' },
|
||||||
context
|
context
|
||||||
}
|
}
|
||||||
await uploadPlugin.doExecute(uploadOptions)
|
await uploadPlugin.doExecute(uploadOptions)
|
||||||
|
|
||||||
const deployPlugin = new DeployCertToTencentCDN()
|
const deployPlugin = new DeployCertToTencentCDN(options)
|
||||||
const deployOpts = {
|
const deployOpts = {
|
||||||
accessProviders: options.accessProviders,
|
|
||||||
cert,
|
cert,
|
||||||
props: { domainName: 'tentcent-certd.docmirror.cn', certName: 'certd部署测试', accessProvider: 'tencent' },
|
props: { domainName: 'tentcent-certd.docmirror.cn', certName: 'certd部署测试', accessProvider: 'tencent' },
|
||||||
context
|
context
|
||||||
}
|
}
|
||||||
const ret = await deployPlugin.doExecute(deployOpts)
|
await deployPlugin.doExecute(deployOpts)
|
||||||
expect(ret).ok
|
|
||||||
console.log('context:', context)
|
console.log('context:', context)
|
||||||
|
expect(context.tencentCertId).ok
|
||||||
|
|
||||||
await uploadPlugin.doRollback(uploadOptions)
|
await uploadPlugin.doRollback(uploadOptions)
|
||||||
})
|
})
|
||||||
@@ -38,17 +36,17 @@ describe('DeployToTencentCDN', function () {
|
|||||||
options.args.test = false
|
options.args.test = false
|
||||||
options.cert.email = 'xiaojunnuo@qq.com'
|
options.cert.email = 'xiaojunnuo@qq.com'
|
||||||
options.cert.domains = ['*.docmirror.cn']
|
options.cert.domains = ['*.docmirror.cn']
|
||||||
const plugin = new DeployCertToTencentCDN()
|
const plugin = new DeployCertToTencentCDN(options)
|
||||||
const certd = new Certd(options)
|
const certd = new Certd(options)
|
||||||
const cert = await certd.readCurrentCert()
|
const cert = await certd.readCurrentCert()
|
||||||
const context = {}
|
const context = {}
|
||||||
const deployOpts = {
|
const deployOpts = {
|
||||||
accessProviders: options.accessProviders,
|
|
||||||
cert,
|
cert,
|
||||||
props: { domainName: 'tentcent-certd.docmirror.cn', accessProvider: 'tencent' },
|
props: { domainName: 'tentcent-certd.docmirror.cn', accessProvider: 'tencent' },
|
||||||
context
|
context
|
||||||
}
|
}
|
||||||
const ret = await plugin.doExecute(deployOpts)
|
const ret = await plugin.doExecute(deployOpts)
|
||||||
console.log('context:', context, ret)
|
console.log('context:', context, ret)
|
||||||
|
expect(context).be.empty
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1,23 +1,22 @@
|
|||||||
import pkg from 'chai'
|
import pkg from 'chai'
|
||||||
import { DeployCertToTencentCLB } from '../../src/tencent/deploy-to-clb/index.js'
|
import { DeployCertToTencentCLB } from '../../src/plugins/deploy-to-clb/index.js'
|
||||||
import { Certd } from '@certd/certd'
|
import { Certd } from '@certd/certd'
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
import { createOptions } from '../../../../test/options.js'
|
import { createOptions } from '../../../../../test/options.js'
|
||||||
import { UploadCertToTencent } from '../../src/tencent/upload-to-tencent/index.js'
|
import { UploadCertToTencent } from '../../src/plugins/upload-to-tencent/index.js'
|
||||||
const { expect } = pkg
|
const { expect } = pkg
|
||||||
describe('DeployToTencentCLB', function () {
|
describe('DeployToTencentCLB', function () {
|
||||||
it('#execute-getClbList', async function () {
|
it('#execute-getClbList', async function () {
|
||||||
const options = createOptions()
|
const options = createOptions()
|
||||||
options.args.test = false
|
options.args.test = false
|
||||||
options.cert.dnsProvider = 'tencent-yonsz'
|
options.cert.dnsProvider = 'tencent-yonsz'
|
||||||
const deployPlugin = new DeployCertToTencentCLB()
|
const deployPlugin = new DeployCertToTencentCLB(options)
|
||||||
const props = {
|
const props = {
|
||||||
region: 'ap-guangzhou',
|
region: 'ap-guangzhou',
|
||||||
domain: 'certd-test-no-sni.base.yonsz.net',
|
domain: 'certd-test-no-sni.base.yonsz.net',
|
||||||
accessProvider: 'tencent-yonsz'
|
accessProvider: 'tencent-yonsz'
|
||||||
}
|
}
|
||||||
const accessProviders = options.accessProviders
|
const accessProvider = deployPlugin.getAccessProvider(props.accessProvider)
|
||||||
const accessProvider = deployPlugin.getAccessProvider(props.accessProvider, accessProviders)
|
|
||||||
const { region } = props
|
const { region } = props
|
||||||
const client = deployPlugin.getClient(accessProvider, region)
|
const client = deployPlugin.getClient(accessProvider, region)
|
||||||
|
|
||||||
@@ -34,13 +33,14 @@ describe('DeployToTencentCLB', function () {
|
|||||||
region: 'ap-guangzhou',
|
region: 'ap-guangzhou',
|
||||||
domain: 'certd-test-no-sni.base.yonsz.net',
|
domain: 'certd-test-no-sni.base.yonsz.net',
|
||||||
accessProvider: 'tencent-yonsz',
|
accessProvider: 'tencent-yonsz',
|
||||||
loadBalancerId: 'lb-59yhe5xo'
|
loadBalancerId: 'lb-59yhe5xo',
|
||||||
|
listenerId: 'lbl-1vfwx8dq'
|
||||||
}
|
}
|
||||||
const accessProvider = deployPlugin.getAccessProvider(props.accessProvider)
|
const accessProvider = deployPlugin.getAccessProvider(props.accessProvider)
|
||||||
const { region } = props
|
const { region } = props
|
||||||
const client = deployPlugin.getClient(accessProvider, region)
|
const client = deployPlugin.getClient(accessProvider, region)
|
||||||
|
|
||||||
const ret = await deployPlugin.getListenerList(client, props.loadBalancerId, props)
|
const ret = await deployPlugin.getListenerList(client, props.loadBalancerId, [props.listenerId])
|
||||||
expect(ret.length > 0).ok
|
expect(ret.length > 0).ok
|
||||||
console.log('clb count:', ret.length, ret)
|
console.log('clb count:', ret.length, ret)
|
||||||
})
|
})
|
||||||
@@ -54,10 +54,9 @@ describe('DeployToTencentCLB', function () {
|
|||||||
options.cert.domains = ['*.docmirror.cn']
|
options.cert.domains = ['*.docmirror.cn']
|
||||||
const certd = new Certd(options)
|
const certd = new Certd(options)
|
||||||
const cert = await certd.readCurrentCert()
|
const cert = await certd.readCurrentCert()
|
||||||
const deployPlugin = new DeployCertToTencentCLB()
|
const deployPlugin = new DeployCertToTencentCLB(options)
|
||||||
const context = {}
|
const context = {}
|
||||||
const deployOpts = {
|
const deployOpts = {
|
||||||
accessProviders: options.accessProviders,
|
|
||||||
cert,
|
cert,
|
||||||
props: {
|
props: {
|
||||||
region: 'ap-guangzhou',
|
region: 'ap-guangzhou',
|
||||||
@@ -72,7 +71,7 @@ describe('DeployToTencentCLB', function () {
|
|||||||
console.log('ret:', ret)
|
console.log('ret:', ret)
|
||||||
|
|
||||||
// 删除测试证书
|
// 删除测试证书
|
||||||
const uploadPlugin = new UploadCertToTencent()
|
const uploadPlugin = new UploadCertToTencent(options)
|
||||||
await uploadPlugin.doRollback(deployOpts)
|
await uploadPlugin.doRollback(deployOpts)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -83,10 +82,9 @@ describe('DeployToTencentCLB', function () {
|
|||||||
options.cert.dnsProvider = 'tencent-yonsz'
|
options.cert.dnsProvider = 'tencent-yonsz'
|
||||||
const certd = new Certd(options)
|
const certd = new Certd(options)
|
||||||
const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn'])
|
const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn'])
|
||||||
const deployPlugin = new DeployCertToTencentCLB()
|
const deployPlugin = new DeployCertToTencentCLB(options)
|
||||||
const context = {}
|
const context = {}
|
||||||
const deployOpts = {
|
const deployOpts = {
|
||||||
accessProviders: options.accessProviders,
|
|
||||||
cert,
|
cert,
|
||||||
props: {
|
props: {
|
||||||
region: 'ap-guangzhou',
|
region: 'ap-guangzhou',
|
||||||
@@ -98,10 +96,10 @@ describe('DeployToTencentCLB', function () {
|
|||||||
context
|
context
|
||||||
}
|
}
|
||||||
const ret = await deployPlugin.doExecute(deployOpts)
|
const ret = await deployPlugin.doExecute(deployOpts)
|
||||||
expect(ret).ok
|
|
||||||
console.log('ret:', ret)
|
console.log('ret:', ret)
|
||||||
|
expect(ret).ok
|
||||||
// 删除测试证书
|
// 删除测试证书
|
||||||
const uploadPlugin = new UploadCertToTencent()
|
const uploadPlugin = new UploadCertToTencent(options)
|
||||||
await uploadPlugin.doRollback(deployOpts)
|
await uploadPlugin.doRollback(deployOpts)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import pkg from 'chai'
|
||||||
|
import { DeployCertToTencentTKEIngress } from '../../src/plugins/deploy-to-tke-ingress/index.js'
|
||||||
|
import { Certd } from '@certd/certd'
|
||||||
|
import { createOptions } from '../../../../../test/options.js'
|
||||||
|
import { K8sClient } from '../../src/utils/util.k8s.client.js'
|
||||||
|
|
||||||
|
const { expect } = pkg
|
||||||
|
|
||||||
|
async function getOptions () {
|
||||||
|
const options = createOptions()
|
||||||
|
options.args.test = false
|
||||||
|
options.cert.email = 'xiaojunnuo@qq.com'
|
||||||
|
options.cert.domains = ['*.docmirror.cn']
|
||||||
|
const certd = new Certd(options)
|
||||||
|
const cert = await certd.readCurrentCert()
|
||||||
|
const context = {}
|
||||||
|
const deployOpts = {
|
||||||
|
accessProviders: options.accessProviders,
|
||||||
|
cert,
|
||||||
|
props: {
|
||||||
|
accessProvider: 'tencent-yonsz',
|
||||||
|
region: 'ap-guangzhou',
|
||||||
|
clusterId: 'cls-6lbj1vee'
|
||||||
|
},
|
||||||
|
context
|
||||||
|
}
|
||||||
|
return { options, deployOpts }
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DeployCertToTencentTKEIngressNginx', function () {
|
||||||
|
it('#getTKESecrets', async function () {
|
||||||
|
this.timeout(50000)
|
||||||
|
const { options, deployOpts } = await getOptions()
|
||||||
|
const plugin = new DeployCertToTencentTKEIngress(options)
|
||||||
|
const tkeClient = plugin.getTkeClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.region)
|
||||||
|
const kubeConfig = await plugin.getTkeKubeConfig(tkeClient, deployOpts.props.clusterId)
|
||||||
|
|
||||||
|
const k8sClient = new K8sClient(kubeConfig)
|
||||||
|
k8sClient.setLookup({
|
||||||
|
'cls-6lbj1vee.ccs.tencent-cloud.com': { ip: '13.123.123.123' }
|
||||||
|
})
|
||||||
|
const secrets = await k8sClient.getSecret({ namespace: 'stress' })
|
||||||
|
|
||||||
|
console.log('secrets:', secrets)
|
||||||
|
})
|
||||||
|
it('#execute', async function () {
|
||||||
|
this.timeout(5000)
|
||||||
|
|
||||||
|
const { options, deployOpts } = await getOptions()
|
||||||
|
deployOpts.props.ingressName = 'stress-ingress-nginx'
|
||||||
|
deployOpts.props.ingressClass = 'nginx'
|
||||||
|
deployOpts.props.secretName = 'stress-all'
|
||||||
|
deployOpts.props.namespace = 'stress'
|
||||||
|
const plugin = new DeployCertToTencentTKEIngress(options)
|
||||||
|
|
||||||
|
const ret = await plugin.doExecute(deployOpts)
|
||||||
|
console.log('sucess', ret)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import pkg from 'chai'
|
||||||
|
import { DeployCertToTencentTKEIngress } from '../../src/plugins/deploy-to-tke-ingress/index.js'
|
||||||
|
import { Certd } from '@certd/certd'
|
||||||
|
import { createOptions } from '../../../../../test/options.js'
|
||||||
|
import { K8sClient } from '../../src/utils/util.k8s.client.js'
|
||||||
|
|
||||||
|
const { expect } = pkg
|
||||||
|
|
||||||
|
async function getOptions () {
|
||||||
|
const options = createOptions()
|
||||||
|
options.args.test = false
|
||||||
|
options.cert.email = 'xiaojunnuo@qq.com'
|
||||||
|
options.cert.domains = ['*.docmirror.cn']
|
||||||
|
const certd = new Certd(options)
|
||||||
|
const cert = await certd.readCurrentCert()
|
||||||
|
const context = {}
|
||||||
|
const deployOpts = {
|
||||||
|
accessProviders: options.accessProviders,
|
||||||
|
cert,
|
||||||
|
props: {
|
||||||
|
accessProvider: 'tencent-yonsz',
|
||||||
|
region: 'ap-guangzhou',
|
||||||
|
clusterId: 'cls-6lbj1vee'
|
||||||
|
},
|
||||||
|
context
|
||||||
|
}
|
||||||
|
return { options, deployOpts }
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DeployCertToTencentTKEIngress', function () {
|
||||||
|
it('#getTKESecrets', async function () {
|
||||||
|
this.timeout(50000)
|
||||||
|
const { options, deployOpts } = await getOptions()
|
||||||
|
const plugin = new DeployCertToTencentTKEIngress(options)
|
||||||
|
const tkeClient = plugin.getTkeClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.region)
|
||||||
|
const kubeConfig = await plugin.getTkeKubeConfig(tkeClient, deployOpts.props.clusterId)
|
||||||
|
|
||||||
|
const k8sClient = new K8sClient(kubeConfig)
|
||||||
|
k8sClient.setLookup({
|
||||||
|
'cls-6lbj1vee.ccs.tencent-cloud.com': { ip: '13.123.123.123' }
|
||||||
|
})
|
||||||
|
const secrets = await k8sClient.getSecret({ namespace: 'default' })
|
||||||
|
|
||||||
|
console.log('secrets:', secrets)
|
||||||
|
})
|
||||||
|
it('#execute', async function () {
|
||||||
|
this.timeout(5000)
|
||||||
|
const { options, deployOpts } = await getOptions()
|
||||||
|
deployOpts.props.ingressName = 'ingress-base'
|
||||||
|
deployOpts.props.secretName = 'cert---docmirror-cn'
|
||||||
|
deployOpts.context.tencentCertId = 'hNUZJrZf'
|
||||||
|
const plugin = new DeployCertToTencentTKEIngress(options)
|
||||||
|
|
||||||
|
const ret = await plugin.doExecute(deployOpts)
|
||||||
|
console.log('sucess', ret)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import pkg from 'chai'
|
import pkg from 'chai'
|
||||||
import { UploadCertToTencent } from '../../src/tencent/upload-to-tencent/index.js'
|
import { UploadCertToTencent } from '../../src/plugins/upload-to-tencent/index.js'
|
||||||
import { Certd } from '@certd/certd'
|
import { Certd } from '@certd/certd'
|
||||||
import { createOptions } from '../../../../test/options.js'
|
import { createOptions } from '../../../../../test/options.js'
|
||||||
const { expect } = pkg
|
const { expect } = pkg
|
||||||
describe('PluginUploadToTencent', function () {
|
describe('PluginUploadToTencent', function () {
|
||||||
it('#execute', async function () {
|
it('#execute', async function () {
|
||||||
const options = createOptions()
|
const options = createOptions()
|
||||||
const plugin = new UploadCertToTencent()
|
const plugin = new UploadCertToTencent(options)
|
||||||
options.args = { test: false }
|
options.args = { test: false }
|
||||||
options.cert.email = 'xiaojunnuo@qq.com'
|
options.cert.email = 'xiaojunnuo@qq.com'
|
||||||
options.cert.domains = ['*.docmirror.cn']
|
options.cert.domains = ['*.docmirror.cn']
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user