mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:00:24 +08:00
119 lines
3.6 KiB
TypeScript
119 lines
3.6 KiB
TypeScript
import type {
|
|
ExecutionCommand,
|
|
ExecutionResourcePolicy,
|
|
ExecutionSpec,
|
|
ExecutorType,
|
|
} from './execution';
|
|
import { EXECUTOR_TYPES } from './execution';
|
|
import { cloneExecutionSpec } from './executionSpec';
|
|
import type { RunDispatchCandidate } from './runDispatchCandidate';
|
|
|
|
export interface TaskExecutionSpecTemplate {
|
|
command: ExecutionCommand;
|
|
workingDirectory?: string;
|
|
environmentPolicy: ExecutionSpec['environmentPolicy'];
|
|
timeoutMs?: number;
|
|
terminationGraceMs: number;
|
|
resourcePolicy?: ExecutionResourcePolicy;
|
|
}
|
|
|
|
export interface PinnedTaskExecutionRevision {
|
|
projectId: string;
|
|
taskId: string;
|
|
taskRevision: string;
|
|
executorType: ExecutorType;
|
|
execution: TaskExecutionSpecTemplate;
|
|
/** Opaque immutable recipe for Secret, output and callback capabilities. */
|
|
contextRef: string;
|
|
}
|
|
|
|
function assertIdentifier(name: string, value: string, maximum: number): void {
|
|
if (
|
|
typeof value !== 'string' ||
|
|
value.length < 1 ||
|
|
value.length > maximum ||
|
|
/[\u0000-\u001f\u007f]/.test(value)
|
|
) {
|
|
throw new TypeError(`${name} is invalid`);
|
|
}
|
|
}
|
|
|
|
function templateSpec(
|
|
revision: PinnedTaskExecutionRevision,
|
|
identity: Pick<
|
|
ExecutionSpec,
|
|
'runId' | 'attemptId' | 'projectId' | 'taskId' | 'taskRevision'
|
|
>,
|
|
): ExecutionSpec {
|
|
const execution = revision.execution;
|
|
return {
|
|
runId: identity.runId,
|
|
attemptId: identity.attemptId,
|
|
projectId: identity.projectId,
|
|
taskId: identity.taskId,
|
|
taskRevision: identity.taskRevision,
|
|
command: execution.command,
|
|
...(execution.workingDirectory === undefined
|
|
? {}
|
|
: { workingDirectory: execution.workingDirectory }),
|
|
environmentPolicy: execution.environmentPolicy,
|
|
...(execution.timeoutMs === undefined
|
|
? {}
|
|
: { timeoutMs: execution.timeoutMs }),
|
|
terminationGraceMs: execution.terminationGraceMs,
|
|
...(execution.resourcePolicy === undefined
|
|
? {}
|
|
: { resourcePolicy: execution.resourcePolicy }),
|
|
};
|
|
}
|
|
|
|
export function assertPinnedTaskExecutionRevision(
|
|
revision: PinnedTaskExecutionRevision,
|
|
): void {
|
|
if (!revision || typeof revision !== 'object' || Array.isArray(revision)) {
|
|
throw new TypeError('Pinned Task execution revision must be an object');
|
|
}
|
|
assertIdentifier('projectId', revision.projectId, 128);
|
|
assertIdentifier('taskId', revision.taskId, 255);
|
|
assertIdentifier('taskRevision', revision.taskRevision, 128);
|
|
assertIdentifier('contextRef', revision.contextRef, 512);
|
|
if (!EXECUTOR_TYPES.includes(revision.executorType)) {
|
|
throw new TypeError('Pinned Task executorType is invalid');
|
|
}
|
|
cloneExecutionSpec(
|
|
templateSpec(revision, {
|
|
runId: 'revision-validation-run',
|
|
attemptId: 'revision-validation-attempt',
|
|
projectId: revision.projectId,
|
|
taskId: revision.taskId,
|
|
taskRevision: revision.taskRevision,
|
|
}),
|
|
);
|
|
}
|
|
|
|
export function executionSpecFromPinnedTaskRevision(
|
|
candidate: RunDispatchCandidate,
|
|
revision: PinnedTaskExecutionRevision,
|
|
): ExecutionSpec {
|
|
assertPinnedTaskExecutionRevision(revision);
|
|
if (
|
|
revision.projectId !== candidate.projectId ||
|
|
revision.taskId !== candidate.taskId ||
|
|
revision.taskRevision !== candidate.taskRevision ||
|
|
revision.executorType !== candidate.executorType
|
|
) {
|
|
throw new TypeError(
|
|
'Pinned Task execution revision does not match its dispatch candidate',
|
|
);
|
|
}
|
|
return cloneExecutionSpec(
|
|
templateSpec(revision, {
|
|
runId: candidate.runId,
|
|
attemptId: candidate.attemptId,
|
|
projectId: candidate.projectId,
|
|
taskId: candidate.taskId,
|
|
taskRevision: candidate.taskRevision,
|
|
}),
|
|
);
|
|
}
|