@spinnaker/ecs 2026.1.1 → 2026.2.0

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.
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import type { IAccountDetails } from '@spinnaker/core';
2
3
  import type { IEcsDockerImage, IEcsServerGroupCommand, IEcsTargetGroupMapping } from '../../serverGroupConfiguration.service';
3
4
  export interface IContainerProps {
4
5
  command: IEcsServerGroupCommand;
@@ -10,6 +11,8 @@ interface IContainerState {
10
11
  computeUnits: number;
11
12
  reservedMemory: number;
12
13
  dockerImages: IEcsDockerImage[];
14
+ dockerRegistryAccounts: IAccountDetails[];
15
+ selectedDockerAccount: string;
13
16
  targetGroupsAvailable: string[];
14
17
  targetGroupMappings: IEcsTargetGroupMapping[];
15
18
  }
@@ -18,6 +21,7 @@ export declare class Container extends React.Component<IContainerProps, IContain
18
21
  componentDidMount(): void;
19
22
  private getIdToImageMap;
20
23
  private getEmptyImageDescription;
24
+ private updateDockerRegistryAccount;
21
25
  private pushTargetGroupMapping;
22
26
  private updateContainerMappingImage;
23
27
  private updateComputeUnits;
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import type { IAccountDetails } from '@spinnaker/core';
2
3
  import type { IEcsContainerMapping, IEcsDockerImage, IEcsServerGroupCommand, IEcsTargetGroupMapping, IEcsTaskDefinitionArtifact } from '../../serverGroupConfiguration.service';
3
4
  export interface ITaskDefinitionProps {
4
5
  command: IEcsServerGroupCommand;
@@ -11,6 +12,8 @@ interface ITaskDefinitionState {
11
12
  containerMappings: IEcsContainerMapping[];
12
13
  targetGroupMappings: IEcsTargetGroupMapping[];
13
14
  dockerImages: IEcsDockerImage[];
15
+ dockerRegistryAccounts: IAccountDetails[];
16
+ selectedDockerAccount: string;
14
17
  targetGroupsAvailable: string[];
15
18
  loadBalancedContainer: string;
16
19
  evaluateTaskDefinitionArtifactExpressions: boolean;
@@ -18,6 +21,7 @@ interface ITaskDefinitionState {
18
21
  export declare class TaskDefinition extends React.Component<ITaskDefinitionProps, ITaskDefinitionState> {
19
22
  constructor(props: ITaskDefinitionProps);
20
23
  componentDidMount(): void;
24
+ private updateDockerRegistryAccount;
21
25
  private getIdToImageMap;
22
26
  private getEmptyImageDescription;
23
27
  private excludedArtifactTypePatterns;
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "https://github.com/spinnaker/spinnaker.git"
6
6
  },
7
7
  "license": "Apache-2.0",
8
- "version": "2026.1.1",
8
+ "version": "2026.2.0",
9
9
  "module": "dist/index.js",
10
10
  "typings": "dist/index.d.ts",
11
11
  "publishConfig": {
@@ -26,7 +26,7 @@
26
26
  "@spinnaker/docker": "^0.0.147",
27
27
  "@uirouter/angularjs": "1.0.26",
28
28
  "@uirouter/react": "1.0.7",
29
- "angular": "1.6.10",
29
+ "angular": "1.8.3",
30
30
  "angular-ui-bootstrap": "2.5.0",
31
31
  "lodash": "4.18.1",
32
32
  "ngimport": "0.6.1",
@@ -47,5 +47,5 @@
47
47
  "shx": "0.3.3",
48
48
  "typescript": "5.0.4"
49
49
  },
50
- "gitHead": "f842aaebe413ea1a14ccec06dd6824e07dc7426b"
50
+ "gitHead": "a9155eb5c4e40b9db5aa9741af28fc2412f7c7d8"
51
51
  }
@@ -239,13 +239,18 @@ export class EcsServerGroupConfigurationService {
239
239
  imageQueries.push(imageQuery);
240
240
  }
241
241
 
242
+ // Scope image search to a known account. If no account is available yet the Docker registry
243
+ // account picker in the wizard will trigger an account-scoped reload once the user selects one.
244
+ const searchAccount = cmd.imageDescription?.account;
245
+
242
246
  let imagesPromise;
243
- if (imageQueries.length) {
247
+ if (imageQueries.length && searchAccount) {
244
248
  imagesPromise = this.$q
245
249
  .all(
246
250
  imageQueries.map((q) =>
247
251
  DockerImageReader.findImages({
248
252
  provider: 'dockerRegistry',
253
+ account: searchAccount,
249
254
  count: 50,
250
255
  q: q,
251
256
  }),
@@ -0,0 +1,121 @@
1
+ import { mount } from 'enzyme';
2
+ import React from 'react';
3
+
4
+ import type { IAccountDetails } from '@spinnaker/core';
5
+ import { AccountService } from '@spinnaker/core';
6
+ import type { IDockerImage } from '@spinnaker/docker';
7
+ import { DockerImageReader } from '@spinnaker/docker';
8
+
9
+ import type { IEcsDockerImage, IEcsServerGroupCommand } from '../../serverGroupConfiguration.service';
10
+ import { Container } from './Container';
11
+
12
+ const flushPromises = (): Promise<void> => new Promise((resolve) => setTimeout(resolve, 0));
13
+
14
+ describe('Container', () => {
15
+ let command: IEcsServerGroupCommand;
16
+
17
+ const configureCommand = (_query: string) => Promise.resolve() as PromiseLike<void>;
18
+ const notifyAngular = (_key: string, _value: any) => {};
19
+
20
+ const dockerAccounts: IAccountDetails[] = [
21
+ {
22
+ name: 'my-docker-account',
23
+ accountId: '1',
24
+ requiredGroupMembership: [],
25
+ type: 'dockerRegistry',
26
+ } as IAccountDetails,
27
+ ];
28
+
29
+ const dockerImages: IDockerImage[] = [
30
+ { account: 'my-docker-account', registry: 'my-registry', repository: 'my-repo', tag: 'latest' },
31
+ ];
32
+
33
+ beforeEach(() => {
34
+ command = ({
35
+ computeUnits: 256,
36
+ reservedMemory: 512,
37
+ imageDescription: null as any,
38
+ targetGroupMappings: [],
39
+ containerMappings: null as any,
40
+ targetGroup: '',
41
+ loadBalancedContainer: '',
42
+ viewState: { dirty: { targetGroups: [] } } as any,
43
+ backingData: { filtered: { images: [], targetGroups: [] } } as any,
44
+ } as any) as IEcsServerGroupCommand;
45
+
46
+ spyOn(AccountService, 'listAccounts').and.returnValue(Promise.resolve(dockerAccounts));
47
+ spyOn(DockerImageReader, 'findImages').and.returnValue(Promise.resolve(dockerImages));
48
+ });
49
+
50
+ describe('updateDockerRegistryAccount', () => {
51
+ it('calls DockerImageReader.findImages with the selected account', async () => {
52
+ const wrapper = mount(
53
+ <Container command={command} notifyAngular={notifyAngular} configureCommand={configureCommand} />,
54
+ );
55
+
56
+ await flushPromises();
57
+
58
+ (wrapper.instance() as any).updateDockerRegistryAccount({ value: 'my-docker-account' });
59
+
60
+ expect(DockerImageReader.findImages).toHaveBeenCalledWith({
61
+ provider: 'dockerRegistry',
62
+ account: 'my-docker-account',
63
+ count: 50,
64
+ });
65
+ });
66
+
67
+ it('updates component state and backingData with images returned for the account', async () => {
68
+ const wrapper = mount(
69
+ <Container command={command} notifyAngular={notifyAngular} configureCommand={configureCommand} />,
70
+ );
71
+
72
+ (wrapper.instance() as any).updateDockerRegistryAccount({ value: 'my-docker-account' });
73
+ await flushPromises();
74
+ wrapper.update();
75
+
76
+ expect((wrapper.instance() as Container).state.dockerImages).toEqual(dockerImages as IEcsDockerImage[]);
77
+ expect(command.backingData.filtered.images).toEqual(dockerImages as IEcsDockerImage[]);
78
+ });
79
+
80
+ it('clears existing images immediately when account changes', () => {
81
+ command.backingData.filtered.images = dockerImages as IEcsDockerImage[];
82
+ const wrapper = mount(
83
+ <Container command={command} notifyAngular={notifyAngular} configureCommand={configureCommand} />,
84
+ );
85
+
86
+ (wrapper.instance() as any).updateDockerRegistryAccount({ value: 'my-docker-account' });
87
+
88
+ // State clears synchronously before the findImages promise resolves
89
+ expect((wrapper.instance() as Container).state.dockerImages).toEqual([]);
90
+ });
91
+
92
+ it('pre-selects the account from imageDescription when command already has one', () => {
93
+ command.imageDescription = {
94
+ account: 'my-docker-account',
95
+ registry: 'my-registry',
96
+ repository: 'my-repo',
97
+ tag: 'latest',
98
+ imageId: 'my-registry/my-repo:latest',
99
+ message: '',
100
+ fromTrigger: false,
101
+ fromContext: false,
102
+ stageId: '',
103
+ imageLabelOrSha: '',
104
+ } as IEcsDockerImage;
105
+
106
+ const wrapper = mount(
107
+ <Container command={command} notifyAngular={notifyAngular} configureCommand={configureCommand} />,
108
+ );
109
+
110
+ expect((wrapper.instance() as Container).state.selectedDockerAccount).toBe('my-docker-account');
111
+ });
112
+
113
+ it('starts with no account selected when imageDescription has no account', () => {
114
+ const wrapper = mount(
115
+ <Container command={command} notifyAngular={notifyAngular} configureCommand={configureCommand} />,
116
+ );
117
+
118
+ expect((wrapper.instance() as Container).state.selectedDockerAccount).toBe('');
119
+ });
120
+ });
121
+ });
@@ -5,7 +5,9 @@ import { Alert } from 'react-bootstrap';
5
5
  import type { Option } from 'react-select';
6
6
  import { react2angular } from 'react2angular';
7
7
 
8
- import { HelpField, TetheredSelect, withErrorBoundary } from '@spinnaker/core';
8
+ import type { IAccountDetails } from '@spinnaker/core';
9
+ import { AccountService, HelpField, TetheredSelect, withErrorBoundary } from '@spinnaker/core';
10
+ import { DockerImageReader } from '@spinnaker/docker';
9
11
 
10
12
  import type {
11
13
  IEcsDockerImage,
@@ -24,6 +26,8 @@ interface IContainerState {
24
26
  computeUnits: number;
25
27
  reservedMemory: number;
26
28
  dockerImages: IEcsDockerImage[];
29
+ dockerRegistryAccounts: IAccountDetails[];
30
+ selectedDockerAccount: string;
27
31
  targetGroupsAvailable: string[];
28
32
  targetGroupMappings: IEcsTargetGroupMapping[];
29
33
  }
@@ -60,6 +64,8 @@ export class Container extends React.Component<IContainerProps, IContainerState>
60
64
  computeUnits: cmd.computeUnits,
61
65
  reservedMemory: cmd.reservedMemory,
62
66
  dockerImages: cmd.backingData && cmd.backingData.filtered ? cmd.backingData.filtered.images : [],
67
+ dockerRegistryAccounts: [],
68
+ selectedDockerAccount: cmd.imageDescription?.account ?? '',
63
69
  targetGroupMappings: cmd.targetGroupMappings,
64
70
  targetGroupsAvailable: cmd.backingData && cmd.backingData.filtered ? cmd.backingData.filtered.targetGroups : [],
65
71
  };
@@ -70,6 +76,10 @@ export class Container extends React.Component<IContainerProps, IContainerState>
70
76
  }
71
77
 
72
78
  public componentDidMount() {
79
+ AccountService.listAccounts('dockerRegistry').then((accounts: IAccountDetails[]) => {
80
+ this.setState({ dockerRegistryAccounts: accounts });
81
+ });
82
+
73
83
  this.props.configureCommand('1').then(() => {
74
84
  this.setState({
75
85
  dockerImages: this.props.command.backingData.filtered.images,
@@ -104,6 +114,16 @@ export class Container extends React.Component<IContainerProps, IContainerState>
104
114
  };
105
115
  };
106
116
 
117
+ private updateDockerRegistryAccount = (newAccount: Option<string>) => {
118
+ const account = newAccount.value;
119
+ this.setState({ selectedDockerAccount: account, dockerImages: [] });
120
+ DockerImageReader.findImages({ provider: 'dockerRegistry', account, count: 50 }).then((images) => {
121
+ const ecsImages = images as IEcsDockerImage[];
122
+ this.props.command.backingData.filtered.images = ecsImages;
123
+ this.setState({ dockerImages: ecsImages });
124
+ });
125
+ };
126
+
107
127
  private pushTargetGroupMapping = () => {
108
128
  const targetMaps = this.state.targetGroupMappings;
109
129
  targetMaps.push({ containerName: '', targetGroup: '', containerPort: 80 });
@@ -171,6 +191,11 @@ export class Container extends React.Component<IContainerProps, IContainerState>
171
191
  ? this.props.command.viewState.dirty.targetGroups
172
192
  : [];
173
193
 
194
+ const dockerRegistryAccountOptions = this.state.dockerRegistryAccounts.map((account) => ({
195
+ label: account.name,
196
+ value: account.name,
197
+ }));
198
+
174
199
  const dockerImageOptions = this.state.dockerImages.map(function (image) {
175
200
  let msg = '';
176
201
  if (image.fromTrigger || image.fromContext) {
@@ -257,6 +282,20 @@ export class Container extends React.Component<IContainerProps, IContainerState>
257
282
  return (
258
283
  <div className="container-fluid form-horizontal">
259
284
  {dirtyTagetGroups.length > 0 ? <div>{dirtyTargetGroupSection}</div> : ''}
285
+ <div className="form-group">
286
+ <div className="col-md-3 sm-label-right">
287
+ <b>Docker Registry Account</b>
288
+ </div>
289
+ <div className="col-md-9" data-test-id="ContainerInputs.dockerRegistryAccount">
290
+ <TetheredSelect
291
+ placeholder="Select a Docker registry account..."
292
+ options={dockerRegistryAccountOptions}
293
+ value={this.state.selectedDockerAccount}
294
+ onChange={(e: Option) => this.updateDockerRegistryAccount(e as Option<string>)}
295
+ clearable={false}
296
+ />
297
+ </div>
298
+ </div>
260
299
  <div className="form-group">
261
300
  <div className="col-md-3 sm-label-right">
262
301
  <b>Container Image</b>
@@ -0,0 +1,124 @@
1
+ import { mount } from 'enzyme';
2
+ import React from 'react';
3
+
4
+ import type { IAccountDetails } from '@spinnaker/core';
5
+ import { AccountService } from '@spinnaker/core';
6
+ import type { IDockerImage } from '@spinnaker/docker';
7
+ import { DockerImageReader } from '@spinnaker/docker';
8
+
9
+ import type { IEcsDockerImage, IEcsServerGroupCommand } from '../../serverGroupConfiguration.service';
10
+ import { TaskDefinition } from './TaskDefinition';
11
+
12
+ const flushPromises = (): Promise<void> => new Promise((resolve) => setTimeout(resolve, 0));
13
+
14
+ describe('TaskDefinition', () => {
15
+ let command: IEcsServerGroupCommand;
16
+
17
+ const configureCommand = (_query: string) => Promise.resolve() as PromiseLike<void>;
18
+ const notifyAngular = (_key: string, _value: any) => {};
19
+
20
+ const dockerAccounts: IAccountDetails[] = [
21
+ {
22
+ name: 'my-docker-account',
23
+ accountId: '1',
24
+ requiredGroupMembership: [],
25
+ type: 'dockerRegistry',
26
+ } as IAccountDetails,
27
+ ];
28
+
29
+ const dockerImages: IDockerImage[] = [
30
+ { account: 'my-docker-account', registry: 'my-registry', repository: 'my-repo', tag: 'latest' },
31
+ ];
32
+
33
+ beforeEach(() => {
34
+ command = ({
35
+ computeUnits: 256,
36
+ reservedMemory: 512,
37
+ imageDescription: null as any,
38
+ targetGroupMappings: [],
39
+ containerMappings: [] as any,
40
+ targetGroup: '',
41
+ loadBalancedContainer: '',
42
+ taskDefinitionArtifact: {} as any,
43
+ taskDefinitionArtifactAccount: '',
44
+ evaluateTaskDefinitionArtifactExpressions: false,
45
+ useTaskDefinitionArtifact: false,
46
+ viewState: { dirty: { targetGroups: [] }, pipeline: null as any, currentStage: null as any } as any,
47
+ backingData: { filtered: { images: [], targetGroups: [] } } as any,
48
+ } as any) as IEcsServerGroupCommand;
49
+
50
+ spyOn(AccountService, 'listAccounts').and.returnValue(Promise.resolve(dockerAccounts));
51
+ spyOn(DockerImageReader, 'findImages').and.returnValue(Promise.resolve(dockerImages));
52
+ });
53
+
54
+ describe('updateDockerRegistryAccount', () => {
55
+ it('calls DockerImageReader.findImages with the selected account', async () => {
56
+ const wrapper = mount(
57
+ <TaskDefinition command={command} notifyAngular={notifyAngular} configureCommand={configureCommand} />,
58
+ );
59
+
60
+ await flushPromises();
61
+
62
+ (wrapper.instance() as any).updateDockerRegistryAccount({ value: 'my-docker-account' });
63
+
64
+ expect(DockerImageReader.findImages).toHaveBeenCalledWith({
65
+ provider: 'dockerRegistry',
66
+ account: 'my-docker-account',
67
+ count: 50,
68
+ });
69
+ });
70
+
71
+ it('updates component state and backingData with images returned for the account', async () => {
72
+ const wrapper = mount(
73
+ <TaskDefinition command={command} notifyAngular={notifyAngular} configureCommand={configureCommand} />,
74
+ );
75
+
76
+ (wrapper.instance() as any).updateDockerRegistryAccount({ value: 'my-docker-account' });
77
+ await flushPromises();
78
+ wrapper.update();
79
+
80
+ expect((wrapper.instance() as TaskDefinition).state.dockerImages).toEqual(dockerImages as IEcsDockerImage[]);
81
+ expect(command.backingData.filtered.images).toEqual(dockerImages as IEcsDockerImage[]);
82
+ });
83
+
84
+ it('clears existing images immediately when account changes', () => {
85
+ command.backingData.filtered.images = dockerImages as IEcsDockerImage[];
86
+ const wrapper = mount(
87
+ <TaskDefinition command={command} notifyAngular={notifyAngular} configureCommand={configureCommand} />,
88
+ );
89
+
90
+ (wrapper.instance() as any).updateDockerRegistryAccount({ value: 'my-docker-account' });
91
+
92
+ expect((wrapper.instance() as TaskDefinition).state.dockerImages).toEqual([]);
93
+ });
94
+
95
+ it('pre-selects the account from imageDescription when command already has one', () => {
96
+ command.imageDescription = {
97
+ account: 'my-docker-account',
98
+ registry: 'my-registry',
99
+ repository: 'my-repo',
100
+ tag: 'latest',
101
+ imageId: 'my-registry/my-repo:latest',
102
+ message: '',
103
+ fromTrigger: false,
104
+ fromContext: false,
105
+ stageId: '',
106
+ imageLabelOrSha: '',
107
+ } as IEcsDockerImage;
108
+
109
+ const wrapper = mount(
110
+ <TaskDefinition command={command} notifyAngular={notifyAngular} configureCommand={configureCommand} />,
111
+ );
112
+
113
+ expect((wrapper.instance() as TaskDefinition).state.selectedDockerAccount).toBe('my-docker-account');
114
+ });
115
+
116
+ it('starts with no account selected when imageDescription has no account', () => {
117
+ const wrapper = mount(
118
+ <TaskDefinition command={command} notifyAngular={notifyAngular} configureCommand={configureCommand} />,
119
+ );
120
+
121
+ expect((wrapper.instance() as TaskDefinition).state.selectedDockerAccount).toBe('');
122
+ });
123
+ });
124
+ });
@@ -5,8 +5,9 @@ import { Alert } from 'react-bootstrap';
5
5
  import type { Option } from 'react-select';
6
6
  import { react2angular } from 'react2angular';
7
7
 
8
- import type { IArtifact, IExpectedArtifact } from '@spinnaker/core';
8
+ import type { IAccountDetails, IArtifact, IExpectedArtifact } from '@spinnaker/core';
9
9
  import {
10
+ AccountService,
10
11
  ArtifactTypePatterns,
11
12
  CheckboxInput,
12
13
  HelpField,
@@ -15,6 +16,7 @@ import {
15
16
  TetheredSelect,
16
17
  withErrorBoundary,
17
18
  } from '@spinnaker/core';
19
+ import { DockerImageReader } from '@spinnaker/docker';
18
20
 
19
21
  import type {
20
22
  IEcsContainerMapping,
@@ -36,6 +38,8 @@ interface ITaskDefinitionState {
36
38
  containerMappings: IEcsContainerMapping[];
37
39
  targetGroupMappings: IEcsTargetGroupMapping[];
38
40
  dockerImages: IEcsDockerImage[];
41
+ dockerRegistryAccounts: IAccountDetails[];
42
+ selectedDockerAccount: string;
39
43
  targetGroupsAvailable: string[];
40
44
  loadBalancedContainer: string;
41
45
  evaluateTaskDefinitionArtifactExpressions: boolean;
@@ -73,6 +77,8 @@ export class TaskDefinition extends React.Component<ITaskDefinitionProps, ITaskD
73
77
  targetGroupMappings: cmd.targetGroupMappings,
74
78
  targetGroupsAvailable: cmd.backingData && cmd.backingData.filtered ? cmd.backingData.filtered.targetGroups : [],
75
79
  dockerImages: cmd.backingData && cmd.backingData.filtered ? cmd.backingData.filtered.images : [],
80
+ dockerRegistryAccounts: [],
81
+ selectedDockerAccount: cmd.imageDescription?.account ?? '',
76
82
  loadBalancedContainer: cmd.loadBalancedContainer || defaultContainer,
77
83
  taskDefArtifactAccount: cmd.taskDefinitionArtifactAccount,
78
84
  evaluateTaskDefinitionArtifactExpressions: cmd.evaluateTaskDefinitionArtifactExpressions,
@@ -81,6 +87,10 @@ export class TaskDefinition extends React.Component<ITaskDefinitionProps, ITaskD
81
87
 
82
88
  // TODO: Separate docker image component used by both TaskDefinition and Container
83
89
  public componentDidMount() {
90
+ AccountService.listAccounts('dockerRegistry').then((accounts: IAccountDetails[]) => {
91
+ this.setState({ dockerRegistryAccounts: accounts });
92
+ });
93
+
84
94
  this.props.configureCommand('1').then(() => {
85
95
  this.setState({
86
96
  dockerImages: this.props.command.backingData.filtered.images,
@@ -89,6 +99,16 @@ export class TaskDefinition extends React.Component<ITaskDefinitionProps, ITaskD
89
99
  });
90
100
  }
91
101
 
102
+ private updateDockerRegistryAccount = (newAccount: Option<string>) => {
103
+ const account = newAccount.value;
104
+ this.setState({ selectedDockerAccount: account, dockerImages: [] });
105
+ DockerImageReader.findImages({ provider: 'dockerRegistry', account, count: 50 }).then((images) => {
106
+ const ecsImages = images as IEcsDockerImage[];
107
+ this.props.command.backingData.filtered.images = ecsImages;
108
+ this.setState({ dockerImages: ecsImages });
109
+ });
110
+ };
111
+
92
112
  private getIdToImageMap = (): Map<string, IEcsDockerImage> => {
93
113
  const imageIdToDescription = new Map<string, IEcsDockerImage>();
94
114
  this.props.command.backingData.filtered.images.forEach((e) => {
@@ -224,6 +244,11 @@ export class TaskDefinition extends React.Component<ITaskDefinitionProps, ITaskD
224
244
  const updateTargetGroupMappingPort = this.updateTargetGroupMappingPort;
225
245
  const updateEvaluateTaskDefArtifactFlag = this.updateEvaluateTaskDefArtifactFlag;
226
246
 
247
+ const dockerRegistryAccountOptions = this.state.dockerRegistryAccounts.map((account) => ({
248
+ label: account.name,
249
+ value: account.name,
250
+ }));
251
+
227
252
  const dockerImageOptions = this.state.dockerImages.map(function (image) {
228
253
  let msg = '';
229
254
  if (image.fromTrigger || image.fromContext) {
@@ -373,6 +398,21 @@ export class TaskDefinition extends React.Component<ITaskDefinitionProps, ITaskD
373
398
  />
374
399
  </StageConfigField>
375
400
 
401
+ <div className="form-group">
402
+ <div className="col-md-3 sm-label-right">
403
+ <b>Docker Registry Account</b>
404
+ </div>
405
+ <div className="col-md-9" data-test-id="Artifacts.dockerRegistryAccount">
406
+ <TetheredSelect
407
+ placeholder="Select a Docker registry account..."
408
+ options={dockerRegistryAccountOptions}
409
+ value={this.state.selectedDockerAccount}
410
+ onChange={(e: Option) => this.updateDockerRegistryAccount(e as Option<string>)}
411
+ clearable={false}
412
+ />
413
+ </div>
414
+ </div>
415
+
376
416
  <div className="form-group">
377
417
  <div className="sm-label-left">
378
418
  <b>Container Mappings</b>