@spinnaker/azure 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 (215) hide show
  1. package/dist/azure.module.d.ts +3 -1
  2. package/dist/image/image.reader.d.ts +4 -2
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/instance/azureInstanceType.service.d.ts +6 -2
  6. package/dist/instance/details/AzureInstanceDetails.d.ts +67 -0
  7. package/dist/loadBalancer/configure/AzureLoadBalancerChoiceModal.d.ts +10 -5
  8. package/dist/loadBalancer/configure/AzureLoadBalancerModal.d.ts +102 -0
  9. package/dist/loadBalancer/details/azureLoadBalancerDetails.d.ts +26 -0
  10. package/dist/loadBalancer/loadBalancer.transformer.d.ts +45 -2
  11. package/dist/pipeline/stages/AzureAccountRegionClusterSelector.d.ts +32 -0
  12. package/dist/pipeline/stages/bake/azureBakeStage.d.ts +46 -2
  13. package/dist/pipeline/stages/destroyAsg/azureDestroyAsgStage.d.ts +14 -2
  14. package/dist/pipeline/stages/disableAsg/azureDisableAsgStage.d.ts +4 -2
  15. package/dist/pipeline/stages/enableAsg/azureEnableAsgStage.d.ts +4 -2
  16. package/dist/securityGroup/configure/AzureSecurityGroupModal.d.ts +70 -0
  17. package/dist/securityGroup/details/AzureSecurityGroupDetails.d.ts +51 -0
  18. package/dist/securityGroup/securityGroup.reader.d.ts +3 -2
  19. package/dist/securityGroup/securityGroup.transformer.d.ts +3 -2
  20. package/dist/securityGroup/securityGroup.write.service.d.ts +9 -2
  21. package/dist/serverGroup/configure/serverGroupCommandBuilder.service.d.ts +88 -2
  22. package/dist/serverGroup/configure/serverGroupConfiguration.service.d.ts +30 -2
  23. package/dist/serverGroup/configure/wizard/AzureCloneServerGroupModal.d.ts +44 -0
  24. package/dist/serverGroup/configure/wizard/pages/ServerGroupAdvancedSettings.d.ts +8 -0
  25. package/dist/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.d.ts +12 -0
  26. package/dist/serverGroup/configure/wizard/pages/ServerGroupCapacityAndZones.d.ts +11 -0
  27. package/dist/serverGroup/configure/wizard/pages/ServerGroupHealthSettings.d.ts +10 -0
  28. package/dist/serverGroup/configure/wizard/pages/ServerGroupImageSettings.d.ts +12 -0
  29. package/dist/serverGroup/configure/wizard/pages/ServerGroupInstanceType.d.ts +9 -0
  30. package/dist/serverGroup/configure/wizard/pages/ServerGroupLoadBalancers.d.ts +14 -0
  31. package/dist/serverGroup/configure/wizard/pages/ServerGroupNetworkSettings.d.ts +10 -0
  32. package/dist/serverGroup/configure/wizard/pages/ServerGroupSecurityGroups.d.ts +6 -0
  33. package/dist/serverGroup/configure/wizard/pages/ServerGroupTags.d.ts +9 -0
  34. package/dist/serverGroup/configure/wizard/pages/common.d.ts +14 -0
  35. package/dist/serverGroup/configure/wizard/pages/index.d.ts +10 -0
  36. package/dist/serverGroup/details/azureServerGroupDetails.d.ts +13 -0
  37. package/dist/serverGroup/details/rollback/RollbackServerGroupModal.d.ts +6 -4
  38. package/dist/serverGroup/serverGroup.transformer.d.ts +61 -2
  39. package/dist/validation/applicationName.validator.d.ts +6 -2
  40. package/package.json +5 -8
  41. package/src/azure.module.spec.ts +921 -0
  42. package/src/azure.module.ts +56 -68
  43. package/src/image/image.reader.js +4 -15
  44. package/src/image/image.reader.spec.js +4 -7
  45. package/src/instance/azureInstanceType.service.js +8 -16
  46. package/src/instance/azureInstanceType.service.spec.js +5 -13
  47. package/src/instance/details/AzureInstanceDetails.spec.tsx +364 -0
  48. package/src/instance/details/AzureInstanceDetails.tsx +533 -0
  49. package/src/loadBalancer/configure/AzureLoadBalancerChoiceModal.spec.tsx +125 -0
  50. package/src/loadBalancer/configure/AzureLoadBalancerChoiceModal.tsx +46 -26
  51. package/src/loadBalancer/configure/AzureLoadBalancerModal.spec.tsx +658 -0
  52. package/src/loadBalancer/configure/AzureLoadBalancerModal.tsx +960 -0
  53. package/src/loadBalancer/details/azureLoadBalancerDetails.spec.tsx +470 -0
  54. package/src/loadBalancer/details/azureLoadBalancerDetails.tsx +412 -0
  55. package/src/loadBalancer/loadBalancer.transformer.js +83 -93
  56. package/src/pipeline/stages/AzureAccountRegionClusterSelector.tsx +238 -0
  57. package/src/pipeline/stages/bake/azureBakeStage.tsx +723 -0
  58. package/src/pipeline/stages/destroyAsg/azureDestroyAsgStage.tsx +128 -0
  59. package/src/pipeline/stages/disableAsg/azureDisableAsgStage.tsx +92 -0
  60. package/src/pipeline/stages/enableAsg/azureEnableAsgStage.tsx +87 -0
  61. package/src/routerConsumers.spec.tsx +69 -0
  62. package/src/securityGroup/configure/AzureSecurityGroupModal.spec.tsx +558 -0
  63. package/src/securityGroup/configure/AzureSecurityGroupModal.tsx +782 -0
  64. package/src/securityGroup/details/AzureSecurityGroupDetails.spec.tsx +291 -0
  65. package/src/securityGroup/details/AzureSecurityGroupDetails.tsx +354 -0
  66. package/src/securityGroup/securityGroup.reader.js +3 -13
  67. package/src/securityGroup/securityGroup.transformer.js +3 -13
  68. package/src/securityGroup/securityGroup.write.service.spec.ts +105 -0
  69. package/src/securityGroup/securityGroup.write.service.ts +73 -0
  70. package/src/serverGroup/configure/serverGroupCommandBuilder.service.js +167 -166
  71. package/src/serverGroup/configure/serverGroupCommandBuilder.service.spec.js +63 -0
  72. package/src/serverGroup/configure/serverGroupConfiguration.service.js +325 -309
  73. package/src/serverGroup/configure/serverGroupConfiguration.service.spec.js +57 -53
  74. package/src/serverGroup/configure/wizard/AzureCloneServerGroupModal.spec.tsx +758 -0
  75. package/src/serverGroup/configure/wizard/AzureCloneServerGroupModal.tsx +349 -0
  76. package/src/serverGroup/configure/wizard/pages/ServerGroupAdvancedSettings.tsx +208 -0
  77. package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.tsx +122 -0
  78. package/src/serverGroup/configure/wizard/pages/ServerGroupCapacityAndZones.tsx +80 -0
  79. package/src/serverGroup/configure/wizard/pages/ServerGroupHealthSettings.tsx +97 -0
  80. package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.tsx +151 -0
  81. package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.tsx +41 -0
  82. package/src/serverGroup/configure/wizard/pages/ServerGroupLoadBalancers.tsx +214 -0
  83. package/src/serverGroup/configure/wizard/pages/ServerGroupNetworkSettings.tsx +119 -0
  84. package/src/serverGroup/configure/wizard/pages/ServerGroupSecurityGroups.tsx +54 -0
  85. package/src/serverGroup/configure/wizard/pages/ServerGroupTags.tsx +40 -0
  86. package/src/serverGroup/configure/wizard/pages/common.tsx +72 -0
  87. package/src/serverGroup/configure/wizard/pages/index.ts +10 -0
  88. package/src/serverGroup/details/azureServerGroupDetails.spec.tsx +263 -0
  89. package/src/serverGroup/details/azureServerGroupDetails.tsx +318 -0
  90. package/src/serverGroup/details/rollback/RollbackServerGroupModal.tsx +24 -30
  91. package/src/serverGroup/serverGroup.transformer.js +11 -19
  92. package/src/serverGroup/serverGroup.transformer.spec.js +2 -6
  93. package/src/validation/applicationName.validator.js +14 -29
  94. package/dist/instance/details/instance.details.controller.d.ts +0 -2
  95. package/dist/loadBalancer/configure/createLoadBalancer.controller.d.ts +0 -2
  96. package/dist/loadBalancer/details/loadBalancerDetail.controller.d.ts +0 -2
  97. package/dist/pipeline/stages/bake/bakeExecutionDetails.controller.d.ts +0 -2
  98. package/dist/securityGroup/clone/cloneSecurityGroup.controller.d.ts +0 -2
  99. package/dist/securityGroup/configure/CreateSecurityGroupCtrl.d.ts +0 -2
  100. package/dist/securityGroup/configure/EditSecurityGroupCtrl.d.ts +0 -2
  101. package/dist/securityGroup/details/securityGroupDetail.controller.d.ts +0 -2
  102. package/dist/serverGroup/configure/serverGroup.configure.azure.module.d.ts +0 -2
  103. package/dist/serverGroup/configure/wizard/CloneServerGroup.azure.controller.d.ts +0 -2
  104. package/dist/serverGroup/configure/wizard/ServerGroupInstanceArchetype.controller.d.ts +0 -2
  105. package/dist/serverGroup/configure/wizard/ServerGroupInstanceType.controller.d.ts +0 -2
  106. package/dist/serverGroup/configure/wizard/advancedSettings/ServerGroupAdvancedSettings.controller.d.ts +0 -2
  107. package/dist/serverGroup/configure/wizard/advancedSettings/advancedSettingsSelector.directive.d.ts +0 -2
  108. package/dist/serverGroup/configure/wizard/basicSettings/ServerGroupBasicSettings.controller.d.ts +0 -2
  109. package/dist/serverGroup/configure/wizard/basicSettings/image.regional.filter.d.ts +0 -2
  110. package/dist/serverGroup/configure/wizard/capacity/capacitySelector.directive.d.ts +0 -2
  111. package/dist/serverGroup/configure/wizard/healthSettings/ServerGroupHealthSettings.controller.d.ts +0 -2
  112. package/dist/serverGroup/configure/wizard/healthSettings/healthSettingsSelector.directive.d.ts +0 -2
  113. package/dist/serverGroup/configure/wizard/image/ServerGroupImageSettings.controller.d.ts +0 -2
  114. package/dist/serverGroup/configure/wizard/image/imageSettingsSelector.directive.d.ts +0 -2
  115. package/dist/serverGroup/configure/wizard/loadBalancers/ServerGroupLoadBalancers.controller.d.ts +0 -2
  116. package/dist/serverGroup/configure/wizard/loadBalancers/serverGroupLoadBalancersSelector.directive.d.ts +0 -2
  117. package/dist/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettings.controller.d.ts +0 -2
  118. package/dist/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettingsSelector.directive.d.ts +0 -2
  119. package/dist/serverGroup/configure/wizard/securityGroup/ServerGroupSecurityGroups.controller.d.ts +0 -2
  120. package/dist/serverGroup/configure/wizard/securityGroup/serverGroupSecurityGroupsSelector.directive.d.ts +0 -2
  121. package/dist/serverGroup/configure/wizard/tags/tagsSelector.directive.d.ts +0 -2
  122. package/dist/serverGroup/configure/wizard/zones/zoneSelector.directive.d.ts +0 -2
  123. package/dist/serverGroup/details/serverGroup.details.module.d.ts +0 -2
  124. package/dist/serverGroup/details/serverGroupDetails.azure.controller.d.ts +0 -2
  125. package/src/instance/details/instance.details.controller.js +0 -371
  126. package/src/instance/details/instance.details.controller.spec.js +0 -129
  127. package/src/instance/details/instanceDetails.html +0 -43
  128. package/src/loadBalancer/configure/advancedSettings.html +0 -95
  129. package/src/loadBalancer/configure/createLoadBalancer.controller.js +0 -275
  130. package/src/loadBalancer/configure/createLoadBalancer.controller.spec.js +0 -39
  131. package/src/loadBalancer/configure/createLoadBalancer.html +0 -25
  132. package/src/loadBalancer/configure/createLoadBalancerProperties.html +0 -128
  133. package/src/loadBalancer/configure/editLoadBalancer.html +0 -26
  134. package/src/loadBalancer/configure/healthCheck.html +0 -74
  135. package/src/loadBalancer/configure/listeners.html +0 -50
  136. package/src/loadBalancer/details/loadBalancerDetail.controller.js +0 -181
  137. package/src/loadBalancer/details/loadBalancerDetail.controller.spec.js +0 -39
  138. package/src/loadBalancer/details/loadBalancerDetail.html +0 -128
  139. package/src/pipeline/stages/bake/azureBakeStage.js +0 -276
  140. package/src/pipeline/stages/bake/bakeExecutionDetails.controller.js +0 -40
  141. package/src/pipeline/stages/bake/bakeExecutionDetails.html +0 -50
  142. package/src/pipeline/stages/bake/bakeStage.html +0 -187
  143. package/src/pipeline/stages/destroyAsg/azureDestroyAsgStage.js +0 -73
  144. package/src/pipeline/stages/destroyAsg/destroyAsgStage.html +0 -9
  145. package/src/pipeline/stages/destroyAsg/destroyAsgStepLabel.html +0 -1
  146. package/src/pipeline/stages/disableAsg/azureDisableAsgStage.js +0 -65
  147. package/src/pipeline/stages/disableAsg/disableAsgStage.html +0 -11
  148. package/src/pipeline/stages/disableAsg/disableAsgStepLabel.html +0 -1
  149. package/src/pipeline/stages/enableAsg/azureEnableAsgStage.js +0 -70
  150. package/src/pipeline/stages/enableAsg/enableAsgStage.html +0 -11
  151. package/src/pipeline/stages/enableAsg/enableAsgStepLabel.html +0 -1
  152. package/src/securityGroup/clone/cloneSecurityGroup.controller.js +0 -162
  153. package/src/securityGroup/clone/cloneSecurityGroup.html +0 -8
  154. package/src/securityGroup/configure/CreateSecurityGroup.controller.spec.js +0 -78
  155. package/src/securityGroup/configure/CreateSecurityGroupCtrl.js +0 -265
  156. package/src/securityGroup/configure/EditSecurityGroupCtrl.js +0 -222
  157. package/src/securityGroup/configure/configSecurityGroup.mixin.controller.js +0 -262
  158. package/src/securityGroup/configure/createSecurityGroup.html +0 -20
  159. package/src/securityGroup/configure/createSecurityGroupIngress.html +0 -75
  160. package/src/securityGroup/configure/createSecurityGroupProperties.html +0 -88
  161. package/src/securityGroup/configure/editSecurityGroup.html +0 -19
  162. package/src/securityGroup/details/securityGroupDetail.controller.js +0 -142
  163. package/src/securityGroup/details/securityGroupDetail.html +0 -78
  164. package/src/securityGroup/securityGroup.write.service.js +0 -57
  165. package/src/serverGroup/configure/serverGroup.configure.azure.module.js +0 -47
  166. package/src/serverGroup/configure/wizard/CloneServerGroup.azure.controller.js +0 -247
  167. package/src/serverGroup/configure/wizard/ServerGroupInstanceArchetype.controller.js +0 -35
  168. package/src/serverGroup/configure/wizard/ServerGroupInstanceType.controller.js +0 -15
  169. package/src/serverGroup/configure/wizard/advancedSettings/ServerGroupAdvancedSettings.controller.js +0 -25
  170. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettings.html +0 -5
  171. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettingsSelector.directive.html +0 -138
  172. package/src/serverGroup/configure/wizard/advancedSettings/advancedSettingsSelector.directive.js +0 -43
  173. package/src/serverGroup/configure/wizard/basicSettings/ServerGroupBasicSettings.controller.js +0 -71
  174. package/src/serverGroup/configure/wizard/basicSettings/basicSettings.html +0 -131
  175. package/src/serverGroup/configure/wizard/basicSettings/image.regional.filter.js +0 -15
  176. package/src/serverGroup/configure/wizard/basicSettings/image.regional.filter.spec.js +0 -42
  177. package/src/serverGroup/configure/wizard/capacity/capacity.html +0 -5
  178. package/src/serverGroup/configure/wizard/capacity/capacitySelector.directive.html +0 -20
  179. package/src/serverGroup/configure/wizard/capacity/capacitySelector.directive.js +0 -21
  180. package/src/serverGroup/configure/wizard/capacity/zones.html +0 -5
  181. package/src/serverGroup/configure/wizard/healthSettings/ServerGroupHealthSettings.controller.js +0 -38
  182. package/src/serverGroup/configure/wizard/healthSettings/healthSettings.html +0 -5
  183. package/src/serverGroup/configure/wizard/healthSettings/healthSettingsSelector.directive.html +0 -32
  184. package/src/serverGroup/configure/wizard/healthSettings/healthSettingsSelector.directive.js +0 -22
  185. package/src/serverGroup/configure/wizard/image/ServerGroupImageSettings.controller.js +0 -34
  186. package/src/serverGroup/configure/wizard/image/imageSettings.html +0 -5
  187. package/src/serverGroup/configure/wizard/image/imageSettingsSelector.directive.html +0 -33
  188. package/src/serverGroup/configure/wizard/image/imageSettingsSelector.directive.js +0 -22
  189. package/src/serverGroup/configure/wizard/instanceArchetype.html +0 -33
  190. package/src/serverGroup/configure/wizard/instanceType/instanceType.html +0 -10
  191. package/src/serverGroup/configure/wizard/instanceType.html +0 -33
  192. package/src/serverGroup/configure/wizard/loadBalancers/ServerGroupLoadBalancers.controller.js +0 -137
  193. package/src/serverGroup/configure/wizard/loadBalancers/loadBalancers.html +0 -3
  194. package/src/serverGroup/configure/wizard/loadBalancers/serverGroupLoadBalancersSelector.directive.html +0 -41
  195. package/src/serverGroup/configure/wizard/loadBalancers/serverGroupLoadBalancersSelector.directive.js +0 -22
  196. package/src/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettings.controller.js +0 -44
  197. package/src/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettingsSelector.directive.html +0 -54
  198. package/src/serverGroup/configure/wizard/networkSettings/ServerGroupNetworkSettingsSelector.directive.js +0 -19
  199. package/src/serverGroup/configure/wizard/networkSettings/networkSettings.html +0 -3
  200. package/src/serverGroup/configure/wizard/securityGroup/ServerGroupSecurityGroups.controller.js +0 -28
  201. package/src/serverGroup/configure/wizard/securityGroup/securityGroups.html +0 -3
  202. package/src/serverGroup/configure/wizard/securityGroup/serverGroupSecurityGroupsSelector.directive.html +0 -40
  203. package/src/serverGroup/configure/wizard/securityGroup/serverGroupSecurityGroupsSelector.directive.js +0 -38
  204. package/src/serverGroup/configure/wizard/serverGroupWizard.html +0 -61
  205. package/src/serverGroup/configure/wizard/tags/tags.html +0 -5
  206. package/src/serverGroup/configure/wizard/tags/tagsSelector.directive.html +0 -16
  207. package/src/serverGroup/configure/wizard/tags/tagsSelector.directive.js +0 -30
  208. package/src/serverGroup/configure/wizard/templateSelection.html +0 -9
  209. package/src/serverGroup/configure/wizard/zones/zoneSelector.directive.html +0 -21
  210. package/src/serverGroup/configure/wizard/zones/zoneSelector.directive.js +0 -28
  211. package/src/serverGroup/details/serverGroup.details.module.js +0 -11
  212. package/src/serverGroup/details/serverGroupDetails.azure.controller.js +0 -260
  213. package/src/serverGroup/details/serverGroupDetails.html +0 -98
  214. package/src/subnet/subnetSelectField.directive.html +0 -28
  215. package/src/subnet/subnetSelectField.directive.js +0 -35
