@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,723 @@
1
+ import React from 'react';
2
+
3
+ import type { IExecutionDetailsSectionProps } from '@spinnaker/core';
4
+ import {
5
+ AccountService,
6
+ AuthenticationService,
7
+ BakeExecutionLabel,
8
+ BakeryReader,
9
+ ExecutionDetailsSection,
10
+ ExecutionDetailsTasks,
11
+ Registry,
12
+ SETTINGS,
13
+ Spinner,
14
+ StageConfigField,
15
+ StageFailureMessage,
16
+ } from '@spinnaker/core';
17
+
18
+ import { AzureImageReader } from '../../../image/image.reader';
19
+
20
+ const osTypeOptions = ['linux', 'windows'];
21
+ const packageTypeOptions = ['DEB', 'RPM'];
22
+
23
+ interface IAzureBakeStageState {
24
+ accounts: string[];
25
+ baseLabelOptions: string[];
26
+ baseOsOptions: any[];
27
+ extendedAttributeKey: string;
28
+ extendedAttributeValue: string;
29
+ loading: boolean;
30
+ loadError: boolean;
31
+ managedImageOptions: Array<{ id: string; name: string; osType: string }>;
32
+ regions: string[];
33
+ roscoMode: boolean;
34
+ showAdvancedOptions: boolean;
35
+ sourceImageMode: 'default' | 'managed' | 'custom';
36
+ }
37
+
38
+ function deleteEmptyProperties(stage: any): any {
39
+ return Object.keys(stage).reduce((acc, key) => {
40
+ if (stage[key] !== '') {
41
+ acc[key] = stage[key];
42
+ }
43
+ return acc;
44
+ }, {} as any);
45
+ }
46
+
47
+ function baseOsDescription(baseOsOption: any): string {
48
+ const baseOsName = baseOsOption?.displayName || baseOsOption?.id || '';
49
+ return baseOsOption?.shortDescription ? `${baseOsName} (${baseOsOption.shortDescription})` : baseOsName;
50
+ }
51
+
52
+ function sourceImageMode(stage: any): 'default' | 'managed' | 'custom' {
53
+ if (stage.managedImage != null) {
54
+ return 'managed';
55
+ }
56
+ if (stage.publisher != null) {
57
+ return 'custom';
58
+ }
59
+ return 'default';
60
+ }
61
+
62
+ export class AzureBakeStageConfig extends React.Component<any, IAzureBakeStageState> {
63
+ private azureImageReader = new AzureImageReader();
64
+ private mounted = false;
65
+
66
+ public state: IAzureBakeStageState = {
67
+ accounts: [],
68
+ baseLabelOptions: [],
69
+ baseOsOptions: [],
70
+ extendedAttributeKey: '',
71
+ extendedAttributeValue: '',
72
+ loading: true,
73
+ loadError: false,
74
+ managedImageOptions: [],
75
+ regions: [],
76
+ roscoMode: false,
77
+ showAdvancedOptions: false,
78
+ sourceImageMode: sourceImageMode(this.props.stage),
79
+ };
80
+
81
+ public componentDidMount(): void {
82
+ this.mounted = true;
83
+ this.initialize();
84
+ }
85
+
86
+ public componentWillUnmount(): void {
87
+ this.mounted = false;
88
+ }
89
+
90
+ private initialize(): void {
91
+ const stage = this.props.stage;
92
+ stage.extendedAttributes = stage.extendedAttributes || {};
93
+ stage.regions = stage.regions || [];
94
+
95
+ Promise.all([
96
+ AccountService.getCredentialsKeyedByAccount('azure'),
97
+ BakeryReader.getRegions('azure'),
98
+ BakeryReader.getBaseOsOptions('azure'),
99
+ BakeryReader.getBaseLabelOptions(),
100
+ ])
101
+ .then(([credentialsKeyedByAccount, regions, baseOsOptions, baseLabelOptions]) => {
102
+ if (!this.mounted) {
103
+ return;
104
+ }
105
+
106
+ const nextStage = this.applyDefaults(
107
+ stage,
108
+ regions as string[],
109
+ baseOsOptions.baseImages || [],
110
+ baseLabelOptions,
111
+ );
112
+ this.replaceStage(nextStage);
113
+
114
+ const nextState = {
115
+ accounts: Object.keys(credentialsKeyedByAccount),
116
+ baseLabelOptions,
117
+ baseOsOptions: baseOsOptions.baseImages || [],
118
+ loading: false,
119
+ loadError: false,
120
+ regions: regions as string[],
121
+ roscoMode: this.roscoMode(nextStage),
122
+ showAdvancedOptions: this.showAdvanced(nextStage),
123
+ sourceImageMode: sourceImageMode(nextStage),
124
+ };
125
+ this.setState(nextState);
126
+
127
+ if (nextState.sourceImageMode === 'managed') {
128
+ this.loadManagedImages(nextStage.account);
129
+ }
130
+ })
131
+ .catch(() => {
132
+ if (this.mounted) {
133
+ this.setState({ loading: false, loadError: true });
134
+ }
135
+ });
136
+ }
137
+
138
+ private applyDefaults(stage: any, regions: string[], baseOsOptions: any[], baseLabelOptions: string[]): any {
139
+ const nextStage = deleteEmptyProperties({ ...stage });
140
+ nextStage.extendedAttributes = nextStage.extendedAttributes || {};
141
+ nextStage.regions = nextStage.regions || [];
142
+
143
+ if (!nextStage.user) {
144
+ nextStage.user = AuthenticationService.getAuthenticatedUser()?.name;
145
+ }
146
+ if (regions.length === 1) {
147
+ nextStage.region = regions[0];
148
+ } else if (nextStage.region && !regions.includes(nextStage.region)) {
149
+ delete nextStage.region;
150
+ }
151
+ if (!nextStage.regions.length && this.props.application.defaultRegions.azure) {
152
+ nextStage.regions = [this.props.application.defaultRegions.azure];
153
+ }
154
+ if (!nextStage.baseLabel && baseLabelOptions.length) {
155
+ nextStage.baseLabel = baseLabelOptions[0];
156
+ }
157
+ if (!nextStage.baseOs && baseOsOptions.length && sourceImageMode(nextStage) === 'default') {
158
+ nextStage.baseOs = baseOsOptions[0].id;
159
+ }
160
+
161
+ return nextStage;
162
+ }
163
+
164
+ private replaceStage(nextStage: any): void {
165
+ Object.keys(this.props.stage).forEach((key) => {
166
+ if (!Object.prototype.hasOwnProperty.call(nextStage, key)) {
167
+ delete this.props.stage[key];
168
+ }
169
+ });
170
+ Object.assign(this.props.stage, nextStage);
171
+ if (this.props.updateStage) {
172
+ this.props.updateStage(nextStage);
173
+ } else if (this.props.updateStageField) {
174
+ this.props.updateStageField(nextStage);
175
+ }
176
+ }
177
+
178
+ private updateStage(changes: any): void {
179
+ const nextStage = deleteEmptyProperties({ ...this.props.stage, ...changes });
180
+ this.replaceStage(nextStage);
181
+ if (typeof SETTINGS.feature.roscoSelector === 'function') {
182
+ this.setState({ roscoMode: this.roscoMode(nextStage) });
183
+ }
184
+ }
185
+
186
+ private updateExtendedAttribute(key: string, value: string): void {
187
+ this.updateStage({ extendedAttributes: { ...(this.props.stage.extendedAttributes || {}), [key]: value } });
188
+ }
189
+
190
+ private removeExtendedAttribute(key: string): void {
191
+ const extendedAttributes = { ...(this.props.stage.extendedAttributes || {}) };
192
+ delete extendedAttributes[key];
193
+ this.updateStage({ extendedAttributes });
194
+ }
195
+
196
+ private addExtendedAttribute(): void {
197
+ const key = this.state.extendedAttributeKey.trim();
198
+ if (!key) {
199
+ return;
200
+ }
201
+
202
+ this.updateExtendedAttribute(key, this.state.extendedAttributeValue);
203
+ this.setState({ extendedAttributeKey: '', extendedAttributeValue: '', showAdvancedOptions: true });
204
+ }
205
+
206
+ private roscoMode(stage: any): boolean {
207
+ return (
208
+ SETTINGS.feature.roscoMode ||
209
+ (typeof SETTINGS.feature.roscoSelector === 'function' && SETTINGS.feature.roscoSelector(stage))
210
+ );
211
+ }
212
+
213
+ private showAdvanced(stage: any): boolean {
214
+ return !!(
215
+ stage.templateFileName ||
216
+ (stage.extendedAttributes && Object.keys(stage.extendedAttributes).length > 0) ||
217
+ stage.varFileName
218
+ );
219
+ }
220
+
221
+ private loadManagedImages(account: string): void {
222
+ this.azureImageReader
223
+ .findImages({ provider: 'azure', managedImages: true, account })
224
+ .then((images: any[]) => {
225
+ if (!this.mounted || this.props.stage.account !== account) {
226
+ return;
227
+ }
228
+ this.setState({
229
+ managedImageOptions: images.map((image) => ({
230
+ id: image.imageName,
231
+ name: image.imageName,
232
+ osType: image.ostype,
233
+ })),
234
+ });
235
+ })
236
+ .catch(() => {});
237
+ }
238
+
239
+ private loadAccountRegions(account: string): void {
240
+ AccountService.getRegionsForAccount(account)
241
+ .then((regions: any[]) => {
242
+ if (this.mounted && this.props.stage.account === account) {
243
+ this.setState({ regions: regions.map((region) => region.name) });
244
+ }
245
+ })
246
+ .catch(() => {});
247
+ }
248
+
249
+ private setSourceImageMode(sourceImageModeValue: 'default' | 'managed' | 'custom'): void {
250
+ this.setState({ sourceImageMode: sourceImageModeValue });
251
+
252
+ if (sourceImageModeValue === 'default') {
253
+ this.updateStage({
254
+ managedImage: null,
255
+ offer: null,
256
+ osType: null,
257
+ packageType: null,
258
+ publisher: null,
259
+ sku: null,
260
+ });
261
+ return;
262
+ }
263
+
264
+ if (sourceImageModeValue === 'managed') {
265
+ this.setState({ managedImageOptions: [] });
266
+ this.loadManagedImages(this.props.stage.account);
267
+ this.updateStage({ baseOs: null, offer: null, osType: null, packageType: null, publisher: null, sku: null });
268
+ return;
269
+ }
270
+
271
+ this.updateStage({ baseOs: null, managedImage: null, osType: null, packageType: null });
272
+ }
273
+
274
+ private onAccountChange(account: string): void {
275
+ this.updateStage({ account, managedImage: null, osType: null, packageType: null });
276
+ if (account) {
277
+ this.loadAccountRegions(account);
278
+ }
279
+ if (this.state.sourceImageMode === 'managed') {
280
+ this.setState({ managedImageOptions: [] });
281
+ this.loadManagedImages(account);
282
+ }
283
+ }
284
+
285
+ private onManagedImageChange(managedImage: string): void {
286
+ const selectedManagedImage = this.state.managedImageOptions.find((option) => option.id === managedImage);
287
+ this.updateStage({
288
+ managedImage,
289
+ osType: selectedManagedImage?.osType?.toLowerCase(),
290
+ packageType: null,
291
+ });
292
+ }
293
+
294
+ private renderSourceImageFields(): React.ReactNode {
295
+ const stage = this.props.stage;
296
+
297
+ if (this.state.sourceImageMode === 'managed') {
298
+ return (
299
+ <StageConfigField label="Managed Image">
300
+ <select
301
+ className="form-control input-sm"
302
+ name="managedImage"
303
+ value={stage.managedImage || ''}
304
+ onChange={(event) => this.onManagedImageChange(event.target.value)}
305
+ >
306
+ <option value="" />
307
+ {this.state.managedImageOptions.map((image) => (
308
+ <option key={image.id} value={image.id}>
309
+ {image.name}
310
+ </option>
311
+ ))}
312
+ </select>
313
+ </StageConfigField>
314
+ );
315
+ }
316
+
317
+ if (this.state.sourceImageMode === 'custom') {
318
+ return (
319
+ <>
320
+ <StageConfigField label="Publisher">
321
+ <input
322
+ className="form-control input-sm"
323
+ value={stage.publisher || ''}
324
+ onChange={(event) => this.updateStage({ publisher: event.target.value })}
325
+ />
326
+ </StageConfigField>
327
+ <StageConfigField label="Offer">
328
+ <input
329
+ className="form-control input-sm"
330
+ value={stage.offer || ''}
331
+ onChange={(event) => this.updateStage({ offer: event.target.value })}
332
+ />
333
+ </StageConfigField>
334
+ <StageConfigField label="SKU">
335
+ <input
336
+ className="form-control input-sm"
337
+ value={stage.sku || ''}
338
+ onChange={(event) => this.updateStage({ sku: event.target.value })}
339
+ />
340
+ </StageConfigField>
341
+ <StageConfigField label="OS Type">
342
+ {osTypeOptions.map((osType) => (
343
+ <label className="radio-inline" key={osType}>
344
+ <input
345
+ checked={stage.osType === osType}
346
+ name="osType"
347
+ type="radio"
348
+ value={osType}
349
+ onChange={() => this.updateStage({ osType, packageType: null })}
350
+ />
351
+ {osType}
352
+ </label>
353
+ ))}
354
+ </StageConfigField>
355
+ </>
356
+ );
357
+ }
358
+
359
+ return (
360
+ <StageConfigField label="Base OS">
361
+ <select
362
+ className="form-control input-sm"
363
+ value={stage.baseOs || ''}
364
+ onChange={(event) => this.updateStage({ baseOs: event.target.value })}
365
+ >
366
+ {this.state.baseOsOptions.map((baseOsOption) => (
367
+ <option key={baseOsOption.id} value={baseOsOption.id}>
368
+ {baseOsDescription(baseOsOption)}
369
+ </option>
370
+ ))}
371
+ </select>
372
+ </StageConfigField>
373
+ );
374
+ }
375
+
376
+ public render() {
377
+ if (this.state.loading) {
378
+ return <Spinner />;
379
+ }
380
+ if (this.state.loadError) {
381
+ return <div className="alert alert-danger">Unable to load Azure bake options.</div>;
382
+ }
383
+
384
+ const stage = this.props.stage;
385
+ const extendedAttributes = stage.extendedAttributes || {};
386
+ const showAdvancedFields = this.state.showAdvancedOptions || this.state.roscoMode;
387
+
388
+ return (
389
+ <div className="form-horizontal">
390
+ <StageConfigField label="Account">
391
+ <select
392
+ className="form-control input-sm"
393
+ name="account"
394
+ value={stage.account || ''}
395
+ onChange={(event) => this.onAccountChange(event.target.value)}
396
+ >
397
+ <option value="" />
398
+ {this.state.accounts.map((account) => (
399
+ <option key={account} value={account}>
400
+ {account}
401
+ </option>
402
+ ))}
403
+ </select>
404
+ </StageConfigField>
405
+ <StageConfigField label="Regions">
406
+ {this.state.regions.map((region) => (
407
+ <label className="checkbox-inline" key={region}>
408
+ <input
409
+ checked={(stage.regions || []).includes(region)}
410
+ type="checkbox"
411
+ onChange={(event) => {
412
+ const regions = new Set(stage.regions || []);
413
+ event.target.checked ? regions.add(region) : regions.delete(region);
414
+ const scalarRegion = event.target.checked
415
+ ? region
416
+ : stage.region === region
417
+ ? undefined
418
+ : stage.region;
419
+ this.updateStage({ region: scalarRegion, regions: Array.from(regions) });
420
+ }}
421
+ />
422
+ {region}
423
+ </label>
424
+ ))}
425
+ </StageConfigField>
426
+ <div className="panel panel-default">
427
+ <div className="panel-heading">
428
+ <label className="col-md-3 sm-label-right" />
429
+ <div className="btn-group btn-group-xs" role="group" aria-label="Source Image">
430
+ <button
431
+ className={`btn btn-default ${this.state.sourceImageMode === 'default' ? 'active' : ''}`}
432
+ onClick={() => this.setSourceImageMode('default')}
433
+ type="button"
434
+ >
435
+ Default Images
436
+ </button>
437
+ <button
438
+ className={`btn btn-default ${this.state.sourceImageMode === 'managed' ? 'active' : ''}`}
439
+ onClick={() => this.setSourceImageMode('managed')}
440
+ type="button"
441
+ >
442
+ Managed Images
443
+ </button>
444
+ <button
445
+ className={`btn btn-default ${this.state.sourceImageMode === 'custom' ? 'active' : ''}`}
446
+ onClick={() => this.setSourceImageMode('custom')}
447
+ type="button"
448
+ >
449
+ Custom Image
450
+ </button>
451
+ </div>
452
+ </div>
453
+ <div className="panel-body">
454
+ {this.renderSourceImageFields()}
455
+ {stage.osType === 'linux' && (
456
+ <StageConfigField label="Package Type">
457
+ {packageTypeOptions.map((packageType) => (
458
+ <label className="radio-inline" key={packageType}>
459
+ <input
460
+ checked={stage.packageType === packageType}
461
+ name="packageType"
462
+ type="radio"
463
+ value={packageType}
464
+ onChange={() => this.updateStage({ packageType })}
465
+ />
466
+ {packageType}
467
+ </label>
468
+ ))}
469
+ </StageConfigField>
470
+ )}
471
+ </div>
472
+ </div>
473
+ <StageConfigField label="Package" helpKey="pipeline.config.bake.package">
474
+ <input
475
+ className="form-control input-sm"
476
+ value={stage.package || ''}
477
+ onChange={(event) => this.updateStage({ package: event.target.value })}
478
+ />
479
+ </StageConfigField>
480
+ <StageConfigField label="Base Label">
481
+ {this.state.baseLabelOptions.map((baseLabel) => (
482
+ <label className="radio-inline" key={baseLabel}>
483
+ <input
484
+ checked={stage.baseLabel === baseLabel}
485
+ name="baseLabel"
486
+ type="radio"
487
+ value={baseLabel}
488
+ onChange={() => this.updateStage({ baseLabel })}
489
+ />
490
+ {baseLabel}
491
+ </label>
492
+ ))}
493
+ </StageConfigField>
494
+ {(this.state.roscoMode || stage.rebake) && (
495
+ <StageConfigField label="Rebake">
496
+ <div className="checkbox" style={{ marginBottom: 0 }}>
497
+ <label>
498
+ <input
499
+ checked={!!stage.rebake}
500
+ type="checkbox"
501
+ onChange={(event) => this.updateStage({ rebake: event.target.checked })}
502
+ />
503
+ Rebake image without regard to the status of any existing bake
504
+ </label>
505
+ </div>
506
+ </StageConfigField>
507
+ )}
508
+ <StageConfigField label="Base Name">
509
+ <input
510
+ className="form-control input-sm"
511
+ value={stage.baseName || ''}
512
+ onChange={(event) => this.updateStage({ baseName: event.target.value })}
513
+ />
514
+ </StageConfigField>
515
+ <div className="form-group">
516
+ <div className="col-md-9 col-md-offset-1">
517
+ <div className="checkbox">
518
+ <label>
519
+ <input
520
+ checked={this.state.showAdvancedOptions}
521
+ type="checkbox"
522
+ onChange={(event) => this.setState({ showAdvancedOptions: event.target.checked })}
523
+ />
524
+ <strong>Show Advanced Options</strong>
525
+ </label>
526
+ </div>
527
+ </div>
528
+ </div>
529
+ {showAdvancedFields && (
530
+ <>
531
+ <StageConfigField label="Template File Name" helpKey="pipeline.config.bake.templateFileName">
532
+ <input
533
+ className="form-control input-sm"
534
+ value={stage.templateFileName || ''}
535
+ onChange={(event) => this.updateStage({ templateFileName: event.target.value })}
536
+ />
537
+ </StageConfigField>
538
+ <StageConfigField label="Extended Attributes" helpKey="pipeline.config.bake.extendedAttributes">
539
+ <table className="table table-condensed packed">
540
+ <tbody>
541
+ {Object.keys(extendedAttributes).map((key) => (
542
+ <tr key={key}>
543
+ <td>
544
+ <strong className="small">{key}</strong>
545
+ </td>
546
+ <td>
547
+ <input
548
+ className="form-control input-sm"
549
+ value={extendedAttributes[key]}
550
+ onChange={(event) => this.updateExtendedAttribute(key, event.target.value)}
551
+ />
552
+ </td>
553
+ <td className="text-right">
554
+ <button
555
+ className="btn btn-link btn-sm"
556
+ onClick={() => this.removeExtendedAttribute(key)}
557
+ type="button"
558
+ >
559
+ Remove
560
+ </button>
561
+ </td>
562
+ </tr>
563
+ ))}
564
+ <tr>
565
+ <td>
566
+ <input
567
+ className="form-control input-sm"
568
+ placeholder="Key"
569
+ value={this.state.extendedAttributeKey}
570
+ onChange={(event) => this.setState({ extendedAttributeKey: event.target.value })}
571
+ />
572
+ </td>
573
+ <td>
574
+ <input
575
+ className="form-control input-sm"
576
+ placeholder="Value"
577
+ value={this.state.extendedAttributeValue}
578
+ onChange={(event) => this.setState({ extendedAttributeValue: event.target.value })}
579
+ />
580
+ </td>
581
+ <td className="text-right">
582
+ <button
583
+ className="btn btn-block btn-sm add-new"
584
+ onClick={() => this.addExtendedAttribute()}
585
+ type="button"
586
+ >
587
+ Add Extended Attribute
588
+ </button>
589
+ </td>
590
+ </tr>
591
+ </tbody>
592
+ </table>
593
+ </StageConfigField>
594
+ <StageConfigField label="Var File Name" helpKey="pipeline.config.bake.varFileName">
595
+ <input
596
+ className="form-control input-sm"
597
+ value={stage.varFileName || ''}
598
+ onChange={(event) => this.updateStage({ varFileName: event.target.value })}
599
+ />
600
+ </StageConfigField>
601
+ </>
602
+ )}
603
+ </div>
604
+ );
605
+ }
606
+ }
607
+
608
+ function valueForPath(source: any, path: string): string {
609
+ const value = path.split('.').reduce((acc, key) => acc?.[key], source);
610
+ return value == null ? '' : String(value);
611
+ }
612
+
613
+ function interpolatedBakeDetailUrl(stage: any): string {
614
+ const context = stage.context || {};
615
+ const roscoMode =
616
+ SETTINGS.feature.roscoMode ||
617
+ (typeof SETTINGS.feature.roscoSelector === 'function' && SETTINGS.feature.roscoSelector(context));
618
+ const template = (roscoMode && SETTINGS.roscoDetailUrl ? SETTINGS.roscoDetailUrl : SETTINGS.bakeryDetailUrl) || '';
619
+ const source = { context };
620
+
621
+ return template.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_match: string, path: string) => valueForPath(source, path));
622
+ }
623
+
624
+ export function AzureBakeExecutionDetails({ current, execution, name, stage }: IExecutionDetailsSectionProps) {
625
+ const context = stage.context || {};
626
+ const resourceId = context.status?.resourceId;
627
+ const roscoMode =
628
+ SETTINGS.feature.roscoMode ||
629
+ (typeof SETTINGS.feature.roscoSelector === 'function' && SETTINGS.feature.roscoSelector(context));
630
+ const showRebake = roscoMode || execution?.trigger?.rebake || context.rebake;
631
+
632
+ return (
633
+ <ExecutionDetailsSection name={name} current={current}>
634
+ <div className="row">
635
+ <div className="col-md-6">
636
+ <dl className="dl-narrow dl-horizontal">
637
+ <dt>Provider</dt>
638
+ <dd>Azure</dd>
639
+ <dt>Image</dt>
640
+ <dd>{context.ami}</dd>
641
+ <dt>Region</dt>
642
+ <dd>{context.region}</dd>
643
+ <dt>Package</dt>
644
+ <dd>{context.package}</dd>
645
+ </dl>
646
+ </div>
647
+ <div className="col-md-6">
648
+ <dl className="dl-narrow dl-horizontal">
649
+ <dt>Base OS</dt>
650
+ <dd>{context.baseOs}</dd>
651
+ <dt>Label</dt>
652
+ <dd>{context.baseLabel}</dd>
653
+ {showRebake && (
654
+ <>
655
+ <dt>Rebake</dt>
656
+ <dd>{String(execution?.trigger?.rebake || context.rebake || false)}</dd>
657
+ </>
658
+ )}
659
+ {context.templateFileName && (
660
+ <>
661
+ <dt>Template</dt>
662
+ <dd>{context.templateFileName}</dd>
663
+ </>
664
+ )}
665
+ {context.varFileName && (
666
+ <>
667
+ <dt>Var File</dt>
668
+ <dd>{context.varFileName}</dd>
669
+ </>
670
+ )}
671
+ </dl>
672
+ </div>
673
+ </div>
674
+ <StageFailureMessage stage={stage} message={stage.failureMessage} />
675
+ {context.region && resourceId && (
676
+ <div className="row">
677
+ <div className="col-md-12">
678
+ <div className={`alert alert-${stage.isFailed ? 'danger' : 'info'}`}>
679
+ {context.previouslyBaked && <div>No changes detected; reused existing bake</div>}
680
+ <a target="_blank" rel="noopener noreferrer" href={interpolatedBakeDetailUrl(stage)}>
681
+ View Bakery Details
682
+ </a>
683
+ </div>
684
+ </div>
685
+ </div>
686
+ )}
687
+ </ExecutionDetailsSection>
688
+ );
689
+ }
690
+
691
+ (AzureBakeExecutionDetails as any).title = 'bakeConfig';
692
+
693
+ export function registerAzureBakeStage() {
694
+ Registry.pipeline.registerStage({
695
+ key: 'bake',
696
+ provides: 'bake',
697
+ cloudProvider: 'azure',
698
+ label: 'Bake',
699
+ description: 'Bakes an image',
700
+ component: AzureBakeStageConfig,
701
+ executionDetailsSections: [AzureBakeExecutionDetails as any, ExecutionDetailsTasks],
702
+ executionLabelComponent: BakeExecutionLabel,
703
+ extraLabelLines: (stage: any) => {
704
+ return stage.masterStage.context.allPreviouslyBaked || stage.masterStage.context.somePreviouslyBaked ? 1 : 0;
705
+ },
706
+ supportsCustomTimeout: true,
707
+ validators: [
708
+ { type: 'requiredField', fieldName: 'package' },
709
+ { type: 'requiredField', fieldName: 'regions' },
710
+ {
711
+ type: 'upstreamVersionProvided',
712
+ checkParentTriggers: true,
713
+ getMessage: (labels: string[]) =>
714
+ 'Bake stages should always have a stage or trigger preceding them that provides version information: ' +
715
+ '<ul>' +
716
+ labels.map((label: string) => `<li>${label}</li>`).join('') +
717
+ '</ul>' +
718
+ 'Otherwise, Spinnaker will bake and deploy the most-recently built package.',
719
+ },
720
+ ] as any,
721
+ restartable: true,
722
+ } as any);
723
+ }