Skip to content

Commit 46ac793

Browse files
author
Tarun Belani
committed
feat(imagebuilder): add support for EC2 Image Builder L2 Constructs - Infrastructure Configuration
1 parent e8c029f commit 46ac793

File tree

10 files changed

+807
-49
lines changed

10 files changed

+807
-49
lines changed

packages/@aws-cdk/aws-imagebuilder-alpha/awslint.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"exclude": [
33
"props-no-arn-refs:@aws-cdk/aws-imagebuilder-alpha.InfrastructureConfigurationProps.ec2InstanceHostResourceGroupArn"
44
]
5-
}
5+
}

packages/@aws-cdk/aws-imagebuilder-alpha/lib/infrastructure-configuration.ts

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export interface InfrastructureConfigurationLogging {
9494
/**
9595
* The S3 logging prefix to use for detailed build logging
9696
*
97-
* @default - No prefix
97+
* @default No prefix
9898
*/
9999
readonly s3KeyPrefix?: string;
100100
}
@@ -107,21 +107,21 @@ export interface InfrastructureConfigurationProps {
107107
* The name of the infrastructure configuration. This name must be normalized by transforming all alphabetical
108108
* characters to lowercase, and replacing all spaces and underscores with hyphens.
109109
*
110-
* @default - A name is generated
110+
* @default A name is generated
111111
*/
112112
readonly infrastructureConfigurationName?: string;
113113

114114
/**
115115
* The description of the infrastructure configuration.
116116
*
117-
* @default - None
117+
* @default None
118118
*/
119119
readonly description?: string;
120120

121121
/**
122122
* The instance types to launch build and test EC2 instances with.
123123
*
124-
* @default - Image Builder will choose from a default set of instance types compatible with the AMI
124+
* @default Image Builder will choose from a default set of instance types compatible with the AMI
125125
*/
126126
readonly instanceTypes?: ec2.InstanceType[];
127127

@@ -134,7 +134,7 @@ export interface InfrastructureConfigurationProps {
134134
* If an S3 logging bucket and key prefix is provided, an IAM inline policy will be attached to the instance profile's
135135
* role, allowing s3:PutObject permissions on the bucket.
136136
*
137-
* @default - An instance profile will be generated
137+
* @default An instance profile will be generated
138138
*/
139139
readonly instanceProfile?: iam.IInstanceProfile;
140140
/**
@@ -148,119 +148,119 @@ export interface InfrastructureConfigurationProps {
148148
* assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com')
149149
* });
150150
*
151-
* @default - A role will automatically be created, it can be accessed via the `role` property
151+
* @default A role will automatically be created, it can be accessed via the `role` property
152152
*/
153153
readonly role?: iam.IRole;
154154

155155
/**
156156
* The VPC to place the instance used to customize the AMI.
157157
*
158-
* @default - The default VPC will be used
158+
* @default The default VPC will be used
159159
*/
160160
readonly vpc?: ec2.IVpc;
161161

162162
/**
163163
* Select which subnet to place the instance used to customize the AMI. The first subnet that is selected will be used.
164164
* You must specify the VPC to customize the subnet selection.
165165
*
166-
* @default - The first subnet selected from the provided VPC will be used
166+
* @default The first subnet selected from the provided VPC will be used
167167
*/
168168
readonly subnetSelection?: ec2.SubnetSelection;
169169

170170
/**
171171
* The security groups to associate with the instance used to customize the AMI.
172172
*
173-
* @default - The default security group for the VPC will be used
173+
* @default The default security group for the VPC will be used
174174
*/
175175
readonly securityGroups?: ec2.ISecurityGroup[];
176176

177177
/**
178178
* The key pair used to connect to the build and test EC2 instances. The key pair can be used to log into the build
179179
* or test instances for troubleshooting any failures.
180180
*
181-
* @default - None
181+
* @default None
182182
*/
183183
readonly keyPair?: ec2.IKeyPair;
184184

185185
/**
186186
* Whether to terminate the EC2 instance when the build or test workflow fails.
187187
*
188-
* @default - true
188+
* @default true
189189
*/
190190
readonly terminateInstanceOnFailure?: boolean;
191191

192192
/**
193193
* The maximum number of hops that an instance metadata request can traverse to reach its destination. By default,
194194
* this is set to 2.
195195
*
196-
* @default - 2
196+
* @default 2
197197
*/
198198
readonly httpPutResponseHopLimit?: number;
199199

200200
/**
201201
* Indicates whether a signed token header is required for instance metadata retrieval requests. By default, this is
202202
* set to `required` to require IMDSv2 on build and test EC2 instances.
203203
*
204-
* @default - HttpTokens.REQUIRED
204+
* @default HttpTokens.REQUIRED
205205
*/
206206
readonly httpTokens?: HttpTokens;
207207

208208
/**
209209
* The SNS topic on which notifications are sent when an image build completes.
210210
*
211-
* @default - No notifications are sent
211+
* @default No notifications are sent
212212
*/
213213
readonly notificationTopic?: sns.ITopic;
214214

215215
/**
216216
* The log settings for detailed build logging.
217217
*
218-
* @default - None
218+
* @default None
219219
*/
220220
readonly logging?: InfrastructureConfigurationLogging;
221221

222222
/**
223223
* The availability zone to place Image Builder build and test EC2 instances.
224224
*
225-
* @default - EC2 will select a random zone
225+
* @default EC2 will select a random zone
226226
*/
227227
readonly ec2InstanceAvailabilityZone?: string;
228228

229229
/**
230230
* The ID of the Dedicated Host on which build and test instances run. This only applies if the instance tenancy is
231231
* `host`. This cannot be used with the `ec2InstanceHostResourceGroupArn` parameter.
232232
*
233-
* @default - None
233+
* @default None
234234
*/
235235
readonly ec2InstanceHostId?: string;
236236

237237
/**
238238
* The ARN of the host resource group on which build and test instances run. This only applies if the instance tenancy
239239
* is `host`. This cannot be used with the `ec2InstanceHostId` parameter.
240240
*
241-
* @default - None
241+
* @default None
242242
*/
243243
readonly ec2InstanceHostResourceGroupArn?: string;
244244

245245
/**
246246
* The tenancy of the instance. Dedicated tenancy runs instances on single-tenant hardware, while host tenancy runs
247247
* instances on a dedicated host. Shared tenancy is used by default.
248248
*
249-
* @default - Tenancy.DEFAULT
249+
* @default Tenancy.DEFAULT
250250
*/
251251
readonly ec2InstanceTenancy?: Tenancy;
252252

253253
/**
254254
* The additional tags to assign to the Amazon EC2 instance that Image Builder launches during the build process.
255255
*
256-
* @default - None
256+
* @default None
257257
*/
258258
readonly resourceTags?: { [key: string]: string };
259259

260260
/**
261261
* The tags to apply to the infrastructure configuration
262262
*
263-
* @default - None
263+
* @default None
264264
*/
265265
readonly tags?: { [key: string]: string };
266266
}
@@ -389,7 +389,7 @@ export class InfrastructureConfiguration extends InfrastructureConfigurationBase
389389
public readonly instanceProfile: iam.IInstanceProfile;
390390

391391
/**
392-
* The role associateded with the EC2 instance profile used for the build
392+
* The role associated with the EC2 instance profile used for the build
393393
*/
394394
public readonly role?: iam.IRole;
395395

@@ -463,6 +463,14 @@ export class InfrastructureConfiguration extends InfrastructureConfigurationBase
463463
);
464464
}
465465

