mirror of https://github.com/certd/certd
feat: 上传证书到服务器,执行远程脚本
parent
67bff28255
commit
4cd7b02cb7
|
@ -26,14 +26,21 @@ export class CertStore {
|
||||||
return this.store.buildKey(this.certsRootPath, this.safetyDomain, dir)
|
return this.store.buildKey(this.certsRootPath, this.safetyDomain, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formatCert (pem) {
|
||||||
|
pem = pem.replace(/\r/g, '')
|
||||||
|
pem = pem.replace(/\n\n/g, '\n')
|
||||||
|
pem = pem.replace(/\n$/g, '')
|
||||||
|
return pem
|
||||||
|
}
|
||||||
|
|
||||||
async writeCert (cert) {
|
async writeCert (cert) {
|
||||||
const newDir = this.buildNewCertRootPath()
|
const newDir = this.buildNewCertRootPath()
|
||||||
|
|
||||||
const crtKey = this.buildKey(newDir, this.safetyDomain + '.crt')
|
const crtKey = this.buildKey(newDir, this.safetyDomain + '.crt')
|
||||||
const priKey = this.buildKey(newDir, this.safetyDomain + '.key')
|
const priKey = this.buildKey(newDir, this.safetyDomain + '.key')
|
||||||
const csrKey = this.buildKey(newDir, this.safetyDomain + '.csr')
|
const csrKey = this.buildKey(newDir, this.safetyDomain + '.csr')
|
||||||
await this.store.set(crtKey, cert.crt)
|
await this.store.set(crtKey, this.formatCert(cert.crt))
|
||||||
await this.store.set(priKey, cert.key)
|
await this.store.set(priKey, this.formatCert(cert.key))
|
||||||
await this.store.set(csrKey, cert.csr)
|
await this.store.set(csrKey, cert.csr)
|
||||||
|
|
||||||
await this.store.link(newDir, this.currentRootPath)
|
await this.store.link(newDir, this.currentRootPath)
|
||||||
|
@ -53,7 +60,12 @@ export class CertStore {
|
||||||
const csr = await this.store.get(csrKey)
|
const csr = await this.store.get(csrKey)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
crt, key, csr, certDir: this.store.getActualKey(dir)
|
crt: this.formatCert(crt),
|
||||||
|
key: this.formatCert(key),
|
||||||
|
csr,
|
||||||
|
crtPath: this.store.getActualKey(crtKey),
|
||||||
|
keyPath: this.store.getActualKey(priKey),
|
||||||
|
certDir: this.store.getActualKey(dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,14 @@ import DefaultPlugins from '@certd/plugins'
|
||||||
import logger from './util.log.js'
|
import logger from './util.log.js'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import { Trace } from './trace.js'
|
||||||
export class Executor {
|
export class Executor {
|
||||||
constructor (args = {}) {
|
constructor (args = {}) {
|
||||||
const { plugins } = args
|
const { plugins } = args
|
||||||
this.plugins = {}
|
this.plugins = {}
|
||||||
this.usePlugins(DefaultPlugins)
|
this.usePlugins(DefaultPlugins)
|
||||||
this.usePlugins(plugins)
|
this.usePlugins(plugins)
|
||||||
|
this.trace = new Trace()
|
||||||
}
|
}
|
||||||
|
|
||||||
use (plugin) {
|
use (plugin) {
|
||||||
|
@ -32,17 +34,18 @@ export class Executor {
|
||||||
|
|
||||||
async run (options, args) {
|
async run (options, args) {
|
||||||
try {
|
try {
|
||||||
return await this.doRun(options, args)
|
if (args != null) {
|
||||||
|
_.merge(options.args, args)
|
||||||
|
}
|
||||||
|
return await this.doRun(options)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('任务执行出错:', e)
|
logger.error('任务执行出错:', e)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async doRun (options, args) {
|
async doRun (options) {
|
||||||
if (args != null) {
|
// 申请证书
|
||||||
_.merge(options.args, args)
|
|
||||||
}
|
|
||||||
logger.info('任务开始')
|
logger.info('任务开始')
|
||||||
const certd = new Certd(options)
|
const certd = new Certd(options)
|
||||||
const cert = await this.runCertd(certd)
|
const cert = await this.runCertd(certd)
|
||||||
|
@ -50,28 +53,36 @@ export class Executor {
|
||||||
throw new Error('申请证书失败')
|
throw new Error('申请证书失败')
|
||||||
}
|
}
|
||||||
logger.info('证书保存路径:', cert.certDir)
|
logger.info('证书保存路径:', cert.certDir)
|
||||||
|
|
||||||
|
logger.info('----------------------')
|
||||||
if (!cert.isNew) {
|
if (!cert.isNew) {
|
||||||
// 如果没有更新
|
// 如果没有更新
|
||||||
if (!options.args.forceDeploy) {
|
if (!options.args?.forceDeploy && !options.args?.forceRedeploy) {
|
||||||
// 且不需要强制运行deploy
|
// 且不需要强制运行deploy
|
||||||
logger.info('证书无更新,无需重新部署')
|
logger.info('证书无更新,无需重新部署')
|
||||||
logger.info('任务完成')
|
logger.info('任务完成')
|
||||||
return { cert }
|
return { cert }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 读取上次执行进度
|
||||||
let context = {}
|
let context = {
|
||||||
|
certIsNew: !!cert.isNew
|
||||||
|
}
|
||||||
const contextJson = await certd.certStore.getCurrentFile('context.json')
|
const contextJson = await certd.certStore.getCurrentFile('context.json')
|
||||||
if (contextJson) {
|
if (contextJson) {
|
||||||
context = JSON.parse(contextJson)
|
context = JSON.parse(contextJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const trace = new Trace(context)
|
||||||
|
// 运行部署任务
|
||||||
try {
|
try {
|
||||||
await this.runDeploys({ options, cert, context })
|
await this.runDeploys({ options, cert, context, trace })
|
||||||
} finally {
|
} finally {
|
||||||
await certd.certStore.setCurrentFile('context.json', JSON.stringify(context))
|
await certd.certStore.setCurrentFile('context.json', JSON.stringify(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('任务完成')
|
logger.info('任务完成')
|
||||||
|
trace.print()
|
||||||
return {
|
return {
|
||||||
cert,
|
cert,
|
||||||
context
|
context
|
||||||
|
@ -79,54 +90,69 @@ export class Executor {
|
||||||
}
|
}
|
||||||
|
|
||||||
async runCertd (certd) {
|
async runCertd (certd) {
|
||||||
logger.info(`申请证书${JSON.stringify(certd.options.cert.domains)}开始`)
|
logger.info(`证书任务 ${JSON.stringify(certd.options.cert.domains)} 开始`)
|
||||||
const cert = await certd.certApply()
|
const cert = await certd.certApply()
|
||||||
logger.info(`申请证书${JSON.stringify(certd.options.cert.domains)}完成`)
|
logger.info(`证书任务 ${JSON.stringify(certd.options.cert.domains)} 完成`)
|
||||||
return cert
|
return cert
|
||||||
}
|
}
|
||||||
|
|
||||||
async runDeploys ({ options, cert, context }) {
|
async runDeploys ({ options, cert, context, trace }) {
|
||||||
if (cert == null) {
|
if (cert == null) {
|
||||||
const certd = new Certd(options)
|
const certd = new Certd(options)
|
||||||
cert = await certd.readCurrentCert()
|
cert = await certd.readCurrentCert()
|
||||||
}
|
}
|
||||||
|
logger.info('部署任务开始')
|
||||||
for (const deploy of options.deploy) {
|
for (const deploy of options.deploy) {
|
||||||
logger.info(`--部署任务【${deploy.deployName}】开始`)
|
const deployName = deploy.deployName
|
||||||
|
logger.info(`------------【${deployName}】-----------`)
|
||||||
if (deploy.disabled === true) {
|
if (deploy.disabled === true) {
|
||||||
logger.info('----此部署任务已被禁用,跳过')
|
logger.info('此流程已被禁用,跳过')
|
||||||
|
logger.info('')
|
||||||
|
trace.set({ deployName, value: { current: 'skip', status: 'disabled', remark: '流程禁用' } })
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for (const task of deploy.tasks) {
|
try {
|
||||||
await this.runTask({ options, cert, task, context })
|
for (const task of deploy.tasks) {
|
||||||
|
if (context[deployName] == null) {
|
||||||
|
context[deployName] = {}
|
||||||
|
}
|
||||||
|
const taskContext = context[deployName]
|
||||||
|
await this.runTask({ options, cert, task, context: taskContext, deploy, trace })
|
||||||
|
}
|
||||||
|
|
||||||
|
trace.set({ deployName, value: { status: 'success', remark: '执行成功' } })
|
||||||
|
} catch (e) {
|
||||||
|
trace.set({ deployName, value: { status: 'error', remark: '执行失败:' + e.message } })
|
||||||
|
logger.error('流程执行失败', e)
|
||||||
}
|
}
|
||||||
logger.info(`--部署任务【${deploy.deployName}】完成`)
|
|
||||||
|
logger.info('')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async runTask ({ options, task, cert, context }) {
|
async runTask ({ options, task, cert, context, deploy, trace }) {
|
||||||
const taskType = task.type
|
const taskType = task.type
|
||||||
const Plugin = this.plugins[taskType]
|
const Plugin = this.plugins[taskType]
|
||||||
|
const deployName = deploy.deployName
|
||||||
|
const taskName = task.taskName
|
||||||
if (Plugin == null) {
|
if (Plugin == null) {
|
||||||
throw new Error(`----插件:${taskType}还未安装`)
|
throw new Error(`插件:${taskType}还未安装`)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`----任务【${task.taskName}】开始执行`)
|
|
||||||
let instance = Plugin
|
let instance = Plugin
|
||||||
if (Plugin instanceof Function) {
|
if (Plugin instanceof Function) {
|
||||||
instance = new Plugin()
|
instance = new Plugin({ accessProviders: options.accessProviders })
|
||||||
}
|
}
|
||||||
if (context.progress && context.progress[task.taskName] && context.progress[task.taskName].success) {
|
const traceStatus = trace.get({ deployName: deploy.deployName, taskName: taskName })
|
||||||
logger.info(`----任务【${task.taskName}】已经执行完成,跳过此任务`)
|
if (traceStatus?.status === 'success' && !options?.args?.forceRedeploy) {
|
||||||
|
logger.info(`----【${taskName}】已经执行完成,跳过此任务`)
|
||||||
|
trace.set({ deployName, taskName, value: { current: 'skip', status: 'success', remark: '已执行成功过,本次跳过' } })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await instance.execute({ cert, accessProviders: options.accessProviders, props: task.props, context })
|
logger.info(`----【${taskName}】开始执行`)
|
||||||
if (context.progress == null) {
|
await instance.execute({ cert, props: task.props, context })
|
||||||
context.progress = {}
|
trace.set({ deployName, taskName, value: { current: 'success', status: 'success', remark: '执行成功', time: dayjs().format() } })
|
||||||
}
|
logger.info(`----任务【${taskName}】执行完成`)
|
||||||
context.progress[task.taskName] = {
|
logger.info('')
|
||||||
success: true,
|
|
||||||
time: dayjs().format()
|
|
||||||
}
|
|
||||||
logger.info(`----任务【${task.taskName}】执行完成`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
import logger from './util.log.js'
|
||||||
|
import _ from 'lodash'
|
||||||
|
export class Trace {
|
||||||
|
constructor (context) {
|
||||||
|
this.context = context
|
||||||
|
}
|
||||||
|
|
||||||
|
set ({ deployName, taskName, prop, value }) {
|
||||||
|
const key = this.buildTraceKey({ deployName, taskName, prop })
|
||||||
|
const oldValue = _.get(this.context, key) || {}
|
||||||
|
_.merge(oldValue, value)
|
||||||
|
_.set(this.context, key, oldValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
get ({ deployName, taskName, prop }) {
|
||||||
|
return _.get(this.context, this.buildTraceKey({ deployName, taskName, prop }))
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTraceKey ({ deployName, taskName, prop }) {
|
||||||
|
let key = '__trace__'
|
||||||
|
if (deployName) {
|
||||||
|
key += '.'
|
||||||
|
key += deployName.replace(/\./g, '_')
|
||||||
|
}
|
||||||
|
if (taskName) {
|
||||||
|
key += '.tasks.'
|
||||||
|
key += taskName.replace(/\./g, '_')
|
||||||
|
}
|
||||||
|
if (prop) {
|
||||||
|
key += '.' + prop
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
getStringLength (str) {
|
||||||
|
const enLength = str.replace(/[\u0391-\uFFE5]/g, '').length // 先把中文替换成两个字节的英文,再计算长度
|
||||||
|
return Math.floor((str.length - enLength) * 1.5) + enLength
|
||||||
|
}
|
||||||
|
|
||||||
|
print () {
|
||||||
|
const context = this.context
|
||||||
|
logger.info('---------------------------任务结果总览--------------------------')
|
||||||
|
if (!context.certIsNew) {
|
||||||
|
this.printTraceLine({ current: 'skip', remark: '还未到过期时间,跳过' }, '更新证书')
|
||||||
|
} else {
|
||||||
|
this.printTraceLine({ current: 'success', remark: '证书更新成功' }, '更新证书')
|
||||||
|
}
|
||||||
|
const trace = this.get({ })
|
||||||
|
// logger.info('trace', trace)
|
||||||
|
for (const deployName in trace) {
|
||||||
|
if (trace[deployName] == null) {
|
||||||
|
trace[deployName] = {}
|
||||||
|
}
|
||||||
|
const traceStatus = this.printTraceLine(trace[deployName], deployName)
|
||||||
|
|
||||||
|
const tasks = traceStatus.tasks
|
||||||
|
if (tasks) {
|
||||||
|
for (const taskName in tasks) {
|
||||||
|
if (tasks[taskName] == null) {
|
||||||
|
tasks[taskName] = {}
|
||||||
|
}
|
||||||
|
this.printTraceLine(tasks[taskName], taskName, ' └')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printTraceLine (traceStatus, name, prefix = '') {
|
||||||
|
const length = this.getStringLength(name)
|
||||||
|
const endPad = _.repeat('-', 45 - prefix.length - length) + '\t'
|
||||||
|
const status = traceStatus.current || traceStatus.status || ''
|
||||||
|
const remark = traceStatus.remark || ''
|
||||||
|
logger.info(`${prefix}【${name}】${endPad}[${status}] \t${remark}`)
|
||||||
|
return traceStatus
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ const { expect } = pkg
|
||||||
|
|
||||||
describe('AutoDeploy', function () {
|
describe('AutoDeploy', function () {
|
||||||
it('#run', async function () {
|
it('#run', async function () {
|
||||||
|
this.timeout(20000)
|
||||||
const options = createOptions()
|
const options = createOptions()
|
||||||
const executor = new Executor()
|
const executor = new Executor()
|
||||||
const ret = await executor.run(options)
|
const ret = await executor.run(options)
|
||||||
|
@ -12,6 +13,7 @@ describe('AutoDeploy', function () {
|
||||||
expect(ret.cert).ok
|
expect(ret.cert).ok
|
||||||
})
|
})
|
||||||
it('#forceCert', async function () {
|
it('#forceCert', async function () {
|
||||||
|
this.timeout(20000)
|
||||||
const executor = new Executor()
|
const executor = new Executor()
|
||||||
const options = createOptions()
|
const options = createOptions()
|
||||||
const ret = await executor.run(options, { forceCert: true, forceDeploy: false })
|
const ret = await executor.run(options, { forceCert: true, forceDeploy: false })
|
||||||
|
@ -19,9 +21,10 @@ describe('AutoDeploy', function () {
|
||||||
expect(ret.cert).ok
|
expect(ret.cert).ok
|
||||||
})
|
})
|
||||||
it('#forceDeploy', async function () {
|
it('#forceDeploy', async function () {
|
||||||
|
this.timeout(20000)
|
||||||
const executor = new Executor()
|
const executor = new Executor()
|
||||||
const options = createOptions()
|
const options = createOptions()
|
||||||
const ret = await executor.run(options, { forceCert: false, forceDeploy: true })
|
const ret = await executor.run(options, { forceCert: false, forceDeploy: true, forceRedeploy: true })
|
||||||
expect(ret).ok
|
expect(ret).ok
|
||||||
expect(ret.cert).ok
|
expect(ret.cert).ok
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"kubernetes-client": "^9.0.0",
|
"kubernetes-client": "^9.0.0",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"log4js": "^6.3.0",
|
"log4js": "^6.3.0",
|
||||||
|
"ssh2": "^0.8.9",
|
||||||
"tencentcloud-sdk-nodejs": "^4.0.39"
|
"tencentcloud-sdk-nodejs": "^4.0.39"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -3,8 +3,9 @@ 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'
|
||||||
export class AbstractPlugin {
|
export class AbstractPlugin {
|
||||||
constructor () {
|
constructor ({ accessProviders }) {
|
||||||
this.logger = logger
|
this.logger = logger
|
||||||
|
this.accessProviders = accessProviders
|
||||||
}
|
}
|
||||||
|
|
||||||
appendTimeSuffix (name) {
|
appendTimeSuffix (name) {
|
||||||
|
@ -58,7 +59,7 @@ export class AbstractPlugin {
|
||||||
console.error('请实现此方法,rollback:', options.context)
|
console.error('请实现此方法,rollback:', options.context)
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccessProvider (accessProvider, accessProviders) {
|
getAccessProvider (accessProvider, accessProviders = this.accessProviders) {
|
||||||
if (typeof accessProvider === 'string' && accessProviders) {
|
if (typeof accessProvider === 'string' && accessProviders) {
|
||||||
accessProvider = accessProviders[accessProvider]
|
accessProvider = accessProviders[accessProvider]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
import { AbstractPlugin } from '../abstract-plugin/index.js'
|
import { AbstractPlugin } from '../abstract-plugin/index.js'
|
||||||
|
|
||||||
export class AbstractAliyunPlugin extends AbstractPlugin {
|
export class AbstractAliyunPlugin extends AbstractPlugin {
|
||||||
format (pem) {
|
|
||||||
pem = pem.replace(/\r/g, '')
|
|
||||||
pem = pem.replace(/\n\n/g, '\n')
|
|
||||||
pem = pem.replace(/\n$/g, '')
|
|
||||||
return pem
|
|
||||||
}
|
|
||||||
|
|
||||||
checkRet (ret) {
|
checkRet (ret) {
|
||||||
if (ret.code != null) {
|
if (ret.code != null) {
|
||||||
throw new Error('执行失败:', ret.Message)
|
throw new Error('执行失败:', ret.Message)
|
||||||
|
|
|
@ -51,8 +51,8 @@ export class DeployCertToAliyunCDN extends AbstractAliyunPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute ({ accessProviders, cert, props, context }) {
|
async execute ({ cert, props, context }) {
|
||||||
const accessProvider = this.getAccessProvider(props.accessProvider, accessProviders)
|
const accessProvider = this.getAccessProvider(props.accessProvider)
|
||||||
const client = this.getClient(accessProvider)
|
const client = this.getClient(accessProvider)
|
||||||
const params = this.buildParams(props, context, cert)
|
const params = this.buildParams(props, context, cert)
|
||||||
await this.doRequest(client, params)
|
await this.doRequest(client, params)
|
||||||
|
@ -77,8 +77,8 @@ export class DeployCertToAliyunCDN extends AbstractAliyunPlugin {
|
||||||
ServerCertificateStatus: 'on',
|
ServerCertificateStatus: 'on',
|
||||||
CertName: CertName,
|
CertName: CertName,
|
||||||
CertType: from,
|
CertType: from,
|
||||||
ServerCertificate: super.format(cert.crt.toString()),
|
ServerCertificate: cert.crt,
|
||||||
PrivateKey: super.format(cert.key.toString())
|
PrivateKey: cert.key
|
||||||
}
|
}
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import Core from '@alicloud/pop-core'
|
import Core from '@alicloud/pop-core'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { AbstractAliyunPlugin } from '../abstract-aliyun.js'
|
import { AbstractAliyunPlugin } from '../abstract-aliyun.js'
|
||||||
export class UploadCertToAliyun extends AbstractAliyunPlugin {
|
export class UploadCertToAliyun extends AbstractAliyunPlugin {
|
||||||
/**
|
/**
|
||||||
|
@ -45,21 +44,21 @@ export class UploadCertToAliyun extends AbstractAliyunPlugin {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute ({ accessProviders, cert, props, context }) {
|
async execute ({ cert, props, context }) {
|
||||||
const { name, accessProvider } = props
|
const { name, accessProvider } = props
|
||||||
const certName = this.appendTimeSuffix(name || cert.domain)
|
const certName = this.appendTimeSuffix(name || cert.domain)
|
||||||
const params = {
|
const params = {
|
||||||
RegionId: props.regionId || 'cn-hangzhou',
|
RegionId: props.regionId || 'cn-hangzhou',
|
||||||
Name: certName,
|
Name: certName,
|
||||||
Cert: this.format(cert.crt.toString()),
|
Cert: cert.crt,
|
||||||
Key: this.format(cert.key.toString())
|
Key: cert.key
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestOption = {
|
const requestOption = {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}
|
}
|
||||||
|
|
||||||
const provider = super.getAccessProvider(accessProvider, accessProviders)
|
const provider = this.getAccessProvider(accessProvider)
|
||||||
const client = this.getClient(provider)
|
const client = this.getClient(provider)
|
||||||
const ret = await client.request('CreateUserCertificate', params, requestOption)
|
const ret = await client.request('CreateUserCertificate', params, requestOption)
|
||||||
this.checkRet(ret)
|
this.checkRet(ret)
|
||||||
|
@ -75,7 +74,7 @@ export class UploadCertToAliyun extends AbstractAliyunPlugin {
|
||||||
* @param context
|
* @param context
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async rollback ({ accessProviders, cert, props, context }) {
|
async rollback ({ cert, props, context }) {
|
||||||
const { accessProvider } = props
|
const { accessProvider } = props
|
||||||
const { aliyunCertId } = context
|
const { aliyunCertId } = context
|
||||||
this.logger.info('准备删除阿里云证书:', aliyunCertId)
|
this.logger.info('准备删除阿里云证书:', aliyunCertId)
|
||||||
|
@ -88,7 +87,7 @@ export class UploadCertToAliyun extends AbstractAliyunPlugin {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}
|
}
|
||||||
|
|
||||||
const provider = super.getAccessProvider(accessProvider, accessProviders)
|
const provider = this.getAccessProvider(accessProvider)
|
||||||
const client = this.getClient(provider)
|
const client = this.getClient(provider)
|
||||||
const ret = await client.request('DeleteUserCertificate', params, requestOption)
|
const ret = await client.request('DeleteUserCertificate', params, requestOption)
|
||||||
this.checkRet(ret)
|
this.checkRet(ret)
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { AbstractPlugin } from '../abstract-plugin/index.js'
|
||||||
|
|
||||||
|
export class AbstractHostPlugin extends AbstractPlugin {
|
||||||
|
checkRet (ret) {
|
||||||
|
if (ret.code != null) {
|
||||||
|
throw new Error('执行失败:', ret.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { AbstractHostPlugin } from '../abstract-host.js'
|
||||||
|
import { SshClient } from '../ssh.js'
|
||||||
|
export class HostShellExecute extends AbstractHostPlugin {
|
||||||
|
/**
|
||||||
|
* 插件定义
|
||||||
|
* 名称
|
||||||
|
* 入参
|
||||||
|
* 出参
|
||||||
|
*/
|
||||||
|
static define () {
|
||||||
|
return {
|
||||||
|
name: 'hostShellExecute',
|
||||||
|
label: '执行远程主机脚本命令',
|
||||||
|
input: {
|
||||||
|
script: {
|
||||||
|
label: 'shell脚本命令'
|
||||||
|
},
|
||||||
|
accessProvider: {
|
||||||
|
label: '主机登录配置',
|
||||||
|
type: [String, Object],
|
||||||
|
desc: 'AccessProviders的key 或 一个包含用户名密码的对象',
|
||||||
|
options: 'accessProviders[type=ssh]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute ({ cert, props, context }) {
|
||||||
|
const { script, accessProvider } = props
|
||||||
|
const connectConf = this.getAccessProvider(accessProvider)
|
||||||
|
const sshClient = new SshClient()
|
||||||
|
const ret = await sshClient.shell({
|
||||||
|
connectConf,
|
||||||
|
script
|
||||||
|
})
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cert
|
||||||
|
* @param props
|
||||||
|
* @param context
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async rollback ({ cert, props, context }) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
import ssh2 from 'ssh2'
|
||||||
|
import logger from '../utils/util.log.js'
|
||||||
|
import path from 'path'
|
||||||
|
export class SshClient {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param connectConf
|
||||||
|
{
|
||||||
|
host: '192.168.100.100',
|
||||||
|
port: 22,
|
||||||
|
username: 'frylock',
|
||||||
|
password: 'nodejsrules'
|
||||||
|
}
|
||||||
|
* @param transports
|
||||||
|
*/
|
||||||
|
uploadFiles ({ connectConf, transports }) {
|
||||||
|
const conn = new ssh2.Client()
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
conn.on('ready', () => {
|
||||||
|
logger.info('连接服务器成功')
|
||||||
|
conn.sftp(async (err, sftp) => {
|
||||||
|
if (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const transport of transports) {
|
||||||
|
logger.info('上传文件:', JSON.stringify(transport))
|
||||||
|
await this.exec({ conn, cmd: 'mkdir ' + path.dirname(transport.remotePath) })
|
||||||
|
await this.fastPut({ sftp, ...transport })
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
} finally {
|
||||||
|
conn.end()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).connect(connectConf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
shell ({ connectConf, script }) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.connect({
|
||||||
|
connectConf,
|
||||||
|
onReady: (conn) => {
|
||||||
|
conn.shell((err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const output = []
|
||||||
|
stream.on('close', () => {
|
||||||
|
logger.info('Stream :: close')
|
||||||
|
conn.end()
|
||||||
|
resolve(output)
|
||||||
|
}).on('data', (data) => {
|
||||||
|
logger.info('' + data)
|
||||||
|
output.push('' + data)
|
||||||
|
})
|
||||||
|
stream.end(script + '\nexit\n')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
connect ({ connectConf, onReady }) {
|
||||||
|
const conn = new ssh2.Client()
|
||||||
|
conn.on('ready', () => {
|
||||||
|
console.log('Client :: ready')
|
||||||
|
onReady(conn)
|
||||||
|
}).connect(connectConf)
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
fastPut ({ sftp, localPath, remotePath }) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
sftp.fastPut(localPath, remotePath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { AbstractHostPlugin } from '../abstract-host.js'
|
||||||
|
import { SshClient } from '../ssh.js'
|
||||||
|
export class UploadCertToHost extends AbstractHostPlugin {
|
||||||
|
/**
|
||||||
|
* 插件定义
|
||||||
|
* 名称
|
||||||
|
* 入参
|
||||||
|
* 出参
|
||||||
|
*/
|
||||||
|
static define () {
|
||||||
|
return {
|
||||||
|
name: 'uploadCertToHost',
|
||||||
|
label: '上传证书到主机',
|
||||||
|
input: {
|
||||||
|
crtPath: {
|
||||||
|
label: '证书路径'
|
||||||
|
},
|
||||||
|
keyPath: {
|
||||||
|
label: '私钥路径'
|
||||||
|
},
|
||||||
|
accessProvider: {
|
||||||
|
label: '主机登录配置',
|
||||||
|
type: [String, Object],
|
||||||
|
desc: 'AccessProviders的key 或 一个包含用户名密码的对象',
|
||||||
|
options: 'accessProviders[type=ssh]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
hostCrtPath: {
|
||||||
|
type: String,
|
||||||
|
desc: '上传成功后的证书路径'
|
||||||
|
},
|
||||||
|
hostKeyPath: {
|
||||||
|
type: String,
|
||||||
|
desc: '上传成功后的私钥路径'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute ({ cert, props, context }) {
|
||||||
|
const { crtPath, keyPath, accessProvider } = props
|
||||||
|
const connectConf = this.getAccessProvider(accessProvider)
|
||||||
|
const sshClient = new SshClient()
|
||||||
|
await sshClient.uploadFiles({
|
||||||
|
connectConf,
|
||||||
|
transports: [
|
||||||
|
{
|
||||||
|
localPath: cert.crtPath,
|
||||||
|
remotePath: crtPath
|
||||||
|
},
|
||||||
|
{
|
||||||
|
localPath: cert.keyPath,
|
||||||
|
remotePath: keyPath
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
this.logger.info('证书上传成功:crtPath=', crtPath, ',keyPath=', keyPath)
|
||||||
|
|
||||||
|
context.hostCrtPath = crtPath
|
||||||
|
context.hostKeyPath = keyPath
|
||||||
|
return {
|
||||||
|
hostCrtPath: crtPath,
|
||||||
|
hostKeyPath: keyPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cert
|
||||||
|
* @param props
|
||||||
|
* @param context
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async rollback ({ cert, props, context }) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,6 @@
|
||||||
import { AbstractPlugin } from '../abstract-plugin/index.js'
|
import { AbstractPlugin } from '../abstract-plugin/index.js'
|
||||||
|
|
||||||
export class AbstractTencentPlugin extends AbstractPlugin {
|
export class AbstractTencentPlugin extends AbstractPlugin {
|
||||||
format (pem) {
|
|
||||||
pem = pem.replace(/\r/g, '')
|
|
||||||
pem = pem.replace(/\n\n/g, '\n')
|
|
||||||
pem = pem.replace(/\n$/g, '')
|
|
||||||
return pem
|
|
||||||
}
|
|
||||||
|
|
||||||
checkRet (ret) {
|
checkRet (ret) {
|
||||||
if (!ret || ret.Error) {
|
if (!ret || ret.Error) {
|
||||||
throw new Error('执行失败:' + ret.Error.Code + ',' + ret.Error.Message)
|
throw new Error('执行失败:' + ret.Error.Code + ',' + ret.Error.Message)
|
||||||
|
|
|
@ -52,14 +52,14 @@ export class DeployCertToTencentCDN extends AbstractTencentPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute ({ accessProviders, cert, props, context }) {
|
async execute ({ cert, props, context }) {
|
||||||
const accessProvider = this.getAccessProvider(props.accessProvider, accessProviders)
|
const accessProvider = this.getAccessProvider(props.accessProvider)
|
||||||
const client = this.getClient(accessProvider)
|
const client = this.getClient(accessProvider)
|
||||||
const params = this.buildParams(props, context, cert)
|
const params = this.buildParams(props, context, cert)
|
||||||
await this.doRequest(client, params)
|
await this.doRequest(client, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
async rollback ({ accessProviders, cert, props, context }) {
|
async rollback ({ cert, props, context }) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,8 +99,8 @@ export class DeployCertToTencentCDN extends AbstractTencentPlugin {
|
||||||
}
|
}
|
||||||
if (from === 'upload' || tencentCertId == null) {
|
if (from === 'upload' || tencentCertId == null) {
|
||||||
params.Https.CertInfo = {
|
params.Https.CertInfo = {
|
||||||
Certificate: this.format(cert.crt.toString()),
|
Certificate: cert.crt,
|
||||||
PrivateKey: this.format(cert.key.toString())
|
PrivateKey: cert.key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return params
|
return params
|
||||||
|
|
|
@ -48,8 +48,8 @@ export class DeployCertToTencentCLB extends AbstractTencentPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute ({ accessProviders, cert, props, context }) {
|
async execute ({ cert, props, context }) {
|
||||||
const accessProvider = this.getAccessProvider(props.accessProvider, accessProviders)
|
const accessProvider = this.getAccessProvider(props.accessProvider)
|
||||||
const { region } = props
|
const { region } = props
|
||||||
const client = this.getClient(accessProvider, region)
|
const client = this.getClient(accessProvider, region)
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ export class DeployCertToTencentCLB extends AbstractTencentPlugin {
|
||||||
return certId
|
return certId
|
||||||
}
|
}
|
||||||
|
|
||||||
async rollback ({ accessProviders, cert, props, context }) {
|
async rollback ({ cert, props, context }) {
|
||||||
this.logger.warn('未实现rollback')
|
this.logger.warn('未实现rollback')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,8 +140,8 @@ export class DeployCertToTencentCLB extends AbstractTencentPlugin {
|
||||||
|
|
||||||
if (tencentCertId == null) {
|
if (tencentCertId == null) {
|
||||||
params.Certificate.CertName = this.appendTimeSuffix(certName || cert.domain)
|
params.Certificate.CertName = this.appendTimeSuffix(certName || cert.domain)
|
||||||
params.Certificate.CertKey = this.format(cert.key.toString())
|
params.Certificate.CertKey = cert.key
|
||||||
params.Certificate.CertContent = this.format(cert.crt.toString())
|
params.Certificate.CertContent = cert.crt
|
||||||
}
|
}
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,20 +14,28 @@ export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
|
||||||
name: 'deployCertToTencentTKEIngress',
|
name: 'deployCertToTencentTKEIngress',
|
||||||
label: '部署到腾讯云TKE-ingress',
|
label: '部署到腾讯云TKE-ingress',
|
||||||
input: {
|
input: {
|
||||||
|
region: {
|
||||||
|
label: '大区',
|
||||||
|
value: 'ap-guangzhou'
|
||||||
|
},
|
||||||
clusterId: {
|
clusterId: {
|
||||||
label: '集群ID',
|
label: '集群ID',
|
||||||
required: true,
|
required: true,
|
||||||
desc: '例如:cls-6lbj1vee'
|
desc: '例如:cls-6lbj1vee'
|
||||||
},
|
},
|
||||||
region: {
|
namespace: {
|
||||||
label: '大区',
|
label: '集群的namespace',
|
||||||
value: 'ap-guangzhou'
|
value: 'default'
|
||||||
},
|
},
|
||||||
secreteName: {
|
secreteName: {
|
||||||
label: '证书的secret名称'
|
type: [String, Array],
|
||||||
|
label: '证书的secret名称',
|
||||||
|
desc: '支持多个(传入数组)'
|
||||||
},
|
},
|
||||||
ingressName: {
|
ingressName: {
|
||||||
label: 'ingress名称'
|
type: [String, Array],
|
||||||
|
label: 'ingress名称',
|
||||||
|
desc: '支持多个(传入数组)'
|
||||||
},
|
},
|
||||||
accessProvider: {
|
accessProvider: {
|
||||||
label: 'Access提供者',
|
label: 'Access提供者',
|
||||||
|
@ -43,11 +51,12 @@ export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute ({ accessProviders, cert, props, context }) {
|
async execute ({ cert, props, context }) {
|
||||||
const accessProvider = this.getAccessProvider(props.accessProvider, accessProviders)
|
const accessProvider = this.getAccessProvider(props.accessProvider)
|
||||||
const tkeClient = this.getTkeClient(accessProvider, props.region)
|
const tkeClient = this.getTkeClient(accessProvider, props.region)
|
||||||
const kubeConfigStr = await this.getTkeKubeConfig(tkeClient, props.clusterId)
|
const kubeConfigStr = await this.getTkeKubeConfig(tkeClient, props.clusterId)
|
||||||
|
|
||||||
|
this.logger.info('kubeconfig已成功获取')
|
||||||
const k8sClient = new K8sClient(kubeConfigStr)
|
const k8sClient = new K8sClient(kubeConfigStr)
|
||||||
await this.patchCertSecret({ k8sClient, props, context })
|
await this.patchCertSecret({ k8sClient, props, context })
|
||||||
await this.sleep(2000) // 停留2秒,等待secret部署完成
|
await this.sleep(2000) // 停留2秒,等待secret部署完成
|
||||||
|
@ -84,33 +93,6 @@ export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
|
||||||
return ret.Kubeconfig
|
return ret.Kubeconfig
|
||||||
}
|
}
|
||||||
|
|
||||||
async createCertSecret ({ k8sClient, props, cert, context }) {
|
|
||||||
const { tencentCertId } = context
|
|
||||||
if (tencentCertId == null) {
|
|
||||||
throw new Error('请先将【上传证书到腾讯云】作为前置任务')
|
|
||||||
}
|
|
||||||
const certIdBase64 = Buffer.from(tencentCertId).toString('base64')
|
|
||||||
|
|
||||||
let name = 'cert-' + cert.domain.replace(/\./g, '-')
|
|
||||||
name = name.replace(/\*/g, '-')
|
|
||||||
// name = this.appendTimeSuffix(name)
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
kind: 'Secret',
|
|
||||||
data: {
|
|
||||||
qcloud_cert_id: certIdBase64
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
name,
|
|
||||||
labels: {
|
|
||||||
certd: 'certd-' + this.getSafetyDomain(cert.domain)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
type: 'Opaque'
|
|
||||||
}
|
|
||||||
return await k8sClient.createSecret({ namespace: props.namespace, body: body })
|
|
||||||
}
|
|
||||||
|
|
||||||
async patchCertSecret ({ k8sClient, props, context }) {
|
async patchCertSecret ({ k8sClient, props, context }) {
|
||||||
const { tencentCertId } = context
|
const { tencentCertId } = context
|
||||||
if (tencentCertId == null) {
|
if (tencentCertId == null) {
|
||||||
|
@ -130,7 +112,14 @@ export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await k8sClient.patchSecret({ namespace, secretName, body })
|
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 }) {
|
||||||
|
@ -143,6 +132,13 @@ export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await k8sClient.patchIngress({ namespace, ingressName, body })
|
let ingressNames = ingressName
|
||||||
|
if (typeof ingressName === 'string') {
|
||||||
|
ingressNames = [ingressName]
|
||||||
|
}
|
||||||
|
for (const ingress of ingressNames) {
|
||||||
|
await k8sClient.patchIngress({ namespace, ingressName: ingress, body })
|
||||||
|
this.logger.info(`ingress已重启:${ingress}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,16 +52,16 @@ export class UploadCertToTencent extends AbstractTencentPlugin {
|
||||||
return new SslClient(clientConfig)
|
return new SslClient(clientConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute ({ accessProviders, cert, props, context, logger }) {
|
async execute ({ cert, props, context, logger }) {
|
||||||
const { name, accessProvider } = props
|
const { name, accessProvider } = props
|
||||||
const certName = this.appendTimeSuffix(name)
|
const certName = this.appendTimeSuffix(name)
|
||||||
|
|
||||||
const provider = super.getAccessProvider(accessProvider, accessProviders)
|
const provider = this.getAccessProvider(accessProvider)
|
||||||
const client = this.getClient(provider)
|
const client = this.getClient(provider)
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
CertificatePublicKey: this.format(cert.crt.toString()),
|
CertificatePublicKey: cert.crt,
|
||||||
CertificatePrivateKey: this.format(cert.key.toString()),
|
CertificatePrivateKey: cert.key,
|
||||||
Alias: certName
|
Alias: certName
|
||||||
}
|
}
|
||||||
const ret = await client.UploadCertificate(params)
|
const ret = await client.UploadCertificate(params)
|
||||||
|
@ -70,9 +70,9 @@ export class UploadCertToTencent extends AbstractTencentPlugin {
|
||||||
context.tencentCertId = ret.CertificateId
|
context.tencentCertId = ret.CertificateId
|
||||||
}
|
}
|
||||||
|
|
||||||
async rollback ({ accessProviders, cert, props, context }) {
|
async rollback ({ cert, props, context }) {
|
||||||
const { accessProvider } = props
|
const { accessProvider } = props
|
||||||
const provider = super.getAccessProvider(accessProvider, accessProviders)
|
const provider = super.getAccessProvider(accessProvider)
|
||||||
const client = this.getClient(provider)
|
const client = this.getClient(provider)
|
||||||
|
|
||||||
const { tencentCertId } = context
|
const { tencentCertId } = context
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import pkg from 'chai'
|
||||||
|
import { HostShellExecute } from '../../src/host/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 ', accessProvider: 'aliyun-ssh' },
|
||||||
|
context
|
||||||
|
}
|
||||||
|
const ret = await plugin.doExecute(uploadOpts)
|
||||||
|
for (const retElement of ret) {
|
||||||
|
console.log('-----' + retElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
await plugin.doRollback(uploadOpts)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,27 @@
|
||||||
|
import pkg from 'chai'
|
||||||
|
import { UploadCertToHost } from '../../src/host/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)
|
||||||
|
})
|
||||||
|
})
|
|
@ -36,9 +36,11 @@ describe('DeployToTencentCDN', function () {
|
||||||
it('#execute-upload', async function () {
|
it('#execute-upload', async function () {
|
||||||
const options = createOptions()
|
const options = createOptions()
|
||||||
options.args.test = false
|
options.args.test = false
|
||||||
|
options.cert.email = 'xiaojunnuo@qq.com'
|
||||||
|
options.cert.domains = ['*.docmirror.cn']
|
||||||
const plugin = new DeployCertToTencentCDN()
|
const plugin = new DeployCertToTencentCDN()
|
||||||
const certd = new Certd(options)
|
const certd = new Certd(options)
|
||||||
const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn'])
|
const cert = await certd.readCurrentCert()
|
||||||
const context = {}
|
const context = {}
|
||||||
const deployOpts = {
|
const deployOpts = {
|
||||||
accessProviders: options.accessProviders,
|
accessProviders: options.accessProviders,
|
||||||
|
|
|
@ -29,15 +29,14 @@ describe('DeployToTencentCLB', 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',
|
||||||
loadBalancerId: 'lb-59yhe5xo'
|
loadBalancerId: 'lb-59yhe5xo'
|
||||||
}
|
}
|
||||||
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)
|
||||||
|
|
||||||
|
@ -51,8 +50,10 @@ describe('DeployToTencentCLB', 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'
|
||||||
|
options.cert.email = 'xiaojunnuo@qq.com'
|
||||||
|
options.cert.domains = ['*.docmirror.cn']
|
||||||
const certd = new Certd(options)
|
const certd = new Certd(options)
|
||||||
const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn'])
|
const cert = await certd.readCurrentCert()
|
||||||
const deployPlugin = new DeployCertToTencentCLB()
|
const deployPlugin = new DeployCertToTencentCLB()
|
||||||
const context = {}
|
const context = {}
|
||||||
const deployOpts = {
|
const deployOpts = {
|
||||||
|
|
|
@ -8,8 +8,10 @@ describe('PluginUploadToTencent', function () {
|
||||||
const options = createOptions()
|
const options = createOptions()
|
||||||
const plugin = new UploadCertToTencent()
|
const plugin = new UploadCertToTencent()
|
||||||
options.args = { test: false }
|
options.args = { test: false }
|
||||||
|
options.cert.email = 'xiaojunnuo@qq.com'
|
||||||
|
options.cert.domains = ['*.docmirror.cn']
|
||||||
const certd = new Certd(options)
|
const certd = new Certd(options)
|
||||||
const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn'])
|
const cert = await certd.readCurrentCert()
|
||||||
const context = {}
|
const context = {}
|
||||||
const uploadOpts = {
|
const uploadOpts = {
|
||||||
accessProviders: options.accessProviders,
|
accessProviders: options.accessProviders,
|
||||||
|
|
|
@ -382,7 +382,7 @@ array.prototype.flatmap@^1.2.3:
|
||||||
es-abstract "^1.18.0-next.1"
|
es-abstract "^1.18.0-next.1"
|
||||||
function-bind "^1.1.1"
|
function-bind "^1.1.1"
|
||||||
|
|
||||||
asn1@~0.2.3:
|
asn1@~0.2.0, asn1@~0.2.3:
|
||||||
version "0.2.4"
|
version "0.2.4"
|
||||||
resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||||
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
|
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
|
||||||
|
@ -458,7 +458,7 @@ base64url@^3.0.1:
|
||||||
resolved "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
|
resolved "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
|
||||||
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
|
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
|
||||||
|
|
||||||
bcrypt-pbkdf@^1.0.0:
|
bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||||
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
|
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
|
||||||
|
@ -2500,6 +2500,22 @@ sprintf-js@~1.0.2:
|
||||||
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||||
|
|
||||||
|
ssh2-streams@~0.4.10:
|
||||||
|
version "0.4.10"
|
||||||
|
resolved "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34"
|
||||||
|
integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==
|
||||||
|
dependencies:
|
||||||
|
asn1 "~0.2.0"
|
||||||
|
bcrypt-pbkdf "^1.0.2"
|
||||||
|
streamsearch "~0.1.2"
|
||||||
|
|
||||||
|
ssh2@^0.8.9:
|
||||||
|
version "0.8.9"
|
||||||
|
resolved "https://registry.npmjs.org/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3"
|
||||||
|
integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==
|
||||||
|
dependencies:
|
||||||
|
ssh2-streams "~0.4.10"
|
||||||
|
|
||||||
sshpk@^1.7.0:
|
sshpk@^1.7.0:
|
||||||
version "1.16.1"
|
version "1.16.1"
|
||||||
resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
|
resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
|
||||||
|
@ -2524,6 +2540,11 @@ streamroller@^2.2.4:
|
||||||
debug "^4.1.1"
|
debug "^4.1.1"
|
||||||
fs-extra "^8.1.0"
|
fs-extra "^8.1.0"
|
||||||
|
|
||||||
|
streamsearch@~0.1.2:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
||||||
|
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
|
||||||
|
|
||||||
"string-width@^1.0.2 || 2":
|
"string-width@^1.0.2 || 2":
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
resolved "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
||||||
|
|
Loading…
Reference in New Issue