@spinnaker/titus 0.0.0-2025.1-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 (185) hide show
  1. package/CHANGELOG.md +2661 -0
  2. package/LICENSE.txt +203 -0
  3. package/dist/domain/IJobDisruptionBudget.d.ts +35 -0
  4. package/dist/domain/ITitusCredentials.d.ts +5 -0
  5. package/dist/domain/ITitusInstance.d.ts +28 -0
  6. package/dist/domain/ITitusScalingPolicy.d.ts +4 -0
  7. package/dist/domain/ITitusServerGroup.d.ts +52 -0
  8. package/dist/domain/ITitusServiceJobProcesses.d.ts +3 -0
  9. package/dist/domain/index.d.ts +5 -0
  10. package/dist/help/titus.help.d.ts +1 -0
  11. package/dist/index.d.ts +6 -0
  12. package/dist/index.js +12828 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/instance/details/TitusInstanceDetails.d.ts +25 -0
  15. package/dist/instance/details/TitusInstanceDns.d.ts +8 -0
  16. package/dist/instance/details/TitusInstanceInformation.d.ts +7 -0
  17. package/dist/instance/details/index.d.ts +4 -0
  18. package/dist/instance/details/titusInstanceDetailsUtils.d.ts +14 -0
  19. package/dist/pipeline/stages/cloneServerGroup/cloneServerGroupExecutionDetails.controller.d.ts +2 -0
  20. package/dist/pipeline/stages/cloneServerGroup/titusCloneServerGroupStage.d.ts +2 -0
  21. package/dist/pipeline/stages/destroyAsg/titusDestroyAsgStage.d.ts +2 -0
  22. package/dist/pipeline/stages/disableAsg/titusDisableAsgStage.d.ts +2 -0
  23. package/dist/pipeline/stages/disableCluster/titusDisableClusterStage.d.ts +2 -0
  24. package/dist/pipeline/stages/enableAsg/titusEnableAsgStage.d.ts +2 -0
  25. package/dist/pipeline/stages/findAmi/titusFindAmiStage.d.ts +2 -0
  26. package/dist/pipeline/stages/resizeAsg/titusResizeAsgStage.d.ts +2 -0
  27. package/dist/pipeline/stages/runJob/RunJobExecutionDetails.d.ts +16 -0
  28. package/dist/pipeline/stages/runJob/TitusRunJobStageConfig.d.ts +25 -0
  29. package/dist/pipeline/stages/runJob/TitusSecurityGroupPicker.d.ts +34 -0
  30. package/dist/pipeline/stages/runJob/titusRunJobStage.d.ts +1 -0
  31. package/dist/pipeline/stages/scaleDownCluster/titusScaleDownClusterStage.d.ts +2 -0
  32. package/dist/pipeline/stages/shrinkCluster/titusShrinkClusterStage.d.ts +2 -0
  33. package/dist/reactShims/index.d.ts +1 -0
  34. package/dist/reactShims/titus.react.injector.d.ts +11 -0
  35. package/dist/reactShims/titus.react.module.d.ts +1 -0
  36. package/dist/securityGroup/securityGroup.read.service.d.ts +2 -0
  37. package/dist/serverGroup/configure/ServerGroupCommandBuilder.d.ts +2 -0
  38. package/dist/serverGroup/configure/serverGroup.configure.titus.module.d.ts +2 -0
  39. package/dist/serverGroup/configure/serverGroupConfiguration.service.d.ts +89 -0
  40. package/dist/serverGroup/configure/wizard/TitusCloneServerGroupModal.d.ts +31 -0
  41. package/dist/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.d.ts +28 -0
  42. package/dist/serverGroup/configure/wizard/pages/ServerGroupParameters.d.ts +15 -0
  43. package/dist/serverGroup/configure/wizard/pages/ServerGroupResources.d.ts +11 -0
  44. package/dist/serverGroup/configure/wizard/pages/TitusMapLayout.d.ts +4 -0
  45. package/dist/serverGroup/configure/wizard/pages/disruptionBudget/JobDisruptionBudget.d.ts +40 -0
  46. package/dist/serverGroup/configure/wizard/pages/disruptionBudget/PolicyOptions.d.ts +2 -0
  47. package/dist/serverGroup/configure/wizard/pages/disruptionBudget/RateOptions.d.ts +2 -0
  48. package/dist/serverGroup/configure/wizard/pages/disruptionBudget/WindowPicker.d.ts +23 -0
  49. package/dist/serverGroup/configure/wizard/pages/index.d.ts +4 -0
  50. package/dist/serverGroup/details/TitusCapacityDetailsSection.d.ts +11 -0
  51. package/dist/serverGroup/details/TitusLaunchConfigSection.d.ts +9 -0
  52. package/dist/serverGroup/details/TitusPackageDetailsSection.d.ts +6 -0
  53. package/dist/serverGroup/details/TitusSecurityGroups.d.ts +17 -0
  54. package/dist/serverGroup/details/capacityDetailsSection.component.d.ts +1 -0
  55. package/dist/serverGroup/details/disruptionBudget/DisruptionBudgetSection.d.ts +19 -0
  56. package/dist/serverGroup/details/disruptionBudget/EditDisruptionBudgetModal.d.ts +14 -0
  57. package/dist/serverGroup/details/index.d.ts +5 -0
  58. package/dist/serverGroup/details/launchConfigSection.component.d.ts +1 -0
  59. package/dist/serverGroup/details/resize/TitusResizeServerGroupModal.d.ts +8 -0
  60. package/dist/serverGroup/details/resize/useTaskMonitor.d.ts +25 -0
  61. package/dist/serverGroup/details/rollback/rollbackServerGroup.controller.d.ts +2 -0
  62. package/dist/serverGroup/details/scalingActivity/TitusScalingActivitiesModal.d.ts +7 -0
  63. package/dist/serverGroup/details/scalingPolicy/CreateScalingPolicyButton.d.ts +21 -0
  64. package/dist/serverGroup/details/scalingPolicy/TitusCustomScalingPolicy.d.ts +7 -0
  65. package/dist/serverGroup/details/scalingPolicy/createScalingPolicyButton.component.d.ts +1 -0
  66. package/dist/serverGroup/details/scalingPolicy/index.d.ts +3 -0
  67. package/dist/serverGroup/details/scalingPolicy/scalingPolicy.module.d.ts +1 -0
  68. package/dist/serverGroup/details/scalingPolicy/targetTracking/TitusTargetTrackingChart.d.ts +9 -0
  69. package/dist/serverGroup/details/scalingPolicy/targetTracking/UpsertTargetTrackingModal.d.ts +10 -0
  70. package/dist/serverGroup/details/scalingPolicy/titusCustomScalingPolicy.component.d.ts +1 -0
  71. package/dist/serverGroup/details/scalingPolicy/upsert/TitusScalingPolicyCommandBuilderService.d.ts +10 -0
  72. package/dist/serverGroup/details/scalingPolicy/upsert/UpsertScalingPolicyModal.d.ts +10 -0
  73. package/dist/serverGroup/details/sections/ITitusServerGroupDetailsSectionProps.d.ts +5 -0
  74. package/dist/serverGroup/details/serverGroupDetails.titus.controller.d.ts +2 -0
  75. package/dist/serverGroup/details/serviceJobProcesses/ServiceJobProcesses.d.ts +3 -0
  76. package/dist/serverGroup/details/serviceJobProcesses/ServiceJobProcessesSection.d.ts +7 -0
  77. package/dist/serverGroup/details/titusPackageDetailsSection.component.d.ts +1 -0
  78. package/dist/serverGroup/details/titusSecurityGroups.component.d.ts +1 -0
  79. package/dist/serverGroup/serverGroup.transformer.d.ts +2 -0
  80. package/dist/titus.module.d.ts +5 -0
  81. package/dist/titus.settings.d.ts +14 -0
  82. package/dist/validation/ApplicationNameValidator.d.ts +1 -0
  83. package/package.json +52 -0
  84. package/src/domain/IJobDisruptionBudget.ts +19 -0
  85. package/src/domain/ITitusCredentials.ts +6 -0
  86. package/src/domain/ITitusInstance.ts +30 -0
  87. package/src/domain/ITitusScalingPolicy.ts +5 -0
  88. package/src/domain/ITitusServerGroup.ts +56 -0
  89. package/src/domain/ITitusServiceJobProcesses.ts +3 -0
  90. package/src/domain/index.ts +5 -0
  91. package/src/help/titus.help.ts +99 -0
  92. package/src/index.ts +6 -0
  93. package/src/instance/details/TitusInstanceDetails.tsx +234 -0
  94. package/src/instance/details/TitusInstanceDns.tsx +40 -0
  95. package/src/instance/details/TitusInstanceInformation.tsx +42 -0
  96. package/src/instance/details/index.ts +4 -0
  97. package/src/instance/details/titusInstanceDetailsUtils.spec.ts +124 -0
  98. package/src/instance/details/titusInstanceDetailsUtils.ts +181 -0
  99. package/src/logo/titus.logo.less +5 -0
  100. package/src/logo/titus.logo.png +0 -0
  101. package/src/logo/titus.logo.svg +7 -0
  102. package/src/pipeline/stages/cloneServerGroup/cloneServerGroupExecutionDetails.controller.js +66 -0
  103. package/src/pipeline/stages/cloneServerGroup/cloneServerGroupExecutionDetails.html +46 -0
  104. package/src/pipeline/stages/cloneServerGroup/cloneServerGroupStage.html +106 -0
  105. package/src/pipeline/stages/cloneServerGroup/cloneServerGroupStepLabel.html +1 -0
  106. package/src/pipeline/stages/cloneServerGroup/titusCloneServerGroupStage.js +104 -0
  107. package/src/pipeline/stages/destroyAsg/destroyAsgStage.html +9 -0
  108. package/src/pipeline/stages/destroyAsg/destroyAsgStepLabel.html +1 -0
  109. package/src/pipeline/stages/destroyAsg/titusDestroyAsgStage.js +65 -0
  110. package/src/pipeline/stages/disableAsg/disableAsgStage.html +11 -0
  111. package/src/pipeline/stages/disableAsg/disableAsgStepLabel.html +1 -0
  112. package/src/pipeline/stages/disableAsg/titusDisableAsgStage.js +69 -0
  113. package/src/pipeline/stages/disableCluster/disableClusterStage.html +26 -0
  114. package/src/pipeline/stages/disableCluster/titusDisableClusterStage.js +85 -0
  115. package/src/pipeline/stages/enableAsg/enableAsgStage.html +11 -0
  116. package/src/pipeline/stages/enableAsg/enableAsgStepLabel.html +1 -0
  117. package/src/pipeline/stages/enableAsg/titusEnableAsgStage.js +73 -0
  118. package/src/pipeline/stages/findAmi/findAmiStage.html +24 -0
  119. package/src/pipeline/stages/findAmi/titusFindAmiStage.js +79 -0
  120. package/src/pipeline/stages/resizeAsg/resizeAsgStage.html +99 -0
  121. package/src/pipeline/stages/resizeAsg/resizeAsgStepLabel.html +1 -0
  122. package/src/pipeline/stages/resizeAsg/titusResizeAsgStage.js +125 -0
  123. package/src/pipeline/stages/runJob/RunJobExecutionDetails.tsx +165 -0
  124. package/src/pipeline/stages/runJob/TitusRunJobStageConfig.tsx +466 -0
  125. package/src/pipeline/stages/runJob/TitusSecurityGroupPicker.tsx +170 -0
  126. package/src/pipeline/stages/runJob/titusRunJobStage.ts +30 -0
  127. package/src/pipeline/stages/scaleDownCluster/scaleDownClusterStage.html +35 -0
  128. package/src/pipeline/stages/scaleDownCluster/titusScaleDownClusterStage.js +79 -0
  129. package/src/pipeline/stages/shrinkCluster/shrinkClusterStage.html +34 -0
  130. package/src/pipeline/stages/shrinkCluster/titusShrinkClusterStage.js +73 -0
  131. package/src/reactShims/index.ts +1 -0
  132. package/src/reactShims/titus.react.injector.ts +17 -0
  133. package/src/reactShims/titus.react.module.ts +10 -0
  134. package/src/securityGroup/securityGroup.read.service.js +15 -0
  135. package/src/serverGroup/configure/ServerGroupCommandBuilder.js +247 -0
  136. package/src/serverGroup/configure/serverGroup.configure.titus.module.js +9 -0
  137. package/src/serverGroup/configure/serverGroupCommandBuilder.spec.js +63 -0
  138. package/src/serverGroup/configure/serverGroupConfiguration.service.ts +410 -0
  139. package/src/serverGroup/configure/wizard/TitusCloneServerGroupModal.tsx +293 -0
  140. package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.tsx +339 -0
  141. package/src/serverGroup/configure/wizard/pages/ServerGroupParameters.less +5 -0
  142. package/src/serverGroup/configure/wizard/pages/ServerGroupParameters.tsx +217 -0
  143. package/src/serverGroup/configure/wizard/pages/ServerGroupResources.tsx +200 -0
  144. package/src/serverGroup/configure/wizard/pages/TitusMapLayout.less +3 -0
  145. package/src/serverGroup/configure/wizard/pages/TitusMapLayout.tsx +29 -0
  146. package/src/serverGroup/configure/wizard/pages/disruptionBudget/JobDisruptionBudget.tsx +285 -0
  147. package/src/serverGroup/configure/wizard/pages/disruptionBudget/PolicyOptions.tsx +112 -0
  148. package/src/serverGroup/configure/wizard/pages/disruptionBudget/RateOptions.tsx +89 -0
  149. package/src/serverGroup/configure/wizard/pages/disruptionBudget/WindowPicker.tsx +202 -0
  150. package/src/serverGroup/configure/wizard/pages/index.ts +4 -0
  151. package/src/serverGroup/details/TitusCapacityDetailsSection.tsx +66 -0
  152. package/src/serverGroup/details/TitusLaunchConfigSection.tsx +35 -0
  153. package/src/serverGroup/details/TitusPackageDetailsSection.tsx +40 -0
  154. package/src/serverGroup/details/TitusSecurityGroups.tsx +107 -0
  155. package/src/serverGroup/details/capacityDetailsSection.component.ts +12 -0
  156. package/src/serverGroup/details/disruptionBudget/DisruptionBudgetSection.tsx +226 -0
  157. package/src/serverGroup/details/disruptionBudget/EditDisruptionBudgetModal.tsx +92 -0
  158. package/src/serverGroup/details/index.ts +5 -0
  159. package/src/serverGroup/details/launchConfigSection.component.ts +12 -0
  160. package/src/serverGroup/details/resize/TitusResizeServerGroupModal.tsx +302 -0
  161. package/src/serverGroup/details/resize/useTaskMonitor.ts +30 -0
  162. package/src/serverGroup/details/rollback/rollbackServerGroup.controller.js +149 -0
  163. package/src/serverGroup/details/rollback/rollbackServerGroup.html +133 -0
  164. package/src/serverGroup/details/scalingActivity/TitusScalingActivitiesModal.tsx +81 -0
  165. package/src/serverGroup/details/scalingPolicy/CreateScalingPolicyButton.tsx +102 -0
  166. package/src/serverGroup/details/scalingPolicy/TitusCustomScalingPolicy.tsx +13 -0
  167. package/src/serverGroup/details/scalingPolicy/createScalingPolicyButton.component.ts +15 -0
  168. package/src/serverGroup/details/scalingPolicy/index.js +3 -0
  169. package/src/serverGroup/details/scalingPolicy/scalingPolicy.module.ts +7 -0
  170. package/src/serverGroup/details/scalingPolicy/targetTracking/TitusTargetTrackingChart.tsx +57 -0
  171. package/src/serverGroup/details/scalingPolicy/targetTracking/UpsertTargetTrackingModal.tsx +82 -0
  172. package/src/serverGroup/details/scalingPolicy/titusCustomScalingPolicy.component.ts +17 -0
  173. package/src/serverGroup/details/scalingPolicy/upsert/TitusScalingPolicyCommandBuilderService.ts +115 -0
  174. package/src/serverGroup/details/scalingPolicy/upsert/UpsertScalingPolicyModal.tsx +157 -0
  175. package/src/serverGroup/details/sections/ITitusServerGroupDetailsSectionProps.ts +6 -0
  176. package/src/serverGroup/details/serverGroupDetails.html +191 -0
  177. package/src/serverGroup/details/serverGroupDetails.titus.controller.js +457 -0
  178. package/src/serverGroup/details/serviceJobProcesses/ServiceJobProcesses.ts +12 -0
  179. package/src/serverGroup/details/serviceJobProcesses/ServiceJobProcessesSection.tsx +136 -0
  180. package/src/serverGroup/details/titusPackageDetailsSection.component.ts +12 -0
  181. package/src/serverGroup/details/titusSecurityGroups.component.ts +12 -0
  182. package/src/serverGroup/serverGroup.transformer.js +90 -0
  183. package/src/titus.module.ts +95 -0
  184. package/src/titus.settings.ts +22 -0
  185. package/src/validation/ApplicationNameValidator.ts +43 -0
