@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,285 @@
1
+ import React from 'react';
2
+ import { Modal } from 'react-bootstrap';
3
+
4
+ import type {
5
+ Application,
6
+ DeckRuntimeServices,
7
+ IModalComponentProps,
8
+ IServerGroup,
9
+ IServerGroupJob,
10
+ } from '@spinnaker/core';
11
+ import {
12
+ DeckRuntimeContext,
13
+ FormikFormField,
14
+ ModalClose,
15
+ noop,
16
+ PlatformHealthOverride,
17
+ ReactModal,
18
+ ReactSelectInput,
19
+ SpinFormik,
20
+ TaskMonitor,
21
+ TaskMonitorWrapper,
22
+ TaskReason,
23
+ UserVerification,
24
+ ValidationMessage,
25
+ } from '@spinnaker/core';
26
+
27
+ export interface IEcsRollbackServerGroupModalProps extends IModalComponentProps {
28
+ application: Application;
29
+ serverGroup: IServerGroup;
30
+ }
31
+
32
+ export interface IEcsRollbackServerGroupValues {
33
+ interestingHealthProviderNames?: string[];
34
+ reason?: string;
35
+ restoreServerGroupName?: string;
36
+ targetHealthyRollbackPercentage: number;
37
+ }
38
+
39
+ export interface IEcsRollbackServerGroupJob extends IServerGroupJob {
40
+ interestingHealthProviderNames?: string[];
41
+ platformHealthOnlyShowOverride?: boolean;
42
+ reason?: string;
43
+ rollbackContext: {
44
+ restoreServerGroupName: string;
45
+ rollbackServerGroupName: string;
46
+ targetHealthyRollbackPercentage: number;
47
+ };
48
+ rollbackType: 'EXPLICIT';
49
+ }
50
+
51
+ export interface IEcsRollbackServerGroupErrors {
52
+ restoreServerGroupName?: string;
53
+ targetHealthyRollbackPercentage?: string;
54
+ }
55
+
56
+ interface IEcsRollbackServerGroupModalState {
57
+ initialValues: IEcsRollbackServerGroupValues;
58
+ taskMonitor: TaskMonitor;
59
+ verified: boolean;
60
+ }
61
+
62
+ function coordinate(serverGroup: any, key: 'account' | 'cluster'): string | undefined {
63
+ if (key === 'account') {
64
+ return serverGroup.account || serverGroup.accountId;
65
+ }
66
+ return serverGroup.cluster || serverGroup.moniker?.cluster;
67
+ }
68
+
69
+ export function getEcsRollbackTargets(application: Application, serverGroup: IServerGroup): IServerGroup[] {
70
+ const app = application as any;
71
+ const appName = (serverGroup as any).moniker?.app || app.name;
72
+ const candidates: IServerGroup[] = app.serverGroups?.data || app.getDataSource?.('serverGroups')?.data || [];
73
+
74
+ return candidates
75
+ .filter((candidate: any) => {
76
+ const candidateApp = candidate.moniker?.app || candidate.application || appName;
77
+ return (
78
+ candidate.isDisabled &&
79
+ candidateApp === appName &&
80
+ coordinate(candidate, 'cluster') === coordinate(serverGroup, 'cluster') &&
81
+ coordinate(candidate, 'account') === coordinate(serverGroup, 'account') &&
82
+ candidate.region === serverGroup.region
83
+ );
84
+ })
85
+ .sort((a, b) => b.name.localeCompare(a.name));
86
+ }
87
+
88
+ export function validateEcsRollbackValues(
89
+ values: IEcsRollbackServerGroupValues,
90
+ eligibleTargetNames?: string[],
91
+ ): IEcsRollbackServerGroupErrors {
92
+ const errors: IEcsRollbackServerGroupErrors = {};
93
+ if (!values.restoreServerGroupName) {
94
+ errors.restoreServerGroupName = 'Select a server group to restore';
95
+ } else if (eligibleTargetNames && !eligibleTargetNames.includes(values.restoreServerGroupName)) {
96
+ errors.restoreServerGroupName = 'Select an eligible server group to restore';
97
+ }
98
+ if (
99
+ !Number.isFinite(values.targetHealthyRollbackPercentage) ||
100
+ values.targetHealthyRollbackPercentage < 0 ||
101
+ values.targetHealthyRollbackPercentage > 100
102
+ ) {
103
+ errors.targetHealthyRollbackPercentage = 'Healthy threshold must be between 0 and 100';
104
+ }
105
+ return errors;
106
+ }
107
+
108
+ export class EcsRollbackServerGroupModal extends React.Component<
109
+ IEcsRollbackServerGroupModalProps,
110
+ IEcsRollbackServerGroupModalState
111
+ > {
112
+ public static contextType = DeckRuntimeContext;
113
+ public declare context: React.ContextType<typeof DeckRuntimeContext>;
114
+
115
+ public static defaultProps: Partial<IEcsRollbackServerGroupModalProps> = {
116
+ closeModal: noop,
117
+ dismissModal: noop,
118
+ };
119
+
120
+ public static show(
121
+ props: IEcsRollbackServerGroupModalProps,
122
+ runtimeServices: DeckRuntimeServices,
123
+ ): Promise<IEcsRollbackServerGroupJob> {
124
+ return ReactModal.show(EcsRollbackServerGroupModal, props, undefined, runtimeServices);
125
+ }
126
+
127
+ public constructor(props: IEcsRollbackServerGroupModalProps) {
128
+ super(props);
129
+ const { application, serverGroup } = props;
130
+ const attributes = application.attributes || {};
131
+ this.state = {
132
+ initialValues: {
133
+ interestingHealthProviderNames:
134
+ attributes.platformHealthOnlyShowOverride && attributes.platformHealthOnly ? ['Ecs'] : undefined,
135
+ targetHealthyRollbackPercentage: 100,
136
+ },
137
+ taskMonitor: new TaskMonitor({
138
+ application,
139
+ title: `Rollback ${serverGroup.name}`,
140
+ onDismiss: () => this.props.dismissModal(),
141
+ onTaskComplete: () => application.serverGroups.refresh(),
142
+ }),
143
+ verified: false,
144
+ };
145
+ }
146
+
147
+ private close = (): void => this.props.dismissModal();
148
+
149
+ private submit = (values: IEcsRollbackServerGroupValues): void => {
150
+ const eligibleTargetNames = getEcsRollbackTargets(this.props.application, this.props.serverGroup).map(
151
+ ({ name }) => name,
152
+ );
153
+ if (!this.state.verified || Object.keys(validateEcsRollbackValues(values, eligibleTargetNames)).length > 0) {
154
+ return;
155
+ }
156
+
157
+ const { application, serverGroup } = this.props;
158
+ const command: IEcsRollbackServerGroupJob = {
159
+ interestingHealthProviderNames: values.interestingHealthProviderNames,
160
+ platformHealthOnlyShowOverride: application.attributes?.platformHealthOnlyShowOverride,
161
+ reason: values.reason,
162
+ rollbackContext: {
163
+ restoreServerGroupName: values.restoreServerGroupName,
164
+ rollbackServerGroupName: serverGroup.name,
165
+ targetHealthyRollbackPercentage: values.targetHealthyRollbackPercentage,
166
+ },
167
+ rollbackType: 'EXPLICIT',
168
+ };
169
+
170
+ this.state.taskMonitor.submit(() =>
171
+ this.context.services.serverGroupWriter.rollbackServerGroup(serverGroup, application, command),
172
+ );
173
+ };
174
+
175
+ public render(): JSX.Element {
176
+ const { application, serverGroup } = this.props;
177
+ const targets = getEcsRollbackTargets(application, serverGroup);
178
+
179
+ return (
180
+ <>
181
+ <TaskMonitorWrapper monitor={this.state.taskMonitor} />
182
+ <SpinFormik<IEcsRollbackServerGroupValues>
183
+ initialValues={this.state.initialValues}
184
+ onSubmit={this.submit}
185
+ validate={(values) =>
186
+ validateEcsRollbackValues(
187
+ values,
188
+ targets.map(({ name }) => name),
189
+ )
190
+ }
191
+ render={(formik) => (
192
+ <>
193
+ <ModalClose dismiss={this.close} />
194
+ <Modal.Header>
195
+ <Modal.Title>Rollback {serverGroup.name}</Modal.Title>
196
+ </Modal.Header>
197
+ <Modal.Body>
198
+ <form className="form-horizontal">
199
+ <div className="form-group">
200
+ <div className="col-sm-3 sm-label-right">Restore to</div>
201
+ <div className="col-sm-7">
202
+ <FormikFormField
203
+ name="restoreServerGroupName"
204
+ fastField={true}
205
+ input={(props) => (
206
+ <ReactSelectInput
207
+ {...props}
208
+ clearable={false}
209
+ stringOptions={targets.map(({ name }) => name)}
210
+ />
211
+ )}
212
+ required={true}
213
+ />
214
+ </div>
215
+ </div>
216
+
217
+ {application.attributes?.platformHealthOnlyShowOverride && (
218
+ <div className="form-group">
219
+ <div className="col-sm-10 col-sm-offset-1">
220
+ <PlatformHealthOverride
221
+ interestingHealthProviderNames={formik.values.interestingHealthProviderNames}
222
+ onChange={(names) => formik.setFieldValue('interestingHealthProviderNames', names)}
223
+ platformHealthType="Ecs"
224
+ showHelpDetails={true}
225
+ />
226
+ </div>
227
+ </div>
228
+ )}
229
+
230
+ <TaskReason
231
+ reason={formik.values.reason}
232
+ onChange={(reason) => formik.setFieldValue('reason', reason)}
233
+ />
234
+
235
+ <div className="form-group">
236
+ <div className="col-sm-3 sm-label-right">Healthy threshold</div>
237
+ <div className="col-sm-3">
238
+ <input
239
+ className="form-control input-sm"
240
+ max={100}
241
+ min={0}
242
+ name="targetHealthyRollbackPercentage"
243
+ onChange={(event) =>
244
+ formik.setFieldValue('targetHealthyRollbackPercentage', Number(event.target.value))
245
+ }
246
+ type="number"
247
+ value={formik.values.targetHealthyRollbackPercentage}
248
+ />
249
+ </div>
250
+ <div className="col-sm-2 sm-control-field">percent</div>
251
+ </div>
252
+ {formik.errors.targetHealthyRollbackPercentage && (
253
+ <div className="col-sm-7 col-sm-offset-3">
254
+ <ValidationMessage
255
+ message={formik.errors.targetHealthyRollbackPercentage as string}
256
+ type="error"
257
+ />
258
+ </div>
259
+ )}
260
+ </form>
261
+ </Modal.Body>
262
+ <Modal.Footer>
263
+ <UserVerification
264
+ account={serverGroup.account}
265
+ onValidChange={(verified) => this.setState({ verified })}
266
+ />
267
+ <button className="btn btn-default" onClick={this.close} type="button">
268
+ Cancel
269
+ </button>
270
+ <button
271
+ className="btn btn-primary"
272
+ disabled={!this.state.verified || !formik.isValid}
273
+ onClick={() => this.submit(formik.values)}
274
+ type="submit"
275
+ >
276
+ Submit
277
+ </button>
278
+ </Modal.Footer>
279
+ </>
280
+ )}
281
+ />
282
+ </>
283
+ );
284
+ }
285
+ }
@@ -0,0 +1 @@
1
+ export * from './EcsRollbackServerGroupModal';
@@ -0,0 +1,130 @@
1
+ import React from 'react';
2
+ import { shallow } from 'enzyme';
3
+
4
+ import { CollapsibleSection, HealthCounts } from '@spinnaker/core';
5
+
6
+ import {
7
+ EcsBuildInfoSection,
8
+ EcsCapacitySection,
9
+ EcsEnvironmentVariablesSection,
10
+ EcsFirewallsSection,
11
+ EcsHealthSection,
12
+ EcsScalingPoliciesSection,
13
+ EcsTaskDefinitionSection,
14
+ } from './EcsServerGroupDetailsSections';
15
+ import { EcsServerGroupInformationSection } from './EcsServerGroupInformationSection';
16
+ import { EventsLink } from '../../events/EventsLink';
17
+ import { EcsServerGroupEventsSection } from './EcsServerGroupEventsSection';
18
+
19
+ describe('ECS server group details sections', () => {
20
+ const serverGroup = {
21
+ account: 'test',
22
+ region: 'us-east-1',
23
+ createdTime: 1710000000000,
24
+ ecsCluster: 'production',
25
+ vpcId: 'vpc-123',
26
+ taskDefinition: {
27
+ taskName: 'fnord:42',
28
+ containerImage: 'example/fnord:1.2.3',
29
+ iamRole: 'fnord-task-role',
30
+ containerPort: 8080,
31
+ cpuUnits: 512,
32
+ memoryReservation: 1024,
33
+ memoryLimit: 2048,
34
+ environmentVariables: [
35
+ { name: 'ENVIRONMENT', value: 'production' },
36
+ { name: 'REGION', value: 'eu' },
37
+ ],
38
+ },
39
+ instanceCounts: { total: 2, up: 2, down: 0, unknown: 0, outOfService: 0, starting: 0 },
40
+ securityGroups: ['sg-web', 'sg-admin'],
41
+ instances: [{ id: 'one' }, { id: 'two' }],
42
+ capacity: { desired: 3, min: 1, max: 5 },
43
+ metricAlarms: ['cpu-high', 'memory-high'],
44
+ buildInfo: {
45
+ jenkins: { host: 'https://jenkins.example/', name: 'fnord', number: 123 },
46
+ package_name: 'fnord-package',
47
+ commit: '1234567890abcdef',
48
+ version: '1.2.3',
49
+ },
50
+ } as any;
51
+ const props = { app: {} as any, serverGroup };
52
+
53
+ const sectionContent = (wrapper: any) => shallow(<div>{wrapper.find(CollapsibleSection).prop('children')}</div>);
54
+
55
+ it('renders general ECS location information', () => {
56
+ const wrapper = shallow(<EcsServerGroupInformationSection {...props} />);
57
+
58
+ expect(sectionContent(wrapper).text()).toContain('production');
59
+ expect(sectionContent(wrapper).text()).toContain('vpc-123');
60
+ });
61
+
62
+ it('renders task definition and container resources', () => {
63
+ const wrapper = shallow(<EcsTaskDefinitionSection {...props} />);
64
+ const text = sectionContent(wrapper).text();
65
+
66
+ expect(text).toContain('fnord:42');
67
+ expect(text).toContain('example/fnord:1.2.3');
68
+ expect(text).toContain('fnord-task-role');
69
+ expect(text).toContain('8080');
70
+ expect(text).toContain('512');
71
+ expect(text).toContain('1024 MB');
72
+ expect(text).toContain('2048 MB');
73
+ });
74
+
75
+ it('renders environment variables and its empty state', () => {
76
+ const populated = shallow(<EcsEnvironmentVariablesSection {...props} />);
77
+ const empty = shallow(
78
+ <EcsEnvironmentVariablesSection
79
+ {...props}
80
+ serverGroup={{ ...serverGroup, taskDefinition: { ...serverGroup.taskDefinition, environmentVariables: [] } }}
81
+ />,
82
+ );
83
+
84
+ expect(sectionContent(populated).text()).toContain('ENVIRONMENT');
85
+ expect(sectionContent(populated).text()).toContain('production');
86
+ expect(sectionContent(empty).text()).toContain('This server group has no environment variables');
87
+ });
88
+
89
+ it('renders health, firewalls, capacity, and scaling alarms', () => {
90
+ const health = shallow(<EcsHealthSection {...props} />);
91
+ const firewalls = shallow(<EcsFirewallsSection {...props} />);
92
+ const capacity = shallow(<EcsCapacitySection {...props} />);
93
+ const alarms = shallow(<EcsScalingPoliciesSection {...props} />);
94
+
95
+ expect(health.find(HealthCounts).prop('container')).toBe(serverGroup.instanceCounts);
96
+ expect(sectionContent(firewalls).text()).toContain('sg-web');
97
+ expect(sectionContent(firewalls).text()).toContain('sg-admin');
98
+ expect(sectionContent(capacity).text()).toContain('Current2');
99
+ expect(sectionContent(capacity).text()).toContain('Desired3');
100
+ expect(sectionContent(capacity).text()).toContain('Min1');
101
+ expect(sectionContent(capacity).text()).toContain('Max5');
102
+ expect(sectionContent(alarms).text()).toContain('cpu-high');
103
+ expect(sectionContent(alarms).text()).toContain('memory-high');
104
+ });
105
+
106
+ it('renders the scaling alarms empty state', () => {
107
+ const wrapper = shallow(
108
+ <EcsScalingPoliciesSection {...props} serverGroup={{ ...serverGroup, metricAlarms: [] }} />,
109
+ );
110
+
111
+ expect(sectionContent(wrapper).text()).toContain('There are no scaling policies assigned.');
112
+ });
113
+
114
+ it('renders build metadata and a Jenkins link', () => {
115
+ const wrapper = shallow(<EcsBuildInfoSection {...props} />);
116
+ const content = sectionContent(wrapper);
117
+ const text = content.text();
118
+
119
+ expect(text).toContain('fnord-package');
120
+ expect(text).toContain('12345678');
121
+ expect(text).toContain('1.2.3');
122
+ expect(content.find('a').prop('href')).toBe('https://jenkins.example/job/fnord/123');
123
+ });
124
+
125
+ it('renders the ECS events link', () => {
126
+ const wrapper = shallow(<EcsServerGroupEventsSection {...props} />);
127
+
128
+ expect(wrapper.find(EventsLink).prop('serverGroup')).toBe(serverGroup);
129
+ });
130
+ });
@@ -0,0 +1,172 @@
1
+ import React from 'react';
2
+
3
+ import type { IServerGroupDetailsSectionProps } from '@spinnaker/core';
4
+ import { CollapsibleSection, FirewallLabels, HealthCounts } from '@spinnaker/core';
5
+
6
+ export function EcsTaskDefinitionSection({ serverGroup }: IServerGroupDetailsSectionProps) {
7
+ const taskDefinition = (serverGroup as any).taskDefinition || {};
8
+ return (
9
+ <CollapsibleSection heading="Task Definition" defaultExpanded={true}>
10
+ <dl className="dl-horizontal dl-narrow">
11
+ <dt>Task Name</dt>
12
+ <dd>{taskDefinition.taskName}</dd>
13
+ <dt>Container URL</dt>
14
+ <dd>{taskDefinition.containerImage}</dd>
15
+ <dt>Container IAM Profile</dt>
16
+ <dd>{taskDefinition.iamRole}</dd>
17
+ {taskDefinition.containerPort != null && (
18
+ <>
19
+ <dt>Container Port</dt>
20
+ <dd>{taskDefinition.containerPort}</dd>
21
+ </>
22
+ )}
23
+ <dt>Container CPU Units</dt>
24
+ <dd>{taskDefinition.cpuUnits}</dd>
25
+ {taskDefinition.memoryReservation != null && (
26
+ <>
27
+ <dt>Container Reserved Memory</dt>
28
+ <dd>{taskDefinition.memoryReservation} MB</dd>
29
+ </>
30
+ )}
31
+ {taskDefinition.memoryLimit != null && (
32
+ <>
33
+ <dt>Container Memory Limit</dt>
34
+ <dd>{taskDefinition.memoryLimit} MB</dd>
35
+ </>
36
+ )}
37
+ </dl>
38
+ </CollapsibleSection>
39
+ );
40
+ }
41
+
42
+ export function EcsEnvironmentVariablesSection({ serverGroup }: IServerGroupDetailsSectionProps) {
43
+ const environmentVariables = (serverGroup as any).taskDefinition?.environmentVariables || [];
44
+ return (
45
+ <CollapsibleSection heading="Environment Variables">
46
+ {environmentVariables.length === 0 ? (
47
+ <div>This server group has no environment variables</div>
48
+ ) : (
49
+ <dl className="dl-horizontal dl-narrow">
50
+ {environmentVariables.map((variable: any) => (
51
+ <React.Fragment key={variable.name}>
52
+ <dt>{variable.name}</dt>
53
+ <dd>{variable.value}</dd>
54
+ </React.Fragment>
55
+ ))}
56
+ </dl>
57
+ )}
58
+ </CollapsibleSection>
59
+ );
60
+ }
61
+
62
+ export function EcsHealthSection({ serverGroup }: IServerGroupDetailsSectionProps) {
63
+ if (!serverGroup.instanceCounts || serverGroup.instanceCounts.total <= 0) {
64
+ return null;
65
+ }
66
+ return (
67
+ <CollapsibleSection heading="Health" defaultExpanded={true}>
68
+ <dl className="dl-horizontal dl-narrow">
69
+ <dt>Instances</dt>
70
+ <dd>
71
+ <HealthCounts container={serverGroup.instanceCounts} className="pull-left" />
72
+ </dd>
73
+ </dl>
74
+ </CollapsibleSection>
75
+ );
76
+ }
77
+
78
+ export function EcsFirewallsSection({ serverGroup }: IServerGroupDetailsSectionProps) {
79
+ const securityGroups = (serverGroup as any).securityGroups || [];
80
+ return (
81
+ <CollapsibleSection heading={FirewallLabels.get('Firewalls')}>
82
+ <ul>
83
+ {securityGroups.map((securityGroup: string) => (
84
+ <li key={securityGroup}>{securityGroup}</li>
85
+ ))}
86
+ </ul>
87
+ </CollapsibleSection>
88
+ );
89
+ }
90
+
91
+ export function EcsCapacitySection({ serverGroup }: IServerGroupDetailsSectionProps) {
92
+ const capacity = (serverGroup as any).capacity || {};
93
+ return (
94
+ <CollapsibleSection heading="Capacity">
95
+ <dl className="dl-horizontal dl-narrow">
96
+ <dt>Current</dt>
97
+ <dd>{serverGroup.instances?.length || 0}</dd>
98
+ <dt>Desired</dt>
99
+ <dd>{capacity.desired}</dd>
100
+ <dt>Min</dt>
101
+ <dd>{capacity.min}</dd>
102
+ <dt>Max</dt>
103
+ <dd>{capacity.max}</dd>
104
+ </dl>
105
+ </CollapsibleSection>
106
+ );
107
+ }
108
+
109
+ export function EcsScalingPoliciesSection({ serverGroup }: IServerGroupDetailsSectionProps) {
110
+ const metricAlarms = (serverGroup as any).metricAlarms || [];
111
+ return (
112
+ <CollapsibleSection heading="Scaling Policies">
113
+ {metricAlarms.length ? (
114
+ <ul>
115
+ {metricAlarms.map((alarm: string) => (
116
+ <li key={alarm}>{alarm}</li>
117
+ ))}
118
+ </ul>
119
+ ) : (
120
+ <i>There are no scaling policies assigned.</i>
121
+ )}
122
+ </CollapsibleSection>
123
+ );
124
+ }
125
+
126
+ function getBuildLink(buildInfo: any): string {
127
+ if (buildInfo.buildInfoUrl) {
128
+ return buildInfo.buildInfoUrl;
129
+ }
130
+ if (buildInfo.jenkins) {
131
+ return `${buildInfo.jenkins.host.replace(/\/?$/, '/')}job/${buildInfo.jenkins.name}/${buildInfo.jenkins.number}`;
132
+ }
133
+ return null;
134
+ }
135
+
136
+ export function EcsBuildInfoSection({ serverGroup }: IServerGroupDetailsSectionProps) {
137
+ const buildInfo = (serverGroup as any).buildInfo;
138
+ if (!buildInfo) {
139
+ return null;
140
+ }
141
+ const buildLink = getBuildLink(buildInfo);
142
+ return (
143
+ <CollapsibleSection heading="Build Data">
144
+ <dl className="dl-horizontal dl-narrow">
145
+ {buildInfo.jenkins && (
146
+ <>
147
+ <dt>Job</dt>
148
+ <dd>{buildInfo.jenkins.name}</dd>
149
+ <dt>Build</dt>
150
+ <dd>{buildInfo.jenkins.number}</dd>
151
+ </>
152
+ )}
153
+ <dt>Package</dt>
154
+ <dd>{buildInfo.package_name}</dd>
155
+ <dt>Commit</dt>
156
+ <dd>{buildInfo.commit?.substring(0, 8)}</dd>
157
+ <dt>Version</dt>
158
+ <dd>{buildInfo.version}</dd>
159
+ {buildLink && (
160
+ <>
161
+ <dt>Build Link</dt>
162
+ <dd>
163
+ <a target="_blank" rel="noopener noreferrer" href={buildLink}>
164
+ {buildLink}
165
+ </a>
166
+ </dd>
167
+ </>
168
+ )}
169
+ </dl>
170
+ </CollapsibleSection>
171
+ );
172
+ }
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+
3
+ import type { IServerGroupDetailsSectionProps } from '@spinnaker/core';
4
+ import { CollapsibleSection } from '@spinnaker/core';
5
+
6
+ import { EventsLink } from '../../events/EventsLink';
7
+
8
+ export function EcsServerGroupEventsSection({ serverGroup }: IServerGroupDetailsSectionProps) {
9
+ return (
10
+ <CollapsibleSection heading="ECS Events">
11
+ <EventsLink serverGroup={serverGroup} />
12
+ </CollapsibleSection>
13
+ );
14
+ }
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+
3
+ import type { IServerGroupDetailsSectionProps } from '@spinnaker/core';
4
+ import { AccountTag, CollapsibleSection, timestamp } from '@spinnaker/core';
5
+
6
+ export function EcsServerGroupInformationSection({ serverGroup }: IServerGroupDetailsSectionProps) {
7
+ return (
8
+ <CollapsibleSection heading="Server Group Information" defaultExpanded={true}>
9
+ <dl className="dl-horizontal dl-narrow">
10
+ <dt>Account</dt>
11
+ <dd>
12
+ <AccountTag account={serverGroup.account} />
13
+ </dd>
14
+ <dt>Region</dt>
15
+ <dd>{serverGroup.region}</dd>
16
+ <dt>Created</dt>
17
+ <dd>{serverGroup.createdTime ? timestamp(serverGroup.createdTime) : 'Unknown'}</dd>
18
+ <dt>ECS Cluster</dt>
19
+ <dd>{(serverGroup as any).ecsCluster}</dd>
20
+ <dt>VPC ID</dt>
21
+ <dd>{(serverGroup as any).vpcId}</dd>
22
+ </dl>
23
+ </CollapsibleSection>
24
+ );
25
+ }