@atomist/sdm-core
Version:
Atomist Software Delivery Machine - Implementation
257 lines (226 loc) • 9.72 kB
text/typescript
/*
* Copyright © 2019 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.
*/
import {
Configuration,
ConfigurationPostProcessor,
guid,
logger,
} from "@atomist/automation-client";
import {
ConfigurationValues,
SoftwareDeliveryMachine,
SoftwareDeliveryMachineConfiguration,
validateConfigurationValues,
} from "@atomist/sdm";
import * as _ from "lodash";
import { FulfillGoalOnRequested } from "../../handlers/events/delivery/goals/FulfillGoalOnRequested";
import {
GoalExecutionAutomationEventListener,
GoalExecutionRequestProcessor,
} from "../../handlers/events/delivery/goals/goalExecution";
import { CacheCleanupAutomationEventListener } from "../../handlers/events/delivery/goals/k8s/CacheCleanupAutomationEventListener";
import { defaultSoftwareDeliveryMachineConfiguration } from "../../machine/defaultSoftwareDeliveryMachineConfiguration";
import { toArray } from "../../util/misc/array";
import { GoalSigningAutomationEventListener } from "../signing/goalSigning";
import { SdmGoalMetricReportingAutomationEventListener } from "../util/SdmGoalMetricReportingAutomationEventListener";
import {
sdmExtensionPackStartupMessage,
sdmStartupMessage,
} from "../util/startupMessage";
import { InvokeSdmStartupListenersAutomationEventListener } from "./InvokeSdmStartupListenersAutomationEventListener";
import { LocalSoftwareDeliveryMachineConfiguration } from "./LocalSoftwareDeliveryMachineOptions";
import {
isGitHubAction,
isInLocalMode,
} from "./modes";
/**
* Options passed to the set up of the SDM.
*/
// tslint:disable-next-line:no-empty-interface
export interface ConfigureOptions extends ConfigurationValues {
// Empty for future extensibility
}
/**
* Type that can create a fully configured SDM
*/
export type SoftwareDeliveryMachineMaker =
(configuration: LocalSoftwareDeliveryMachineConfiguration) => SoftwareDeliveryMachine | Promise<SoftwareDeliveryMachine>;
/**
* Configure and set up a Software Delivery Machine instance with the automation-client framework for standalone
* or single goal based execution
* @param {(configuration: (Configuration & SoftwareDeliveryMachineOptions)) => SoftwareDeliveryMachine} machineMaker
* @param {ConfigureOptions} options
* @returns {ConfigurationPostProcessor}
*/
export function configureSdm(machineMaker: SoftwareDeliveryMachineMaker,
options: ConfigureOptions = {}): ConfigurationPostProcessor<LocalSoftwareDeliveryMachineConfiguration> {
return async (config: Configuration) => {
let mergedConfig = config as LocalSoftwareDeliveryMachineConfiguration;
// Configure the local SDM
mergedConfig = await doWithSdmLocal<LocalSoftwareDeliveryMachineConfiguration>(local => {
return local.configureLocal()(mergedConfig);
}) || mergedConfig;
const defaultSdmConfiguration = defaultSoftwareDeliveryMachineConfiguration(config);
mergedConfig = _.merge(defaultSdmConfiguration, mergedConfig);
validateConfigurationValues(mergedConfig, options);
const sdm = await machineMaker(mergedConfig);
await doWithSdmLocal<void>(local =>
sdm.addExtensionPacks(local.LocalLifecycle, local.LocalSdmConfig),
);
// Configure the job forking ability
await configureJobLaunching(mergedConfig, sdm);
configureGoalSigning(mergedConfig);
await registerMetadata(mergedConfig, sdm);
// Register startup message detail
_.update(mergedConfig, "logging.banner.contributors",
old => !!old ? old : []);
mergedConfig.logging.banner.contributors.push(
sdmStartupMessage(sdm),
sdmExtensionPackStartupMessage(sdm));
_.update(mergedConfig, "listeners",
old => !!old ? old : []);
mergedConfig.listeners.push(
new InvokeSdmStartupListenersAutomationEventListener(sdm),
new SdmGoalMetricReportingAutomationEventListener());
return mergedConfig;
};
}
/**
* Configure how this SDM is going to handle goals
* @param mergedConfig
* @param machine
*/
async function configureJobLaunching(mergedConfig: SoftwareDeliveryMachineConfiguration,
machine: SoftwareDeliveryMachine): Promise<void> {
const forked = process.env.ATOMIST_ISOLATED_GOAL === "true";
if (forked) {
configureSdmToRunExactlyOneGoal(mergedConfig, machine);
} else {
// initialize the GoalSchedulers
for (const goalScheduler of toArray(mergedConfig.sdm.goalScheduler || [])) {
if (!!goalScheduler.initialize) {
await goalScheduler.initialize(mergedConfig);
}
}
_.update(mergedConfig, "commands",
old => !!old ? old : []);
mergedConfig.commands.push(...machine.commandHandlers);
_.update(mergedConfig, "events",
old => !!old ? old : []);
mergedConfig.events.push(...machine.eventHandlers);
_.update(mergedConfig, "ingesters",
old => !!old ? old : []);
mergedConfig.ingesters.push(...machine.ingesters);
}
}
/**
* Configure SDM to run only one goal
* @param mergedConfig
* @param sdm
*/
function configureSdmToRunExactlyOneGoal(mergedConfig: SoftwareDeliveryMachineConfiguration,
sdm: SoftwareDeliveryMachine): void {
if (process.env.ATOMIST_JOB_NAME) {
mergedConfig.name = process.env.ATOMIST_REGISTRATION_NAME;
} else {
mergedConfig.name = `${mergedConfig.name}-${process.env.ATOMIST_GOAL_ID || guid()}`;
}
// Force ephemeral policy and no handlers or ingesters
mergedConfig.policy = "ephemeral";
mergedConfig.commands = [];
mergedConfig.events = [
() => new FulfillGoalOnRequested(
sdm.goalFulfillmentMapper,
[...sdm.goalExecutionListeners])];
mergedConfig.ingesters = [];
mergedConfig.ws.enabled = false;
mergedConfig.cluster.enabled = false;
mergedConfig.listeners.push(
new GoalExecutionAutomationEventListener(sdm),
new CacheCleanupAutomationEventListener(sdm));
mergedConfig.requestProcessorFactory =
(automations, cfg, listeners) => new GoalExecutionRequestProcessor(automations, cfg, listeners);
// Disable app events for forked clients
mergedConfig.applicationEvents.enabled = false;
}
/**
* Configure SDM to sign and verify goals
* @param mergedConfig
*/
function configureGoalSigning(mergedConfig: SoftwareDeliveryMachineConfiguration): void {
if (!!mergedConfig.sdm.goalSigning && mergedConfig.sdm.goalSigning.enabled === true) {
_.update(mergedConfig, "graphql.listeners",
old => !!old ? old : []);
mergedConfig.graphql.listeners.push(
new GoalSigningAutomationEventListener(mergedConfig.sdm.goalSigning));
}
}
async function registerMetadata(config: Configuration,
machine: SoftwareDeliveryMachine): Promise<void> {
// tslint:disable-next-line:no-implicit-dependencies
const sdmPj = require("@atomist/sdm/package.json");
// tslint:disable-next-line:no-implicit-dependencies
const sdmCorePj = require("@atomist/sdm-core/package.json");
config.metadata = {
...config.metadata,
"atomist.sdm": `${sdmPj.name}:${sdmPj.version}`,
"atomist.sdm-core": `${sdmCorePj.name}:${sdmCorePj.version}`,
"atomist.sdm.name": machine.name,
"atomist.sdm.extension-packs": machine.extensionPacks.map(ex => `${ex.name}:${ex.version}`).join(", "),
};
config.sdm.name = machine.name;
await doWithSdmLocal(() => {
// tslint:disable-next-line:no-implicit-dependencies
const sdmLocalPj = require("@atomist/sdm-local/package.json");
config.metadata["atomist.sdm-local"] = `${sdmLocalPj.name}:${sdmLocalPj.version}`;
});
}
/**
* Perform the given operation with the sdm-local module if it's available.
* If it isn't, silently continue without error.
* @param {(sdmLocal: any) => any} callback
* @return {any}
*/
async function doWithSdmLocal<R>(callback: (sdmLocal: any) => any): Promise<R | undefined> {
if (isInLocalMode() || isGitHubAction()) {
// tslint:disable-next-line:no-implicit-dependencies
const local = attemptToRequire("@atomist/sdm-local", !process.env.ATOMIST_NPM_LOCAL_LINK);
if (local) {
return callback(local) as R;
} else {
logger.warn("Skipping local mode configuration because 'ATOMIST_NPM_LOCAL_LINK' was defined, " +
"but '@atomist/sdm-local' could not be loaded");
return undefined;
}
}
return undefined;
}
/**
* Attempt to NPM require module
* @param module
* @param failOnError
*/
function attemptToRequire<T = any>(module: string, failOnError: boolean): T | null {
try {
return require(module) as T;
} catch (err) {
if (failOnError) {
throw new Error(`Unable to load '${module}'. Please install with 'npm install ${module}'.`);
} else {
return undefined;
}
}
}