UNPKG

@adpt/core

Version:
301 lines 10.9 kB
"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