couchbase-index-manager
Version:
Manage Couchbase indexes during the CI/CD process
158 lines • 5.79 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Plan = void 0;
const tslib_1 = require("tslib");
const chalk_1 = tslib_1.__importDefault(require("chalk"));
const lodash_1 = require("lodash");
const index_manager_1 = require("../index-manager");
/**
* Adds mutations to a collection already grouped by phase
*/
function addMutationsByPhase(mutations, currentMutations) {
mutations.reduce((accumulator, mutation) => {
const phase = mutation.phase - 1;
if (!accumulator[phase]) {
accumulator[phase] = [];
}
accumulator[phase].push(mutation);
return accumulator;
}, currentMutations);
return currentMutations;
}
const defaultOptions = {
logger: console,
buildDelay: 3000,
};
/**
* Represents a planned set of mutations for synchronization
*/
class Plan {
constructor(manager, mutations, options) {
this.manager = manager;
this.options = {
...defaultOptions,
...options
};
this.mutations = addMutationsByPhase(mutations, []);
}
/**
* Returns true if the plan is empty
*/
isEmpty() {
return this.mutations.length === 0;
}
/**
* Prints the plan
*/
print() {
if (this.isEmpty()) {
this.options.logger.info(chalk_1.default.yellowBright('No mutations to be performed'));
return;
}
this.options.logger.info();
this.options.logger.info('Index sync plan:');
this.options.logger.info();
(0, lodash_1.flatten)(this.mutations).forEach((mutation) => {
mutation.print(this.options.logger);
// Add blank line
this.options.logger.info();
});
}
/**
* Executes the plan
*/
async execute() {
let errorCount = 0;
let skipCount = 0;
for (const phase of this.mutations) {
if (phase.length <= 0) {
continue;
}
const phaseNum = phase[0].phase;
if (errorCount > 0) {
// Skip this phase if there are errors
this.options.logger.info(chalk_1.default.yellowBright(`Skipping phase ${phaseNum} (${phase.length} tasks)`));
skipCount += phase.length;
break;
}
else {
this.options.logger.info(chalk_1.default.greenBright(`Executing phase ${phaseNum}...`));
}
for (let i = 0; i < phase.length; i++) {
try {
await phase[i].execute(this.manager, this.options.logger);
}
catch (e) {
this.options.logger.error(chalk_1.default.redBright(`${e}`));
errorCount++;
}
}
await this.buildIndexes(phase);
}
if (errorCount === 0) {
this.options.logger.info(chalk_1.default.greenBright('Plan completed'));
this.options.logger.info();
}
else if (skipCount > 0) {
const msg = `Plan failed with ${errorCount} errors, ${skipCount} skipped`;
throw new Error(msg);
}
else {
const msg = `Plan completed with ${errorCount} errors`;
throw new Error(msg);
}
}
/**
* Adds mutations to the plan
*/
addMutation(...mutations) {
addMutationsByPhase(mutations, this.mutations);
}
async buildIndexes(phase) {
// Wait 3 seconds for index nodes to synchronize before building
// This helps to reduce race conditions
// https://github.com/brantburnett/couchbase-index-manager/issues/35
if (this.options.buildDelay > 0) {
await new Promise((resolve) => setTimeout(resolve, this.options.buildDelay));
}
// Get a list of distinct scopes/collections
const collections = [...new Set(phase.map(index => `${index.scope}~${index.collection}`))]
.map(str => {
const arr = str.split('~');
return {
scope: arr[0],
collection: arr[1]
};
});
// Build each collection separately
for (const collection of collections) {
if (collection.scope === index_manager_1.DEFAULT_SCOPE && collection.collection === index_manager_1.DEFAULT_COLLECTION) {
this.options.logger.info(chalk_1.default.greenBright('Building indexes...'));
}
else {
this.options.logger.info(chalk_1.default.greenBright(`Building indexes on ${collection.scope}.${collection.collection}...`));
}
await this.manager.buildDeferredIndexes(collection.scope, collection.collection);
const waitOptions = {
...collection,
timeoutMs: this.options.buildTimeout
};
if (!await this.manager.waitForIndexBuild(waitOptions, this.indexBuildTickHandler.bind(this))) {
this.options.logger.warn(chalk_1.default.yellowBright('Some indexes are not online'));
}
}
}
/**
* When running in Kubernetes and attaching to logs, there is a five
* minute timeout if there is no console output. This tick handler
* ensures that output continues during that time.
*/
indexBuildTickHandler(milliseconds) {
const secs = milliseconds / 1000;
const secsPart = (0, lodash_1.padStart)(Math.floor(secs % 60).toString(10), 2, '0');
const minsPart = Math.floor(secs / 60);
this.options.logger.log(chalk_1.default.greenBright(`Building ${minsPart}m${secsPart}s...`));
}
}
exports.Plan = Plan;
//# sourceMappingURL=plan.js.map