@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,735 @@
|
|
|
1
|
+
import { groupBy, reduce, trim, uniq } from 'lodash';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import type { Option } from 'react-select';
|
|
4
|
+
import Select from 'react-select';
|
|
5
|
+
|
|
6
|
+
import type { IAccount, IFindImageParams } from '@spinnaker/core';
|
|
7
|
+
import { AccountService, HelpField, Tooltip, ValidationMessage } from '@spinnaker/core';
|
|
8
|
+
|
|
9
|
+
import type { IDockerImage } from './DockerImageReader';
|
|
10
|
+
import { DockerImageReader } from './DockerImageReader';
|
|
11
|
+
import type { IDockerImageParts } from './DockerImageUtils';
|
|
12
|
+
import { DockerImageUtils } from './DockerImageUtils';
|
|
13
|
+
|
|
14
|
+
export type IDockerLookupType = 'tag' | 'digest';
|
|
15
|
+
|
|
16
|
+
export interface IDockerImageAndTagChanges {
|
|
17
|
+
account?: string;
|
|
18
|
+
organization?: string;
|
|
19
|
+
registry?: string;
|
|
20
|
+
repository?: string;
|
|
21
|
+
tag?: string;
|
|
22
|
+
digest?: string;
|
|
23
|
+
imageId?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface IDockerImageAndTagSelectorProps {
|
|
27
|
+
specifyTagByRegex: boolean;
|
|
28
|
+
imageId: string;
|
|
29
|
+
organization: string;
|
|
30
|
+
registry: string;
|
|
31
|
+
repository: string;
|
|
32
|
+
tag: string;
|
|
33
|
+
digest: string;
|
|
34
|
+
account: string;
|
|
35
|
+
showRegistry?: boolean;
|
|
36
|
+
onChange: (changes: IDockerImageAndTagChanges) => void;
|
|
37
|
+
deferInitialization?: boolean;
|
|
38
|
+
showDigest?: boolean;
|
|
39
|
+
allowManualDefinition?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface IDockerImageAndTagSelectorState {
|
|
43
|
+
accountOptions: Array<Option<string>>;
|
|
44
|
+
switchedManualWarning: string;
|
|
45
|
+
missingFields?: string[];
|
|
46
|
+
imagesLoaded: boolean;
|
|
47
|
+
imagesLoading: boolean;
|
|
48
|
+
organizationOptions: Array<Option<string>>;
|
|
49
|
+
repositoryOptions: Array<Option<string>>;
|
|
50
|
+
defineManually: boolean;
|
|
51
|
+
tagOptions: Array<Option<string>>;
|
|
52
|
+
lookupType: IDockerLookupType;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const imageFields = ['organization', 'repository', 'tag', 'digest'];
|
|
56
|
+
const defineOptions = [
|
|
57
|
+
{ label: 'Manually', value: true },
|
|
58
|
+
{ label: 'Select from list', value: false },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
export class DockerImageAndTagSelector extends React.Component<
|
|
62
|
+
IDockerImageAndTagSelectorProps,
|
|
63
|
+
IDockerImageAndTagSelectorState
|
|
64
|
+
> {
|
|
65
|
+
public static defaultProps: Partial<IDockerImageAndTagSelectorProps> = {
|
|
66
|
+
organization: '',
|
|
67
|
+
registry: '',
|
|
68
|
+
repository: '',
|
|
69
|
+
showDigest: true,
|
|
70
|
+
allowManualDefinition: true,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
private unmounted = false;
|
|
74
|
+
private images: IDockerImage[];
|
|
75
|
+
private accounts: string[];
|
|
76
|
+
|
|
77
|
+
private registryMap: { [key: string]: string };
|
|
78
|
+
private accountMap: { [key: string]: string[] };
|
|
79
|
+
private newAccounts: string[];
|
|
80
|
+
private organizationMap: { [key: string]: string[] };
|
|
81
|
+
private repositoryMap: { [key: string]: string[] };
|
|
82
|
+
private organizations: string[];
|
|
83
|
+
private cachedValues: { [key: string]: string } = {};
|
|
84
|
+
|
|
85
|
+
public constructor(props: IDockerImageAndTagSelectorProps) {
|
|
86
|
+
super(props);
|
|
87
|
+
|
|
88
|
+
const accountOptions = props.account ? [{ label: props.account, value: props.account }] : [];
|
|
89
|
+
const organizationOptions =
|
|
90
|
+
props.organization && props.organization.length ? [{ label: props.organization, value: props.organization }] : [];
|
|
91
|
+
const repositoryOptions =
|
|
92
|
+
props.repository && props.repository.length ? [{ label: props.repository, value: props.repository }] : [];
|
|
93
|
+
const tagOptions = props.tag && props.tag.length ? [{ label: props.tag, value: props.tag }] : [];
|
|
94
|
+
const parsedImageId = DockerImageUtils.splitImageId(props.imageId);
|
|
95
|
+
const defineManually = props.allowManualDefinition && Boolean(props.imageId && props.imageId.includes('${'));
|
|
96
|
+
|
|
97
|
+
this.state = {
|
|
98
|
+
accountOptions,
|
|
99
|
+
switchedManualWarning: undefined,
|
|
100
|
+
imagesLoaded: false,
|
|
101
|
+
imagesLoading: false,
|
|
102
|
+
organizationOptions,
|
|
103
|
+
repositoryOptions,
|
|
104
|
+
defineManually,
|
|
105
|
+
tagOptions,
|
|
106
|
+
lookupType: props.digest || parsedImageId.digest ? 'digest' : 'tag',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private getAccountMap(images: IDockerImage[]): { [key: string]: string[] } {
|
|
111
|
+
const groupedImages = groupBy(
|
|
112
|
+
images.filter((image) => image.account),
|
|
113
|
+
'account',
|
|
114
|
+
);
|
|
115
|
+
return reduce<IDockerImage[], { [key: string]: string[] }>(
|
|
116
|
+
groupedImages,
|
|
117
|
+
(acc, image, key) => {
|
|
118
|
+
acc[key] = uniq(image.map((i) => `${i.repository.split('/').slice(0, -1).join('/')}`));
|
|
119
|
+
return acc;
|
|
120
|
+
},
|
|
121
|
+
{},
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private getRegistryMap(images: IDockerImage[]) {
|
|
126
|
+
return images.reduce((m: { [key: string]: string }, image: IDockerImage) => {
|
|
127
|
+
m[image.account] = image.registry;
|
|
128
|
+
return m;
|
|
129
|
+
}, {} as { [key: string]: string });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private getOrganizationMap(images: IDockerImage[]): { [key: string]: string[] } {
|
|
133
|
+
const extractGroupByKey = (image: IDockerImage) =>
|
|
134
|
+
`${image.account}/${image.repository.split('/').slice(0, -1).join('/')}`;
|
|
135
|
+
const groupedImages = groupBy(
|
|
136
|
+
images.filter((image) => image.repository),
|
|
137
|
+
extractGroupByKey,
|
|
138
|
+
);
|
|
139
|
+
return reduce<IDockerImage[], { [key: string]: string[] }>(
|
|
140
|
+
groupedImages,
|
|
141
|
+
(acc, image, key) => {
|
|
142
|
+
acc[key] = uniq(image.map((i) => i.repository));
|
|
143
|
+
return acc;
|
|
144
|
+
},
|
|
145
|
+
{},
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private getRepositoryMap(images: IDockerImage[]) {
|
|
150
|
+
const groupedImages = groupBy(
|
|
151
|
+
images.filter((image) => image.account),
|
|
152
|
+
'repository',
|
|
153
|
+
);
|
|
154
|
+
return reduce<IDockerImage[], { [key: string]: string[] }>(
|
|
155
|
+
groupedImages,
|
|
156
|
+
(acc, image, key) => {
|
|
157
|
+
acc[key] = uniq(image.map((i) => i.tag));
|
|
158
|
+
return acc;
|
|
159
|
+
},
|
|
160
|
+
{},
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private getOrganizationsList(accountMap: { [key: string]: string[] }) {
|
|
165
|
+
return accountMap ? accountMap[this.props.showRegistry ? this.props.account : this.props.registry] || [] : [];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private getRepositoryList(organizationMap: { [key: string]: string[] }, organization: string, registry: string) {
|
|
169
|
+
if (organizationMap) {
|
|
170
|
+
const key = `${this.props.showRegistry ? this.props.account : registry}/${organization || ''}`;
|
|
171
|
+
return organizationMap[key] || [];
|
|
172
|
+
}
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private getTags(tag: string, repositoryMap: { [key: string]: string[] }, repository: string) {
|
|
177
|
+
let tags: string[] = [];
|
|
178
|
+
if (this.props.specifyTagByRegex) {
|
|
179
|
+
if (tag && trim(tag) === '') {
|
|
180
|
+
tag = undefined;
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
if (repositoryMap) {
|
|
184
|
+
tags = repositoryMap[repository] || [];
|
|
185
|
+
if (!tags.includes(tag) && tag && !tag.includes('${')) {
|
|
186
|
+
tag = undefined;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return { tag, tags };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public componentWillReceiveProps(nextProps: IDockerImageAndTagSelectorProps) {
|
|
195
|
+
if (
|
|
196
|
+
!this.images ||
|
|
197
|
+
['account', 'showRegistry'].some(
|
|
198
|
+
(key: keyof IDockerImageAndTagSelectorProps) => this.props[key] !== nextProps[key],
|
|
199
|
+
)
|
|
200
|
+
) {
|
|
201
|
+
this.refreshImages(nextProps);
|
|
202
|
+
} else if (
|
|
203
|
+
['organization', 'registry', 'repository'].some(
|
|
204
|
+
(key: keyof IDockerImageAndTagSelectorProps) => this.props[key] !== nextProps[key],
|
|
205
|
+
)
|
|
206
|
+
) {
|
|
207
|
+
this.updateThings(nextProps);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (nextProps.imageId && nextProps.imageId.includes('${')) {
|
|
211
|
+
this.setState({ defineManually: true });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
componentWillUnmount() {
|
|
216
|
+
this.unmounted = true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private synchronizeChanges(values: IDockerImageParts, registry: string) {
|
|
220
|
+
const { organization, repository, tag, digest } = values;
|
|
221
|
+
if (this.props.onChange) {
|
|
222
|
+
const imageId = DockerImageUtils.generateImageId({ organization, repository, tag, digest });
|
|
223
|
+
const changes: IDockerImageAndTagChanges = {};
|
|
224
|
+
if (tag !== this.props.tag) {
|
|
225
|
+
changes.tag = tag;
|
|
226
|
+
}
|
|
227
|
+
if (imageId !== this.props.imageId) {
|
|
228
|
+
changes.imageId = imageId;
|
|
229
|
+
}
|
|
230
|
+
if (organization !== this.props.organization) {
|
|
231
|
+
changes.organization = organization;
|
|
232
|
+
}
|
|
233
|
+
if (registry !== this.props.registry) {
|
|
234
|
+
changes.registry = registry;
|
|
235
|
+
}
|
|
236
|
+
if (repository !== this.props.repository) {
|
|
237
|
+
changes.repository = repository;
|
|
238
|
+
}
|
|
239
|
+
if (digest !== this.props.digest) {
|
|
240
|
+
changes.digest = digest;
|
|
241
|
+
}
|
|
242
|
+
if (Object.keys(changes).length > 0) {
|
|
243
|
+
this.props.onChange(changes);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private updateThings(props: IDockerImageAndTagSelectorProps, allowAutoSwitchToManualEntry = false) {
|
|
249
|
+
if (!this.repositoryMap || this.unmounted) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const { imageId, specifyTagByRegex } = props;
|
|
254
|
+
let { organization, registry, repository } = props;
|
|
255
|
+
|
|
256
|
+
if (props.showRegistry) {
|
|
257
|
+
registry = this.registryMap[props.account];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const organizationFound = !organization || this.organizations.includes(organization) || organization.includes('${');
|
|
261
|
+
if (!organizationFound) {
|
|
262
|
+
organization = '';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const repositories = this.getRepositoryList(this.organizationMap, organization, registry);
|
|
266
|
+
const repositoryFound = !repository || repository.includes('${') || repositories.includes(repository);
|
|
267
|
+
|
|
268
|
+
if (!repositoryFound) {
|
|
269
|
+
repository = '';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const { tag, tags } = this.getTags(props.tag, this.repositoryMap, repository);
|
|
273
|
+
const tagFound = tag === props.tag || specifyTagByRegex;
|
|
274
|
+
|
|
275
|
+
const newState = {
|
|
276
|
+
accountOptions: this.newAccounts.sort().map((a) => ({ label: a, value: a })),
|
|
277
|
+
organizationOptions: this.organizations
|
|
278
|
+
.filter((o) => o)
|
|
279
|
+
.sort()
|
|
280
|
+
.map((o) => ({ label: o, value: o })),
|
|
281
|
+
imagesLoaded: true,
|
|
282
|
+
repositoryOptions: repositories.sort().map((r) => ({ label: r, value: r })),
|
|
283
|
+
tagOptions: tags.sort().map((t) => ({ label: t, value: t })),
|
|
284
|
+
} as IDockerImageAndTagSelectorState;
|
|
285
|
+
|
|
286
|
+
if (
|
|
287
|
+
imageId &&
|
|
288
|
+
(!this.state.imagesLoaded || allowAutoSwitchToManualEntry) &&
|
|
289
|
+
(!organizationFound || !repositoryFound || !tagFound)
|
|
290
|
+
) {
|
|
291
|
+
newState.defineManually = true;
|
|
292
|
+
|
|
293
|
+
const missingFields: string[] = [];
|
|
294
|
+
if (!organizationFound) {
|
|
295
|
+
missingFields.push('organization');
|
|
296
|
+
}
|
|
297
|
+
if (!repositoryFound) {
|
|
298
|
+
missingFields.push('image');
|
|
299
|
+
}
|
|
300
|
+
if (!tagFound) {
|
|
301
|
+
missingFields.push('tag');
|
|
302
|
+
}
|
|
303
|
+
newState.missingFields = missingFields;
|
|
304
|
+
newState.switchedManualWarning = `Could not find ${missingFields.join(' or ')}, switched to manual entry`;
|
|
305
|
+
} else if (!imageId || !imageId.includes('${')) {
|
|
306
|
+
this.synchronizeChanges(
|
|
307
|
+
this.state.defineManually
|
|
308
|
+
? DockerImageUtils.splitImageId(imageId)
|
|
309
|
+
: { organization, repository, tag, digest: this.props.digest },
|
|
310
|
+
registry,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
this.setState(newState);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private initializeImages(props: IDockerImageAndTagSelectorProps) {
|
|
318
|
+
if (this.state.imagesLoading) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const { showRegistry, account, registry } = props;
|
|
323
|
+
|
|
324
|
+
const imageConfig: IFindImageParams = {
|
|
325
|
+
provider: 'dockerRegistry',
|
|
326
|
+
account: showRegistry ? account : registry,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
this.setState({ imagesLoading: true });
|
|
330
|
+
DockerImageReader.findImages(imageConfig)
|
|
331
|
+
.then((images: IDockerImage[]) => {
|
|
332
|
+
this.images = images;
|
|
333
|
+
this.registryMap = this.getRegistryMap(this.images);
|
|
334
|
+
this.accountMap = this.getAccountMap(this.images);
|
|
335
|
+
this.newAccounts = this.accounts || Object.keys(this.accountMap);
|
|
336
|
+
|
|
337
|
+
this.organizationMap = this.getOrganizationMap(this.images);
|
|
338
|
+
this.repositoryMap = this.getRepositoryMap(this.images);
|
|
339
|
+
this.organizations = this.getOrganizationsList(this.accountMap);
|
|
340
|
+
this.updateThings(props, true);
|
|
341
|
+
})
|
|
342
|
+
.finally(() => {
|
|
343
|
+
if (!this.unmounted) {
|
|
344
|
+
this.setState({ imagesLoading: false });
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
public handleRefreshImages = (): void => {
|
|
350
|
+
this.refreshImages(this.props);
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
public refreshImages(props: IDockerImageAndTagSelectorProps): void {
|
|
354
|
+
this.initializeImages(props);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private initializeAccounts(props: IDockerImageAndTagSelectorProps) {
|
|
358
|
+
let { account } = props;
|
|
359
|
+
AccountService.listAccounts('dockerRegistry').then((allAccounts: IAccount[]) => {
|
|
360
|
+
const accounts = allAccounts.map((a: IAccount) => a.name);
|
|
361
|
+
if (this.props.showRegistry && !account) {
|
|
362
|
+
account = accounts[0];
|
|
363
|
+
}
|
|
364
|
+
this.accounts = accounts;
|
|
365
|
+
this.refreshImages({ ...props, ...{ account } });
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private isNew(): boolean {
|
|
370
|
+
const { account, organization, registry, repository, tag } = this.props;
|
|
371
|
+
return !account && !organization && !registry && !repository && !tag;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
public componentDidMount() {
|
|
375
|
+
if (!this.props.deferInitialization && (this.props.registry || this.isNew())) {
|
|
376
|
+
this.initializeAccounts(this.props);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private valueChanged(name: string, value: string) {
|
|
381
|
+
const changes = { [name]: value };
|
|
382
|
+
if (imageFields.some((n) => n === name)) {
|
|
383
|
+
// values are parts of the image
|
|
384
|
+
const { organization, repository, tag, digest } = this.props;
|
|
385
|
+
const imageParts = { ...{ organization, repository, tag, digest }, ...changes };
|
|
386
|
+
const imageId = DockerImageUtils.generateImageId(imageParts);
|
|
387
|
+
changes.imageId = imageId;
|
|
388
|
+
}
|
|
389
|
+
this.props.onChange && this.props.onChange(changes);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private lookupTypeChanged = (o: Option<IDockerLookupType>) => {
|
|
393
|
+
const newType = o.value;
|
|
394
|
+
const oldType = this.state.lookupType;
|
|
395
|
+
const oldValue = this.props[oldType];
|
|
396
|
+
const cachedValue = this.cachedValues[newType];
|
|
397
|
+
|
|
398
|
+
this.valueChanged(oldType, undefined);
|
|
399
|
+
if (this.cachedValues[newType]) {
|
|
400
|
+
this.valueChanged(newType, cachedValue);
|
|
401
|
+
}
|
|
402
|
+
this.setState({ lookupType: newType });
|
|
403
|
+
this.cachedValues[oldType] = oldValue;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
private showManualInput = (defineManually: boolean) => {
|
|
407
|
+
if (!defineManually) {
|
|
408
|
+
const newFields = DockerImageUtils.splitImageId(this.props.imageId || '');
|
|
409
|
+
this.props.onChange(newFields);
|
|
410
|
+
if (this.state.switchedManualWarning) {
|
|
411
|
+
this.setState({ switchedManualWarning: undefined, missingFields: undefined });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
this.setState({ defineManually });
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
public render() {
|
|
418
|
+
const {
|
|
419
|
+
account,
|
|
420
|
+
allowManualDefinition,
|
|
421
|
+
digest,
|
|
422
|
+
imageId,
|
|
423
|
+
organization,
|
|
424
|
+
repository,
|
|
425
|
+
showDigest,
|
|
426
|
+
showRegistry,
|
|
427
|
+
specifyTagByRegex,
|
|
428
|
+
tag,
|
|
429
|
+
} = this.props;
|
|
430
|
+
const {
|
|
431
|
+
accountOptions,
|
|
432
|
+
switchedManualWarning,
|
|
433
|
+
missingFields,
|
|
434
|
+
imagesLoading,
|
|
435
|
+
lookupType,
|
|
436
|
+
organizationOptions,
|
|
437
|
+
repositoryOptions,
|
|
438
|
+
defineManually,
|
|
439
|
+
tagOptions,
|
|
440
|
+
} = this.state;
|
|
441
|
+
|
|
442
|
+
const parsedImageId = DockerImageUtils.splitImageId(imageId);
|
|
443
|
+
|
|
444
|
+
const manualInputToggle = (
|
|
445
|
+
<div className="sp-formItem groupHeader">
|
|
446
|
+
<div className="sp-formItem__left">
|
|
447
|
+
<div className="sp-formLabel">Define Image ID</div>
|
|
448
|
+
|
|
449
|
+
<div className="sp-formActions sp-formActions--mobile">
|
|
450
|
+
<span className="action" />
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
|
|
454
|
+
<div className="sp-formItem__right">
|
|
455
|
+
<div className="sp-form">
|
|
456
|
+
<span className="field">
|
|
457
|
+
<Select
|
|
458
|
+
value={defineManually}
|
|
459
|
+
disabled={imagesLoading || !allowManualDefinition}
|
|
460
|
+
onChange={(o: Option<boolean>) => this.showManualInput(o.value)}
|
|
461
|
+
options={defineOptions}
|
|
462
|
+
clearable={false}
|
|
463
|
+
/>
|
|
464
|
+
</span>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
const warning = switchedManualWarning ? (
|
|
471
|
+
<div className="sp-formItem">
|
|
472
|
+
<div className="sp-formItem__left" />
|
|
473
|
+
<div className="sp-formItem__right">
|
|
474
|
+
<ValidationMessage
|
|
475
|
+
type="warning"
|
|
476
|
+
message={
|
|
477
|
+
<>
|
|
478
|
+
{switchedManualWarning}
|
|
479
|
+
{(missingFields || []).map((f) => (
|
|
480
|
+
<div key={f}>
|
|
481
|
+
<HelpField expand={true} id={`pipeline.config.docker.trigger.missing.${f}`} />
|
|
482
|
+
</div>
|
|
483
|
+
))}
|
|
484
|
+
</>
|
|
485
|
+
}
|
|
486
|
+
/>
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
) : null;
|
|
490
|
+
|
|
491
|
+
if (defineManually) {
|
|
492
|
+
return (
|
|
493
|
+
<div className="sp-formGroup">
|
|
494
|
+
{manualInputToggle}
|
|
495
|
+
<div className="sp-formItem">
|
|
496
|
+
<div className="sp-formItem__left">
|
|
497
|
+
<div className="sp-formLabel">Image ID</div>
|
|
498
|
+
<div className="sp-formActions sp-formActions--mobile">
|
|
499
|
+
<span className="action" />
|
|
500
|
+
</div>
|
|
501
|
+
</div>
|
|
502
|
+
<div className="sp-formItem__right">
|
|
503
|
+
<div className="sp-form">
|
|
504
|
+
<span className="field">
|
|
505
|
+
<input
|
|
506
|
+
className="form-control input-sm"
|
|
507
|
+
value={imageId || ''}
|
|
508
|
+
onChange={(e) => this.valueChanged('imageId', e.target.value)}
|
|
509
|
+
/>
|
|
510
|
+
</span>
|
|
511
|
+
</div>
|
|
512
|
+
</div>
|
|
513
|
+
</div>
|
|
514
|
+
{warning}
|
|
515
|
+
</div>
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const Registry = showRegistry ? (
|
|
520
|
+
<div className="sp-formItem">
|
|
521
|
+
<div className="sp-formItem__left">
|
|
522
|
+
<div className="sp-formLabel">Registry Name</div>
|
|
523
|
+
</div>
|
|
524
|
+
<div className="sp-formItem__right">
|
|
525
|
+
<div className="sp-form">
|
|
526
|
+
<span className="field">
|
|
527
|
+
<Select
|
|
528
|
+
value={account}
|
|
529
|
+
disabled={imagesLoading}
|
|
530
|
+
onChange={(o: Option<string>) => this.valueChanged('account', o ? o.value : '')}
|
|
531
|
+
options={accountOptions}
|
|
532
|
+
isLoading={imagesLoading}
|
|
533
|
+
/>
|
|
534
|
+
</span>
|
|
535
|
+
<span className="sp-formActions sp-formActions--web">
|
|
536
|
+
<span className="action">
|
|
537
|
+
<Tooltip value={imagesLoading ? 'Images refreshing' : 'Refresh images list'}>
|
|
538
|
+
<i
|
|
539
|
+
className={`fa icon-button-refresh-arrows ${imagesLoading ? 'fa-spin' : ''}`}
|
|
540
|
+
onClick={this.handleRefreshImages}
|
|
541
|
+
/>
|
|
542
|
+
</Tooltip>
|
|
543
|
+
</span>
|
|
544
|
+
</span>
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
</div>
|
|
548
|
+
) : null;
|
|
549
|
+
|
|
550
|
+
const Organization = (
|
|
551
|
+
<div className="sp-formItem">
|
|
552
|
+
<div className="sp-formItem__left">
|
|
553
|
+
<div className="sp-formLabel">Organization</div>
|
|
554
|
+
</div>
|
|
555
|
+
|
|
556
|
+
<div className="sp-formItem__right">
|
|
557
|
+
<div className="sp-form">
|
|
558
|
+
<span className="field">
|
|
559
|
+
{organization.includes('${') ? (
|
|
560
|
+
<input
|
|
561
|
+
disabled={imagesLoading}
|
|
562
|
+
className="form-control input-sm"
|
|
563
|
+
value={organization || ''}
|
|
564
|
+
onChange={(e) => this.valueChanged('organization', e.target.value)}
|
|
565
|
+
/>
|
|
566
|
+
) : (
|
|
567
|
+
<Select
|
|
568
|
+
value={organization || ''}
|
|
569
|
+
disabled={imagesLoading}
|
|
570
|
+
onChange={(o: Option<string>) => this.valueChanged('organization', (o && o.value) || '')}
|
|
571
|
+
placeholder="No organization"
|
|
572
|
+
options={organizationOptions}
|
|
573
|
+
isLoading={imagesLoading}
|
|
574
|
+
/>
|
|
575
|
+
)}
|
|
576
|
+
</span>
|
|
577
|
+
</div>
|
|
578
|
+
</div>
|
|
579
|
+
</div>
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
const Image = (
|
|
583
|
+
<div className="sp-formItem">
|
|
584
|
+
<div className="sp-formItem__left">
|
|
585
|
+
<div className="sp-formLabel">Image</div>
|
|
586
|
+
</div>
|
|
587
|
+
|
|
588
|
+
<div className="sp-formItem__right">
|
|
589
|
+
<div className="sp-form">
|
|
590
|
+
<span className="field">
|
|
591
|
+
{repository.includes('${') ? (
|
|
592
|
+
<input
|
|
593
|
+
className="form-control input-sm"
|
|
594
|
+
disabled={imagesLoading}
|
|
595
|
+
value={repository || ''}
|
|
596
|
+
onChange={(e) => this.valueChanged('repository', e.target.value)}
|
|
597
|
+
/>
|
|
598
|
+
) : (
|
|
599
|
+
<Select
|
|
600
|
+
value={repository || ''}
|
|
601
|
+
disabled={imagesLoading}
|
|
602
|
+
onChange={(o: Option<string>) => this.valueChanged('repository', (o && o.value) || '')}
|
|
603
|
+
options={repositoryOptions}
|
|
604
|
+
required={true}
|
|
605
|
+
isLoading={imagesLoading}
|
|
606
|
+
/>
|
|
607
|
+
)}
|
|
608
|
+
</span>
|
|
609
|
+
</div>
|
|
610
|
+
</div>
|
|
611
|
+
</div>
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
const Tag =
|
|
615
|
+
lookupType === 'tag' ? (
|
|
616
|
+
specifyTagByRegex ? (
|
|
617
|
+
<div className="sp-formItem">
|
|
618
|
+
<div className="sp-formItem__left">
|
|
619
|
+
<div className="sp-formLabel">Tag</div>
|
|
620
|
+
</div>
|
|
621
|
+
|
|
622
|
+
<div className="sp-formItem__right">
|
|
623
|
+
<div className="sp-form">
|
|
624
|
+
<span className="field">
|
|
625
|
+
<input
|
|
626
|
+
type="text"
|
|
627
|
+
className="form-control input-sm"
|
|
628
|
+
value={tag || ''}
|
|
629
|
+
disabled={imagesLoading || !repository}
|
|
630
|
+
onChange={(e) => this.valueChanged('tag', e.target.value)}
|
|
631
|
+
/>
|
|
632
|
+
</span>
|
|
633
|
+
</div>
|
|
634
|
+
<HelpField id="pipeline.config.docker.trigger.tag" expand={true} />
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
) : (
|
|
638
|
+
<div className="sp-formItem">
|
|
639
|
+
<div className="sp-formItem__left">
|
|
640
|
+
<div className="sp-formLabel">Tag</div>
|
|
641
|
+
</div>
|
|
642
|
+
|
|
643
|
+
<div className="sp-formItem__right">
|
|
644
|
+
<div className="sp-form">
|
|
645
|
+
<span className="field">
|
|
646
|
+
{tag && tag.includes('${') ? (
|
|
647
|
+
<input
|
|
648
|
+
className="form-control input-sm"
|
|
649
|
+
disabled={imagesLoading}
|
|
650
|
+
value={tag || ''}
|
|
651
|
+
onChange={(e) => this.valueChanged('tag', e.target.value)}
|
|
652
|
+
required={true}
|
|
653
|
+
/>
|
|
654
|
+
) : (
|
|
655
|
+
<>
|
|
656
|
+
<Select
|
|
657
|
+
value={tag || ''}
|
|
658
|
+
disabled={imagesLoading || !repository}
|
|
659
|
+
isLoading={imagesLoading}
|
|
660
|
+
onChange={(o: Option<string>) => this.valueChanged('tag', o ? o.value : undefined)}
|
|
661
|
+
options={tagOptions}
|
|
662
|
+
placeholder="No tag"
|
|
663
|
+
required={true}
|
|
664
|
+
/>
|
|
665
|
+
<HelpField id="pipeline.config.docker.trigger.tag.additionalInfo" expand={true} />
|
|
666
|
+
</>
|
|
667
|
+
)}
|
|
668
|
+
</span>
|
|
669
|
+
</div>
|
|
670
|
+
</div>
|
|
671
|
+
</div>
|
|
672
|
+
)
|
|
673
|
+
) : null;
|
|
674
|
+
|
|
675
|
+
const Digest =
|
|
676
|
+
lookupType === 'digest' ? (
|
|
677
|
+
<div className="sp-formItem">
|
|
678
|
+
<div className="sp-formItem__left">
|
|
679
|
+
<div className="sp-formLabel">
|
|
680
|
+
Digest <HelpField id="pipeline.config.docker.trigger.digest" />
|
|
681
|
+
</div>
|
|
682
|
+
</div>
|
|
683
|
+
<div className="sp-formItem__right">
|
|
684
|
+
<div className="sp-form">
|
|
685
|
+
<span className="field">
|
|
686
|
+
<input
|
|
687
|
+
className="form-control input-sm"
|
|
688
|
+
placeholder="sha256:abc123"
|
|
689
|
+
value={digest || parsedImageId.digest || ''}
|
|
690
|
+
onChange={(e) => this.valueChanged('digest', e.target.value)}
|
|
691
|
+
required={true}
|
|
692
|
+
/>
|
|
693
|
+
</span>
|
|
694
|
+
</div>
|
|
695
|
+
</div>
|
|
696
|
+
</div>
|
|
697
|
+
) : null;
|
|
698
|
+
|
|
699
|
+
const LookupTypeSelector = showDigest ? (
|
|
700
|
+
<div className="sp-formItem">
|
|
701
|
+
<div className="sp-formItem__left">
|
|
702
|
+
<div className="sp-formLabel">Type</div>
|
|
703
|
+
</div>
|
|
704
|
+
|
|
705
|
+
<div className="sp-formItem__right">
|
|
706
|
+
<div className="sp-form">
|
|
707
|
+
<span className="field">
|
|
708
|
+
<Select
|
|
709
|
+
clearable={false}
|
|
710
|
+
value={lookupType}
|
|
711
|
+
options={[
|
|
712
|
+
{ value: 'digest', label: 'Digest' },
|
|
713
|
+
{ value: 'tag', label: 'Tag' },
|
|
714
|
+
]}
|
|
715
|
+
onChange={this.lookupTypeChanged}
|
|
716
|
+
/>
|
|
717
|
+
</span>
|
|
718
|
+
</div>
|
|
719
|
+
</div>
|
|
720
|
+
</div>
|
|
721
|
+
) : null;
|
|
722
|
+
|
|
723
|
+
return (
|
|
724
|
+
<div className="sp-formGroup">
|
|
725
|
+
{manualInputToggle}
|
|
726
|
+
{Registry}
|
|
727
|
+
{Organization}
|
|
728
|
+
{Image}
|
|
729
|
+
{LookupTypeSelector}
|
|
730
|
+
{Digest}
|
|
731
|
+
{Tag}
|
|
732
|
+
</div>
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
}
|