@@ -0,0 +1,410 @@
1
+ import { module } from 'angular';
2
+ import { chain, cloneDeep, flatten, intersection, xor } from 'lodash';
3
+ import { $q } from 'ngimport';
4
+ import { Subject } from 'rxjs';
5
+
6
+ import type {
7
+ IAmazonApplicationLoadBalancer,
8
+ IAmazonLoadBalancer,
9
+ IAmazonServerGroupCommandDirty,
10
+ } from '@spinnaker/amazon';
11
+ import { VpcReader } from '@spinnaker/amazon';
12
+ import type {
13
+ Application,
14
+ CacheInitializerService,
15
+ IAccountDetails,
16
+ ICluster,
17
+ IDeploymentStrategy,
18
+ ISecurityGroup,
19
+ IServerGroupCommand,
20
+ IServerGroupCommandBackingData,
21
+ IServerGroupCommandViewState,
22
+ IVpc,
23
+ LoadBalancerReader,
24
+ SecurityGroupReader,
25
+ } from '@spinnaker/core';
26
+ import {
27
+ AccountService,
28
+ CACHE_INITIALIZER_SERVICE,
29
+ LOAD_BALANCER_READ_SERVICE,
30
+ NameUtils,
31
+ SECURITY_GROUP_READER,
32
+ setMatchingResourceSummary,
33
+ SubnetReader,
34
+ } from '@spinnaker/core';
35
+ import type { IJobDisruptionBudget, ITitusResources } from '../../domain';
36
+ import type { ITitusServiceJobProcesses } from '../../domain/ITitusServiceJobProcesses';
37
+
38
+ export interface ITitusServerGroupCommandBackingData extends IServerGroupCommandBackingData {
39
+ accounts: string[];
40
+ vpcs: IVpc[];
41
+ }
42
+
43
+ export interface ITitusServerGroupCommandViewState extends IServerGroupCommandViewState {
44
+ accountChangedStream: Subject<{}>;
45
+ regionChangedStream: Subject<{}>;
46
+ groupsRemovedStream: Subject<{}>;
47
+ dirty: IAmazonServerGroupCommandDirty;
48
+ defaultIamProfile: string;
49
+ }
50
+
51
+ export const defaultJobDisruptionBudget: IJobDisruptionBudget = {
52
+ availabilityPercentageLimit: {
53
+ percentageOfHealthyContainers: 95,
54
+ },
55
+ ratePercentagePerInterval: {
56
+ intervalMs: 600000,
57
+ percentageLimitPerInterval: 5,
58
+ },
59
+ containerHealthProviders: [{ name: 'eureka' }],
60
+ timeWindows: [
61
+ {
62
+ days: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
63
+ hourlyTimeWindows: [{ startHour: 10, endHour: 16 }],
64
+ timeZone: 'PST',
65
+ },
66
+ ],
67
+ };
68
+
69
+ export const getDefaultJobDisruptionBudgetForApp = (application: Application): IJobDisruptionBudget => {
70
+ const budget = cloneDeep(defaultJobDisruptionBudget);
71
+ if (application.attributes && application.attributes.platformHealthOnly) {
72
+ budget.containerHealthProviders = [];
73
+ }
74
+ return budget;
75
+ };
76
+
77
+ export interface ITitusServerGroupCommand extends IServerGroupCommand {
78
+ cluster?: ICluster;
79
+ disruptionBudget?: IJobDisruptionBudget;
80
+ deferredInitialization?: boolean;
81
+ registry: string;
82
+ imageId: string;
83
+ organization: string;
84
+ repository: string;
85
+ tag?: string;
86
+ digest?: string;
87
+ image: string;
88
+ inService: boolean;
89
+ resources: ITitusResources;
90
+ efs: {
91
+ efsId: string;
92
+ mountPoint: string;
93
+ mountPerm: string;
94
+ efsRelativeMountPoint: string;
95
+ };
96
+ viewState: ITitusServerGroupCommandViewState;
97
+ targetGroups: string[];
98
+ removedTargetGroups: string[];
99
+ backingData: ITitusServerGroupCommandBackingData;
100
+ labels: { [key: string]: string };
101
+ containerAttributes: { [key: string]: string };
102
+ env: { [key: string]: string };
103
+ iamProfile: string;
104
+ migrationPolicy: {
105
+ type: string;
106
+ };
107
+ constraints: {
108
+ hard: { [key: string]: string };
109
+ soft: { [key: string]: string };
110
+ };
111
+ serviceJobProcesses: ITitusServiceJobProcesses;
112
+ }
113
+
114
+ export class TitusServerGroupConfigurationService {
115
+ public static $inject = ['cacheInitializer', 'loadBalancerReader', 'securityGroupReader'];
116
+ constructor(
117
+ private cacheInitializer: CacheInitializerService,
118
+ private loadBalancerReader: LoadBalancerReader,
119
+ private securityGroupReader: SecurityGroupReader,
120
+ ) {}
121
+
122
+ public configureZones(command: ITitusServerGroupCommand) {
123
+ command.backingData.filtered.regions = command.backingData.credentialsKeyedByAccount[command.credentials].regions;
124
+ }
125
+
126
+ public configureSubnets(command: ITitusServerGroupCommand) {
127
+ const result = { dirty: {} };
128
+ const filteredData = command.backingData.filtered;
129
+ if (command.region === null) {
130
+ return result;
131
+ }
132
+
133
+ const accountDetails = this.getCredentials(command);
134
+ filteredData.subnetPurposes = chain(command.backingData.subnets)
135
+ .filter({ account: accountDetails.awsAccount, region: command.region })
136
+ .reject({ target: 'elb' })
137
+ .reject({ purpose: null })
138
+ .uniqBy('purpose')
139
+ .value();
140
+
141
+ const subnetType = command.subnetType || 'titus (vpc0)';
142
+ const subnetIds = chain(command.backingData.subnets)
143
+ .filter({ account: accountDetails.awsAccount, region: command.region, purpose: subnetType })
144
+ .map((subnet) => subnet.id)
145
+ .value();
146
+ command.containerAttributes['titusParameter.agent.subnets'] = subnetIds.join(',');
147
+
148
+ return result;
149
+ }
150
+
151
+ private attachEventHandlers(cmd: ITitusServerGroupCommand) {
152
+ cmd.credentialsChanged = (command: ITitusServerGroupCommand) => {
153
+ const result = { dirty: {} };
154
+ const backingData = command.backingData;
155
+ this.configureZones(command);
156
+ if (command.credentials) {
157
+ command.registry = (backingData.credentialsKeyedByAccount[command.credentials] as any).registry;
158
+ backingData.filtered.regions = backingData.credentialsKeyedByAccount[command.credentials].regions;
159
+
160
+ const accountId = backingData.credentialsKeyedByAccount[command.credentials].accountId;
161
+ command.containerAttributes['titusParameter.agent.accountId'] = accountId;
162
+ if (!backingData.filtered.regions.some((r) => r.name === command.region)) {
163
+ command.region = null;
164
+ command.regionChanged(command);
165
+ }
166
+ } else {
167
+ command.region = null;
168
+ }
169
+ command.viewState.dirty = { ...(command.viewState.dirty || {}), ...result.dirty };
170
+ this.configureLoadBalancerOptions(command);
171
+ this.configureSecurityGroupOptions(command);
172
+ this.configureSubnets(command);
173
+ setMatchingResourceSummary(command);
174
+ return result;
175
+ };
176
+
177
+ cmd.regionChanged = (command: ITitusServerGroupCommand) => {
178
+ this.configureLoadBalancerOptions(command);
179
+ this.configureSecurityGroupOptions(command);
180
+ this.configureSubnets(command);
181
+ setMatchingResourceSummary(command);
182
+ return {};
183
+ };
184
+
185
+ cmd.clusterChanged = (command: ITitusServerGroupCommand): void => {
186
+ command.moniker = NameUtils.getMoniker(command.application, command.stack, command.freeFormDetails);
187
+ setMatchingResourceSummary(command);
188
+ };
189
+
190
+ cmd.subnetChanged = (command: ITitusServerGroupCommand) => {
191
+ return this.configureSubnets(command);
192
+ };
193
+ }
194
+
195
+ public configureCommand(cmd: ITitusServerGroupCommand): PromiseLike<void> {
196
+ cmd.viewState.accountChangedStream = new Subject();
197
+ cmd.viewState.regionChangedStream = new Subject();
198
+ cmd.viewState.groupsRemovedStream = new Subject();
199
+ cmd.viewState.dirty = {};
200
+ cmd.onStrategyChange = (command: ITitusServerGroupCommand, strategy: IDeploymentStrategy) => {
201
+ // Any strategy other than None or Custom should force traffic to be enabled
202
+ if (strategy.key !== '' && strategy.key !== 'custom') {
203
+ command.inService = true;
204
+ }
205
+ };
206
+ cmd.image = cmd.viewState.imageId;
207
+ return $q
208
+ .all([
209
+ AccountService.getCredentialsKeyedByAccount('titus'),
210
+ this.securityGroupReader.getAllSecurityGroups(),
211
+ VpcReader.listVpcs(),
212
+ SubnetReader.listSubnets(),
213
+ AccountService.getCredentialsKeyedByAccount('aws'),
214
+ ])
215
+ .then(([credentialsKeyedByAccount, securityGroups, vpcs, subnets, awsCredentials]) => {
216
+ const backingData: any = {
217
+ credentialsKeyedByAccount,
218
+ securityGroups,
219
+ vpcs,
220
+ };
221
+
222
+ // add the AWS accountId to the credentialsKeyedByAccount to easily set container attributes
223
+ Object.keys(credentialsKeyedByAccount).forEach((acc) => {
224
+ const awsAccount = credentialsKeyedByAccount[acc].awsAccount;
225
+ const awsAccountId = awsCredentials[awsAccount].accountId;
226
+ credentialsKeyedByAccount[acc].accountId = awsAccountId;
227
+ });
228
+ backingData.credentialsKeyedByAccount = credentialsKeyedByAccount;
229
+
230
+ if (cmd.credentials) {
231
+ cmd.containerAttributes['titusParameter.agent.accountId'] =
232
+ backingData.credentialsKeyedByAccount[cmd.credentials].accountId;
233
+ }
234
+
235
+ backingData.images = [];
236
+ backingData.accounts = Object.keys(credentialsKeyedByAccount);
237
+ backingData.subnets = subnets;
238
+ backingData.filtered = {};
239
+ if (cmd.credentials.includes('${')) {
240
+ // If our dependency is an expression, the only thing we can really do is to just preserve current selections
241
+ backingData.filtered.regions = [{ name: cmd.region }];
242
+ } else {
243
+ backingData.filtered.regions = credentialsKeyedByAccount[cmd.credentials]?.regions ?? [];
244
+ }
245
+ cmd.backingData = backingData;
246
+ backingData.filtered.securityGroups = this.getRegionalSecurityGroups(cmd);
247
+
248
+ let securityGroupRefresher: PromiseLike<any> = $q.when();
249
+ if (cmd.securityGroups && cmd.securityGroups.length) {
250
+ const regionalSecurityGroupIds = backingData.filtered.securityGroups.map((g: ISecurityGroup) => g.id);
251
+ if (intersection(cmd.securityGroups, regionalSecurityGroupIds).length < cmd.securityGroups.length) {
252
+ securityGroupRefresher = this.refreshSecurityGroups(cmd, false);
253
+ }
254
+ }
255
+
256
+ return $q.all([this.refreshLoadBalancers(cmd), securityGroupRefresher]).then(() => {
257
+ this.attachEventHandlers(cmd);
258
+ });
259
+ });
260
+ }
261
+
262
+ private getVpcId(command: ITitusServerGroupCommand): string {
263
+ const credentials = this.getCredentials(command);
264
+ const match = command.backingData.vpcs.find(
265
+ (vpc) =>
266
+ vpc.name === credentials.awsVpc &&
267
+ vpc.account === credentials.awsAccount &&
268
+ vpc.region === this.getRegion(command) &&
269
+ vpc.cloudProvider === 'aws',
270
+ );
271
+ return match ? match.id : null;
272
+ }
273
+
274
+ private getRegionalSecurityGroups(command: ITitusServerGroupCommand): ISecurityGroup[] {
275
+ const newSecurityGroups: any = command.backingData.securityGroups[this.getAwsAccount(command)] || { aws: {} };
276
+ return chain<ISecurityGroup>(newSecurityGroups.aws[this.getRegion(command)])
277
+ .filter({ vpcId: this.getVpcId(command) })
278
+ .sortBy('name')
279
+ .value();
280
+ }
281
+
282
+ private configureSecurityGroupOptions(command: ITitusServerGroupCommand): void {
283
+ const currentOptions = command.backingData.filtered.securityGroups;
284
+ if (command.credentials.includes('${') || (command.region && command.region.includes('${'))) {
285
+ // If any of our dependencies are expressions, the only thing we can do is preserve current values
286
+ command.backingData.filtered.securityGroups = command.securityGroups.map((group) => ({ name: group, id: group }));
287
+ } else {
288
+ const newRegionalSecurityGroups = this.getRegionalSecurityGroups(command);
289
+ const isExpression =
290
+ typeof command.securityGroups === 'string' && (command.securityGroups as string).includes('${');
291
+ if (currentOptions && command.securityGroups && !isExpression) {
292
+ // not initializing - we are actually changing groups
293
+ const currentGroupNames: string[] = command.securityGroups.map((groupId: string) => {
294
+ const match = currentOptions.find((o) => o.id === groupId);
295
+ return match ? match.name : groupId;
296
+ });
297
+
298
+ const matchedGroups = command.securityGroups
299
+ .map((groupId: string) => {
300
+ const securityGroup: any = currentOptions.find((o) => o.id === groupId || o.name === groupId);
301
+ return securityGroup ? securityGroup.name : null;
302
+ })
303
+ .map((groupName: string) => newRegionalSecurityGroups.find((g) => g.name === groupName))
304
+ .filter((group: any) => group);
305
+
306
+ const matchedGroupNames: string[] = matchedGroups.map((g) => g.name);
307
+ const removed: string[] = xor(currentGroupNames, matchedGroupNames);
308
+ command.securityGroups = matchedGroups.map((g) => g.id);
309
+ if (removed.length) {
310
+ command.viewState.dirty.securityGroups = removed;
311
+ }
312
+ }
313
+ command.backingData.filtered.securityGroups = newRegionalSecurityGroups.sort((a, b) => {
314
+ if (command.securityGroups.includes(a.id)) {
315
+ return -1;
316
+ }
317
+ if (command.securityGroups.includes(b.id)) {
318
+ return 1;
319
+ }
320
+ return a.name.localeCompare(b.name);
321
+ });
322
+ }
323
+ }
324
+
325
+ public refreshSecurityGroups(
326
+ command: ITitusServerGroupCommand,
327
+ skipCommandReconfiguration: boolean,
328
+ ): PromiseLike<void> {
329
+ return this.cacheInitializer.refreshCache('securityGroups').then(() => {
330
+ return this.securityGroupReader.getAllSecurityGroups().then((securityGroups: any) => {
331
+ command.backingData.securityGroups = securityGroups;
332
+ if (!skipCommandReconfiguration) {
333
+ this.configureSecurityGroupOptions(command);
334
+ }
335
+ });
336
+ });
337
+ }
338
+
339
+ private getCredentials(command: ITitusServerGroupCommand): IAccountDetails {
340
+ return command.backingData.credentialsKeyedByAccount[command.credentials];
341
+ }
342
+
343
+ private getAwsAccount(command: ITitusServerGroupCommand): string {
344
+ return this.getCredentials(command).awsAccount;
345
+ }
346
+
347
+ private getRegion(command: ITitusServerGroupCommand): string {
348
+ return command.region || (command.cluster ? command.cluster.region : null);
349
+ }
350
+
351
+ public getTargetGroupNames(command: ITitusServerGroupCommand): string[] {
352
+ const loadBalancersV2 = this.getLoadBalancerMap(command).filter(
353
+ (lb) => lb.loadBalancerType !== 'classic',
354
+ ) as IAmazonApplicationLoadBalancer[];
355
+ const instanceTargetGroups = flatten(
356
+ loadBalancersV2.map<any>((lb) => lb.targetGroups.filter((tg) => tg.targetType === 'ip')),
357
+ );
358
+ return instanceTargetGroups.map((tg) => tg.name).sort();
359
+ }
360
+
361
+ private getLoadBalancerMap(command: ITitusServerGroupCommand): IAmazonLoadBalancer[] {
362
+ return chain(command.backingData.loadBalancers)
363
+ .map('accounts')
364
+ .flattenDeep()
365
+ .filter({ name: this.getAwsAccount(command) })
366
+ .map('regions')
367
+ .flattenDeep()
368
+ .filter({ name: this.getRegion(command) })
369
+ .map<IAmazonLoadBalancer>('loadBalancers')
370
+ .flattenDeep<IAmazonLoadBalancer>()
371
+ .value();
372
+ }
373
+
374
+ public configureLoadBalancerOptions(command: ITitusServerGroupCommand) {
375
+ const currentTargetGroups = command.targetGroups || [];
376
+ if (command.credentials.includes('${') || (command.region && command.region.includes('${'))) {
377
+ // If any of our dependencies are expressions, the only thing we can do is preserve current values
378
+ command.targetGroups = currentTargetGroups;
379
+ (command.backingData.filtered as any).targetGroups = currentTargetGroups;
380
+ } else {
381
+ const allTargetGroups = this.getTargetGroupNames(command);
382
+
383
+ if (currentTargetGroups && command.targetGroups) {
384
+ const matched = intersection(allTargetGroups, currentTargetGroups);
385
+ const removedTargetGroups = xor(matched, currentTargetGroups);
386
+ command.targetGroups = intersection(allTargetGroups, matched);
387
+ if (removedTargetGroups && removedTargetGroups.length > 0) {
388
+ command.viewState.dirty.targetGroups = removedTargetGroups;
389
+ } else {
390
+ delete command.viewState.dirty.targetGroups;
391
+ }
392
+ }
393
+ (command.backingData.filtered as any).targetGroups = allTargetGroups;
394
+ }
395
+ }
396
+
397
+ public refreshLoadBalancers(command: ITitusServerGroupCommand) {
398
+ return this.loadBalancerReader.listLoadBalancers('aws').then((loadBalancers) => {
399
+ command.backingData.loadBalancers = loadBalancers;
400
+ this.configureLoadBalancerOptions(command);
401
+ });
402
+ }
403
+ }
404
+
405
+ export const TITUS_SERVER_GROUP_CONFIGURATION_SERVICE = 'spinnaker.titus.serverGroup.configure.service';
406
+ module(TITUS_SERVER_GROUP_CONFIGURATION_SERVICE, [
407
+ CACHE_INITIALIZER_SERVICE,
408
+ LOAD_BALANCER_READ_SERVICE,
409
+ SECURITY_GROUP_READER,
410
+ ]).service('titusServerGroupConfigurationService', TitusServerGroupConfigurationService);
@@ -0,0 +1,293 @@
1
+ import { get, isEqual } from 'lodash';
2
+ import React from 'react';
3
+
4
+ import { ServerGroupCapacity, ServerGroupLoadBalancers, ServerGroupSecurityGroups } from '@spinnaker/amazon';
5
+ import type { Application, IModalComponentProps, IStage } from '@spinnaker/core';
6
+ import {
7
+ AccountTag,
8
+ DeployInitializer,
9
+ FirewallLabels,
10
+ noop,
11
+ ReactInjector,
12
+ ReactModal,
13
+ TaskMonitor,
14
+ WizardModal,
15
+ WizardPage,
16
+ } from '@spinnaker/core';
17
+
18
+ import { ServerGroupBasicSettings, ServerGroupParameters, ServerGroupResources } from './pages';
19
+ import { JobDisruptionBudget } from './pages/disruptionBudget/JobDisruptionBudget';
20
+ import { TitusReactInjector } from '../../../reactShims';
21
+ import type { ITitusServerGroupCommand } from '../serverGroupConfiguration.service';
22
+ import { getDefaultJobDisruptionBudgetForApp } from '../serverGroupConfiguration.service';
23
+
24
+ export interface ITitusCloneServerGroupModalProps extends IModalComponentProps {
25
+ title: string;
26
+ application: Application;
27
+ command: ITitusServerGroupCommand;
28
+ }
29
+
30
+ export interface ITitusCloneServerGroupModalState {
31
+ firewallsLabel: string;
32
+ loaded: boolean;
33
+ requiresTemplateSelection: boolean;
34
+ taskMonitor: TaskMonitor;
35
+ }
36
+
37
+ export class TitusCloneServerGroupModal extends React.Component<
38
+ ITitusCloneServerGroupModalProps,
39
+ ITitusCloneServerGroupModalState
40
+ > {
41
+ public static defaultProps: Partial<ITitusCloneServerGroupModalProps> = {
42
+ closeModal: noop,
43
+ dismissModal: noop,
44
+ };
45
+
46
+ private _isUnmounted = false;
47
+ private refreshUnsubscribe: () => void;
48
+
49
+ public static show(props: ITitusCloneServerGroupModalProps): Promise<ITitusServerGroupCommand> {
50
+ const modalProps = { dialogClassName: 'wizard-modal modal-lg' };
51
+ return ReactModal.show(TitusCloneServerGroupModal, props, modalProps);
52
+ }
53
+
54
+ constructor(props: ITitusCloneServerGroupModalProps) {
55
+ super(props);
56
+
57
+ const requiresTemplateSelection = get(props, 'command.viewState.requiresTemplateSelection', false);
58
+ if (!requiresTemplateSelection) {
59
+ this.configureCommand();
60
+ }
61
+
62
+ this.state = {
63
+ firewallsLabel: FirewallLabels.get('Firewalls'),
64
+ loaded: false,
65
+ requiresTemplateSelection,
66
+ taskMonitor: new TaskMonitor({
67
+ application: props.application,
68
+ title: 'Creating your server group',
69
+ modalInstance: TaskMonitor.modalInstanceEmulation(() => this.props.dismissModal()),
70
+ onTaskComplete: this.onTaskComplete,
71
+ }),
72
+ };
73
+ }
74
+
75
+ private templateSelected = () => {
76
+ this.setState({ requiresTemplateSelection: false });
77
+ this.configureCommand();
78
+ };
79
+
80
+ private onTaskComplete = () => {
81
+ this.props.application.serverGroups.refresh();
82
+ this.props.application.serverGroups.onNextRefresh(null, this.onApplicationRefresh);
83
+ };
84
+
85
+ protected onApplicationRefresh = (): void => {
86
+ if (this._isUnmounted) {
87
+ return;
88
+ }
89
+
90
+ const { command } = this.props;
91
+ const { taskMonitor } = this.state;
92
+ const cloneStage = taskMonitor.task.execution.stages.find((stage: IStage) => stage.type === 'cloneServerGroup');
93
+ if (cloneStage && cloneStage.context['deploy.server.groups']) {
94
+ const newServerGroupName = cloneStage.context['deploy.server.groups'][command.region];
95
+ if (newServerGroupName) {
96
+ const newStateParams = {
97
+ serverGroup: newServerGroupName,
98
+ accountId: command.credentials,
99
+ region: command.region,
100
+ provider: 'titus',
101
+ };
102
+ let transitionTo = '^.^.^.clusters.serverGroup';
103
+ if (ReactInjector.$state.includes('**.clusters.serverGroup')) {
104
+ // clone via details, all view
105
+ transitionTo = '^.serverGroup';
106
+ }
107
+ if (ReactInjector.$state.includes('**.clusters.cluster.serverGroup')) {
108
+ // clone or create with details open
109
+ transitionTo = '^.^.serverGroup';
110
+ }
111
+ if (ReactInjector.$state.includes('**.clusters')) {
112
+ // create new, no details open
113
+ transitionTo = '.serverGroup';
114
+ }
115
+ ReactInjector.$state.go(transitionTo, newStateParams);
116
+ }
117
+ }
118
+ };
119
+
120
+ private configureCommand = () => {
121
+ const { command } = this.props;
122
+ TitusReactInjector.titusServerGroupConfigurationService.configureCommand(command).then(() => {
123
+ TitusReactInjector.titusServerGroupConfigurationService.configureSubnets(command);
124
+ if (!command.credentials.includes('${')) {
125
+ // so as to not erase registry when account is a spel expression
126
+ command.registry = ((command.backingData.credentialsKeyedByAccount[command.credentials] as any) || {}).registry;
127
+ }
128
+ this.setState({ loaded: true, requiresTemplateSelection: false });
129
+ });
130
+ };
131
+
132
+ public componentWillUnmount(): void {
133
+ this._isUnmounted = true;
134
+ if (this.refreshUnsubscribe) {
135
+ this.refreshUnsubscribe();
136
+ }
137
+ }
138
+
139
+ private submit = (command: ITitusServerGroupCommand): void => {
140
+ const forPipelineConfig = command.viewState.mode === 'editPipeline' || command.viewState.mode === 'createPipeline';
141
+ // never persist the default budget
142
+ let toSubmit = command;
143
+ // TODO: see if this is needed
144
+ if (command.disruptionBudget.timeWindows && !command.disruptionBudget.timeWindows.length) {
145
+ delete command.disruptionBudget.timeWindows;
146
+ }
147
+ if (isEqual(getDefaultJobDisruptionBudgetForApp(this.props.application), command.disruptionBudget)) {
148
+ toSubmit = { ...command, disruptionBudget: undefined };
149
+ }
150
+ if (forPipelineConfig) {
151
+ this.props.closeModal && this.props.closeModal(toSubmit);
152
+ } else {
153
+ this.state.taskMonitor.submit(() =>
154
+ ReactInjector.serverGroupWriter.cloneServerGroup(toSubmit, this.props.application),
155
+ );
156
+ }
157
+ };
158
+
159
+ private getLoadBalancerNote = (command: ITitusServerGroupCommand) => {
160
+ return (
161
+ <div className="form-group small" style={{ marginTop: '20px' }}>
162
+ <div className="col-md-8 col-md-offset-3">
163
+ <p>
164
+ Only target groups with target type <em>ip</em> are supported in Titus. It is not possible to re-use target
165
+ groups of target type <em>instance</em> used by Amazon instances.{' '}
166
+ </p>
167
+ {command.backingData !== undefined &&
168
+ command.backingData.credentialsKeyedByAccount !== undefined &&
169
+ command.credentials !== undefined && (
170
+ <p>
171
+ Uses target groups from the Amazon account{' '}
172
+ <AccountTag
173
+ account={
174
+ command.backingData.credentialsKeyedByAccount[command.credentials] &&
175
+ command.backingData.credentialsKeyedByAccount[command.credentials].awsAccount
176
+ }
177
+ />
178
+ </p>
179
+ )}
180
+ </div>
181
+ </div>
182
+ );
183
+ };
184
+
185
+ private getSecurityGroupNote = (command: ITitusServerGroupCommand) => {
186
+ const amazonAccount =
187
+ command.backingData &&
188
+ command.backingData.credentialsKeyedByAccount &&
189
+ command.backingData.credentialsKeyedByAccount[command.credentials] &&
190
+ command.backingData.credentialsKeyedByAccount[command.credentials].awsAccount;
191
+ if (!amazonAccount || command.credentials === undefined) {
192
+ return null;
193
+ }
194
+
195
+ return (
196
+ <div className="form-group small">
197
+ <div className="col-md-9 col-md-offset-3">
198
+ Uses {FirewallLabels.get('firewalls')} from the Amazon account <AccountTag account={amazonAccount} />
199
+ </div>
200
+ </div>
201
+ );
202
+ };
203
+
204
+ public render() {
205
+ const { application, command, dismissModal, title } = this.props;
206
+ const { loaded, taskMonitor, requiresTemplateSelection } = this.state;
207
+
208
+ if (requiresTemplateSelection) {
209
+ return (
210
+ <DeployInitializer
211
+ application={application}
212
+ cloudProvider="titus"
213
+ command={command}
214
+ onDismiss={dismissModal}
215
+ onTemplateSelected={this.templateSelected}
216
+ />
217
+ );
218
+ }
219
+
220
+ return (
221
+ <WizardModal<ITitusServerGroupCommand>
222
+ heading={title}
223
+ initialValues={command}
224
+ loading={!loaded}
225
+ taskMonitor={taskMonitor}
226
+ dismissModal={dismissModal}
227
+ closeModal={this.submit}
228
+ submitButtonLabel={command.viewState.submitButtonLabel}
229
+ render={({ formik, nextIdx, wizard }) => (
230
+ <>
231
+ <WizardPage
232
+ label="Basic Settings"
233
+ wizard={wizard}
234
+ order={nextIdx()}
235
+ render={({ innerRef }) => <ServerGroupBasicSettings ref={innerRef} formik={formik} app={application} />}
236
+ />
237
+
238
+ <WizardPage
239
+ label="Resources"
240
+ wizard={wizard}
241
+ order={nextIdx()}
242
+ render={({ innerRef }) => <ServerGroupResources ref={innerRef} formik={formik} />}
243
+ />
244
+
245
+ <WizardPage
246
+ label="Capacity"
247
+ wizard={wizard}
248
+ order={nextIdx()}
249
+ render={({ innerRef }) => <ServerGroupCapacity ref={innerRef} formik={formik} />}
250
+ />
251
+
252
+ <WizardPage
253
+ label="Load Balancers"
254
+ wizard={wizard}
255
+ order={nextIdx()}
256
+ note={this.getLoadBalancerNote(formik.values)}
257
+ render={({ innerRef }) => (
258
+ <ServerGroupLoadBalancers
259
+ ref={innerRef}
260
+ formik={formik as any}
261
+ hideLoadBalancers={true}
262
+ targetGroupTypeHelpText={'ip'}
263
+ />
264
+ )}
265
+ />
266
+
267
+ <WizardPage
268
+ label={FirewallLabels.get('Firewalls')}
269
+ wizard={wizard}
270
+ order={nextIdx()}
271
+ note={this.getSecurityGroupNote(formik.values)}
272
+ render={({ innerRef }) => <ServerGroupSecurityGroups ref={innerRef} formik={formik as any} />}
273
+ />
274
+
275
+ <WizardPage
276
+ label="Job Disruption Budget"
277
+ wizard={wizard}
278
+ order={nextIdx()}
279
+ render={({ innerRef }) => <JobDisruptionBudget ref={innerRef} formik={formik} app={application} />}
280
+ />
281
+
282
+ <WizardPage
283
+ label="Advanced Settings"
284
+ wizard={wizard}
285
+ order={nextIdx()}
286
+ render={({ innerRef }) => <ServerGroupParameters ref={innerRef} formik={formik} app={application} />}
287
+ />
288
+ </>
289
+ )}
290
+ />
291
+ );
292
+ }
293
+ }