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. + + + `, + 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');