@spinnaker/amazon 0.11.1 → 0.12.3

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 (75) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/domain/IAmazonLaunchTemplate.d.ts +1 -1
  3. package/dist/domain/IAmazonServerGroup.d.ts +10 -0
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/instance/awsInstanceType.service.d.ts +10 -2
  7. package/dist/instance/awsInstanceTypes.d.ts +10 -0
  8. package/dist/instance/details/CostFactor.d.ts +6 -0
  9. package/dist/reactShims/aws.react.injector.d.ts +2 -1
  10. package/dist/search/searchResultFormatter.d.ts +2 -2
  11. package/dist/serverGroup/configure/serverGroupCommandBuilder.service.d.ts +17 -2
  12. package/dist/serverGroup/configure/serverGroupConfiguration.service.d.ts +39 -0
  13. package/dist/serverGroup/configure/wizard/instanceType/CpuCreditsToggle.d.ts +3 -4
  14. package/dist/serverGroup/configure/wizard/instanceType/InstanceTypeSelector.d.ts +9 -0
  15. package/dist/serverGroup/configure/wizard/instanceType/advancedMode/AdvancedModeSelector.d.ts +13 -0
  16. package/dist/serverGroup/configure/wizard/instanceType/advancedMode/InstanceProfileSelector.d.ts +9 -0
  17. package/dist/serverGroup/configure/wizard/instanceType/advancedMode/InstanceTypeRow.d.ts +11 -0
  18. package/dist/serverGroup/configure/wizard/instanceType/advancedMode/InstanceTypeTable.d.ts +14 -0
  19. package/dist/serverGroup/configure/wizard/instanceType/advancedMode/InstanceTypeTableBody.d.ts +12 -0
  20. package/dist/serverGroup/configure/wizard/instanceType/advancedMode/InstanceTypeTableParts.d.ts +15 -0
  21. package/dist/serverGroup/configure/wizard/instanceType/advancedMode/InstancesDistribution.d.ts +8 -0
  22. package/dist/serverGroup/configure/wizard/instanceType/simpleMode/SimpleModeSelector.d.ts +8 -0
  23. package/dist/serverGroup/configure/wizard/pages/ServerGroupInstanceType.d.ts +9 -6
  24. package/dist/serverGroup/serverGroup.transformer.d.ts +2 -1
  25. package/package.json +5 -5
  26. package/src/domain/IAmazonLaunchTemplate.ts +1 -1
  27. package/src/domain/IAmazonServerGroup.ts +11 -0
  28. package/src/function/details/FunctionActions.spec.tsx +10 -9
  29. package/src/help/amazon.help.ts +9 -6
  30. package/src/image/image.reader.spec.ts +75 -0
  31. package/src/instance/awsInstanceType.service.spec.js +37 -71
  32. package/src/instance/awsInstanceType.service.ts +191 -0
  33. package/src/instance/awsInstanceTypes.ts +311 -0
  34. package/src/instance/details/CostFactor.tsx +29 -0
  35. package/src/instance/details/InstanceInformation.spec.tsx +2 -1
  36. package/src/instance/details/instance.details.controller.js +471 -473
  37. package/src/loadBalancer/details/loadBalancerDetails.controller.spec.ts +5 -3
  38. package/src/reactShims/aws.react.injector.ts +2 -1
  39. package/src/search/{searchResultFormatter.js → searchResultFormatter.ts} +5 -4
  40. package/src/serverGroup/configure/AmazonImageSelectInput.spec.tsx +10 -7
  41. package/src/serverGroup/configure/serverGroupCommandBuilder.aws.service.spec.js +302 -1
  42. package/src/serverGroup/configure/{serverGroupCommandBuilder.service.js → serverGroupCommandBuilder.service.ts} +182 -73
  43. package/src/serverGroup/configure/serverGroupCommandBuilder.spec.js +25 -8
  44. package/src/serverGroup/configure/serverGroupConfiguration.service.spec.ts +6 -13
  45. package/src/serverGroup/configure/serverGroupConfiguration.service.ts +53 -0
  46. package/src/serverGroup/configure/wizard/AmazonCloneServerGroupModal.tsx +1 -1
  47. package/src/serverGroup/configure/wizard/instanceType/CpuCreditsToggle.tsx +31 -31
  48. package/src/serverGroup/configure/wizard/instanceType/InstanceTypeSelector.tsx +133 -0
  49. package/src/serverGroup/configure/wizard/instanceType/advancedMode/AdvancedModeSelector.tsx +99 -0
  50. package/src/serverGroup/configure/wizard/instanceType/advancedMode/InstanceProfileSelector.tsx +36 -0
  51. package/src/serverGroup/configure/wizard/instanceType/advancedMode/InstanceTypeRow.tsx +144 -0
  52. package/src/serverGroup/configure/wizard/instanceType/advancedMode/InstanceTypeTable.tsx +138 -0
  53. package/src/serverGroup/configure/wizard/instanceType/advancedMode/InstanceTypeTableBody.tsx +135 -0
  54. package/src/serverGroup/configure/wizard/instanceType/advancedMode/InstanceTypeTableParts.tsx +137 -0
  55. package/src/serverGroup/configure/wizard/instanceType/advancedMode/InstancesDistribution.less +5 -0
  56. package/src/serverGroup/configure/wizard/instanceType/advancedMode/InstancesDistribution.tsx +108 -0
  57. package/src/serverGroup/configure/wizard/instanceType/advancedMode/advancedMode.less +110 -0
  58. package/src/serverGroup/configure/wizard/instanceType/simpleMode/SimpleModeSelector.tsx +62 -0
  59. package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.tsx +107 -62
  60. package/src/serverGroup/configure/wizard/pages/advancedSettings/ServerGroupAdvancedSettingsCommon.tsx +1 -1
  61. package/src/serverGroup/details/scalingPolicy/scalingPolicySummary.component.ts +6 -2
  62. package/src/serverGroup/details/scalingProcesses/AutoScalingProcessService.spec.ts +1 -2
  63. package/src/serverGroup/details/sections/InstancesDistributionDetailsSection.spec.tsx +8 -5
  64. package/src/serverGroup/details/sections/LaunchTemplateDetailsSection.spec.tsx +8 -5
  65. package/src/serverGroup/serverGroup.transformer.spec.ts +5 -3
  66. package/src/serverGroup/serverGroup.transformer.ts +24 -22
  67. package/src/subnet/SubnetSelectInput.spec.tsx +7 -4
  68. package/src/vpc/{VpcReader.spec.js → VpcReader.spec.ts} +2 -10
  69. package/src/vpc/VpcTag.spec.tsx +37 -0
  70. package/src/vpc/vpc.module.ts +6 -2
  71. package/dist/vpc/vpcTag.directive.d.ts +0 -2
  72. package/src/image/image.reader.spec.js +0 -123
  73. package/src/instance/awsInstanceType.service.js +0 -437
  74. package/src/vpc/vpcTag.directive.js +0 -34
  75. package/src/vpc/vpcTag.directive.spec.js +0 -60
