token-injectable-docker-builder 0.1.6 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.ts DELETED
@@ -1,220 +0,0 @@
1
- import * as path from 'path';
2
- import { Duration, CustomResource, Stack } from 'aws-cdk-lib';
3
- import { Project, Source, LinuxBuildImage, BuildSpec } from 'aws-cdk-lib/aws-codebuild';
4
- import { Repository } from 'aws-cdk-lib/aws-ecr';
5
- import { PolicyStatement } from 'aws-cdk-lib/aws-iam';
6
- import { Code, DockerImageCode, Runtime } from 'aws-cdk-lib/aws-lambda';
7
- import { Asset } from 'aws-cdk-lib/aws-s3-assets';
8
- import { Provider } from 'aws-cdk-lib/custom-resources';
9
- import { Construct } from 'constructs';
10
- import { ContainerImage } from 'aws-cdk-lib/aws-ecs';
11
- import * as crypto from 'crypto';
12
- import { Function } from 'aws-cdk-lib/aws-lambda';
13
-
14
-
15
- /**
16
- * Properties for the `TokenInjectableDockerBuilder` construct.
17
- */
18
- export interface TokenInjectableDockerBuilderProps {
19
- /**
20
- * The path to the directory containing the Dockerfile or source code.
21
- */
22
- path: string;
23
-
24
- /**
25
- * Build arguments to pass to the Docker build process.
26
- * These are transformed into `--build-arg` flags.
27
- * @example
28
- * {
29
- * TOKEN: 'my-secret-token',
30
- * ENV: 'production'
31
- * }
32
- */
33
- buildArgs?: { [key: string]: string };
34
- }
35
-
36
-
37
- /**
38
- * A CDK construct to build and push Docker images to an ECR repository using CodeBuild and Lambda custom resources.
39
- *
40
- * @example
41
- * const dockerBuilder = new TokenInjectableDockerBuilder(this, 'DockerBuilder', {
42
- * path: './docker',
43
- * buildArgs: {
44
- * TOKEN: 'my-secret-token',
45
- * ENV: 'production'
46
- * },
47
- * });
48
- *
49
- * const containerImage = dockerBuilder.getContainerImage();
50
- */
51
- export class TokenInjectableDockerBuilder extends Construct {
52
- private readonly ecrRepository: Repository;
53
- private readonly buildTriggerResource: CustomResource;
54
-
55
- /**
56
- * Creates a new `TokenInjectableDockerBuilder` instance.
57
- *
58
- * @param scope The parent construct/stack.
59
- * @param id The unique ID of the construct.
60
- * @param props Configuration properties for the construct.
61
- */
62
- constructor(scope: Construct, id: string, props: TokenInjectableDockerBuilderProps) {
63
- super(scope, id);
64
-
65
- const { path: sourcePath, buildArgs } = props; // Default to linux/amd64
66
-
67
- // Define absolute paths for Lambda handlers
68
- const onEventHandlerPath = path.resolve(__dirname, '../src/onEventHandler');
69
- const isCompleteHandlerPath = path.resolve(__dirname, '../src/isCompleteHandler');
70
-
71
- // Create an ECR repository
72
- this.ecrRepository = new Repository(this, 'ECRRepository');
73
-
74
- // Package the source code as an asset
75
- const sourceAsset = new Asset(this, 'SourceAsset', {
76
- path: sourcePath, // Path to the Dockerfile or source code
77
- });
78
-
79
- // Transform buildArgs into a string of --build-arg KEY=VALUE
80
- const buildArgsString = buildArgs
81
- ? Object.entries(buildArgs)
82
- .map(([key, value]) => `--build-arg ${key}=${value}`)
83
- .join(' ')
84
- : '';
85
-
86
- // Pass the buildArgsString and platform as environment variables
87
- const environmentVariables: { [name: string]: { value: string } } = {
88
- ECR_REPO_URI: { value: this.ecrRepository.repositoryUri },
89
- BUILD_ARGS: { value: buildArgsString },
90
- };
91
-
92
- // Create a CodeBuild project
93
- const codeBuildProject = new Project(this, 'UICodeBuildProject', {
94
- source: Source.s3({
95
- bucket: sourceAsset.bucket,
96
- path: sourceAsset.s3ObjectKey,
97
- }),
98
- environment: {
99
- buildImage: LinuxBuildImage.STANDARD_7_0,
100
- privileged: true, // Required for Docker builds
101
- },
102
- environmentVariables: environmentVariables,
103
- buildSpec: BuildSpec.fromObject({
104
- version: '0.2',
105
- phases: {
106
- pre_build: {
107
- commands: [
108
- 'echo "Retrieving AWS Account ID..."',
109
- 'export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)',
110
- 'echo "Logging in to Amazon ECR..."',
111
- 'aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com',
112
- ],
113
- },
114
- build: {
115
- commands: [
116
- 'echo Build phase: Building the Docker image...',
117
- 'docker build $BUILD_ARGS -t $ECR_REPO_URI:latest $CODEBUILD_SRC_DIR',
118
- ],
119
- },
120
- post_build: {
121
- commands: [
122
- 'echo Post-build phase: Pushing the Docker image...',
123
- 'docker push $ECR_REPO_URI:latest',
124
- ],
125
- },
126
- },
127
- }),
128
- });
129
-
130
- // Grant permissions to interact with ECR
131
- this.ecrRepository.grantPullPush(codeBuildProject);
132
-
133
- codeBuildProject.role!.addToPrincipalPolicy(
134
- new PolicyStatement({
135
- actions: ['ecr:GetAuthorizationToken'],
136
- resources: ['*'],
137
- })
138
- );
139
-
140
- // Grant permissions to CodeBuild for CloudWatch Logs
141
- codeBuildProject.role!.addToPrincipalPolicy(
142
- new PolicyStatement({
143
- actions: ['logs:PutLogEvents', 'logs:CreateLogGroup', 'logs:CreateLogStream'],
144
- resources: [`arn:aws:logs:${Stack.of(this).region}:${Stack.of(this).account}:*`],
145
- })
146
- );
147
-
148
- // Create Node.js Lambda function for onEvent
149
- const onEventHandlerFunction = new Function(this, 'OnEventHandlerFunction', {
150
- runtime: Runtime.NODEJS_LATEST, // Use Node.js runtime
151
- code: Code.fromAsset(onEventHandlerPath), // Path to handler code
152
- handler: 'index.handler', // Entry point (adjust as needed)
153
- timeout: Duration.minutes(15),
154
- });
155
-
156
- onEventHandlerFunction.addToRolePolicy(
157
- new PolicyStatement({
158
- actions: ['codebuild:StartBuild'],
159
- resources: [codeBuildProject.projectArn], // Restrict to specific project
160
- })
161
- );
162
-
163
- // Create Node.js Lambda function for isComplete
164
- const isCompleteHandlerFunction = new Function(this, 'IsCompleteHandlerFunction', {
165
- runtime: Runtime.NODEJS_LATEST,
166
- code: Code.fromAsset(isCompleteHandlerPath),
167
- handler: 'index.handler',
168
- timeout: Duration.minutes(15),
169
- });
170
-
171
- isCompleteHandlerFunction.addToRolePolicy(
172
- new PolicyStatement({
173
- actions: [
174
- 'codebuild:BatchGetBuilds',
175
- 'codebuild:ListBuildsForProject',
176
- 'logs:GetLogEvents',
177
- 'logs:DescribeLogStreams',
178
- 'logs:DescribeLogGroups'
179
- ],
180
- resources: ['*'],
181
- })
182
- );
183
-
184
- // Create a custom resource provider
185
- const provider = new Provider(this, 'CustomResourceProvider', {
186
- onEventHandler: onEventHandlerFunction,
187
- isCompleteHandler: isCompleteHandlerFunction,
188
- queryInterval: Duration.minutes(1),
189
- });
190
-
191
- // Define the custom resource
192
- this.buildTriggerResource = new CustomResource(this, 'BuildTriggerResource', {
193
- serviceToken: provider.serviceToken,
194
- properties: {
195
- ProjectName: codeBuildProject.projectName,
196
- Trigger: crypto.randomUUID(),
197
- },
198
- });
199
-
200
- this.buildTriggerResource.node.addDependency(codeBuildProject);
201
- }
202
-
203
- /**
204
- * Retrieves the container image from the ECR repository.
205
- *
206
- * @returns A `ContainerImage` object representing the built image.
207
- */
208
- public getContainerImage(): ContainerImage {
209
- return ContainerImage.fromEcrRepository(this.ecrRepository, 'latest');
210
- }
211
-
212
- /**
213
- * Retrieves the Docker image code for use in AWS Lambda.
214
- *
215
- * @returns A `DockerImageCode` object for the Docker image.
216
- */
217
- public getDockerImageCode(): DockerImageCode {
218
- return DockerImageCode.fromEcr(this.ecrRepository);
219
- }
220
- }
@@ -1,97 +0,0 @@
1
- const { CodeBuildClient, ListBuildsForProjectCommand, BatchGetBuildsCommand } = require('@aws-sdk/client-codebuild');
2
- const { CloudWatchLogsClient, GetLogEventsCommand } = require('@aws-sdk/client-cloudwatch-logs');
3
-
4
- exports.handler = async (event, context) => {
5
- console.log('isCompleteHandler Event:', JSON.stringify(event, null, 2));
6
-
7
- // Initialize AWS SDK v3 clients
8
- const codebuildClient = new CodeBuildClient({ region: process.env.AWS_REGION });
9
- const cloudwatchlogsClient = new CloudWatchLogsClient({ region: process.env.AWS_REGION });
10
-
11
- try {
12
- const projectName = event.ResourceProperties.ProjectName;
13
-
14
- if (!projectName) {
15
- throw new Error('ProjectName is required in ResourceProperties');
16
- }
17
-
18
- console.log(`Checking status for CodeBuild project: ${projectName}`);
19
-
20
- // Retrieve the latest build for the given project
21
- const listBuildsCommand = new ListBuildsForProjectCommand({
22
- projectName: projectName,
23
- sortOrder: 'DESCENDING',
24
- maxResults: 1,
25
- });
26
-
27
- const listBuildsResp = await codebuildClient.send(listBuildsCommand);
28
- const buildIds = listBuildsResp.ids;
29
-
30
- if (!buildIds || buildIds.length === 0) {
31
- throw new Error(`No builds found for project: ${projectName}`);
32
- }
33
-
34
- const buildId = buildIds[0];
35
- console.log(`Latest Build ID: ${buildId}`);
36
-
37
- // Get build details
38
- const batchGetBuildsCommand = new BatchGetBuildsCommand({
39
- ids: [buildId],
40
- });
41
-
42
- const buildDetailsResp = await codebuildClient.send(batchGetBuildsCommand);
43
- const build = buildDetailsResp.builds[0];
44
-
45
- if (!build) {
46
- throw new Error(`Build details not found for Build ID: ${buildId}`);
47
- }
48
-
49
- const buildStatus = build.buildStatus;
50
- console.log(`Build Status: ${buildStatus}`);
51
-
52
- if (buildStatus === 'IN_PROGRESS') {
53
- // Build is still in progress
54
- console.log('Build is still in progress.');
55
- return { IsComplete: false };
56
- } else if (buildStatus === 'SUCCEEDED') {
57
- // Build succeeded
58
- console.log('Build succeeded.');
59
- return { IsComplete: true };
60
- } else if (['FAILED', 'FAULT', 'STOPPED', 'TIMED_OUT'].includes(buildStatus)) {
61
- // Build failed; retrieve last 5 log lines
62
- const logsInfo = build.logs;
63
- if (logsInfo && logsInfo.groupName && logsInfo.streamName) {
64
- console.log(`Retrieving logs from CloudWatch Logs Group: ${logsInfo.groupName}, Stream: ${logsInfo.streamName}`);
65
-
66
- const getLogEventsCommand = new GetLogEventsCommand({
67
- logGroupName: logsInfo.groupName,
68
- logStreamName: logsInfo.streamName,
69
- startFromHead: false, // Start from the end to get latest logs
70
- limit: 5,
71
- });
72
-
73
- const logEventsResp = await cloudwatchlogsClient.send(getLogEventsCommand);
74
- const logEvents = logEventsResp.events;
75
- const lastFiveMessages = logEvents.map((event) => event.message).reverse().join('\n');
76
-
77
- const errorMessage = `Build failed with status: ${buildStatus}\nLast 5 build logs:\n${lastFiveMessages}`;
78
- console.error(errorMessage);
79
-
80
- // Throw an error to indicate failure to the CDK provider
81
- throw new Error(errorMessage);
82
- } else {
83
- const errorMessage = `Build failed with status: ${buildStatus}, but logs are not available.`;
84
- console.error(errorMessage);
85
- throw new Error(errorMessage);
86
- }
87
- } else {
88
- const errorMessage = `Unknown build status: ${buildStatus}`;
89
- console.error(errorMessage);
90
- throw new Error(errorMessage);
91
- }
92
- } catch (error) {
93
- console.error('Error in isCompleteHandler:', error);
94
- // Rethrow the error to inform the CDK provider of the failure
95
- throw error;
96
- }
97
- };
@@ -1,39 +0,0 @@
1
- const { CodeBuildClient, StartBuildCommand } = require('@aws-sdk/client-codebuild');
2
-
3
- exports.handler = async (event, context) => {
4
- console.log('Event:', JSON.stringify(event, null, 2));
5
-
6
- // Initialize the AWS SDK v3 CodeBuild client
7
- const codebuildClient = new CodeBuildClient({ region: process.env.AWS_REGION });
8
-
9
- // Set the PhysicalResourceId
10
- let physicalResourceId = event.PhysicalResourceId || event.LogicalResourceId;
11
-
12
- if (event.RequestType === 'Create' || event.RequestType === 'Update') {
13
- const params = {
14
- projectName: event.ResourceProperties.ProjectName,
15
- };
16
-
17
- try {
18
- const command = new StartBuildCommand(params); // Create the command
19
- const build = await codebuildClient.send(command); // Send the command
20
- console.log('Started build:', JSON.stringify(build, null, 2));
21
- } catch (error) {
22
- console.error('Error starting build:', error);
23
-
24
- return {
25
- PhysicalResourceId: physicalResourceId,
26
- Data: {},
27
- Reason: error.message,
28
- };
29
- }
30
- } else if (event.RequestType === 'Delete') {
31
- // No action needed for delete, but ensure PhysicalResourceId remains the same
32
- console.log('Delete request received. No action required.');
33
- }
34
-
35
- return {
36
- PhysicalResourceId: physicalResourceId,
37
- Data: {},
38
- };
39
- };
package/tsconfig.json DELETED
@@ -1,31 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "commonjs",
5
- "lib": [
6
- "es2020",
7
- "dom"
8
- ],
9
- "declaration": true,
10
- "strict": true,
11
- "noImplicitAny": true,
12
- "strictNullChecks": true,
13
- "noImplicitThis": true,
14
- "alwaysStrict": true,
15
- "noUnusedLocals": false,
16
- "noUnusedParameters": false,
17
- "noImplicitReturns": true,
18
- "noFallthroughCasesInSwitch": false,
19
- "inlineSourceMap": true,
20
- "inlineSources": true,
21
- "experimentalDecorators": true,
22
- "strictPropertyInitialization": false,
23
- "typeRoots": [
24
- "./node_modules/@types"
25
- ]
26
- },
27
- "exclude": [
28
- "node_modules",
29
- "cdk.out"
30
- ]
31
- }