Skip to content

Commit

Permalink
feat(artifact): Custom artifact helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
Lars Wander committed Oct 16, 2017
1 parent 7b14c87 commit 95be870
Show file tree
Hide file tree
Showing 13 changed files with 247 additions and 69 deletions.
1 change: 1 addition & 0 deletions app/scripts/modules/core/src/domain/IArtifact.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface IArtifact {
kind: string; // json model only
type: string;
name: string;
version: string;
Expand Down
8 changes: 8 additions & 0 deletions app/scripts/modules/core/src/domain/IArtifactKindConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface IArtifactKindConfig {
label: string;
description: string;
key: string;
template: string;
controller: string;
controllerAs?: string;
}
1 change: 1 addition & 0 deletions app/scripts/modules/core/src/domain/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from '../search/searchResult/model/IApplicationSearchResult';

export * from './IArtifact'
export * from './IArtifactKindConfig'

export * from './IBuild';
export * from './IBuildDiffInfo';
Expand Down
11 changes: 8 additions & 3 deletions app/scripts/modules/core/src/help/help.contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,22 @@ module(HELP_CONTENTS, [])
the configuration based on the newest server group in the cluster.</p>
<p>If you want to start from scratch, select "None".</p>
<p>You can always edit the cluster configuration after you've created it.</p>`,
'pipeline.config.expectedArtifact.custom.matchArtifact': `
'pipeline.config.expectedArtifact.matchArtifact': `
<p>This specifies which fields in your incoming artifact to match against. Every field that you supply will be used to match against all incoming artifacts. If all fields specified match, the incoming artifact is bound to your pipeline context.</p>
<p>For example, if you want to match against any GCS object, only supply <b>type</b>=<b>gcs/object</b>. If you also want to restrict the matches by other fields, include those as well.</p>`,
'pipeline.config.expectedArtifact.custom.ifMissing': `
<p>For example, if you want to match against any GCS object, only supply <b>type</b> = gcs/object. If you also want to restrict the matches by other fields, include those as well.</p>
<p>Regex is accepted, so you could for example match on a filepath like so <b>name</b> = .*\\.yaml to match all incoming YAML files.</p>`,
'pipeline.config.expectedArtifact.ifMissing': `
<p>If no artifact was supplied by your trigger to match against this expected artifact, you have a few options:
<ul>
<li>1. Attempt to match against an artifact in the prior pipeline execution's context. This ensures that you will always be using the most recently supplied artifact to this pipeline, and is generally a safe choice.</li>
<li>2. If option 1 fails, or isn't specified, you can provide a default artifact with all required fields specified to use instead.</li>
<li>3. Fail the pipeline if options 1 or 2 fail or aren't selected.</li>
</ul>
</p>`,
'pipeline.config.expectedArtifact.defaultArtifact': `
<p>If your artifact either wasn't supplied from a trigger, or it wasn't found in a prior execution, the artifact specified below will end up in your pipeline's execution context.</p>`,
'pipeline.config.expectedArtifact.gcs.name': `
<p>The GCS object name, in the form 'gs://bucket/path/to/file.yml.'</p>`,
'loadBalancer.advancedSettings.healthTimeout': '<p>Configures the timeout, in seconds, for reaching the healthCheck target. Must be less than the interval.</p><p> Default: <b>5</b></p>',
'loadBalancer.advancedSettings.healthInterval': '<p>Configures the interval, in seconds, between ELB health checks. Must be greater than the timeout.</p><p>Default: <b>10</b></p>',
'loadBalancer.advancedSettings.healthyThreshold': '<p>Configures the number of healthy observations before reinstituting an instance into the ELB’s traffic rotation.</p><p>Default: <b>10</b></p>',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import { cloneDeep, intersection, memoize } from 'lodash';
import { $log } from 'ngimport';

import { Application } from 'core/application/application.model';
import { IExecution, IStage, ITriggerTypeConfig, IStageTypeConfig } from 'core/domain';
import { IExecution, IStage, ITriggerTypeConfig, IStageTypeConfig, IArtifactKindConfig, IStageOrTriggerTypeConfig } from 'core/domain';
import { SETTINGS } from 'core/config/settings';
import { IStageOrTriggerTypeConfig } from '@spinnaker/core';

export interface ITransformer {
transform: (application: Application, execution: IExecution) => void;
Expand All @@ -17,6 +16,7 @@ export class PipelineConfigProvider implements IServiceProvider {
private triggerTypes: ITriggerTypeConfig[] = [];
private stageTypes: IStageTypeConfig[] = [];
private transformers: ITransformer[] = [];
private artifactKinds: IArtifactKindConfig[] = [];

constructor() {
this.getStageConfig = memoize(this.getStageConfig.bind(this),
Expand Down Expand Up @@ -58,6 +58,10 @@ export class PipelineConfigProvider implements IServiceProvider {
this.normalizeStageTypes();
}

public registerArtifactKind(artifactKindConfig: IArtifactKindConfig): void {
this.artifactKinds.push(artifactKindConfig);
}

public getExecutionTransformers(): ITransformer[] {
return this.transformers;
}
Expand All @@ -70,6 +74,10 @@ export class PipelineConfigProvider implements IServiceProvider {
return cloneDeep(this.stageTypes);
}

public getArtifactKinds(): IArtifactKindConfig[] {
return cloneDeep(this.artifactKinds);
}

private getCloudProvidersForStage(type: IStageTypeConfig, allStageTypes: IStageTypeConfig[], providers: string[]): string[] {
let cloudProviders: string[] = [];
if (type.providesFor) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,88 @@
import { IComponentController, IComponentOptions, module } from 'angular';
import {
ICompileService,
IComponentOptions,
IController,
IControllerService,
IRootElementService,
IRootScopeService,
module
} from 'angular';
import { IArtifact, IArtifactKindConfig } from 'core/domain';
import { PipelineConfigProvider } from 'core/pipeline';

import { PIPELINE_CONFIG_PROVIDER } from 'core/pipeline/config/pipelineConfigProvider';
import { IArtifact } from 'core/domain/IArtifact';

class ArtifactController implements IComponentController {
class ArtifactCtrl implements IController {
public artifact: IArtifact;
public options: IArtifactKindConfig[];
public description: string;

constructor(private pipelineConfig: PipelineConfigProvider,
private $controller: IControllerService,
private $compile: ICompileService,
private $element: IRootElementService,
private $rootScope: IRootScopeService) {
'ngInject';
this.options = this.pipelineConfig.getArtifactKinds();
this.loadArtifactKind();
}

public loadArtifactKind(): void {
const kind = this.artifact.kind;
if (!kind) {
return;
}
const artifactKindConfig = this.options.filter(function(config) {
return config.key === kind;
});

if (artifactKindConfig.length) {
const config = artifactKindConfig[0];
const template: Element = config.template as any;
this.description = config.description;

const ctrl = config.controller;
const controller = this.$controller(ctrl, {artifact: this.artifact});
const scope = this.$rootScope.$new();
const controllerAs = config.controllerAs;
if (controllerAs) {
scope[config.controllerAs] = controller;
} else {
scope['ctrl'] = controller;
}

const templateBody = this.$compile(template)(scope) as any;
this.$element.find('.artifact-body').html(templateBody);
}
}
}

class ArtifactComponent implements IComponentOptions {
public bindings = { artifact: '=' };
public templateUrl = require('./artifact.html');
public controller = ArtifactController;
public bindings: any = {artifact: '='};
public controller: any = ArtifactCtrl;
public controllerAs = 'ctrl';
public template = `
<div class="form-group">
<div class="col-md-2 col-md-offset-1">
<select class="input-sm"
required
ng-change="ctrl.loadArtifactKind()"
ng-options="option.key as option.label for option in ctrl.options"
ng-model="ctrl.artifact.kind">
<option style="display:none" value="">Select a kind</option>
</select>
</div>
<div class="col-md-9">
{{ctrl.description}}
</div>
</div>
<hr>
<div class="form-group">
<div class="col-md-12">
<div class="artifact-body"></div>
</div>
</div>
`;
}

export const ARTIFACT = 'spinnaker.core.pipeline.trigger.artifact';
module(ARTIFACT, [
PIPELINE_CONFIG_PROVIDER,
]).component('artifact', new ArtifactComponent());
export const ARTIFACT = 'spinnaker.core.pipeline.config.trigger.artifacts.artifact';
module(ARTIFACT, [])
.component('artifact', new ArtifactComponent());
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { module } from 'angular';

import { EXPECTED_ARTIFACT } from './expectedArtifact.component';
import { CUSTOM_ARTIFACT } from './custom/custom.artifact';
import { GCS_ARTIFACT } from './gcs/gcs.artifact';
import { ARTIFACT } from './artifact.component';

export const ARTIFACT_MODULE = 'spinnaker.core.pipeline.config.trigger.artifacts';

module(ARTIFACT_MODULE, [
EXPECTED_ARTIFACT,
CUSTOM_ARTIFACT,
GCS_ARTIFACT,
ARTIFACT,
]);
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
import { IController, module } from 'angular';

import { PIPELINE_CONFIG_PROVIDER } from 'core/pipeline/config/pipelineConfigProvider';
import { IArtifact } from 'core/domain/IArtifact';
import { PipelineConfigProvider } from 'core/pipeline';

class CustomArtifactController implements IController {
constructor(public artifact: IArtifact) {
'ngInject';
}
}

export const CUSTOM_ARTIFACT = 'spinnaker.core.pipeline.trigger.custom.artifact';
module(CUSTOM_ARTIFACT, [
PIPELINE_CONFIG_PROVIDER,
]).config((pipelineConfigProvider: PipelineConfigProvider) => {
pipelineConfigProvider.registerArtifactKind({
label: 'Custom',
description: 'A custom-defined artifact.',
key: 'custom',
controller: 'customArtifactCtrl',
controllerAs: 'ctrl',
template: `
<div class="col-md-12">
<div class="form-group row">
<label class="col-md-2 sm-label-right">
Expand Down Expand Up @@ -44,4 +67,8 @@
class="form-control input-sm"
ng-model="ctrl.artifact.reference"/>
</div>
</div>
</div>
`
});
}).controller('customArtifactCtrl', CustomArtifactController);
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { equals, IComponentController, IComponentOptions, module } from 'angular';

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

class ExpectedArtifactController implements IComponentController {
public expectedArtifact: IExpectedArtifact;
Expand All @@ -25,12 +24,57 @@ class ExpectedArtifactController implements IComponentController {

class ExpectedArtifactComponent implements IComponentOptions {
public bindings = { expectedArtifact: '=', pipeline: '=' };
public templateUrl = require('./expectedArtifact.html');
public controller = ExpectedArtifactController;
public controllerAs = 'ctrl';
public template = `
<div class="row">
<div class="col-md-12">
<div class="form-horizontal panel-pipeline-phase">
<div class="form-group row">
<div class="col-md-3">
Match against
<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()">
<span class="glyphicon glyphicon-trash" uib-tooltip="Remove expected artifact"></span>
<span class="visible-xl-inline">Remove artifact</span>
</button>
</div>
</div>
<artifact artifact="ctrl.expectedArtifact.matchArtifact"></artifact>
If missing
<help-field key="pipeline.config.expectedArtifact.ifMissing"></help-field>
<div class="form-group row">
<label class="col-md-3 sm-label-right">
Use Prior Execution
</label>
<input class="col-md-1" type="checkbox" ng-model="ctrl.expectedArtifact.usePriorExecution">
</div>
<div class="form-group row">
<label class="col-md-3 sm-label-right">
Use Default Artifact
</label>
<input class="col-md-1" type="checkbox" ng-model="ctrl.expectedArtifact.useDefaultArtifact">
</div>
<div ng-show="ctrl.expectedArtifact.useDefaultArtifact">
<div class="form-group row">
<div class="col-md-3">
Default artifact
<help-field key="pipeline.config.expectedArtifact.defaultArtifact"></help-field>
</div>
</div>
<div class="form-group row">
<artifact artifact="ctrl.expectedArtifact.defaultArtifact"></artifact>
</div>
</div>
</div>
</div>
</div>
`
}

export const EXPECTED_ARTIFACT = 'spinnaker.core.pipeline.trigger.expected.artifact';
export const EXPECTED_ARTIFACT = 'spinnaker.core.pipeline.trigger.artifacts.expected';
module(EXPECTED_ARTIFACT, [
PIPELINE_CONFIG_PROVIDER,
]).component('expectedArtifact', new ExpectedArtifactComponent());

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { IController, module } from 'angular';

import { PIPELINE_CONFIG_PROVIDER } from 'core/pipeline/config/pipelineConfigProvider';
import { IArtifact } from 'core/domain/IArtifact';
import { PipelineConfigProvider } from 'core/pipeline';

class GcsArtifactController implements IController {
constructor(public artifact: IArtifact) {
'ngInject';
this.artifact.type = 'gcs/object';
}
}

export const GCS_ARTIFACT = 'spinnaker.core.pipeline.trigger.gcs.artifact';
module(GCS_ARTIFACT, [
PIPELINE_CONFIG_PROVIDER,
]).config((pipelineConfigProvider: PipelineConfigProvider) => {
pipelineConfigProvider.registerArtifactKind({
label: 'GCS',
description: 'A GCS object.',
key: 'gcs',
controller: 'gcsArtifactCtrl',
controllerAs: 'ctrl',
template: `
<div class="col-md-12">
<div class="form-group row">
<label class="col-md-2 sm-label-right">
Object path
<help-field key="pipeline.config.expectedArtifact.gcs.name"></help-field>
</label>
<div class="col-md-8">
<input type="text"
placeholder="gs://bucket/path/to/file"
class="form-control input-sm"
ng-model="ctrl.artifact.name"/>
</div>
</div>
</div>
`,
});
}).controller('gcsArtifactCtrl', GcsArtifactController);

Loading

0 comments on commit 95be870

Please sign in to comment.