466+
if (props.httpPutResponseHopLimit !== undefined && props.httpPutResponseHopLimit < 1) {
467+
throw new cdk.ValidationError('httpPutResponseHopLimit must be at least 1', this);
468+
}
469+
470+
if (props.httpPutResponseHopLimit !== undefined && props.httpPutResponseHopLimit > 64) {
471+
throw new cdk.ValidationError('httpPutResponseHopLimit must be at most 64', this);
472+
}
473+
466474
if (!props.instanceProfile && !props.role) {
467475
this.autoGeneratedInstanceProfileRole = new iam.Role(this, 'InstanceProfileRole', {
468476
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
@@ -481,16 +489,27 @@ export class InfrastructureConfiguration extends InfrastructureConfigurationBase
481489
this.role = this.instanceProfile.role;
482490
this.logBucket = props.logging?.s3Bucket;
483491

484-
if (this.logBucket && this.role && props.logging?.s3KeyPrefix !== undefined) {
485-
this.logBucket.grantPut(this.role, `${props.logging.s3KeyPrefix}/*`);
492+
if (this.logBucket && this.role) {
493+
this.logBucket.grantPut(this.role, props.logging?.s3KeyPrefix ? `${props.logging.s3KeyPrefix}/*` : '*');
486494
}
487495

488-
const placement: CfnInfrastructureConfiguration.PlacementProperty = {
489-
...(props.ec2InstanceAvailabilityZone && { availabilityZone: props.ec2InstanceAvailabilityZone }),
490-
...(props.ec2InstanceHostId && { hostId: props.ec2InstanceHostId }),
491-
...(props.ec2InstanceHostResourceGroupArn && { hostResourceGroupArn: props.ec2InstanceHostResourceGroupArn }),
492-
...(props.ec2InstanceTenancy && { tenancy: props.ec2InstanceTenancy }),
493-
};
496+
const hasPlacementOptions =
497+
props.ec2InstanceAvailabilityZone !== undefined ||
498+
props.ec2InstanceHostId !== undefined ||
499+
props.ec2InstanceHostResourceGroupArn !== undefined ||
500+
props.ec2InstanceTenancy !== undefined;
501+
const placement: CfnInfrastructureConfiguration.PlacementProperty | undefined = hasPlacementOptions
502+
? {
503+
...(props.ec2InstanceAvailabilityZone !== undefined && {
504+
availabilityZone: props.ec2InstanceAvailabilityZone,
505+
}),
506+
...(props.ec2InstanceHostId !== undefined && { hostId: props.ec2InstanceHostId }),
507+
...(props.ec2InstanceHostResourceGroupArn !== undefined && {
508+
hostResourceGroupArn: props.ec2InstanceHostResourceGroupArn,
509+
}),
510+
...(props.ec2InstanceTenancy !== undefined && { tenancy: props.ec2InstanceTenancy }),
511+
}
512+
: undefined;
494513

495514
const infrastructureConfiguration = new CfnInfrastructureConfiguration(this, 'Resource', {
496515
name: this.physicalName,
@@ -512,7 +531,7 @@ export class InfrastructureConfiguration extends InfrastructureConfigurationBase
512531
},
513532
},
514533
}),
515-
placement: Object.keys(placement).length ? placement : undefined,
534+
...(placement && { placement }),
516535
resourceTags: props.resourceTags,
517536
securityGroupIds: props.securityGroups?.length
518537
? props.securityGroups?.map((securityGroup) => securityGroup.securityGroupId)

packages/@aws-cdk/aws-imagebuilder-alpha/test/infrastructure-configuration.test.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -571,19 +571,39 @@ describe('Infrastructure Configuration', () => {
571571
});
572572
});
573573

