UNPKG

token-injectable-docker-builder

Version:

The TokenInjectableDockerBuilder is a flexible AWS CDK construct that enables the usage of AWS CDK tokens in the building, pushing, and deployment of Docker images to Amazon Elastic Container Registry (ECR). It leverages AWS CodeBuild and Lambda custom re

123 lines (108 loc) 4.42 kB
const { CodeBuildClient, StartBuildCommand } = require('@aws-sdk/client-codebuild'); const { CloudWatchLogsClient, CreateLogGroupCommand, PutRetentionPolicyCommand, DeleteLogGroupCommand, } = require('@aws-sdk/client-cloudwatch-logs'); const crypto = require('crypto'); function buildLogGroupName(projectName) { return `/docker-builder/${projectName}`; } async function ensureLogGroup(logsClient, logGroupName) { try { await logsClient.send(new CreateLogGroupCommand({ logGroupName })); console.log(`Created log group: ${logGroupName}`); } catch (error) { if (error.name === 'ResourceAlreadyExistsException') { console.log(`Log group already exists: ${logGroupName}`); } else { throw error; } } await logsClient.send(new PutRetentionPolicyCommand({ logGroupName, retentionInDays: 7, })); console.log(`Set 7-day retention on: ${logGroupName}`); } async function deleteLogGroup(logsClient, logGroupName) { try { await logsClient.send(new DeleteLogGroupCommand({ logGroupName })); console.log(`Deleted log group: ${logGroupName}`); } catch (error) { if (error.name === 'ResourceNotFoundException') { console.log(`Log group does not exist, nothing to delete: ${logGroupName}`); } else { throw error; } } } exports.handler = async (event, context) => { console.log('Event:', JSON.stringify(event, null, 2)); const region = process.env.AWS_REGION; const codebuildClient = new CodeBuildClient({ region }); const logsClient = new CloudWatchLogsClient({ region }); let physicalResourceId = event.PhysicalResourceId || event.LogicalResourceId; const projectName = event.ResourceProperties.ProjectName; const retainBuildLogs = event.ResourceProperties.RetainBuildLogs === 'true'; const logGroupName = buildLogGroupName(projectName); if (event.RequestType === 'Create' || event.RequestType === 'Update') { if (retainBuildLogs) { await ensureLogGroup(logsClient, logGroupName); } else { await deleteLogGroup(logsClient, logGroupName); } const params = { projectName, idempotencyToken: crypto.randomUUID(), ...(retainBuildLogs && { logsConfigOverride: { cloudWatchLogs: { status: 'ENABLED', groupName: logGroupName, }, }, }), }; try { const command = new StartBuildCommand(params); const build = await codebuildClient.send(command); console.log('Started build:', JSON.stringify(build, null, 2)); // Pass the build ID through to isComplete via Data so it polls // *this* build, not whatever ListBuildsForProject returns. The // CDK Provider framework merges onEvent's Data into the event // passed to isComplete (see createResponseEvent in // aws-cdk-lib/custom-resources/.../framework.js). // // Without this, eventual consistency in the Builds listing API // can return the previous SUCCEEDED build before the newly- // started build appears. isComplete then reports success // immediately with the new (not-yet-pushed) image tag, // breaking the downstream Lambda/ECS update with "Source // image does not exist". const startedBuildId = build?.build?.id; if (!startedBuildId) { throw new Error('StartBuild succeeded but returned no build.id'); } return { PhysicalResourceId: physicalResourceId, Data: { BuildId: startedBuildId }, }; } catch (error) { console.error('Error starting build:', error); return { PhysicalResourceId: physicalResourceId, Data: {}, Reason: error.message, }; } } else if (event.RequestType === 'Delete') { await deleteLogGroup(logsClient, logGroupName); console.log('Delete request received. Cleaned up log group.'); } return { PhysicalResourceId: physicalResourceId, Data: {}, }; };