mirror of https://github.com/certd/certd
perf: 支持已跳过的步骤重新运行
parent
724a85028b
commit
ea775adae1
|
@ -36,6 +36,8 @@ export class Executor {
|
|||
options: ExecutorOptions;
|
||||
abort: AbortController = new AbortController();
|
||||
|
||||
_inited = false;
|
||||
|
||||
onChanged: (history: RunHistory) => Promise<void>;
|
||||
constructor(options: ExecutorOptions) {
|
||||
this.options = options;
|
||||
|
@ -50,6 +52,10 @@ export class Executor {
|
|||
}
|
||||
|
||||
async init() {
|
||||
if (this._inited) {
|
||||
return;
|
||||
}
|
||||
this._inited = true;
|
||||
const lastRuntime = await this.pipelineContext.getObj(`lastRuntime`);
|
||||
this.lastRuntime = lastRuntime;
|
||||
this.lastStatusMap = new RunnableCollection(lastRuntime?.pipeline);
|
||||
|
@ -315,4 +321,8 @@ export class Executor {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearLastStatus(stepId: string) {
|
||||
this.lastStatusMap.clearById(stepId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -166,6 +166,14 @@ export class RunnableCollection {
|
|||
});
|
||||
}
|
||||
|
||||
clearById(id: string) {
|
||||
const runnable = this.collection[id];
|
||||
if (runnable?.status) {
|
||||
runnable.status.status = ResultType.none;
|
||||
runnable.status.result = ResultType.none;
|
||||
}
|
||||
}
|
||||
|
||||
add(runnable: Runnable) {
|
||||
this.collection[runnable.id] = runnable;
|
||||
}
|
||||
|
|
|
@ -45,6 +45,10 @@ h1, h2, h3, h4, h5, h6 {
|
|||
vertical-align: 0 !important;
|
||||
}
|
||||
|
||||
.pointer{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.flex-center{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -122,3 +126,8 @@ h1, h2, h3, h4, h5, h6 {
|
|||
padding-bottom:3px;
|
||||
border-bottom: 1px solid #dedede;
|
||||
}
|
||||
|
||||
|
||||
.color-blue{
|
||||
color: #1890ff;
|
||||
}
|
|
@ -59,11 +59,11 @@ export function Save(pipelineEntity: any) {
|
|||
});
|
||||
}
|
||||
|
||||
export function Trigger(id: any) {
|
||||
export function Trigger(id: any, stepId?: string) {
|
||||
return request({
|
||||
url: apiPrefix + "/trigger",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id, stepId }
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as api from "./api";
|
|||
import { useI18n } from "vue-i18n";
|
||||
import { computed, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, useUi } from "@fast-crud/fast-crud";
|
||||
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
|
||||
import { nanoid } from "nanoid";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
|
@ -29,9 +29,16 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
|||
};
|
||||
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
form.content = JSON.stringify({
|
||||
title: form.title
|
||||
});
|
||||
if (form.content == null) {
|
||||
form.content = JSON.stringify({
|
||||
title: form.title
|
||||
});
|
||||
} else {
|
||||
const content = JSON.parse(form.content);
|
||||
content.title = form.title;
|
||||
form.content = JSON.stringify(content);
|
||||
}
|
||||
|
||||
const res = await api.AddObj(form);
|
||||
lastResRef.value = res;
|
||||
return res;
|
||||
|
@ -136,6 +143,18 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
|||
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "false" } });
|
||||
}
|
||||
},
|
||||
copy: {
|
||||
click: async (context) => {
|
||||
const { ui } = useUi();
|
||||
// @ts-ignore
|
||||
const row = context[ui.tableColumn.row];
|
||||
row.title = row.title + "_copy";
|
||||
await crudExpose.openCopy({
|
||||
row: row,
|
||||
index: context.index
|
||||
});
|
||||
}
|
||||
},
|
||||
config: {
|
||||
order: 1,
|
||||
title: null,
|
||||
|
|
|
@ -54,9 +54,9 @@ export default defineComponent({
|
|||
content: JSON.stringify(pipelineConfig)
|
||||
});
|
||||
},
|
||||
async doTrigger(options: { pipelineId: number }) {
|
||||
const { pipelineId } = options;
|
||||
await api.Trigger(pipelineId);
|
||||
async doTrigger(options: { pipelineId: number; stepId?: string }) {
|
||||
const { pipelineId, stepId } = options;
|
||||
await api.Trigger(pipelineId, stepId);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</template>
|
||||
<p>
|
||||
<fs-date-format :model-value="runnable.status?.startTime"></fs-date-format>
|
||||
<a-tag class="ml-1" :color="status.color" :closable="status.value === 'start'" @close="cancelTask">
|
||||
<a-tag class="ml-5" :color="status.color" :closable="status.value === 'start'" @close="cancelTask">
|
||||
{{ status.label }}
|
||||
</a-tag>
|
||||
<a-tag v-if="isCurrent" class="pointer" color="green" :closable="true" @close="cancel">当前</a-tag>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<span v-if="statusRef" class="pi-status-show">
|
||||
<span v-if="statusRef" class="pi-status-show flex-o">
|
||||
<template v-if="type === 'icon'">
|
||||
<fs-icon class="status-icon" v-bind="statusRef" :style="{ color: statusRef.color }" />
|
||||
</template>
|
||||
|
|
|
@ -82,10 +82,20 @@
|
|||
</div>
|
||||
<div class="task">
|
||||
<a-button shape="round" @click="taskEdit(stage, index, task, taskIndex)">
|
||||
<span class="flex-o w-100">
|
||||
<span class="ellipsis flex-1" :class="{ 'mr-15': editMode }">{{ task.title }}</span>
|
||||
<pi-status-show :status="task.status?.result"></pi-status-show>
|
||||
</span>
|
||||
<a-popover title="步骤">
|
||||
<!-- :open="true"-->
|
||||
<template #content>
|
||||
<div v-for="(item, index) of task.steps" class="flex-o w-100">
|
||||
<span class="ellipsis flex-1">{{ index + 1 }}. {{ item.title }} </span>
|
||||
<pi-status-show :status="item.status?.result"></pi-status-show>
|
||||
<fs-icon class="pointer color-blue" title="重新运行此步骤" icon="SyncOutlined" @click="run(item.id)"></fs-icon>
|
||||
</div>
|
||||
</template>
|
||||
<span class="flex-o w-100">
|
||||
<span class="ellipsis flex-1" :class="{ 'mr-15': editMode }">{{ task.title }}</span>
|
||||
<pi-status-show :status="task.status?.result"></pi-status-show>
|
||||
</span>
|
||||
</a-popover>
|
||||
</a-button>
|
||||
<fs-icon v-if="editMode" class="copy" title="复制" icon="ion:copy-outline" @click="taskCopy(stage, index, task)"></fs-icon>
|
||||
</div>
|
||||
|
@ -226,10 +236,11 @@ import { nanoid } from "nanoid";
|
|||
import { PipelineDetail, PipelineOptions, PluginGroups, RunHistory } from "./type";
|
||||
import type { Runnable } from "@certd/pipeline";
|
||||
import PiHistoryTimelineItem from "/@/views/certd/pipeline/pipeline/component/history-timeline-item.vue";
|
||||
import { FsIcon } from "@fast-crud/fast-crud";
|
||||
export default defineComponent({
|
||||
name: "PipelineEdit",
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: { PiHistoryTimelineItem, PiTaskForm, PiTriggerForm, PiTaskView, PiStatusShow, PiNotificationForm },
|
||||
components: { FsIcon, PiHistoryTimelineItem, PiTaskForm, PiTriggerForm, PiTaskView, PiStatusShow, PiNotificationForm },
|
||||
props: {
|
||||
pipelineId: {
|
||||
type: [Number, String],
|
||||
|
@ -529,7 +540,7 @@ export default defineComponent({
|
|||
|
||||
function useActions() {
|
||||
const saveLoading = ref();
|
||||
const run = async () => {
|
||||
const run = async (stepId?: string) => {
|
||||
if (props.editMode) {
|
||||
message.warn("请先保存,再运行管道");
|
||||
return;
|
||||
|
@ -549,11 +560,12 @@ export default defineComponent({
|
|||
//@ts-ignore
|
||||
await changeCurrentHistory(null);
|
||||
watchNewHistoryList();
|
||||
await props.options.doTrigger({ pipelineId: pipeline.value.id });
|
||||
await props.options.doTrigger({ pipelineId: pipeline.value.id, stepId: stepId });
|
||||
notification.success({ message: "管道已经开始运行" });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function toggleEditMode(editMode: boolean) {
|
||||
ctx.emit("update:editMode", editMode);
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ export class PluginGroups {
|
|||
}
|
||||
|
||||
export type PipelineOptions = {
|
||||
doTrigger(options: { pipelineId: number }): Promise<void>;
|
||||
doTrigger(options: { pipelineId: number; stepId?: string }): Promise<void>;
|
||||
doSave(pipelineConfig: Pipeline): Promise<void>;
|
||||
getPipelineDetail(query: { pipelineId: number }): Promise<PipelineDetail>;
|
||||
getHistoryList(query: { pipelineId: number }): Promise<RunHistory[]>;
|
||||
|
|
|
@ -87,9 +87,9 @@ export class PipelineController extends CrudController<PipelineService> {
|
|||
}
|
||||
|
||||
@Post('/trigger', { summary: Constants.per.authOnly })
|
||||
async trigger(@Query('id') id: number) {
|
||||
async trigger(@Query('id') id: number, @Query('stepId') stepId?: string) {
|
||||
await this.authService.checkEntityUserId(this.ctx, this.getService(), id);
|
||||
await this.service.trigger(id);
|
||||
await this.service.trigger(id, stepId);
|
||||
return this.ok({});
|
||||
}
|
||||
|
||||
|
|
|
@ -197,14 +197,14 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||
}
|
||||
}
|
||||
|
||||
async trigger(id) {
|
||||
async trigger(id: any, stepId?: string) {
|
||||
this.cron.register({
|
||||
name: `pipeline.${id}.trigger.once`,
|
||||
cron: null,
|
||||
job: async () => {
|
||||
logger.info('用户手动启动job');
|
||||
try {
|
||||
await this.run(id, null);
|
||||
await this.run(id, null, stepId);
|
||||
} catch (e) {
|
||||
logger.error('手动job执行失败:', e);
|
||||
}
|
||||
|
@ -279,7 +279,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||
logger.info('当前定时器数量:', this.cron.getTaskSize());
|
||||
}
|
||||
|
||||
async run(id: number, triggerId: string) {
|
||||
async run(id: number, triggerId: string, stepId?: string) {
|
||||
const entity: PipelineEntity = await this.info(id);
|
||||
|
||||
const pipeline = JSON.parse(entity.content);
|
||||
|
@ -333,6 +333,10 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||
try {
|
||||
runningTasks.set(historyId, executor);
|
||||
await executor.init();
|
||||
if (stepId) {
|
||||
// 清除该step的状态
|
||||
executor.clearLastStatus(stepId);
|
||||
}
|
||||
await executor.run(historyId, triggerType);
|
||||
} catch (e) {
|
||||
logger.error('执行失败:', e);
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import { IsAccess, AccessInput } from '@certd/pipeline';
|
||||
|
||||
@IsAccess({
|
||||
name: 'aliyun',
|
||||
title: '阿里云授权',
|
||||
desc: '',
|
||||
})
|
||||
export class AliyunAccess {
|
||||
@AccessInput({
|
||||
title: 'accessKeyId',
|
||||
component: {
|
||||
placeholder: 'accessKeyId',
|
||||
},
|
||||
helper: '登录阿里云控制台->AccessKey管理页面获取。',
|
||||
required: true,
|
||||
})
|
||||
accessKeyId = '';
|
||||
@AccessInput({
|
||||
title: 'accessKeySecret',
|
||||
component: {
|
||||
placeholder: 'accessKeySecret',
|
||||
},
|
||||
required: true,
|
||||
encrypt: true,
|
||||
helper: '注意:证书申请需要dns解析权限;其他阿里云插件,需要对应的权限,比如证书上传需要证书管理权限;嫌麻烦就用主账号的全量权限的accessKey',
|
||||
})
|
||||
accessKeySecret = '';
|
||||
}
|
||||
|
||||
new AliyunAccess();
|
|
@ -1 +0,0 @@
|
|||
export * from './aliyun-access.js';
|
|
@ -1,12 +1,7 @@
|
|||
import Core from '@alicloud/pop-core';
|
||||
import {
|
||||
AbstractDnsProvider,
|
||||
CreateRecordOptions,
|
||||
IsDnsProvider,
|
||||
RemoveRecordOptions,
|
||||
} from '@certd/plugin-cert';
|
||||
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
||||
import { Autowire, ILogger } from '@certd/pipeline';
|
||||
import { AliyunAccess } from '../access/index.js';
|
||||
import { AliyunAccess } from '@certd/plugin-plus';
|
||||
|
||||
@IsDnsProvider({
|
||||
name: 'aliyun',
|
||||
|
@ -110,11 +105,7 @@ export class AliyunDnsProvider extends AbstractDnsProvider {
|
|||
};
|
||||
|
||||
try {
|
||||
const ret = await this.client.request(
|
||||
'AddDomainRecord',
|
||||
params,
|
||||
requestOption
|
||||
);
|
||||
const ret = await this.client.request('AddDomainRecord', params, requestOption);
|
||||
this.logger.info('添加域名解析成功:', value, value, ret.RecordId);
|
||||
return ret.RecordId;
|
||||
} catch (e: any) {
|
||||
|
@ -136,11 +127,7 @@ export class AliyunDnsProvider extends AbstractDnsProvider {
|
|||
method: 'POST',
|
||||
};
|
||||
|
||||
const ret = await this.client.request(
|
||||
'DeleteDomainRecord',
|
||||
params,
|
||||
requestOption
|
||||
);
|
||||
const ret = await this.client.request('DeleteDomainRecord', params, requestOption);
|
||||
this.logger.info('删除域名解析成功:', fullRecord, value, ret.RecordId);
|
||||
return ret.RecordId;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
export * from './access/index.js';
|
||||
export * from './dns-provider/index.js';
|
||||
export * from './plugin/index.js';
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, utils } from '@certd/pipeline';
|
||||
// @ts-ignore
|
||||
import { ROAClient } from '@alicloud/pop-core';
|
||||
import { AliyunAccess } from '../../access/index.js';
|
||||
import { AliyunAccess } from '@certd/plugin-plus';
|
||||
import { appendTimeSuffix } from '../../utils/index.js';
|
||||
import { CertInfo } from '@certd/plugin-cert';
|
||||
|
||||
|
@ -113,7 +111,7 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
|
|||
console.log('开始部署证书到阿里云cdn');
|
||||
const { regionId, ingressClass, clusterId, isPrivateIpAddress, cert } = this;
|
||||
const access = (await this.accessService.getById(this.accessId)) as AliyunAccess;
|
||||
const client = this.getClient(access, regionId);
|
||||
const client = await this.getClient(access, regionId);
|
||||
const kubeConfigStr = await this.getKubeConfig(client, clusterId, isPrivateIpAddress);
|
||||
|
||||
this.logger.info('kubeconfig已成功获取');
|
||||
|
@ -200,8 +198,10 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
getClient(aliyunProvider: any, regionId: string) {
|
||||
return new ROAClient({
|
||||
async getClient(aliyunProvider: any, regionId: string) {
|
||||
const ROAClient = await import('@alicloud/pop-core');
|
||||
|
||||
return new ROAClient.default({
|
||||
accessKeyId: aliyunProvider.accessKeyId,
|
||||
accessKeySecret: aliyunProvider.accessKeySecret,
|
||||
endpoint: `https://cs.${regionId}.aliyuncs.com`,
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import dayjs from 'dayjs';
|
||||
import Core from '@alicloud/pop-core';
|
||||
import RPCClient from '@alicloud/pop-core';
|
||||
import { AliyunAccess } from '../../access/index.js';
|
||||
|
||||
import { AliyunAccess } from '@certd/plugin-plus';
|
||||
import type RPCClient from '@alicloud/pop-core';
|
||||
@IsTaskPlugin({
|
||||
name: 'DeployCertToAliyunCDN',
|
||||
title: '部署证书至阿里云CDN',
|
||||
|
@ -54,14 +52,16 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
|||
async execute(): Promise<void> {
|
||||
console.log('开始部署证书到阿里云cdn');
|
||||
const access = (await this.accessService.getById(this.accessId)) as AliyunAccess;
|
||||
const client = this.getClient(access);
|
||||
const client = await this.getClient(access);
|
||||
const params = await this.buildParams();
|
||||
await this.doRequest(client, params);
|
||||
console.log('部署完成');
|
||||
}
|
||||
|
||||
getClient(access: AliyunAccess) {
|
||||
return new Core({
|
||||
async getClient(access: AliyunAccess) {
|
||||
const Core = await import('@alicloud/pop-core');
|
||||
|
||||
return new Core.default({
|
||||
accessKeyId: access.accessKeyId,
|
||||
accessKeySecret: access.accessKeySecret,
|
||||
endpoint: 'https://cdn.aliyuncs.com',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
|
||||
import Core from '@alicloud/pop-core';
|
||||
import { AliyunAccess } from '../../access/index.js';
|
||||
import { AliyunAccess } from '@certd/plugin-plus';
|
||||
import { appendTimeSuffix, checkRet, ZoneOptions } from '../../utils/index.js';
|
||||
|
||||
@IsTaskPlugin({
|
||||
|
|
|
@ -29,6 +29,7 @@ export class HostShellExecutePlugin extends AbstractTaskPlugin {
|
|||
component: {
|
||||
name: 'a-textarea',
|
||||
vModel: 'value',
|
||||
rows: 6,
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue