Skip to content

Commit 7f472f7

Browse files
authored
Merge pull request #12 from aws-samples/feat/sns-encrypt-and-multi-component
feat: SNS Encryption via KMS & Support for Multiple Components
2 parents 046c5fb + 14abbec commit 7f472f7

File tree

5 files changed

+171
-106
lines changed

5 files changed

+171
-106
lines changed

API.md

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Any object.
8585
| **Name** | **Type** | **Description** |
8686
| --- | --- | --- |
8787
| <code><a href="#cdk-image-pipeline.ImagePipeline.property.node">node</a></code> | <code>constructs.Node</code> | The tree node. |
88+
| <code><a href="#cdk-image-pipeline.ImagePipeline.property.imageRecipeComponents">imageRecipeComponents</a></code> | <code>aws-cdk-lib.aws_imagebuilder.CfnImageRecipe.ComponentConfigurationProperty[]</code> | *No description.* |
8889

8990
---
9091

@@ -100,6 +101,16 @@ The tree node.
100101

101102
---
102103

104+
##### `imageRecipeComponents`<sup>Required</sup> <a name="imageRecipeComponents" id="cdk-image-pipeline.ImagePipeline.property.imageRecipeComponents"></a>
105+
106+
```typescript
107+
public readonly imageRecipeComponents: ComponentConfigurationProperty[];
108+
```
109+
110+
- *Type:* aws-cdk-lib.aws_imagebuilder.CfnImageRecipe.ComponentConfigurationProperty[]
111+
112+
---
113+
103114

104115
## Structs <a name="Structs" id="Structs"></a>
105116

@@ -117,10 +128,12 @@ const imagePipelineProps: ImagePipelineProps = { ... }
117128

118129
| **Name** | **Type** | **Description** |
119130
| --- | --- | --- |
120-
| <code><a href="#cdk-image-pipeline.ImagePipelineProps.property.componentDocPath">componentDocPath</a></code> | <code>string</code> | Relative path to the Image Builder component document. |
121-
| <code><a href="#cdk-image-pipeline.ImagePipelineProps.property.componentName">componentName</a></code> | <code>string</code> | Name of the Component. |
131+
| <code><a href="#cdk-image-pipeline.ImagePipelineProps.property.componentDocuments">componentDocuments</a></code> | <code>string[]</code> | Relative path to Image Builder component documents. |
132+
| <code><a href="#cdk-image-pipeline.ImagePipelineProps.property.componentNames">componentNames</a></code> | <code>string[]</code> | Names of the Component Documents. |
133+
| <code><a href="#cdk-image-pipeline.ImagePipelineProps.property.componentVersions">componentVersions</a></code> | <code>string[]</code> | Versions for each component document. |
122134
| <code><a href="#cdk-image-pipeline.ImagePipelineProps.property.imageRecipe">imageRecipe</a></code> | <code>string</code> | Name of the Image Recipe. |
123135
| <code><a href="#cdk-image-pipeline.ImagePipelineProps.property.infraConfigName">infraConfigName</a></code> | <code>string</code> | Name of the Infrastructure Configuration for Image Builder. |
136+
| <code><a href="#cdk-image-pipeline.ImagePipelineProps.property.kmsKeyAlias">kmsKeyAlias</a></code> | <code>string</code> | KMS Key used to encrypt the SNS topic. |
124137
| <code><a href="#cdk-image-pipeline.ImagePipelineProps.property.parentImage">parentImage</a></code> | <code>string</code> | The source (parent) image that the image recipe uses as its base environment. |
125138
| <code><a href="#cdk-image-pipeline.ImagePipelineProps.property.pipelineName">pipelineName</a></code> | <code>string</code> | Name of the Image Pipeline. |
126139
| <code><a href="#cdk-image-pipeline.ImagePipelineProps.property.profileName">profileName</a></code> | <code>string</code> | Name of the instance profile that will be associated with the Instance Configuration. |
@@ -133,27 +146,39 @@ const imagePipelineProps: ImagePipelineProps = { ... }
133146

