Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(artifact): Custom artifact helpers #4267

Merged
merged 1 commit into from
Oct 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this show up (given display: none)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, but only when ctrl.artifact.kind is null (if someone manually edits the JSON). Otherwise it's not an option, and disappears when something is selected.

</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