@adpt/core
Version:
AdaptJS core library
301 lines • 10.9 kB
JavaScript
"use strict";
/*
* Copyright 2018-2019 Unbounded Systems, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const utils_1 = require("@adpt/utils");
const lodash_1 = require("lodash");
const randomstring = tslib_1.__importStar(require("randomstring"));
const deploy_1 = require("../deploy");
const error_1 = require("../error");
const server_1 = require("./server");
const deploymentPath = "/deployments";
const maxTries = 100;
const invalidChars = RegExp("[:/]", "g");
function encodePathComponent(comp) {
return comp.replace(invalidChars, "_");
}
exports.encodePathComponent = encodePathComponent;
function dpath(deployID) {
deployID = encodePathComponent(deployID);
return `${deploymentPath}/${deployID}`;
}
function makeName(base) {
const rand = randomstring.generate({
length: 4,
charset: "alphabetic",
readable: true,
capitalization: "lowercase",
});
return `${base}-${rand}`;
}
function isPathNotFound(err) {
// FIXME(mark): AdaptServer should have its own error. This is actually
// specific to the current local server implementation. Encapsulating here
// to make it easier to fix later.
return lodash_1.isError(err) && err.name === "DataError";
}
async function createDeployment(server, projectName, stackName, options = {}) {
const baseName = `${projectName}::${stackName}`;
let deployID = "";
for (let i = 0; i < maxTries; i++) {
deployID = options.deployID || makeName(baseName);
const deployData = {
deployID,
currentOpID: null,
deployOpInfo: {},
stateDirs: [],
};
try {
await server.set(dpath(deployID), deployData, { mustCreate: true });
break;
}
catch (err) {
if (server_1.isServerPathExists(err) && options.deployID) {
throw new utils_1.UserError(`DeployID '${deployID}' already exists`);
}
if (!isPathNotFound(err))
throw err;
// continue
}
}
const deployment = new DeploymentImpl(deployID, server);
await deployment.init();
return deployment;
}
exports.createDeployment = createDeployment;
async function loadDeployment(server, deployID) {
try {
// Validate that the deployment exists
await server.get(dpath(deployID));
}
catch (err) {
if (!isPathNotFound(err))
throw err;
throw new Error(`Deployment '${deployID}' does not exist`);
}
const deployment = new DeploymentImpl(deployID, server);
await deployment.init();
return deployment;
}
exports.loadDeployment = loadDeployment;
async function destroyDeployment(server, deployID) {
let history;
const errs = [];
try {
history = await server.historyStore(dpath(deployID), false);
}
catch (err) {
if (!isPathNotFound(err))
throw err;
// Not an error if the store is not found. If there's no entry
// for this deployID, server.delete will give the right error to the
// caller.
}
try {
await server.delete(dpath(deployID));
}
catch (err) {
errs.push(err.message || err.toString());
}
try {
if (history)
await history.destroy();
}
catch (err) {
errs.push(err.message || err.toString());
}
if (errs.length > 0) {
const msg = `Error deleting deployment '${deployID}': ` +
errs.join(" and ");
throw new Error(msg);
}
}
exports.destroyDeployment = destroyDeployment;
async function listDeploymentIDs(server) {
try {
const deps = await listDeployments(server);
return deps.map((dep) => dep.deployID);
}
catch (err) {
throw new Error(`Error listing deployments: ${err}`);
}
}
exports.listDeploymentIDs = listDeploymentIDs;
async function listDeployments(server) {
try {
const deps = await server.get(deploymentPath);
return Object.keys(deps).map((key) => ({ deployID: deps[key].deployID }));
}
catch (err) {
// deploymentPath may not have been set yet.
if (isPathNotFound(err))
return [];
throw new Error(`Error listing deployments: ${err}`);
}
}
exports.listDeployments = listDeployments;
class DeploymentImpl {
constructor(deployID, server) {
this.deployID = deployID;
this.server = server;
this.init = async () => {
this.historyStore_ = await this.server.historyStore(this.basePath_, true);
};
this.getDataDir = (withStatus) => this.historyStore.getDataDir(withStatus);
this.releaseDataDir = () => this.historyStore.releaseDataDir();
this.commitEntry = (toStore) => this.historyStore.commitEntry(toStore);
this.historyEntry = (name) => this.historyStore.historyEntry(name);
this.lastEntry = (withStatus) => this.historyStore.last(withStatus);
this.basePath_ = dpath(this.deployID);
}
async status(stepID, info) {
const path = this.stepInfoPath(stepID);
if (info !== undefined) {
// Set
return server_1.withLock(this.server, async (lock) => {
await this.assertCurrent(stepID, lock);
const cur = await this.server.get(path, { lock });
await this.server.set(path, Object.assign({}, cur, info), { lock });
return;
});
}
// Get
try {
const i = await this.server.get(path);
return lodash_1.cloneDeep(i);
}
catch (err) {
if (!isPathNotFound(err))
throw err;
throw new error_1.DeployStepIDNotFound(stepID.deployOpID, stepID.deployStepNum);
}
}
async elementStatus(stepID, idOrMap) {
const path = this.stepInfoPath(stepID) + `/elementStatus`;
return server_1.withLock(this.server, async (lock) => {
// Get
if (typeof idOrMap === "string") {
await this.currentOpID(lock); // Throws if currentOpID===null
try {
return await this.server.get(`${path}/${idOrMap}`, { lock });
}
catch (err) {
if (!isPathNotFound(err))
throw err;
throw new Error(`ElementID '${idOrMap}' not found`);
}
}
// Set
await this.assertCurrent(stepID, lock);
let old = {};
try {
old = await this.server.get(path, { lock });
}
catch (err) {
if (!isPathNotFound(err))
throw err;
// Fall through
}
await this.server.set(path, Object.assign({}, old, idOrMap), { lock });
});
}
async newOpID() {
return server_1.withLock(this.server, async (lock) => {
const opPath = this.basePath_ + "/currentOpID";
const cur = await this.server.get(opPath, { lock });
const next = cur == null ? 0 : cur + 1;
const stepPath = this.stepNumPath(next);
await this.server.set(stepPath, null, { lock });
await this.server.set(opPath, next, { lock });
return next;
});
}
async currentOpID(lock) {
const path = this.basePath_ + "/currentOpID";
const cur = await this.server.get(path, { lock });
if (cur == null)
throw new error_1.DeploymentNotActive(this.deployID);
return cur;
}
async newStepID(deployOpID) {
return server_1.withLock(this.server, async (lock) => {
await this.assertCurrentOpID(deployOpID, lock);
const cur = await this.currentStepNum(deployOpID, lock);
const deployStepNum = cur == null ? 0 : cur + 1;
const info = {
deployStatus: deploy_1.DeployStatus.Initial,
goalStatus: deploy_1.DeployStatus.Initial,
elementStatus: {},
};
const step = {
deployOpID,
deployStepNum,
};
await this.server.set(this.stepInfoPath(step), info, { lock });
await this.server.set(this.stepNumPath(deployOpID), deployStepNum, { lock });
return step;
});
}
async currentStepID(deployOpID) {
return server_1.withLock(this.server, async (lock) => {
await this.assertCurrentOpID(deployOpID, lock);
const deployStepNum = await this.currentStepNum(deployOpID, lock);
if (deployStepNum == null) {
throw new error_1.DeploymentOpIDNotActive(this.deployID, deployOpID);
}
return {
deployOpID,
deployStepNum,
};
});
}
stepInfoPath(stepID) {
return `${this.basePath_}/deployOpInfo/` +
`${stepID.deployOpID}/${stepID.deployStepNum}`;
}
stepNumPath(opID) {
return this.basePath_ + `/deployOpInfo/${opID}/currentStepNum`;
}
get historyStore() {
if (this.historyStore_ == null)
throw new error_1.InternalError(`null historyStore`);
return this.historyStore_;
}
async currentStepNum(opID, lock) {
const path = this.stepNumPath(opID);
return this.server.get(path, { lock });
}
async assertCurrentOpID(opID, lock) {
const current = await this.currentOpID(lock);
if (opID !== current) {
throw new Error(`Requested DeployOpID (${opID}) is not current (${current})`);
}
}
async assertCurrent(stepID, lock) {
await this.assertCurrentOpID(stepID.deployOpID, lock);
const current = await this.currentStepNum(stepID.deployOpID, lock);
if (current == null) {
throw new error_1.DeploymentOpIDNotActive(this.deployID, stepID.deployOpID);
}
if (stepID.deployStepNum !== current) {
throw new Error(`Requested DeployStepID ` +
`(${stepID.deployOpID}.${stepID.deployStepNum}) is not ` +
`current (${stepID.deployOpID}.${current})`);
}
}
}
//# sourceMappingURL=deployment.js.map