@atomist/sdm-core
Version:
Atomist Software Delivery Machine - Implementation
112 lines (100 loc) • 4.21 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 {
automationClientInstance,
EventFired,
GraphQL,
HandlerContext,
HandlerResult,
logger,
Success,
Value,
} from "@atomist/automation-client";
import { EventHandler } from "@atomist/automation-client/lib/decorators";
import { HandleEvent } from "@atomist/automation-client/lib/HandleEvent";
import {
fetchGoalsFromPush,
mapKeyToGoal,
SdmGoalEvent,
SdmGoalState,
SoftwareDeliveryMachineConfiguration,
updateGoal,
} from "@atomist/sdm";
import { SdmGoalKey } from "@atomist/sdm/lib/api/goal/SdmGoalMessage";
import { shouldHandle } from "../../../../internal/delivery/goals/support/validateGoal";
import { verifyGoal } from "../../../../internal/signing/goalSigning";
import { OnAnyFailedSdmGoal } from "../../../../typings/types";
/**
* Skip downstream goals on failed or stopped goal
*/
("Skip downstream goals on failed, stopped or canceled goal",
() => GraphQL.subscription({
name: "OnAnyFailedSdmGoal",
variables: { registration: () => [automationClientInstance()?.configuration?.name] },
}))
export class SkipDownstreamGoalsOnGoalFailure implements HandleEvent<OnAnyFailedSdmGoal.Subscription> {
("")
public configuration: SoftwareDeliveryMachineConfiguration;
public async handle(event: EventFired<OnAnyFailedSdmGoal.Subscription>,
context: HandlerContext): Promise<HandlerResult> {
const failedGoal = event.data.SdmGoal[0] as SdmGoalEvent;
if (!shouldHandle(failedGoal)) {
logger.debug(`Goal ${failedGoal.uniqueName} skipped because not managed by this SDM`);
return Success;
}
await verifyGoal(failedGoal, this.configuration.sdm.goalSigning, context);
const goals = fetchGoalsFromPush(failedGoal);
const goalsToSkip = goals.filter(g => isDependentOn(failedGoal, g, mapKeyToGoal(goals)))
.filter(g => g.state === "planned");
let failedGoalState;
let failedGoalDescription;
switch (failedGoal.state) {
case SdmGoalState.failure:
failedGoalDescription = "failed";
failedGoalState = SdmGoalState.skipped;
break;
case SdmGoalState.stopped:
failedGoalDescription = "stopped goals";
failedGoalState = SdmGoalState.skipped;
break;
case SdmGoalState.canceled:
failedGoalDescription = "was canceled";
failedGoalState = SdmGoalState.canceled;
break;
}
await Promise.all(goalsToSkip.map(g => updateGoal(context, g, {
state: failedGoalState,
description: `${failedGoalState === SdmGoalState.skipped ? "Skipped" : "Canceled"
} ${g.name} because ${failedGoal.name} ${failedGoalDescription}`,
})));
return Success;
}
}
function isDependentOn(failedGoal: SdmGoalKey, goal: SdmGoalEvent, preconditionToGoal: (g: SdmGoalKey) => SdmGoalEvent): boolean {
if (!goal) {
return false;
}
if (!goal.preConditions || goal.preConditions.length === 0) {
return false; // no preconditions? not dependent
}
if (mapKeyToGoal(goal.preConditions)(failedGoal)) {
return true; // the failed goal is one of my preconditions? dependent
}
// otherwise, recurse on my preconditions
return !!goal.preConditions
.map(precondition => isDependentOn(failedGoal, preconditionToGoal(precondition), preconditionToGoal))
.find(a => a); // if one is true, return true
}