UNPKG

@atomist/sdm

Version:

Atomist Software Delivery Machine SDK

253 lines 11 kB
"use strict"; /* * Copyright © 2020 Atomist, Inc. * * 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 }); exports.sanitizeClassifier = exports.resolveClassifierPath = exports.cacheRemove = exports.cacheRestore = exports.NoOpGoalProjectListenerRegistration = exports.cachePut = exports.CacheOutputGoalDataKey = exports.CacheInputGoalDataKey = void 0; const configuration_1 = require("@atomist/automation-client/lib/configuration"); const fileGlobs_1 = require("@atomist/automation-client/lib/project/fileGlobs"); const projectUtils_1 = require("@atomist/automation-client/lib/project/util/projectUtils"); const _ = require("lodash"); const sdmGoal_1 = require("../../../api-helper/goal/sdmGoal"); const GoalInvocation_1 = require("../../../api/goal/GoalInvocation"); const commonPushTests_1 = require("../../../api/mapping/support/commonPushTests"); const resolvePlaceholder_1 = require("../../machine/yaml/resolvePlaceholder"); const array_1 = require("../../util/misc/array"); const CompressingGoalCache_1 = require("./CompressingGoalCache"); exports.CacheInputGoalDataKey = "@atomist/sdm/input"; exports.CacheOutputGoalDataKey = "@atomist/sdm/output"; const DefaultGoalCache = new CompressingGoalCache_1.CompressingGoalCache(); /** * Goal listener that performs caching after a goal has been run. * @param options The options for caching * @param classifier Whether only a specific classifier, as defined in the options, * needs to be cached. If omitted, all classifiers are cached. * @param classifiers Additional classifiers that need to be created. */ function cachePut(options, classifier, ...classifiers) { const allClassifiers = []; if (classifier) { allClassifiers.push(classifier, ...(classifiers || [])); } const entries = !!classifier ? options.entries.filter(pattern => allClassifiers.includes(pattern.classifier)) : options.entries; const listenerName = `caching outputs`; return { name: listenerName, listener: async (p, gi) => { const { goalEvent } = gi; if (!!isCacheEnabled(gi) && !process.env.ATOMIST_ISOLATED_GOAL_INIT) { const cloneEntries = _.cloneDeep(entries); const goalCache = cacheStore(gi); for (const entry of cloneEntries) { const files = []; if (isGlobFilePattern(entry.pattern)) { files.push(...(await getFilePathsThroughPattern(p, entry.pattern.globPattern))); } else if (isDirectoryPattern(entry.pattern)) { files.push(entry.pattern.directory); } if (!_.isEmpty(files)) { const resolvedClassifier = await resolveClassifierPath(entry.classifier, gi); const uri = await goalCache.put(gi, p, files, resolvedClassifier); if (!!resolvedClassifier && !!uri) { entry.classifier = resolvedClassifier; entry.uri = uri; } } } // Set outputs on the goal data const data = sdmGoal_1.goalData(goalEvent); const newData = { [exports.CacheOutputGoalDataKey]: [ ...(data[exports.CacheOutputGoalDataKey] || []), ...cloneEntries, ], }; goalEvent.data = JSON.stringify(Object.assign(Object.assign({}, (sdmGoal_1.goalData(goalEvent))), newData)); } }, pushTest: options.pushTest, events: [GoalInvocation_1.GoalProjectListenerEvent.after], }; } exports.cachePut = cachePut; function isGlobFilePattern(toBeDetermined) { return toBeDetermined.globPattern !== undefined; } function isDirectoryPattern(toBeDetermined) { return toBeDetermined.directory !== undefined; } async function pushTestSucceeds(pushTest, gi, p) { return (pushTest || commonPushTests_1.AnyPush).mapping({ push: gi.goalEvent.push, project: p, id: gi.id, configuration: gi.configuration, addressChannels: gi.addressChannels, context: gi.context, preferences: gi.preferences, credentials: gi.credentials, skill: gi.skill, }); } async function invokeCacheMissListeners(optsToUse, p, gi, event) { for (const cacheMissFallback of array_1.toArray(optsToUse.onCacheMiss)) { const allEvents = [GoalInvocation_1.GoalProjectListenerEvent.before, GoalInvocation_1.GoalProjectListenerEvent.after]; if ((cacheMissFallback.events || allEvents).filter(e => e === event).length > 0 && await pushTestSucceeds(cacheMissFallback.pushTest, gi, p)) { await cacheMissFallback.listener(p, gi, event); } } } exports.NoOpGoalProjectListenerRegistration = { name: "NoOpListener", listener: async () => { }, pushTest: commonPushTests_1.AnyPush, }; /** * Goal listener that performs cache restores before a goal has been run. * @param options The options for caching * @param classifier Whether only a specific classifier, as defined in the options, * needs to be restored. If omitted, all classifiers defined in the options are restored. * @param classifiers Additional classifiers that need to be restored. */ function cacheRestore(options, classifier, ...classifiers) { const allClassifiers = []; if (classifier) { allClassifiers.push(classifier, ...(classifiers || [])); } const optsToUse = Object.assign({ onCacheMiss: exports.NoOpGoalProjectListenerRegistration }, options); const classifiersToBeRestored = []; if (allClassifiers.length > 0) { classifiersToBeRestored.push(...allClassifiers); } else { classifiersToBeRestored.push(...optsToUse.entries.map(entry => entry.classifier)); } const listenerName = `restoring inputs`; return { name: listenerName, listener: async (p, gi, event) => { if (!!isCacheEnabled(gi)) { const goalCache = cacheStore(gi); for (const c of classifiersToBeRestored) { try { const resolvedClassifier = await resolveClassifierPath(c, gi); await goalCache.retrieve(gi, p, resolvedClassifier); } catch (e) { await invokeCacheMissListeners(optsToUse, p, gi, event); } } } else { await invokeCacheMissListeners(optsToUse, p, gi, event); } // Set inputs on the goal data const { goalEvent } = gi; const data = sdmGoal_1.goalData(goalEvent); const newData = { [exports.CacheInputGoalDataKey]: [ ...(data[exports.CacheInputGoalDataKey] || []), ...classifiersToBeRestored.map(c => ({ classifier: c, })), ], }; goalEvent.data = JSON.stringify(Object.assign(Object.assign({}, (sdmGoal_1.goalData(goalEvent))), newData)); }, pushTest: optsToUse.pushTest, events: [GoalInvocation_1.GoalProjectListenerEvent.before], }; } exports.cacheRestore = cacheRestore; /** * Goal listener that cleans up the cache restores after a goal has been run. * @param options The options for caching * @param classifier Whether only a specific classifier, as defined in the options, * needs to be removed. If omitted, all classifiers are removed. * @param classifiers Additional classifiers that need to be removed. */ function cacheRemove(options, classifier, ...classifiers) { const allClassifiers = []; if (classifier) { allClassifiers.push(...[classifier, ...classifiers]); } const classifiersToBeRemoved = []; if (allClassifiers.length > 0) { classifiersToBeRemoved.push(...allClassifiers); } else { classifiersToBeRemoved.push(...options.entries.map(entry => entry.classifier)); } const listenerName = `removing outputs`; return { name: listenerName, listener: async (p, gi) => { if (!!isCacheEnabled(gi)) { const goalCache = cacheStore(gi); for (const c of classifiersToBeRemoved) { const resolvedClassifier = await resolveClassifierPath(c, gi); await goalCache.remove(gi, resolvedClassifier); } } }, pushTest: options.pushTest, events: [GoalInvocation_1.GoalProjectListenerEvent.after], }; } exports.cacheRemove = cacheRemove; async function getFilePathsThroughPattern(project, globPattern) { const oldExcludes = fileGlobs_1.DefaultExcludes; fileGlobs_1.DefaultExcludes.splice(0, fileGlobs_1.DefaultExcludes.length); // necessary evil try { return await projectUtils_1.gatherFromFiles(project, globPattern, async (f) => f.path); } finally { fileGlobs_1.DefaultExcludes.push(...oldExcludes); } } function isCacheEnabled(gi) { return _.get(gi.configuration, "sdm.cache.enabled", false); } function cacheStore(gi) { return _.get(gi.configuration, "sdm.cache.store", DefaultGoalCache); } /** * Interpolate information from goal invocation into the classifier. */ async function resolveClassifierPath(classifier, gi) { if (!classifier) { return gi.context.workspaceId; } const wrapper = { classifier }; await configuration_1.resolvePlaceholders(wrapper, v => resolvePlaceholder_1.resolvePlaceholder(v, gi.goalEvent, gi, {})); return gi.context.workspaceId + "/" + sanitizeClassifier(wrapper.classifier); } exports.resolveClassifierPath = resolveClassifierPath; /** * Sanitize classifier for use in path. Replace any characters * which might cause problems on POSIX or MS Windows with "_", * including path separators. Ensure resulting file is not "hidden". */ function sanitizeClassifier(classifier) { return classifier.replace(/[^-.0-9A-Za-z_+]/g, "_") .replace(/^\.+/, ""); // hidden } exports.sanitizeClassifier = sanitizeClassifier; //# sourceMappingURL=goalCaching.js.map