@spinnaker/ecs 2026.2.2 → 2026.3.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.
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
@@ -1,73 +1,19 @@
1
- 'use strict';
2
-
3
- import { module } from 'angular';
4
-
5
- import { AccountService, Registry } from '@spinnaker/core';
6
-
7
- export const ECS_PIPELINE_STAGES_SHRINKCLUSTER_ECSSHRINKCLUSTERSTAGE =
8
- 'spinnaker.ecs.pipeline.stage.ecs.shrinkClusterStage';
9
- export const name = ECS_PIPELINE_STAGES_SHRINKCLUSTER_ECSSHRINKCLUSTERSTAGE; // for backwards compatibility
10
- module(ECS_PIPELINE_STAGES_SHRINKCLUSTER_ECSSHRINKCLUSTERSTAGE, [])
11
- .config(function () {
12
- Registry.pipeline.registerStage({
13
- provides: 'shrinkCluster',
14
- cloudProvider: 'ecs',
15
- templateUrl: require('./shrinkClusterStage.html'),
16
- accountExtractor: (stage) => [stage.context.credentials],
17
- configAccountExtractor: (stage) => [stage.credentials],
18
- validators: [
19
- { type: 'requiredField', fieldName: 'cluster' },
20
- { type: 'requiredField', fieldName: 'shrinkToSize', fieldLabel: 'shrink to [X] Server Groups' },
21
- { type: 'requiredField', fieldName: 'regions' },
22
- { type: 'requiredField', fieldName: 'credentials', fieldLabel: 'account' },
23
- ],
24
- });
25
- })
26
- .controller('ecsShrinkClusterStageCtrl', [
27
- '$scope',
28
- function ($scope) {
29
- const ctrl = this;
30
-
31
- const stage = $scope.stage;
32
-
33
- $scope.state = {
34
- accounts: false,
35
- regionsLoaded: false,
36
- };
37
-
38
- AccountService.listAccounts('ecs').then(function (accounts) {
39
- $scope.accounts = accounts;
40
- $scope.state.accounts = true;
41
- });
42
-
43
- stage.regions = stage.regions || [];
44
- stage.cloudProvider = 'ecs';
45
-
46
- if (!stage.credentials && $scope.application.defaultCredentials.ecs) {
47
- stage.credentials = $scope.application.defaultCredentials.ecs;
48
- }
49
- if (!stage.regions.length && $scope.application.defaultRegions.ecs) {
50
- stage.regions.push($scope.application.defaultRegions.ecs);
51
- }
52
-
53
- if (stage.shrinkToSize === undefined) {
54
- stage.shrinkToSize = 1;
55
- }
56
-
57
- if (stage.allowDeleteActive === undefined) {
58
- stage.allowDeleteActive = false;
59
- }
60
-
61
- ctrl.pluralize = function (str, val) {
62
- if (val === 1) {
63
- return str;
64
- }
65
- return str + 's';
66
- };
67
-
68
- if (stage.retainLargerOverNewer === undefined) {
69
- stage.retainLargerOverNewer = 'false';
70
- }
71
- stage.retainLargerOverNewer = stage.retainLargerOverNewer.toString();
72
- },
73
- ]);
1
+ import { Registry } from '@spinnaker/core';
2
+
3
+ import { EcsShrinkClusterStageConfig } from '../common/EcsStageConfigs';
4
+
5
+ export function registerEcsShrinkClusterStage() {
6
+ Registry.pipeline.registerStage({
7
+ provides: 'shrinkCluster',
8
+ cloudProvider: 'ecs',
9
+ component: EcsShrinkClusterStageConfig,
10
+ accountExtractor: (stage) => [stage.context.credentials],
11
+ configAccountExtractor: (stage) => [stage.credentials],
12
+ validators: [
13
+ { type: 'requiredField', fieldName: 'cluster' },
14
+ { type: 'requiredField', fieldName: 'shrinkToSize', fieldLabel: 'shrink to [X] Server Groups' },
15
+ { type: 'requiredField', fieldName: 'regions' },
16
+ { type: 'requiredField', fieldName: 'credentials', fieldLabel: 'account' },
17
+ ],
18
+ });
19
+ }
@@ -1,5 +1,3 @@
1
- import { module } from 'angular';
2
-
3
1
  import type { IPlacementStrategy } from './IPlacementStrategy';
