增加依赖是否已经安装判断

This commit is contained in:
whyour 2023-07-10 23:48:05 +08:00
parent f3791cbb62
commit b69ff2895e
6 changed files with 105 additions and 44 deletions

View File

@ -399,6 +399,18 @@ export function promiseExec(command: string): Promise<string> {
});
}
export function promiseExecSuccess(command: string): Promise<string> {
return new Promise((resolve) => {
exec(
command,
{ maxBuffer: 200 * 1024 * 1024, encoding: 'utf8' },
(err, stdout, stderr) => {
resolve(stdout || '');
},
);
});
}
export function parseHeaders(headers: string) {
if (!headers) return {};

View File

@ -4,9 +4,9 @@ import { DataTypes, Model, ModelDefined } from 'sequelize';
export class Dependence {
timestamp?: string;
id?: number;
status?: DependenceStatus;
type?: DependenceTypes;
name?: number;
status: DependenceStatus;
type: DependenceTypes;
name: string;
log?: string[];
remark?: string;
@ -42,19 +42,31 @@ export enum DependenceTypes {
export enum InstallDependenceCommandTypes {
'pnpm add -g',
'pip3 install',
'pip3 install --disable-pip-version-check --root-user-action=ignore',
'apk add',
}
export enum GetDependenceCommandTypes {
'pnpm ls -g ',
'pip3 list --disable-pip-version-check --root-user-action=ignore',
'apk info',
}
export enum versionDependenceCommandTypes {
'@',
'==',
'=',
}
export enum unInstallDependenceCommandTypes {
'pnpm remove -g',
'pip3 uninstall -y',
'pip3 uninstall --disable-pip-version-check --root-user-action=ignore -y',
'apk del',
}
export interface DependenceInstance
extends Model<Dependence, Dependence>,
Dependence {}
Dependence { }
export const DependenceModel = sequelize.define<DependenceInstance>(
'Dependence',
{

View File

@ -8,11 +8,13 @@ import {
DependenceTypes,
unInstallDependenceCommandTypes,
DependenceModel,
GetDependenceCommandTypes,
versionDependenceCommandTypes,
} from '../data/dependence';
import { spawn } from 'cross-spawn';
import SockService from './sock';
import { FindOptions, Op } from 'sequelize';
import { concurrentRun } from '../config/util';
import { promiseExecSuccess } from '../config/util';
import dayjs from 'dayjs';
import taskLimit from '../shared/pLimit';
@ -21,7 +23,7 @@ export default class DependenceService {
constructor(
@Inject('logger') private logger: winston.Logger,
private sockService: SockService,
) { }
) {}
public async create(payloads: Dependence[]): Promise<Dependence[]> {
const tabs = payloads.map((x) => {
@ -137,9 +139,17 @@ export default class DependenceService {
}
private async updateLog(ids: number[], log: string): Promise<void> {
const doc = await DependenceModel.findOne({ where: { id: ids } });
const newLog = doc?.log ? [...doc.log, log] : [log];
await DependenceModel.update({ log: newLog }, { where: { id: ids } });
taskLimit.updateDepLog(async () => {
const docs = await DependenceModel.findAll({ where: { id: ids } });
for (const doc of docs) {
const newLog = doc?.log ? [...doc.log, log] : [log];
await DependenceModel.update(
{ log: newLog },
{ where: { id: doc.id } },
);
}
return null;
});
}
public installOrUninstallDependency(
@ -155,7 +165,7 @@ export default class DependenceService {
: DependenceStatus.removing;
await DependenceModel.update({ status }, { where: { id: depIds } });
const socketMessageType = !force
const socketMessageType = isInstall
? 'installDependence'
: 'uninstallDependence';
const depName = dependency.name;
@ -163,7 +173,7 @@ export default class DependenceService {
isInstall
? InstallDependenceCommandTypes
: unInstallDependenceCommandTypes
)[dependency.type as any];
)[dependency.type];
const actionText = isInstall ? '安装' : '删除';
const startTime = dayjs();
@ -175,7 +185,39 @@ export default class DependenceService {
message,
references: depIds,
});
await this.updateLog(depIds, message);
this.updateLog(depIds, message);
// 判断是否已经安装过依赖
if (isInstall) {
const getCommandPrefix = GetDependenceCommandTypes[dependency.type];
const depVersionStr = versionDependenceCommandTypes[dependency.type];
const [_depName] = dependency.name.split(depVersionStr);
const depInfo = (
await promiseExecSuccess(
dependency.type === DependenceTypes.linux
? `${getCommandPrefix} ${_depName}`
: `${getCommandPrefix} | grep "${_depName}"`,
)
).replace(/\s{2,}/, ' ');
if (depInfo) {
const endTime = dayjs();
const _message = `检测到已经安装 ${_depName}\n\n${depInfo}\n跳过安装\n\n依赖${actionText}成功,结束时间 ${endTime.format(
'YYYY-MM-DD HH:mm:ss',
)} ${endTime.diff(startTime, 'second')} `;
this.sockService.sendMessage({
type: socketMessageType,
message: _message,
references: depIds,
});
this.updateLog(depIds, _message);
await DependenceModel.update(
{ status: DependenceStatus.installed },
{ where: { id: depIds } },
);
return resolve(null);
}
}
const cp = spawn(`${depRunCommand} ${depName}`, {
shell: '/bin/bash',
@ -187,7 +229,7 @@ export default class DependenceService {
message: data.toString(),
references: depIds,
});
await this.updateLog(depIds, data.toString());
this.updateLog(depIds, data.toString());
});
cp.stderr.on('data', async (data) => {
@ -196,7 +238,7 @@ export default class DependenceService {
message: data.toString(),
references: depIds,
});
await this.updateLog(depIds, data.toString());
this.updateLog(depIds, data.toString());
});
cp.on('error', async (err) => {
@ -205,7 +247,7 @@ export default class DependenceService {
message: JSON.stringify(err),
references: depIds,
});
await this.updateLog(depIds, JSON.stringify(err));
this.updateLog(depIds, JSON.stringify(err));
});
cp.on('close', async (code) => {
@ -221,7 +263,7 @@ export default class DependenceService {
message,
references: depIds,
});
await this.updateLog(depIds, message);
this.updateLog(depIds, message);
let status = null;
if (isSucceed) {

View File

@ -4,6 +4,7 @@ import { AuthDataType, AuthModel } from "../data/auth";
class TaskLimit {
private oneLimit = pLimit(1);
private updateLogLimit = pLimit(1);
private cpuLimit = pLimit(Math.max(os.cpus().length, 4));
constructor() {
@ -22,15 +23,15 @@ class TaskLimit {
}
public runWithCpuLimit<T>(fn: () => Promise<T>): Promise<T> {
return this.cpuLimit(() => {
return fn();
});
return this.cpuLimit(fn);
}
public runOneByOne<T>(fn: () => Promise<T>): Promise<T> {
return this.oneLimit(() => {
return fn();
});
return this.oneLimit(fn);
}
public updateDepLog<T>(fn: () => Promise<T>): Promise<T> {
return this.updateLogLimit(fn);
}
}

View File

@ -33,6 +33,7 @@ import DependenceLogModal from './logModal';
import { useOutletContext } from '@umijs/max';
import { SharedContext } from '@/layouts';
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
import dayjs from 'dayjs';
const { Text } = Typography;
const { Search } = Input;
@ -123,27 +124,20 @@ const Dependence = () => {
dataIndex: 'remark',
key: 'remark',
},
{
title: '更新时间',
key: 'updatedAt',
dataIndex: 'updatedAt',
render: (text: string) => {
return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>;
},
},
{
title: '创建时间',
key: 'timestamp',
dataIndex: 'timestamp',
render: (text: string, record: any) => {
const language = navigator.language || navigator.languages[0];
const time = record.createdAt || record.timestamp;
const date = new Date(time)
.toLocaleString(language, {
hour12: false,
})
.replace(' 24:', ' 00:');
return (
<Tooltip
placement="topLeft"
title={date}
trigger={['hover', 'click']}
>
<span>{date}</span>
</Tooltip>
);
key: 'createdAt',
dataIndex: 'createdAt',
render: (text: string) => {
return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>;
},
},
{

View File

@ -122,7 +122,7 @@ const DependenceModal = ({
name="name"
label="名称"
rules={[
{ required: true, message: '请输入依赖名称', whitespace: true },
{ required: true, message: '请输入依赖名称,支持指定版本', whitespace: true },
]}
>
<Input.TextArea