refactor: 1

This commit is contained in:
xiaojunnuo
2022-11-07 23:31:20 +08:00
parent f710c00c0d
commit d66bc33761
97 changed files with 1384 additions and 3562 deletions
@@ -1,26 +0,0 @@
export class SSHAccessProvider {
static define () {
return {
name: 'ssh',
title: '主机',
desc: '',
input: {
host: { rules: [{ required: true, message: '此项必填' }] },
port: {
title: '端口',
value: '22',
rules: [{ required: true, message: '此项必填' }]
},
username: {
value: 'root',
rules: [{ required: true, message: '此项必填' }]
},
password: { helper: '登录密码' },
privateKey: {
title: '密钥',
helper: '密钥或密码必填一项'
}
}
}
}
}
-22
View File
@@ -1,22 +0,0 @@
import _ from 'lodash'
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)
}
}
+148
View File
@@ -0,0 +1,148 @@
import ssh2 from "ssh2";
import path from "path";
import _ from "lodash";
import { Logger } from "log4js";
export class SshClient {
logger: Logger;
constructor(logger: Logger) {
this.logger = logger;
}
/**
*
* @param connectConf
{
host: '192.168.100.100',
port: 22,
username: 'frylock',
password: 'nodejsrules'
}
* @param options
*/
uploadFiles(options: { connectConf: any; transports: any; sudo: boolean }) {
const { connectConf, transports, sudo } = options;
const conn = new ssh2.Client();
return new Promise((resolve, reject) => {
conn
.on("ready", () => {
this.logger.info("连接服务器成功");
conn.sftp(async (err: Error, sftp: any) => {
if (err) {
throw err;
}
try {
for (const transport of transports) {
this.logger.info("上传文件:", JSON.stringify(transport));
const sudoCmd = sudo ? "sudo" : "";
await this.exec({ connectConf, script: `${sudoCmd} mkdir -p ${path.dirname(transport.remotePath)} ` });
await this.fastPut({ sftp, ...transport });
}
resolve({});
} catch (e) {
reject(e);
} finally {
conn.end();
}
});
})
.connect(connectConf);
});
}
exec(options: { connectConf: any; script: string | Array<string> }) {
let { script } = options;
const { connectConf } = options;
if (_.isArray(script)) {
script = script.join("\n");
}
this.logger.info("执行命令:", script);
return new Promise((resolve, reject) => {
this.connect({
connectConf,
onReady: (conn: any) => {
conn.exec(script, (err: Error, stream: any) => {
if (err) {
reject(err);
return;
}
let data: any = null;
stream
.on("close", (code: any, signal: any) => {
this.logger.info(`[${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: any) => {
this.logger.info(`[${connectConf.host}][info]: ` + ret);
data = ret;
})
.stderr.on("data", (err: Error) => {
this.logger.info(`[${connectConf.host}][error]: ` + err);
data = err;
});
});
},
});
});
}
shell(options: { connectConf: any; script: string }) {
const { connectConf, script } = options;
return new Promise((resolve, reject) => {
this.connect({
connectConf,
onReady: (conn: any) => {
conn.shell((err: Error, stream: any) => {
if (err) {
reject(err);
return;
}
const output: any = [];
stream
.on("close", () => {
this.logger.info("Stream :: close");
conn.end();
resolve(output);
})
.on("data", (data: any) => {
this.logger.info("" + data);
output.push("" + data);
});
stream.end(script + "\nexit\n");
});
},
});
});
}
connect(options: { connectConf: any; onReady: any }) {
const { connectConf, onReady } = options;
const conn = new ssh2.Client();
conn
.on("ready", () => {
this.logger.info("Client :: ready");
onReady(conn);
})
.connect(connectConf);
return conn;
}
fastPut(options: { sftp: any; localPath: string; remotePath: string }) {
const { sftp, localPath, remotePath } = options;
return new Promise((resolve, reject) => {
sftp.fastPut(localPath, remotePath, (err: Error) => {
if (err) {
reject(err);
return;
}
resolve({});
});
});
}
}
@@ -0,0 +1,54 @@
import { IsTask, TaskInput, TaskOutput, TaskPlugin, AbstractPlugin, RunStrategy } from "@certd/pipeline";
import { SshClient } from "../../lib/ssh";
@IsTask(() => {
return {
name: "hostShellExecute",
title: "执行远程主机脚本命令",
input: {
accessId: {
title: "主机登录配置",
helper: "登录",
component: {
name: "pi-access-selector",
type: "ssh",
},
required: true,
},
cert: {
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
},
required: true,
},
script: {
title: "shell脚本命令",
component: {
name: "a-textarea",
vModel: "value",
},
},
},
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
output: {},
};
})
export class HostShellExecutePlugin extends AbstractPlugin implements TaskPlugin {
async execute(input: TaskInput): Promise<TaskOutput> {
const { script, accessId } = input;
const connectConf = this.accessService.getById(accessId);
const sshClient = new SshClient(this.logger);
const ret = await sshClient.exec({
connectConf,
script,
});
this.logger.info("exec res:", ret);
return {};
}
}
@@ -0,0 +1,81 @@
import { IsTask, TaskInput, TaskOutput, TaskPlugin, AbstractPlugin, RunStrategy } from "@certd/pipeline";
import { SshClient } from "../../lib/ssh";
@IsTask(() => {
return {
name: "uploadCertToHost",
title: "上传证书到主机",
input: {
crtPath: {
title: "证书保存路径",
},
keyPath: {
title: "私钥保存路径",
},
cert: {
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
},
required: true,
},
accessId: {
title: "主机登录配置",
helper: "access授权",
component: {
name: "pi-access-selector",
type: "ssh",
},
rules: [{ required: true, message: "此项必填" }],
},
sudo: {
title: "是否sudo",
component: {
name: "a-checkbox",
vModel: "checked",
},
},
},
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
output: {
hostCrtPath: {
title: "上传成功后的证书路径",
},
hostKeyPath: {
title: "上传成功后的私钥路径",
},
},
};
})
export class UploadCertToHostPlugin extends AbstractPlugin implements TaskPlugin {
async execute(input: TaskInput): Promise<TaskOutput> {
const { crtPath, keyPath, cert, accessId, sudo } = input;
const connectConf = this.accessService.getById(accessId);
const sshClient = new SshClient(this.logger);
await sshClient.uploadFiles({
connectConf,
transports: [
{
localPath: cert.crtPath,
remotePath: crtPath,
},
{
localPath: cert.keyPath,
remotePath: keyPath,
},
],
sudo,
});
this.logger.info("证书上传成功:crtPath=", crtPath, ",keyPath=", keyPath);
return {
hostCrtPath: crtPath,
hostKeyPath: keyPath,
};
}
}
@@ -1,9 +0,0 @@
import { AbstractPlugin } from '@certd/api'
export class AbstractHostPlugin extends AbstractPlugin {
checkRet (ret) {
if (ret.code != null) {
throw new Error('执行失败:', ret.Message)
}
}
}
@@ -1,57 +0,0 @@
import { AbstractHostPlugin } from '../abstract-host.js'
import { SshClient } from '../ssh.js'
export class HostShellExecute extends AbstractHostPlugin {
/**
* 插件定义
* 名称
* 入参
* 出参
*/
static define () {
return {
name: 'hostShellExecute',
title: '执行远程主机脚本命令',
input: {
accessProvider: {
title: '主机登录配置',
helper: '登录',
component: {
name: 'access-selector',
type: 'ssh'
},
required: true
},
script: {
title: 'shell脚本命令',
component: {
name: 'a-textarea'
}
}
},
output: {
}
}
}
async execute ({ cert, props, context }) {
const { script, accessProvider } = props
const connectConf = this.getAccessProvider(accessProvider)
const sshClient = new SshClient()
const ret = await sshClient.exec({
connectConf,
script
})
return ret
}
/**
* @param cert
* @param props
* @param context
* @returns {Promise<void>}
*/
async rollback ({ cert, props, context }) {
}
}
@@ -1,130 +0,0 @@
import ssh2 from 'ssh2'
import path from 'path'
import { util } from '@certd/api'
import _ from 'lodash'
const logger = util.logger
export class SshClient {
/**
*
* @param connectConf
{
host: '192.168.100.100',
port: 22,
username: 'frylock',
password: 'nodejsrules'
}
* @param transports
*/
uploadFiles ({ connectConf, transports, sudo = false }) {
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))
sudo = sudo ? 'sudo' : ''
await this.exec({ connectConf, script: `${sudo} mkdir -p ${path.dirname(transport.remotePath)} ` })
await this.fastPut({ sftp, ...transport })
}
resolve()
} catch (e) {
reject(e)
} finally {
conn.end()
}
})
}).connect(connectConf)
})
}
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 }) {
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()
})
})
}
}
@@ -1,85 +0,0 @@
import { AbstractHostPlugin } from '../abstract-host.js'
import { SshClient } from '../ssh.js'
export class UploadCertToHost extends AbstractHostPlugin {
/**
* 插件定义
* 名称
* 入参
* 出参
*/
static define () {
return {
name: 'uploadCertToHost',
title: '上传证书到主机',
input: {
crtPath: {
title: '证书保存路径'
},
keyPath: {
title: '私钥保存路径'
},
accessProvider: {
title: '主机登录配置',
helper: 'access授权',
component: {
name: 'access-selector',
type: 'ssh'
},
rules: [{ required: true, message: '此项必填' }]
},
sudo: {
title: '是否sudo',
component: {
name: 'a-checkbox',
vModel: 'checked'
}
}
},
output: {
hostCrtPath: {
helper: '上传成功后的证书路径'
},
hostKeyPath: {
helper: '上传成功后的私钥路径'
}
}
}
}
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 }) {
}
}