UNPKG

9.72 kBPlain TextView Raw
1/*
2 * Copyright © 2019 Atomist, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {
18 Configuration,
19 ConfigurationPostProcessor,
20 guid,
21 logger,
22} from "@atomist/automation-client";
23import {
24 ConfigurationValues,
25 SoftwareDeliveryMachine,
26 SoftwareDeliveryMachineConfiguration,
27 validateConfigurationValues,
28} from "@atomist/sdm";
29import * as _ from "lodash";
30import { FulfillGoalOnRequested } from "../../handlers/events/delivery/goals/FulfillGoalOnRequested";
31import {
32 GoalExecutionAutomationEventListener,
33 GoalExecutionRequestProcessor,
34} from "../../handlers/events/delivery/goals/goalExecution";
35import { CacheCleanupAutomationEventListener } from "../../handlers/events/delivery/goals/k8s/CacheCleanupAutomationEventListener";
36import { defaultSoftwareDeliveryMachineConfiguration } from "../../machine/defaultSoftwareDeliveryMachineConfiguration";
37import { toArray } from "../../util/misc/array";
38import { GoalSigningAutomationEventListener } from "../signing/goalSigning";
39import { SdmGoalMetricReportingAutomationEventListener } from "../util/SdmGoalMetricReportingAutomationEventListener";
40import {
41 sdmExtensionPackStartupMessage,
42 sdmStartupMessage,
43} from "../util/startupMessage";
44import { InvokeSdmStartupListenersAutomationEventListener } from "./InvokeSdmStartupListenersAutomationEventListener";
45import { LocalSoftwareDeliveryMachineConfiguration } from "./LocalSoftwareDeliveryMachineOptions";
46import {
47 isGitHubAction,
48 isInLocalMode,
49} from "./modes";
50
51/**
52 * Options passed to the set up of the SDM.
53 */
54// tslint:disable-next-line:no-empty-interface
55export interface ConfigureOptions extends ConfigurationValues {
56 // Empty for future extensibility
57}
58
59/**
60 * Type that can create a fully configured SDM
61 */
62export type SoftwareDeliveryMachineMaker =
63 (configuration: LocalSoftwareDeliveryMachineConfiguration) => SoftwareDeliveryMachine | Promise<SoftwareDeliveryMachine>;
64
65/**
66 * Configure and set up a Software Delivery Machine instance with the automation-client framework for standalone
67 * or single goal based execution
68 * @param {(configuration: (Configuration & SoftwareDeliveryMachineOptions)) => SoftwareDeliveryMachine} machineMaker
69 * @param {ConfigureOptions} options
70 * @returns {ConfigurationPostProcessor}
71 */
72export function configureSdm(machineMaker: SoftwareDeliveryMachineMaker,
73 options: ConfigureOptions = {}): ConfigurationPostProcessor<LocalSoftwareDeliveryMachineConfiguration> {
74
75 return async (config: Configuration) => {
76 let mergedConfig = config as LocalSoftwareDeliveryMachineConfiguration;
77
78 // Configure the local SDM
79 mergedConfig = await doWithSdmLocal<LocalSoftwareDeliveryMachineConfiguration>(local => {
80 return local.configureLocal()(mergedConfig);
81 }) || mergedConfig;
82
83 const defaultSdmConfiguration = defaultSoftwareDeliveryMachineConfiguration(config);
84 mergedConfig = _.merge(defaultSdmConfiguration, mergedConfig);
85
86 validateConfigurationValues(mergedConfig, options);
87 const sdm = await machineMaker(mergedConfig);
88
89 await doWithSdmLocal<void>(local =>
90 sdm.addExtensionPacks(local.LocalLifecycle, local.LocalSdmConfig),
91 );
92
93 // Configure the job forking ability
94 await configureJobLaunching(mergedConfig, sdm);
95 configureGoalSigning(mergedConfig);
96
97 await registerMetadata(mergedConfig, sdm);
98
99 // Register startup message detail
100 _.update(mergedConfig, "logging.banner.contributors",
101 old => !!old ? old : []);
102 mergedConfig.logging.banner.contributors.push(
103 sdmStartupMessage(sdm),
104 sdmExtensionPackStartupMessage(sdm));
105
106 _.update(mergedConfig, "listeners",
107 old => !!old ? old : []);
108 mergedConfig.listeners.push(
109 new InvokeSdmStartupListenersAutomationEventListener(sdm),
110 new SdmGoalMetricReportingAutomationEventListener());
111
112 return mergedConfig;
113 };
114}
115
116/**
117 * Configure how this SDM is going to handle goals
118 * @param mergedConfig
119 * @param machine
120 */
121async function configureJobLaunching(mergedConfig: SoftwareDeliveryMachineConfiguration,
122 machine: SoftwareDeliveryMachine): Promise<void> {
123 const forked = process.env.ATOMIST_ISOLATED_GOAL === "true";
124 if (forked) {
125 configureSdmToRunExactlyOneGoal(mergedConfig, machine);
126 } else {
127 // initialize the GoalSchedulers
128 for (const goalScheduler of toArray(mergedConfig.sdm.goalScheduler || [])) {
129 if (!!goalScheduler.initialize) {
130 await goalScheduler.initialize(mergedConfig);
131 }
132 }
133
134 _.update(mergedConfig, "commands",
135 old => !!old ? old : []);
136 mergedConfig.commands.push(...machine.commandHandlers);
137
138 _.update(mergedConfig, "events",
139 old => !!old ? old : []);
140 mergedConfig.events.push(...machine.eventHandlers);
141
142 _.update(mergedConfig, "ingesters",
143 old => !!old ? old : []);
144 mergedConfig.ingesters.push(...machine.ingesters);
145 }
146}
147
148/**
149 * Configure SDM to run only one goal
150 * @param mergedConfig
151 * @param sdm
152 */
153function configureSdmToRunExactlyOneGoal(mergedConfig: SoftwareDeliveryMachineConfiguration,
154 sdm: SoftwareDeliveryMachine): void {
155 if (process.env.ATOMIST_JOB_NAME) {
156 mergedConfig.name = process.env.ATOMIST_REGISTRATION_NAME;
157 } else {
158 mergedConfig.name = `${mergedConfig.name}-${process.env.ATOMIST_GOAL_ID || guid()}`;
159 }
160
161 // Force ephemeral policy and no handlers or ingesters
162 mergedConfig.policy = "ephemeral";
163 mergedConfig.commands = [];
164 mergedConfig.events = [
165 () => new FulfillGoalOnRequested(
166 sdm.goalFulfillmentMapper,
167 [...sdm.goalExecutionListeners])];
168 mergedConfig.ingesters = [];
169 mergedConfig.ws.enabled = false;
170 mergedConfig.cluster.enabled = false;
171
172 mergedConfig.listeners.push(
173 new GoalExecutionAutomationEventListener(sdm),
174 new CacheCleanupAutomationEventListener(sdm));
175 mergedConfig.requestProcessorFactory =
176 (automations, cfg, listeners) => new GoalExecutionRequestProcessor(automations, cfg, listeners);
177
178 // Disable app events for forked clients
179 mergedConfig.applicationEvents.enabled = false;
180}
181
182/**
183 * Configure SDM to sign and verify goals
184 * @param mergedConfig
185 */
186function configureGoalSigning(mergedConfig: SoftwareDeliveryMachineConfiguration): void {
187 if (!!mergedConfig.sdm.goalSigning && mergedConfig.sdm.goalSigning.enabled === true) {
188 _.update(mergedConfig, "graphql.listeners",
189 old => !!old ? old : []);
190 mergedConfig.graphql.listeners.push(
191 new GoalSigningAutomationEventListener(mergedConfig.sdm.goalSigning));
192 }
193}
194
195async function registerMetadata(config: Configuration,
196 machine: SoftwareDeliveryMachine): Promise<void> {
197 // tslint:disable-next-line:no-implicit-dependencies
198 const sdmPj = require("@atomist/sdm/package.json");
199 // tslint:disable-next-line:no-implicit-dependencies
200 const sdmCorePj = require("@atomist/sdm-core/package.json");
201
202 config.metadata = {
203 ...config.metadata,
204 "atomist.sdm": `${sdmPj.name}:${sdmPj.version}`,
205 "atomist.sdm-core": `${sdmCorePj.name}:${sdmCorePj.version}`,
206 "atomist.sdm.name": machine.name,
207 "atomist.sdm.extension-packs": machine.extensionPacks.map(ex => `${ex.name}:${ex.version}`).join(", "),
208 };
209
210 config.sdm.name = machine.name;
211
212 await doWithSdmLocal(() => {
213 // tslint:disable-next-line:no-implicit-dependencies
214 const sdmLocalPj = require("@atomist/sdm-local/package.json");
215 config.metadata["atomist.sdm-local"] = `${sdmLocalPj.name}:${sdmLocalPj.version}`;
216 });
217}
218
219/**
220 * Perform the given operation with the sdm-local module if it's available.
221 * If it isn't, silently continue without error.
222 * @param {(sdmLocal: any) => any} callback
223 * @return {any}
224 */
225async function doWithSdmLocal<R>(callback: (sdmLocal: any) => any): Promise<R | undefined> {
226 if (isInLocalMode() || isGitHubAction()) {
227 // tslint:disable-next-line:no-implicit-dependencies
228 const local = attemptToRequire("@atomist/sdm-local", !process.env.ATOMIST_NPM_LOCAL_LINK);
229 if (local) {
230 return callback(local) as R;
231 } else {
232 logger.warn("Skipping local mode configuration because 'ATOMIST_NPM_LOCAL_LINK' was defined, " +
233 "but '@atomist/sdm-local' could not be loaded");
234 return undefined;
235 }
236 }
237 return undefined;
238}
239
240/**
241 * Attempt to NPM require module
242 * @param module
243 * @param failOnError
244 */
245function attemptToRequire<T = any>(module: string, failOnError: boolean): T | null {
246 try {
247 return require(module) as T;
248 } catch (err) {
249 if (failOnError) {
250 throw new Error(`Unable to load '${module}'. Please install with 'npm install ${module}'.`);
251 } else {
252 return undefined;
253 }
254 }
255
256}