forked from spinnaker/deck
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(stages/webhook): Webhook stage spinnaker/spinnaker#1512
Create a new stage for calling an external webhook / service, that will be useful for having a flexible way of interacting with technologies that have not yet been integrated with Spinnaker, for example running AWS lambdas or running Marathon deployments.
- Loading branch information
Albert Manya
committed
Apr 7, 2017
1 parent
d360f2e
commit dcd0c77
Showing
7 changed files
with
318 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
60 changes: 60 additions & 0 deletions
60
...scripts/modules/core/pipeline/config/stages/webhook/webhookExecutionDetails.controller.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import {module, IScope} from 'angular'; | ||
import {IStateParamsService} from 'angular-ui-router'; | ||
import {get} from 'lodash'; | ||
|
||
import { | ||
EXECUTION_DETAILS_SECTION_SERVICE, | ||
ExecutionDetailsSectionService | ||
} from 'core/delivery/details/executionDetailsSection.service'; | ||
|
||
export class WebhookExecutionDetailsCtrl { | ||
static get $inject() { | ||
return ['$stateParams', 'executionDetailsSectionService', '$scope']; | ||
} | ||
|
||
public configSections = ['webhookConfig', 'taskStatus']; | ||
public detailsSection: string; | ||
public failureMessage: string; | ||
public progressMessage: string; | ||
public stage: any; | ||
|
||
constructor(private $stateParams: IStateParamsService, | ||
private executionDetailsSectionService: ExecutionDetailsSectionService, | ||
private $scope: IScope) { | ||
this.stage = this.$scope.stage; | ||
this.initialize(); | ||
this.$scope.$on('$stateChangeSuccess', () => this.initialize()); | ||
} | ||
|
||
public initialized(): void { | ||
this.detailsSection = get<string>(this.$stateParams, 'details', ''); | ||
this.failureMessage = this.getFailureMessage(); | ||
this.progressMessage = this.getProgressMessage(); | ||
} | ||
|
||
private getProgressMessage(): string { | ||
const context = this.stage.context || {}, | ||
buildInfo = context.buildInfo || {}; | ||
return buildInfo.progressMessage; | ||
} | ||
|
||
private getFailureMessage(): string { | ||
let failureMessage = this.stage.failureMessage; | ||
const context = this.stage.context || {}, | ||
buildInfo = context.buildInfo || {}; | ||
if (buildInfo.status === 'TERMINAL') { | ||
failureMessage = `Webhook failed: ${buildInfo.reason}`; | ||
} | ||
return failureMessage; | ||
} | ||
|
||
private initialize(): void { | ||
this.executionDetailsSectionService.synchronizeSection(this.configSections, () => this.initialized()); | ||
} | ||
} | ||
|
||
export const WEBHOOK_EXECUTION_DETAILS_CONTROLLER = 'spinnaker.core.pipeline.stage.webhook.executionDetails.controller'; | ||
module(WEBHOOK_EXECUTION_DETAILS_CONTROLLER, [ | ||
EXECUTION_DETAILS_SECTION_SERVICE, | ||
require('core/delivery/details/executionDetailsSectionNav.directive.js'), | ||
]).controller('WebhookExecutionDetailsCtrl', WebhookExecutionDetailsCtrl); |
36 changes: 36 additions & 0 deletions
36
app/scripts/modules/core/pipeline/config/stages/webhook/webhookExecutionDetails.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<div ng-controller="WebhookExecutionDetailsCtrl as ctrl"> | ||
<execution-details-section-nav sections="ctrl.configSections"></execution-details-section-nav> | ||
<div class="step-section-details" ng-if="ctrl.detailsSection === 'webhookConfig'"> | ||
<div class="row"> | ||
<div class="col-md-12"> | ||
<h5>Webhook Stage Configuration</h5> | ||
<dl class="dl-narrow dl-horizontal"> | ||
<dt>Url</dt> | ||
<dd>{{ctrl.stage.context.url}}</dd> | ||
<dt>Payload</dt> | ||
<dd>{{ctrl.stage.context.payload}}</dd> | ||
</dl> | ||
<dl class="dl-narrow dl-horizontal" | ||
ng-if="ctrl.stage.context.waitForCompletion"> | ||
<dt>Status endpoint</dt> | ||
<dd>{{ctrl.stage.context.statusEndpoint}}</dd> | ||
</dl> | ||
</div> | ||
</div> | ||
<stage-failure-message stage="ctrl.stage" is-failed="ctrl.stage.isFailed" message="ctrl.getException(stage) || ctrl.failureMessage"></stage-failure-message> | ||
<div class="well alert-info" ng-if="ctrl.stage.context.progressMessage || ctrl.stage.status"> | ||
<h4>Results</h4> | ||
<dl class="dl-narrow dl-horizontal ng-scope"> | ||
<dt>Status</dt> | ||
<dd class="ng-binding">{{ctrl.stage.status}}</dd> | ||
<dt>Info</dt> | ||
<dd class="ng-binding">{{ctrl.stage.context.progressMessage}}</dd> | ||
</dl> | ||
</div> | ||
</div> | ||
<div class="step-section-details" ng-if="ctrl.detailsSection === 'taskStatus'"> | ||
<div class="row"> | ||
<execution-step-details item="ctrl.stage"></execution-step-details> | ||
</div> | ||
</div> | ||
</div> |
110 changes: 110 additions & 0 deletions
110
app/scripts/modules/core/pipeline/config/stages/webhook/webhookStage.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
<div class="form-horizontal"> | ||
<stage-config-field label="Webhook URL"> | ||
<input type="text" class="form-control input-sm" ng-model="$ctrl.stage.url"/> | ||
</stage-config-field> | ||
<stage-config-field label="Method"> | ||
<ui-select ng-model="$ctrl.stage.method" class="form-control input-sm"> | ||
<ui-select-match placeholder="Select a method...">{{$select.selected}}</ui-select-match> | ||
<ui-select-choices repeat="method in $ctrl.methods | filter: $select.search"> | ||
<span ng-bind-html="method | highlight: $select.search"></span> | ||
</ui-select-choices> | ||
</ui-select> | ||
</stage-config-field> | ||
<stage-config-field label="Payload" | ||
help-key="pipeline.config.webhook.payload" | ||
ng-if="$ctrl.stage.method !== 'GET' && $ctrl.stage.method !== 'HEAD'"> | ||
<textarea class="code form-control flex-fill" | ||
rows="5" | ||
ng-model="$ctrl.command.payloadJSON" | ||
ng-change="$ctrl.updatePayload()"/> | ||
|
||
<div class="form-group row slide-in" ng-if="$ctrl.command.invalid"> | ||
<div class="col-sm-9 col-sm-offset-3 error-message"> | ||
Error: {{$ctrl.command.errorMessage}} | ||
</div> | ||
</div> | ||
</stage-config-field> | ||
<stage-config-field label="Wait for completion" help-key="pipeline.config.webhook.waitForCompletion"> | ||
<input type="checkbox" class="input-sm" name="waitForCompletion" | ||
ng-model="$ctrl.viewState.waitForCompletion" | ||
ng-change="$ctrl.waitForCompletionChanged()"/> | ||
</stage-config-field> | ||
<div ng-class="{collapse: $ctrl.viewState.waitForCompletion !== true, 'collapse.in': !$ctrl.viewState.waitForCompletion === true}"> | ||
<div class="form-group"> | ||
<div class="col-md-3 sm-label-right">Status URL</div> | ||
<div class="col-md-9 radio"> | ||
<label> | ||
<input type="radio" | ||
ng-model="$ctrl.viewState.statusUrlResolution" | ||
ng-change="$ctrl.statusUrlResolutionChanged()" | ||
value="getMethod" | ||
id="statusUrlResolutionIsGetMethod"/> | ||
GET method against webhook URL | ||
<help-field key="pipeline.config.webhook.statusUrlResolutionIsGetMethod"></help-field> | ||
</label> | ||
</div> | ||
<div class="col-md-9 col-md-offset-3 radio"> | ||
<label> | ||
<input type="radio" | ||
ng-model="$ctrl.viewState.statusUrlResolution" | ||
ng-change="$ctrl.statusUrlResolutionChanged()" | ||
value="locationHeader" | ||
id="statusUrlResolutionIsLocationHeader"/> | ||
From the Location header | ||
<help-field key="pipeline.config.webhook.statusUrlResolutionIsLocationHeader"></help-field> | ||
</label> | ||
</div> | ||
<div class="col-md-9 col-md-offset-3 radio"> | ||
<label> | ||
<input type="radio" | ||
ng-model="$ctrl.viewState.statusUrlResolution" | ||
ng-change="$ctrl.statusUrlResolutionChanged()" | ||
value="webhookResponse" | ||
id="statusUrlResolutionIsWebhookResponse"/> | ||
From webhook's response | ||
<help-field key="pipeline.config.webhook.useStatusUrlFromLocationHeaderFalse"></help-field> | ||
</label> | ||
</div> | ||
<div class="form-group" ng-if="$ctrl.viewState.statusUrlResolution === 'webhookResponse'"> | ||
<div class="col-md-offset-3"> | ||
<stage-config-field label="Status URL path" help-key="pipeline.config.webhook.statusUrlJsonPath"> | ||
<input type="text" | ||
class="form-control input-sm" | ||
ng-model="$ctrl.stage.statusUrlJsonPath" | ||
required /> | ||
</stage-config-field> | ||
</div> | ||
</div> | ||
</div> | ||
<stage-config-field label="Status JsonPath" | ||
help-key="pipeline.config.webhook.statusJsonPath"> | ||
<input type="text" | ||
class="form-control input-sm" | ||
ng-model="$ctrl.stage.statusJsonPath"/> | ||
</stage-config-field> | ||
<stage-config-field label="Progress location" | ||
help-key="pipeline.config.webhook.progressJsonPath"> | ||
<input type="text" | ||
class="form-control input-sm" | ||
ng-model="$ctrl.stage.progressJsonPath"/> | ||
</stage-config-field> | ||
<stage-config-field label="SUCCESS status mapping" | ||
help-key="pipeline.config.webhook.successStatuses"> | ||
<input type="text" | ||
class="form-control input-sm" | ||
ng-model="$ctrl.stage.successStatuses"/> | ||
</stage-config-field> | ||
<stage-config-field label="CANCELED status mapping" | ||
help-key="pipeline.config.webhook.canceledStatuses"> | ||
<input type="text" | ||
class="form-control input-sm" | ||
ng-model="$ctrl.stage.canceledStatuses"/> | ||
</stage-config-field> | ||
<stage-config-field label="TERMINAL status mapping" | ||
help-key="pipeline.config.webhook.terminalStatuses"> | ||
<input type="text" | ||
class="form-control input-sm" | ||
ng-model="$ctrl.stage.terminalStatuses"/> | ||
</stage-config-field> | ||
</div> | ||
</div> |
15 changes: 15 additions & 0 deletions
15
app/scripts/modules/core/pipeline/config/stages/webhook/webhookStage.module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import {module} from 'angular'; | ||
|
||
import {WEBHOOK_STAGE} from './webhookStage'; | ||
import {IGOR_SERVICE} from 'core/ci/igor.service'; | ||
import {WEBHOOK_EXECUTION_DETAILS_CONTROLLER} from './webhookExecutionDetails.controller'; | ||
|
||
export const WEBHOOK_STAGE_MODULE = 'spinnaker.core.pipeline.stage.webhook'; | ||
module(WEBHOOK_STAGE_MODULE, [ | ||
WEBHOOK_STAGE, | ||
require('../stage.module.js'), | ||
require('../core/stage.core.module.js'), | ||
require('core/utils/timeFormatters.js'), | ||
IGOR_SERVICE, | ||
WEBHOOK_EXECUTION_DETAILS_CONTROLLER, | ||
]); |
84 changes: 84 additions & 0 deletions
84
app/scripts/modules/core/pipeline/config/stages/webhook/webhookStage.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import {module, extend} from 'angular'; | ||
|
||
import {JSON_UTILITY_SERVICE, JsonUtilityService} from 'core/utils/json/json.utility.service'; | ||
|
||
interface IViewState { | ||
waitForCompletion?: boolean; | ||
statusUrlResolution: string; | ||
} | ||
|
||
interface ICommand { | ||
errorMessage?: string; | ||
invalid?: boolean; | ||
payloadJSON: string; | ||
} | ||
|
||
export class WebhookStage { | ||
static get $inject() { | ||
return ['stage', 'jsonUtilityService']; | ||
} | ||
|
||
public command: ICommand; | ||
public viewState: IViewState; | ||
public methods: string[]; | ||
|
||
constructor(public stage: any, | ||
private jsonUtilityService: JsonUtilityService) { | ||
this.methods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE']; | ||
|
||
this.viewState = { | ||
waitForCompletion: this.stage.waitForCompletion || false, | ||
statusUrlResolution: this.stage.statusUrlResolution || 'getMethod' | ||
}; | ||
|
||
this.command = { | ||
payloadJSON: this.jsonUtilityService.makeSortedStringFromObject(this.stage.payload || {}), | ||
}; | ||
} | ||
|
||
public updatePayload(): void { | ||
this.command.invalid = false; | ||
this.command.errorMessage = ''; | ||
try { | ||
const parsed = JSON.parse(this.command.payloadJSON); | ||
if (!this.stage.payload) { | ||
this.stage.payload = {}; | ||
} | ||
extend(this.stage.payload, parsed); | ||
} catch (e) { | ||
this.command.invalid = true; | ||
this.command.errorMessage = e.message; | ||
} | ||
} | ||
|
||
public waitForCompletionChanged(): void { | ||
this.stage.waitForCompletion = this.viewState.waitForCompletion; | ||
} | ||
|
||
public statusUrlResolutionChanged(): void { | ||
this.stage.statusUrlResolution = this.viewState.statusUrlResolution; | ||
} | ||
|
||
} | ||
|
||
export const WEBHOOK_STAGE = 'spinnaker.core.pipeline.stage.webhookStage'; | ||
|
||
module(WEBHOOK_STAGE, [ | ||
JSON_UTILITY_SERVICE, | ||
require('../../pipelineConfigProvider.js') | ||
]).config((pipelineConfigProvider: any) => { | ||
pipelineConfigProvider.registerStage({ | ||
label: 'Webhook', | ||
description: 'Runs a Webhook job', | ||
key: 'webhook', | ||
restartable: true, | ||
controller: 'WebhookStageCtrl', | ||
controllerAs: '$ctrl', | ||
templateUrl: require('./webhookStage.html'), | ||
executionDetailsUrl: require('./webhookExecutionDetails.html'), | ||
validators: [ | ||
{type: 'requiredField', fieldName: 'url'}, | ||
], | ||
strategy: true, | ||
}); | ||
}).controller('WebhookStageCtrl', WebhookStage); |