UNPKG

@adpt/core

Version:
235 lines 9.09 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 fs = tslib_1.__importStar(require("fs-extra")); const lodash_1 = require("lodash"); const moment_1 = tslib_1.__importDefault(require("moment")); const path = tslib_1.__importStar(require("path")); const lockfile_1 = require("../utils/lockfile"); const history_1 = require("./history"); // These are exported only for testing exports.dataDirName = "dataDir"; exports.domFilename = "adapt_dom.xml"; exports.stateFilename = "adapt_state.json"; exports.observationsFilename = "adapt_observations.json"; exports.infoFilename = "adapt_deploy.json"; // Example dirName: 00000-preAct-2018-11-15T22:20:46+00:00 const dirNameRegEx = /^(\d{5})-([^-]+)-(.*)$/; function dirStatus(dirName) { const m = dirName.match(dirNameRegEx); if (m) { const status = m[2]; if (history_1.isHistoryStatus(status)) return status; } throw new Error(`History directory '${dirName}' unrecognized format`); } function dirStatusMatches(dirName, expected) { if (expected === undefined) return true; const status = dirStatus(dirName); if (expected === history_1.HistoryStatus.complete) return history_1.isStatusComplete(status); else return dirStatus(dirName) === expected; } class LocalHistoryStore { constructor(db, dbPath, rootDir) { this.db = db; this.dbPath = dbPath; this.rootDir = rootDir; this.dataDir = path.join(rootDir, exports.dataDirName + ".uncommitted"); } async init(create) { let currentInfo; try { currentInfo = this.db.getData(this.dbPath); } catch (err) { if (err.name !== "DataError") throw err; if (!create) throw new Error(`History store does not exist`); currentInfo = { stateDirs: [], }; this.db.push(this.dbPath, currentInfo); } await fs.ensureDir(this.rootDir); } async destroy() { await this.releaseDataDir(); try { this.db.delete(this.dbPath); } catch (err) { /**/ } try { await fs.remove(this.rootDir); } catch (err) { /**/ } } async commitEntry(toStore) { const { domXml, stateJson, observationsJson } = toStore, info = tslib_1.__rest(toStore, ["domXml", "stateJson", "observationsJson"]); if (toStore.dataDir !== this.dataDir) { throw new Error(`Internal error: commiting invalid dataDir ` + `'${toStore.dataDir}'. Should be '${this.dataDir}'`); } const dirName = await this.nextDirName(toStore.status); const dirPath = path.join(this.rootDir, dirName); info.dataDir = path.join(dirPath, exports.dataDirName); await fs.outputFile(path.join(dirPath, exports.domFilename), domXml); await fs.outputFile(path.join(dirPath, exports.stateFilename), stateJson); await fs.outputFile(path.join(dirPath, exports.observationsFilename), observationsJson); await fs.outputJson(path.join(dirPath, exports.infoFilename), info); // If we're committing preAct, the directory remains in place and // locked; just snapshot it with a copy. Otherwise, we're done with it // and we can move it and unlock it. if (toStore.status === history_1.HistoryStatus.preAct) { await fs.copy(this.dataDir, info.dataDir, { overwrite: false, errorOnExist: true, preserveTimestamps: true, }); } else { await fs.move(this.dataDir, info.dataDir); await this.releaseDataDir(); } this.db.push(this.dbPath + "/stateDirs[]", dirName, false /*override*/); } async last(withStatus) { const lastDir = this.lastDir(withStatus); if (lastDir === undefined) return undefined; return this.historyEntry(lastDir); } async remove(historyName) { await fs.remove(historyName); const info = await this.getInfo(); info.stateDirs = info.stateDirs.filter((d) => d !== historyName); await this.setInfo(info); } async historyEntry(historyName) { const dirName = path.join(this.rootDir, historyName); const domXml = await fs.readFile(path.join(dirName, exports.domFilename)); const stateJson = await fs.readFile(path.join(dirName, exports.stateFilename)); const observationsJson = await fs.readFile(path.join(dirName, exports.observationsFilename)); const info = await fs.readJson(path.join(dirName, exports.infoFilename)); return { domXml: domXml.toString(), stateJson: stateJson.toString(), observationsJson: observationsJson.toString(), fileName: info.fileName, projectRoot: info.projectRoot, stackName: info.stackName, dataDir: info.dataDir, status: info.status, }; } async getDataDir(withStatus) { if (this.dataDirRelease) { throw new Error(`Internal error: attempting to lock dataDir ` + `'${this.dataDir}' twice`); } // dataDir must exist in order to lock it. await fs.ensureDir(this.dataDir); try { this.dataDirRelease = await lockfile_1.lock(this.dataDir); } catch (e) { throw new Error(`Unable to get exclusive access to deployment ` + `directory '${this.dataDir}'. Please retry in a moment. ` + `[${e.message}]`); } // Ensure we start from the last committed state await fs.remove(this.dataDir); await fs.ensureDir(this.dataDir); const last = this.lastDir(withStatus); if (last) { await fs.copy(path.join(this.rootDir, last, exports.dataDirName), this.dataDir, { preserveTimestamps: true, }); } return this.dataDir; } async releaseDataDir() { if (!this.dataDirRelease) return; // We don't hold the lock await fs.remove(this.dataDir); await this.dataDirRelease(); this.dataDirRelease = undefined; } async nextDirName(status) { const lastDir = this.lastDir(); let nextSeq; if (lastDir === undefined) { nextSeq = 0; } else { const matches = lastDir.match(/^\d+/); if (matches == null) throw new Error(`stateDir entry '${lastDir}' is invalid`); const lastSeq = parseInt(matches[0], 10); if (status === history_1.HistoryStatus.preAct) { // Completely new entry nextSeq = lastSeq + 1; } else { if (dirStatus(lastDir) !== history_1.HistoryStatus.preAct) { throw new Error(`Unexpected status for last history entry. ` + `(lastDir: ${lastDir}, status: ${status})`); } // Completion of the current entry nextSeq = lastSeq; } } const seqStr = lodash_1.padStart(nextSeq.toString(10), 5, "0"); const timestamp = moment_1.default().format(); return `${seqStr}-${status}-${timestamp}`; } lastDir(withStatus) { try { // The array returned by getData is the actual object stored in the // DB. Make a copy. const dirList = this.db.getData(this.dbPath + "/stateDirs").slice(); while (true) { const dir = dirList.pop(); if (dir === undefined || dirStatusMatches(dir, withStatus)) return dir; } } catch (err) { if (err.name !== "DataError") throw err; return undefined; } } async getInfo() { return this.db.getData(this.dbPath); } async setInfo(info) { return this.db.push(this.dbPath, info, true); } } async function createLocalHistoryStore(db, dbPath, rootDir, create) { const h = new LocalHistoryStore(db, dbPath, rootDir); await h.init(create); return h; } exports.createLocalHistoryStore = createLocalHistoryStore; //# sourceMappingURL=local_history.js.map