mirror of
https://github.com/certd/certd.git
synced 2026-04-23 11:37:23 +08:00
pref: 调整插件目录,增加一些帮助说明
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export * from './ssh-access';
|
||||
@@ -0,0 +1,53 @@
|
||||
import { AccessInput, IAccess, IsAccess } from '@certd/pipeline';
|
||||
|
||||
@IsAccess({
|
||||
name: 'ssh',
|
||||
title: '主机登录授权',
|
||||
desc: '',
|
||||
input: {},
|
||||
})
|
||||
export class SshAccess implements IAccess {
|
||||
@AccessInput({
|
||||
title: '主机地址',
|
||||
component: {
|
||||
placeholder: '主机域名或IP地址',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
host!: string;
|
||||
@AccessInput({
|
||||
title: '端口',
|
||||
value: '22',
|
||||
component: {
|
||||
placeholder: '22',
|
||||
},
|
||||
rules: [{ required: true, message: '此项必填' }],
|
||||
})
|
||||
port!: string;
|
||||
@AccessInput({
|
||||
title: '用户名',
|
||||
value: 'root',
|
||||
rules: [{ required: true, message: '此项必填' }],
|
||||
})
|
||||
username!: string;
|
||||
@AccessInput({
|
||||
title: '密码',
|
||||
component: {
|
||||
name: 'a-input-password',
|
||||
vModel: 'value',
|
||||
},
|
||||
helper: '登录密码或密钥必填一项',
|
||||
})
|
||||
password!: string;
|
||||
@AccessInput({
|
||||
title: '密钥',
|
||||
helper: '密钥或密码必填一项',
|
||||
component: {
|
||||
name: 'a-textarea',
|
||||
vModel: 'value',
|
||||
},
|
||||
})
|
||||
privateKey!: string;
|
||||
}
|
||||
|
||||
new SshAccess();
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './access';
|
||||
export * from './lib/ssh';
|
||||
export * from './plugin';
|
||||
@@ -0,0 +1,162 @@
|
||||
// @ts-ignore
|
||||
import ssh2 from 'ssh2';
|
||||
import path from 'path';
|
||||
import _ from 'lodash';
|
||||
import { ILogger } from '@certd/pipeline';
|
||||
export class SshClient {
|
||||
logger: ILogger;
|
||||
constructor(logger: ILogger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param connectConf
|
||||
{
|
||||
host: '192.168.100.100',
|
||||
port: 22,
|
||||
username: 'frylock',
|
||||
password: 'nodejsrules'
|
||||
}
|
||||
* @param options
|
||||
*/
|
||||
uploadFiles(options: { connectConf: any; transports: any }) {
|
||||
const { connectConf, transports } = options;
|
||||
const conn = new ssh2.Client();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
conn
|
||||
.on('ready', () => {
|
||||
this.logger.info('连接服务器成功');
|
||||
conn.sftp(async (err: any, sftp: any) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
for (const transport of transports) {
|
||||
this.logger.info('上传文件:', JSON.stringify(transport));
|
||||
await this.exec({
|
||||
connectConf,
|
||||
script: `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 as Array<string>;
|
||||
script = script.join('\n');
|
||||
}
|
||||
this.logger.info('执行命令:', script);
|
||||
return new Promise((resolve, reject) => {
|
||||
this.connect({
|
||||
connectConf,
|
||||
onError(err: any) {
|
||||
reject(err);
|
||||
},
|
||||
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,
|
||||
onError: (err: any) => {
|
||||
this.logger.error(err);
|
||||
reject(err);
|
||||
},
|
||||
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; onError: any }) {
|
||||
const { connectConf, onReady, onError } = options;
|
||||
const conn = new ssh2.Client();
|
||||
conn
|
||||
.on('error', (err: any) => {
|
||||
onError(err);
|
||||
})
|
||||
.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,53 @@
|
||||
import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||
import { SshClient } from "../../lib/ssh";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: "hostShellExecute",
|
||||
title: "执行远程主机脚本命令",
|
||||
input: {},
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
output: {},
|
||||
})
|
||||
export class HostShellExecutePlugin extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
title: "主机登录配置",
|
||||
helper: "登录",
|
||||
component: {
|
||||
name: "pi-access-selector",
|
||||
type: "ssh",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
accessId!: string;
|
||||
@TaskInput({
|
||||
title: "shell脚本命令",
|
||||
component: {
|
||||
name: "a-textarea",
|
||||
vModel: "value",
|
||||
},
|
||||
})
|
||||
script!: string;
|
||||
|
||||
accessService!: IAccessService;
|
||||
logger!: ILogger;
|
||||
async onInstance() {
|
||||
this.accessService = this.ctx.accessService;
|
||||
this.logger = this.ctx.logger;
|
||||
}
|
||||
async execute(): Promise<void> {
|
||||
const { script, accessId } = this;
|
||||
const connectConf = await this.accessService.getById(accessId);
|
||||
const sshClient = new SshClient(this.logger);
|
||||
const ret = await sshClient.exec({
|
||||
connectConf,
|
||||
script,
|
||||
});
|
||||
this.logger.info("exec res:", ret);
|
||||
}
|
||||
}
|
||||
|
||||
new HostShellExecutePlugin();
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './host-shell-execute';
|
||||
export * from './upload-to-host';
|
||||
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
AbstractTaskPlugin,
|
||||
IAccessService,
|
||||
ILogger,
|
||||
IsTaskPlugin,
|
||||
RunStrategy,
|
||||
TaskInput,
|
||||
TaskOutput,
|
||||
} from '@certd/pipeline';
|
||||
import { SshClient } from '../../lib/ssh';
|
||||
import { CertInfo, CertReader } from '@certd/plugin-cert';
|
||||
import * as fs from 'fs';
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'uploadCertToHost',
|
||||
title: '上传证书到主机',
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
})
|
||||
export class UploadCertToHostPlugin extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
title: '证书保存路径',
|
||||
helper: '需要有写入权限,路径要包含证书文件名',
|
||||
component: {
|
||||
placeholder: '/root/deploy/nginx/cert.crt',
|
||||
},
|
||||
})
|
||||
crtPath!: string;
|
||||
@TaskInput({
|
||||
title: '私钥保存路径',
|
||||
helper: '需要有写入权限,路径要包含证书文件名',
|
||||
component: {
|
||||
placeholder: '/root/deploy/nginx/cert.crt',
|
||||
},
|
||||
})
|
||||
keyPath!: string;
|
||||
@TaskInput({
|
||||
title: '域名证书',
|
||||
helper: '请选择前置任务输出的域名证书',
|
||||
component: {
|
||||
name: 'pi-output-selector',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
cert!: CertInfo;
|
||||
@TaskInput({
|
||||
title: '主机登录配置',
|
||||
helper: 'access授权',
|
||||
component: {
|
||||
name: 'pi-access-selector',
|
||||
type: 'ssh',
|
||||
},
|
||||
rules: [{ required: true, message: '此项必填' }],
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
@TaskOutput({
|
||||
title: '证书保存路径',
|
||||
})
|
||||
hostCrtPath!: string;
|
||||
|
||||
@TaskOutput({
|
||||
title: '私钥保存路径',
|
||||
})
|
||||
hostKeyPath!: string;
|
||||
|
||||
accessService!: IAccessService;
|
||||
logger!: ILogger;
|
||||
|
||||
async onInstance() {
|
||||
this.accessService = this.ctx.accessService;
|
||||
this.logger = this.ctx.logger;
|
||||
}
|
||||
async execute(): Promise<void> {
|
||||
const { crtPath, keyPath, cert, accessId } = this;
|
||||
const certReader = new CertReader(cert);
|
||||
const connectConf = await this.accessService.getById(accessId);
|
||||
const sshClient = new SshClient(this.logger);
|
||||
|
||||
const saveCrtPath = certReader.saveToFile('crt');
|
||||
const saveKeyPath = certReader.saveToFile('key');
|
||||
|
||||
await sshClient.uploadFiles({
|
||||
connectConf,
|
||||
transports: [
|
||||
{
|
||||
localPath: saveCrtPath,
|
||||
remotePath: crtPath,
|
||||
},
|
||||
{
|
||||
localPath: saveKeyPath,
|
||||
remotePath: keyPath,
|
||||
},
|
||||
],
|
||||
});
|
||||
this.logger.info('证书上传成功:crtPath=', crtPath, ',keyPath=', keyPath);
|
||||
|
||||
//删除临时文件
|
||||
fs.unlinkSync(saveCrtPath);
|
||||
fs.unlinkSync(saveKeyPath);
|
||||
|
||||
//输出
|
||||
this.hostCrtPath = crtPath;
|
||||
this.hostKeyPath = keyPath;
|
||||
}
|
||||
}
|
||||
|
||||
new UploadCertToHostPlugin();
|
||||
Reference in New Issue
Block a user