Skip to content

Commit 6e987ca

Browse files
author
Tarun Belani
committed
Addressed review comments
1 parent 01b96fc commit 6e987ca

13 files changed

+740
-47
lines changed

packages/@aws-cdk/aws-imagebuilder-alpha/lib/image-pipeline.ts

Lines changed: 147 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,46 @@ export interface IImagePipeline extends cdk.IResource {
8686
*/
8787
onCVEDetected(id: string, options?: events.OnEventOptions): events.Rule;
8888

89+
/**
90+
* Creates an EventBridge rule for Image Builder image state change events.
91+
*
92+
* @param id Unique identifier for the rule
93+
* @param options Configuration options for the event rule
94+
*
95+
* @see https://docs.aws.amazon.com/imagebuilder/latest/userguide/integ-eventbridge.html
96+
*/
97+
onImageBuildStateChange(id: string, options?: events.OnEventOptions): events.Rule;
98+
99+
/**
100+
* Creates an EventBridge rule for Image Builder image build completion events.
101+
*
102+
* @param id Unique identifier for the rule
103+
* @param options Configuration options for the event rule
104+
*
105+
* @see https://docs.aws.amazon.com/imagebuilder/latest/userguide/integ-eventbridge.html
106+
*/
107+
onImageBuildCompleted(id: string, options?: events.OnEventOptions): events.Rule;
108+
109+
/**
110+
* Creates an EventBridge rule for Image Builder image build failure events.
111+
*
112+
* @param id Unique identifier for the rule
113+
* @param options Configuration options for the event rule
114+
*
115+
* @see https://docs.aws.amazon.com/imagebuilder/latest/userguide/integ-eventbridge.html
116+
*/
117+
onImageBuildFailed(id: string, options?: events.OnEventOptions): events.Rule;
118+
119+
/**
120+
* Creates an EventBridge rule for Image Builder image success events.
121+
*
122+
* @param id Unique identifier for the rule
123+
* @param options Configuration options for the event rule
124+
*
125+
* @see https://docs.aws.amazon.com/imagebuilder/latest/userguide/integ-eventbridge.html
126+
*/
127+
onImageBuildSucceeded(id: string, options?: events.OnEventOptions): events.Rule;
128+
89129
/**
90130
* Creates an EventBridge rule for Image Builder image pipeline automatically disabled events.
91131
*
@@ -95,6 +135,16 @@ export interface IImagePipeline extends cdk.IResource {
95135
* @see https://docs.aws.amazon.com/imagebuilder/latest/userguide/integ-eventbridge.html
96136
*/
97137
onImagePipelineAutoDisabled(id: string, options?: events.OnEventOptions): events.Rule;
138+
139+
/**
140+
* Creates an EventBridge rule for Image Builder wait for action events
141+
*
142+
* @param id Unique identifier for the rule
143+
* @param options Configuration options for the event rule
144+
*
145+
* @see https://docs.aws.amazon.com/imagebuilder/latest/userguide/integ-eventbridge.html
146+
*/
147+
onWaitForAction(id: string, options?: events.OnEventOptions): events.Rule;
98148
}
99149

