Files
qinglong/back/migrations/0023-approved-run-action-receipts.ts
T

189 lines
6.5 KiB
TypeScript

import { createHash } from 'crypto';
import { DataTypes, Op } from 'sequelize';
import {
APPROVED_RUN_ACTION_TYPE,
APPROVED_RUN_RECEIPT_RESULT_CODE,
APPROVED_RUN_RECEIPT_SCHEMA_VERSION,
} from '../runtime/domain/approvedRunAction';
import { PROJECT_TABLE } from './0017-project-policy';
import {
APPROVAL_REQUEST_TABLE,
APPROVED_ACTION_DISPATCH_TABLE,
} from './0020-approval-requests';
import { RUN_TABLE } from './0002-run-schema';
import type { Migration } from './types';
export const APPROVED_RUN_ACTION_RECEIPT_TABLE = 'ApprovedRunActionReceipts';
export const APPROVED_RUN_ACTION_RECEIPT_PROJECT_INDEX =
'approved_run_receipt_project_idx';
export const APPROVED_RUN_ACTION_RECEIPT_RESOURCE_UNIQUE_INDEX =
'approved_run_receipt_resource_uidx';
const columns = [
'dispatch_id',
'approval_request_id',
'project_id',
'schema_version',
'action_type',
'action_digest',
'execution_attempt',
'execution_version',
'started_at_ms',
'idempotency_key',
'outcome',
'result_code',
'resource_type',
'resource_id',
'finished_at_ms',
'evidence_digest',
'created_at_ms',
];
const manifest = {
table: APPROVED_RUN_ACTION_RECEIPT_TABLE,
columns,
indexes: [
`${APPROVED_RUN_ACTION_RECEIPT_PROJECT_INDEX}(project_id,created_at_ms,dispatch_id)`,
`${APPROVED_RUN_ACTION_RECEIPT_RESOURCE_UNIQUE_INDEX}(resource_type,resource_id)`,
],
constraints: [
'approved_run_receipt_schema_version_check',
'approved_run_receipt_action_type_check',
'approved_run_receipt_execution_attempt_check',
'approved_run_receipt_execution_version_check',
'approved_run_receipt_idempotency_check',
'approved_run_receipt_outcome_check',
'approved_run_receipt_result_code_check',
'approved_run_receipt_resource_type_check',
'approved_run_receipt_timestamps_check',
],
};
export const approvedRunActionReceiptManifest = manifest;
export const approvedRunActionReceiptMigration: Migration = {
id: '0023-approved-run-action-receipts',
checksum: createHash('sha256').update(JSON.stringify(manifest)).digest('hex'),
async up({ queryInterface, transaction }) {
await queryInterface.createTable(
APPROVED_RUN_ACTION_RECEIPT_TABLE,
{
dispatch_id: {
type: DataTypes.STRING(64),
allowNull: false,
primaryKey: true,
references: { model: APPROVED_ACTION_DISPATCH_TABLE, key: 'id' },
onDelete: 'RESTRICT',
onUpdate: 'CASCADE',
},
approval_request_id: {
type: DataTypes.STRING(64),
allowNull: false,
references: { model: APPROVAL_REQUEST_TABLE, key: 'id' },
onDelete: 'RESTRICT',
onUpdate: 'CASCADE',
},
project_id: {
type: DataTypes.STRING(128),
allowNull: false,
references: { model: PROJECT_TABLE, key: 'id' },
onDelete: 'RESTRICT',
onUpdate: 'CASCADE',
},
schema_version: { type: DataTypes.INTEGER, allowNull: false },
action_type: { type: DataTypes.STRING(64), allowNull: false },
action_digest: { type: DataTypes.STRING(64), allowNull: false },
execution_attempt: { type: DataTypes.INTEGER, allowNull: false },
execution_version: { type: DataTypes.INTEGER, allowNull: false },
started_at_ms: { type: DataTypes.BIGINT, allowNull: false },
idempotency_key: { type: DataTypes.STRING(64), allowNull: false },
outcome: { type: DataTypes.STRING(16), allowNull: false },
result_code: { type: DataTypes.STRING(64), allowNull: false },
resource_type: { type: DataTypes.STRING(16), allowNull: false },
resource_id: {
type: DataTypes.STRING(64),
allowNull: false,
references: { model: RUN_TABLE, key: 'id' },
onDelete: 'RESTRICT',
onUpdate: 'CASCADE',
},
finished_at_ms: { type: DataTypes.BIGINT, allowNull: false },
evidence_digest: { type: DataTypes.STRING(64), allowNull: false },
created_at_ms: { type: DataTypes.BIGINT, allowNull: false },
},
{ transaction },
);
for (const [field, value, name] of [
[
'schema_version',
APPROVED_RUN_RECEIPT_SCHEMA_VERSION,
'approved_run_receipt_schema_version_check',
],
[
'action_type',
APPROVED_RUN_ACTION_TYPE,
'approved_run_receipt_action_type_check',
],
['outcome', 'succeeded', 'approved_run_receipt_outcome_check'],
[
'result_code',
APPROVED_RUN_RECEIPT_RESULT_CODE,
'approved_run_receipt_result_code_check',
],
['resource_type', 'run', 'approved_run_receipt_resource_type_check'],
] as const) {
await queryInterface.addConstraint(APPROVED_RUN_ACTION_RECEIPT_TABLE, {
fields: [field],
type: 'check',
where: { [field]: value },
name,
transaction,
});
}
for (const [field, maximum, name] of [
['execution_attempt', 16, 'approved_run_receipt_execution_attempt_check'],
[
'execution_version',
2_147_483_647,
'approved_run_receipt_execution_version_check',
],
] as const) {
await queryInterface.addConstraint(APPROVED_RUN_ACTION_RECEIPT_TABLE, {
fields: [field],
type: 'check',
where: { [field]: { [Op.between]: [1, maximum] } },
name,
transaction,
});
}
await queryInterface.addConstraint(APPROVED_RUN_ACTION_RECEIPT_TABLE, {
fields: ['dispatch_id', 'idempotency_key'],
type: 'check',
where: { idempotency_key: { [Op.eq]: { [Op.col]: 'dispatch_id' } } },
name: 'approved_run_receipt_idempotency_check',
transaction,
});
await queryInterface.addConstraint(APPROVED_RUN_ACTION_RECEIPT_TABLE, {
fields: ['started_at_ms', 'finished_at_ms', 'created_at_ms'],
type: 'check',
where: {
finished_at_ms: { [Op.gte]: { [Op.col]: 'started_at_ms' } },
created_at_ms: { [Op.eq]: { [Op.col]: 'finished_at_ms' } },
},
name: 'approved_run_receipt_timestamps_check',
transaction,
});
await queryInterface.addIndex(APPROVED_RUN_ACTION_RECEIPT_TABLE, {
fields: ['project_id', 'created_at_ms', 'dispatch_id'],
name: APPROVED_RUN_ACTION_RECEIPT_PROJECT_INDEX,
transaction,
});
await queryInterface.addIndex(APPROVED_RUN_ACTION_RECEIPT_TABLE, {
fields: ['resource_type', 'resource_id'],
unique: true,
name: APPROVED_RUN_ACTION_RECEIPT_RESOURCE_UNIQUE_INDEX,
transaction,
});
},
};