@@ -1,6 +1,8 @@
1
+ import type { IQService } from 'angular';
1
2
  import * as angular from 'angular';
2
3
  import _ from 'lodash';
3
4
 
5
+ import type { Application, InstanceTypeService } from '@spinnaker/core';
4
6
  import {
5
7
  AccountService,
6
8
  DeploymentStrategyRegistry,
@@ -8,13 +10,45 @@ import {
8
10
  NameUtils,
9
11
  SubnetReader,
10
12
  } from '@spinnaker/core';
11
- import { AWSProviderSettings } from '../../aws.settings';
12
13
 
14
+ import { AWSProviderSettings } from '../../aws.settings';
15
+ import type { IAmazonLaunchTemplateOverrides, ILaunchTemplateData } from '../../domain';
16
+ import type { IAmazonServerGroup, IAmazonServerGroupView, INetworkInterface } from '../../domain';
17
+ import type {
18
+ AwsServerGroupConfigurationService,
19
+ IAmazonInstanceTypeOverride,
20
+ IAmazonServerGroupCommand,
21
+ IAmazonServerGroupCommandViewState,
22
+ IAmazonServerGroupDeployConfiguration,
23
+ } from './serverGroupConfiguration.service';
13
24
  import { AWS_SERVER_GROUP_CONFIGURATION_SERVICE } from './serverGroupConfiguration.service';
14
25
 
15
26
  export const AMAZON_SERVERGROUP_CONFIGURE_SERVERGROUPCOMMANDBUILDER_SERVICE =
16
27
  'spinnaker.amazon.serverGroupCommandBuilder.service';
17
28
  export const name = AMAZON_SERVERGROUP_CONFIGURE_SERVERGROUPCOMMANDBUILDER_SERVICE; // for backwards compatibility
29
+
30
+ export interface AwsServerGroupCommandBuilder {
31
+ buildNewServerGroupCommand(
32
+ application: Application,
33
+ defaults?: { account?: string; region?: string; subnet?: string; mode?: string },
34
+ ): PromiseLike<Partial<IAmazonServerGroupCommand>>;
35
+
36
+ buildServerGroupCommandFromExisting(
37
+ application: Application,
38
+ serverGroup: IAmazonServerGroupView,
39
+ mode?: string,
40
+ ): PromiseLike<Partial<IAmazonServerGroupCommand>>;
41
+
42
+ buildNewServerGroupCommandForPipeline(): PromiseLike<Partial<IAmazonServerGroupCommand>>;
43
+
44
+ buildServerGroupCommandFromPipeline(
45
+ application: Application,
46
+ originalCluster: IAmazonServerGroupDeployConfiguration,
47
+ ): PromiseLike<Partial<IAmazonServerGroupCommand>>;
48
+
49
+ buildUpdateServerGroupCommand(serverGroup: IAmazonServerGroup): Partial<IAmazonServerGroupCommand>;
50
+ }
51
+
18
52
  angular
19
53
  .module(AMAZON_SERVERGROUP_CONFIGURE_SERVERGROUPCOMMANDBUILDER_SERVICE, [
20
54
  INSTANCE_TYPE_SERVICE,
@@ -24,9 +58,15 @@ angular
24
58
  '$q',
25
59
  'instanceTypeService',
26
60
  'awsServerGroupConfigurationService',
27
- function ($q, instanceTypeService, awsServerGroupConfigurationService) {
28
- function buildNewServerGroupCommand(application, defaults) {
29
- defaults = defaults || {};
61
+ function (
62
+ $q: IQService,
63
+ instanceTypeService: InstanceTypeService,
64
+ awsServerGroupConfigurationService: AwsServerGroupConfigurationService,
65
+ ) {
66
+ function buildNewServerGroupCommand(
67
+ application: Application,
68
+ defaults: { account?: string; region?: string; subnet?: string; mode?: string } = {},
69
+ ) {
30
70
  const credentialsLoader = AccountService.getCredentialsKeyedByAccount('aws');
31
71
 
32
72
  const defaultCredentials =
@@ -45,14 +85,14 @@ angular
45
85
  .then(function ([preferredZones, credentialsKeyedByAccount]) {
46
86
  const credentials = credentialsKeyedByAccount[defaultCredentials];
47
87
  const keyPair = credentials ? credentials.defaultKeyPair : null;
48
- const applicationAwsSettings = _.get(application, 'attributes.providerSettings.aws', {});
88
+ const applicationAwsSettings = application.attributes?.providerSettings?.aws ?? {};
49
89
 
50
90
  let defaultIamRole = AWSProviderSettings.defaults.iamRole || 'BaseIAMRole';
51
91
  defaultIamRole = defaultIamRole.replace('{{application}}', application.name);
52
92
 
53
93
  const useAmiBlockDeviceMappings = applicationAwsSettings.useAmiBlockDeviceMappings || false;
54
94
 
55
- const command = {
95
+ const command: Partial<IAmazonServerGroupCommand> = {
56
96
  application: application.name,
57
97
  credentials: defaultCredentials,
58
98
  region: defaultRegion,
@@ -86,6 +126,7 @@ angular
86
126
  copySourceCustomBlockDeviceMappings: false, // default to using block device mappings from current instance type
87
127
  viewState: {
88
128
  instanceProfile: 'custom',
129
+ useSimpleInstanceTypeSelector: true,
89
130
  useAllImageSelection: false,
90
131
  useSimpleCapacity: true,
91
132
  usePreferredZones: true,
@@ -93,47 +134,46 @@ angular
93
134
  disableStrategySelection: true,
94
135
  dirty: {},
95
136
  submitButtonLabel: getSubmitButtonLabel(defaults.mode || 'create'),
96
- },
137
+ } as IAmazonServerGroupCommandViewState,
97
138
  };
98
139
 
99
- if (
100
- application.attributes &&
101
- application.attributes.platformHealthOnlyShowOverride &&
102
- application.attributes.platformHealthOnly
103
- ) {
140
+ if (application.attributes?.platformHealthOnlyShowOverride && application.attributes?.platformHealthOnly) {
104
141
  command.interestingHealthProviderNames = ['Amazon'];
105
142
  }
106
143
 
107
- if (
108
- defaultCredentials === 'test' &&
109
- AWSProviderSettings.serverGroups &&
110
- AWSProviderSettings.serverGroups.enableIPv6
111
- ) {
144
+ if (defaultCredentials === 'test' && AWSProviderSettings.serverGroups?.enableIPv6) {
112
145
  command.associateIPv6Address = true;
113
146
  }
114
147
 
115
- if (AWSProviderSettings.serverGroups && AWSProviderSettings.serverGroups.enableIMDSv2) {
148
+ if (AWSProviderSettings.serverGroups?.enableIMDSv2) {
116
149
  /**
117
150
  * Older SDKs do not support IMDSv2. A timestamp can be optionally configured at which any apps created after can safely default to using IMDSv2.
118
151
  */
119
152
  const appAgeRequirement = AWSProviderSettings.serverGroups.defaultIMDSv2AppAgeLimit;
120
- const creationDate = application.attributes && application.attributes.createTs;
153
+ const creationDate = application.attributes?.createTs;
121
154
 
122
- command.requireIMDSv2 =
123
- appAgeRequirement && creationDate && Number(creationDate) > appAgeRequirement ? true : false;
155
+ command.requireIMDSv2 = appAgeRequirement && creationDate && Number(creationDate) > appAgeRequirement;
124
156
  }
125
157
 
126
158
  return command;
127
159
  });
128
160
  }
129
161
 
130
- function buildServerGroupCommandFromPipeline(application, originalCluster) {
162
+ function buildServerGroupCommandFromPipeline(
163
+ application: Application,
164
+ originalCluster: IAmazonServerGroupDeployConfiguration,
165
+ ) {
131
166
  const pipelineCluster = _.cloneDeep(originalCluster);
132
167
  const region = Object.keys(pipelineCluster.availabilityZones)[0];
133
- const instanceTypeCategoryLoader = instanceTypeService.getCategoryForInstanceType(
168
+
169
+ const instanceTypes = pipelineCluster.launchTemplateOverridesForInstanceType
170
+ ? pipelineCluster.launchTemplateOverridesForInstanceType.map((o) => o.instanceType)
171
+ : [pipelineCluster.instanceType];
172
+ const instanceTypeCategoryLoader = instanceTypeService.getCategoryForMultipleInstanceTypes(
134
173
  'aws',
135
- pipelineCluster.instanceType,
174
+ instanceTypes,
136
175
  );
176
+
137
177
  const commandOptions = { account: pipelineCluster.account, region: region };
138
178
  const asyncLoader = $q.all([
139
179
  buildNewServerGroupCommand(application, commandOptions),
@@ -156,6 +196,7 @@ angular
156
196
  templatingEnabled: true,
157
197
  existingPipelineCluster: true,
158
198
  dirty: {},
199
+ useSimpleInstanceTypeSelector: isSimpleModeEnabled(pipelineCluster),
159
200
  };
160
201
 
161
202
  const viewOverrides = {
@@ -177,11 +218,11 @@ angular
177
218
  return $q.when({
178
219
  viewState: {
179
220
  requiresTemplateSelection: true,
180
- },
221
+ } as IAmazonServerGroupCommandViewState,
181
222
  });
182
223
  }
183
224
 
184
- function getSubmitButtonLabel(mode) {
225
+ function getSubmitButtonLabel(mode: string) {
185
226
  switch (mode) {
186
227
  case 'createPipeline':
187
228
  return 'Add';
@@ -194,38 +235,52 @@ angular
194
235
  }
195
236
  }
196
237
 
197
- function buildUpdateServerGroupCommand(serverGroup) {
198
- const command = {
238
+ function buildUpdateServerGroupCommand(serverGroup: IAmazonServerGroup) {
239
+ const command = ({
199
240
  type: 'modifyAsg',
200
241
  asgs: [{ asgName: serverGroup.name, region: serverGroup.region }],
201
242
  cooldown: serverGroup.asg.defaultCooldown,
202
- enabledMetrics: _.get(serverGroup, 'asg.enabledMetrics', []).map((m) => m.metric),
243
+ enabledMetrics: (serverGroup.asg?.enabledMetrics ?? []).map((m) => m.metric),
203
244
  healthCheckGracePeriod: serverGroup.asg.healthCheckGracePeriod,
204
245
  healthCheckType: serverGroup.asg.healthCheckType,
205
246
  terminationPolicies: angular.copy(serverGroup.asg.terminationPolicies),
206
247
  credentials: serverGroup.account,
207
248
  capacityRebalance: serverGroup.asg.capacityRebalance,
208
- };
249
+ } as Partial<IAmazonServerGroupCommand>) as IAmazonServerGroupCommand;
209
250
  awsServerGroupConfigurationService.configureUpdateCommand(command);
210
251
  return command;
211
252
  }
212
253
 
213
- function buildServerGroupCommandFromExisting(application, serverGroup, mode = 'clone') {
254
+ function buildServerGroupCommandFromExisting(
255
+ application: Application,
256
+ serverGroup: IAmazonServerGroupView,
257
+ mode = 'clone',
258
+ ) {
214
259
  const preferredZonesLoader = AccountService.getPreferredZonesByAccount('aws');
215
260
  const subnetsLoader = SubnetReader.listSubnets();
216
261
 
217
262
  const serverGroupName = NameUtils.parseServerGroupName(serverGroup.asg.autoScalingGroupName);
218
263
 
219
- const instanceType = serverGroup.launchConfig
220
- ? serverGroup.launchConfig.instanceType
221
- : serverGroup.launchTemplate
222
- ? serverGroup.launchTemplate.launchTemplateData.instanceType
223
- : null;
224
- const instanceTypeCategoryLoader = instanceTypeService.getCategoryForInstanceType('aws', instanceType);
264
+ let instanceTypes;
265
+ if (serverGroup.mixedInstancesPolicy) {
266
+ const ltOverrides = serverGroup.mixedInstancesPolicy?.launchTemplateOverridesForInstanceType;
267
+ // note: single launch template case is currently the only supported case for mixed instances policy
268
+ instanceTypes = ltOverrides
269
+ ? ltOverrides.map((o) => o.instanceType)
270
+ : [serverGroup.mixedInstancesPolicy?.launchTemplates[0]?.launchTemplateData?.instanceType];
271
+ } else if (serverGroup.launchTemplate) {
272
+ instanceTypes = [_.get(serverGroup, 'launchTemplate.launchTemplateData.instanceType')];
273
+ } else if (serverGroup.launchConfig) {
274
+ instanceTypes = [_.get(serverGroup, 'launchConfig.instanceType')];
275
+ }
276
+ const instanceTypeCategoryLoader = instanceTypeService.getCategoryForMultipleInstanceTypes(
277
+ 'aws',
278
+ instanceTypes,
279
+ );
225
280
 
226
281
  return $q
227
282
  .all([preferredZonesLoader, subnetsLoader, instanceTypeCategoryLoader])
228
- .then(function ([preferredZones, subnets, instanceProfile]) {
283
+ .then(([preferredZones, subnets, instanceProfile]) => {
229
284
  const zones = serverGroup.asg.availabilityZones.sort();
230
285
  let usePreferredZones = false;
231
286
  const preferredZonesForAccount = preferredZones[serverGroup.account];
@@ -237,10 +292,10 @@ angular
237
292
  // These processes should never be copied over, as the affect launching instances and enabling traffic
238
293
  const enabledProcesses = ['Launch', 'Terminate', 'AddToLoadBalancer'];
239
294
 
240
- const applicationAwsSettings = _.get(application, 'attributes.providerSettings.aws', {});
295
+ const applicationAwsSettings = application.attributes?.providerSettings?.aws ?? {};
241
296
  const useAmiBlockDeviceMappings = applicationAwsSettings.useAmiBlockDeviceMappings || false;
242
297
 
243
- const existingTags = {};
298
+ const existingTags: { [key: string]: string } = {};
244
299
  // These tags are applied by Clouddriver (if configured to do so), regardless of what the user might enter
245
300
  // Might be worth feature flagging this if it turns out other folks are hard-coding these values
246
301
  const reservedTags = ['spinnaker:application', 'spinnaker:stack', 'spinnaker:details'];
@@ -252,7 +307,7 @@ angular
252
307
  });
253
308
  }
254
309
 
255
- const command = {
310
+ const command: Partial<IAmazonServerGroupCommand> = {
256
311
  application: application.name,
257
312
  strategy: '',
258
313
  stack: serverGroupName.stack,
@@ -295,14 +350,10 @@ angular
295
350
  submitButtonLabel: getSubmitButtonLabel(mode),
296
351
  isNew: false,
297
352
  dirty: {},
298
- },
353
+ } as IAmazonServerGroupCommandViewState,
299
354
  };
300
355
 
301
- if (
302
- application.attributes &&
303
- application.attributes.platformHealthOnlyShowOverride &&
304
- application.attributes.platformHealthOnly
305
- ) {
356
+ if (application.attributes?.platformHealthOnlyShowOverride && application.attributes?.platformHealthOnly) {
306
357
  command.interestingHealthProviderNames = ['Amazon'];
307
358
  }
308
359
 
@@ -318,7 +369,7 @@ angular
318
369
  const vpcZoneIdentifier = serverGroup.asg.vpczoneIdentifier;
319
370
  if (vpcZoneIdentifier !== '') {
320
371
  const subnetId = vpcZoneIdentifier.split(',')[0];
321
- const subnet = _.chain(subnets).find({ id: subnetId }).value();
372
+ const subnet = subnets.find((x) => x.id === subnetId);
322
373
  command.subnetType = subnet.purpose;
323
374
  command.vpcId = subnet.vpcId;
324
375
  } else {
@@ -333,7 +384,8 @@ angular
333
384
  keyPair: serverGroup.launchConfig.keyName,
334
385
  associatePublicIpAddress: serverGroup.launchConfig.associatePublicIpAddress,
335
386
  ramdiskId: serverGroup.launchConfig.ramdiskId,
336
- instanceMonitoring: serverGroup.launchConfig.instanceMonitoring.enabled,
387
+ instanceMonitoring:
388
+ serverGroup.launchConfig.instanceMonitoring && serverGroup.launchConfig.instanceMonitoring.enabled,
337
389
  ebsOptimized: serverGroup.launchConfig.ebsOptimized,
338
390
  spotPrice: serverGroup.launchConfig.spotPrice,
339
391
  });
@@ -341,18 +393,46 @@ angular
341
393
  command.base64UserData = serverGroup.launchConfig.userData;
342
394
  }
343
395
  command.viewState.imageId = serverGroup.launchConfig.imageId;
396
+ command.viewState.useSimpleInstanceTypeSelector = true;
344
397
  }
345
398
 
346
- if (serverGroup.launchTemplate) {
347
- const { launchTemplateData } = serverGroup.launchTemplate;
348
- const maxPrice =
349
- launchTemplateData.instanceMarketOptions &&
350
- launchTemplateData.instanceMarketOptions.spotOptions &&
351
- launchTemplateData.instanceMarketOptions.spotOptions.maxPrice;
352
- const { ipv6AddressCount } =
353
- launchTemplateData.networkInterfaces &&
354
- launchTemplateData.networkInterfaces.length &&
355
- launchTemplateData.networkInterfaces[0];
399
+ if (serverGroup.launchTemplate || serverGroup.mixedInstancesPolicy) {
400
+ let launchTemplateData: ILaunchTemplateData, spotMaxPrice: string;
401
+ if (serverGroup.launchTemplate) {
402
+ launchTemplateData = serverGroup.launchTemplate.launchTemplateData;
403
+ spotMaxPrice = launchTemplateData.instanceMarketOptions?.spotOptions?.maxPrice;
404
+ command.instanceType = launchTemplateData.instanceType;
405
+ command.viewState.useSimpleInstanceTypeSelector = true;
406
+ }
407
+
408
+ if (serverGroup.mixedInstancesPolicy) {
409
+ const mip = serverGroup.mixedInstancesPolicy;
410
+ // note: single launch template case is currently the only supported case for mixed instances policy
411
+ launchTemplateData = mip?.launchTemplates?.[0]?.launchTemplateData;
412
+ spotMaxPrice = mip?.instancesDistribution?.spotMaxPrice;
413
+ command.securityGroups = launchTemplateData.networkInterfaces
414
+ ? (launchTemplateData.networkInterfaces.find((ni) => ni.deviceIndex === 0) ?? {}).groups
415
+ : launchTemplateData.securityGroups;
416
+ command.onDemandAllocationStrategy = mip.instancesDistribution.onDemandAllocationStrategy;
417
+ command.onDemandBaseCapacity = mip.instancesDistribution.onDemandBaseCapacity;
418
+ command.onDemandPercentageAboveBaseCapacity =
419
+ mip.instancesDistribution.onDemandPercentageAboveBaseCapacity;
420
+ command.spotAllocationStrategy = mip.instancesDistribution.spotAllocationStrategy;
421
+ command.spotInstancePools = mip.instancesDistribution.spotInstancePools;
422
+
423
+ // 'launchTemplateOverridesForInstanceType' is used for multiple instance types case, 'instanceType' is used for all other cases.
424
+ if (mip.launchTemplateOverridesForInstanceType) {
425
+ command.launchTemplateOverridesForInstanceType = getInstanceTypesWithPriority(
426
+ mip.launchTemplateOverridesForInstanceType,
427
+ );
428
+ } else {
429
+ command.instanceType = launchTemplateData.instanceType;
430
+ }
431
+
432
+ command.viewState.useSimpleInstanceTypeSelector = isSimpleModeEnabled(command);
433
+ }
434
+
435
+ const ipv6AddressCount = _.get(launchTemplateData, 'networkInterfaces[0]');
356
436
 
357
437
  const asgSettings = AWSProviderSettings.serverGroups;
358
438
  const isTestEnv = serverGroup.accountDetails && serverGroup.accountDetails.environment === 'test';
@@ -360,17 +440,14 @@ angular
360
440
  asgSettings && asgSettings.enableIPv6 && asgSettings.setIPv6InTest && isTestEnv;
361
441
 
362
442
  angular.extend(command, {
363
- instanceType: launchTemplateData.instanceType,
364
443
  iamRole: launchTemplateData.iamInstanceProfile.name,
365
444
  keyPair: launchTemplateData.keyName,
366
445
  associateIPv6Address: shouldAutoEnableIPv6 || Boolean(ipv6AddressCount),
367
446
  ramdiskId: launchTemplateData.ramdiskId,
368
- instanceMonitoring: launchTemplateData.monitoring.enabled,
447
+ instanceMonitoring: launchTemplateData.monitoring && launchTemplateData.monitoring.enabled,
369
448
  ebsOptimized: launchTemplateData.ebsOptimized,
370
- spotPrice: maxPrice || undefined,
371
- requireIMDSv2: Boolean(
372
- launchTemplateData.metadataOptions && launchTemplateData.metadataOptions.httpTokens === 'required',
373
- ),
449
+ spotPrice: spotMaxPrice || undefined,
450
+ requireIMDSv2: Boolean(launchTemplateData.metadataOptions?.httpTokens === 'required'),
374
451
  unlimitedCpuCredits: launchTemplateData.creditSpecification
375
452
  ? launchTemplateData.creditSpecification.cpuCredits === 'unlimited'
376
453
  : undefined,
@@ -393,8 +470,8 @@ angular
393
470
 
394
471
  if (serverGroup.launchTemplate && serverGroup.launchTemplate.launchTemplateData.networkInterfaces) {
395
472
  const networkInterface =
396
- serverGroup.launchTemplate.launchTemplateData.networkInterfaces.find((ni) => ni.deviceIndex === 0) ||
397
- {};
473
+ serverGroup.launchTemplate.launchTemplateData.networkInterfaces.find((ni) => ni.deviceIndex === 0) ??
474
+ ({} as INetworkInterface);
398
475
  command.securityGroups = networkInterface.groups;
399
476
  }
400
477
 
@@ -402,12 +479,44 @@ angular
402
479
  });
403
480
  }
404
481
 
482
+ // Since Deck allows changing priority of instance types via drag handle, fill priority field explicitly if empty
483
+ function getInstanceTypesWithPriority(
484
+ instanceTypeOverrides: IAmazonLaunchTemplateOverrides[],
485
+ ): IAmazonInstanceTypeOverride[] {
486
+ let explicitPriority = 1;
487
+ return _.sortBy(instanceTypeOverrides, ['priority']).map((override) => {
488
+ const { instanceType, weightedCapacity } = override;
489
+ let priority;
490
+ if (override.priority) {
491
+ priority = override.priority;
492
+ explicitPriority = override.priority + 1;
493
+ } else {
494
+ priority = explicitPriority++;
495
+ }
496
+ return { instanceType, weightedCapacity, priority };
497
+ });
498
+ }
499
+
500
+ function isSimpleModeEnabled(
501
+ command: IAmazonServerGroupDeployConfiguration | Partial<IAmazonServerGroupCommand>,
502
+ ) {
503
+ const isAdvancedModeEnabledInCommand =
504
+ command.onDemandAllocationStrategy ||
505
+ command.onDemandBaseCapacity ||
506
+ command.onDemandPercentageAboveBaseCapacity ||
507
+ command.spotAllocationStrategy ||
508
+ command.spotInstancePools ||
509
+ (command.launchTemplateOverridesForInstanceType && command.launchTemplateOverridesForInstanceType.length > 0);
510
+
511
+ return !isAdvancedModeEnabledInCommand;
512
+ }
513
+
405
514
  return {
406
- buildNewServerGroupCommand: buildNewServerGroupCommand,
407
- buildServerGroupCommandFromExisting: buildServerGroupCommandFromExisting,
408
- buildNewServerGroupCommandForPipeline: buildNewServerGroupCommandForPipeline,
409
- buildServerGroupCommandFromPipeline: buildServerGroupCommandFromPipeline,
410
- buildUpdateServerGroupCommand: buildUpdateServerGroupCommand,
411
- };
515
+ buildNewServerGroupCommand,
516
+ buildServerGroupCommandFromExisting,
517
+ buildNewServerGroupCommandForPipeline,
518
+ buildServerGroupCommandFromPipeline,
519
+ buildUpdateServerGroupCommand,
520
+ } as AwsServerGroupCommandBuilder;
412
521
  },
413
522
  ]);
@@ -4,7 +4,11 @@ import { AccountService, SubnetReader } from '@spinnaker/core';
4
4
 
5
5
  import { AWSProviderSettings } from '../../aws.settings';
6
6
 
7
- import { createMockAmazonServerGroupWithLt, createCustomMockLaunchTemplate } from '@spinnaker/mocks';
7
+ import {
8
+ createMockAmazonServerGroupWithLt,
9
+ createCustomMockLaunchTemplate,
10
+ mockLaunchTemplate,
11
+ } from '@spinnaker/mocks';
8
12
 
9
13
  describe('awsServerGroupCommandBuilder', function () {
10
14
  const AccountServiceFixture = require('./AccountServiceFixtures');
@@ -64,7 +68,7 @@ describe('awsServerGroupCommandBuilder', function () {
64
68
 
65
69
  describe('buildServerGroupCommandFromExisting', function () {
66
70
  it('sets usePreferredZones flag based on initial value', function () {
67
- spyOn(this.instanceTypeService, 'getCategoryForInstanceType').and.returnValue(this.$q.when('custom'));
71
+ spyOn(this.instanceTypeService, 'getCategoryForMultipleInstanceTypes').and.returnValue(this.$q.when('custom'));
68
72
  const baseServerGroup = {
69
73
  account: 'prod',
70
74
  region: 'us-west-1',
@@ -72,6 +76,7 @@ describe('awsServerGroupCommandBuilder', function () {
72
76
  availabilityZones: ['g', 'h', 'i'],
73
77
  vpczoneIdentifier: '',
74
78
  },
79
+ launchTemplate: mockLaunchTemplate,
75
80
  };
76
81
  let command = null;
77
82
 
@@ -101,7 +106,9 @@ describe('awsServerGroupCommandBuilder', function () {
101
106
  });
102
107
 
103
108
  it('sets profile and instance type if available', function () {
104
- spyOn(this.instanceTypeService, 'getCategoryForInstanceType').and.returnValue(this.$q.when('selectedProfile'));
109
+ spyOn(this.instanceTypeService, 'getCategoryForMultipleInstanceTypes').and.returnValue(
110
+ this.$q.when('selectedProfile'),
111
+ );
105
112
 
106
113
  const baseServerGroup = {
107
114
  account: 'prod',
@@ -131,7 +138,9 @@ describe('awsServerGroupCommandBuilder', function () {
131
138
  });
132
139
 
133
140
  it('copies suspended processes unless the mode is "editPipeline"', function () {
134
- spyOn(this.instanceTypeService, 'getCategoryForInstanceType').and.returnValue(this.$q.when('selectedProfile'));
141
+ spyOn(this.instanceTypeService, 'getCategoryForMultipleInstanceTypes').and.returnValue(
142
+ this.$q.when('selectedProfile'),
143
+ );
135
144
 
136
145
  const baseServerGroup = {
137
146
  account: 'prod',
@@ -167,7 +176,9 @@ describe('awsServerGroupCommandBuilder', function () {
167
176
  });
168
177
 
169
178
  it('copies tags not in the reserved list:', function () {
170
- spyOn(this.instanceTypeService, 'getCategoryForInstanceType').and.returnValue(this.$q.when('selectedProfile'));
179
+ spyOn(this.instanceTypeService, 'getCategoryForMultipleInstanceTypes').and.returnValue(
180
+ this.$q.when('selectedProfile'),
181
+ );
171
182
 
172
183
  const baseServerGroup = {
173
184
  account: 'prod',
@@ -208,7 +219,9 @@ describe('awsServerGroupCommandBuilder', function () {
208
219
  });
209
220
 
210
221
  it('sets unlimitedCpuCredits to false when building from source server group with standard credits', function () {
211
- spyOn(this.instanceTypeService, 'getCategoryForInstanceType').and.returnValue(this.$q.when('selectedProfile'));
222
+ spyOn(this.instanceTypeService, 'getCategoryForMultipleInstanceTypes').and.returnValue(
223
+ this.$q.when('selectedProfile'),
224
+ );
212
225
 
213
226
  const baseServerGroup = createMockAmazonServerGroupWithLt(
214
227
  createCustomMockLaunchTemplate('testLtCpuCredits', {
@@ -229,7 +242,9 @@ describe('awsServerGroupCommandBuilder', function () {
229
242
  });
230
243
 
231
244
  it('sets unlimitedCpuCredits to true when building from source server group with unlimited credits', function () {
232
- spyOn(this.instanceTypeService, 'getCategoryForInstanceType').and.returnValue(this.$q.when('selectedProfile'));
245
+ spyOn(this.instanceTypeService, 'getCategoryForMultipleInstanceTypes').and.returnValue(
246
+ this.$q.when('selectedProfile'),
247
+ );
233
248
 
234
249
  const baseServerGroup = createMockAmazonServerGroupWithLt(
235
250
  createCustomMockLaunchTemplate('testLtCpuCredits', {
@@ -250,7 +265,9 @@ describe('awsServerGroupCommandBuilder', function () {
250
265
  });
251
266
 
252
267
  it('sets unlimitedCpuCredits to undefined when building from source server group with cpu credits unset', function () {
253
- spyOn(this.instanceTypeService, 'getCategoryForInstanceType').and.returnValue(this.$q.when('selectedProfile'));
268
+ spyOn(this.instanceTypeService, 'getCategoryForMultipleInstanceTypes').and.returnValue(
269
+ this.$q.when('selectedProfile'),
270
+ );
254
271
 
255
272
  const baseServerGroup = createMockAmazonServerGroupWithLt();
256
273
 
@@ -1,19 +1,12 @@
1
- import { mock, IQService, IScope, IRootScopeService } from 'angular';
1
+ import type { IQService, IRootScopeService, IScope } from 'angular';
2
+ import { mock } from 'angular';
2
3
 
3
- import {
4
- AccountService,
5
- ApplicationModelBuilder,
6
- CacheInitializerService,
7
- LoadBalancerReader,
8
- SecurityGroupReader,
9
- SubnetReader,
10
- } from '@spinnaker/core';
4
+ import type { CacheInitializerService, LoadBalancerReader, SecurityGroupReader } from '@spinnaker/core';
5
+ import { AccountService, ApplicationModelBuilder, SubnetReader } from '@spinnaker/core';
11
6
 
12
7
  import { KeyPairsReader } from '../../keyPairs';
13
- import {
14
- AWS_SERVER_GROUP_CONFIGURATION_SERVICE,
15
- AwsServerGroupConfigurationService,
16
- } from './serverGroupConfiguration.service';
8
+ import type { AwsServerGroupConfigurationService } from './serverGroupConfiguration.service';
9
+ import { AWS_SERVER_GROUP_CONFIGURATION_SERVICE } from './serverGroupConfiguration.service';
17
10
 
18
11
  describe('Service: awsServerGroupConfiguration', function () {
19
12
  let service: AwsServerGroupConfigurationService,
@@ -80,15 +80,44 @@ export interface IAmazonServerGroupCommandBackingData extends IServerGroupComman
80
80
  }
81
81
 
82
82
  export interface IAmazonServerGroupCommandViewState extends IServerGroupCommandViewState {
83
+ isNew: boolean;
83
84
  dirty: IAmazonServerGroupCommandDirty;
84
85
  spelTargetGroups: string[];
85
86
  spelLoadBalancers: string[];
87
+ useSimpleInstanceTypeSelector: boolean;
88
+ }
89
+
90
+ export interface IAmazonInstanceTypeOverride {
91
+ instanceType: string;
92
+ weightedCapacity?: string;
93
+ priority?: number;
94
+ }
95
+
96
+ /**
97
+ * We model server group commands in two subtly different ways.
98
+ * It's unclear what the intention is, but we should converge on a single model eventually.
99
+ *
100
+ * This model is stored in a Aws Deploy Stage under `clusters[]`.
101
+ * It is also sent across the wire during a deploy task from infrastructure view
102
+ * (i.e., "clone server group", "create server group")
103
+ */
104
+ export interface IAmazonServerGroupDeployConfiguration extends IAmazonServerGroupCommand_Internal {
105
+ account: string;
106
+ availabilityZones: {
107
+ [region: string]: string[];
108
+ };
109
+ }
110
+
111
+ interface IAmazonServerGroupCommand_Internal extends IAmazonServerGroupCommand {
112
+ // widen 'string[]' to 'any' so we can narrow it to an incompatible type in IAmazonServerGroupDeployConfiguration
113
+ availabilityZones: any;
86
114
  }
87
115
 
88
116
  export interface IAmazonServerGroupCommand extends IServerGroupCommand {
89
117
  associateIPv6Address?: boolean;
90
118
  associatePublicIpAddress: boolean;
91
119
  backingData: IAmazonServerGroupCommandBackingData;
120
+ base64UserData: string;
92
121
  copySourceCustomBlockDeviceMappings: boolean;
93
122
  ebsOptimized: boolean;
94
123
  healthCheckGracePeriod: number;
@@ -103,12 +132,25 @@ export interface IAmazonServerGroupCommand extends IServerGroupCommand {
103
132
  setLaunchTemplate?: boolean;
104
133
  unlimitedCpuCredits?: boolean;
105
134
  capacityRebalance?: boolean;
135
+ ramdiskId?: string;
106
136
  viewState: IAmazonServerGroupCommandViewState;
137
+ source: {
138
+ account: string;
139
+ region: string;
140
+ asgName: string;
141
+ };
142
+ onDemandAllocationStrategy?: string;
143
+ onDemandBaseCapacity?: number;
144
+ onDemandPercentageAboveBaseCapacity?: number;
145
+ spotAllocationStrategy?: string;
146
+ spotInstancePools?: number;
147
+ launchTemplateOverridesForInstanceType?: IAmazonInstanceTypeOverride[];
107
148
 
108
149
  getBlockDeviceMappingsSource: (command: IServerGroupCommand) => IBlockDeviceMappingSource;
109
150
  selectBlockDeviceMappingsSource: (command: IServerGroupCommand, selection: string) => void;
110
151
  usePreferredZonesChanged: (command: IServerGroupCommand) => IAmazonServerGroupCommandResult;
111
152
  regionIsDeprecated: (command: IServerGroupCommand) => boolean;
153
+ launchTemplateOverridesChanged: (command: IServerGroupCommand) => void;
112
154
  }
113
155
 
114
156
  export class AwsServerGroupConfigurationService {
@@ -643,10 +685,21 @@ export class AwsServerGroupConfigurationService {
643
685
  cmd.imageChanged = (command: IAmazonServerGroupCommand): IServerGroupCommandResult =>
644
686
  this.configureInstanceTypes(command);
645
687
 
688
+ // Handles derived values for single instance type case
646
689
  cmd.instanceTypeChanged = (command: IAmazonServerGroupCommand): void => {
647
690
  command.ebsOptimized = this.awsInstanceTypeService.isEbsOptimized(command.instanceType);
648
691
  };
649
692
 
693
+ // Handles derived values for multiple instance types case
694
+ cmd.launchTemplateOverridesChanged = (command: IAmazonServerGroupCommand): void => {
695
+ // set ebsOptimized to true if all instance types in overrides are included in the hard-coded array in awsInstanceTypeService.
696
+ // this function exists for backwards compatibility with single instance type case, matching behavior in `cmd.instanceTypeChanged`
697
+ // note: this parameter has no effect on instance types with EBS optimization enabled by default.
698
+ command.ebsOptimized = command.launchTemplateOverridesForInstanceType?.every((o) =>
699
+ this.awsInstanceTypeService.isEbsOptimized(o.instanceType),
700
+ );
701
+ };
702
+
650
703
  this.applyOverrides('attachEventHandlers', cmd);
651
704
  }
652
705
  }
@@ -207,7 +207,7 @@ export class AmazonCloneServerGroupModal extends React.Component<
207
207
  />
208
208
 
209
209
  <WizardPage
210
- label="Instance Type"
210
+ label={command.viewState.useSimpleInstanceTypeSelector ? 'Instance Type' : 'Instance Types'}
211
211
  wizard={wizard}
212
212
  order={nextIdx()}
213
213
  render={({ innerRef }) => <ServerGroupInstanceType ref={innerRef} formik={formik} />}