100150
/**
@@ -371,7 +421,7 @@ abstract class ImagePipelineBase extends cdk.Resource implements IImagePipeline
371421
rule.addTarget(options.target);
372422
rule.addEventPattern({
373423
source: ['aws.imagebuilder'],
374-
resources: [this.imagePipelineArn],
424+
...(options.eventPattern?.resources?.length && { resources: options.eventPattern.resources }),
375425
...(options.eventPattern?.detailType?.length && { detailType: options.eventPattern.detailType }),
376426
...(options.eventPattern?.detail !== undefined && { detail: options.eventPattern.detail }),
377427
});
@@ -387,7 +437,83 @@ abstract class ImagePipelineBase extends cdk.Resource implements IImagePipeline
387437
* @see https://docs.aws.amazon.com/imagebuilder/latest/userguide/integ-eventbridge.html
388438
*/
389439
public onCVEDetected(id: string, options: events.OnEventOptions = {}): events.Rule {
390-
return this.onEvent(id, { ...options, eventPattern: { detailType: ['EC2 Image Builder CVE Detected'] } });
440+
return this.onEvent(id, {
441+
...options,
442+
eventPattern: {
443+
...options.eventPattern,
444+
detailType: ['EC2 Image Builder CVE Detected'],
445+
resources: [this.imagePipelineArn],
446+
},
447+
});
448+
}
449+
450+
/**
451+
* Creates an EventBridge rule for Image Builder image state change events.
452+
*
453+
* @param id Unique identifier for the rule
454+
* @param options Configuration options for the event rule
455+
*
456+
* @see https://docs.aws.amazon.com/imagebuilder/latest/userguide/integ-eventbridge.html
457+
*/
458+
public onImageBuildStateChange(id: string, options: events.OnEventOptions = {}): events.Rule {
459+
return this.onEvent(id, {
460+
...options,
461+
eventPattern: { ...options.eventPattern, detailType: ['EC2 Image Builder Image State Change'] },
462+
});
463+
}
464+
465+
/**
466+
* Creates an EventBridge rule for Image Builder image build completion events.
467+
*
468+
* @param id Unique identifier for the rule
469+
* @param options Configuration options for the event rule
470+
*
471+
* @see https://docs.aws.amazon.com/imagebuilder/latest/userguide/integ-eventbridge.html
472+
*/
473+
public onImageBuildCompleted(id: string, options: events.OnEventOptions = {}): events.Rule {
474+
return this.onImageBuildStateChange(id, {
475+
...options,
476+
eventPattern: {
477+
...options.eventPattern,
478+
detail: { ...options.eventPattern?.detail, state: { status: ['AVAILABLE', 'CANCELLED', 'FAILED'] } },
479+
},
480+
});
481+
}
482+
483+
/**
484+
* Creates an EventBridge rule for Image Builder image build failure events.
485+
*
486+
* @param id Unique identifier for the rule
487+
* @param options Configuration options for the event rule
488+
*
489+
* @see https://docs.aws.amazon.com/imagebuilder/latest/userguide/integ-eventbridge.html
490+
*/
491+
public onImageBuildFailed(id: string, options: events.OnEventOptions = {}): events.Rule {
492+
return this.onImageBuildStateChange(id, {
493+
...options,
494+
eventPattern: {
495+
...options.eventPattern,
496+
detail: { ...options.eventPattern?.detail, state: { status: ['FAILED'] } },
497+
},
498+
});
499+
}
500+
501+
/**
502+
* Creates an EventBridge rule for Image Builder image success events.
503+
*
504+
* @param id Unique identifier for the rule
505+
* @param options Configuration options for the event rule
506+
*
507+
* @see https://docs.aws.amazon.com/imagebuilder/latest/userguide/integ-eventbridge.html
508+
*/
509+
public onImageBuildSucceeded(id: string, options: events.OnEventOptions = {}): events.Rule {
510+
return this.onImageBuildStateChange(id, {
511+
...options,
512+
eventPattern: {
513+
...options.eventPattern,
514+
detail: { ...options.eventPattern?.detail, state: { status: ['AVAILABLE'] } },
515+
},
516+
});
391517
}
392518

393519
/**
@@ -401,7 +527,25 @@ abstract class ImagePipelineBase extends cdk.Resource implements IImagePipeline
401527
public onImagePipelineAutoDisabled(id: string, options: events.OnEventOptions = {}): events.Rule {
402528
return this.onEvent(id, {
403529
...options,
404-
eventPattern: { detailType: ['EC2 Image Builder Image Pipeline Automatically Disabled'] },
530+
eventPattern: {
531+
detailType: ['EC2 Image Builder Image Pipeline Automatically Disabled'],
532+
resources: [this.imagePipelineArn],
533+
},
534+
});
535+
}
536+
537+
/**
538+
* Creates an EventBridge rule for Image Builder wait for action events
539+
*
540+
* @param id Unique identifier for the rule
541+
* @param options Configuration options for the event rule
542+
*
543+
* @see https://docs.aws.amazon.com/imagebuilder/latest/userguide/integ-eventbridge.html
544+
*/
545+
public onWaitForAction(id: string, options: events.OnEventOptions = {}): events.Rule {
546+
return this.onEvent(id, {
547+
...options,
548+
eventPattern: { ...options.eventPattern, detailType: ['EC2 Image Builder Workflow Step Waiting'] },
405549
});
406550
}
407551
}