4
2
 
5
3
  export class PlacementStrategyService {
@@ -57,7 +55,3 @@ export class PlacementStrategyService {
57
55
  return [{ type: 'spread', field: 'instanceId' }];
58
56
  }
59
57
  }
60
-
61
- export const PLACEMENT_STRATEGY_SERVICE = 'spinnaker.ecs.placementStrategyService.service';
62
-
63
- module(PLACEMENT_STRATEGY_SERVICE, []).service('placementStrategyService', PlacementStrategyService);
@@ -1,14 +1,8 @@
1
- import { module } from 'angular';
2
-
3
1
  import { REST } from '@spinnaker/core';
4
2
  import type { ISecretDescriptor } from './ISecret';
5
3
 
6
4
  export class SecretReader {
7
- public listSecrets(): PromiseLike<ISecretDescriptor[]> {
5
+ public listSecrets(): Promise<ISecretDescriptor[]> {
8
6
  return REST('/ecs/secrets').get();
9
7
  }
10
8
  }
11
-
12
- export const ECS_SECRET_READ_SERVICE = 'spinnaker.ecs.secret.read.service';
13
-
14
- module(ECS_SECRET_READ_SERVICE, []).service('secretReader', SecretReader);
@@ -0,0 +1,245 @@
1
+ import { shallow } from 'enzyme';
2
+ import React from 'react';
3
+
4
+ import { IPRangeRules } from '@spinnaker/amazon';
5
+ import { CollapsibleSection } from '@spinnaker/core';
6
+
7
+ import { EcsSecurityGroupDetailsComponent as EcsSecurityGroupDetails } from './EcsSecurityGroupDetails';
8
+
9
+ const tick = () => new Promise((resolve) => setTimeout(resolve));
10
+
11
+ describe('EcsSecurityGroupDetails', () => {
12
+ const resolvedSecurityGroup = {
13
+ accountId: 'test-account',
14
+ name: 'web-sg',
15
+ provider: 'ecs',
16
+ region: 'eu-west-1',
17
+ vpcId: 'vpc-1',
18
+ };
19
+
20
+ function app(isStandalone = false) {
21
+ return {
22
+ isStandalone,
23
+ getDataSource: () => ({
24
+ ready: () => Promise.resolve(),
25
+ onRefresh: () => jasmine.createSpy('unsubscribe'),
26
+ }),
27
+ } as any;
28
+ }
29
+
30
+ it('replaces missing details through the injected state service', () => {
31
+ const stateService = { go: jasmine.createSpy('go') };
32
+ const component = new EcsSecurityGroupDetails({
33
+ app: app(),
34
+ resolvedSecurityGroup,
35
+ router: {},
36
+ stateParams: {},
37
+ stateService,
38
+ } as any);
39
+
40
+ (component as any).showNotFound();
41
+
42
+ expect(stateService.go).toHaveBeenCalledWith('^', { allowModalToStayOpen: true }, { location: 'replace' });
43
+ });
44
+
45
+ function securityGroup(name = 'web-sg') {
46
+ return {
47
+ accountId: 'test-account',
48
+ accountName: 'test-account',
49
+ description: 'Web ingress',
50
+ id: `id-${name}`,
51
+ ipRangeRules: [
52
+ {
53
+ protocol: 'tcp',
54
+ range: { ip: '10.0.0.0', cidr: '/24' },
55
+ portRanges: [{ startPort: 80, endPort: 80 }],
56
+ },
57
+ ],
58
+ name,
59
+ region: 'eu-west-1',
60
+ securityGroupRules: [
61
+ {
62
+ protocol: 'tcp',
63
+ portRanges: [{ startPort: 443, endPort: 443 }],
64
+ securityGroup: {
65
+ accountName: 'shared-account',
66
+ id: 'sg-source',
67
+ name: 'source-sg',
68
+ region: 'eu-west-1',
69
+ vpcId: 'vpc-1',
70
+ },
71
+ },
72
+ ],
73
+ vpcId: 'vpc-1',
74
+ };
75
+ }
76
+
77
+ it('loads full details and renders descriptions, VPC names, and Amazon rule models with ECS links', async () => {
78
+ const details = securityGroup();
79
+ const securityGroupReader = {
80
+ getApplicationSecurityGroup: jasmine.createSpy('getApplicationSecurityGroup').and.returnValue({}),
81
+ getSecurityGroupDetails: jasmine.createSpy('getSecurityGroupDetails').and.returnValue(Promise.resolve(details)),
82
+ };
83
+ const vpcReader = {
84
+ getVpcName: jasmine.createSpy('getVpcName').and.returnValue(Promise.resolve('Production VPC')),
85
+ };
86
+ const wrapper = shallow(
87
+ <EcsSecurityGroupDetails
88
+ app={app()}
89
+ resolvedSecurityGroup={resolvedSecurityGroup}
90
+ securityGroupReader={securityGroupReader as any}
91
+ vpcReader={vpcReader}
92
+ />,
93
+ );
94
+
95
+ await tick();
96
+ wrapper.update();
97
+
98
+ expect(securityGroupReader.getSecurityGroupDetails).toHaveBeenCalledWith(
99
+ jasmine.anything(),
100
+ 'test-account',
101
+ 'ecs',
102
+ 'eu-west-1',
103
+ 'vpc-1',
104
+ 'web-sg',
105
+ );
106
+ expect(vpcReader.getVpcName).toHaveBeenCalledWith('vpc-1');
107
+ const detailsSection = shallow(<div>{wrapper.find(CollapsibleSection).first().prop('children')}</div>);
108
+ expect(detailsSection.text()).toContain('Web ingress');
109
+ expect(detailsSection.text()).toContain('Production VPC');
110
+ expect(wrapper.find(IPRangeRules).prop('ipRules')).toEqual([
111
+ {
112
+ address: '10.0.0.0/24',
113
+ rules: [{ description: '', endPort: 80, protocol: 'tcp', startPort: 80 }],
114
+ },
115
+ ]);
116
+
117
+ const referencedRules = shallow(<div>{wrapper.find(CollapsibleSection).last().prop('children')}</div>);
118
+ expect(referencedRules.text()).toContain('tcp: 443');
119
+ const securityGroupLink = referencedRules.find('UISref');
120
+ expect(shallow(<div>{securityGroupLink.prop('children')}</div>).text()).toContain('source-sg (sg-source)');
121
+ expect(securityGroupLink.prop('params')).toEqual(jasmine.objectContaining({ provider: 'ecs', name: 'source-sg' }));
122
+ });
123
+
124
+ it('renders explicit empty states when no IP or referenced security-group rules exist', async () => {
125
+ const details = { ...securityGroup(), ipRangeRules: [], securityGroupRules: [] };
126
+ const securityGroupReader = {
127
+ getApplicationSecurityGroup: () => ({}),
128
+ getSecurityGroupDetails: () => Promise.resolve(details),
129
+ };
130
+ const wrapper = shallow(
131
+ <EcsSecurityGroupDetails
132
+ app={app()}
133
+ resolvedSecurityGroup={resolvedSecurityGroup}
134
+ securityGroupReader={securityGroupReader as any}
135
+ vpcReader={{ getVpcName: () => Promise.resolve(null) }}
136
+ />,
137
+ );
138
+
139
+ await tick();
140
+ wrapper.update();
141
+
142
+ expect(wrapper.find(IPRangeRules).prop('ipRules')).toEqual([]);
143
+ expect(wrapper.find('[data-test-id="ecs-ip-rules-empty"]').text()).toBe('None');
144
+ expect(wrapper.find('[data-test-id="ecs-security-group-rules-empty"]').text()).toBe('None');
145
+ });
146
+
147
+ it('shows the standalone not-found state for empty and failed detail loads', async () => {
148
+ const emptyReader = {
149
+ getSecurityGroupDetails: () => Promise.resolve({}),
150
+ };
151
+ const failedReader = {
152
+ getSecurityGroupDetails: () => Promise.reject(new Error('not found')),
153
+ };
154
+ const emptyWrapper = shallow(
155
+ <EcsSecurityGroupDetails
156
+ app={app(true)}
157
+ resolvedSecurityGroup={resolvedSecurityGroup}
158
+ securityGroupReader={emptyReader as any}
159
+ />,
160
+ );
161
+ const failedWrapper = shallow(
162
+ <EcsSecurityGroupDetails
163
+ app={app(true)}
164
+ resolvedSecurityGroup={resolvedSecurityGroup}
165
+ securityGroupReader={failedReader as any}
166
+ />,
167
+ );
168
+
169
+ await tick();
170
+ emptyWrapper.update();
171
+ failedWrapper.update();
172
+
173
+ expect(emptyWrapper.text()).toContain('Could not find');
174
+ expect(emptyWrapper.text()).toContain('web-sg');
175
+ expect(failedWrapper.text()).toContain('Could not find');
176
+ expect(failedWrapper.text()).toContain('web-sg');
177
+ });
178
+
179
+ it('ignores stale detail and VPC responses after coordinates change', async () => {
180
+ let resolveFirstDetails: (details: any) => void;
181
+ let resolveFirstVpc: (name: string) => void;
182
+ const firstDetails = new Promise<any>((resolve) => (resolveFirstDetails = resolve));
183
+ const firstVpc = new Promise<string>((resolve) => (resolveFirstVpc = resolve));
184
+ const securityGroupReader = {
185
+ getApplicationSecurityGroup: () => ({}),
186
+ getSecurityGroupDetails: jasmine
187
+ .createSpy('getSecurityGroupDetails')
188
+ .and.callFake((_app: any, _account: string, _provider: string, _region: string, _vpcId: string, name: string) =>
189
+ name === 'web-sg' ? firstDetails : Promise.resolve({ ...securityGroup('api-sg'), vpcId: 'vpc-2' }),
190
+ ),
191
+ };
192
+ const vpcReader = {
193
+ getVpcName: jasmine
194
+ .createSpy('getVpcName')
195
+ .and.callFake((vpcId: string) => (vpcId === 'vpc-1' ? firstVpc : Promise.resolve('API VPC'))),
196
+ };
197
+ const wrapper = shallow(
198
+ <EcsSecurityGroupDetails
199
+ app={app()}
200
+ resolvedSecurityGroup={resolvedSecurityGroup}
201
+ securityGroupReader={securityGroupReader as any}
202
+ vpcReader={vpcReader}
203
+ />,
204
+ );
205
+
206
+ await tick();
207
+ resolveFirstDetails(securityGroup());
208
+ await tick();
209
+ wrapper.setProps({
210
+ resolvedSecurityGroup: { ...resolvedSecurityGroup, name: 'api-sg', vpcId: 'vpc-2' },
211
+ });
212
+ await tick();
213
+ wrapper.update();
214
+ expect(wrapper.text()).toContain('api-sg');
215
+ expect(shallow(<div>{wrapper.find(CollapsibleSection).first().prop('children')}</div>).text()).toContain('API VPC');
216
+
217
+ resolveFirstVpc('Stale VPC');
218
+ await tick();
219
+ wrapper.update();
220
+
221
+ expect(wrapper.text()).toContain('api-sg');
222
+ expect(wrapper.text()).not.toContain('Stale VPC');
223
+ });
224
+
225
+ it('does not update state when a detail request resolves after unmount', async () => {
226
+ let resolveDetails: (details: any) => void;
227
+ const securityGroupReader = {
228
+ getSecurityGroupDetails: () => new Promise<any>((resolve) => (resolveDetails = resolve)),
229
+ };
230
+ const component = new EcsSecurityGroupDetails({
231
+ app: app(),
232
+ resolvedSecurityGroup,
233
+ securityGroupReader: securityGroupReader as any,
234
+ });
235
+ spyOn(component, 'setState');
236
+
237
+ (component as any).loadSecurityGroup();
238
+ (component.setState as jasmine.Spy).calls.reset();
239
+ component.componentWillUnmount();
240
+ resolveDetails(securityGroup());
241
+ await tick();
242
+
243
+ expect(component.setState).not.toHaveBeenCalled();
244
+ });
245
+ });
@@ -0,0 +1,285 @@
1
+ import { UISref } from '@uirouter/react';
2
+ import { isEmpty } from 'lodash';
3
+ import React from 'react';
4
+
5
+ import { buildIpRulesModel, buildSecurityGroupRulesModel, IPRangeRules, VpcReader } from '@spinnaker/amazon';
6
+ import type {
7
+ Application,
8
+ IRouterInjectedProps,
9
+ ISecurityGroup,
10
+ ISecurityGroupDetail,
11
+ SecurityGroupReader,
12
+ } from '@spinnaker/core';
13
+ import {
14
+ AccountTag,
15
+ CloudProviderLogo,
16
+ CollapsibleSection,
17
+ DeckRuntimeContext,
18
+ FirewallLabels,
19
+ RecentHistoryService,
20
+ Spinner,
21
+ withRouter,
22
+ } from '@spinnaker/core';
23
+
24
+ interface IEcsResolvedSecurityGroup {
25
+ accountId: string;
26
+ name: string;
27
+ provider?: string;
28
+ region: string;
29
+ vpcId?: string;
30
+ }
31
+
32
+ interface IEcsSecurityGroupDetailsProps {
33
+ app: Application;
34
+ autoClose?: () => void;
35
+ resolvedSecurityGroup: IEcsResolvedSecurityGroup;
36
+ securityGroupReader?: SecurityGroupReader;
37
+ vpcReader?: Pick<typeof VpcReader, 'getVpcName'>;
38
+ }
39
+
40
+ interface IEcsSecurityGroupDetailsState {
41
+ loading: boolean;
42
+ notFound?: boolean;
43
+ securityGroup?: ISecurityGroupDetail & ISecurityGroup & Record<string, any>;
44
+ }
45
+
46
+ export class EcsSecurityGroupDetailsComponent extends React.Component<
47
+ IEcsSecurityGroupDetailsProps & IRouterInjectedProps,
48
+ IEcsSecurityGroupDetailsState
49
+ > {
50
+ public static contextType = DeckRuntimeContext;
51
+ public declare context: React.ContextType<typeof DeckRuntimeContext>;
52
+
53
+ public state: IEcsSecurityGroupDetailsState = { loading: true };
54
+
55
+ private isUnmounted = false;
56
+ private loadRequestId = 0;
57
+ private unsubscribeFromRefresh: () => void;
58
+
59
+ public componentDidMount(): void {
60
+ const dataSource = this.props.app.getDataSource?.('securityGroups');
61
+ if (!dataSource) {
62
+ this.loadSecurityGroup();
63
+ return;
64
+ }
65
+
66
+ dataSource.ready().then(() => {
67
+ if (this.isUnmounted) {
68
+ return;
69
+ }
70
+ this.loadSecurityGroup();
71
+ this.unsubscribeFromRefresh = dataSource.onRefresh(this.loadSecurityGroup);
72
+ });
73
+ }
74
+
75
+ public componentDidUpdate(prevProps: IEcsSecurityGroupDetailsProps): void {
76
+ if (this.getCoordinatesKey(prevProps) !== this.getCoordinatesKey(this.props)) {
77
+ this.loadSecurityGroup();
78
+ }
79
+ }
80
+
81
+ public componentWillUnmount(): void {
82
+ this.isUnmounted = true;
83
+ this.unsubscribeFromRefresh?.();
84
+ }
85
+
86
+ private getCoordinatesKey(props: IEcsSecurityGroupDetailsProps): string {
87
+ const { accountId, name, region, vpcId } = props.resolvedSecurityGroup;
88
+ return [accountId, region, vpcId || '', name].join(':');
89
+ }
90
+
91
+ private getSecurityGroupReader(): SecurityGroupReader {
92
+ return this.props.securityGroupReader || this.context.services.securityGroupReader;
93
+ }
94
+
95
+ private getVpcReader(): Pick<typeof VpcReader, 'getVpcName'> {
96
+ return this.props.vpcReader || VpcReader;
97
+ }
98
+
99
+ private showNotFound = (): void => {
100
+ if (this.isUnmounted) {
101
+ return;
102
+ }
103
+ if (this.props.autoClose) {
104
+ this.props.autoClose();
105
+ return;
106
+ }
107
+ if (this.props.app.isStandalone) {
108
+ RecentHistoryService.removeLastItem('securityGroups');
109
+ this.setState({ loading: false, notFound: true, securityGroup: undefined });
110
+ return;
111
+ }
112
+ this.props.stateService.go('^', { allowModalToStayOpen: true }, { location: 'replace' });
113
+ };
114
+
115
+ private loadSecurityGroup = (): void => {
116
+ const { app, resolvedSecurityGroup } = this.props;
117
+ const requestId = ++this.loadRequestId;
118
+ this.setState({ loading: true, notFound: false, securityGroup: undefined });
119
+
120
+ this.getSecurityGroupReader()
121
+ .getSecurityGroupDetails(
122
+ app,
123
+ resolvedSecurityGroup.accountId,
124
+ 'ecs',
125
+ resolvedSecurityGroup.region,
126
+ resolvedSecurityGroup.vpcId || '',
127
+ resolvedSecurityGroup.name,
128
+ )
129
+ .then((details: ISecurityGroupDetail & ISecurityGroup & Record<string, any>) => {
130
+ if (this.isUnmounted || requestId !== this.loadRequestId) {
131
+ return;
132
+ }
133
+ if (!details || isEmpty(details)) {
134
+ this.showNotFound();
135
+ return;
136
+ }
137
+
138
+ Promise.resolve(this.getVpcReader().getVpcName(details.vpcId)).then(
139
+ (vpcName) => {
140
+ if (this.isUnmounted || requestId !== this.loadRequestId) {
141
+ return;
142
+ }
143
+ const applicationSecurityGroup = this.getSecurityGroupReader().getApplicationSecurityGroup?.(
144
+ app,
145
+ resolvedSecurityGroup.accountId,
146
+ resolvedSecurityGroup.region,
147
+ resolvedSecurityGroup.name,
148
+ );
149
+ this.setState({
150
+ loading: false,
151
+ securityGroup: {
152
+ ...(applicationSecurityGroup || {}),
153
+ ...details,
154
+ accountId: details.accountId || resolvedSecurityGroup.accountId,
155
+ name: details.name || resolvedSecurityGroup.name,
156
+ region: details.region || resolvedSecurityGroup.region,
157
+ vpcId: details.vpcId || resolvedSecurityGroup.vpcId,
158
+ vpcName,
159
+ },
160
+ });
161
+ },
162
+ () => {
163
+ if (!this.isUnmounted && requestId === this.loadRequestId) {
164
+ this.showNotFound();
165
+ }
166
+ },
167
+ );
168
+ })
169
+ .catch(() => {
170
+ if (!this.isUnmounted && requestId === this.loadRequestId) {
171
+ this.showNotFound();
172
+ }
173
+ });
174
+ };
175
+
176
+ private closeDetails = (): void => {
177
+ this.props.stateService.go('^');
178
+ };
179
+
180
+ public render(): JSX.Element {
181
+ const { app, resolvedSecurityGroup } = this.props;
182
+ const { loading, notFound, securityGroup } = this.state;
183
+
184
+ if (notFound) {
185
+ return (
186
+ <div className="text-center">
187
+ <h3>
188
+ Could not find {FirewallLabels.get('firewall')} {resolvedSecurityGroup.name}.
189
+ </h3>
190
+ <UISref to="home.infrastructure">
191
+ <a>Back to search results</a>
192
+ </UISref>
193
+ </div>
194
+ );
195
+ }
196
+
197
+ const ipRules = securityGroup ? buildIpRulesModel(securityGroup) : [];
198
+ const securityGroupRules = securityGroup ? buildSecurityGroupRulesModel(securityGroup) : [];
199
+
200
+ return (
201
+ <div className="details-panel">
202
+ <div className="header">
203
+ {!app.isStandalone && (
204
+ <div className="close-button">
205
+ <a className="btn btn-link" onClick={this.closeDetails}>
206
+ <span className="glyphicon glyphicon-remove" />
207
+ </a>
208
+ </div>
209
+ )}
210
+ {loading && (
211
+ <div className="horizontal center middle">
212
+ <Spinner size="small" />
213
+ </div>
214
+ )}
215
+ {!loading && securityGroup && (
216
+ <div className="header-text horizontal middle">
217
+ <CloudProviderLogo provider="ecs" height="36px" width="36px" />
218
+ <h3>{securityGroup.name || '(not found)'}</h3>
219
+ </div>
220
+ )}
221
+ </div>
222
+ {!loading && securityGroup && (
223
+ <div className="content">
224
+ <CollapsibleSection heading={`${FirewallLabels.get('Firewall')} Details`} defaultExpanded={true}>
225
+ <dl className="dl-horizontal dl-medium">
226
+ <dt>ID</dt>
227
+ <dd>{securityGroup.id}</dd>
228
+ <dt>Account</dt>
229
+ <dd>
230
+ <AccountTag account={securityGroup.accountName || securityGroup.accountId} />
231
+ </dd>
232
+ <dt>Region</dt>
233
+ <dd>{securityGroup.region}</dd>
234
+ <dt>VPC</dt>
235
+ <dd>{securityGroup.vpcName || securityGroup.vpcId}</dd>
236
+ <dt>Description</dt>
237
+ <dd>{securityGroup.description}</dd>
238
+ </dl>
239
+ </CollapsibleSection>
240
+ <IPRangeRules ipRules={ipRules} />
241
+ {!ipRules.length && <div data-test-id="ecs-ip-rules-empty">None</div>}
242
+ <CollapsibleSection
243
+ heading={`${FirewallLabels.get('Firewall')} Rules (${securityGroupRules.length})`}
244
+ defaultExpanded={securityGroupRules.length > 0}
245
+ >
246
+ {!securityGroupRules.length && <div data-test-id="ecs-security-group-rules-empty">None</div>}
247
+ {securityGroupRules.map((rule) => (
248
+ <dl className="dl-horizontal dl-medium" key={rule.securityGroup.id || rule.securityGroup.name}>
249
+ <dt>{FirewallLabels.get('Firewall')}</dt>
250
+ <dd>
251
+ <UISref
252
+ to="^.firewallDetails"
253
+ params={{
254
+ accountId: rule.securityGroup.accountName || rule.securityGroup.accountId,
255
+ name: rule.securityGroup.name,
256
+ provider: 'ecs',
257
+ region: rule.securityGroup.region,
258
+ vpcId: rule.securityGroup.vpcId,
259
+ }}
260
+ >
261
+ <a>
262
+ {rule.securityGroup.accountName !== securityGroup.accountName && (
263
+ <AccountTag account={rule.securityGroup.accountName || rule.securityGroup.accountId} />
264
+ )}{' '}
265
+ {rule.securityGroup.name} ({rule.securityGroup.id})
266
+ </a>
267
+ </UISref>
268
+ </dd>
269
+ <dt>Port Ranges</dt>
270
+ {rule.rules.map((portRange) => (
271
+ <dd key={`${portRange.protocol}-${portRange.startPort}-${portRange.endPort}`}>
272
+ {portRange.protocol}: {portRange.startPort} &rarr; {portRange.endPort}
273
+ </dd>
274
+ ))}
275
+ </dl>
276
+ ))}
277
+ </CollapsibleSection>
278
+ </div>
279
+ )}
280
+ </div>
281
+ );
282
+ }
283
+ }
284
+
285
+ export const EcsSecurityGroupDetails = withRouter(EcsSecurityGroupDetailsComponent);
@@ -1,5 +1,3 @@
1
- import { module } from 'angular';
2
-
3
1
  import type { ISecurityGroup, ISecurityGroupsByAccount } from '@spinnaker/core';
4
2
 
5
3
  export class EcsSecurityGroupReader {
@@ -11,6 +9,3 @@ export class EcsSecurityGroupReader {
11
9
  return indexedSecurityGroups[container.account][container.region][securityGroupId];
12
10
  }
13
11
  }
14
-
15
- export const ECS_SECURITY_GROUP_READER = 'spinnaker.ecs.securityGroup.reader';
16
- module(ECS_SECURITY_GROUP_READER, []).service('ecsSecurityGroupReader', EcsSecurityGroupReader);
@@ -1,13 +1,5 @@
1
- 'use strict';
2
-
3
- import { module } from 'angular';
4
-
5
- export const ECS_SECURITYGROUP_SECURITYGROUP_TRANSFORMER = 'spinnaker.ecs.securityGroup.transformer';
6
- export const name = ECS_SECURITYGROUP_SECURITYGROUP_TRANSFORMER; // for backwards compatibility
7
- module(ECS_SECURITYGROUP_SECURITYGROUP_TRANSFORMER, []).factory('ecsSecurityGroupTransformer', function () {
8
- function normalizeSecurityGroup() {}
9
-
10
- return {
11
- normalizeSecurityGroup: normalizeSecurityGroup,
12
- };
13
- });
1
+ export class EcsSecurityGroupTransformer {
2
+ normalizeSecurityGroup(securityGroup) {
3
+ return Promise.resolve(securityGroup);
4
+ }
5
+ }