@spinnaker/docker 0.0.0-main-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1413 -0
- package/LICENSE.txt +203 -0
- package/dist/docker.module.d.ts +2 -0
- package/dist/image/DockerImageAndTagSelector.d.ts +74 -0
- package/dist/image/DockerImageReader.d.ts +12 -0
- package/dist/image/DockerImageUtils.d.ts +10 -0
- package/dist/image/index.d.ts +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +976 -0
- package/dist/index.js.map +1 -0
- package/dist/pipeline/stages/bake/bakeExecutionDetails.controller.d.ts +2 -0
- package/dist/pipeline/stages/bake/dockerBakeStage.d.ts +2 -0
- package/dist/pipeline/trigger/DockerTrigger.d.ts +1 -0
- package/dist/pipeline/trigger/DockerTriggerConfig.d.ts +8 -0
- package/dist/pipeline/trigger/DockerTriggerTemplate.d.ts +28 -0
- package/package.json +41 -0
- package/src/docker.module.ts +7 -0
- package/src/image/DockerImageAndTagSelector.tsx +735 -0
- package/src/image/DockerImageReader.ts +42 -0
- package/src/image/DockerImageUtils.spec.ts +148 -0
- package/src/image/DockerImageUtils.ts +52 -0
- package/src/image/index.ts +3 -0
- package/src/index.ts +2 -0
- package/src/pipeline/stages/bake/bakeExecutionDetails.controller.js +36 -0
- package/src/pipeline/stages/bake/bakeExecutionDetails.html +47 -0
- package/src/pipeline/stages/bake/bakeStage.html +32 -0
- package/src/pipeline/stages/bake/dockerBakeStage.js +81 -0
- package/src/pipeline/trigger/DockerTrigger.tsx +44 -0
- package/src/pipeline/trigger/DockerTriggerConfig.tsx +40 -0
- package/src/pipeline/trigger/DockerTriggerTemplate.tsx +246 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { IFindImageParams, IFindTagsParams, IImage } from '@spinnaker/core';
|
|
2
|
+
import { REST, RetryService } from '@spinnaker/core';
|
|
3
|
+
|
|
4
|
+
export interface IDockerImage extends IImage {
|
|
5
|
+
account: string;
|
|
6
|
+
registry: string;
|
|
7
|
+
repository: string;
|
|
8
|
+
tag: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class DockerImageReader {
|
|
12
|
+
public static getImage(imageName: string, region: string, credentials: string): PromiseLike<IDockerImage> {
|
|
13
|
+
return REST('/images')
|
|
14
|
+
.path(credentials, region, imageName)
|
|
15
|
+
.query({ provider: 'docker' })
|
|
16
|
+
.get()
|
|
17
|
+
.then((results: IDockerImage[]) => (results && results.length ? results[0] : null))
|
|
18
|
+
.catch((): IDockerImage => null);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static findImages(params: IFindImageParams): PromiseLike<IDockerImage[]> {
|
|
22
|
+
return RetryService.buildRetrySequence<IDockerImage[]>(
|
|
23
|
+
() => REST('/images/find').query(params).get(),
|
|
24
|
+
(results: IDockerImage[]) => results.length > 0,
|
|
25
|
+
10,
|
|
26
|
+
1000,
|
|
27
|
+
)
|
|
28
|
+
.then((results: IDockerImage[]) => results)
|
|
29
|
+
.catch((): IDockerImage[] => []);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public static findTags(params: IFindTagsParams): PromiseLike<string[]> {
|
|
33
|
+
return RetryService.buildRetrySequence<string[]>(
|
|
34
|
+
() => REST('/images/tags').query(params).get(),
|
|
35
|
+
(results: string[]) => results.length > 0,
|
|
36
|
+
10,
|
|
37
|
+
1000,
|
|
38
|
+
)
|
|
39
|
+
.then((results: string[]) => results)
|
|
40
|
+
.catch((): string[] => []);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { DockerImageUtils } from './DockerImageUtils';
|
|
2
|
+
|
|
3
|
+
describe('imageId parsing', () => {
|
|
4
|
+
it('parses undefined without choking', () => {
|
|
5
|
+
expect(DockerImageUtils.splitImageId(undefined)).toEqual({
|
|
6
|
+
organization: '',
|
|
7
|
+
repository: '',
|
|
8
|
+
digest: undefined,
|
|
9
|
+
tag: undefined,
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('parses image with no organization and no tag/digest', () => {
|
|
14
|
+
const imageId = 'image';
|
|
15
|
+
expect(DockerImageUtils.splitImageId(imageId)).toEqual({
|
|
16
|
+
organization: '',
|
|
17
|
+
repository: 'image',
|
|
18
|
+
digest: undefined,
|
|
19
|
+
tag: undefined,
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('parses image with tag but no organization', () => {
|
|
24
|
+
const imageId = 'image:tag';
|
|
25
|
+
expect(DockerImageUtils.splitImageId(imageId)).toEqual({
|
|
26
|
+
organization: '',
|
|
27
|
+
repository: 'image',
|
|
28
|
+
digest: undefined,
|
|
29
|
+
tag: 'tag',
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('parses image with no organization and correctly distinguishes digest from tag', () => {
|
|
34
|
+
const imageId = 'image:sha256:abc123';
|
|
35
|
+
expect(DockerImageUtils.splitImageId(imageId)).toEqual({
|
|
36
|
+
organization: '',
|
|
37
|
+
repository: 'image',
|
|
38
|
+
digest: 'sha256:abc123',
|
|
39
|
+
tag: undefined,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('parses image with organization but no tag/digest', () => {
|
|
44
|
+
const imageId = 'organization/image';
|
|
45
|
+
expect(DockerImageUtils.splitImageId(imageId)).toEqual({
|
|
46
|
+
organization: 'organization',
|
|
47
|
+
repository: 'organization/image',
|
|
48
|
+
digest: undefined,
|
|
49
|
+
tag: undefined,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('parses image with organization and tag', () => {
|
|
54
|
+
const imageId = 'organization/image:tag';
|
|
55
|
+
expect(DockerImageUtils.splitImageId(imageId)).toEqual({
|
|
56
|
+
organization: 'organization',
|
|
57
|
+
repository: 'organization/image',
|
|
58
|
+
digest: undefined,
|
|
59
|
+
tag: 'tag',
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('parses image with organization and correctly distinguishes digest from tag', () => {
|
|
64
|
+
const imageId = 'organization/image@sha256:abc123';
|
|
65
|
+
expect(DockerImageUtils.splitImageId(imageId)).toEqual({
|
|
66
|
+
organization: 'organization',
|
|
67
|
+
repository: 'organization/image',
|
|
68
|
+
digest: 'sha256:abc123',
|
|
69
|
+
tag: undefined,
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('parses image with nested organization', () => {
|
|
74
|
+
const imageId = 'nested/organization/image';
|
|
75
|
+
expect(DockerImageUtils.splitImageId(imageId)).toEqual({
|
|
76
|
+
organization: 'nested/organization',
|
|
77
|
+
repository: 'nested/organization/image',
|
|
78
|
+
digest: undefined,
|
|
79
|
+
tag: undefined,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('parses image with tag and nested organization', () => {
|
|
84
|
+
const imageId = 'nested/organization/image:tag';
|
|
85
|
+
expect(DockerImageUtils.splitImageId(imageId)).toEqual({
|
|
86
|
+
organization: 'nested/organization',
|
|
87
|
+
repository: 'nested/organization/image',
|
|
88
|
+
digest: undefined,
|
|
89
|
+
tag: 'tag',
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('parses image with nested organization and correctly distinguishes digest from tag', () => {
|
|
94
|
+
const imageId = 'nested/organization/image@sha256:abc123';
|
|
95
|
+
expect(DockerImageUtils.splitImageId(imageId)).toEqual({
|
|
96
|
+
organization: 'nested/organization',
|
|
97
|
+
repository: 'nested/organization/image',
|
|
98
|
+
digest: 'sha256:abc123',
|
|
99
|
+
tag: undefined,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('imageId generating', () => {
|
|
105
|
+
it('generate imageId without repository', () => {
|
|
106
|
+
expect(
|
|
107
|
+
DockerImageUtils.generateImageId({
|
|
108
|
+
organization: 'gcr.io/project',
|
|
109
|
+
repository: '',
|
|
110
|
+
digest: undefined,
|
|
111
|
+
tag: undefined,
|
|
112
|
+
}),
|
|
113
|
+
).toEqual(undefined);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('generate imageId with repository but no tag/digest', () => {
|
|
117
|
+
expect(
|
|
118
|
+
DockerImageUtils.generateImageId({
|
|
119
|
+
organization: 'gcr.io/project',
|
|
120
|
+
repository: 'gcr.io/project/my-image',
|
|
121
|
+
digest: undefined,
|
|
122
|
+
tag: undefined,
|
|
123
|
+
}),
|
|
124
|
+
).toEqual(undefined);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('generate imageId with digest', () => {
|
|
128
|
+
expect(
|
|
129
|
+
DockerImageUtils.generateImageId({
|
|
130
|
+
organization: 'gcr.io/project',
|
|
131
|
+
repository: 'gcr.io/project/my-image',
|
|
132
|
+
digest: 'sha256:28f82eba',
|
|
133
|
+
tag: undefined,
|
|
134
|
+
}),
|
|
135
|
+
).toEqual('gcr.io/project/my-image@sha256:28f82eba');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('generate imageId with tag', () => {
|
|
139
|
+
expect(
|
|
140
|
+
DockerImageUtils.generateImageId({
|
|
141
|
+
organization: 'gcr.io/project',
|
|
142
|
+
repository: 'gcr.io/project/my-image',
|
|
143
|
+
digest: undefined,
|
|
144
|
+
tag: 'v1.2',
|
|
145
|
+
}),
|
|
146
|
+
).toEqual('gcr.io/project/my-image:v1.2');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface IDockerImageParts {
|
|
2
|
+
organization: string;
|
|
3
|
+
repository: string;
|
|
4
|
+
tag?: string;
|
|
5
|
+
digest?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class DockerImageUtils {
|
|
9
|
+
// Split the image id up into the selectable parts to feed the UI
|
|
10
|
+
public static splitImageId(imageId = ''): IDockerImageParts {
|
|
11
|
+
let imageParts: string[];
|
|
12
|
+
if (imageId.includes('@')) {
|
|
13
|
+
imageParts = imageId.split('@');
|
|
14
|
+
} else {
|
|
15
|
+
imageParts = imageId.split(':');
|
|
16
|
+
}
|
|
17
|
+
const repository = imageParts[0];
|
|
18
|
+
const repositoryParts = repository.split('/');
|
|
19
|
+
// Everything before the last slash is considered the organization
|
|
20
|
+
const organization = repositoryParts.slice(0, -1).join('/');
|
|
21
|
+
|
|
22
|
+
const lookup = imageParts.length > 1 ? imageParts.slice(1).join(':') : '';
|
|
23
|
+
|
|
24
|
+
let tag: string;
|
|
25
|
+
let digest: string;
|
|
26
|
+
if (lookup) {
|
|
27
|
+
if (lookup.startsWith('sha256:')) {
|
|
28
|
+
digest = lookup;
|
|
29
|
+
} else {
|
|
30
|
+
tag = lookup;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { organization, repository, digest, tag };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public static generateImageId(parts: IDockerImageParts): string {
|
|
38
|
+
if (!parts.repository || !(parts.digest || parts.tag)) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let imageId: string;
|
|
43
|
+
|
|
44
|
+
if (parts.digest) {
|
|
45
|
+
imageId = `${parts.repository}@${parts.digest}`;
|
|
46
|
+
} else {
|
|
47
|
+
imageId = `${parts.repository}:${parts.tag}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return imageId;
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import UIROUTER_ANGULARJS from '@uirouter/angularjs';
|
|
4
|
+
import { module } from 'angular';
|
|
5
|
+
|
|
6
|
+
import { SETTINGS } from '@spinnaker/core';
|
|
7
|
+
|
|
8
|
+
export const DOCKER_PIPELINE_STAGES_BAKE_BAKEEXECUTIONDETAILS_CONTROLLER =
|
|
9
|
+
'spinnaker.docker.pipeline.stage.bake.executionDetails.controller';
|
|
10
|
+
export const name = DOCKER_PIPELINE_STAGES_BAKE_BAKEEXECUTIONDETAILS_CONTROLLER; // for backwards compatibility
|
|
11
|
+
module(DOCKER_PIPELINE_STAGES_BAKE_BAKEEXECUTIONDETAILS_CONTROLLER, [UIROUTER_ANGULARJS]).controller(
|
|
12
|
+
'dockerBakeExecutionDetailsCtrl',
|
|
13
|
+
[
|
|
14
|
+
'$scope',
|
|
15
|
+
'$stateParams',
|
|
16
|
+
'executionDetailsSectionService',
|
|
17
|
+
'$interpolate',
|
|
18
|
+
function ($scope, $stateParams, executionDetailsSectionService, $interpolate) {
|
|
19
|
+
$scope.configSections = ['bakeConfig', 'taskStatus'];
|
|
20
|
+
|
|
21
|
+
const initialized = () => {
|
|
22
|
+
$scope.detailsSection = $stateParams.details;
|
|
23
|
+
$scope.provider = $scope.stage.context.cloudProviderType || 'docker';
|
|
24
|
+
$scope.bakeryDetailUrl = $interpolate(
|
|
25
|
+
$scope.roscoMode && SETTINGS.roscoDetailUrl ? SETTINGS.roscoDetailUrl : SETTINGS.bakeryDetailUrl,
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const initialize = () => executionDetailsSectionService.synchronizeSection($scope.configSections, initialized);
|
|
30
|
+
|
|
31
|
+
initialize();
|
|
32
|
+
|
|
33
|
+
$scope.$on('$stateChangeSuccess', initialize);
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<div ng-controller="dockerBakeExecutionDetailsCtrl">
|
|
2
|
+
<execution-details-section-nav sections="configSections"></execution-details-section-nav>
|
|
3
|
+
<div class="step-section-details" ng-if="detailsSection === 'bakeConfig'">
|
|
4
|
+
<div class="row">
|
|
5
|
+
<div class="col-md-6">
|
|
6
|
+
<dl class="dl-narrow dl-horizontal">
|
|
7
|
+
<dt if-multiple-providers>Provider</dt>
|
|
8
|
+
<dd if-multiple-providers>Docker</dd>
|
|
9
|
+
<dt>Organization</dt>
|
|
10
|
+
<dd>{{stage.context.organization}}</dd>
|
|
11
|
+
<dt>Image Name</dt>
|
|
12
|
+
<dd>{{stage.context.ami_name}}</dd>
|
|
13
|
+
<dt>Image Tag</dt>
|
|
14
|
+
<dd>{{stage.context.extendedAttributes['docker_target_image_tag']}}</dd>
|
|
15
|
+
<dt>Image</dt>
|
|
16
|
+
<dd>{{stage.context.ami}}</dd>
|
|
17
|
+
</dl>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="col-md-6">
|
|
20
|
+
<dl class="dl-narrow dl-horizontal">
|
|
21
|
+
<dt>Base OS</dt>
|
|
22
|
+
<dd>{{stage.context.baseOs}}</dd>
|
|
23
|
+
<dt>Region</dt>
|
|
24
|
+
<dd>{{stage.context.region}}</dd>
|
|
25
|
+
<dt>Package</dt>
|
|
26
|
+
<dd>{{stage.context.package}}</dd>
|
|
27
|
+
<dt>Label</dt>
|
|
28
|
+
<dd>{{stage.context.baseLabel}}</dd>
|
|
29
|
+
</dl>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
<stage-failure-message stage="stage" message="stage.failureMessage"></stage-failure-message>
|
|
33
|
+
|
|
34
|
+
<div class="row" ng-if="stage.context.region && stage.context.status.resourceId">
|
|
35
|
+
<div class="col-md-12">
|
|
36
|
+
<div class="alert alert-{{stage.isFailed ? 'danger' : 'info'}}">
|
|
37
|
+
<a target="_blank" href="{{ bakeryDetailUrl(stage) }}"> View Bakery Details </a>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="step-section-details" ng-if="detailsSection === 'taskStatus'">
|
|
43
|
+
<div class="row">
|
|
44
|
+
<execution-step-details item="stage"></execution-step-details>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<div ng-controller="dockerBakeStageCtrl as bakeStageCtrl">
|
|
2
|
+
<stage-config-field label="Package" help-key="pipeline.config.bake.package">
|
|
3
|
+
<input type="text" class="form-control input-sm" ng-model="stage.package" />
|
|
4
|
+
</stage-config-field>
|
|
5
|
+
<stage-config-field label="Organization" help-key="pipeline.config.docker.bake.organization">
|
|
6
|
+
<input type="text" class="form-control input-sm" ng-model="stage.organization" />
|
|
7
|
+
</stage-config-field>
|
|
8
|
+
<stage-config-field label="Image Name" help-key="pipeline.config.docker.bake.targetImage">
|
|
9
|
+
<input type="text" class="form-control input-sm" ng-model="stage.ami_name" />
|
|
10
|
+
</stage-config-field>
|
|
11
|
+
<stage-config-field label="Image tag" help-key="pipeline.config.docker.bake.targetImageTag">
|
|
12
|
+
<input type="text" class="form-control input-sm" ng-model="stage.extendedAttributes['docker_target_image_tag']" />
|
|
13
|
+
</stage-config-field>
|
|
14
|
+
<stage-config-field label="Base OS">
|
|
15
|
+
<bake-stage-choose-os model="stage.baseOs" base-os-options="baseOsOptions"></bake-stage-choose-os>
|
|
16
|
+
</stage-config-field>
|
|
17
|
+
|
|
18
|
+
<stage-config-field label="Base Label">
|
|
19
|
+
<label class="radio-inline" ng-repeat="baseLabel in baseLabelOptions">
|
|
20
|
+
<input type="radio" ng-model="stage.baseLabel" ng-value="baseLabel" />
|
|
21
|
+
{{baseLabel}}
|
|
22
|
+
</label>
|
|
23
|
+
</stage-config-field>
|
|
24
|
+
<stage-config-field label="Rebake">
|
|
25
|
+
<div class="checkbox" style="margin-bottom: 0">
|
|
26
|
+
<label>
|
|
27
|
+
<input type="checkbox" ng-model="stage.rebake" />
|
|
28
|
+
Rebake image without regard to the status of any existing bake
|
|
29
|
+
</label>
|
|
30
|
+
</div>
|
|
31
|
+
</stage-config-field>
|
|
32
|
+
</div>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { module } from 'angular';
|
|
4
|
+
import _ from 'lodash';
|
|
5
|
+
|
|
6
|
+
import { AuthenticationService, BakeExecutionLabel, BakeryReader, Registry } from '@spinnaker/core';
|
|
7
|
+
import { DOCKER_PIPELINE_STAGES_BAKE_BAKEEXECUTIONDETAILS_CONTROLLER } from './bakeExecutionDetails.controller';
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
This stage is just here so that we can experiment with baking Docker containers within pipelines.
|
|
11
|
+
Without this stage, programmatically-created pipelines with Docker bake stages would not render
|
|
12
|
+
execution details.
|
|
13
|
+
*/
|
|
14
|
+
export const DOCKER_PIPELINE_STAGES_BAKE_DOCKERBAKESTAGE = 'spinnaker.docker.pipeline.stage.bakeStage';
|
|
15
|
+
export const name = DOCKER_PIPELINE_STAGES_BAKE_DOCKERBAKESTAGE; // for backwards compatibility
|
|
16
|
+
module(DOCKER_PIPELINE_STAGES_BAKE_DOCKERBAKESTAGE, [DOCKER_PIPELINE_STAGES_BAKE_BAKEEXECUTIONDETAILS_CONTROLLER])
|
|
17
|
+
.config(function () {
|
|
18
|
+
Registry.pipeline.registerStage({
|
|
19
|
+
provides: 'bake',
|
|
20
|
+
cloudProvider: 'docker',
|
|
21
|
+
label: 'Bake',
|
|
22
|
+
description: 'Bakes an image',
|
|
23
|
+
templateUrl: require('./bakeStage.html'),
|
|
24
|
+
executionDetailsUrl: require('./bakeExecutionDetails.html'),
|
|
25
|
+
executionLabelComponent: BakeExecutionLabel,
|
|
26
|
+
extraLabelLines: (stage) => {
|
|
27
|
+
return stage.masterStage.context.allPreviouslyBaked || stage.masterStage.context.somePreviouslyBaked ? 1 : 0;
|
|
28
|
+
},
|
|
29
|
+
supportsCustomTimeout: true,
|
|
30
|
+
validators: [{ type: 'requiredField', fieldName: 'package' }],
|
|
31
|
+
restartable: true,
|
|
32
|
+
});
|
|
33
|
+
})
|
|
34
|
+
.controller('dockerBakeStageCtrl', [
|
|
35
|
+
'$scope',
|
|
36
|
+
'$q',
|
|
37
|
+
function ($scope, $q) {
|
|
38
|
+
const stage = $scope.stage;
|
|
39
|
+
|
|
40
|
+
stage.region = 'global';
|
|
41
|
+
|
|
42
|
+
if (!$scope.stage.user) {
|
|
43
|
+
$scope.stage.user = AuthenticationService.getAuthenticatedUser().name;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
$scope.viewState = {
|
|
47
|
+
loading: true,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function initialize() {
|
|
51
|
+
$scope.viewState.providerSelected = true;
|
|
52
|
+
$q.all([BakeryReader.getBaseOsOptions('docker'), BakeryReader.getBaseLabelOptions()]).then(function ([
|
|
53
|
+
baseOsOptions,
|
|
54
|
+
baseLabelOptions,
|
|
55
|
+
]) {
|
|
56
|
+
$scope.baseOsOptions = baseOsOptions.baseImages;
|
|
57
|
+
$scope.baseLabelOptions = baseLabelOptions;
|
|
58
|
+
|
|
59
|
+
if (!$scope.stage.baseOs && $scope.baseOsOptions && $scope.baseOsOptions.length) {
|
|
60
|
+
$scope.stage.baseOs = $scope.baseOsOptions[0].id;
|
|
61
|
+
}
|
|
62
|
+
if (!$scope.stage.baseLabel && $scope.baseLabelOptions && $scope.baseLabelOptions.length) {
|
|
63
|
+
$scope.stage.baseLabel = $scope.baseLabelOptions[0];
|
|
64
|
+
}
|
|
65
|
+
$scope.viewState.loading = false;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function deleteEmptyProperties() {
|
|
70
|
+
_.forOwn($scope.stage, function (val, key) {
|
|
71
|
+
if (val === '') {
|
|
72
|
+
delete $scope.stage[key];
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
$scope.$watch('stage', deleteEmptyProperties, true);
|
|
78
|
+
|
|
79
|
+
initialize();
|
|
80
|
+
},
|
|
81
|
+
]);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { IDockerTrigger, IExecutionTriggerStatusComponentProps } from '@spinnaker/core';
|
|
4
|
+
import { Registry } from '@spinnaker/core';
|
|
5
|
+
|
|
6
|
+
import { DockerTriggerConfig } from './DockerTriggerConfig';
|
|
7
|
+
import { DockerTriggerTemplate } from './DockerTriggerTemplate';
|
|
8
|
+
|
|
9
|
+
const DockerTriggerExecutionStatus = (props: IExecutionTriggerStatusComponentProps) => {
|
|
10
|
+
const trigger = props.trigger as IDockerTrigger;
|
|
11
|
+
return (
|
|
12
|
+
<li>
|
|
13
|
+
{trigger.repository}:{trigger.tag}
|
|
14
|
+
</li>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
Registry.pipeline.registerTrigger({
|
|
19
|
+
label: 'Docker Registry',
|
|
20
|
+
description: 'Executes the pipeline on an image update',
|
|
21
|
+
key: 'docker',
|
|
22
|
+
component: DockerTriggerConfig,
|
|
23
|
+
manualExecutionComponent: DockerTriggerTemplate,
|
|
24
|
+
executionStatusComponent: DockerTriggerExecutionStatus,
|
|
25
|
+
executionTriggerLabel: () => 'Docker Registry',
|
|
26
|
+
validators: [
|
|
27
|
+
{
|
|
28
|
+
type: 'requiredField',
|
|
29
|
+
fieldName: 'account',
|
|
30
|
+
message: '<strong>Registry</strong> is a required field for Docker Registry triggers.',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: 'requiredField',
|
|
34
|
+
fieldName: 'repository',
|
|
35
|
+
message: '<strong>Image</strong> is a required field for Docker Registry triggers.',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'serviceAccountAccess',
|
|
39
|
+
preventSave: true,
|
|
40
|
+
message: `You do not have access to the service account configured in this pipeline's Docker Registry trigger.
|
|
41
|
+
You will not be able to save your edits to this pipeline.`,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { FormikProps } from 'formik';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import type { IDockerTrigger } from '@spinnaker/core';
|
|
5
|
+
|
|
6
|
+
import type { IDockerImageAndTagChanges } from '../../image';
|
|
7
|
+
import { DockerImageAndTagSelector } from '../../image';
|
|
8
|
+
|
|
9
|
+
export interface IDockerTriggerConfigProps {
|
|
10
|
+
formik: FormikProps<IDockerTrigger>;
|
|
11
|
+
triggerUpdated: (trigger: IDockerTrigger) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function DockerTriggerConfig(props: IDockerTriggerConfigProps) {
|
|
15
|
+
const { formik } = props;
|
|
16
|
+
const trigger = formik.values;
|
|
17
|
+
|
|
18
|
+
const dockerChanged = (changes: IDockerImageAndTagChanges) => {
|
|
19
|
+
// Trigger doesn't use imageId.
|
|
20
|
+
const { imageId, ...rest } = changes;
|
|
21
|
+
props.triggerUpdated(rest as IDockerTrigger);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="form-horizontal">
|
|
26
|
+
<DockerImageAndTagSelector
|
|
27
|
+
allowManualDefinition={false}
|
|
28
|
+
specifyTagByRegex={true}
|
|
29
|
+
account={trigger.account}
|
|
30
|
+
organization={trigger.organization}
|
|
31
|
+
registry={trigger.registry}
|
|
32
|
+
repository={trigger.repository}
|
|
33
|
+
tag={trigger.tag}
|
|
34
|
+
showRegistry={true}
|
|
35
|
+
onChange={dockerChanged}
|
|
36
|
+
showDigest={false}
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|