diff --git a/app/scripts/modules/core/src/domain/IOrchestratedItem.ts b/app/scripts/modules/core/src/domain/IOrchestratedItem.ts
index f342e6e638d..89f1de975ea 100644
--- a/app/scripts/modules/core/src/domain/IOrchestratedItem.ts
+++ b/app/scripts/modules/core/src/domain/IOrchestratedItem.ts
@@ -27,5 +27,6 @@ export interface IOrchestratedItem extends ITimedItem {
isCanceled: boolean;
isSuspended: boolean;
isPaused: boolean;
+ isHalted: boolean;
runningTime: string;
}
diff --git a/app/scripts/modules/core/src/help/help.contents.ts b/app/scripts/modules/core/src/help/help.contents.ts
index 5ef64d38fbb..1e91edcb1da 100644
--- a/app/scripts/modules/core/src/help/help.contents.ts
+++ b/app/scripts/modules/core/src/help/help.contents.ts
@@ -80,6 +80,10 @@ const helpContents: { [key: string]: string } = {
When this option is enabled, stage will only execute when the supplied expression evaluates true.
The expression does not need to be wrapped in \${ and }.
If this expression evaluates to false, the stages following this stage will still execute.
`,
+ 'pipeline.config.allowIgnoreFailure': `
+ When this option is enabled, users will be able to manually ignore the stage if it failed.
+ You should use this only for stages that other stages don't closely depend on.
+ For example, if later stages depend on the outputs of this stage, you should not allow that option.
`,
'pipeline.config.checkPreconditions.failPipeline': `
Checked - the overall pipeline will fail whenever this precondition is false.
Unchecked - the overall pipeline will continue executing but this particular branch will stop.
`,
diff --git a/app/scripts/modules/core/src/orchestratedItem/orchestratedItem.transformer.ts b/app/scripts/modules/core/src/orchestratedItem/orchestratedItem.transformer.ts
index 54eb42f14de..4c8e40408c7 100644
--- a/app/scripts/modules/core/src/orchestratedItem/orchestratedItem.transformer.ts
+++ b/app/scripts/modules/core/src/orchestratedItem/orchestratedItem.transformer.ts
@@ -83,6 +83,9 @@ export class OrchestratedItemTransformer {
isPaused: {
get: (): boolean => item.status === 'PAUSED',
},
+ isHalted: {
+ get: (): boolean => ['TERMINAL', 'CANCELED', 'STOPPED'].includes(item.status),
+ },
status: {
// Returns either SUCCEEDED, RUNNING, FAILED, CANCELED, or NOT_STARTED
get: (): string => this.normalizeStatus(item),
diff --git a/app/scripts/modules/core/src/pipeline/config/stages/allowIgnoreFailure/allowIgnoreFailure.directive.html b/app/scripts/modules/core/src/pipeline/config/stages/allowIgnoreFailure/allowIgnoreFailure.directive.html
new file mode 100644
index 00000000000..38ae6430972
--- /dev/null
+++ b/app/scripts/modules/core/src/pipeline/config/stages/allowIgnoreFailure/allowIgnoreFailure.directive.html
@@ -0,0 +1,13 @@
+
diff --git a/app/scripts/modules/core/src/pipeline/config/stages/allowIgnoreFailure/allowIgnoreFailure.directive.js b/app/scripts/modules/core/src/pipeline/config/stages/allowIgnoreFailure/allowIgnoreFailure.directive.js
new file mode 100644
index 00000000000..6e432013d7f
--- /dev/null
+++ b/app/scripts/modules/core/src/pipeline/config/stages/allowIgnoreFailure/allowIgnoreFailure.directive.js
@@ -0,0 +1,16 @@
+'use strict';
+
+import { module } from 'angular';
+
+export const CORE_PIPELINE_CONFIG_STAGES_ALLOWIGNOREFAILURE_ALLOWIGNOREFAILURE_DIRECTIVE =
+ 'spinnaker.core.pipeline.stage.allowIgnoreFailure.directive';
+export const name = CORE_PIPELINE_CONFIG_STAGES_ALLOWIGNOREFAILURE_ALLOWIGNOREFAILURE_DIRECTIVE; // for backwards compatibility
+module(CORE_PIPELINE_CONFIG_STAGES_ALLOWIGNOREFAILURE_ALLOWIGNOREFAILURE_DIRECTIVE, []).component(
+ 'allowIgnoreFailure',
+ {
+ bindings: {
+ stage: '<',
+ },
+ templateUrl: require('./allowIgnoreFailure.directive.html'),
+ },
+);
diff --git a/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html b/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html
index 970065b9a2f..2afbfe10617 100644
--- a/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html
+++ b/app/scripts/modules/core/src/pipeline/config/stages/common/executionSummary.html
@@ -2,7 +2,7 @@
Stage details: {{stageSummary.name || stageSummary.type }}
-
+
@@ -49,6 +59,11 @@
+ Failure ignored manually by {{stage.context.ignoreFailureDetails.by}} —
+ {{stage.context.ignoreFailureDetails.time | timestamp}} {{stage.context.ignoreFailureDetails.previousException?
+ 'Previous exception:' + stage.context.ignoreFailureDetails.previousException : ''}}
+
diff --git a/app/scripts/modules/core/src/pipeline/config/stages/stage.html b/app/scripts/modules/core/src/pipeline/config/stages/stage.html
index 4e39f4c955a..960bca3bc96 100644
--- a/app/scripts/modules/core/src/pipeline/config/stages/stage.html
+++ b/app/scripts/modules/core/src/pipeline/config/stages/stage.html
@@ -131,6 +131,7 @@
>
+
+ Warning: Ignoring this failure may have unpredictable results.
+
+ - Downstream stages that depend on the outputs of this stage may fail or behave unexpectedly.
+
+
+ `,
+ submitMethod: (reason: object) =>
+ this.executionService
+ .ignoreStageFailureInExecution(this.execution.id, this.stage.id, reason)
+ .then(() =>
+ this.executionService.waitUntilExecutionMatches(this.execution.id, (execution) => {
+ const updatedStage = execution.stages.find((stage) => stage.id === this.stage.id);
+ return updatedStage && updatedStage.status === 'FAILED_CONTINUE';
+ }),
+ )
+ .then((updated) => this.executionService.updateExecution(this.application, updated)),
+ });
+ }
+
public openManualSkipStageModal(): void {
const topLevelStage = this.getTopLevelStage();
ConfirmationModalService.confirm({
diff --git a/app/scripts/modules/core/src/pipeline/service/execution.service.ts b/app/scripts/modules/core/src/pipeline/service/execution.service.ts
index c3e958ef9d1..49868098c83 100644
--- a/app/scripts/modules/core/src/pipeline/service/execution.service.ts
+++ b/app/scripts/modules/core/src/pipeline/service/execution.service.ts
@@ -538,6 +538,10 @@ export class ExecutionService {
return REST('/pipelines').path(executionId, 'stages', stageId).patch(data);
}
+ public ignoreStageFailureInExecution(executionId: string, stageId: string, reason: object): PromiseLike {
+ return REST('/pipelines').path(executionId, 'stages', stageId, 'ignoreFailure').put(reason);
+ }
+
private stringifyExecution(execution: IExecution): string {
const transient = { ...execution };
transient.stages = transient.stages.filter((s) => s.status !== 'SUCCEEDED' && s.status !== 'NOT_STARTED');