@spinnaker/ecs 2026.2.4 → 2026.3.1

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.
Files changed (209) hide show
  1. package/dist/ecs.module.d.ts +3 -1
  2. package/dist/ecsCluster/ecsCluster.read.service.d.ts +2 -3
  3. package/dist/iamRoles/iamRole.read.service.d.ts +1 -2
  4. package/dist/index.d.ts +5 -0
  5. package/dist/index.js +1 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/instance/details/EcsInstanceDetails.d.ts +50 -0
  8. package/dist/loadBalancer/loadBalancer.transformer.d.ts +1 -5
  9. package/dist/loadBalancer/targetGroup.states.d.ts +2 -1
  10. package/dist/metricAlarm/metricAlarm.read.service.d.ts +1 -2
  11. package/dist/pipeline/stages/cloneServerGroup/ecsCloneServerGroupStage.d.ts +1 -0
  12. package/dist/pipeline/stages/common/EcsServerGroupStepLabel.d.ts +7 -0
  13. package/dist/pipeline/stages/common/EcsStageConfigs.d.ts +11 -0
  14. package/dist/pipeline/stages/destroyAsg/ecsDestroyAsgStage.d.ts +1 -2
  15. package/dist/pipeline/stages/disableAsg/ecsDisableAsgStage.d.ts +1 -2
  16. package/dist/pipeline/stages/disableCluster/ecsDisableClusterStage.d.ts +1 -2
  17. package/dist/pipeline/stages/enableAsg/ecsEnableAsgStage.d.ts +1 -2
  18. package/dist/pipeline/stages/findImageFromTags/EcsFindImageFromTagsExecutionDetails.d.ts +5 -0
  19. package/dist/pipeline/stages/findImageFromTags/ecsFindImageFromTagStage.d.ts +1 -2
  20. package/dist/pipeline/stages/resizeAsg/ecsResizeAsgStage.d.ts +1 -2
  21. package/dist/pipeline/stages/scaleDownCluster/ecsScaleDownClusterStage.d.ts +1 -2
  22. package/dist/pipeline/stages/shrinkCluster/ecsShrinkClusterStage.d.ts +1 -2
  23. package/dist/placementStrategy/placementStrategy.service.d.ts +0 -1
  24. package/dist/secrets/secret.read.service.d.ts +1 -2
  25. package/dist/securityGroup/details/EcsSecurityGroupDetails.d.ts +45 -0
  26. package/dist/securityGroup/securityGroup.reader.d.ts +0 -1
  27. package/dist/securityGroup/securityGroup.transformer.d.ts +3 -2
  28. package/dist/serverGroup/configure/dockerImage.util.d.ts +3 -0
  29. package/dist/serverGroup/configure/serverGroupCommandBuilder.service.d.ts +111 -2
  30. package/dist/serverGroup/configure/serverGroupConfiguration.service.d.ts +13 -63
  31. package/dist/serverGroup/configure/wizard/EcsCloneServerGroupModal.d.ts +64 -0
  32. package/dist/serverGroup/configure/wizard/capacityProvider/CapacityProvider.d.ts +29 -4
  33. package/dist/serverGroup/configure/wizard/container/Container.d.ts +3 -3
  34. package/dist/serverGroup/configure/wizard/networking/Networking.d.ts +3 -3
  35. package/dist/serverGroup/configure/wizard/pages/AdvancedSettings.d.ts +3 -0
  36. package/dist/serverGroup/configure/wizard/pages/BasicSettings.d.ts +3 -0
  37. package/dist/serverGroup/configure/wizard/pages/ContainerSettings.d.ts +3 -0
  38. package/dist/serverGroup/configure/wizard/pages/HorizontalScalingSettings.d.ts +3 -0
  39. package/dist/serverGroup/configure/wizard/pages/LoggingSettings.d.ts +3 -0
  40. package/dist/serverGroup/configure/wizard/pages/NetworkingSettings.d.ts +3 -0
  41. package/dist/serverGroup/configure/wizard/pages/ServiceDiscoverySettings.d.ts +3 -0
  42. package/dist/serverGroup/configure/wizard/pages/TaskDefinitionSettings.d.ts +3 -0
  43. package/dist/serverGroup/configure/wizard/pages/common.d.ts +8 -0
  44. package/dist/serverGroup/configure/wizard/pages/validation.d.ts +22 -0
  45. package/dist/serverGroup/configure/wizard/serviceDiscovery/ServiceDiscovery.d.ts +5 -3
  46. package/dist/serverGroup/configure/wizard/taskDefinition/TaskDefinition.d.ts +3 -3
  47. package/dist/serverGroup/details/EcsServerGroupActions.d.ts +6 -0
  48. package/dist/serverGroup/details/ecsServerGroupDetailsGetter.d.ts +3 -0
  49. package/dist/serverGroup/details/resize/EcsResizeServerGroupModal.d.ts +43 -0
  50. package/dist/serverGroup/details/resize/index.d.ts +1 -0
  51. package/dist/serverGroup/details/rollback/EcsRollbackServerGroupModal.d.ts +46 -0
  52. package/dist/serverGroup/details/rollback/index.d.ts +1 -0
  53. package/dist/serverGroup/details/sections/EcsServerGroupDetailsSections.d.ts +9 -0
  54. package/dist/serverGroup/details/sections/EcsServerGroupEventsSection.d.ts +3 -0
  55. package/dist/serverGroup/details/sections/EcsServerGroupInformationSection.d.ts +3 -0
  56. package/dist/serverGroup/events/EcsServerGroupEventsModal.d.ts +22 -0
  57. package/dist/serverGroup/events/EventsLink.d.ts +2 -0
  58. package/dist/serverGroup/events/serverGroupEventsReader.service.d.ts +1 -1
  59. package/dist/serverGroup/serverGroup.transformer.d.ts +1 -2
  60. package/dist/serviceDiscovery/serviceDiscovery.read.service.d.ts +1 -1
  61. package/package.json +3 -9
  62. package/src/ecs.module.spec.ts +86 -0
  63. package/src/ecs.module.ts +68 -84
  64. package/src/ecsCluster/ecsCluster.read.service.ts +3 -9
  65. package/src/iamRoles/iamRole.read.service.ts +1 -7
  66. package/src/index.ts +6 -0
  67. package/src/instance/details/EcsInstanceDetails.spec.tsx +426 -0
  68. package/src/instance/details/EcsInstanceDetails.tsx +474 -0
  69. package/src/loadBalancer/details/loadBalancerDetails.tsx +1 -1
  70. package/src/loadBalancer/details/targetGroupDetails.tsx +1 -1
  71. package/src/loadBalancer/loadBalancer.transformer.ts +2 -7
  72. package/src/loadBalancer/targetGroup.states.ts +55 -53
  73. package/src/metricAlarm/metricAlarm.read.service.ts +1 -7
  74. package/src/pipeline/stages/cloneServerGroup/ecsCloneServerGroupStage.js +22 -94
  75. package/src/pipeline/stages/common/EcsServerGroupStepLabel.tsx +16 -0
  76. package/src/pipeline/stages/common/EcsStageConfigs.tsx +587 -0
  77. package/src/pipeline/stages/destroyAsg/ecsDestroyAsgStage.js +29 -65
  78. package/src/pipeline/stages/disableAsg/ecsDisableAsgStage.js +27 -70
  79. package/src/pipeline/stages/disableCluster/ecsDisableClusterStage.js +21 -84
  80. package/src/pipeline/stages/enableAsg/ecsEnableAsgStage.js +22 -73
  81. package/src/pipeline/stages/findImageFromTags/EcsFindImageFromTagsExecutionDetails.tsx +40 -0
  82. package/src/pipeline/stages/findImageFromTags/ecsFindImageFromTagStage.js +12 -24
  83. package/src/pipeline/stages/resizeAsg/ecsResizeAsgStage.js +30 -128
  84. package/src/pipeline/stages/scaleDownCluster/ecsScaleDownClusterStage.js +24 -78
  85. package/src/pipeline/stages/shrinkCluster/ecsShrinkClusterStage.js +19 -73
  86. package/src/placementStrategy/placementStrategy.service.ts +0 -6
  87. package/src/secrets/secret.read.service.ts +1 -7
  88. package/src/securityGroup/details/EcsSecurityGroupDetails.spec.tsx +245 -0
  89. package/src/securityGroup/details/EcsSecurityGroupDetails.tsx +285 -0
  90. package/src/securityGroup/securityGroup.reader.ts +0 -5
  91. package/src/securityGroup/securityGroup.transformer.js +5 -13
  92. package/src/serverGroup/configure/dockerImage.util.ts +29 -0
  93. package/src/serverGroup/configure/serverGroupCommandBuilder.service.js +215 -278
  94. package/src/serverGroup/configure/serverGroupCommandBuilder.service.spec.ts +83 -0
  95. package/src/serverGroup/configure/serverGroupConfiguration.service.ts +16 -633
  96. package/src/serverGroup/configure/wizard/EcsCloneServerGroupModal.spec.tsx +2078 -0
  97. package/src/serverGroup/configure/wizard/EcsCloneServerGroupModal.tsx +712 -0
  98. package/src/serverGroup/configure/wizard/capacityProvider/CapacityProvider.tsx +98 -49
  99. package/src/serverGroup/configure/wizard/container/Container.spec.tsx +20 -8
  100. package/src/serverGroup/configure/wizard/container/Container.tsx +53 -24
  101. package/src/serverGroup/configure/wizard/networking/Networking.tsx +60 -38
  102. package/src/serverGroup/configure/wizard/pages/AdvancedSettings.tsx +290 -0
  103. package/src/serverGroup/configure/wizard/pages/BasicSettings.tsx +101 -0
  104. package/src/serverGroup/configure/wizard/pages/ContainerSettings.tsx +10 -0
  105. package/src/serverGroup/configure/wizard/pages/HorizontalScalingSettings.tsx +137 -0
  106. package/src/serverGroup/configure/wizard/pages/LoggingSettings.tsx +60 -0
  107. package/src/serverGroup/configure/wizard/pages/NetworkingSettings.tsx +10 -0
  108. package/src/serverGroup/configure/wizard/pages/ServiceDiscoverySettings.tsx +10 -0
  109. package/src/serverGroup/configure/wizard/pages/TaskDefinitionSettings.tsx +59 -0
  110. package/src/serverGroup/configure/wizard/pages/common.ts +10 -0
  111. package/src/serverGroup/configure/wizard/pages/validation.tsx +190 -0
  112. package/src/serverGroup/configure/wizard/serviceDiscovery/ServiceDiscovery.tsx +83 -34
  113. package/src/serverGroup/configure/wizard/taskDefinition/TaskDefinition.spec.tsx +21 -8
  114. package/src/serverGroup/configure/wizard/taskDefinition/TaskDefinition.tsx +63 -33
  115. package/src/serverGroup/details/EcsServerGroupActions.spec.tsx +244 -0
  116. package/src/serverGroup/details/EcsServerGroupActions.tsx +152 -0
  117. package/src/serverGroup/details/ecsServerGroupDetailsGetter.spec.ts +110 -0
  118. package/src/serverGroup/details/ecsServerGroupDetailsGetter.ts +73 -0
  119. package/src/serverGroup/details/resize/EcsResizeServerGroupModal.spec.tsx +130 -0
  120. package/src/serverGroup/details/resize/EcsResizeServerGroupModal.tsx +263 -0
  121. package/src/serverGroup/details/resize/index.ts +1 -0
  122. package/src/serverGroup/details/rollback/EcsRollbackServerGroupModal.spec.tsx +173 -0
  123. package/src/serverGroup/details/rollback/EcsRollbackServerGroupModal.tsx +285 -0
  124. package/src/serverGroup/details/rollback/index.ts +1 -0
  125. package/src/serverGroup/details/sections/EcsServerGroupDetailsSections.spec.tsx +130 -0
  126. package/src/serverGroup/details/sections/EcsServerGroupDetailsSections.tsx +172 -0
  127. package/src/serverGroup/details/sections/EcsServerGroupEventsSection.tsx +14 -0
  128. package/src/serverGroup/details/sections/EcsServerGroupInformationSection.tsx +25 -0
  129. package/src/serverGroup/events/EcsServerGroupEventsModal.spec.tsx +109 -0
  130. package/src/serverGroup/events/EcsServerGroupEventsModal.tsx +112 -0
  131. package/src/serverGroup/events/EventsLink.tsx +18 -0
  132. package/src/serverGroup/events/serverGroupEventsReader.service.spec.ts +41 -0
  133. package/src/serverGroup/events/serverGroupEventsReader.service.ts +2 -8
  134. package/src/serverGroup/serverGroup.transformer.spec.ts +10 -24
  135. package/src/serverGroup/serverGroup.transformer.ts +1 -5
  136. package/src/serviceDiscovery/serviceDiscovery.read.service.ts +1 -1
  137. package/dist/common/common.module.d.ts +0 -1
  138. package/dist/common/footer.component.d.ts +0 -1
  139. package/dist/instance/details/instance.details.controller.d.ts +0 -2
  140. package/dist/securityGroup/details/securityGroupDetail.controller.d.ts +0 -2
  141. package/dist/securityGroup/securityGroup.module.d.ts +0 -1
  142. package/dist/serverGroup/configure/wizard/CloneServerGroup.ecs.controller.d.ts +0 -2
  143. package/dist/serverGroup/configure/wizard/advancedSettings/advancedSettings.component.d.ts +0 -2
  144. package/dist/serverGroup/configure/wizard/horizontalScaling/horizontalScaling.component.d.ts +0 -2
  145. package/dist/serverGroup/configure/wizard/location/ServerGroupBasicSettings.controller.d.ts +0 -2
  146. package/dist/serverGroup/configure/wizard/logging/logging.component.d.ts +0 -1
  147. package/dist/serverGroup/details/resize/resizeCapacity.component.d.ts +0 -1
  148. package/dist/serverGroup/details/resize/resizeServerGroup.controller.d.ts +0 -2
  149. package/dist/serverGroup/details/rollback/rollbackServerGroup.controller.d.ts +0 -2
  150. package/dist/serverGroup/details/serverGroupDetails.ecs.controller.d.ts +0 -2
  151. package/dist/serverGroup/details/serverGroupDetails.module.d.ts +0 -1
  152. package/dist/serverGroup/events/events.component.d.ts +0 -3
  153. package/dist/serverGroup/events/events.controller.d.ts +0 -19
  154. package/dist/serverGroup/serverGroup.module.d.ts +0 -1
  155. package/src/common/common.module.ts +0 -6
  156. package/src/common/footer.component.ts +0 -29
  157. package/src/instance/details/instance.details.controller.js +0 -356
  158. package/src/instance/details/instanceDetails.html +0 -204
  159. package/src/pipeline/stages/cloneServerGroup/cloneServerGroupStage.html +0 -101
  160. package/src/pipeline/stages/cloneServerGroup/cloneServerGroupStepLabel.html +0 -1
  161. package/src/pipeline/stages/destroyAsg/destroyAsgStage.html +0 -9
  162. package/src/pipeline/stages/destroyAsg/destroyAsgStepLabel.html +0 -1
  163. package/src/pipeline/stages/disableAsg/disableAsgStage.html +0 -11
  164. package/src/pipeline/stages/disableAsg/disableAsgStepLabel.html +0 -1
  165. package/src/pipeline/stages/disableCluster/disableClusterStage.html +0 -26
  166. package/src/pipeline/stages/enableAsg/enableAsgStage.html +0 -11
  167. package/src/pipeline/stages/enableAsg/enableAsgStepLabel.html +0 -1
  168. package/src/pipeline/stages/findImageFromTags/findImageFromTagsExecutionDetails.html +0 -36
  169. package/src/pipeline/stages/findImageFromTags/findImageFromTagsStage.html +0 -9
  170. package/src/pipeline/stages/resizeAsg/resizeAsgStage.html +0 -87
  171. package/src/pipeline/stages/resizeAsg/resizeAsgStepLabel.html +0 -1
  172. package/src/pipeline/stages/scaleDownCluster/scaleDownClusterStage.html +0 -35
  173. package/src/pipeline/stages/shrinkCluster/shrinkClusterStage.html +0 -34
  174. package/src/securityGroup/details/securityGroupDetail.controller.js +0 -147
  175. package/src/securityGroup/details/securityGroupDetail.html +0 -104
  176. package/src/securityGroup/securityGroup.module.ts +0 -12
  177. package/src/serverGroup/configure/wizard/CloneServerGroup.ecs.controller.js +0 -259
  178. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettings.component.html +0 -194
  179. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettings.component.js +0 -17
  180. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettings.html +0 -10
  181. package/src/serverGroup/configure/wizard/capacityProvider/capacityProvider.html +0 -11
  182. package/src/serverGroup/configure/wizard/container/container.html +0 -9
  183. package/src/serverGroup/configure/wizard/horizontalScaling/horizontalScaling.component.html +0 -118
  184. package/src/serverGroup/configure/wizard/horizontalScaling/horizontalScaling.component.js +0 -20
  185. package/src/serverGroup/configure/wizard/horizontalScaling/horizontalScaling.html +0 -13
  186. package/src/serverGroup/configure/wizard/location/ServerGroupBasicSettings.controller.js +0 -41
  187. package/src/serverGroup/configure/wizard/location/basicSettings.html +0 -137
  188. package/src/serverGroup/configure/wizard/logging/logging.component.html +0 -29
  189. package/src/serverGroup/configure/wizard/logging/logging.component.ts +0 -12
  190. package/src/serverGroup/configure/wizard/logging/logging.html +0 -7
  191. package/src/serverGroup/configure/wizard/networking/networking.html +0 -9
  192. package/src/serverGroup/configure/wizard/serverGroupWizard.html +0 -55
  193. package/src/serverGroup/configure/wizard/serviceDiscovery/serviceDiscovery.html +0 -11
  194. package/src/serverGroup/configure/wizard/taskDefinition/taskDefinition.html +0 -51
  195. package/src/serverGroup/configure/wizard/templateSelection/templateSelection.html +0 -9
  196. package/src/serverGroup/configure/wizard/verticalScaling/verticalScaling.html +0 -10
  197. package/src/serverGroup/details/resize/resizeCapacity.component.html +0 -94
  198. package/src/serverGroup/details/resize/resizeCapacity.component.ts +0 -14
  199. package/src/serverGroup/details/resize/resizeServerGroup.controller.js +0 -85
  200. package/src/serverGroup/details/resize/resizeServerGroup.html +0 -28
  201. package/src/serverGroup/details/rollback/rollbackServerGroup.controller.js +0 -97
  202. package/src/serverGroup/details/rollback/rollbackServerGroup.html +0 -67
  203. package/src/serverGroup/details/serverGroupDetails.ecs.controller.js +0 -366
  204. package/src/serverGroup/details/serverGroupDetails.html +0 -216
  205. package/src/serverGroup/details/serverGroupDetails.module.ts +0 -5
  206. package/src/serverGroup/events/events.component.ts +0 -35
  207. package/src/serverGroup/events/events.controller.ts +0 -45
  208. package/src/serverGroup/events/events.html +0 -36
  209. package/src/serverGroup/serverGroup.module.ts +0 -6