packages/@aws-cdk/aws-imagebuilder-alpha/lib/private/policy-helper.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ export const defaultExecutionRolePolicy = <PropsT extends ImagePipelineProps>(
1717
scope: Construct,
1818
props?: PropsT,
1919
): iam.PolicyStatement[] => {
20-
const partition = cdk.Stack.of(scope).partition;
20+
const stack = cdk.Stack.of(scope);
21+
const partition = stack.partition;
22+
const region = stack.region;
23+
const account = stack.account;
2124

2225
// Permissions are identical to https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSServiceRoleForImageBuilder.html
2326
// SLR policies cannot be attached to roles, and no managed policy exists for these permissions yet
@@ -147,7 +150,7 @@ export const defaultExecutionRolePolicy = <PropsT extends ImagePipelineProps>(
147150
new iam.PolicyStatement({
148151
effect: iam.Effect.ALLOW,
149152
actions: ['sns:Publish'],
150-
resources: ['*'],
153+
resources: [`arn:${partition}:sns:${region}:${account}:*`],
151154
}),
152155
new iam.PolicyStatement({
153156
effect: iam.Effect.ALLOW,
@@ -285,7 +288,7 @@ export const defaultExecutionRolePolicy = <PropsT extends ImagePipelineProps>(
285288
new iam.PolicyStatement({
286289
effect: iam.Effect.ALLOW,
287290
actions: ['license-manager:UpdateLicenseSpecificationsForResource'],
288-
resources: ['*'],
291+
resources: [`arn:${partition}:license-manager:*:${account}:license-configuration:*`],
289292
}),
290293
new iam.PolicyStatement({
291294
effect: iam.Effect.ALLOW,
@@ -294,14 +297,14 @@ export const defaultExecutionRolePolicy = <PropsT extends ImagePipelineProps>(
294297
}),
295298
new iam.PolicyStatement({
296299
effect: iam.Effect.ALLOW,
297-
actions: [
298-
'ec2:CreateLaunchTemplateVersion',
299-
'ec2:DescribeLaunchTemplates',
300-
'ec2:ModifyLaunchTemplate',
301-
'ec2:DescribeLaunchTemplateVersions',
302-
],
300+
actions: ['ec2:DescribeLaunchTemplates', 'ec2:DescribeLaunchTemplateVersions'],
303301
resources: ['*'],
304302
}),
303+
new iam.PolicyStatement({
304+
effect: iam.Effect.ALLOW,
305+
actions: ['ec2:CreateLaunchTemplateVersion', 'ec2:ModifyLaunchTemplate'],
306+
resources: [`arn:${partition}:ec2:${region}:${account}:launch-template/*`],
307+
}),
305308
new iam.PolicyStatement({
306309
effect: iam.Effect.ALLOW,
307310
actions: ['ec2:ExportImage'],

packages/@aws-cdk/aws-imagebuilder-alpha/test/image-pipeline.test.ts

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,25 @@ describe('ImagePipeline', () => {
127127
imageTestsEnabled: false,
128128
imageScanningEnabled: true,
129129
});
130-
imagePipeline.grantDefaultExecutionRolePermissions(executionRole);
130+
const grants = imagePipeline.grantDefaultExecutionRolePermissions(executionRole);
131+
132+
expect(grants.length).toBeGreaterThanOrEqual(1);
131133

132134
expect(ImagePipeline.isImagePipeline(imagePipeline as unknown)).toBeTruthy();
133135
expect(ImagePipeline.isImagePipeline('ImagePipeline')).toBeFalsy();
134136

135-
Template.fromStack(stack).templateMatches({
137+
const template = Template.fromStack(stack);
138+
139+
// Validate that default permissions were added - check presence of ec2:CreateImage
140+
template.hasResourceProperties('AWS::IAM::Policy', {
141+
PolicyDocument: {
142+
Statement: Match.arrayWith([
143+
{ Action: Match.arrayWith(['ec2:CreateImage']), Effect: 'Allow', Resource: Match.anyValue() },
144+
]),
145+
},
146+
});
147+
148+
template.templateMatches({
136149
Resources: {
137150
ImagePipeline7DDDE57F: Match.objectEquals({
138151
Type: 'AWS::ImageBuilder::ImagePipeline',
@@ -775,10 +788,7 @@ describe('ImagePipeline', () => {
775788
imagePipeline.onEvent('PipelineRule');
776789

777790
Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', {
778-
EventPattern: {
779-
source: ['aws.imagebuilder'],
780-
resources: [{ 'Fn::GetAtt': ['ImagePipeline7DDDE57F', 'Arn'] }],
781-
},
791+
EventPattern: { source: ['aws.imagebuilder'] },
782792
State: 'ENABLED',
783793
});
784794
});
@@ -800,6 +810,73 @@ describe('ImagePipeline', () => {
800810
});
801811
});
802812

813+
test('creates a rule from onImageBuildStateChange', () => {
814+
const imagePipeline = new ImagePipeline(stack, 'ImagePipeline', {
815+
recipe: ImageRecipe.fromImageRecipeName(stack, 'ImageRecipe', 'imported-image-recipe'),
816+
});
817+
818+
imagePipeline.onImageBuildStateChange('ImageBuildStateChangeRule');
819+
820+
Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', {
821+
EventPattern: {
822+
source: ['aws.imagebuilder'],
823+
'detail-type': ['EC2 Image Builder Image State Change'],
824+
},
825+
State: 'ENABLED',
826+
});
827+
});
828+
829+
test('creates a rule from onImageBuildCompleted', () => {
830+
const imagePipeline = new ImagePipeline(stack, 'ImagePipeline', {
831+
recipe: ImageRecipe.fromImageRecipeName(stack, 'ImageRecipe', 'imported-image-recipe'),
832+
});
833+
834+
imagePipeline.onImageBuildCompleted('ImageBuildCompletedRule');
835+
836+
Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', {
837+
EventPattern: {
838+
source: ['aws.imagebuilder'],
839+
'detail-type': ['EC2 Image Builder Image State Change'],
840+
detail: { state: { status: ['AVAILABLE', 'CANCELLED', 'FAILED'] } },
841+
},
842+
State: 'ENABLED',
843+
});
844+
});
845+
846+
test('creates a rule from onImageBuildFailed', () => {
847+
const imagePipeline = new ImagePipeline(stack, 'ImagePipeline', {
848+
recipe: ImageRecipe.fromImageRecipeName(stack, 'ImageRecipe', 'imported-image-recipe'),
849+
});
850+
851+
imagePipeline.onImageBuildFailed('ImageBuildFailedRule');
852+
853+
Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', {
854+
EventPattern: {
855+
source: ['aws.imagebuilder'],
856+
'detail-type': ['EC2 Image Builder Image State Change'],
857+
detail: { state: { status: ['FAILED'] } },
858+
},
859+
State: 'ENABLED',
860+
});
861+
});
862+
863+
test('creates a rule from onImageBuildSucceeded', () => {
864+
const imagePipeline = new ImagePipeline(stack, 'ImagePipeline', {
865+
recipe: ImageRecipe.fromImageRecipeName(stack, 'ImageRecipe', 'imported-image-recipe'),
866+
});
867+
868+
imagePipeline.onImageBuildSucceeded('ImageBuildSuccessRule');
869+
870+
Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', {
871+
EventPattern: {
872+
source: ['aws.imagebuilder'],
873+
'detail-type': ['EC2 Image Builder Image State Change'],
874+
detail: { state: { status: ['AVAILABLE'] } },
875+
},
876+
State: 'ENABLED',
877+
});
878+
});
879+
803880
test('creates a rule from onImagePipelineAutoDisabled', () => {
804881
const imagePipeline = new ImagePipeline(stack, 'ImagePipeline', {
805882
recipe: ImageRecipe.fromImageRecipeName(stack, 'ImageRecipe', 'imported-image-recipe'),
@@ -817,6 +894,22 @@ describe('ImagePipeline', () => {
817894
});
818895
});
819896

897+
test('creates a rule from onWaitForAction', () => {
898+
const imagePipeline = new ImagePipeline(stack, 'ImagePipeline', {
899+
recipe: ImageRecipe.fromImageRecipeName(stack, 'ImageRecipe', 'imported-image-recipe'),
900+
});
901+
902+
imagePipeline.onWaitForAction('ImageBuildWaitingRule');
903+
904+
Template.fromStack(stack).hasResourceProperties('AWS::Events::Rule', {
905+
EventPattern: {
906+
source: ['aws.imagebuilder'],
907+
'detail-type': ['EC2 Image Builder Workflow Step Waiting'],
908+
},
909+
State: 'ENABLED',
910+
});
911+
});
912+
820913
test('throws a validation error when the resource name is too long', () => {
821914
expect(() => {
822915
new ImagePipeline(stack, 'ImagePipeline', {
@@ -871,6 +964,18 @@ describe('ImagePipeline', () => {
871964
}).toThrow(cdk.ValidationError);
872965
});
873966

967+
test('does not throw a validation error when the auto disable failure count is an unresolved token', () => {
968+
expect(() => {
969+
new ImagePipeline(stack, 'ImagePipeline', {
970+
schedule: {
971+
expression: events.Schedule.rate(cdk.Duration.days(7)),
972+
autoDisableFailureCount: cdk.Lazy.number({ produce: () => 100 }),
973+
},
974+
recipe: ImageRecipe.fromImageRecipeName(stack, 'ImageRecipe', 'imported-image-recipe'),
975+
});
976+
}).not.toThrow(cdk.ValidationError);
977+
});
978+
874979
test('throws a validation error when an image scanning ECR repository is provided for an AMI pipeline', () => {
875980
expect(() => {
876981
new ImagePipeline(stack, 'ImagePipeline', {

0 commit comments

Comments
 (0)