import { Annotations, Stack, StackProps } from "aws-cdk-lib"; import { IVpc, SecurityGroup, SubnetType, Vpc } from "aws-cdk-lib/aws-ec2"; import { IBucket } from "aws-cdk-lib/aws-s3"; import { BucketDeployment, Source } from "aws-cdk-lib/aws-s3-deployment"; import { ParameterTier, StringParameter } from "aws-cdk-lib/aws-ssm"; import { Construct } from "constructs"; import { existsSync, readdirSync, readFileSync } from "fs"; import * as path from "path"; import { AWSImageBuilderConstruct, ImageBuilderComponent, ParentImage, PipelineConfig, } from "./aws-image-builder"; import { AWSSecureBucket } from "./aws-secure-bucket"; /** * AWS Image builder constructs stack */ export class ImageBuilderStack extends Stack { private readonly imageBuilderToolsBucket: IBucket; constructor(scope: Construct, id: string, props: StackProps) { super(scope, id, props); /** * S3 Bucket * Hosts the Image builder Installation files code. */ this.imageBuilderToolsBucket = new AWSSecureBucket( this, "toolsBucket", {} ).bucket; const vpc = Vpc.fromLookup(this, "vpc", { isDefault: true, }); /** * S3 Deploy * Uploads react built code to the S3 bucket and invalidates CloudFront */ new BucketDeployment(this, "Deploy-components", { sources: [Source.asset("./image-builder-components")], destinationBucket: this.imageBuilderToolsBucket, memoryLimit: 3008, prune: false, }); // 👇 Create a SG for a Image builder server const imageBuilderSG = new SecurityGroup(this, "image-server-sg", { vpc: vpc, allowAllOutbound: true, description: "security group for a image builder server", }); // Choose the subnet Image builder server - ensure it has internet const imageBuilderSubnetId = vpc.selectSubnets({ subnetType: SubnetType.PUBLIC, }).subnetIds[0]; const imageBuilderPipelineConfigurations = this.validAndGetPipelineConfiguration(); if (!imageBuilderPipelineConfigurations) { return; } for (const imageBuilderPipeline of imageBuilderPipelineConfigurations) { this.createImageBuilderByConfig( imageBuilderPipeline, imageBuilderSubnetId, imageBuilderSG, vpc ); } } private createImageBuilderByConfig( imageBuilderPipeline: any, imageBuilderSubnetId: string, imageBuilderSG: SecurityGroup, vpc: IVpc ) { const dir = imageBuilderPipeline.dir; const files = readdirSync(dir); if (files.some((f) => !existsSync(f))) { } const componentList: ImageBuilderComponent[] = files.map((file) => ({ name: file.split(".")[0], data: this.getData(dir, file), })); const parentImage: ParentImage = imageBuilderPipeline.parentImage; const amiIdLocation = new StringParameter(this, "Parameter", { description: `The value of image`, parameterName: `ec2image_ami_${imageBuilderPipeline.name}`, stringValue: "n/a", tier: ParameterTier.ADVANCED, }); new AWSImageBuilderConstruct( this, `AWS-ImageBuilder-Events-${imageBuilderPipeline.name}`, { imageBuilderToolsBucket: this.imageBuilderToolsBucket, name: imageBuilderPipeline.name, subnetId: imageBuilderSubnetId, imageBuilderSG: imageBuilderSG, instanceProfileName: imageBuilderPipeline.instanceProfileName, imageBuilderComponentList: componentList, cfnImageRecipeName: imageBuilderPipeline.cfnImageRecipeName, version: imageBuilderPipeline.version, parentImage: parentImage, amiIdLocation: amiIdLocation, vpc: vpc, } ); } validAndGetPipelineConfiguration() { // Get pipeline details from json const imageBuilderPipelineConfigurations = this.node.tryGetContext( "ImageBuilderPipelineConfigurations" ); if ( imageBuilderPipelineConfigurations || imageBuilderPipelineConfigurations === "" ) { if (Array.isArray(imageBuilderPipelineConfigurations)) { if (imageBuilderPipelineConfigurations.length === 0) { Annotations.of(this).addError( "An ImageBuilder pipeline configuration list requires at least one configration, found 0" ); return; } if ( (<Array<PipelineConfig>>imageBuilderPipelineConfigurations).some( (pipeConfig) => !pipeConfig.name || !pipeConfig.dir || !pipeConfig.instanceProfileName || !pipeConfig.version || !pipeConfig.cfnImageRecipeName || !pipeConfig.parentImage ) ) { Annotations.of(this).addError( "An ImageBuilder pipeline configuration is missing one of the following required values: name, dir, instanceProfileName, version, cfnImageRecipeName, parentImage" ); return; } } else { Annotations.of(this).addError( "The imageBuilderPipelinesConfiguration variable must be an array" ); return; } } else { Annotations.of(this).addError( "Mandaotry configuration ImageBuilderPipelineConfigurations is missing, expecting a list of pipeline configurations" ); return; } return imageBuilderPipelineConfigurations; } getData = (dir: string, file: string) => { const filePath = path.join(__dirname, "..", dir, file); if (!existsSync(filePath)) { Annotations.of(this).addError( `Component file ${filePath} does not exists` ); return ""; } return readFileSync(path.join(__dirname, "..", dir, file)).toString(); }; }