@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,246 @@
|
|
|
1
|
+
import { get } from 'lodash';
|
|
2
|
+
import { $q } from 'ngimport';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import type { Option } from 'react-select';
|
|
5
|
+
import type { Subscription } from 'rxjs';
|
|
6
|
+
import { from as observableFrom, Subject } from 'rxjs';
|
|
7
|
+
import { debounceTime, switchMap } from 'rxjs/operators';
|
|
8
|
+
|
|
9
|
+
import type { IDockerTrigger, IPipelineCommand, ITriggerTemplateComponentProps } from '@spinnaker/core';
|
|
10
|
+
import { HelpField, Spinner, TetheredSelect } from '@spinnaker/core';
|
|
11
|
+
|
|
12
|
+
import type { IDockerLookupType } from '../../image';
|
|
13
|
+
import { DockerImageReader } from '../../image';
|
|
14
|
+
|
|
15
|
+
const lookupTypeOptions = [
|
|
16
|
+
{ value: 'digest', label: 'Digest' },
|
|
17
|
+
{ value: 'tag', label: 'Tag' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export interface IDockerTriggerTemplateState {
|
|
21
|
+
digest: string;
|
|
22
|
+
tags: string[];
|
|
23
|
+
tagsLoading: boolean;
|
|
24
|
+
loadError: boolean;
|
|
25
|
+
lookupType: string;
|
|
26
|
+
selectedTag: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class DockerTriggerTemplate extends React.Component<
|
|
30
|
+
ITriggerTemplateComponentProps,
|
|
31
|
+
IDockerTriggerTemplateState
|
|
32
|
+
> {
|
|
33
|
+
private queryStream = new Subject();
|
|
34
|
+
private subscription: Subscription;
|
|
35
|
+
|
|
36
|
+
public static formatLabel(trigger: IDockerTrigger): PromiseLike<string> {
|
|
37
|
+
return $q.when(`(Docker Registry) ${trigger.account ? trigger.account + ':' : ''} ${trigger.repository || ''}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public constructor(props: ITriggerTemplateComponentProps) {
|
|
41
|
+
super(props);
|
|
42
|
+
this.state = {
|
|
43
|
+
digest: '',
|
|
44
|
+
tags: [],
|
|
45
|
+
tagsLoading: true,
|
|
46
|
+
loadError: false,
|
|
47
|
+
lookupType: 'tag',
|
|
48
|
+
selectedTag: '',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private handleQuery = () => {
|
|
53
|
+
const trigger = this.props.command.trigger as IDockerTrigger;
|
|
54
|
+
return observableFrom(
|
|
55
|
+
DockerImageReader.findTags({
|
|
56
|
+
provider: 'dockerRegistry',
|
|
57
|
+
account: trigger.account,
|
|
58
|
+
repository: trigger.repository,
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
private lookupTypeChanged = (o: Option<IDockerLookupType>) => {
|
|
64
|
+
const newType = o.value;
|
|
65
|
+
this.updateArtifact(this.props.command, newType === 'tag' ? this.state.selectedTag : this.state.digest);
|
|
66
|
+
this.setState({ lookupType: newType });
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
private updateArtifact(command: IPipelineCommand, tagOrDigest: string) {
|
|
70
|
+
this.props.updateCommand('extraFields.tag', tagOrDigest);
|
|
71
|
+
const trigger = command.trigger as IDockerTrigger;
|
|
72
|
+
if (trigger && trigger.repository) {
|
|
73
|
+
let imageName = '';
|
|
74
|
+
if (trigger.registry) {
|
|
75
|
+
imageName += trigger.registry + '/';
|
|
76
|
+
}
|
|
77
|
+
imageName += trigger.repository;
|
|
78
|
+
|
|
79
|
+
let imageReference = '';
|
|
80
|
+
if (this.state.lookupType === 'digest') {
|
|
81
|
+
imageReference = `${imageName}@${tagOrDigest}`;
|
|
82
|
+
} else {
|
|
83
|
+
imageReference = `${imageName}:${tagOrDigest}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.props.updateCommand('extraFields.artifacts', [
|
|
87
|
+
{
|
|
88
|
+
type: 'docker/image',
|
|
89
|
+
name: imageName,
|
|
90
|
+
version: tagOrDigest,
|
|
91
|
+
reference: imageReference,
|
|
92
|
+
},
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private updateSelectedTag = (tag: string) => {
|
|
98
|
+
this.updateArtifact(this.props.command, tag);
|
|
99
|
+
this.setState({ selectedTag: tag });
|
|
100
|
+
this.props.command.triggerInvalid = false;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
private updateDigest = (digest: string) => {
|
|
104
|
+
this.updateArtifact(this.props.command, digest);
|
|
105
|
+
this.setState({ digest });
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
private tagLoadSuccess = (tags: string[]) => {
|
|
109
|
+
const { command } = this.props;
|
|
110
|
+
const trigger = command.trigger as IDockerTrigger;
|
|
111
|
+
const newState = {} as IDockerTriggerTemplateState;
|
|
112
|
+
newState.tags = tags || [];
|
|
113
|
+
// default to what is supplied by the trigger if possible
|
|
114
|
+
const defaultSelection = newState.tags.find((t) => t === trigger.tag);
|
|
115
|
+
if (defaultSelection) {
|
|
116
|
+
newState.selectedTag = defaultSelection;
|
|
117
|
+
this.updateSelectedTag(defaultSelection);
|
|
118
|
+
}
|
|
119
|
+
newState.tagsLoading = false;
|
|
120
|
+
this.setState(newState);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
private tagLoadFailure = () => {
|
|
124
|
+
this.setState({
|
|
125
|
+
tagsLoading: false,
|
|
126
|
+
loadError: true,
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
private initialize = () => {
|
|
131
|
+
const { command } = this.props;
|
|
132
|
+
this.props.updateCommand('triggerInvalid', true);
|
|
133
|
+
|
|
134
|
+
// These fields will be added to the trigger when the form is submitted
|
|
135
|
+
this.props.updateCommand('extraFields', {
|
|
136
|
+
tag: get(command, 'extraFields.tag', ''),
|
|
137
|
+
artifacts: get(command, 'extraFields.artifacts', ''),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (this.subscription) {
|
|
141
|
+
this.subscription.unsubscribe();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// cancel search stream if trigger has changed to some other type
|
|
145
|
+
if (command.trigger.type !== 'docker') {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.subscription = this.queryStream
|
|
150
|
+
.pipe(debounceTime(250), switchMap(this.handleQuery))
|
|
151
|
+
.subscribe(this.tagLoadSuccess, this.tagLoadFailure);
|
|
152
|
+
|
|
153
|
+
this.searchTags();
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
public componentDidMount() {
|
|
157
|
+
this.initialize();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public componentWillUnmount() {
|
|
161
|
+
if (this.subscription) {
|
|
162
|
+
this.subscription.unsubscribe();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private searchTags = (query = '') => {
|
|
167
|
+
this.setState({ tags: [`<span>Finding tags${query && ` matching ${query}`}...</span>`] });
|
|
168
|
+
this.queryStream.next();
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
public render() {
|
|
172
|
+
const { digest, tags, tagsLoading, loadError, selectedTag, lookupType } = this.state;
|
|
173
|
+
|
|
174
|
+
const options = tags.map((tag) => {
|
|
175
|
+
return { value: tag } as Option<string>;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<>
|
|
180
|
+
<div className="form-group">
|
|
181
|
+
<div className="sm-label-right col-md-4">Type</div>
|
|
182
|
+
<div className="col-md-3">
|
|
183
|
+
<TetheredSelect
|
|
184
|
+
clearable={false}
|
|
185
|
+
value={lookupType}
|
|
186
|
+
options={lookupTypeOptions}
|
|
187
|
+
onChange={this.lookupTypeChanged}
|
|
188
|
+
/>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
{lookupType === 'tag' && (
|
|
192
|
+
<div className="form-group">
|
|
193
|
+
<label className="col-md-4 sm-label-right">Tag</label>
|
|
194
|
+
{tagsLoading && (
|
|
195
|
+
<div className="col-md-6">
|
|
196
|
+
<div className="form-control-static text-center">
|
|
197
|
+
<Spinner size="small" />
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
{loadError && <div className="col-md-6">Error loading tags!</div>}
|
|
202
|
+
{!tagsLoading && (
|
|
203
|
+
<div className="col-md-6">
|
|
204
|
+
{tags.length === 0 && (
|
|
205
|
+
<div>
|
|
206
|
+
<p className="form-control-static">No tags found</p>
|
|
207
|
+
</div>
|
|
208
|
+
)}
|
|
209
|
+
{tags.length > 0 && (
|
|
210
|
+
<TetheredSelect
|
|
211
|
+
options={options}
|
|
212
|
+
optionRenderer={(o) => <span>{o.value}</span>}
|
|
213
|
+
clearable={false}
|
|
214
|
+
value={selectedTag}
|
|
215
|
+
valueRenderer={(o) => (
|
|
216
|
+
<span>
|
|
217
|
+
<strong>{o.value}</strong>
|
|
218
|
+
</span>
|
|
219
|
+
)}
|
|
220
|
+
onChange={(o: Option<string>) => this.updateSelectedTag(o.value)}
|
|
221
|
+
placeholder="Search tags..."
|
|
222
|
+
/>
|
|
223
|
+
)}
|
|
224
|
+
</div>
|
|
225
|
+
)}
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
{lookupType === 'digest' && (
|
|
229
|
+
<div className="form-group">
|
|
230
|
+
<label className="col-md-4 sm-label-right">
|
|
231
|
+
Digest <HelpField id="pipeline.config.docker.trigger.digest" />
|
|
232
|
+
</label>
|
|
233
|
+
<div className="col-md-6">
|
|
234
|
+
<input
|
|
235
|
+
value={digest}
|
|
236
|
+
onChange={(e) => this.updateDigest(e.target.value)}
|
|
237
|
+
className="form-control input-sm"
|
|
238
|
+
required={true}
|
|
239
|
+
/>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
243
|
+
</>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
}
|