Skip to content

Commit

Permalink
feat(artifacts): allow specific stages to 'produce' artifacts (#4858)
Browse files Browse the repository at this point in the history
  • Loading branch information
lwander committed Feb 15, 2018
1 parent 02733d9 commit 388953d
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 20 deletions.
1 change: 1 addition & 0 deletions app/scripts/modules/core/src/domain/IStageTypeConfig.ts
Expand Up @@ -26,6 +26,7 @@ export interface IStageTypeConfig extends IStageOrTriggerTypeConfig {
nameToCheckInTest?: string;
provides?: string;
providesFor?: string[];
producesArtifacts?: boolean;
restartable?: boolean;
stageFilter?: (stage: IStage) => boolean;
strategy?: boolean;
Expand Down
@@ -0,0 +1,80 @@
import { IComponentOptions, IController, module } from 'angular';

import { UUIDGenerator } from 'core/utils/uuid.service';
import { IStage, IExpectedArtifact } from 'core';

class ProducesArtifactsCtrl implements IController {
public stage: IStage;

constructor() {
'nginject';
}

public hasExpectedArtifacts(): boolean {
return this.stage
&& this.stage.expectedArtifacts instanceof Array
&& this.stage.expectedArtifacts.length > 0;
}

public removeExpectedArtifact(stage: IStage, expectedArtifact: IExpectedArtifact) {
if (!this.hasExpectedArtifacts()) {
return;
}

stage.expectedArtifacts = stage.expectedArtifacts
.filter((a: IExpectedArtifact) => a.id !== expectedArtifact.id);
}

private defaultArtifact() {
return { kind: 'custom' };
}

public addExpectedArtifact() {
const newArtifact = {
matchArtifact: this.defaultArtifact(),
usePriorExecution: false,
useDefaultArtifact: false,
defaultArtifact: this.defaultArtifact(),
id: UUIDGenerator.generateUuid()
};

if (!this.stage.expectedArtifacts) {
this.stage.expectedArtifacts = [];
}

this.stage.expectedArtifacts.push(newArtifact);
}
}

class ProducesArtifactsComponent implements IComponentOptions {
public bindings: any = { stage: '=' };
public controllerAs = 'ctrl';
public controller = ProducesArtifactsCtrl;
public template = `
<div class="container-fluid form-horizontal">
<expected-artifact
ng-repeat="expectedArtifact in ctrl.stage.expectedArtifacts"
remove-expected-artifact="ctrl.removeExpectedArtifact"
context="ctrl.stage"
expected-artifact="expectedArtifact"
application="application">
</expected-artifact>
<div class="row" ng-if="ctrl.hasExpectedArtifacts()">
<p class="col-md-12">
You don't have any expected artifacts for {{ ctrl.stage.name }}.
</p>
</div>
<div class="row">
<div class="col-md-12">
<button class="btn btn-block btn-add-trigger add-new" ng-click="ctrl.addExpectedArtifact()">
<span class="glyphicon glyphicon-plus-sign"></span> Add Artifact
</button>
</div>
</div>
</div>
`;
}

export const PRODUCES_ARTIFACTS = 'spinnaker.core.pipeline.stage.producesArtifacts';
module(PRODUCES_ARTIFACTS, [])
.component('producesArtifacts', new ProducesArtifactsComponent());
Expand Up @@ -67,6 +67,11 @@ <h4 ng-bind="stage.name || '[new stage]'"></h4>
<page-section key="notification" label="Notifications" visible="canConfigureNotifications" badge="stage.notifications.length">
<notification-list level="stage" notifications="stage.notifications" parent="stage"></notification-list>
</page-section>
<render-if-feature feature="artifacts">
<page-section key="producesArtifacts" label="Produces Artifacts" visible="stageProducesArtifacts()">
<produces-artifacts stage="stage"></produces-artifacts>
</page-section>
</render-if-feature>
<page-section key="comments" label="Comments" no-wrapper="true">
<textarea class="form-control" ng-model="stage.comments" rows="3"
placeholder="(Optional) anything that might be helpful to explain the purpose of this stage; HTML is okay"></textarea>
Expand Down
Expand Up @@ -73,6 +73,20 @@ module.exports = angular.module('spinnaker.core.pipeline.config.stage', [
requisiteStageRefIds.includes(stage.refId) ? null : 'Downstream dependencies (unavailable)';
};

$scope.stageProducesArtifacts = function() {
if (!$scope.stage) {
return false;
}

const stageConfig = pipelineConfig.getStageConfig($scope.stage);

if (!stageConfig) {
return false;
} else {
return !!stageConfig.producesArtifacts;
}
};

$scope.updateAvailableDependencyStages = function() {
var availableDependencyStages = pipelineConfigService.getDependencyCandidateStages($scope.pipeline, $scope.stage);
$scope.options.dependencies = availableDependencyStages.map(function(stage) {
Expand Down
@@ -1,29 +1,23 @@
import { equals, IComponentController, IComponentOptions, module } from 'angular';
import { IAttributes, IComponentController, IComponentOptions, module } from 'angular';

import { PIPELINE_CONFIG_PROVIDER } from 'core/pipeline/config/pipelineConfigProvider';
import { IExpectedArtifact, IPipeline } from 'core/domain';
import { IExpectedArtifact } from 'core/domain';

class ExpectedArtifactController implements IComponentController {
public expectedArtifact: IExpectedArtifact;
public pipeline: IPipeline;
public usePriorExecution: boolean;
public removeExpectedArtifact: any;
public context: any;

private static expectedArtifactEquals(first: IExpectedArtifact, other: IExpectedArtifact): boolean {
// Interesting to point out that if two artifact's match artifacts equal, they match the same artifacts & are effectively equal
return equals(first.matchArtifact, other.matchArtifact);
}

public removeExpectedArtifact(): void {
this.pipeline.expectedArtifacts = this.pipeline.expectedArtifacts
.filter(a => !ExpectedArtifactController.expectedArtifactEquals(a, this.expectedArtifact));
public constructor(private $attrs: IAttributes) {
'nginject'

this.pipeline.triggers
.forEach(t => t.expectedArtifactIds = t.expectedArtifactIds
.filter(eid => this.expectedArtifact.id !== eid));
this.usePriorExecution = this.$attrs.$attr.hasOwnProperty('usePriorExecution');
}
}

class ExpectedArtifactComponent implements IComponentOptions {
public bindings = { expectedArtifact: '=', pipeline: '=' };
public bindings = { expectedArtifact: '=', removeExpectedArtifact: '=', context: '=' };
public controller = ExpectedArtifactController;
public controllerAs = 'ctrl';
public template = `
Expand All @@ -36,7 +30,7 @@ class ExpectedArtifactComponent implements IComponentOptions {
<help-field key="pipeline.config.expectedArtifact.matchArtifact"></help-field>
</div>
<div class="col-md-2 col-md-offset-7">
<button class="btn btn-sm btn-default" ng-click="ctrl.removeExpectedArtifact()">
<button class="btn btn-sm btn-default" ng-click="ctrl.removeExpectedArtifact(ctrl.context, ctrl.expectedArtifact)">
<span class="glyphicon glyphicon-trash" uib-tooltip="Remove expected artifact"></span>
<span class="visible-xl-inline">Remove artifact</span>
</button>
Expand All @@ -45,7 +39,7 @@ class ExpectedArtifactComponent implements IComponentOptions {
<artifact is-match artifact="ctrl.expectedArtifact.matchArtifact"></artifact>
If missing
<help-field key="pipeline.config.expectedArtifact.ifMissing"></help-field>
<div class="form-group row">
<div class="form-group row" ng-if="ctrl.usePriorExecution">
<label class="col-md-3 sm-label-right">
Use Prior Execution
</label>
Expand Down
Expand Up @@ -38,6 +38,23 @@ module.exports = angular.module('spinnaker.core.pipeline.config.trigger.triggers
kind: 'custom'
});

this.removeExpectedArtifact = (pipeline, expectedArtifact) => {
if (!pipeline.expectedArtifacts) {
return;
}

pipeline.expectedArtifacts = pipeline.expectedArtifacts
.filter(a => a.id !== expectedArtifact.id);

if (!pipeline.triggers) {
return;
}

pipeline.triggers
.forEach(t => t.expectedArtifactIds = t.expectedArtifactIds
.filter(eid => expectedArtifact.id !== eid));
};

this.addArtifact = () => {
const newArtifact = {
matchArtifact: this.defaultArtifact(),
Expand Down
Expand Up @@ -45,11 +45,11 @@
</p>
</div>
<expected-artifact
use-prior-execution
ng-repeat="expectedArtifact in pipeline.expectedArtifacts"
expected-artifact="expectedArtifact"
pipeline="pipeline"
help-field-key="kubernetes.manifest.requiredArtifactsToBind"
application="application">
context="pipeline"
remove-expected-artifact="triggersCtrl.removeExpectedArtifact">
</expected-artifact>
<hr/>
<div class="row" ng-if="!pipeline.expectedArtifacts.length">
Expand Down
2 changes: 2 additions & 0 deletions app/scripts/modules/core/src/pipeline/pipeline.module.ts
Expand Up @@ -31,6 +31,7 @@ import { EXECUTION_FILTER_SERVICE } from 'core/pipeline/filter/executionFilter.s
import { STAGE_FAILURE_MESSAGE_COMPONENT } from './details/stageFailureMessage.component';
import { STEP_EXECUTION_DETAILS_COMPONENT } from './details/stepExecutionDetails.component';
import { STAGE_SUMMARY_COMPONENT } from './details/stageSummary.component';
import { PRODUCES_ARTIFACTS } from './config/stages/producesArtifacts/producesArtifacts.component';

import './pipeline.less';
import 'angular-ui-sortable';
Expand Down Expand Up @@ -78,6 +79,7 @@ module(PIPELINE_MODULE, [
MANUAL_JUDGMENT_STAGE_MODULE,
require('./config/stages/tagImage/tagImageStage.module').name,
require('./config/stages/pipeline/pipelineStage.module').name,
PRODUCES_ARTIFACTS,
RESIZE_ASG_STAGE,
require('./config/stages/runJob/runJobStage.module').name,
SCALE_DOWN_CLUSTER_STAGE,
Expand Down
Expand Up @@ -23,6 +23,7 @@ module.exports = angular.module('spinnaker.kubernetes.pipeline.stage.runJobStage
cloudProvider: 'kubernetes',
templateUrl: require('./runJobStage.html'),
executionDetailsUrl: require('./runJobExecutionDetails.html'),
producesArtifacts: true,
defaultTimeoutMs: 2 * 60 * 60 * 1000, // 2 hours
validators: [
{ type: 'requiredField', fieldName: 'account' },
Expand Down
Expand Up @@ -29,6 +29,7 @@ module(KUBERNETES_DEPLOY_MANIFEST_STAGE, [
controllerAs: 'ctrl',
executionDetailsUrl: require('./deployManifestExecutionDetails.html'),
executionConfigSections: ['deployStatus', 'taskStatus'],
producesArtifacts: true,
validators: [
{ type: 'requiredField', fieldName: 'moniker.cluster', fieldLabel: 'Cluster' }
],
Expand Down

0 comments on commit 388953d

Please sign in to comment.