Skip to content

Commit 4361f8b

Browse files
authored
feat(imagebuilder-alpha): add support for Image Recipe Construct (#36092)
### Issue aws/aws-cdk-rfcs#789 ### Reason for this change This change adds a new alpha module for EC2 Image Builder L2 Constructs (@aws-cdk/aws-imagebuilder-alpha), as outlined in aws/aws-cdk-rfcs#789. This PR specifically implements the ImageRecipe construct. ### Description of changes This change implements the `ImageRecipe` construct, which is a higher-level construct of [CfnImageRecipe](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_imagebuilder.CfnImageRecipe.html). #### Example ```ts const userData = ec2.UserData.forLinux(); userData.addS3DownloadCommand({ bucket: s3.Bucket.fromBucketName(this, 'Bucket', `test-bucket-${this.account}`), bucketKey: 'test-key', localFile: 's3-executable.sh', }); userData.addExecuteFileCommand({ filePath: 's3-executable.sh' }); userData.addCommands('User Data complete!'); const imageRecipe = new imagebuilder.ImageRecipe(this, 'ImageRecipe', { imageRecipeName: 'test-image-recipe', imageRecipeVersion: '1.0.0', description: 'An Image Recipe', // Use an AL2023 base image baseImage: imagebuilder.BaseImage.fromSsmParameterName( this, 'MachineImage', '/aws/service/ami-amazon-linux-latest/al2023-ami-minimal-kernel-default-x86_64', ), // Use an AWS-managed component, shared component, self-owned component with parameters, and marketplace component components: [ { component: imagebuilder.AwsManagedComponent.updateOS(this, 'UpdateOS', { platform: imagebuilder.Platform.Linux, }), }, { component: imagebuilder.Component.fromComponentArn( this, 'ComplianceTestComponent', `arn:${this.partition}:imagebuilder:${this.region}:123456789012:component/compliance-test/2025.x.x.x`, ), }, { component: imagebuilder.AwsMarketplaceComponent.fromAwsMarketplaceComponentAttributes( this, 'MarketplaceComponent', { name: 'marketplace-component-name', marketplaceProductId: '12345678-1234-1234-1234-123456789012', }, ), }, { component: imagebuilder.Component.fromComponentAttributes(this, 'CustomComponent', { componentName: 'custom-component', }), parameters: { CUSTOM_PARAMETER_KEY: imagebuilder.ComponentParameterValue.fromString('custom-parameter-value'), }, }, ], workingDirectory: '/var/tmp', // Optional - retain the SSM agent after the build, and apply custom userdata uninstallSsmAgentAfterBuild: false, userDataOverride: userData, // Optional - attach additional block device to the build instance blockDevices: [ { deviceName: '/dev/sda1', mappingEnabled: true, volume: ec2.BlockDeviceVolume.ebs(50, { deleteOnTermination: true, iops: 1000, volumeType: ec2.EbsDeviceVolumeType.GP3, throughput: 1000, encrypted: true, kmsKey: kms.Key.fromLookup(this, 'VolumeKey', { aliasName: 'alias/volume-encryption-key' }), }), }, ], // Optional - specify tags to apply to the output AMI amiTags: { Environment: 'production', }, }); ``` ### Describe any new or updated permissions being added N/A - new L2 construct in alpha module ### Description of how you validated changes Validated with unit tests and integration tests. Manually verified generated CFN templates as well. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 616d32a commit 4361f8b

File tree

37 files changed

+3823
-4
lines changed

37 files changed

+3823
-4
lines changed

packages/@aws-cdk/aws-imagebuilder-alpha/README.md

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,202 @@ EC2 Image Builder supports AWS-managed components for common tasks, AWS Marketpl
3636
that you create. Components run during specific workflow phases: build and validate phases during the build stage, and
3737
test phase during the test stage.
3838

39+
### Image Recipe
40+
41+
#### Image Recipe Basic Usage
42+
43+
Create an image recipe with the required base image:
44+
45+
```ts
46+
const imageRecipe = new imagebuilder.ImageRecipe(this, 'MyImageRecipe', {
47+
baseImage: imagebuilder.BaseImage.fromSsmParameterName(
48+
'/aws/service/ami-amazon-linux-latest/al2023-ami-minimal-kernel-default-x86_64'
49+
)
50+
});
51+
```
52+
53+
#### Image Recipe Base Images
54+
55+
To create a recipe, you have to select a base image to build and customize from. This base image can be referenced from
56+
various sources, such as from SSM parameters, AWS Marketplace products, and AMI IDs directly.
57+
58+
##### SSM Parameters
59+
60+
Using SSM parameter references:
61+
62+
```ts
63+
const imageRecipe = new imagebuilder.ImageRecipe(this, 'SsmImageRecipe', {
64+
baseImage: imagebuilder.BaseImage.fromSsmParameterName(
65+
'/aws/service/ami-amazon-linux-latest/al2023-ami-minimal-kernel-default-x86_64'
66+
)
67+
});
68+
69+
// Using an SSM parameter construct
70+
const parameter = ssm.StringParameter.fromStringParameterName(
71+
this,
72+
'BaseImageParameter',
73+
'/aws/service/ami-windows-latest/Windows_Server-2022-English-Full-Base'
74+
);
75+
const windowsRecipe = new imagebuilder.ImageRecipe(this, 'WindowsImageRecipe', {
76+
baseImage: imagebuilder.BaseImage.fromSsmParameter(parameter)
77+
});
78+
```
79+
80+
##### AMI IDs
81+
82+
When you have a specific AMI to use:
83+
84+
```ts
85+
const imageRecipe = new imagebuilder.ImageRecipe(this, 'AmiImageRecipe', {
86+
baseImage: imagebuilder.BaseImage.fromAmiId('ami-12345678')
87+
});
88+
```
89+
90+
##### Marketplace Images
91+
92+
For marketplace base images:
93+
94+
```ts
95+
const imageRecipe = new imagebuilder.ImageRecipe(this, 'MarketplaceImageRecipe', {
96+
baseImage: imagebuilder.BaseImage.fromMarketplaceProductId('prod-1234567890abcdef0')
97+
});
98+
```
99+
100+
#### Image Recipe Components
101+
102+
Components from various sources, such as custom-owned, AWS-owned, or AWS Marketplace-owned, can optionally be included
103+
in recipes. For parameterized components, you are able to provide the parameters to use in the recipe, which will be
104+
applied during the image build when executing components.
105+
106+
##### Custom Components in Image Recipes
107+
108+
Add your own components to the recipe:
109+
110+
```ts
111+
const customComponent = new imagebuilder.Component(this, 'MyComponent', {
112+
platform: imagebuilder.Platform.LINUX,
113+
data: imagebuilder.ComponentData.fromJsonObject({
114+
schemaVersion: imagebuilder.ComponentSchemaVersion.V1_0,
115+
phases: [
116+
{
117+
name: imagebuilder.ComponentPhaseName.BUILD,
118+
steps: [
119+
{
120+
name: 'install-app',
121+
action: imagebuilder.ComponentAction.EXECUTE_BASH,
122+
inputs: {
123+
commands: ['yum install -y my-application']
124+
}
125+
}
126+
]
127+
}
128+
]
129+
})
130+
});
131+
132+
const imageRecipe = new imagebuilder.ImageRecipe(this, 'ComponentImageRecipe', {
133+
baseImage: imagebuilder.BaseImage.fromSsmParameterName(
134+
'/aws/service/ami-amazon-linux-latest/al2023-ami-minimal-kernel-default-x86_64'
135+
),
136+
components: [
137+
{
138+
component: customComponent
139+
}
140+
]
141+
});
142+
```
143+
144+
##### AWS-Managed Components in Image Recipes
145+
146+
Use pre-built AWS components:
147+
148+
```ts
149+
const imageRecipe = new imagebuilder.ImageRecipe(this, 'AwsManagedImageRecipe', {
150+
baseImage: imagebuilder.BaseImage.fromSsmParameterName(
151+
'/aws/service/ami-amazon-linux-latest/al2023-ami-minimal-kernel-default-x86_64'
152+
),
153+
components: [
154+
{
155+
component: imagebuilder.AwsManagedComponent.updateOS(this, 'UpdateOS', {
156+
platform: imagebuilder.Platform.LINUX
157+
})
158+
},
159+
{
160+
component: imagebuilder.AwsManagedComponent.awsCliV2(this, 'AwsCli', {
161+
platform: imagebuilder.Platform.LINUX
162+
})
163+
}
164+
]
165+
});
166+
```
167+
168+
##### Component Parameters in Image Recipes
169+
170+
Pass parameters to components that accept them:
171+
172+
```ts
173+
const parameterizedComponent = imagebuilder.Component.fromComponentName(
174+
this,
175+
'ParameterizedComponent',
176+
'my-parameterized-component'
177+
);
178+
179+
const imageRecipe = new imagebuilder.ImageRecipe(this, 'ParameterizedImageRecipe', {
180+
baseImage: imagebuilder.BaseImage.fromSsmParameterName(
181+
'/aws/service/ami-amazon-linux-latest/al2023-ami-minimal-kernel-default-x86_64'
182+
),
183+
components: [
184+
{
185+
component: parameterizedComponent,
186+
parameters: {
187+
environment: imagebuilder.ComponentParameterValue.fromString('production'),
188+
version: imagebuilder.ComponentParameterValue.fromString('1.0.0')
189+
}
190+
}
191+
]
192+
});
193+
```
194+
195+
#### Image Recipe Configuration
196+
197+
##### Block Device Configuration
198+
199+
Configure storage for the build instance:
200+
201+
```ts
202+
const imageRecipe = new imagebuilder.ImageRecipe(this, 'BlockDeviceImageRecipe', {
203+
baseImage: imagebuilder.BaseImage.fromSsmParameterName(
204+
'/aws/service/ami-amazon-linux-latest/al2023-ami-minimal-kernel-default-x86_64'
205+
),
206+
blockDevices: [
207+
{
208+
deviceName: '/dev/sda1',
209+
volume: ec2.BlockDeviceVolume.ebs(100, {
210+
encrypted: true,
211+
volumeType: ec2.EbsDeviceVolumeType.GENERAL_PURPOSE_SSD_GP3
212+
})
213+
}
214+
]
215+
});
216+
```
217+
218+
##### AMI Tagging
219+
220+
Tag the output AMI:
221+
222+
```ts
223+
const imageRecipe = new imagebuilder.ImageRecipe(this, 'TaggedImageRecipe', {
224+
baseImage: imagebuilder.BaseImage.fromSsmParameterName(
225+
'/aws/service/ami-amazon-linux-latest/al2023-ami-minimal-kernel-default-x86_64'
226+
),
227+
amiTags: {
228+
Environment: 'Production',
229+
Application: 'WebServer',
230+
Owner: 'DevOps Team'
231+
}
232+
});
233+
```
234+
39235
### Container Recipe
40236

41237
A container recipe is similar to an image recipe but specifically for container images. It defines the base container

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

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,66 @@
11
import * as ecr from 'aws-cdk-lib/aws-ecr';
22
import * as ssm from 'aws-cdk-lib/aws-ssm';
33

4+
/**
5+
* Represents a base image that is used to start from in EC2 Image Builder image builds
6+
*/
7+
export class BaseImage {
8+
/**
9+
* The AMI ID to use as a base image in an image recipe
10+
*
11+
* @param amiId The AMI ID to use as the base image
12+
*/
13+
public static fromAmiId(amiId: string): BaseImage {
14+
return new BaseImage(amiId);
15+
}
16+
17+
/**
18+
* The marketplace product ID for an AMI product to use as the base image in an image recipe
19+
*
20+
* @param productId The Marketplace AMI product ID to use as the base image
21+
*/
22+
public static fromMarketplaceProductId(productId: string): BaseImage {
23+
return new BaseImage(productId);
24+
}
25+
26+
/**
27+
* The SSM parameter to use as the base image in an image recipe
28+
*
29+
* @param parameter The SSM parameter to use as the base image
30+
*/
31+
public static fromSsmParameter(parameter: ssm.IParameter): BaseImage {
32+
return new BaseImage(`ssm:${parameter.parameterArn}`);
33+
}
34+
35+
/**
36+
* The parameter name for the SSM parameter to use as the base image in an image recipe
37+
*
38+
* @param parameterName The name of the SSM parameter to use as the base image
39+
*/
40+
public static fromSsmParameterName(parameterName: string): BaseImage {
41+
return new BaseImage(`ssm:${parameterName}`);
42+
}
43+
44+
/**
45+
* The direct string value of the base image to use in an image recipe. This can be an EC2 Image Builder image ARN,
46+
* an SSM parameter, an AWS Marketplace product ID, or an AMI ID.
47+
*
48+
* @param baseImageString The base image as a direct string value
49+
*/
50+
public static fromString(baseImageString: string): BaseImage {
51+
return new BaseImage(baseImageString);
52+
}
53+
54+
/**
55+
* The rendered base image to use
56+
**/
57+
public readonly image: string;
58+
59+
protected constructor(image: string) {
60+
this.image = image;
61+
}
62+
}
63+
464
/**
565
* Represents a base image that is used to start from in EC2 Image Builder image builds
666
*/
@@ -80,7 +140,7 @@ export class ContainerInstanceImage {
80140
}
81141

82142
/**
83-
* The name of the SSM parameter used to launch the instance for building the container image
143+
* The ARN of the SSM parameter used to launch the instance for building the container image
84144
*
85145
* @param parameterName The name of the SSM parameter used as the container instance image
86146
*/

packages/@aws-cdk/aws-imagebuilder-alpha/lib/container-recipe.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { propertyInjectable } from 'aws-cdk-lib/core/lib/prop-injectable';
99
import { Construct } from 'constructs';
1010
import { BaseContainerImage, ContainerInstanceImage } from './base-image';
1111
import { Repository } from './distribution-configuration';
12+
import { IImageRecipe } from './image-recipe';
1213
import { OSVersion } from './os-version';
1314
import { ComponentConfiguration, IRecipeBase } from './recipe-base';
1415

@@ -378,6 +379,15 @@ export abstract class ContainerRecipeBase extends cdk.Resource implements IConta
378379
public _isContainerRecipe(): this is IContainerRecipe {
379380
return true;
380381
}
382+
383+
/**
384+
* Indicates whether the recipe is an Image Recipe
385+
*
386+
* @internal
387+
*/
388+
public _isImageRecipe(): this is IImageRecipe {
389+
return false;
390+
}
381391
}
382392

383393
/**

0 commit comments

Comments
 (0)