feat(ql3): add opaque cluster environment bundle delivery

This commit is contained in:
whyour
2026-08-24 19:31:03 +08:00
parent cf2c0ec7b3
commit 4abf125ce9
36 changed files with 1682 additions and 382 deletions
@@ -77,9 +77,7 @@ function unavailable(): TaskDefinitionUnavailableError {
return new TaskDefinitionUnavailableError();
}
function taskDefinitionRecord(
row: TaskDefinitionRow,
): TaskDefinitionRecord {
function taskDefinitionRecord(row: TaskDefinitionRow): TaskDefinitionRecord {
try {
const description = row.description;
if (description !== null && typeof description !== 'string') {
@@ -128,6 +126,9 @@ function executionPlanJson(
return Object.freeze({
command: revision.command,
environment: revision.environment,
...(revision.environmentBundleRef === undefined
? {}
: { environmentBundleRef: revision.environmentBundleRef }),
...(revision.workingDirectory === undefined
? {}
: { workingDirectory: revision.workingDirectory }),
@@ -140,7 +141,9 @@ function executionPlanJson(
});
}
function executionRevision(row: TaskDefinitionRow): ClusterTaskExecutionRevision {
function executionRevision(
row: TaskDefinitionRow,
): ClusterTaskExecutionRevision {
try {
const plan = postgresRequiredJsonObject(row.planJson, unavailable);
const keys = Object.keys(plan);
@@ -149,9 +152,14 @@ function executionRevision(row: TaskDefinitionRow): ClusterTaskExecutionRevision
!keys.includes('environment') ||
keys.some(
(key) =>
!['command', 'environment', 'placement', 'timeoutMs', 'workingDirectory'].includes(
key,
),
![
'command',
'environment',
'environmentBundleRef',
'placement',
'timeoutMs',
'workingDirectory',
].includes(key),
)
) {
throw unavailable();
@@ -176,6 +184,9 @@ function executionRevision(row: TaskDefinitionRow): ClusterTaskExecutionRevision
command: plan.command as ClusterTaskExecutionRevision['command'],
environment:
plan.environment as ClusterTaskExecutionRevision['environment'],
...(plan.environmentBundleRef === undefined
? {}
: { environmentBundleRef: plan.environmentBundleRef as string }),
...(plan.workingDirectory === undefined
? {}
: { workingDirectory: plan.workingDirectory as string }),
@@ -185,10 +196,9 @@ function executionRevision(row: TaskDefinitionRow): ClusterTaskExecutionRevision
...(plan.placement === undefined
? {}
: {
placement:
plan.placement as unknown as NonNullable<
ClusterTaskExecutionRevision['placement']
>,
placement: plan.placement as unknown as NonNullable<
ClusterTaskExecutionRevision['placement']
>,
}),
contentDigest: postgresRequiredString(row.contentDigest, unavailable),
createdAtMs: postgresRequiredInteger(row.createdAtMs, unavailable),
@@ -319,7 +329,6 @@ export class PostgresTaskDefinitionSource implements TaskDefinitionSource {
}
}
async findCurrentTaskDefinition(
projectId: string,
taskId: string,
@@ -422,7 +431,9 @@ export class PostgresTaskExecutionRevisionSource
readonly sourceRevision: number;
}): Promise<ClusterTaskExecutionRevision | null> {
if (!identity || typeof identity !== 'object' || Array.isArray(identity)) {
throw new TypeError('Cluster Task execution revision identity is invalid');
throw new TypeError(
'Cluster Task execution revision identity is invalid',
);
}
assertTaskDefinitionIdentifier(identity.projectId, 'projectId');
assertTaskDefinitionIdentifier(identity.taskId, 'taskId');
@@ -447,8 +458,7 @@ export class PostgresTaskDefinitionRepository
{
constructor(
pool: PostgresPool,
private readonly semanticRegistry: TaskSpecSemanticRegistry =
createBuiltInTaskSpecSemanticRegistry(),
private readonly semanticRegistry: TaskSpecSemanticRegistry = createBuiltInTaskSpecSemanticRegistry(),
) {
super(pool);
}
@@ -483,10 +493,7 @@ export class PostgresTaskDefinitionRepository
command.kind === 'command' &&
command.spec.schema === BUILT_IN_COMMAND_TASK_SPEC_SCHEMA
? compileClusterCommandTaskDefinition(
createTaskDefinitionRecord(
command,
command.occurredAtMs,
),
createTaskDefinitionRecord(command, command.occurredAtMs),
this.semanticRegistry,
)
: null;
@@ -560,10 +567,7 @@ export class PostgresTaskDefinitionRepository
WHERE id = $1`,
[command.projectId],
);
if (
project.rows.length !== 1 ||
project.rows[0]?.status !== 'active'
) {
if (project.rows.length !== 1 || project.rows[0]?.status !== 'active') {
throw new TaskDefinitionConflictError();
}
@@ -600,8 +604,9 @@ export class PostgresTaskDefinitionRepository
unavailable,
);
if (
(created ? command.expectedRevision !== null :
currentRevision !== command.expectedRevision) ||
(created
? command.expectedRevision !== null
: currentRevision !== command.expectedRevision) ||
command.occurredAtMs < previousUpdatedAtMs
) {
throw new TaskDefinitionConflictError();
@@ -27,7 +27,8 @@ function text(row: Row, key: string): string {
function integer(row: Row, key: string): number {
const raw = row[key];
const value = typeof raw === 'string' && /^\d+$/.test(raw) ? Number(raw) : raw;
const value =
typeof raw === 'string' && /^\d+$/.test(raw) ? Number(raw) : raw;
if (typeof value !== 'number' || !Number.isSafeInteger(value)) {
throw new RemoteWorkerSecretDeliveryUnavailableError();
}
@@ -44,10 +45,19 @@ function executionRevision(row: Row): ClusterTaskExecutionRevision {
if (
!keys.includes('command') ||
!keys.includes('environment') ||
keys.some((key) =>
!['command', 'environment', 'placement', 'timeoutMs', 'workingDirectory']
.includes(key))
) throw new RemoteWorkerSecretDeliveryUnavailableError();
keys.some(
(key) =>
![
'command',
'environment',
'environmentBundleRef',
'placement',
'timeoutMs',
'workingDirectory',
].includes(key),
)
)
throw new RemoteWorkerSecretDeliveryUnavailableError();
return normalizeClusterTaskExecutionRevision({
projectId: text(row, 'revisionProjectId'),
taskId: text(row, 'revisionTaskId'),
@@ -57,7 +67,11 @@ function executionRevision(row: Row): ClusterTaskExecutionRevision {
executorType: text(row, 'revisionExecutorType') as 'remote_worker',
planSchema: text(row, 'planSchema') as 'qinglong/command-execution@v1',
command: value.command as ClusterTaskExecutionRevision['command'],
environment: value.environment as ClusterTaskExecutionRevision['environment'],
environment:
value.environment as ClusterTaskExecutionRevision['environment'],
...(value.environmentBundleRef === undefined
? {}
: { environmentBundleRef: value.environmentBundleRef as string }),
...(value.workingDirectory === undefined
? {}
: { workingDirectory: value.workingDirectory as string }),
@@ -66,7 +80,11 @@ function executionRevision(row: Row): ClusterTaskExecutionRevision {
: { timeoutMs: value.timeoutMs as number }),
...(value.placement === undefined
? {}
: { placement: value.placement as NonNullable<ClusterTaskExecutionRevision['placement']> }),
: {
placement: value.placement as NonNullable<
ClusterTaskExecutionRevision['placement']
>,
}),
contentDigest: text(row, 'revisionContentDigest'),
createdAtMs: integer(row, 'revisionCreatedAtMs'),
});
@@ -80,7 +98,8 @@ async function begin(client: PostgresClient): Promise<void> {
}
export class PostgresRemoteWorkerSecretDeliveryAuthorityRepository
implements RemoteWorkerSecretDeliveryAuthorityRepository {
implements RemoteWorkerSecretDeliveryAuthorityRepository
{
constructor(private readonly pool: PostgresPool) {
if (!pool || typeof pool.connect !== 'function') {
throw new TypeError('PostgreSQL remote Secret delivery pool is invalid');
@@ -156,7 +175,9 @@ export class PostgresRemoteWorkerSecretDeliveryAuthorityRepository
}
const row = result.rows[0];
if (!row) {
throw new RemoteWorkerSecretDeliveryFenceRejectedError('authority_mismatch');
throw new RemoteWorkerSecretDeliveryFenceRejectedError(
'authority_mismatch',
);
}
const observedAtMs = integer(row, 'observedAtMs');
const tokenDigest = digestRunDispatchLeaseToken(command.leaseToken);
@@ -192,25 +213,38 @@ export class PostgresRemoteWorkerSecretDeliveryAuthorityRepository
row.leaseOfferId === command.offerId &&
integer(row, 'leaseExpiresAtMs') > observedAtMs;
if (!matches) {
throw new RemoteWorkerSecretDeliveryFenceRejectedError('authority_mismatch');
throw new RemoteWorkerSecretDeliveryFenceRejectedError(
'authority_mismatch',
);
}
let revision: ClusterTaskExecutionRevision;
try {
revision = executionRevision(row);
} catch (error) {
if (error instanceof RemoteWorkerSecretDeliveryUnavailableError) throw error;
if (error instanceof RemoteWorkerSecretDeliveryUnavailableError)
throw error;
throw new RemoteWorkerSecretDeliveryUnavailableError();
}
const expectedRefs = Object.freeze([
...new Set(revision.environment.flatMap((binding) =>
binding.kind === 'secret' ? [binding.secretRef] : [])),
...new Set(
revision.environment.flatMap((binding) =>
binding.kind === 'secret' ? [binding.secretRef] : [],
),
),
]);
const expectedEnvironmentBundleRefs = Object.freeze(
revision.environmentBundleRef === undefined
? []
: [revision.environmentBundleRef],
);
if (
revision.projectId !== command.projectId ||
revision.taskId !== command.taskId ||
revision.taskRevision !== command.taskRevision ||
revision.contentDigest !== command.executionDigest ||
JSON.stringify(expectedRefs) !== JSON.stringify(command.secretRefs)
JSON.stringify(expectedRefs) !== JSON.stringify(command.secretRefs) ||
JSON.stringify(expectedEnvironmentBundleRefs) !==
JSON.stringify(command.environmentBundleRefs)
) {
throw new RemoteWorkerSecretDeliveryFenceRejectedError(
'secret_scope_mismatch',
@@ -230,15 +264,21 @@ export class PostgresRemoteWorkerSecretDeliveryAuthorityRepository
leaseGeneration: command.leaseGeneration,
leaseVersion: command.expectedLeaseVersion,
secretRefs: expectedRefs,
environmentBundleRefs: expectedEnvironmentBundleRefs,
});
await client.query('COMMIT');
return authority;
} catch (error) {
try { await client.query('ROLLBACK'); } catch { /* preserve root */ }
try {
await client.query('ROLLBACK');
} catch {
/* preserve root */
}
if (
error instanceof RemoteWorkerSecretDeliveryFenceRejectedError ||
error instanceof RemoteWorkerSecretDeliveryUnavailableError
) throw error;
)
throw error;
throw new RemoteWorkerSecretDeliveryUnavailableError();
} finally {
client.release();