Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | /**
* Mongoose model for the CyberiaContentRelease API: one record per versioned Cyberia content
* database, and which one the runtime serves.
*
* A release is built into its own database, validated there, and promoted by switching the active
* pointer. The record lives in the runtime database, which no content deployment touches. The
* collection holds every invariant: one release at most is active, and one executes.
*
* @module src/api/cyberia-content-release/cyberia-content-release.model.js
* @namespace CyberiaContentReleaseModel
*/
import { Schema, model } from 'mongoose';
import { RELEASE_ID_PATTERN, SOURCE_CHANNELS } from '../../server/release/source-release.js';
/** Lifecycle of a release. */
export const RELEASE_STATUS = Object.freeze(['building', 'candidate', 'validated', 'failed', 'active', 'retired']);
const CheckSchema = new Schema(
{
name: { type: String, required: true },
ok: { type: Boolean, required: true },
count: { type: Number, default: 0 },
findings: { type: [String], default: [] },
},
{ _id: false },
);
const SourceSchema = new Schema(
{
name: { type: String, required: true },
repository: { type: String, required: true },
sourceRevision: { type: String, required: true },
},
{ _id: false },
);
/**
* @typedef {Object} CyberiaContentRelease
* @property {string} releaseId - Unique release id, the database suffix
* @property {string} database - Database the release content lives in
* @property {string} status - One of {@link RELEASE_STATUS}
* @property {{stage:string,message:string,at:Date}} failure - Where and why a failed release stopped
* @property {string[]} instances - Instance codes the release carries
* @property {{from:string,channel:string,repository:string,sourceRevision:string}} source - What the
* release is built from: an exact source revision (`from: source`), the local artifact or the workspace
* @property {Array<{name:string,repository:string,sourceRevision:string}>} sources - Every source the deploy resolved
* @property {{version:string,digest:string}} content - The content artifact the release carries
* @property {{engineVersion:string,engineCommit:string,job:string,builtAt:Date,builtBy:string}} provenance - Who built it
* @property {{instances:string[],maps:number,bindings:number,quests:number,actions:number}} manifest - What the release carries
* @property {string[]} dependencies - Every Object Layer cid the release binds or pins
* @property {{ok:boolean,checkedAt:Date,checks:Array}} validation - Last validation report
* @property {{holder:string,heartbeatAt:Date}} lease - The execution building the release
* @property {Date} promotedAt - When the release became active
* @property {Date} retiredAt - When another release replaced it
* @memberof CyberiaContentReleaseModel
*/
const CyberiaContentReleaseSchema = new Schema(
{
releaseId: { type: String, required: true, unique: true, trim: true, match: RELEASE_ID_PATTERN },
database: { type: String, required: true, trim: true },
status: { type: String, required: true, enum: RELEASE_STATUS, default: 'building' },
failure: {
stage: { type: String, default: '' },
message: { type: String, default: '' },
at: { type: Date },
},
instances: { type: [String], default: [] },
source: {
from: { type: String, enum: ['source', 'backups', 'workspace'], default: 'backups' },
channel: { type: String, enum: ['', ...SOURCE_CHANNELS], default: '' },
repository: { type: String, default: '' },
sourceRevision: { type: String, default: '' },
},
sources: { type: [SourceSchema], default: [] },
content: {
version: { type: String, default: '' },
digest: { type: String, default: '' },
},
provenance: {
engineVersion: { type: String, default: '' },
engineCommit: { type: String, default: '' },
job: { type: String, default: '' },
builtAt: { type: Date },
builtBy: { type: String, default: '' },
},
manifest: {
instances: { type: [String], default: [] },
maps: { type: Number, default: 0 },
bindings: { type: Number, default: 0 },
quests: { type: Number, default: 0 },
actions: { type: Number, default: 0 },
},
dependencies: { type: [String], default: [] },
validation: {
ok: { type: Boolean, default: false },
checkedAt: { type: Date },
checks: { type: [CheckSchema], default: [] },
},
lease: {
executing: { type: Boolean },
holder: { type: String },
heartbeatAt: { type: Date },
},
promotedAt: { type: Date },
retiredAt: { type: Date },
},
{ timestamps: true },
);
CyberiaContentReleaseSchema.index({ status: 1, promotedAt: -1 });
// A host serves one content database: a second promotion racing the first fails instead of doubling.
CyberiaContentReleaseSchema.index({ status: 1 }, { unique: true, partialFilterExpression: { status: 'active' } });
// One release executes at a time, whatever channel its source came from.
CyberiaContentReleaseSchema.index(
{ 'lease.executing': 1 },
{ unique: true, partialFilterExpression: { 'lease.executing': true } },
);
/**
* The release the runtime serves, or null before the first promotion.
* @returns {Promise<Object|null>}
* @memberof CyberiaContentReleaseModel
*/
CyberiaContentReleaseSchema.statics.active = async function () {
return await this.findOne({ status: 'active' }).sort({ promotedAt: -1 }).lean();
};
/**
* The release that was active before the current one: the rollback target.
* @returns {Promise<Object|null>}
* @memberof CyberiaContentReleaseModel
*/
CyberiaContentReleaseSchema.statics.previousActive = async function () {
return await this.findOne({ status: 'retired', promotedAt: { $ne: null } })
.sort({ retiredAt: -1 })
.lean();
};
const CyberiaContentReleaseModel = model('CyberiaContentRelease', CyberiaContentReleaseSchema);
const ProviderSchema = CyberiaContentReleaseSchema;
class CyberiaContentReleaseDto {
static select = {
get: () => ({
_id: 1,
releaseId: 1,
database: 1,
status: 1,
failure: 1,
instances: 1,
source: 1,
sources: 1,
content: 1,
provenance: 1,
manifest: 1,
dependencies: 1,
validation: 1,
promotedAt: 1,
retiredAt: 1,
createdAt: 1,
updatedAt: 1,
}),
};
}
export { CyberiaContentReleaseSchema, CyberiaContentReleaseModel, ProviderSchema, CyberiaContentReleaseDto };
|