574-
test('no permissions are granted to instance profile role when a key prefix is not provided', () => {
574+
test('correct permissions granted to instance profile role when a key prefix is not provided', () => {
575575
const instanceProfile = iam.InstanceProfile.fromInstanceProfileAttributes(stack, 'InstanceProfile', {
576576
instanceProfileArn: 'arn:aws:iam::123456789012:instance-profile/EC2InstanceProfileForImageBuilder',
577577
role: iam.Role.fromRoleName(stack, 'Role', 'EC2InstanceProfileForImageBuilderRole'),
578578
});
579579
new InfrastructureConfiguration(stack, 'InfrastructureConfiguration', {
580580
instanceProfile,
581-
logging: {
582-
s3Bucket: s3.Bucket.fromBucketName(stack, 'S3Bucket', 'imagebuilder-logging-bucket'),
583-
},
581+
logging: { s3Bucket: s3.Bucket.fromBucketName(stack, 'S3Bucket', 'imagebuilder-logging-bucket') },
584582
});
585583

586-
Template.fromStack(stack).resourceCountIs('AWS::IAM::Policy', 0);
584+
Template.fromStack(stack).templateMatches({
585+
Resources: {
586+
RolePolicy72E7D967: Match.objectEquals({
587+
Type: 'AWS::IAM::Policy',
588+
Properties: {
589+
PolicyName: 'RolePolicy72E7D967',
590+
PolicyDocument: {
591+
Version: '2012-10-17',
592+
Statement: [
593+
{
594+
Effect: 'Allow',
595+
Action: Match.arrayWith(['s3:PutObject']),
596+
Resource: {
597+
'Fn::Join': ['', ['arn:', { Ref: 'AWS::Partition' }, ':s3:::imagebuilder-logging-bucket/*']],
598+
},
599+
},
600+
],
601+
},
602+
Roles: ['EC2InstanceProfileForImageBuilderRole'],
603+
},
604+
}),
605+
},
606+
});
587607
});
588608

589609
test('grants read access to IAM roles', () => {
@@ -736,7 +756,7 @@ describe('Infrastructure Configuration', () => {
736756
}).toThrow(cdk.ValidationError);
737757
});
738758

739-
test('throws a validation error an a subnet selection is provided without a VPC', () => {
759+
test('throws a validation error when a subnet selection is provided without a VPC', () => {
740760
expect(() => {
741761
new InfrastructureConfiguration(stack, 'InfrastructureConfiguration', {
742762
subnetSelection: {
@@ -810,4 +830,20 @@ describe('Infrastructure Configuration', () => {
810830
});
811831
}).toThrow(cdk.ValidationError);
812832
});
833+
834+
test('throws a validation error when httpPutResponseHopLimit is below the limit', () => {
835+
expect(() => {
836+
new InfrastructureConfiguration(stack, 'InfrastructureConfiguration', {
837+
httpPutResponseHopLimit: 0,
838+
});
839+
}).toThrow(cdk.ValidationError);
840+
});
841+
842+
test('throws a validation error when httpPutResponseHopLimit is above the limit', () => {
843+
expect(() => {
844+
new InfrastructureConfiguration(stack, 'InfrastructureConfiguration', {
845+
httpPutResponseHopLimit: 65,
846+
});
847+
}).toThrow(cdk.ValidationError);
848+
});
813849
});

0 commit comments

Comments
 (0)