@@ -0,0 +1,244 @@
1
+ import { mount as enzymeMount } from 'enzyme';
2
+ import React from 'react';
3
+ import { Dropdown, Tooltip } from 'react-bootstrap';
4
+
5
+ import { AWSProviderSettings } from '@spinnaker/amazon';
6
+ import {
7
+ AddEntityTagLinks,
8
+ ConfirmationModalService,
9
+ DeckRuntimeContext,
10
+ ManagedMenuItem,
11
+ SETTINGS,
12
+ ServerGroupWarningMessageService,
13
+ } from '@spinnaker/core';
14
+
15
+ import { EcsServerGroupActionsComponent as EcsServerGroupActions } from './EcsServerGroupActions';
16
+ import { EcsResizeServerGroupModal } from './resize/EcsResizeServerGroupModal';
17
+ import { EcsRollbackServerGroupModal } from './rollback/EcsRollbackServerGroupModal';
18
+
19
+ describe('<EcsServerGroupActions />', () => {
20
+ const originalAdHocInfraWritesEnabled = AWSProviderSettings.adHocInfraWritesEnabled;
21
+ let runtimeServices: any;
22
+ const RuntimeWrapper = ({ children }: React.PropsWithChildren<{}>) => (
23
+ <DeckRuntimeContext.Provider value={{ services: runtimeServices } as any}>{children}</DeckRuntimeContext.Provider>
24
+ );
25
+ const shallow = (component: React.ReactElement) => enzymeMount(component, { wrappingComponent: RuntimeWrapper });
26
+
27
+ const buildServerGroup = (overrides: any = {}) => ({
28
+ account: 'test-account',
29
+ capacity: { desired: 2, max: 4, min: 1 },
30
+ cloudProvider: 'ecs',
31
+ cluster: 'test-app-main',
32
+ isDisabled: false,
33
+ moniker: { app: 'test-app', cluster: 'test-app-main' },
34
+ name: 'test-app-main-v002',
35
+ region: 'us-east-1',
36
+ runningTasks: [],
37
+ ...overrides,
38
+ });
39
+
40
+ const buildApp = (overrides: any = {}) => {
41
+ const serverGroups = overrides.serverGroups || [];
42
+ return {
43
+ attributes: {},
44
+ getDataSource: (key: string) => (key === 'serverGroups' ? { data: serverGroups } : undefined),
45
+ name: 'test-app',
46
+ serverGroups: { data: serverGroups, refresh: jasmine.createSpy('refresh') },
47
+ ...overrides,
48
+ } as any;
49
+ };
50
+
51
+ const action = (wrapper: any, label: string) =>
52
+ wrapper.find(ManagedMenuItem).filterWhere((item: any) => item.prop('children') === label);
53
+
54
+ beforeEach(() => {
55
+ AWSProviderSettings.adHocInfraWritesEnabled = true;
56
+ runtimeServices = {
57
+ serverGroupWriter: {
58
+ destroyServerGroup: () => Promise.resolve(),
59
+ disableServerGroup: () => Promise.resolve(),
60
+ enableServerGroup: () => Promise.resolve(),
61
+ },
62
+ };
63
+ });
64
+
65
+ afterEach(() => {
66
+ AWSProviderSettings.adHocInfraWritesEnabled = originalAdHocInfraWritesEnabled;
67
+ SETTINGS.resetToOriginal();
68
+ });
69
+
70
+ it('shows rollback, resize, disable, and destroy for an enabled server group', () => {
71
+ const serverGroup = buildServerGroup();
72
+ const wrapper = shallow(
73
+ <EcsServerGroupActions app={buildApp({ serverGroups: [serverGroup] })} serverGroup={serverGroup} />,
74
+ );
75
+
76
+ expect(action(wrapper, 'Rollback').length).toBe(1);
77
+ expect(action(wrapper, 'Resize').length).toBe(1);
78
+ expect(action(wrapper, 'Disable').length).toBe(1);
79
+ expect(action(wrapper, 'Enable').length).toBe(0);
80
+ expect(action(wrapper, 'Destroy').length).toBe(1);
81
+ });
82
+
83
+ it('shows resize, enable, and destroy for a disabled server group', () => {
84
+ const serverGroup = buildServerGroup({ isDisabled: true });
85
+ const wrapper = shallow(
86
+ <EcsServerGroupActions app={buildApp({ serverGroups: [serverGroup] })} serverGroup={serverGroup} />,
87
+ );
88
+
89
+ expect(action(wrapper, 'Rollback').length).toBe(0);
90
+ expect(action(wrapper, 'Resize').length).toBe(1);
91
+ expect(action(wrapper, 'Disable').length).toBe(0);
92
+ expect(action(wrapper, 'Enable').length).toBe(1);
93
+ expect(action(wrapper, 'Destroy').length).toBe(1);
94
+ });
95
+
96
+ it('locks enable while a resize task is running', () => {
97
+ const serverGroup = buildServerGroup({
98
+ isDisabled: true,
99
+ runningTasks: [{ execution: { stages: [{ type: 'resizeServerGroup' }] } }],
100
+ });
101
+ const wrapper = shallow(<EcsServerGroupActions app={buildApp()} serverGroup={serverGroup} />);
102
+
103
+ expect(action(wrapper, 'Enable').length).toBe(0);
104
+ expect(wrapper.find('li.disabled').length).toBe(1);
105
+ expect(wrapper.find(Tooltip).prop('value')).toContain('Cannot enable');
106
+ });
107
+
108
+ it('hides all actions when AWS ad-hoc infrastructure writes are disabled', () => {
109
+ const app = buildApp();
110
+ const serverGroup = buildServerGroup();
111
+ expect(shallow(<EcsServerGroupActions app={app} serverGroup={serverGroup} />).find(Dropdown).length).toBe(1);
112
+
113
+ AWSProviderSettings.adHocInfraWritesEnabled = false;
114
+
115
+ const wrapper = shallow(<EcsServerGroupActions app={app} serverGroup={serverGroup} />);
116
+
117
+ expect(wrapper.find(Dropdown).length).toBe(0);
118
+ });
119
+
120
+ it('protects every write action with the managed-resource interstitial', () => {
121
+ const app = buildApp();
122
+ const serverGroup = buildServerGroup({ isManaged: true });
123
+ const wrapper = shallow(<EcsServerGroupActions app={app} serverGroup={serverGroup} />);
124
+
125
+ expect(wrapper.find(ManagedMenuItem).length).toBe(4);
126
+ wrapper.find(ManagedMenuItem).forEach((item) => {
127
+ expect(item.prop('resource')).toBe(serverGroup);
128
+ expect(item.prop('application')).toBe(app);
129
+ });
130
+ });
131
+
132
+ it('opens the completed rollback modal with the enriched server group', () => {
133
+ const app = buildApp();
134
+ const serverGroup = buildServerGroup();
135
+ const show = spyOn(EcsRollbackServerGroupModal, 'show').and.returnValue(Promise.resolve({} as any));
136
+ const wrapper = shallow(<EcsServerGroupActions app={app} serverGroup={serverGroup} />);
137
+
138
+ action(wrapper, 'Rollback').prop('onClick')();
139
+
140
+ expect(show).toHaveBeenCalledOnceWith({ application: app, serverGroup }, runtimeServices);
141
+ });
142
+
143
+ it('opens the completed resize modal with the enriched server group', () => {
144
+ const app = buildApp();
145
+ const serverGroup = buildServerGroup();
146
+ const show = spyOn(EcsResizeServerGroupModal, 'show').and.returnValue(Promise.resolve({} as any));
147
+ const wrapper = shallow(<EcsServerGroupActions app={app} serverGroup={serverGroup} />);
148
+
149
+ action(wrapper, 'Resize').prop('onClick')();
150
+
151
+ expect(show).toHaveBeenCalledOnceWith({ application: app, serverGroup }, runtimeServices);
152
+ });
153
+
154
+ ['Disable', 'Enable', 'Destroy'].forEach((label) => {
155
+ it(`confirms ${label.toLowerCase()} with reason and account verification and preserves the exact writer contract`, () => {
156
+ const app = buildApp();
157
+ const serverGroup = buildServerGroup({ isDisabled: label === 'Enable' });
158
+ const writerMethod = `${label.toLowerCase()}ServerGroup`;
159
+ const writer = runtimeServices.serverGroupWriter as any;
160
+ const write = spyOn(writer, writerMethod).and.returnValue(Promise.resolve());
161
+ const confirm = spyOn(ConfirmationModalService, 'confirm').and.returnValue(Promise.resolve());
162
+ const stateService = {
163
+ go: jasmine.createSpy('go'),
164
+ includes: jasmine.createSpy('includes').and.returnValue(true),
165
+ };
166
+ spyOn(ServerGroupWarningMessageService, 'addDisableWarningMessage');
167
+ spyOn(ServerGroupWarningMessageService, 'addDestroyWarningMessage');
168
+ const wrapper = shallow(
169
+ <EcsServerGroupActions
170
+ app={app}
171
+ router={{} as any}
172
+ serverGroup={serverGroup}
173
+ stateParams={{}}
174
+ stateService={stateService as any}
175
+ />,
176
+ );
177
+
178
+ action(wrapper, label).prop('onClick')();
179
+
180
+ const params = confirm.calls.mostRecent().args[0] as any;
181
+ expect(params.account).toBe('test-account');
182
+ expect(params.askForReason).toBe(true);
183
+ const command = { interestingHealthProviderNames: ['Ecs'], reason: 'because it is safe' };
184
+ params.submitMethod(command);
185
+ expect(write).toHaveBeenCalledOnceWith(serverGroup, label === 'Disable' ? app.name : app, command);
186
+ if (label === 'Destroy') {
187
+ params.taskMonitorConfig.onTaskComplete();
188
+ expect(stateService.go).toHaveBeenCalledWith('^');
189
+ }
190
+ });
191
+ });
192
+
193
+ it('preselects ECS health only when the application requests platform-only health', () => {
194
+ const app = buildApp({ attributes: { platformHealthOnly: true, platformHealthOnlyShowOverride: true } });
195
+ const confirm = spyOn(ConfirmationModalService, 'confirm').and.returnValue(Promise.resolve());
196
+ spyOn(ServerGroupWarningMessageService, 'addDisableWarningMessage');
197
+ const wrapper = shallow(<EcsServerGroupActions app={app} serverGroup={buildServerGroup()} />);
198
+
199
+ action(wrapper, 'Disable').prop('onClick')();
200
+
201
+ expect((confirm.calls.mostRecent().args[0] as any).interestingHealthProviderNames).toEqual(['Ecs']);
202
+ });
203
+
204
+ it('adds entity tag links using the enriched ECS coordinates and refreshes after updates', () => {
205
+ SETTINGS.feature.entityTags = true;
206
+ const app = buildApp();
207
+ const serverGroup = buildServerGroup();
208
+ const wrapper = shallow(<EcsServerGroupActions app={app} serverGroup={serverGroup} />);
209
+ const links = wrapper.find(AddEntityTagLinks);
210
+
211
+ expect(links.prop('component')).toBe(serverGroup);
212
+ expect(links.prop('application')).toBe(app);
213
+ expect(links.prop('entityType')).toBe('serverGroup');
214
+ expect(links.prop('ownerOptions')).toEqual([
215
+ jasmine.objectContaining({ type: 'serverGroup', owner: serverGroup }),
216
+ jasmine.objectContaining({
217
+ type: 'cluster',
218
+ owner: { account: 'test-account', cloudProvider: 'ecs', name: 'test-app-main', region: 'us-east-1' },
219
+ }),
220
+ jasmine.objectContaining({
221
+ type: 'cluster',
222
+ owner: { account: 'test-account', cloudProvider: 'ecs', name: 'test-app-main', region: '*' },
223
+ }),
224
+ ]);
225
+
226
+ links.prop('onUpdate')();
227
+ expect(app.serverGroups.refresh).toHaveBeenCalled();
228
+ });
229
+
230
+ it('omits entity tag links when the feature is disabled', () => {
231
+ const app = buildApp();
232
+ const serverGroup = buildServerGroup();
233
+ SETTINGS.feature.entityTags = true;
234
+ expect(shallow(<EcsServerGroupActions app={app} serverGroup={serverGroup} />).find(AddEntityTagLinks).length).toBe(
235
+ 1,
236
+ );
237
+
238
+ SETTINGS.feature.entityTags = false;
239
+
240
+ const wrapper = shallow(<EcsServerGroupActions app={app} serverGroup={serverGroup} />);
241
+
242
+ expect(wrapper.find(AddEntityTagLinks).length).toBe(0);
243
+ });
244
+ });
@@ -0,0 +1,152 @@
1
+ import React from 'react';
2
+ import { Dropdown, Tooltip } from 'react-bootstrap';
3
+
4
+ import { AWSProviderSettings } from '@spinnaker/amazon';
5
+ import type { IOwnerOption, IRouterInjectedProps, IServerGroupActionsProps, IServerGroupJob } from '@spinnaker/core';
6
+ import {
7
+ AddEntityTagLinks,
8
+ ClusterTargetBuilder,
9
+ ConfirmationModalService,
10
+ ManagedMenuItem,
11
+ ServerGroupWarningMessageService,
12
+ SETTINGS,
13
+ useDeckRuntimeServices,
14
+ withRouter,
15
+ } from '@spinnaker/core';
16
+
17
+ import { EcsResizeServerGroupModal } from './resize/EcsResizeServerGroupModal';
18
+ import { EcsRollbackServerGroupModal } from './rollback/EcsRollbackServerGroupModal';
19
+
20
+ type ConfirmedAction = 'destroy' | 'disable' | 'enable';
21
+
22
+ const actionLabels: Record<ConfirmedAction, { present: string; progressive: string }> = {
23
+ destroy: { present: 'Destroy', progressive: 'Destroying' },
24
+ disable: { present: 'Disable', progressive: 'Disabling' },
25
+ enable: { present: 'Enable', progressive: 'Enabling' },
26
+ };
27
+
28
+ export function EcsServerGroupActionsComponent({
29
+ app,
30
+ serverGroup,
31
+ stateService,
32
+ }: IServerGroupActionsProps & IRouterInjectedProps) {
33
+ const runtimeServices = useDeckRuntimeServices();
34
+ const { serverGroupWriter } = runtimeServices;
35
+ if (!AWSProviderSettings.adHocInfraWritesEnabled) {
36
+ return null;
37
+ }
38
+
39
+ const showEntityTags = SETTINGS.feature && SETTINGS.feature.entityTags;
40
+ const entityTagTargets: IOwnerOption[] = ClusterTargetBuilder.buildClusterTargets(serverGroup);
41
+ const enableLocked =
42
+ serverGroup.isDisabled &&
43
+ (serverGroup.runningTasks || []).some((task: any) =>
44
+ (task.execution?.stages || []).some((stage: any) => stage.type === 'resizeServerGroup'),
45
+ );
46
+
47
+ const confirmAction = (action: ConfirmedAction): void => {
48
+ const label = actionLabels[action];
49
+ const stateParams = { name: serverGroup.name, accountId: serverGroup.account, region: serverGroup.region };
50
+ const confirmationModalParams = {
51
+ header: `Really ${action} ${serverGroup.name}?`,
52
+ buttonText: `${label.present} ${serverGroup.name}`,
53
+ account: serverGroup.account,
54
+ taskMonitorConfig: {
55
+ application: app,
56
+ title: `${label.progressive} ${serverGroup.name}`,
57
+ ...(action === 'destroy'
58
+ ? {
59
+ onTaskComplete: () => {
60
+ if (stateService.includes('**.serverGroup', stateParams)) {
61
+ stateService.go('^');
62
+ }
63
+ },
64
+ }
65
+ : {}),
66
+ },
67
+ platformHealthOnlyShowOverride: app.attributes.platformHealthOnlyShowOverride,
68
+ platformHealthType: 'Ecs',
69
+ interestingHealthProviderNames:
70
+ app.attributes.platformHealthOnlyShowOverride && app.attributes.platformHealthOnly ? ['Ecs'] : undefined,
71
+ submitMethod: (params: IServerGroupJob) => {
72
+ if (action === 'destroy') {
73
+ return serverGroupWriter.destroyServerGroup(serverGroup, app, params);
74
+ }
75
+ if (action === 'disable') {
76
+ return serverGroupWriter.disableServerGroup(serverGroup, app.name, params);
77
+ }
78
+ return serverGroupWriter.enableServerGroup(serverGroup, app, params);
79
+ },
80
+ askForReason: true,
81
+ };
82
+
83
+ if (action === 'destroy') {
84
+ ServerGroupWarningMessageService.addDestroyWarningMessage(app, serverGroup, confirmationModalParams);
85
+ } else if (action === 'disable') {
86
+ ServerGroupWarningMessageService.addDisableWarningMessage(app, serverGroup, confirmationModalParams);
87
+ }
88
+
89
+ ConfirmationModalService.confirm(confirmationModalParams);
90
+ };
91
+
92
+ return (
93
+ <Dropdown className="dropdown" id="server-group-actions-dropdown">
94
+ <Dropdown.Toggle className="btn btn-sm btn-primary dropdown-toggle">Server Group Actions</Dropdown.Toggle>
95
+ <Dropdown.Menu className="dropdown-menu">
96
+ {!serverGroup.isDisabled && (
97
+ <ManagedMenuItem
98
+ resource={serverGroup}
99
+ application={app}
100
+ onClick={() => EcsRollbackServerGroupModal.show({ application: app, serverGroup }, runtimeServices)}
101
+ >
102
+ Rollback
103
+ </ManagedMenuItem>
104
+ )}
105
+ <ManagedMenuItem
106
+ resource={serverGroup}
107
+ application={app}
108
+ onClick={() => EcsResizeServerGroupModal.show({ application: app, serverGroup }, runtimeServices)}
109
+ >
110
+ Resize
111
+ </ManagedMenuItem>
112
+ {!serverGroup.isDisabled && (
113
+ <ManagedMenuItem resource={serverGroup} application={app} onClick={() => confirmAction('disable')}>
114
+ Disable
115
+ </ManagedMenuItem>
116
+ )}
117
+ {serverGroup.isDisabled && !enableLocked && (
118
+ <ManagedMenuItem resource={serverGroup} application={app} onClick={() => confirmAction('enable')}>
119
+ Enable
120
+ </ManagedMenuItem>
121
+ )}
122
+ {enableLocked && (
123
+ <li className="disabled">
124
+ <Tooltip
125
+ id="ecs-enable-locked-tooltip"
126
+ value="Cannot enable this server group until resize operation completes"
127
+ placement="left"
128
+ >
129
+ <a>
130
+ <span className="small glyphicon glyphicon-lock" /> Enable
131
+ </a>
132
+ </Tooltip>
133
+ </li>
134
+ )}
135
+ <ManagedMenuItem resource={serverGroup} application={app} onClick={() => confirmAction('destroy')}>
136
+ Destroy
137
+ </ManagedMenuItem>
138
+ {showEntityTags && (
139
+ <AddEntityTagLinks
140
+ component={serverGroup}
141
+ application={app}
142
+ entityType="serverGroup"
143
+ ownerOptions={entityTagTargets}
144
+ onUpdate={() => app.serverGroups.refresh()}
145
+ />
146
+ )}
147
+ </Dropdown.Menu>
148
+ </Dropdown>
149
+ );
150
+ }
151
+
152
+ export const EcsServerGroupActions = withRouter(EcsServerGroupActionsComponent);
@@ -0,0 +1,110 @@
1
+ import { ServerGroupReader } from '@spinnaker/core';
2
+
3
+ import { ecsServerGroupDetailsGetter } from './ecsServerGroupDetailsGetter';
4
+ import { EcsServerGroupTransformer } from '../serverGroup.transformer';
5
+
6
+ function deferred<T>() {
7
+ let resolve: (value: T) => void;
8
+ let reject: (error: any) => void;
9
+ const promise = new Promise<T>((res, rej) => {
10
+ resolve = res;
11
+ reject = rej;
12
+ });
13
+ return { promise, resolve: resolve!, reject: reject! };
14
+ }
15
+
16
+ describe('ecsServerGroupDetailsGetter', () => {
17
+ const serverGroup = { accountId: 'test', name: 'fnord-main-v001', region: 'us-east-1' } as any;
18
+
19
+ it('merges the application summary over full details and normalizes the result', (done) => {
20
+ const summary = {
21
+ account: 'test',
22
+ name: 'fnord-main-v001',
23
+ region: 'us-east-1',
24
+ instanceCounts: { total: 2, up: 2 },
25
+ isDisabled: true,
26
+ };
27
+ const app = {
28
+ name: 'fnord',
29
+ ready: () => Promise.resolve(),
30
+ serverGroups: { data: [summary] },
31
+ loadBalancers: { data: [] },
32
+ } as any;
33
+ const details = {
34
+ name: 'fnord-main-v001',
35
+ isDisabled: false,
36
+ taskDefinition: { taskName: 'fnord:1' },
37
+ scalingPolicies: [],
38
+ };
39
+ spyOn(ServerGroupReader, 'getServerGroup').and.returnValue(Promise.resolve(details) as any);
40
+ const normalize = spyOn(
41
+ EcsServerGroupTransformer.prototype,
42
+ 'normalizeServerGroupDetails',
43
+ ).and.callFake((value: any) => ({ ...value, normalized: true }));
44
+ const autoClose = jasmine.createSpy('autoClose');
45
+
46
+ ecsServerGroupDetailsGetter({ app, serverGroup } as any, autoClose).subscribe({
47
+ next: (result: any) => {
48
+ expect(normalize).toHaveBeenCalledOnceWith(
49
+ jasmine.objectContaining({
50
+ account: 'test',
51
+ instanceCounts: summary.instanceCounts,
52
+ isDisabled: true,
53
+ taskDefinition: details.taskDefinition,
54
+ }),
55
+ );
56
+ expect(result.normalized).toBe(true);
57
+ expect(autoClose).not.toHaveBeenCalled();
58
+ },
59
+ error: done.fail,
60
+ complete: done,
61
+ });
62
+ });
63
+
64
+ it('uses a matching load balancer server group summary when the application summary is unavailable', (done) => {
65
+ const loadBalancerSummary = {
66
+ account: 'test',
67
+ name: 'fnord-main-v001',
68
+ region: 'us-east-1',
69
+ instanceCounts: { total: 1, up: 1 },
70
+ };
71
+ const app = {
72
+ name: 'fnord',
73
+ ready: () => Promise.resolve(),
74
+ serverGroups: { data: [] },
75
+ loadBalancers: { data: [{ account: 'test', region: 'us-east-1', serverGroups: [loadBalancerSummary] }] },
76
+ } as any;
77
+ spyOn(ServerGroupReader, 'getServerGroup').and.returnValue(
78
+ Promise.resolve({ name: 'fnord-main-v001', scalingPolicies: [] }) as any,
79
+ );
80
+
81
+ ecsServerGroupDetailsGetter({ app, serverGroup } as any, done.fail).subscribe({
82
+ next: (result: any) => expect(result.instanceCounts).toBe(loadBalancerSummary.instanceCounts),
83
+ error: done.fail,
84
+ complete: done,
85
+ });
86
+ });
87
+
88
+ it('does not emit or auto-close after the details subscription is cancelled', async () => {
89
+ const request = deferred<any>();
90
+ const app = {
91
+ name: 'fnord',
92
+ ready: () => Promise.resolve(),
93
+ serverGroups: { data: [] },
94
+ loadBalancers: { data: [] },
95
+ } as any;
96
+ spyOn(ServerGroupReader, 'getServerGroup').and.returnValue(request.promise as any);
97
+ const autoClose = jasmine.createSpy('autoClose');
98
+ const next = jasmine.createSpy('next');
99
+ const subscription = ecsServerGroupDetailsGetter({ app, serverGroup } as any, autoClose).subscribe(next);
100
+
101
+ await Promise.resolve();
102
+ subscription.unsubscribe();
103
+ request.reject(new Error('not found'));
104
+ await request.promise.catch(() => undefined);
105
+ await Promise.resolve();
106
+
107
+ expect(next).not.toHaveBeenCalled();
108
+ expect(autoClose).not.toHaveBeenCalled();
109
+ });
110
+ });
@@ -0,0 +1,73 @@
1
+ import { isEmpty } from 'lodash';
2
+ import { Observable } from 'rxjs';
3
+
4
+ import type { IServerGroup, IServerGroupDetailsProps } from '@spinnaker/core';
5
+ import { ServerGroupReader } from '@spinnaker/core';
6
+
7
+ import { EcsServerGroupTransformer } from '../serverGroup.transformer';
8
+
9
+ export function ecsServerGroupDetailsGetter(
10
+ props: IServerGroupDetailsProps,
11
+ autoClose: () => void,
12
+ ): Observable<IServerGroup> {
13
+ const { app, serverGroup: serverGroupInfo } = props;
14
+ return new Observable<IServerGroup>((observer) => {
15
+ let cancelled = false;
16
+ let summary: IServerGroup;
17
+
18
+ app
19
+ .ready()
20
+ .then(() => {
21
+ if (cancelled) {
22
+ return null;
23
+ }
24
+
25
+ summary = app.serverGroups.data.find(
26
+ (candidate: IServerGroup) =>
27
+ candidate.name === serverGroupInfo.name &&
28
+ candidate.account === serverGroupInfo.accountId &&
29
+ candidate.region === serverGroupInfo.region,
30
+ );
31
+ if (!summary) {
32
+ const loadBalancer = app.loadBalancers.data.find(
33
+ (candidate: any) =>
34
+ candidate.account === serverGroupInfo.accountId && candidate.region === serverGroupInfo.region,
35
+ );
36
+ summary = loadBalancer?.serverGroups.find(
37
+ (candidate: IServerGroup) => candidate.name === serverGroupInfo.name,
38
+ );
39
+ }
40
+
41
+ return ServerGroupReader.getServerGroup(
42
+ app.name,
43
+ serverGroupInfo.accountId,
44
+ serverGroupInfo.region,
45
+ serverGroupInfo.name,
46
+ );
47
+ })
48
+ .then((serverGroup: IServerGroup) => {
49
+ if (cancelled || serverGroup === null) {
50
+ return;
51
+ }
52
+ if (isEmpty(serverGroup)) {
53
+ autoClose();
54
+ observer.complete();
55
+ return;
56
+ }
57
+
58
+ const merged = { ...serverGroup, ...summary, account: serverGroupInfo.accountId } as IServerGroup;
59
+ observer.next(new EcsServerGroupTransformer().normalizeServerGroupDetails(merged as any) as IServerGroup);
60
+ observer.complete();
61
+ })
62
+ .catch(() => {
63
+ if (!cancelled) {
64
+ autoClose();
65
+ observer.complete();
66
+ }
67
+ });
68
+
69
+ return () => {
70
+ cancelled = true;
71
+ };
72
+ });
73
+ }