@@ -0,0 +1,291 @@
1
+ import { shallow } from 'enzyme';
2
+ import React from 'react';
3
+ import { MenuItem } from 'react-bootstrap';
4
+
5
+ import { AccountTag, CollapsibleSection, ConfirmationModalService } from '@spinnaker/core';
6
+
7
+ import { AzureSecurityGroupModal } from '../configure/AzureSecurityGroupModal';
8
+ import { AzureSecurityGroupWriter } from '../securityGroup.write.service';
9
+ import {
10
+ AzureSecurityGroupActions,
11
+ AzureSecurityGroupDetailsComponent as AzureSecurityGroupDetails,
12
+ AzureSecurityGroupInformationSection,
13
+ AzureSecurityGroupRulesSection,
14
+ } from './AzureSecurityGroupDetails';
15
+
16
+ describe('AzureSecurityGroupDetails', () => {
17
+ const resolvedSecurityGroup = {
18
+ accountId: 'test-account',
19
+ name: 'fnord-sg',
20
+ provider: 'azure',
21
+ region: 'westus',
22
+ vpcId: 'vnet-1',
23
+ } as any;
24
+
25
+ function app() {
26
+ return {
27
+ name: 'fnord',
28
+ getDataSource: () => ({ ready: () => Promise.resolve(), onRefresh: () => jasmine.createSpy('unsubscribe') }),
29
+ securityGroups: { refresh: jasmine.createSpy('refresh') },
30
+ } as any;
31
+ }
32
+
33
+ function securityGroup() {
34
+ return {
35
+ account: 'test-account',
36
+ accountId: 'test-account',
37
+ name: 'fnord-sg',
38
+ region: 'westus',
39
+ securityRules: [
40
+ {
41
+ name: 'allow-web',
42
+ access: 'Allow',
43
+ destinationAddressPrefix: '*',
44
+ direction: 'Inbound',
45
+ priority: 100,
46
+ protocol: 'Tcp',
47
+ sourceAddressPrefixes: ['10.0.0.0/24', '192.168.0.0/24'],
48
+ sourcePortRange: '*',
49
+ destinationPortRanges: ['80', '443'],
50
+ },
51
+ ],
52
+ vpcId: 'vnet-1',
53
+ } as any;
54
+ }
55
+
56
+ it('closes missing details through the injected state service', () => {
57
+ const stateService = { go: jasmine.createSpy('go') };
58
+ const component = new AzureSecurityGroupDetails({
59
+ app: app(),
60
+ resolvedSecurityGroup,
61
+ router: {},
62
+ stateParams: {},
63
+ stateService,
64
+ } as any);
65
+
66
+ (component as any).autoClose();
67
+
68
+ expect(stateService.go).toHaveBeenCalledWith('^');
69
+ });
70
+
71
+ it('loads details with the core security group reader and renders basic sections', async () => {
72
+ const securityGroupReader = {
73
+ getSecurityGroupDetails: jasmine
74
+ .createSpy('getSecurityGroupDetails')
75
+ .and.returnValue(Promise.resolve(securityGroup())),
76
+ };
77
+ const wrapper = shallow(
78
+ <AzureSecurityGroupDetails
79
+ app={app()}
80
+ resolvedSecurityGroup={resolvedSecurityGroup}
81
+ securityGroupReader={securityGroupReader as any}
82
+ />,
83
+ );
84
+
85
+ await Promise.resolve();
86
+ await Promise.resolve();
87
+ wrapper.update();
88
+
89
+ expect(securityGroupReader.getSecurityGroupDetails).toHaveBeenCalledWith(
90
+ jasmine.anything(),
91
+ 'test-account',
92
+ 'azure',
93
+ 'westus',
94
+ 'vnet-1',
95
+ 'fnord-sg',
96
+ );
97
+ expect(wrapper.find('h3').text()).toContain('fnord-sg');
98
+ expect(wrapper.find(AzureSecurityGroupActions).prop('securityGroup')).toEqual(securityGroup());
99
+ expect(wrapper.find(AzureSecurityGroupRulesSection).prop('securityGroup')).toEqual(securityGroup());
100
+ });
101
+
102
+ it('renders Azure security rules with normalized port and source values', () => {
103
+ const wrapper = shallow(<AzureSecurityGroupRulesSection securityGroup={securityGroup()} />);
104
+ const sectionContent = shallow(<div>{wrapper.find(CollapsibleSection).prop('children')}</div>);
105
+ const text = sectionContent.text();
106
+
107
+ expect(text).toContain('allow-web');
108
+ expect(text).toContain('100');
109
+ expect(text).toContain('10.0.0.0/24, 192.168.0.0/24');
110
+ expect(text).toContain('*');
111
+ expect(text).toContain('80, 443');
112
+ expect(text).toContain('Inbound');
113
+ });
114
+
115
+ it('renders legacy information fields from the Azure details panel', () => {
116
+ const wrapper = shallow(
117
+ <AzureSecurityGroupInformationSection
118
+ securityGroup={
119
+ {
120
+ accountName: 'prod-account',
121
+ description: 'frontend ingress',
122
+ id: 'nsg-resource-id',
123
+ region: 'westus',
124
+ vpcId: 'vnet-1',
125
+ } as any
126
+ }
127
+ />,
128
+ );
129
+ const sectionContent = shallow(<div>{wrapper.find(CollapsibleSection).prop('children')}</div>);
130
+ const text = sectionContent.text();
131
+
132
+ expect(text).toContain('nsg-resource-id');
133
+ expect(sectionContent.find(AccountTag).prop('account')).toBe('prod-account');
134
+ expect(text).toContain('westus');
135
+ expect(text).toContain('frontend ingress');
136
+ });
137
+
138
+ it('renders legacy details model fields for security rule source and ports', () => {
139
+ const wrapper = shallow(
140
+ <AzureSecurityGroupRulesSection
141
+ securityGroup={
142
+ {
143
+ securityRules: [
144
+ {
145
+ name: 'allow-web',
146
+ destinationPortRangeModel: '8080, 8443',
147
+ sourceAddressPrefixModel: '10.0.0.0/24, 192.168.0.0/24',
148
+ },
149
+ ],
150
+ } as any
151
+ }
152
+ />,
153
+ );
154
+ const sectionContent = shallow(<div>{wrapper.find(CollapsibleSection).prop('children')}</div>);
155
+ const text = sectionContent.text();
156
+
157
+ expect(text).toContain('10.0.0.0/24, 192.168.0.0/24');
158
+ expect(text).toContain('8080, 8443');
159
+ });
160
+
161
+ it('renders Azure security rules sorted by priority', () => {
162
+ const wrapper = shallow(
163
+ <AzureSecurityGroupRulesSection
164
+ securityGroup={
165
+ {
166
+ securityRules: [
167
+ { name: 'second', priority: 200, destinationPortRange: '443', sourceAddressPrefix: '*' },
168
+ { name: 'first', priority: 100, destinationPortRange: '80', sourceAddressPrefix: '*' },
169
+ ],
170
+ } as any
171
+ }
172
+ />,
173
+ );
174
+ const sectionContent = shallow(<div>{wrapper.find(CollapsibleSection).prop('children')}</div>);
175
+ const ruleNames = sectionContent.find('tbody tr').map((row) => row.find('td').at(1).text());
176
+
177
+ expect(ruleNames).toEqual(['first', 'second']);
178
+ });
179
+
180
+ it('reloads when coordinates change and ignores stale responses', async () => {
181
+ let resolveFirst: (value: any) => void;
182
+ let resolveSecond: (value: any) => void;
183
+ const securityGroupReader = {
184
+ getSecurityGroupDetails: jasmine
185
+ .createSpy('getSecurityGroupDetails')
186
+ .and.callFake(
187
+ (_app: any, _account: string, _provider: string, _region: string, _vpcId: string, name: string) => {
188
+ if (name === 'fnord-sg') {
189
+ return new Promise((resolve) => {
190
+ resolveFirst = resolve;
191
+ });
192
+ }
193
+ return new Promise((resolve) => {
194
+ resolveSecond = resolve;
195
+ });
196
+ },
197
+ ),
198
+ };
199
+ const wrapper = shallow(
200
+ <AzureSecurityGroupDetails
201
+ app={app()}
202
+ resolvedSecurityGroup={resolvedSecurityGroup}
203
+ securityGroupReader={securityGroupReader as any}
204
+ />,
205
+ );
206
+
207
+ await Promise.resolve();
208
+ wrapper.setProps({ resolvedSecurityGroup: { ...resolvedSecurityGroup, name: 'other-sg' } });
209
+ await Promise.resolve();
210
+ resolveSecond({ ...securityGroup(), name: 'other-sg' });
211
+ await Promise.resolve();
212
+ resolveFirst(securityGroup());
213
+ await Promise.resolve();
214
+ wrapper.update();
215
+
216
+ expect(securityGroupReader.getSecurityGroupDetails.calls.count()).toBe(2);
217
+ expect(wrapper.find('h3').text()).toContain('other-sg');
218
+ });
219
+
220
+ it('auto-closes when the details response is empty', async () => {
221
+ const autoClose = jasmine.createSpy('autoClose');
222
+ const securityGroupReader = {
223
+ getSecurityGroupDetails: jasmine.createSpy('getSecurityGroupDetails').and.returnValue(Promise.resolve({})),
224
+ };
225
+
226
+ shallow(
227
+ <AzureSecurityGroupDetails
228
+ app={app()}
229
+ autoClose={autoClose}
230
+ resolvedSecurityGroup={resolvedSecurityGroup}
231
+ securityGroupReader={securityGroupReader as any}
232
+ />,
233
+ );
234
+
235
+ await Promise.resolve();
236
+ await Promise.resolve();
237
+
238
+ expect(autoClose).toHaveBeenCalled();
239
+ });
240
+
241
+ it('opens edit, clone, and delete actions', () => {
242
+ spyOn(AzureSecurityGroupModal, 'show');
243
+ spyOn(ConfirmationModalService, 'confirm');
244
+ spyOn(AzureSecurityGroupWriter, 'deleteSecurityGroup').and.returnValue(Promise.resolve({} as any));
245
+ const wrapper = shallow(<AzureSecurityGroupActions app={app()} securityGroup={securityGroup()} />);
246
+
247
+ wrapper.find(MenuItem).at(0).prop('onClick')({} as any);
248
+ wrapper.find(MenuItem).at(1).prop('onClick')({} as any);
249
+ wrapper.find(MenuItem).at(2).prop('onClick')({} as any);
250
+
251
+ expect(AzureSecurityGroupModal.show).toHaveBeenCalledWith(jasmine.objectContaining({ mode: 'edit' }));
252
+ expect(AzureSecurityGroupModal.show).toHaveBeenCalledWith(jasmine.objectContaining({ mode: 'clone' }));
253
+ expect(ConfirmationModalService.confirm).toHaveBeenCalled();
254
+ const confirmArgs = (ConfirmationModalService.confirm as jasmine.Spy).calls.mostRecent().args[0];
255
+ confirmArgs.submitMethod();
256
+ expect(AzureSecurityGroupWriter.deleteSecurityGroup).toHaveBeenCalledWith(
257
+ securityGroup(),
258
+ jasmine.anything(),
259
+ jasmine.objectContaining({ cloudProvider: 'azure', vpcId: 'vnet-1' }),
260
+ );
261
+ });
262
+
263
+ it('uses resolved coordinates when deleting fetched details without account identity', () => {
264
+ spyOn(ConfirmationModalService, 'confirm');
265
+ spyOn(AzureSecurityGroupWriter, 'deleteSecurityGroup').and.returnValue(Promise.resolve({} as any));
266
+ const fetchedDetails = {
267
+ name: 'fnord-sg',
268
+ region: 'westus',
269
+ securityRules: [],
270
+ vpcId: 'vnet-1',
271
+ } as any;
272
+ const wrapper = shallow(
273
+ React.createElement(AzureSecurityGroupActions as any, {
274
+ app: app(),
275
+ resolvedSecurityGroup,
276
+ securityGroup: fetchedDetails,
277
+ }),
278
+ );
279
+
280
+ wrapper.find(MenuItem).at(2).prop('onClick')({} as any);
281
+
282
+ const confirmArgs = (ConfirmationModalService.confirm as jasmine.Spy).calls.mostRecent().args[0];
283
+ expect(confirmArgs.account).toBe('test-account');
284
+ confirmArgs.submitMethod();
285
+ expect(AzureSecurityGroupWriter.deleteSecurityGroup).toHaveBeenCalledWith(
286
+ jasmine.objectContaining({ accountId: 'test-account', name: 'fnord-sg', region: 'westus' }),
287
+ jasmine.anything(),
288
+ jasmine.objectContaining({ cloudProvider: 'azure', vpcId: 'vnet-1' }),
289
+ );
290
+ });
291
+ });
@@ -0,0 +1,354 @@
1
+ import React from 'react';
2
+ import { Dropdown, MenuItem } from 'react-bootstrap';
3
+
4
+ import type {
5
+ Application,
6
+ IOverridableProps,
7
+ IRouterInjectedProps,
8
+ ISecurityGroupDetail,
9
+ SecurityGroupReader,
10
+ } from '@spinnaker/core';
11
+ import {
12
+ AccountTag,
13
+ CollapsibleSection,
14
+ ConfirmationModalService,
15
+ DeckRuntimeContext,
16
+ withRouter,
17
+ } from '@spinnaker/core';
18
+
19
+ import { AzureSecurityGroupModal } from '../configure/AzureSecurityGroupModal';
20
+ import { AzureSecurityGroupWriter } from '../securityGroup.write.service';
21
+
22
+ interface IAzureResolvedSecurityGroup {
23
+ accountId?: string;
24
+ name: string;
25
+ provider?: string;
26
+ region: string;
27
+ vpcId?: string;
28
+ }
29
+
30
+ interface IAzureSecurityGroupDetailsProps extends IOverridableProps {
31
+ app: Application;
32
+ autoClose?: () => void;
33
+ resolvedSecurityGroup: IAzureResolvedSecurityGroup;
34
+ securityGroupReader?: SecurityGroupReader;
35
+ }
36
+
37
+ interface IAzureSecurityGroupDetailsState {
38
+ loading: boolean;
39
+ securityGroup?: ISecurityGroupDetail & Record<string, any>;
40
+ }
41
+
42
+ interface IAzureSecurityGroupSectionProps {
43
+ securityGroup: ISecurityGroupDetail & Record<string, any>;
44
+ }
45
+
46
+ export class AzureSecurityGroupDetailsComponent extends React.Component<
47
+ IAzureSecurityGroupDetailsProps & IRouterInjectedProps,
48
+ IAzureSecurityGroupDetailsState
49
+ > {
50
+ public static contextType = DeckRuntimeContext;
51
+ public declare context: React.ContextType<typeof DeckRuntimeContext>;
52
+
53
+ public state: IAzureSecurityGroupDetailsState = { 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 componentWillUnmount(): void {
76
+ this.isUnmounted = true;
77
+ if (this.unsubscribeFromRefresh) {
78
+ this.unsubscribeFromRefresh();
79
+ }
80
+ }
81
+
82
+ public componentDidUpdate(prevProps: IAzureSecurityGroupDetailsProps): void {
83
+ if (this.getCoordinatesKey(prevProps) !== this.getCoordinatesKey(this.props)) {
84
+ this.loadSecurityGroup();
85
+ }
86
+ }
87
+
88
+ private getCoordinatesKey(props: IAzureSecurityGroupDetailsProps): string {
89
+ const { accountId, name, region, vpcId } = props.resolvedSecurityGroup;
90
+ return [accountId, region, vpcId || '', name].join(':');
91
+ }
92
+
93
+ private getSecurityGroupReader(): SecurityGroupReader {
94
+ return this.props.securityGroupReader || this.context.services.securityGroupReader;
95
+ }
96
+
97
+ private loadSecurityGroup = (): void => {
98
+ const { app, resolvedSecurityGroup } = this.props;
99
+ const requestId = ++this.loadRequestId;
100
+ this.setState({ loading: true });
101
+ this.getSecurityGroupReader()
102
+ .getSecurityGroupDetails(
103
+ app,
104
+ resolvedSecurityGroup.accountId,
105
+ 'azure',
106
+ resolvedSecurityGroup.region,
107
+ resolvedSecurityGroup.vpcId || '',
108
+ resolvedSecurityGroup.name,
109
+ )
110
+ .then((securityGroup: ISecurityGroupDetail & Record<string, any>) => {
111
+ if (this.isUnmounted || requestId !== this.loadRequestId) {
112
+ return;
113
+ }
114
+ if (!securityGroup || Object.keys(securityGroup).length === 0) {
115
+ this.autoClose();
116
+ return;
117
+ }
118
+ this.setState({ loading: false, securityGroup });
119
+ })
120
+ .catch(() => {
121
+ if (requestId === this.loadRequestId) {
122
+ this.autoClose();
123
+ }
124
+ });
125
+ };
126
+
127
+ private autoClose = (): void => {
128
+ if (this.isUnmounted) {
129
+ return;
130
+ }
131
+ if (this.props.autoClose) {
132
+ this.props.autoClose();
133
+ return;
134
+ }
135
+ this.props.stateService.go('^');
136
+ };
137
+
138
+ private closeDetails = (): void => {
139
+ this.props.stateService.go('^');
140
+ };
141
+
142
+ public render(): JSX.Element {
143
+ const { app } = this.props;
144
+ const { loading, securityGroup } = this.state;
145
+
146
+ if (loading || !securityGroup) {
147
+ return (
148
+ <div className="details-panel">
149
+ <div className="header">
150
+ <div className="close-button">
151
+ <a className="btn btn-link" onClick={this.closeDetails}>
152
+ <span className="glyphicon glyphicon-remove" />
153
+ </a>
154
+ </div>
155
+ <h4 className="text-center">Loading...</h4>
156
+ </div>
157
+ </div>
158
+ );
159
+ }
160
+
161
+ return (
162
+ <div className="details-panel">
163
+ <div className="header">
164
+ <div className="close-button">
165
+ <a className="btn btn-link" onClick={this.closeDetails}>
166
+ <span className="glyphicon glyphicon-remove" />
167
+ </a>
168
+ </div>
169
+ <div className="header-text horizontal middle">
170
+ <h3>{securityGroup.name}</h3>
171
+ </div>
172
+ <div className="actions">
173
+ <AzureSecurityGroupActions
174
+ app={app}
175
+ resolvedSecurityGroup={this.props.resolvedSecurityGroup}
176
+ securityGroup={securityGroup}
177
+ />
178
+ </div>
179
+ </div>
180
+ <div className="content">
181
+ <AzureSecurityGroupInformationSection securityGroup={securityGroup} />
182
+ <AzureSecurityGroupRulesSection securityGroup={securityGroup} />
183
+ </div>
184
+ </div>
185
+ );
186
+ }
187
+ }
188
+
189
+ export const AzureSecurityGroupDetails = withRouter(AzureSecurityGroupDetailsComponent);
190
+
191
+ function withResolvedCoordinates(securityGroup: any, resolvedSecurityGroup?: IAzureResolvedSecurityGroup): any {
192
+ if (!resolvedSecurityGroup) {
193
+ return securityGroup;
194
+ }
195
+
196
+ const accountId =
197
+ securityGroup.accountId ||
198
+ securityGroup.account ||
199
+ securityGroup.accountName ||
200
+ securityGroup.credentials ||
201
+ resolvedSecurityGroup.accountId;
202
+ const name = securityGroup.name || resolvedSecurityGroup.name;
203
+ const region = securityGroup.region || resolvedSecurityGroup.region;
204
+ const vpcId = securityGroup.vpcId || resolvedSecurityGroup.vpcId;
205
+
206
+ if (
207
+ accountId === securityGroup.accountId &&
208
+ name === securityGroup.name &&
209
+ region === securityGroup.region &&
210
+ vpcId === securityGroup.vpcId
211
+ ) {
212
+ return securityGroup;
213
+ }
214
+
215
+ return { ...securityGroup, accountId, name, region, vpcId };
216
+ }
217
+
218
+ export function AzureSecurityGroupActions({
219
+ app,
220
+ resolvedSecurityGroup,
221
+ securityGroup,
222
+ }: {
223
+ app: Application;
224
+ resolvedSecurityGroup?: IAzureResolvedSecurityGroup;
225
+ securityGroup: any;
226
+ }) {
227
+ const securityGroupWithCoordinates = withResolvedCoordinates(securityGroup, resolvedSecurityGroup);
228
+ const editSecurityGroup = (): void => {
229
+ AzureSecurityGroupModal.show({ app, mode: 'edit', securityGroup: securityGroupWithCoordinates });
230
+ };
231
+ const cloneSecurityGroup = (): void => {
232
+ AzureSecurityGroupModal.show({ app, mode: 'clone', securityGroup: securityGroupWithCoordinates });
233
+ };
234
+ const deleteSecurityGroup = (): void => {
235
+ ConfirmationModalService.confirm({
236
+ header: `Really delete ${securityGroupWithCoordinates.name}?`,
237
+ buttonText: `Delete ${securityGroupWithCoordinates.name}`,
238
+ account: securityGroupWithCoordinates.accountId || securityGroupWithCoordinates.account,
239
+ taskMonitorConfig: {
240
+ application: app,
241
+ title: `Deleting ${securityGroupWithCoordinates.name}`,
242
+ },
243
+ submitMethod: () =>
244
+ AzureSecurityGroupWriter.deleteSecurityGroup(securityGroupWithCoordinates, app, {
245
+ cloudProvider: 'azure',
246
+ vpcId: securityGroupWithCoordinates.vpcId,
247
+ }),
248
+ });
249
+ };
250
+
251
+ return (
252
+ <Dropdown className="dropdown" id="azure-security-group-actions-dropdown">
253
+ <Dropdown.Toggle className="btn btn-sm btn-primary dropdown-toggle">Security Group Actions</Dropdown.Toggle>
254
+ <Dropdown.Menu>
255
+ <MenuItem onClick={editSecurityGroup}>Edit Inbound Rules</MenuItem>
256
+ <MenuItem onClick={cloneSecurityGroup}>Clone Security Group</MenuItem>
257
+ <MenuItem onClick={deleteSecurityGroup}>Delete Security Group</MenuItem>
258
+ </Dropdown.Menu>
259
+ </Dropdown>
260
+ );
261
+ }
262
+
263
+ export function AzureSecurityGroupInformationSection({ securityGroup }: IAzureSecurityGroupSectionProps) {
264
+ return (
265
+ <CollapsibleSection heading="Information" defaultExpanded={true}>
266
+ <dl className="dl-horizontal dl-narrow">
267
+ <dt>ID</dt>
268
+ <dd>{securityGroup.id || '-'}</dd>
269
+ <dt>Account</dt>
270
+ <dd>
271
+ <AccountTag account={securityGroup.accountId || securityGroup.accountName || securityGroup.account} />
272
+ </dd>
273
+ <dt>Region</dt>
274
+ <dd>{securityGroup.region}</dd>
275
+ <dt>Description:</dt>
276
+ <dd>{securityGroup.description || '-'}</dd>
277
+ <dt>VNet</dt>
278
+ <dd>{securityGroup.vpcId || '-'}</dd>
279
+ </dl>
280
+ </CollapsibleSection>
281
+ );
282
+ }
283
+
284
+ function joinRuleValues(...values: any[]): string {
285
+ for (const value of values) {
286
+ if (Array.isArray(value) && value.length) {
287
+ return value.join(', ');
288
+ }
289
+ if (typeof value === 'string' && value.trim()) {
290
+ return value.trim();
291
+ }
292
+ }
293
+ return '-';
294
+ }
295
+
296
+ function ruleSources(rule: any): string {
297
+ return joinRuleValues(
298
+ rule.sourceAddressPrefixModel,
299
+ rule.sourceAddressPrefixes,
300
+ rule.sourceAddressPrefix,
301
+ rule.sourceIPCIDRRanges,
302
+ rule.sourceCidrs,
303
+ );
304
+ }
305
+
306
+ function rulePorts(rule: any): string {
307
+ return joinRuleValues(
308
+ rule.destinationPortRangeModel,
309
+ rule.destinationPortRanges,
310
+ rule.destinationPortRange,
311
+ rule.destPortRanges,
312
+ rule.destinationPorts,
313
+ );
314
+ }
315
+
316
+ export function AzureSecurityGroupRulesSection({ securityGroup }: IAzureSecurityGroupSectionProps) {
317
+ const rules = (securityGroup.securityRules || securityGroup.inboundRules || [])
318
+ .slice()
319
+ .sort((a: any, b: any) => (a.priority ?? Number.MAX_SAFE_INTEGER) - (b.priority ?? Number.MAX_SAFE_INTEGER));
320
+ return (
321
+ <CollapsibleSection heading="Inbound Rules" defaultExpanded={true}>
322
+ <table className="table table-condensed">
323
+ <thead>
324
+ <tr>
325
+ <th>Priority</th>
326
+ <th>Name</th>
327
+ <th>Source</th>
328
+ <th>Source Port</th>
329
+ <th>Destination</th>
330
+ <th>Ports</th>
331
+ <th>Protocol</th>
332
+ <th>Access</th>
333
+ <th>Direction</th>
334
+ </tr>
335
+ </thead>
336
+ <tbody>
337
+ {rules.map((rule: any, index: number) => (
338
+ <tr key={rule.name || index}>
339
+ <td>{rule.priority || '-'}</td>
340
+ <td>{rule.name || '-'}</td>
341
+ <td>{ruleSources(rule)}</td>
342
+ <td>{rule.sourcePortRange || '-'}</td>
343
+ <td>{rule.destinationAddressPrefix || '-'}</td>
344
+ <td>{rulePorts(rule)}</td>
345
+ <td>{rule.protocol || '-'}</td>
346
+ <td>{rule.access || '-'}</td>
347
+ <td>{rule.direction || '-'}</td>
348
+ </tr>
349
+ ))}
350
+ </tbody>
351
+ </table>
352
+ </CollapsibleSection>
353
+ );
354
+ }
@@ -1,17 +1,7 @@
1
- 'use strict';
2
-
3
- import { module } from 'angular';
4
-
5
- export const AZURE_SECURITYGROUP_SECURITYGROUP_READER = 'spinnaker.azure.securityGroup.reader';
6
- export const name = AZURE_SECURITYGROUP_SECURITYGROUP_READER; // for backwards compatibility
7
- module(AZURE_SECURITYGROUP_SECURITYGROUP_READER, []).factory('azureSecurityGroupReader', function () {
8
- function resolveIndexedSecurityGroup(indexedSecurityGroups, container, securityGroupId) {
1
+ export class AzureSecurityGroupReader {
2
+ resolveIndexedSecurityGroup(indexedSecurityGroups, container, securityGroupId) {
9
3
  //hack to get around securityGroupId not matching id in indexedSecurityGroups.
10
4
  const temp = securityGroupId.split('/');
11
5
  return indexedSecurityGroups[container.account][container.region][temp[temp.length - 1]];
12
6
  }
13
-
14
- return {
15
- resolveIndexedSecurityGroup: resolveIndexedSecurityGroup,
16
- };
17
- });
7
+ }
@@ -1,13 +1,3 @@
1
- 'use strict';
2
-
3
- import { module } from 'angular';
4
-
5
- export const AZURE_SECURITYGROUP_SECURITYGROUP_TRANSFORMER = 'spinnaker.azure.securityGroup.transformer';
6
- export const name = AZURE_SECURITYGROUP_SECURITYGROUP_TRANSFORMER; // for backwards compatibility
7
- module(AZURE_SECURITYGROUP_SECURITYGROUP_TRANSFORMER, []).factory('azureSecurityGroupTransformer', function () {
8
- function normalizeSecurityGroup() {}
9
-
10
- return {
11
- normalizeSecurityGroup: normalizeSecurityGroup,
12
- };
13
- });
1
+ export class AzureSecurityGroupTransformer {
2
+ normalizeSecurityGroup() {}
3
+ }