Skip to content

Commit 1fccba3

Browse files
committed
feat(ecs): add capacityOptionType (SPOT) to ManagedInstances config
1 parent 13bceb9 commit 1fccba3

File tree

6 files changed

+721
-2
lines changed

6 files changed

+721
-2
lines changed

packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.managedinstances-capacity-provider.js.snapshot/integ-managedinstances-capacity-provider.template.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@
547547
]
548548
},
549549
"InstanceLaunchTemplate": {
550+
"CapacityOptionType": "SPOT",
550551
"Ec2InstanceProfileArn": {
551552
"Fn::GetAtt": [
552553
"InstanceProfile9F2F41CB",

packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/integ.managedinstances-capacity-provider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const fmiSecurityGroup = new ec2.SecurityGroup(stack, 'ManagedInstancesSecurityG
4949
// Create MI Capacity Provider
5050
const miCapacityProvider = new ecs.ManagedInstancesCapacityProvider(stack, 'ManagedInstancesCapacityProvider', {
5151
infrastructureRole: infrastructureRole,
52+
capacityOptionType: ecs.CapacityOptionType.SPOT,
5253
ec2InstanceProfile: instanceProfile,
5354
subnets: vpc.privateSubnets,
5455
securityGroups: [fmiSecurityGroup],

packages/aws-cdk-lib/aws-ecs/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1675,6 +1675,12 @@ declare const vpc: ec2.Vpc;
16751675
const cluster = new ecs.Cluster(this, 'Cluster', { vpc });
16761676

16771677
const miCapacityProvider = new ecs.ManagedInstancesCapacityProvider(this, 'MICapacityProvider', {
1678+
<<<<<<< HEAD
1679+
=======
1680+
infrastructureRole,
1681+
capacityOptionType: ecs.CapacityOptionType.SPOT, // Optionally specify whether capacity should be ON_DEMAND or SPOT
1682+
ec2InstanceProfile: instanceProfile,
1683+
>>>>>>> 0c4467efa0 (feat(ecs): add capacityOptionType (SPOT) to ManagedInstances config)
16781684
subnets: vpc.privateSubnets,
16791685
instanceRequirements: {
16801686
vCpuCountMin: 1,

packages/aws-cdk-lib/aws-ecs/lib/cluster.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1493,6 +1493,21 @@ export enum PropagateManagedInstancesTags {
14931493
NONE = 'NONE',
14941494
}
14951495

1496+
/**
1497+
* The capacity option type for instances launched by a Managed Instances Capacity Provider.
1498+
*/
1499+
export enum CapacityOptionType {
1500+
/**
1501+
* Launch instances as On-Demand instances
1502+
*/
1503+
ON_DEMAND = 'ON_DEMAND',
1504+
1505+
/**
1506+
* Launch instances as Spot instances
1507+
*/
1508+
SPOT = 'SPOT',
1509+
}
1510+
14961511
/**
14971512
* The options for creating a Managed Instances Capacity Provider.
14981513
*/
@@ -1589,6 +1604,14 @@ export interface ManagedInstancesCapacityProviderProps {
15891604
* @default PropagateManagedInstancesTags.NONE - no tag propagation
15901605
*/
15911606
readonly propagateTags?: PropagateManagedInstancesTags;
1607+
1608+
/**
1609+
* Specifies the capacity option type for instances launched by this capacity provider.
1610+
* This determines whether instances are launched as On-Demand or Spot instances.
1611+
*
1612+
* @default - not specified, ECS will use default behavior
1613+
*/
1614+
readonly capacityOptionType?: CapacityOptionType;
15921615
}
15931616

15941617
/**
@@ -1680,7 +1703,8 @@ export class ManagedInstancesCapacityProvider extends Construct implements ec2.I
16801703
const managedInstancesProviderConfig: CfnCapacityProvider.ManagedInstancesProviderProperty = {
16811704
infrastructureRoleArn: this.infrastructureRole.roleArn,
16821705
instanceLaunchTemplate: {
1683-
ec2InstanceProfileArn: this.ec2InstanceProfile.instanceProfileArn,
1706+
capacityOptionType: props.capacityOptionType,
1707+
ec2InstanceProfileArn: props.ec2InstanceProfile.instanceProfileArn,
16841708
networkConfiguration: {
16851709
subnets: props.subnets.map((subnet: ec2.ISubnet) => subnet.subnetId),
16861710
...(props.securityGroups && {

packages/aws-cdk-lib/aws-ecs/test/cluster.test.ts

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2902,7 +2902,7 @@ describe('cluster', () => {
29022902
});
29032903
});
29042904

2905-
test('with capacity option type', () => {
2905+
test('with capacity option type ON_DEMAND', () => {
29062906
// GIVEN
29072907
const app = new cdk.App();
29082908
const stack = new cdk.Stack(app, 'test');
@@ -2931,6 +2931,7 @@ describe('cluster', () => {
29312931
infrastructureRole,
29322932
ec2InstanceProfile: instanceProfile,
29332933
subnets: vpc.privateSubnets,
2934+
capacityOptionType: ecs.CapacityOptionType.ON_DEMAND,
29342935
});
29352936

29362937
// THEN
@@ -2943,6 +2944,7 @@ describe('cluster', () => {
29432944
],
29442945
},
29452946
InstanceLaunchTemplate: {
2947+
CapacityOptionType: 'ON_DEMAND',
29462948
Ec2InstanceProfileArn: {
29472949
'Fn::GetAtt': [
29482950
Match.stringLikeRegexp('InstanceProfile'),
@@ -2960,6 +2962,125 @@ describe('cluster', () => {
29602962
});
29612963
});
29622964

2965+
test('with capacity option type SPOT', () => {
2966+
// GIVEN
2967+
const app = new cdk.App();
2968+
const stack = new cdk.Stack(app, 'test');
2969+
const vpc = new ec2.Vpc(stack, 'Vpc');
2970+
2971+
const infrastructureRole = new iam.Role(stack, 'InfrastructureRole', {
2972+
assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'),
2973+
managedPolicies: [
2974+
iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'),
2975+
],
2976+
});
2977+
2978+
const instanceRole = new iam.Role(stack, 'InstanceRole', {
2979+
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
2980+
managedPolicies: [
2981+
iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'),
2982+
],
2983+
});
2984+
2985+
const instanceProfile = new iam.InstanceProfile(stack, 'InstanceProfile', {
2986+
role: instanceRole,
2987+
});
2988+
2989+
// WHEN
2990+
new ecs.ManagedInstancesCapacityProvider(stack, 'provider', {
2991+
infrastructureRole,
2992+
ec2InstanceProfile: instanceProfile,
2993+
subnets: vpc.privateSubnets,
2994+
capacityOptionType: ecs.CapacityOptionType.SPOT,
2995+
});
2996+
2997+
// THEN
2998+
Template.fromStack(stack).hasResourceProperties('AWS::ECS::CapacityProvider', {
2999+
ManagedInstancesProvider: {
3000+
InfrastructureRoleArn: {
3001+
'Fn::GetAtt': [
3002+
Match.stringLikeRegexp('InfrastructureRole'),
3003+
'Arn',
3004+
],
3005+
},
3006+
InstanceLaunchTemplate: {
3007+
CapacityOptionType: 'SPOT',
3008+
Ec2InstanceProfileArn: {
3009+
'Fn::GetAtt': [
3010+
Match.stringLikeRegexp('InstanceProfile'),
3011+
'Arn',
3012+
],
3013+
},
3014+
NetworkConfiguration: {
3015+
Subnets: [
3016+
{ Ref: 'VpcPrivateSubnet1Subnet536B997A' },
3017+
{ Ref: 'VpcPrivateSubnet2Subnet3788AAA1' },
3018+
],
3019+
},
3020+
},
3021+
},
3022+
});
3023+
});
3024+
3025+
test('without capacity option type does not include property in template', () => {
3026+
// GIVEN
3027+
const app = new cdk.App();
3028+
const stack = new cdk.Stack(app, 'test');
3029+
const vpc = new ec2.Vpc(stack, 'Vpc');
3030+
3031+
const infrastructureRole = new iam.Role(stack, 'InfrastructureRole', {
3032+
assumedBy: new iam.ServicePrincipal('ecs.amazonaws.com'),
3033+
managedPolicies: [
3034+
iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'),
3035+
],
3036+
});
3037+
3038+
const instanceRole = new iam.Role(stack, 'InstanceRole', {
3039+
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
3040+
managedPolicies: [
3041+
iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'),
3042+
],
3043+
});
3044+
3045+
const instanceProfile = new iam.InstanceProfile(stack, 'InstanceProfile', {
3046+
role: instanceRole,
3047+
});
3048+
3049+
// WHEN
3050+
new ecs.ManagedInstancesCapacityProvider(stack, 'provider', {
3051+
infrastructureRole,
3052+
ec2InstanceProfile: instanceProfile,
3053+
subnets: vpc.privateSubnets,
3054+
});
3055+
3056+
// THEN
3057+
Template.fromStack(stack).hasResourceProperties('AWS::ECS::CapacityProvider', {
3058+
ManagedInstancesProvider: {
3059+
InfrastructureRoleArn: {
3060+
'Fn::GetAtt': [
3061+
Match.stringLikeRegexp('InfrastructureRole'),
3062+
'Arn',
3063+
],
3064+
},
3065+
InstanceLaunchTemplate: {
3066+
Ec2InstanceProfileArn: {
3067+
'Fn::GetAtt': [
3068+
Match.stringLikeRegexp('InstanceProfile'),
3069+
'Arn',
3070+
],
3071+
},
3072+
NetworkConfiguration: {
3073+
Subnets: [
3074+
{ Ref: 'VpcPrivateSubnet1Subnet536B997A' },
3075+
{ Ref: 'VpcPrivateSubnet2Subnet3788AAA1' },
3076+
],
3077+
},
3078+
CapacityOptionType: Match.absent(),
3079+
},
3080+
},
3081+
});
3082+
});
3083+
29633084
test('with instance requirements', () => {
29643085
// GIVEN
29653086
const app = new cdk.App();

0 commit comments

Comments
 (0)