mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
448 lines
15 KiB
TypeScript
448 lines
15 KiB
TypeScript
import { createHash } from 'crypto';
|
|
import { DataTypes, Op } from 'sequelize';
|
|
import {
|
|
APPROVED_ACTION_RECOVERY_CONTROL_STATUSES,
|
|
APPROVED_ACTION_RECOVERY_DECISIONS,
|
|
APPROVED_ACTION_RECOVERY_FINDINGS,
|
|
APPROVED_ACTION_RECOVERY_SOURCES,
|
|
MAX_APPROVED_ACTION_RECOVERY_FINDINGS,
|
|
MAX_APPROVED_ACTION_RECOVERY_VERSION,
|
|
} from '../runtime/domain/approvedActionRecovery';
|
|
import { PROJECT_TABLE } from './0017-project-policy';
|
|
import { APPROVED_ACTION_DISPATCH_EXECUTION_TABLE } from './0021-approved-action-dispatch-executions';
|
|
import type { Migration } from './types';
|
|
|
|
export const APPROVED_ACTION_RECOVERY_CONTROL_TABLE =
|
|
'ApprovedActionRecoveryControls';
|
|
export const APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE =
|
|
'ApprovedActionRecoveryResolutions';
|
|
export const APPROVED_ACTION_RECOVERY_DUE_INDEX =
|
|
'approved_action_recovery_due_idx';
|
|
export const APPROVED_ACTION_RECOVERY_PROJECT_INDEX =
|
|
'approved_action_recovery_project_idx';
|
|
export const APPROVED_ACTION_RECOVERY_LEASE_INDEX =
|
|
'approved_action_recovery_lease_idx';
|
|
export const APPROVED_ACTION_RECOVERY_RESOLUTION_MUTATION_INDEX =
|
|
'approved_action_recovery_resolution_mutation_uidx';
|
|
export const APPROVED_ACTION_RECOVERY_RESOLUTION_PROJECT_INDEX =
|
|
'approved_action_recovery_resolution_project_idx';
|
|
|
|
const controlColumns = [
|
|
'dispatch_id',
|
|
'project_id',
|
|
'execution_version',
|
|
'status',
|
|
'version',
|
|
'next_scan_at_ms',
|
|
'lease_owner',
|
|
'lease_token',
|
|
'lease_expires_at_ms',
|
|
'finding_count',
|
|
'last_finding_mutation_id',
|
|
'last_finding',
|
|
'last_result_code',
|
|
'last_evidence_digest',
|
|
'resolution_mutation_id',
|
|
'created_at_ms',
|
|
'updated_at_ms',
|
|
];
|
|
|
|
const resolutionColumns = [
|
|
'dispatch_id',
|
|
'project_id',
|
|
'execution_version',
|
|
'mutation_id',
|
|
'source',
|
|
'decision',
|
|
'evidence_digest',
|
|
'reason_code',
|
|
'resolved_by_type',
|
|
'resolved_by_id',
|
|
'resolved_at_ms',
|
|
];
|
|
|
|
const constraints = [
|
|
'approved_action_recovery_status_check',
|
|
'approved_action_recovery_execution_version_check',
|
|
'approved_action_recovery_version_check',
|
|
'approved_action_recovery_finding_count_check',
|
|
'approved_action_recovery_lease_tuple_check',
|
|
'approved_action_recovery_finding_tuple_check',
|
|
'approved_action_recovery_finding_value_check',
|
|
'approved_action_recovery_evidence_digest_check',
|
|
'approved_action_recovery_resolution_mutation_tuple_check',
|
|
'approved_action_recovery_timestamps_check',
|
|
'approved_action_recovery_resolution_execution_version_check',
|
|
'approved_action_recovery_resolution_source_check',
|
|
'approved_action_recovery_resolution_decision_check',
|
|
'approved_action_recovery_resolution_actor_tuple_check',
|
|
'approved_action_recovery_resolution_source_tuple_check',
|
|
'approved_action_recovery_resolution_evidence_digest_check',
|
|
'approved_action_recovery_resolution_time_check',
|
|
];
|
|
|
|
const manifest = {
|
|
tables: {
|
|
ApprovedActionRecoveryControls: controlColumns,
|
|
ApprovedActionRecoveryResolutions: resolutionColumns,
|
|
},
|
|
indexes: [
|
|
`${APPROVED_ACTION_RECOVERY_DUE_INDEX}(status,next_scan_at_ms,dispatch_id)`,
|
|
`${APPROVED_ACTION_RECOVERY_PROJECT_INDEX}(project_id,status,created_at_ms,dispatch_id)`,
|
|
`${APPROVED_ACTION_RECOVERY_LEASE_INDEX}(status,lease_expires_at_ms,dispatch_id)`,
|
|
`${APPROVED_ACTION_RECOVERY_RESOLUTION_MUTATION_INDEX}(mutation_id) UNIQUE`,
|
|
`${APPROVED_ACTION_RECOVERY_RESOLUTION_PROJECT_INDEX}(project_id,resolved_at_ms,dispatch_id)`,
|
|
],
|
|
constraints,
|
|
};
|
|
|
|
export const approvedActionRecoveryManifest = manifest;
|
|
|
|
export const approvedActionRecoveryMigration: Migration = {
|
|
id: '0022-approved-action-recovery',
|
|
checksum: createHash('sha256').update(JSON.stringify(manifest)).digest('hex'),
|
|
async up({ queryInterface, transaction }) {
|
|
await queryInterface.createTable(
|
|
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
|
{
|
|
dispatch_id: {
|
|
type: DataTypes.STRING(64),
|
|
allowNull: false,
|
|
primaryKey: true,
|
|
references: {
|
|
model: APPROVED_ACTION_DISPATCH_EXECUTION_TABLE,
|
|
key: 'dispatch_id',
|
|
},
|
|
onDelete: 'CASCADE',
|
|
onUpdate: 'CASCADE',
|
|
},
|
|
project_id: {
|
|
type: DataTypes.STRING(128),
|
|
allowNull: false,
|
|
references: { model: PROJECT_TABLE, key: 'id' },
|
|
onDelete: 'CASCADE',
|
|
onUpdate: 'CASCADE',
|
|
},
|
|
execution_version: { type: DataTypes.INTEGER, allowNull: false },
|
|
status: { type: DataTypes.STRING(24), allowNull: false },
|
|
version: { type: DataTypes.INTEGER, allowNull: false },
|
|
next_scan_at_ms: { type: DataTypes.BIGINT, allowNull: true },
|
|
lease_owner: { type: DataTypes.STRING(128), allowNull: true },
|
|
lease_token: { type: DataTypes.STRING(128), allowNull: true },
|
|
lease_expires_at_ms: { type: DataTypes.BIGINT, allowNull: true },
|
|
finding_count: { type: DataTypes.INTEGER, allowNull: false },
|
|
last_finding_mutation_id: {
|
|
type: DataTypes.STRING(64),
|
|
allowNull: true,
|
|
},
|
|
last_finding: { type: DataTypes.STRING(24), allowNull: true },
|
|
last_result_code: { type: DataTypes.STRING(64), allowNull: true },
|
|
last_evidence_digest: { type: DataTypes.STRING(64), allowNull: true },
|
|
resolution_mutation_id: {
|
|
type: DataTypes.STRING(64),
|
|
allowNull: true,
|
|
},
|
|
created_at_ms: { type: DataTypes.BIGINT, allowNull: false },
|
|
updated_at_ms: { type: DataTypes.BIGINT, allowNull: false },
|
|
},
|
|
{ transaction },
|
|
);
|
|
await queryInterface.createTable(
|
|
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
|
{
|
|
dispatch_id: {
|
|
type: DataTypes.STRING(64),
|
|
allowNull: false,
|
|
primaryKey: true,
|
|
references: {
|
|
model: APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
|
key: 'dispatch_id',
|
|
},
|
|
onDelete: 'CASCADE',
|
|
onUpdate: 'CASCADE',
|
|
},
|
|
project_id: {
|
|
type: DataTypes.STRING(128),
|
|
allowNull: false,
|
|
references: { model: PROJECT_TABLE, key: 'id' },
|
|
onDelete: 'CASCADE',
|
|
onUpdate: 'CASCADE',
|
|
},
|
|
execution_version: { type: DataTypes.INTEGER, allowNull: false },
|
|
mutation_id: { type: DataTypes.STRING(64), allowNull: false },
|
|
source: { type: DataTypes.STRING(24), allowNull: false },
|
|
decision: { type: DataTypes.STRING(24), allowNull: false },
|
|
evidence_digest: { type: DataTypes.STRING(64), allowNull: true },
|
|
reason_code: { type: DataTypes.STRING(64), allowNull: false },
|
|
resolved_by_type: { type: DataTypes.STRING(32), allowNull: true },
|
|
resolved_by_id: { type: DataTypes.STRING(255), allowNull: true },
|
|
resolved_at_ms: { type: DataTypes.BIGINT, allowNull: false },
|
|
},
|
|
{ transaction },
|
|
);
|
|
|
|
for (const [table, field, minimum, maximum, name] of [
|
|
[
|
|
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
|
'execution_version',
|
|
1,
|
|
MAX_APPROVED_ACTION_RECOVERY_VERSION,
|
|
'approved_action_recovery_execution_version_check',
|
|
],
|
|
[
|
|
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
|
'version',
|
|
0,
|
|
MAX_APPROVED_ACTION_RECOVERY_VERSION,
|
|
'approved_action_recovery_version_check',
|
|
],
|
|
[
|
|
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
|
'finding_count',
|
|
0,
|
|
MAX_APPROVED_ACTION_RECOVERY_FINDINGS,
|
|
'approved_action_recovery_finding_count_check',
|
|
],
|
|
[
|
|
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
|
'execution_version',
|
|
1,
|
|
MAX_APPROVED_ACTION_RECOVERY_VERSION,
|
|
'approved_action_recovery_resolution_execution_version_check',
|
|
],
|
|
] as const) {
|
|
await queryInterface.addConstraint(table, {
|
|
fields: [field],
|
|
type: 'check',
|
|
where: { [field]: { [Op.between]: [minimum, maximum] } },
|
|
name,
|
|
transaction,
|
|
});
|
|
}
|
|
for (const [table, field, values, name] of [
|
|
[
|
|
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
|
'status',
|
|
APPROVED_ACTION_RECOVERY_CONTROL_STATUSES,
|
|
'approved_action_recovery_status_check',
|
|
],
|
|
[
|
|
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
|
'last_finding',
|
|
APPROVED_ACTION_RECOVERY_FINDINGS,
|
|
'approved_action_recovery_finding_value_check',
|
|
],
|
|
[
|
|
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
|
'source',
|
|
APPROVED_ACTION_RECOVERY_SOURCES,
|
|
'approved_action_recovery_resolution_source_check',
|
|
],
|
|
[
|
|
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
|
'decision',
|
|
APPROVED_ACTION_RECOVERY_DECISIONS,
|
|
'approved_action_recovery_resolution_decision_check',
|
|
],
|
|
] as const) {
|
|
await queryInterface.addConstraint(table, {
|
|
fields: [field],
|
|
type: 'check',
|
|
where: { [field]: { [Op.in]: values } },
|
|
name,
|
|
transaction,
|
|
});
|
|
}
|
|
await queryInterface.addConstraint(APPROVED_ACTION_RECOVERY_CONTROL_TABLE, {
|
|
fields: ['lease_owner', 'lease_token', 'lease_expires_at_ms'],
|
|
type: 'check',
|
|
where: {
|
|
[Op.or]: [
|
|
{ lease_owner: null, lease_token: null, lease_expires_at_ms: null },
|
|
{
|
|
lease_owner: { [Op.not]: null },
|
|
lease_token: { [Op.not]: null },
|
|
lease_expires_at_ms: { [Op.not]: null },
|
|
},
|
|
],
|
|
},
|
|
name: 'approved_action_recovery_lease_tuple_check',
|
|
transaction,
|
|
});
|
|
await queryInterface.addConstraint(APPROVED_ACTION_RECOVERY_CONTROL_TABLE, {
|
|
fields: ['last_finding_mutation_id', 'last_finding', 'last_result_code'],
|
|
type: 'check',
|
|
where: {
|
|
[Op.or]: [
|
|
{
|
|
last_finding_mutation_id: null,
|
|
last_finding: null,
|
|
last_result_code: null,
|
|
},
|
|
{
|
|
last_finding_mutation_id: { [Op.not]: null },
|
|
last_finding: { [Op.not]: null },
|
|
last_result_code: { [Op.not]: null },
|
|
},
|
|
],
|
|
},
|
|
name: 'approved_action_recovery_finding_tuple_check',
|
|
transaction,
|
|
});
|
|
for (const [table, field, name] of [
|
|
[
|
|
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
|
'last_evidence_digest',
|
|
'approved_action_recovery_evidence_digest_check',
|
|
],
|
|
[
|
|
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
|
'evidence_digest',
|
|
'approved_action_recovery_resolution_evidence_digest_check',
|
|
],
|
|
] as const) {
|
|
await queryInterface.addConstraint(table, {
|
|
fields: [field],
|
|
type: 'check',
|
|
where: {
|
|
[Op.or]: [
|
|
{ [field]: null },
|
|
queryInterface.sequelize.where(
|
|
queryInterface.sequelize.fn(
|
|
'length',
|
|
queryInterface.sequelize.col(field),
|
|
),
|
|
64,
|
|
),
|
|
],
|
|
},
|
|
name,
|
|
transaction,
|
|
});
|
|
}
|
|
await queryInterface.addConstraint(APPROVED_ACTION_RECOVERY_CONTROL_TABLE, {
|
|
fields: ['status', 'resolution_mutation_id'],
|
|
type: 'check',
|
|
where: {
|
|
[Op.or]: [
|
|
{ status: 'resolved', resolution_mutation_id: { [Op.not]: null } },
|
|
{
|
|
status: { [Op.not]: 'resolved' },
|
|
resolution_mutation_id: null,
|
|
},
|
|
],
|
|
},
|
|
name: 'approved_action_recovery_resolution_mutation_tuple_check',
|
|
transaction,
|
|
});
|
|
await queryInterface.addConstraint(APPROVED_ACTION_RECOVERY_CONTROL_TABLE, {
|
|
fields: ['created_at_ms', 'updated_at_ms'],
|
|
type: 'check',
|
|
where: {
|
|
created_at_ms: { [Op.gte]: 0 },
|
|
updated_at_ms: { [Op.gte]: { [Op.col]: 'created_at_ms' } },
|
|
},
|
|
name: 'approved_action_recovery_timestamps_check',
|
|
transaction,
|
|
});
|
|
await queryInterface.addConstraint(
|
|
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
|
{
|
|
fields: ['resolved_by_type', 'resolved_by_id'],
|
|
type: 'check',
|
|
where: {
|
|
[Op.or]: [
|
|
{ resolved_by_type: null, resolved_by_id: null },
|
|
{
|
|
resolved_by_type: 'user',
|
|
resolved_by_id: { [Op.not]: null },
|
|
},
|
|
],
|
|
},
|
|
name: 'approved_action_recovery_resolution_actor_tuple_check',
|
|
transaction,
|
|
},
|
|
);
|
|
await queryInterface.addConstraint(
|
|
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
|
{
|
|
fields: ['source', 'decision', 'evidence_digest', 'resolved_by_type'],
|
|
type: 'check',
|
|
where: {
|
|
[Op.or]: [
|
|
{
|
|
source: 'automatic_evidence',
|
|
decision: {
|
|
[Op.in]: ['confirm_succeeded', 'confirm_failed'],
|
|
},
|
|
evidence_digest: { [Op.not]: null },
|
|
resolved_by_type: null,
|
|
},
|
|
{ source: 'human', resolved_by_type: 'user' },
|
|
],
|
|
},
|
|
name: 'approved_action_recovery_resolution_source_tuple_check',
|
|
transaction,
|
|
},
|
|
);
|
|
await queryInterface.addConstraint(
|
|
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
|
{
|
|
fields: ['resolved_at_ms'],
|
|
type: 'check',
|
|
where: { resolved_at_ms: { [Op.gte]: 0 } },
|
|
name: 'approved_action_recovery_resolution_time_check',
|
|
transaction,
|
|
},
|
|
);
|
|
|
|
await queryInterface.addIndex(
|
|
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
|
['status', 'next_scan_at_ms', 'dispatch_id'],
|
|
{ name: APPROVED_ACTION_RECOVERY_DUE_INDEX, transaction },
|
|
);
|
|
await queryInterface.addIndex(APPROVED_ACTION_RECOVERY_CONTROL_TABLE, {
|
|
fields: ['project_id', 'status', 'created_at_ms', 'dispatch_id'],
|
|
name: APPROVED_ACTION_RECOVERY_PROJECT_INDEX,
|
|
transaction,
|
|
});
|
|
await queryInterface.addIndex(APPROVED_ACTION_RECOVERY_CONTROL_TABLE, {
|
|
fields: ['status', 'lease_expires_at_ms', 'dispatch_id'],
|
|
name: APPROVED_ACTION_RECOVERY_LEASE_INDEX,
|
|
transaction,
|
|
});
|
|
await queryInterface.addIndex(
|
|
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
|
['mutation_id'],
|
|
{
|
|
name: APPROVED_ACTION_RECOVERY_RESOLUTION_MUTATION_INDEX,
|
|
unique: true,
|
|
transaction,
|
|
},
|
|
);
|
|
await queryInterface.addIndex(
|
|
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
|
['project_id', 'resolved_at_ms', 'dispatch_id'],
|
|
{
|
|
name: APPROVED_ACTION_RECOVERY_RESOLUTION_PROJECT_INDEX,
|
|
transaction,
|
|
},
|
|
);
|
|
|
|
await queryInterface.sequelize.query(
|
|
`INSERT INTO "${APPROVED_ACTION_RECOVERY_CONTROL_TABLE}"
|
|
(dispatch_id, project_id, execution_version, status, version,
|
|
next_scan_at_ms, lease_owner, lease_token, lease_expires_at_ms,
|
|
finding_count, last_finding_mutation_id, last_finding,
|
|
last_result_code, last_evidence_digest,
|
|
resolution_mutation_id, created_at_ms, updated_at_ms)
|
|
SELECT dispatch_id, project_id, version, 'armed', 0,
|
|
lease_expires_at_ms, NULL, NULL, NULL,
|
|
0, NULL, NULL, NULL, NULL, NULL, started_at_ms, started_at_ms
|
|
FROM "${APPROVED_ACTION_DISPATCH_EXECUTION_TABLE}"
|
|
WHERE status = 'executing'`,
|
|
{ transaction },
|
|
);
|
|
},
|
|
};
|