134147
---
135148

136-
##### `componentDocPath`<sup>Required</sup> <a name="componentDocPath" id="cdk-image-pipeline.ImagePipelineProps.property.componentDocPath"></a>
149+
##### `componentDocuments`<sup>Required</sup> <a name="componentDocuments" id="cdk-image-pipeline.ImagePipelineProps.property.componentDocuments"></a>
137150

138151
```typescript
139-
public readonly componentDocPath: string;
152+
public readonly componentDocuments: string[];
140153
```
141154

142-
- *Type:* string
155+
- *Type:* string[]
143156

144-
Relative path to the Image Builder component document.
157+
Relative path to Image Builder component documents.
145158

146159
---
147160

148-
##### `componentName`<sup>Required</sup> <a name="componentName" id="cdk-image-pipeline.ImagePipelineProps.property.componentName"></a>
161+
##### `componentNames`<sup>Required</sup> <a name="componentNames" id="cdk-image-pipeline.ImagePipelineProps.property.componentNames"></a>
149162

150163
```typescript
151-
public readonly componentName: string;
164+
public readonly componentNames: string[];
152165
```
153166

154-
- *Type:* string
167+
- *Type:* string[]
168+
169+
Names of the Component Documents.
170+
171+
---
172+
173+
##### `componentVersions`<sup>Required</sup> <a name="componentVersions" id="cdk-image-pipeline.ImagePipelineProps.property.componentVersions"></a>
155174

156-
Name of the Component.
175+
```typescript
176+
public readonly componentVersions: string[];
177+
```
178+
179+
- *Type:* string[]
180+
181+
Versions for each component document.
157182

158183
---
159184

@@ -181,6 +206,20 @@ Name of the Infrastructure Configuration for Image Builder.
181206

182207
---
183208

209+
##### `kmsKeyAlias`<sup>Required</sup> <a name="kmsKeyAlias" id="cdk-image-pipeline.ImagePipelineProps.property.kmsKeyAlias"></a>
210+
211+
```typescript
212+
public readonly kmsKeyAlias: string;
213+
```
214+
215+
- *Type:* string
216+
217+
KMS Key used to encrypt the SNS topic.
218+
219+
Enter an existing KMS Key Alias in your target account/region.
220+
221+
---
222+
184223
##### `parentImage`<sup>Required</sup> <a name="parentImage" id="cdk-image-pipeline.ImagePipelineProps.property.parentImage"></a>
185224

