1+ import { createHash } from 'crypto' ;
12import { readFileSync } from 'fs' ;
23import * as path from 'path' ;
34import {
78 aws_sns as sns ,
89 aws_sns_subscriptions as subscriptions ,
910 aws_lambda as lambda ,
11+ Names ,
1012} from 'aws-cdk-lib' ;
1113import { SnsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources' ;
1214import { Construct } from 'constructs' ;
@@ -40,23 +42,11 @@ export interface ImagePipelineProps {
4042 /**
4143 * List of component props
4244 */
43- readonly components : ComponentProps [ ] ;
44- /**
45- * Name of the instance profile that will be associated with the Instance Configuration.
46- */
47- readonly profileName : string ;
45+ readonly components : ( ComponentProps | string ) [ ] ;
4846 /**
4947 * Additional policies to add to the instance profile associated with the Instance Configurations
5048 */
5149 readonly additionalPolicies ?: iam . ManagedPolicy [ ] ;
52- /**
53- * Name of the Infrastructure Configuration for Image Builder
54- */
55- readonly infraConfigName : string ;
56- /**
57- * Name of the Image Recipe
58- */
59- readonly imageRecipe : string ;
6050 /**
6151 * UserData script that will override default one (if specified)
6252 *
@@ -67,10 +57,6 @@ export interface ImagePipelineProps {
6757 * Image recipe version (Default: 0.0.1)
6858 */
6959 readonly imageRecipeVersion ?: string ;
70- /**
71- * Name of the Image Pipeline
72- */
73- readonly pipelineName : string ;
7460 /**
7561 * 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
7662 */
@@ -150,6 +136,9 @@ export class ImagePipeline extends Construct {
150136 let imageRecipe : imagebuilder . CfnImageRecipe ;
151137 this . imageRecipeComponents = [ ] ;
152138
139+ const uid = Names . uniqueId ( this ) ;
140+ const profileName = `${ uid } Profile` ;
141+
153142 // Construct code below
154143 const topic = new sns . Topic ( this , 'ImageBuilderTopic' , {
155144 displayName : 'Image Builder Notify' ,
@@ -176,21 +165,21 @@ export class ImagePipeline extends Construct {
176165
177166 const profile = new iam . CfnInstanceProfile ( this , 'InstanceProfile' , {
178167 roles : [ role . roleName ] ,
179- instanceProfileName : props . profileName ,
168+ instanceProfileName : profileName ,
180169 } ) ;
181170
182171 if ( props . securityGroups == null || props . subnetId == null ) {
183172 infrastructureConfig = new imagebuilder . CfnInfrastructureConfiguration ( this , 'InfrastructureConfiguration' , {
184- instanceProfileName : props . profileName ,
185- name : props . infraConfigName ,
173+ instanceProfileName : profileName ,
174+ name : ` ${ uid } InfraConfig` ,
186175 description : 'Example Infrastructure Configuration for Image Builder' ,
187176 instanceTypes : props . instanceTypes ?? [ 't3.medium' , 'm5.large' , 'm5.xlarge' ] ,
188177 snsTopicArn : topic . topicArn ,
189178 } ) ;
190179 } else {
191180 infrastructureConfig = new imagebuilder . CfnInfrastructureConfiguration ( this , 'InfrastructureConfiguration' , {
192- instanceProfileName : props . profileName ,
193- name : props . infraConfigName ,
181+ instanceProfileName : profileName ,
182+ name : ` ${ uid } InfraConfig` ,
194183 description : 'Example Infrastructure Configuration for Image Builder' ,
195184 instanceTypes : props . instanceTypes ?? [ 't3.medium' , 'm5.large' , 'm5.xlarge' ] ,
196185 snsTopicArn : topic . topicArn ,
@@ -207,7 +196,7 @@ export class ImagePipeline extends Construct {
207196 let imageRecipeProps : imagebuilder . CfnImageRecipeProps ;
208197 imageRecipeProps = {
209198 components : [ ] ,
210- name : props . imageRecipe ,
199+ name : 'Placeholder' ,
211200 parentImage : props . parentImage ,
212201 version : props . imageRecipeVersion ?? '0.0.1' ,
213202 } ;
@@ -228,22 +217,30 @@ export class ImagePipeline extends Construct {
228217 imageRecipe = new imagebuilder . CfnImageRecipe ( this , 'ImageRecipe' , imageRecipeProps ) ;
229218
230219 props . components . forEach ( ( component ) => {
231- let newComponent = new imagebuilder . CfnComponent ( this , component . name , {
232- name : component . name ,
233- platform : props . platform ? props . platform : 'Linux' ,
234- version : component . version ,
235- data : readFileSync ( component . document ) . toString ( ) ,
236- } ) ;
220+ if ( typeof component === 'string' ) {
221+ this . imageRecipeComponents . push ( { componentArn : component } ) ;
222+ } else {
223+ let newComponent = new imagebuilder . CfnComponent ( this , component . name , {
224+ name : `${ uid } ${ component . name } ` ,
225+ platform : props . platform ? props . platform : 'Linux' ,
226+ version : component . version ,
227+ data : readFileSync ( component . document ) . toString ( ) ,
228+ } ) ;
229+
230+ // add the component to the Image Recipe
231+ this . imageRecipeComponents . push ( { componentArn : newComponent . attrArn } ) ;
232+ }
237233
238- // add the component to the Image Recipe
239- this . imageRecipeComponents . push ( { componentArn : newComponent . attrArn } ) ;
240234 imageRecipe . components = this . imageRecipeComponents ;
241235 } ) ;
242236
237+ const hashId = this . hash ( props . components ) ;
238+ imageRecipe . name = `${ uid } ${ hashId } ` ;
239+
243240 let imagePipelineProps : imagebuilder . CfnImagePipelineProps ;
244241 imagePipelineProps = {
245242 infrastructureConfigurationArn : infrastructureConfig . attrArn ,
246- name : props . pipelineName ,
243+ name : ` ${ uid } ImagePipeline` ,
247244 description : 'A sample image pipeline' ,
248245 imageRecipeArn : imageRecipe . attrArn ,
249246 } ;
@@ -267,8 +264,8 @@ export class ImagePipeline extends Construct {
267264 amiDistributionConfiguration : {
268265 //Capital case here because it's an object of type any, but capital case is what is expected in CloudFormation
269266 //https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-distributionconfiguration-amidistributionconfiguration.html
270- Name : `${ props . imageRecipe } -${ distributionRegion } -{{imagebuilder:buildDate}}` ,
271- Description : `copy AMI ${ props . imageRecipe } to ${ distributionRegion } ` ,
267+ Name : `${ uid } -${ distributionRegion } -{{imagebuilder:buildDate}}` ,
268+ Description : `copy AMI to ${ distributionRegion } ` ,
272269 TargetAccountIds : props . distributionAccountIDs ,
273270 LaunchPermissionConfiguration : {
274271 UserIds : props . distributionAccountIDs ,
@@ -279,8 +276,8 @@ export class ImagePipeline extends Construct {
279276 distributionsList . push ( distributionConfig ) ;
280277 } ) ;
281278 const amiDistributionConfiguration = new imagebuilder . CfnDistributionConfiguration ( this , 'amiDistributionConfiguration' , {
282- name : `${ props . imageRecipe } -distribution-config ` ,
283- description : `Cross account distribution settings for ${ props . imageRecipe } ` ,
279+ name : `${ uid } DistributionConfig ` ,
280+ description : `Cross account distribution settings for ${ uid } ` ,
284281 distributions : distributionsList ,
285282 } ) ;
286283 imagePipelineProps = {
@@ -308,7 +305,7 @@ export class ImagePipeline extends Construct {
308305 } ) ,
309306 ] ,
310307 } ) ;
311- const amiSsmUpdateLambdaRole = new iam . Role ( this , ` ${ props . imageRecipe } UpdateLambdaRole` , {
308+ const amiSsmUpdateLambdaRole = new iam . Role ( this , ' UpdateLambdaRole' , {
312309 assumedBy : new iam . ServicePrincipal ( 'lambda.amazonaws.com' ) ,
313310 managedPolicies : [
314311 iam . ManagedPolicy . fromAwsManagedPolicyName ( 'service-role/AWSLambdaBasicExecutionRole' ) ,
@@ -317,7 +314,7 @@ export class ImagePipeline extends Construct {
317314 AmiSsmUpdateLambdaPolicy : amiSsmUpdateLambdaPolicy ,
318315 } ,
319316 } ) ;
320- const amiSsmUpdateLambda = new lambda . Function ( this , ` ${ props . imageRecipe } UpdateLambda` , {
317+ const amiSsmUpdateLambda = new lambda . Function ( this , ' UpdateLambda' , {
321318 runtime : lambda . Runtime . PYTHON_3_10 ,
322319 code : lambda . Code . fromAsset ( path . join ( __dirname , '../assets/image-builder-update-lambda' ) ) ,
323320 handler : 'image-builder-lambda-update-ssm.lambda_handler' ,
@@ -331,4 +328,22 @@ export class ImagePipeline extends Construct {
331328 }
332329 new imagebuilder . CfnImagePipeline ( this , 'ImagePipeline' , imagePipelineProps ) ;
333330 }
331+
332+ /**
333+ * Helper function to hash an object to create unique resource names
334+ *
335+ * @param o an object to hash
336+ * @returns 6 character hash string
337+ */
338+ private hash ( o : object ) : string {
339+ // Remove any token references that would cause the result to be
340+ // non-deterministic.
341+ const cleanString = JSON . stringify ( o ) . replace ( / \$ { [ ^ { ] * } / g, '' ) ;
342+
343+ return createHash ( 'sha256' )
344+ . update ( cleanString )
345+ . digest ( 'hex' )
346+ . toUpperCase ( )
347+ . slice ( 0 , 6 ) ;
348+ }
334349}
0 commit comments