1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | import {
|
18 | Configuration,
|
19 | ConfigurationPostProcessor,
|
20 | logger,
|
21 | } from "@atomist/automation-client";
|
22 | import {
|
23 | allSatisfied,
|
24 | AnyPush,
|
25 | Goal,
|
26 | GoalContribution,
|
27 | goals,
|
28 | Goals,
|
29 | GoalWithFulfillment,
|
30 | notGoalTest,
|
31 | PushListenerInvocation,
|
32 | PushTest,
|
33 | SdmContext,
|
34 | SoftwareDeliveryMachine,
|
35 | whenPushSatisfies,
|
36 | } from "@atomist/sdm";
|
37 | import * as _ from "lodash";
|
38 | import {
|
39 | ConfigureOptions,
|
40 | configureSdm,
|
41 | } from "../internal/machine/configureSdm";
|
42 | import { LocalSoftwareDeliveryMachineConfiguration } from "../internal/machine/LocalSoftwareDeliveryMachineOptions";
|
43 | import { toArray } from "../util/misc/array";
|
44 | import { createSoftwareDeliveryMachine } from "./machineFactory";
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | export interface GoalStructure {
|
50 |
|
51 | |
52 |
|
53 |
|
54 |
|
55 |
|
56 | test?: PushTest | PushTest[];
|
57 |
|
58 |
|
59 | dependsOn?: string | Goal | Array<string | Goal>;
|
60 |
|
61 | |
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 | goals: Goal | Goals | Array<Goal | Goals | Array<Goal | Goals>>;
|
87 | }
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 | export type GoalData = Record<string, GoalStructure>;
|
95 |
|
96 |
|
97 |
|
98 |
|
99 | export type DeliveryGoals = Record<string, Goal | GoalWithFulfillment>;
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | export type AllGoals = DeliveryGoals;
|
105 |
|
106 |
|
107 |
|
108 |
|
109 | export type GoalCreator<G extends DeliveryGoals> = (sdm: SoftwareDeliveryMachine) => Promise<G>;
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | export type GoalConfigurer<G extends DeliveryGoals> = (sdm: SoftwareDeliveryMachine, goals: G) => Promise<void>;
|
115 |
|
116 |
|
117 |
|
118 |
|
119 | export type CreateGoals<G extends DeliveryGoals> = (creator: GoalCreator<G>,
|
120 | configurers?: GoalConfigurer<G> | Array<GoalConfigurer<G>>) => Promise<G>;
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 | export type Configurer<G extends DeliveryGoals, F extends SdmContext = PushListenerInvocation> =
|
127 | (sdm: SoftwareDeliveryMachine & { createGoals: CreateGoals<G> }) => Promise<void | GoalData | Array<GoalContribution<F>>>;
|
128 |
|
129 |
|
130 |
|
131 |
|
132 | export type ConfigurationPreProcessor = (cfg: LocalSoftwareDeliveryMachineConfiguration) =>
|
133 | Promise<LocalSoftwareDeliveryMachineConfiguration>;
|
134 |
|
135 | export interface ConfigureMachineOptions extends ConfigureOptions {
|
136 | |
137 |
|
138 |
|
139 |
|
140 | name?: string;
|
141 | |
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 | preProcessors?: ConfigurationPreProcessor | ConfigurationPreProcessor[];
|
149 | |
150 |
|
151 |
|
152 | postProcessors?: ConfigurationPostProcessor | ConfigurationPostProcessor[];
|
153 | }
|
154 |
|
155 |
|
156 |
|
157 |
|
158 | export function configure<G extends DeliveryGoals, T extends SdmContext = PushListenerInvocation>(
|
159 | configurer: Configurer<G, T>,
|
160 | options: ConfigureMachineOptions = {}): Configuration {
|
161 | return {
|
162 | postProcessors: [
|
163 | configureSdm(async cfg => {
|
164 | let cfgToUse = cfg;
|
165 |
|
166 |
|
167 | if (!!options.preProcessors) {
|
168 | for (const preProcessor of toArray(options.preProcessors)) {
|
169 | cfgToUse = await preProcessor(cfgToUse);
|
170 | }
|
171 | }
|
172 |
|
173 | const sdm = createSoftwareDeliveryMachine(
|
174 | {
|
175 | name: options.name || cfgToUse.name,
|
176 | configuration: cfgToUse,
|
177 | });
|
178 |
|
179 | const configured = await invokeConfigurer(sdm, configurer);
|
180 |
|
181 | if (Array.isArray(configured)) {
|
182 | sdm.withPushRules(configured[0], ...configured.slice(1));
|
183 | } else if (!!configured) {
|
184 | const goalContributions = convertGoalData(configured);
|
185 | if (goalContributions.length > 0) {
|
186 | sdm.withPushRules(goalContributions[0], ...(goalContributions.slice(1) || []));
|
187 | }
|
188 | }
|
189 |
|
190 | return sdm;
|
191 | }, options),
|
192 | ...(toArray(options.postProcessors || [])),
|
193 | ],
|
194 | };
|
195 | }
|
196 |
|
197 |
|
198 |
|
199 |
|
200 | export function convertGoalData(goalData: GoalData): Array<GoalContribution<any>> {
|
201 | const goalContributions: Array<GoalContribution<any>> = [];
|
202 |
|
203 | _.forEach(goalData, (v, k) => {
|
204 | (v as any).__goals = [];
|
205 |
|
206 | const gs = goals(k.replace(/_/g, " "));
|
207 | let lg: Array<Goal | Goals>;
|
208 |
|
209 | if (!!v.dependsOn) {
|
210 | lg = [];
|
211 | toArray(v.dependsOn).forEach(d => {
|
212 | if (typeof d === "string") {
|
213 | if (!!goalData[d] && !!(goalData[d] as any).__goals) {
|
214 | lg.push(...(goalData[d] as any).__goals);
|
215 | } else {
|
216 | throw new Error(
|
217 | `Provided dependsOn goals with name '${d}' do not exist or is after current goals named '${k}'`);
|
218 | }
|
219 | } else {
|
220 | lg.push(...toArray(d));
|
221 | }
|
222 | });
|
223 | }
|
224 |
|
225 | toArray(v.goals || []).forEach(g => {
|
226 | (v as any).__goals.push(...(Array.isArray(g) ? (g) : [g]));
|
227 | if (!!lg) {
|
228 | gs.plan(...convertGoals(g)).after(...convertGoals(lg));
|
229 | } else {
|
230 | gs.plan(...convertGoals(g));
|
231 | }
|
232 | lg = toArray(g);
|
233 | });
|
234 |
|
235 | goalContributions.push(whenPushSatisfies(convertPushTest(v.test)).setGoals(gs));
|
236 | });
|
237 |
|
238 | return goalContributions;
|
239 | }
|
240 |
|
241 |
|
242 |
|
243 |
|
244 | export async function invokeConfigurer(sdm: SoftwareDeliveryMachine,
|
245 | configurer: Configurer<any, any>): Promise<void | GoalData | Array<GoalContribution<any>>> {
|
246 |
|
247 | try {
|
248 |
|
249 | (sdm as any).createGoals = async (creator: GoalCreator<any>,
|
250 | configurers: GoalConfigurer<any> | Array<GoalConfigurer<any>>) => {
|
251 |
|
252 | let gc;
|
253 | try {
|
254 | gc = await creator(sdm);
|
255 | } catch (e) {
|
256 | e.message = `Creating goals failed: ${e.message}`;
|
257 | logger.error(e.message);
|
258 | throw e;
|
259 | }
|
260 |
|
261 | try {
|
262 | if (!!configurers) {
|
263 | for (const c of toArray(configurers)) {
|
264 | await c(sdm, gc);
|
265 | }
|
266 | }
|
267 | } catch (e) {
|
268 | e.message = `Configuring goals failed: ${e.message}`;
|
269 | logger.error(e.message);
|
270 | throw e;
|
271 | }
|
272 | return gc;
|
273 | };
|
274 |
|
275 | return configurer(sdm as any);
|
276 | } finally {
|
277 | delete (sdm as any).createGoals;
|
278 | }
|
279 | }
|
280 |
|
281 | function convertPushTest(test: PushTest | PushTest[]): PushTest {
|
282 | if (Array.isArray(test)) {
|
283 | return allSatisfied(...test.map(wrapTest));
|
284 | } else {
|
285 | return wrapTest(test || AnyPush);
|
286 | }
|
287 | }
|
288 |
|
289 | function wrapTest(test: PushTest): PushTest {
|
290 | if (!!(test as any).pushTest) {
|
291 | return test;
|
292 | } else {
|
293 | return notGoalTest(test);
|
294 | }
|
295 | }
|
296 |
|
297 | function convertGoals(gs: Goal | Goals | Array<Goal | Goals>): Array<Goal | Goals> {
|
298 | if (Array.isArray(gs)) {
|
299 | return gs;
|
300 | } else {
|
301 | return [gs];
|
302 | }
|
303 | }
|