186225
```typescript

README.md

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ This construct creates the required infrastructure for an Image Pipeline:
1616

1717
- An EC2 Image Builder recipe defines the base image to use as your starting point to create a new image, along with the set of components that you add to customize your image and verify that everything is working as expected.
1818

19-
- Image Builder uses the AWS Task Orchestrator and Executor (AWSTOE) component management application to orchestrate complex workflows. AWSTOE components are based on YAML documents that define the scripts to customize or test your image
19+
- Image Builder uses the AWS Task Orchestrator and Executor (AWSTOE) component management application to orchestrate complex workflows. AWSTOE components are based on YAML documents that define the scripts to customize or test your image. Support for multiple components.
2020

2121
- Image Builder image pipelines provide an automation framework for creating and maintaining custom AMIs and container images.
2222

@@ -47,14 +47,17 @@ import { Construct } from 'constructs';
4747
// ...
4848
// Create a new image pipeline with the required properties
4949
new ImagePipeline(this, "MyImagePipeline", {
50-
componentDocPath: 'path/to/component_doc.yml',
51-
componentName: 'MyComponent',
50+
componentDocuments: ['component_example.yml', 'component_example_2.yml'],
51+
componentNames: ['Component', 'Component2'],
52+
componentVersions: ['0.0.1', '0.1.0'],
53+
kmsKeyAlias: 'alias/my-key',
5254
profileName: 'ImagePipelineInstanceProfile',
5355
infraConfigName: 'MyInfrastructureConfiguration',
5456
imageRecipe: 'MyImageRecipe',
5557
pipelineName: 'MyImagePipeline',
5658
parentImage: 'ami-0e1d30f2c40c4c701'
5759
})
60+
// ...
5861
```
5962
6063
By default, the infrastructure configuration will deploy EC2 instances for the build/test phases into a default VPC using the default security group. If you want to control where the instances are launched, you can specify an existing VPC `SubnetID` and a list of `SecurityGroupIds`. In the example below, a new VPC is created and referenced in the `ImagePipeline` construct object.
@@ -93,8 +96,10 @@ const private_subnet = vpc.privateSubnets;
9396
9497
9598
new ImagePipeline(this, "MyImagePipeline", {
96-
componentDocPath: 'path/to/component_doc.yml',
97-
componentName: 'MyComponent',
99+
componentDocuments: ['component_example.yml', 'component_example_2.yml'],
100+
componentNames: ['Component', 'Component2'],
101+
componentVersions: ['0.0.1', '0.1.0'],
102+
kmsKeyAlias: 'alias/my-key',
98103
profileName: 'ImagePipelineInstanceProfile',
99104
infraConfigName: 'MyInfrastructureConfiguration',
100105
imageRecipe: 'MyImageRecipe',
@@ -103,6 +108,7 @@ new ImagePipeline(this, "MyImagePipeline", {
103108
securityGroups: [sg.securityGroupId],
104109
subnetId: private_subnet[0].subnetId,
105110
})
111+
// ...
106112
```
107113
108114
Python usage:
@@ -115,14 +121,17 @@ from constructs import Construct
115121
image_pipeline = ImagePipeline(
116122
self,
117123
"LatestImagePipeline",
118-
component_doc_path="component_example.yml",
119-
component_name="Comp4",
124+
component_documents=["component_example.yml", "component_example2.yml"],
125+
component_names=["Component", "Component2"],
126+
component_versions=["0.0.1", "0.1.0"],
127+
kms_key_alias="alias/my-key",
120128
image_recipe="Recipe4",
121129
pipeline_name="Pipeline4",
122130
infra_config_name="InfraConfig4",
123131
parent_image="ami-0e1d30f2c40c4c701",
124132
profile_name="ImagePipelineProfile4",
125133
)
134+
# ...
126135
```
127136
128137
```python
@@ -163,8 +172,10 @@ priv_subnets = vpc.private_subnets
163172
image_pipeline = ImagePipeline(
164173
self,
165174
"LatestImagePipeline",
166-
component_doc_path="component_example.yml",
167-
component_name="Comp4",
175+
component_documents=["component_example.yml", "component_example2.yml"],
176+
component_names=["Component", "Component2"],
177+
component_versions=["0.0.1", "0.1.0"],
178+
kms_key_alias="alias/my-key",
168179
image_recipe="Recipe4",
169180
pipeline_name="Pipeline4",
170181
infra_config_name="InfraConfig4",
@@ -173,6 +184,7 @@ image_pipeline = ImagePipeline(
173184
security_groups=[sg.security_group_id],
174185
subnet_id=priv_subnets[0].subnet_id
175186
)
187+
# ...
176188
```
177189
178190
### Component Documents
@@ -209,6 +221,18 @@ phases:
209221
- echo "Hello World! Test.
210222
```
211223
224+
### Multiple Components
225+
226+
To specify multiple components, add additional component documents to the `componentDoucments` property. You can also add the names and versions of these components via the `componentNames` and `componentVersions` properties (_See usage examples above_). The components will be associated to the Image Recipe that gets created as part of the construct.
227+
228+
Be sure to update the `imageRecipeVersion` property when making updates to your components after your initial deployment.
229+
230+
### SNS Encryption using KMS
231+
232+
---
233+
234+
Specify an alias via the `kmsKeyAlias` property which will be used to encrypt the SNS topic.
235+
212236
### Infrastructure Configuration Instance Types
213237
214238
---

src/index.ts

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
import { readFileSync } from 'fs';
22
import * as iam from 'aws-cdk-lib/aws-iam';
33
import * as imagebuilder from 'aws-cdk-lib/aws-imagebuilder';
4+
import * as kms from 'aws-cdk-lib/aws-kms';
45
import * as sns from 'aws-cdk-lib/aws-sns';
56
import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions';
67
import { Construct } from 'constructs';
78

89
export interface ImagePipelineProps {
910
/**
10-
* Relative path to the Image Builder component document
11+
* Relative path to Image Builder component documents
1112
*/
12-
readonly componentDocPath: string;
13+
readonly componentDocuments: string[];
1314
/**
14-
* Name of the Component
15+
* Names of the Component Documents
1516
*/
16-
readonly componentName: string;
17+
readonly componentNames: string[];
18+
/**
19+
* Versions for each component document
20+
*/
21+
readonly componentVersions: string[];
1722
/**
1823
* Name of the instance profile that will be associated with the Instance Configuration.
1924
*/
@@ -38,6 +43,10 @@ export interface ImagePipelineProps {
3843
* The source (parent) image that the image recipe uses as its base environment. The value can be the parent image ARN or an Image Builder AMI ID
3944
*/
4045
readonly parentImage: string;
46+
/**
47+
* KMS Key used to encrypt the SNS topic. Enter an existing KMS Key Alias in your target account/region.
48+
*/
49+
readonly kmsKeyAlias: string;
4150
/**
4251
* List of instance types used in the Instance Configuration (Default: [ 't3.medium', 'm5.large', 'm5.xlarge' ])
4352
*/
@@ -60,14 +69,20 @@ export interface ImagePipelineProps {
6069
readonly subnetId?: string;
6170
}
6271

63-
6472
export class ImagePipeline extends Construct {
73+
imageRecipeComponents: imagebuilder.CfnImageRecipe.ComponentConfigurationProperty[];
6574
constructor(scope: Construct, id: string, props: ImagePipelineProps) {
6675
super(scope, id);
6776
let infrastructureConfig = null;
68-
// Constuct code below
77+
this.imageRecipeComponents = [];
78+
79+
// Construct code below
80+
const kmsKey = kms.Key.fromLookup(this, 'KmsKeyLookup', {
81+
aliasName: props.kmsKeyAlias,
82+
});
6983
const topic = new sns.Topic(this, 'ImageBuilderTopic', {
7084
displayName: 'Image Builder Notify',
85+
masterKey: kmsKey,
7186
});
7287

7388
if (props.email != null) {
@@ -110,22 +125,27 @@ export class ImagePipeline extends Construct {
110125

111126
infrastructureConfig.addDependsOn(profile);
112127

113-
const component = new imagebuilder.CfnComponent(this, 'Component', {
114-
name: props.componentName,
115-
platform: props.platform ?? 'Linux',
116-
version: '1.0.0',
117-
data: readFileSync(props.componentDocPath).toString(),
118-
});
119-
120128
const imageRecipe = new imagebuilder.CfnImageRecipe(this, 'ImageRecipe', {
121-
components: [{
122-
componentArn: component.attrArn,
123-
}],
129+
components: [],
124130
name: props.imageRecipe,
125131
parentImage: props.parentImage,
126132
version: props.imageRecipeVersion ?? '0.0.1',
127133
});
128134

135+
props.componentDocuments.forEach((document, index) => {
136+
let component = new imagebuilder.CfnComponent(this, props.componentNames[index], {
137+
name: props.componentNames[index],
138+
platform: props.platform ?? 'Linux',
139+
version: props.componentVersions[index] ?? '0.0.1',
140+
data: readFileSync(document).toString(),
141+
});
142+
143+
// add the component to the Image Recipe
144+
this.imageRecipeComponents.push({ componentArn: component.attrArn });
145+
imageRecipe.components = this.imageRecipeComponents;
146+
});
147+
148+
129149
new imagebuilder.CfnImagePipeline(this, 'ImagePipeline', {
130150
infrastructureConfigurationArn: infrastructureConfig.attrArn,
131151
name: props.pipelineName,

0 commit comments

Comments
 (0)