token-injectable-docker-builder 1.13.4 → 2.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/.jsii +260 -143
- package/API.md +196 -136
- package/README.md +156 -71
- package/ecrReplication/ecrReplication.js +156 -0
- package/isComplete/isComplete.js +45 -1
- package/lib/build-spec.d.ts +24 -0
- package/lib/build-spec.js +104 -0
- package/lib/builder.d.ts +206 -0
- package/lib/builder.js +289 -0
- package/lib/constants.d.ts +7 -0
- package/lib/constants.js +11 -0
- package/lib/ecr.d.ts +16 -0
- package/lib/ecr.js +30 -0
- package/lib/index.d.ts +2 -261
- package/lib/index.js +6 -402
- package/lib/provider.d.ts +63 -0
- package/lib/provider.js +212 -0
- package/package.json +10 -5
package/isComplete/isComplete.js
CHANGED
|
@@ -6,6 +6,35 @@ const {
|
|
|
6
6
|
CloudWatchLogsClient,
|
|
7
7
|
GetLogEventsCommand,
|
|
8
8
|
} = require('@aws-sdk/client-cloudwatch-logs');
|
|
9
|
+
const {
|
|
10
|
+
ECRClient,
|
|
11
|
+
BatchGetImageCommand,
|
|
12
|
+
} = require('@aws-sdk/client-ecr');
|
|
13
|
+
|
|
14
|
+
async function checkReplicaAvailability(repositoryName, imageTag, replicaRegions) {
|
|
15
|
+
for (const region of replicaRegions) {
|
|
16
|
+
const ecr = new ECRClient({ region });
|
|
17
|
+
try {
|
|
18
|
+
const resp = await ecr.send(new BatchGetImageCommand({
|
|
19
|
+
repositoryName,
|
|
20
|
+
imageIds: [{ imageTag }],
|
|
21
|
+
}));
|
|
22
|
+
const images = resp.images || [];
|
|
23
|
+
if (images.length === 0) {
|
|
24
|
+
console.log(`Replica ${region}: repo exists but tag ${imageTag} not yet present.`);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
console.log(`Replica ${region}: image present.`);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (err.name === 'RepositoryNotFoundException') {
|
|
30
|
+
console.log(`Replica ${region}: destination repo not yet created by ECR replication.`);
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
9
38
|
|
|
10
39
|
exports.handler = async (event) => {
|
|
11
40
|
console.log('--- isComplete Handler Invoked ---');
|
|
@@ -86,9 +115,24 @@ exports.handler = async (event) => {
|
|
|
86
115
|
return { IsComplete: false };
|
|
87
116
|
}
|
|
88
117
|
|
|
89
|
-
// If build succeeded,
|
|
118
|
+
// If build succeeded, optionally wait for cross-region replicas to land
|
|
119
|
+
// before signalling complete. ECR replication is async (most images
|
|
120
|
+
// <30 min, rare cases longer) — without this, a consumer stack in
|
|
121
|
+
// another region may try to pull the image before it's been
|
|
122
|
+
// replicated and fail with "Source image does not exist".
|
|
90
123
|
if (buildStatus === 'SUCCEEDED') {
|
|
91
124
|
const imageTag = event.ResourceProperties?.ImageTag || process.env.IMAGE_TAG;
|
|
125
|
+
const repositoryName = event.ResourceProperties?.RepositoryName;
|
|
126
|
+
const replicaRegions = JSON.parse(event.ResourceProperties?.ReplicaRegions || '[]');
|
|
127
|
+
|
|
128
|
+
if (replicaRegions.length > 0 && repositoryName) {
|
|
129
|
+
const ready = await checkReplicaAvailability(repositoryName, imageTag, replicaRegions);
|
|
130
|
+
if (!ready) {
|
|
131
|
+
console.log('Build succeeded; waiting for replicas to catch up.');
|
|
132
|
+
return { IsComplete: false };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
92
136
|
return {
|
|
93
137
|
IsComplete: true,
|
|
94
138
|
Data: {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a .dockerignore file at `sourcePath` (if present) and combine it with
|
|
3
|
+
* a user-supplied exclude list. The Dockerfile itself is never excluded —
|
|
4
|
+
* `.dockerignore` patterns matching `dockerFile` are filtered out so the
|
|
5
|
+
* S3 asset always contains the Dockerfile.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveExcludes(sourcePath: string, dockerFile: string, exclude: string[] | undefined): string[] | undefined;
|
|
8
|
+
export interface BuildSpecOptions {
|
|
9
|
+
readonly imageTag: string;
|
|
10
|
+
readonly dockerFile?: string;
|
|
11
|
+
readonly buildArgs?: {
|
|
12
|
+
[key: string]: string;
|
|
13
|
+
};
|
|
14
|
+
readonly dockerLoginSecretArn?: string;
|
|
15
|
+
readonly installCommands?: string[];
|
|
16
|
+
readonly preBuildCommands?: string[];
|
|
17
|
+
readonly cacheDisabled: boolean;
|
|
18
|
+
readonly platform: 'linux/amd64' | 'linux/arm64';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Assemble the plain buildspec object that the construct passes to
|
|
22
|
+
* `BuildSpec.fromObject`. Pure function — easy to unit test.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildBuildSpec(opts: BuildSpecOptions): Record<string, unknown>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveExcludes = resolveExcludes;
|
|
4
|
+
exports.buildBuildSpec = buildBuildSpec;
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
/**
|
|
8
|
+
* Parse a .dockerignore file at `sourcePath` (if present) and combine it with
|
|
9
|
+
* a user-supplied exclude list. The Dockerfile itself is never excluded —
|
|
10
|
+
* `.dockerignore` patterns matching `dockerFile` are filtered out so the
|
|
11
|
+
* S3 asset always contains the Dockerfile.
|
|
12
|
+
*/
|
|
13
|
+
function resolveExcludes(sourcePath, dockerFile, exclude) {
|
|
14
|
+
let effective = exclude;
|
|
15
|
+
if (!effective) {
|
|
16
|
+
const dockerignorePath = path.join(sourcePath, '.dockerignore');
|
|
17
|
+
if (fs.existsSync(dockerignorePath)) {
|
|
18
|
+
const fileContent = fs.readFileSync(dockerignorePath, 'utf8');
|
|
19
|
+
effective = fileContent
|
|
20
|
+
.split('\n')
|
|
21
|
+
.map((line) => line.trim())
|
|
22
|
+
.filter((line) => line.length > 0 && !line.startsWith('#'));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!effective)
|
|
26
|
+
return undefined;
|
|
27
|
+
return effective.filter((pattern) => {
|
|
28
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
29
|
+
const regex = new RegExp(`^${escaped.replace(/\*/g, '.*').replace(/\?/g, '.')}$`, 'i');
|
|
30
|
+
return !regex.test(dockerFile);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Assemble the plain buildspec object that the construct passes to
|
|
35
|
+
* `BuildSpec.fromObject`. Pure function — easy to unit test.
|
|
36
|
+
*/
|
|
37
|
+
function buildBuildSpec(opts) {
|
|
38
|
+
const { imageTag, dockerFile, buildArgs, dockerLoginSecretArn, installCommands, preBuildCommands, cacheDisabled, platform, } = opts;
|
|
39
|
+
const buildArgsString = buildArgs
|
|
40
|
+
? Object.entries(buildArgs)
|
|
41
|
+
.map(([k, v]) => `--build-arg ${k}=${v}`)
|
|
42
|
+
.join(' ')
|
|
43
|
+
: '';
|
|
44
|
+
const dockerFileFlag = dockerFile ? `-f $CODEBUILD_SRC_DIR/${dockerFile}` : '';
|
|
45
|
+
const dockerLoginCommands = dockerLoginSecretArn
|
|
46
|
+
? [
|
|
47
|
+
'echo "Retrieving Docker credentials..."',
|
|
48
|
+
'apt-get update -y && apt-get install -y jq',
|
|
49
|
+
`DOCKER_USERNAME=$(aws secretsmanager get-secret-value --secret-id ${dockerLoginSecretArn} --query SecretString --output text | jq -r .username)`,
|
|
50
|
+
`DOCKER_PASSWORD=$(aws secretsmanager get-secret-value --secret-id ${dockerLoginSecretArn} --query SecretString --output text | jq -r .password)`,
|
|
51
|
+
'echo "Logging in to Docker Hub..."',
|
|
52
|
+
'echo $DOCKER_PASSWORD | docker login --username $DOCKER_USERNAME --password-stdin',
|
|
53
|
+
]
|
|
54
|
+
: ['echo "No Docker credentials. Skipping Docker Hub login."'];
|
|
55
|
+
const buildxInstallCommands = cacheDisabled
|
|
56
|
+
? []
|
|
57
|
+
: [
|
|
58
|
+
'echo "Setting up Docker buildx for ECR layer cache..."',
|
|
59
|
+
'docker buildx create --driver docker-container --name ecr-cache-builder --use 2>/dev/null || docker buildx use ecr-cache-builder',
|
|
60
|
+
];
|
|
61
|
+
const platformFlag = `--platform ${platform}`;
|
|
62
|
+
// --provenance=false --sbom=false: Docker Buildx v0.10+ adds attestations by default,
|
|
63
|
+
// producing OCI image indexes that AWS Lambda does not support.
|
|
64
|
+
const buildCommand = cacheDisabled
|
|
65
|
+
? `docker build ${platformFlag} ${dockerFileFlag} ${buildArgsString} -t $ECR_REPO_URI:${imageTag} $CODEBUILD_SRC_DIR`
|
|
66
|
+
: `docker buildx build --push ${platformFlag} --provenance=false --sbom=false --cache-from type=registry,ref=$ECR_REPO_URI:cache --cache-to type=registry,ref=$ECR_REPO_URI:cache,mode=max,image-manifest=true ${dockerFileFlag} ${buildArgsString} -t $ECR_REPO_URI:${imageTag} $CODEBUILD_SRC_DIR`;
|
|
67
|
+
return {
|
|
68
|
+
version: '0.2',
|
|
69
|
+
phases: {
|
|
70
|
+
install: {
|
|
71
|
+
commands: [
|
|
72
|
+
'echo "Beginning install phase..."',
|
|
73
|
+
...(installCommands ?? []),
|
|
74
|
+
...buildxInstallCommands,
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
pre_build: {
|
|
78
|
+
commands: [
|
|
79
|
+
...(preBuildCommands ?? []),
|
|
80
|
+
...dockerLoginCommands,
|
|
81
|
+
'echo "Retrieving AWS Account ID..."',
|
|
82
|
+
'export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)',
|
|
83
|
+
'echo "Logging into Amazon ECR..."',
|
|
84
|
+
'aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com',
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
build: {
|
|
88
|
+
commands: [
|
|
89
|
+
`echo "Building Docker image with tag ${imageTag}..."`,
|
|
90
|
+
buildCommand,
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
...(cacheDisabled && {
|
|
94
|
+
post_build: {
|
|
95
|
+
commands: [
|
|
96
|
+
`echo "Pushing Docker image with tag ${imageTag}..."`,
|
|
97
|
+
`docker push $ECR_REPO_URI:${imageTag}`,
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbGQtc3BlYy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9idWlsZC1zcGVjLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBU0EsMENBd0JDO0FBaUJELHdDQWtGQztBQXBJRCx5QkFBeUI7QUFDekIsNkJBQTZCO0FBRTdCOzs7OztHQUtHO0FBQ0gsU0FBZ0IsZUFBZSxDQUM3QixVQUFrQixFQUNsQixVQUFrQixFQUNsQixPQUE2QjtJQUU3QixJQUFJLFNBQVMsR0FBRyxPQUFPLENBQUM7SUFDeEIsSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO1FBQ2YsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxlQUFlLENBQUMsQ0FBQztRQUNoRSxJQUFJLEVBQUUsQ0FBQyxVQUFVLENBQUMsZ0JBQWdCLENBQUMsRUFBRSxDQUFDO1lBQ3BDLE1BQU0sV0FBVyxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsZ0JBQWdCLEVBQUUsTUFBTSxDQUFDLENBQUM7WUFDOUQsU0FBUyxHQUFHLFdBQVc7aUJBQ3BCLEtBQUssQ0FBQyxJQUFJLENBQUM7aUJBQ1gsR0FBRyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7aUJBQzFCLE1BQU0sQ0FBQyxDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7UUFDaEUsQ0FBQztJQUNILENBQUM7SUFFRCxJQUFJLENBQUMsU0FBUztRQUFFLE9BQU8sU0FBUyxDQUFDO0lBRWpDLE9BQU8sU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFO1FBQ2xDLE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsbUJBQW1CLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFDN0QsTUFB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ZnJvbSAncGF0aCc7XG5cbi8qKlxuICogUGFyc2UgYSAuZG9ja2VyaWdub3JlIGZpbGUgYXQgYHNvdXJjZVBhdGhgIChpZiBwcmVzZW50KSBhbmQgY29tYmluZSBpdCB3aXRoXG4gKiBhIHVzZXItc3VwcGxpZWQgZXhjbHVkZSBsaXN0LiBUaGUgRG9ja2VyZmlsZSBpdHNlbGYgaXMgbmV2ZXIgZXhjbHVkZWQg4oCUXG4gKiBgLmRvY2tlcmlnbm9yZWAgcGF0dGVybnMgbWF0Y2hpbmcgYGRvY2tlckZpbGVgIGFyZSBmaWx0ZXJlZCBvdXQgc28gdGhlXG4gKiBTMyBhc3NldCBhbHdheXMgY29udGFpbnMgdGhlIERvY2tlcmZpbGUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlRXhjbHVkZXMoXG4gIHNvdXJjZVBhdGg6IHN0cmluZyxcbiAgZG9ja2VyRmlsZTogc3RyaW5nLFxuICBleGNsdWRlOiBzdHJpbmdbXSB8IHVuZGVmaW5lZCxcbik6IHN0cmluZ1tdIHwgdW5kZWZpbmVkIHtcbiAgbGV0IGVmZmVjdGl2ZSA9IGV4Y2x1ZGU7XG4gIGlmICghZWZmZWN0aXZlKSB7XG4gICAgY29uc3QgZG9ja2VyaWdub3JlUGF0aCA9IHBhdGguam9pbihzb3VyY2VQYXRoLCAnLmRvY2tlcmlnbm9yZScpO1xuICAgIGlmIChmcy5leGlzdHNTeW5jKGRvY2tlcmlnbm9yZVBhdGgpKSB7XG4gICAgICBjb25zdCBmaWxlQ29udGVudCA9IGZzLnJlYWRGaWxlU3luYyhkb2NrZXJpZ25vcmVQYXRoLCAndXRmOCcpO1xuICAgICAgZWZmZWN0aXZlID0gZmlsZUNvbnRlbnRcbiAgICAgICAgLnNwbGl0KCdcXG4nKVxuICAgICAgICAubWFwKChsaW5lKSA9PiBsaW5lLnRyaW0oKSlcbiAgICAgICAgLmZpbHRlcigobGluZSkgPT4gbGluZS5sZW5ndGggPiAwICYmICFsaW5lLnN0YXJ0c1dpdGgoJyMnKSk7XG4gICAgfVxuICB9XG5cbiAgaWYgKCFlZmZlY3RpdmUpIHJldHVybiB1bmRlZmluZWQ7XG5cbiAgcmV0dXJuIGVmZmVjdGl2ZS5maWx0ZXIoKHBhdHRlcm4pID0+IHtcbiAgICBjb25zdCBlc2NhcGVkID0gcGF0dGVybi5yZXBsYWNlKC9bLiteJHt9KCl8W1xcXVxcXFxdL2csICdcXFxcJCYnKTtcbiAgICBjb25zdCByZWdleCA9IG5ldyBSZWdFeHAoYF4ke2VzY2FwZWQucmVwbGFjZSgvXFwqL2csICcuKicpLnJlcGxhY2UoL1xcPy9nLCAnLicpfSRgLCAnaScpO1xuICAgIHJldHVybiAhcmVnZXgudGVzdChkb2NrZXJGaWxlKTtcbiAgfSk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgQnVpbGRTcGVjT3B0aW9ucyB7XG4gIHJlYWRvbmx5IGltYWdlVGFnOiBzdHJpbmc7XG4gIHJlYWRvbmx5IGRvY2tlckZpbGU/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IGJ1aWxkQXJncz86IHsgW2tleTogc3RyaW5nXTogc3RyaW5nIH07XG4gIHJlYWRvbmx5IGRvY2tlckxvZ2luU2VjcmV0QXJuPzogc3RyaW5nO1xuICByZWFkb25seSBpbnN0YWxsQ29tbWFuZHM/OiBzdHJpbmdbXTtcbiAgcmVhZG9ubHkgcHJlQnVpbGRDb21tYW5kcz86IHN0cmluZ1tdO1xuICByZWFkb25seSBjYWNoZURpc2FibGVkOiBib29sZWFuO1xuICByZWFkb25seSBwbGF0Zm9ybTogJ2xpbnV4L2FtZDY0JyB8ICdsaW51eC9hcm02NCc7XG59XG5cbi8qKlxuICogQXNzZW1ibGUgdGhlIHBsYWluIGJ1aWxkc3BlYyBvYmplY3QgdGhhdCB0aGUgY29uc3RydWN0IHBhc3NlcyB0b1xuICogYEJ1aWxkU3BlYy5mcm9tT2JqZWN0YC4gUHVyZSBmdW5jdGlvbiDigJQgZWFzeSB0byB1bml0IHRlc3QuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBidWlsZEJ1aWxkU3BlYyhvcHRzOiBCdWlsZFNwZWNPcHRpb25zKTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4ge1xuICBjb25zdCB7XG4gICAgaW1hZ2VUYWcsXG4gICAgZG9ja2VyRmlsZSxcbiAgICBidWlsZEFyZ3MsXG4gICAgZG9ja2VyTG9naW5TZWNyZXRBcm4sXG4gICAgaW5zdGFsbENvbW1hbmRzLFxuICAgIHByZUJ1aWxkQ29tbWFuZHMsXG4gICAgY2FjaGVEaXNhYmxlZCxcbiAgICBwbGF0Zm9ybSxcbiAgfSA9IG9wdHM7XG5cbiAgY29uc3QgYnVpbGRBcmdzU3RyaW5nID0gYnVpbGRBcmdzXG4gICAgPyBPYmplY3QuZW50cmllcyhidWlsZEFyZ3MpXG4gICAgICAubWFwKChbaywgdl0pID0+IGAtLWJ1aWxkLWFyZyAke2t9PSR7dn1gKVxuICAgICAgLmpvaW4oJyAnKVxuICAgIDogJyc7XG5cbiAgY29uc3QgZG9ja2VyRmlsZUZsYWcgPSBkb2NrZXJGaWxlID8gYC1mICRDT0RFQlVJTERfU1JDX0RJUi8ke2RvY2tlckZpbGV9YCA6ICcnO1xuXG4gIGNvbnN0IGRvY2tlckxvZ2luQ29tbWFuZHMgPSBkb2NrZXJMb2dpblNlY3JldEFyblxuICAgID8gW1xuICAgICAgJ2VjaG8gXCJSZXRyaWV2aW5nIERvY2tlciBjcmVkZW50aWFscy4uLlwiJyxcbiAgICAgICdhcHQtZ2V0IHVwZGF0ZSAteSAmJiBhcHQtZ2V0IGluc3RhbGwgLXkganEnLFxuICAgICAgYERPQ0tFUl9VU0VSTkFNRT0kKGF3cyBzZWNyZXRzbWFuYWdlciBnZXQtc2VjcmV0LXZhbHVlIC0tc2VjcmV0LWlkICR7ZG9ja2VyTG9naW5TZWNyZXRBcm59IC0tcXVlcnkgU2VjcmV0U3RyaW5nIC0tb3V0cHV0IHRleHQgfCBqcSAtciAudXNlcm5hbWUpYCxcbiAgICAgIGBET0NLRVJfUEFTU1dPUkQ9JChhd3Mgc2VjcmV0c21hbmFnZXIgZ2V0LXNlY3JldC12YWx1ZSAtLXNlY3JldC1pZCAke2RvY2tlckxvZ2luU2VjcmV0QXJufSAtLXF1ZXJ5IFNlY3JldFN0cmluZyAtLW91dHB1dCB0ZXh0IHwganEgLXIgLnBhc3N3b3JkKWAsXG4gICAgICAnZWNobyBcIkxvZ2dpbmcgaW4gdG8gRG9ja2VyIEh1Yi4uLlwiJyxcbiAgICAgICdlY2hvICRET0NLRVJfUEFTU1dPUkQgfCBkb2NrZXIgbG9naW4gLS11c2VybmFtZSAkRE9DS0VSX1VTRVJOQU1FIC0tcGFzc3dvcmQtc3RkaW4nLFxuICAgIF1cbiAgICA6IFsnZWNobyBcIk5vIERvY2tlciBjcmVkZW50aWFscy4gU2tpcHBpbmcgRG9ja2VyIEh1YiBsb2dpbi5cIiddO1xuXG4gIGNvbnN0IGJ1aWxkeEluc3RhbGxDb21tYW5kcyA9IGNhY2hlRGlzYWJsZWRcbiAgICA/IFtdXG4gICAgOiBbXG4gICAgICAnZWNobyBcIlNldHRpbmcgdXAgRG9ja2VyIGJ1aWxkeCBmb3IgRUNSIGxheWVyIGNhY2hlLi4uXCInLFxuICAgICAgJ2RvY2tlciBidWlsZHggY3JlYXRlIC0tZHJpdmVyIGRvY2tlci1jb250YWluZXIgLS1uYW1lIGVjci1jYWNoZS1idWlsZGVyIC0tdXNlIDI+L2Rldi9udWxsIHx8IGRvY2tlciBidWlsZHggdXNlIGVjci1jYWNoZS1idWlsZGVyJyxcbiAgICBdO1xuXG4gIGNvbnN0IHBsYXRmb3JtRmxhZyA9IGAtLXBsYXRmb3JtICR7cGxhdGZvcm19YDtcblxuICAvLyAtLXByb3ZlbmFuY2U9ZmFsc2UgLS1zYm9tPWZhbHNlOiBEb2NrZXIgQnVpbGR4IHYwLjEwKyBhZGRzIGF0dGVzdGF0aW9ucyBieSBkZWZhdWx0LFxuICAvLyBwcm9kdWNpbmcgT0NJIGltYWdlIGluZGV4ZXMgdGhhdCBBV1MgTGFtYmRhIGRvZXMgbm90IHN1cHBvcnQuXG4gIGNvbnN0IGJ1aWxkQ29tbWFuZCA9IGNhY2hlRGlzYWJsZWRcbiAgICA/IGBkb2NrZXIgYnVpbGQgJHtwbGF0Zm9ybUZsYWd9ICR7ZG9ja2VyRmlsZUZsYWd9ICR7YnVpbGRBcmdzU3RyaW5nfSAtdCAkRUNSX1JFUE9fVVJJOiR7aW1hZ2VUYWd9ICRDT0RFQlVJTERfU1JDX0RJUmBcbiAgICA6IGBkb2NrZXIgYnVpbGR4IGJ1aWxkIC0tcHVzaCAke3BsYXRmb3JtRmxhZ30gLS1wcm92ZW5hbmNlPWZhbHNlIC0tc2JvbT1mYWxzZSAtLWNhY2hlLWZyb20gdHlwZT1yZWdpc3RyeSxyZWY9JEVDUl9SRVBPX1VSSTpjYWNoZSAtLWNhY2hlLXRvIHR5cGU9cmVnaXN0cnkscmVmPSRFQ1JfUkVQT19VUkk6Y2FjaGUsbW9kZT1tYXgsaW1hZ2UtbWFuaWZlc3Q9dHJ1ZSAke2RvY2tlckZpbGVGbGFnfSAke2J1aWxkQXJnc1N0cmluZ30gLXQgJEVDUl9SRVBPX1VSSToke2ltYWdlVGFnfSAkQ09ERUJVSUxEX1NSQ19ESVJgO1xuXG4gIHJldHVybiB7XG4gICAgdmVyc2lvbjogJzAuMicsXG4gICAgcGhhc2VzOiB7XG4gICAgICBpbnN0YWxsOiB7XG4gICAgICAgIGNvbW1hbmRzOiBbXG4gICAgICAgICAgJ2VjaG8gXCJCZWdpbm5pbmcgaW5zdGFsbCBwaGFzZS4uLlwiJyxcbiAgICAgICAgICAuLi4oaW5zdGFsbENvbW1hbmRzID8/IFtdKSxcbiAgICAgICAgICAuLi5idWlsZHhJbnN0YWxsQ29tbWFuZHMsXG4gICAgICAgIF0sXG4gICAgICB9LFxuICAgICAgcHJlX2J1aWxkOiB7XG4gICAgICAgIGNvbW1hbmRzOiBbXG4gICAgICAgICAgLi4uKHByZUJ1aWxkQ29tbWFuZHMgPz8gW10pLFxuICAgICAgICAgIC4uLmRvY2tlckxvZ2luQ29tbWFuZHMsXG4gICAgICAgICAgJ2VjaG8gXCJSZXRyaWV2aW5nIEFXUyBBY2NvdW50IElELi4uXCInLFxuICAgICAgICAgICdleHBvcnQgQUNDT1VOVF9JRD0kKGF3cyBzdHMgZ2V0LWNhbGxlci1pZGVudGl0eSAtLXF1ZXJ5IEFjY291bnQgLS1vdXRwdXQgdGV4dCknLFxuICAgICAgICAgICdlY2hvIFwiTG9nZ2luZyBpbnRvIEFtYXpvbiBFQ1IuLi5cIicsXG4gICAgICAgICAgJ2F3cyBlY3IgZ2V0LWxvZ2luLXBhc3N3b3JkIC0tcmVnaW9uICRBV1NfREVGQVVMVF9SRUdJT04gfCBkb2NrZXIgbG9naW4gLS11c2VybmFtZSBBV1MgLS1wYXNzd29yZC1zdGRpbiAkQUNDT1VOVF9JRC5ka3IuZWNyLiRBV1NfREVGQVVMVF9SRUdJT04uYW1hem9uYXdzLmNvbScsXG4gICAgICAgIF0sXG4gICAgICB9LFxuICAgICAgYnVpbGQ6IHtcbiAgICAgICAgY29tbWFuZHM6IFtcbiAgICAgICAgICBgZWNobyBcIkJ1aWxkaW5nIERvY2tlciBpbWFnZSB3aXRoIHRhZyAke2ltYWdlVGFnfS4uLlwiYCxcbiAgICAgICAgICBidWlsZENvbW1hbmQsXG4gICAgICAgIF0sXG4gICAgICB9LFxuICAgICAgLi4uKGNhY2hlRGlzYWJsZWQgJiYge1xuICAgICAgICBwb3N0X2J1aWxkOiB7XG4gICAgICAgICAgY29tbWFuZHM6IFtcbiAgICAgICAgICAgIGBlY2hvIFwiUHVzaGluZyBEb2NrZXIgaW1hZ2Ugd2l0aCB0YWcgJHtpbWFnZVRhZ30uLi5cImAsXG4gICAgICAgICAgICBgZG9ja2VyIHB1c2ggJEVDUl9SRVBPX1VSSToke2ltYWdlVGFnfWAsXG4gICAgICAgICAgXSxcbiAgICAgICAgfSxcbiAgICAgIH0pLFxuICAgIH0sXG4gIH07XG59XG4iXX0=
|
package/lib/builder.d.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { IVpc, ISecurityGroup, SubnetSelection } from 'aws-cdk-lib/aws-ec2';
|
|
2
|
+
import { ContainerImage } from 'aws-cdk-lib/aws-ecs';
|
|
3
|
+
import { DockerImageCode } from 'aws-cdk-lib/aws-lambda';
|
|
4
|
+
import { ILogGroup } from 'aws-cdk-lib/aws-logs';
|
|
5
|
+
import { Construct } from 'constructs';
|
|
6
|
+
import { TokenInjectableDockerBuilderProvider } from './provider';
|
|
7
|
+
/**
|
|
8
|
+
* Properties for the `TokenInjectableDockerBuilder` construct.
|
|
9
|
+
*/
|
|
10
|
+
export interface TokenInjectableDockerBuilderProps {
|
|
11
|
+
/**
|
|
12
|
+
* The path to the directory containing the Dockerfile or source code.
|
|
13
|
+
*/
|
|
14
|
+
readonly path: string;
|
|
15
|
+
/**
|
|
16
|
+
* Build arguments to pass to the Docker build process.
|
|
17
|
+
* These are transformed into `--build-arg KEY=VALUE` flags.
|
|
18
|
+
* @example
|
|
19
|
+
* {
|
|
20
|
+
* TOKEN: 'my-secret-token',
|
|
21
|
+
* ENV: 'production'
|
|
22
|
+
* }
|
|
23
|
+
*/
|
|
24
|
+
readonly buildArgs?: {
|
|
25
|
+
[key: string]: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The ARN of the AWS Secrets Manager secret containing Docker login credentials.
|
|
29
|
+
* The secret must store a JSON object: `{"username":"...","password":"..."}`.
|
|
30
|
+
* Must be in the same region as the stack.
|
|
31
|
+
*
|
|
32
|
+
* @default - No Docker Hub login.
|
|
33
|
+
*/
|
|
34
|
+
readonly dockerLoginSecretArn?: string;
|
|
35
|
+
/**
|
|
36
|
+
* The VPC in which the CodeBuild project will be deployed.
|
|
37
|
+
*
|
|
38
|
+
* @default - CodeBuild uses public internet.
|
|
39
|
+
*/
|
|
40
|
+
readonly vpc?: IVpc;
|
|
41
|
+
/**
|
|
42
|
+
* Security groups attached to the CodeBuild project.
|
|
43
|
+
*
|
|
44
|
+
* @default - No security groups attached.
|
|
45
|
+
*/
|
|
46
|
+
readonly securityGroups?: ISecurityGroup[];
|
|
47
|
+
/**
|
|
48
|
+
* Subnet selection within the VPC.
|
|
49
|
+
*
|
|
50
|
+
* @default - All subnets in the VPC.
|
|
51
|
+
*/
|
|
52
|
+
readonly subnetSelection?: SubnetSelection;
|
|
53
|
+
/**
|
|
54
|
+
* Custom commands to run during the install phase of CodeBuild.
|
|
55
|
+
*
|
|
56
|
+
* @default - No additional install commands.
|
|
57
|
+
*/
|
|
58
|
+
readonly installCommands?: string[];
|
|
59
|
+
/**
|
|
60
|
+
* Custom commands to run during the pre_build phase of CodeBuild.
|
|
61
|
+
*
|
|
62
|
+
* @default - No additional pre-build commands.
|
|
63
|
+
*/
|
|
64
|
+
readonly preBuildCommands?: string[];
|
|
65
|
+
/**
|
|
66
|
+
* Whether to enable KMS encryption for the ECR repository.
|
|
67
|
+
*
|
|
68
|
+
* @default false
|
|
69
|
+
*/
|
|
70
|
+
readonly kmsEncryption?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* File paths in the Docker directory to exclude from the build asset.
|
|
73
|
+
* Falls back to `.dockerignore` if present.
|
|
74
|
+
*
|
|
75
|
+
* @default - No file path exclusions.
|
|
76
|
+
*/
|
|
77
|
+
readonly exclude?: string[];
|
|
78
|
+
/**
|
|
79
|
+
* Name of the Dockerfile (passed as `-f`).
|
|
80
|
+
*
|
|
81
|
+
* @example 'Dockerfile.production'
|
|
82
|
+
* @default 'Dockerfile'
|
|
83
|
+
*/
|
|
84
|
+
readonly file?: string;
|
|
85
|
+
/**
|
|
86
|
+
* When `true`, disables Docker layer caching.
|
|
87
|
+
*
|
|
88
|
+
* @default false
|
|
89
|
+
*/
|
|
90
|
+
readonly cacheDisabled?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* CloudWatch log group for CodeBuild build logs.
|
|
93
|
+
*
|
|
94
|
+
* @default - CodeBuild default logging.
|
|
95
|
+
*/
|
|
96
|
+
readonly buildLogGroup?: ILogGroup;
|
|
97
|
+
/**
|
|
98
|
+
* Target platform for the Docker image.
|
|
99
|
+
*
|
|
100
|
+
* @default 'linux/amd64'
|
|
101
|
+
*/
|
|
102
|
+
readonly platform?: 'linux/amd64' | 'linux/arm64';
|
|
103
|
+
/**
|
|
104
|
+
* Shared provider for the custom resource Lambdas.
|
|
105
|
+
*
|
|
106
|
+
* Pass `TokenInjectableDockerBuilderProvider.getOrCreate(this, { queryInterval })`
|
|
107
|
+
* if you need a non-default query interval. Otherwise, the construct will
|
|
108
|
+
* call `getOrCreate(this)` itself and reuse the per-stack singleton.
|
|
109
|
+
*
|
|
110
|
+
* @default - Per-stack singleton provider, created on first use.
|
|
111
|
+
*/
|
|
112
|
+
readonly provider?: TokenInjectableDockerBuilderProvider;
|
|
113
|
+
/**
|
|
114
|
+
* ECR pull-through cache repository prefixes to grant pull access to.
|
|
115
|
+
*
|
|
116
|
+
* @example ['docker-hub', 'ghcr']
|
|
117
|
+
* @default - No pull-through cache access.
|
|
118
|
+
*/
|
|
119
|
+
readonly ecrPullThroughCachePrefixes?: string[];
|
|
120
|
+
/**
|
|
121
|
+
* When `true`, creates a CloudWatch log group outside of CloudFormation
|
|
122
|
+
* (`/docker-builder/<projectName>`) and directs CodeBuild output there.
|
|
123
|
+
* Survives stack rollbacks for debugging. 7-day retention.
|
|
124
|
+
*
|
|
125
|
+
* @default false
|
|
126
|
+
*/
|
|
127
|
+
readonly retainBuildLogs?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Additional AWS regions to replicate the built image to via ECR's
|
|
130
|
+
* native registry replication. The image is pushed to the primary
|
|
131
|
+
* region's ECR as usual; ECR asynchronously replicates the same
|
|
132
|
+
* `repositoryName` + `imageTag` to each region listed here.
|
|
133
|
+
*
|
|
134
|
+
* Consumers in another region (a Lambda in `us-west-2` referencing an
|
|
135
|
+
* image built in `us-east-1`) can use `dockerImageCodeFor(region)` or
|
|
136
|
+
* `containerImageFor(region)` to import the replicated image.
|
|
137
|
+
*
|
|
138
|
+
* The custom resource waits for replication to complete before
|
|
139
|
+
* signalling deploy-complete, so downstream stacks can safely deploy
|
|
140
|
+
* immediately after.
|
|
141
|
+
*
|
|
142
|
+
* **Caveats:**
|
|
143
|
+
* - Cross-region replication is not supported between AWS partitions.
|
|
144
|
+
* - Replicas do **not** inherit the primary's encryption (defaults to
|
|
145
|
+
* AES-256), lifecycle policies, or repository policies.
|
|
146
|
+
* - Replicated repositories persist on stack deletion — AWS does not
|
|
147
|
+
* auto-delete them. Clean up manually via the ECR console / CLI if
|
|
148
|
+
* needed.
|
|
149
|
+
* - Both the builder stack and any consumer stack in another region
|
|
150
|
+
* must set `crossRegionReferences: true` for the image tag to flow.
|
|
151
|
+
* - Stacks must have a concrete region (`env: { account, region }`),
|
|
152
|
+
* not the env-agnostic default.
|
|
153
|
+
*
|
|
154
|
+
* @example ['us-west-2', 'eu-west-1']
|
|
155
|
+
* @default [] - no replication
|
|
156
|
+
*/
|
|
157
|
+
readonly replicaRegions?: string[];
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* A CDK construct to build and push Docker images to an ECR repository using
|
|
161
|
+
* CodeBuild and Lambda custom resources, **then** retrieve the final image tag
|
|
162
|
+
* so that ECS/Lambda references use the exact built image.
|
|
163
|
+
*/
|
|
164
|
+
export declare class TokenInjectableDockerBuilder extends Construct {
|
|
165
|
+
/** The ECR repository that stores the resulting Docker image. */
|
|
166
|
+
private readonly ecrRepository;
|
|
167
|
+
/** ECS-compatible container image reference (primary region). */
|
|
168
|
+
readonly containerImage: ContainerImage;
|
|
169
|
+
/** Lambda-compatible DockerImageCode reference (primary region). */
|
|
170
|
+
readonly dockerImageCode: DockerImageCode;
|
|
171
|
+
/** The ECR repository name — preserved across replica regions. */
|
|
172
|
+
readonly repositoryName: string;
|
|
173
|
+
/** The resolved image tag (CFN token; available at deploy time). */
|
|
174
|
+
readonly imageTag: string;
|
|
175
|
+
private readonly primaryRegion;
|
|
176
|
+
private readonly accountId;
|
|
177
|
+
private readonly replicaRegions;
|
|
178
|
+
constructor(scope: Construct, id: string, props: TokenInjectableDockerBuilderProps);
|
|
179
|
+
/**
|
|
180
|
+
* Format the ECR repository URI for a given region. The region must
|
|
181
|
+
* be either the primary region or one of `replicaRegions`.
|
|
182
|
+
*/
|
|
183
|
+
repositoryUriFor(region: string): string;
|
|
184
|
+
/**
|
|
185
|
+
* Import the replicated repository as an ECS-compatible
|
|
186
|
+
* `ContainerImage` in a consumer scope (typically a stack in `region`).
|
|
187
|
+
*
|
|
188
|
+
* The consumer's stack must have `crossRegionReferences: true` when
|
|
189
|
+
* `region` differs from the builder's region.
|
|
190
|
+
*/
|
|
191
|
+
containerImageFor(scope: Construct, region: string): ContainerImage;
|
|
192
|
+
/**
|
|
193
|
+
* Import the replicated repository as a Lambda-compatible
|
|
194
|
+
* `DockerImageCode` in a consumer scope (typically a stack in `region`).
|
|
195
|
+
*
|
|
196
|
+
* The consumer's stack must have `crossRegionReferences: true` when
|
|
197
|
+
* `region` differs from the builder's region.
|
|
198
|
+
*/
|
|
199
|
+
dockerImageCodeFor(scope: Construct, region: string): DockerImageCode;
|
|
200
|
+
private importRepoFor;
|
|
201
|
+
private assertRegionIsKnown;
|
|
202
|
+
private validateReplicaRegions;
|
|
203
|
+
private grantEcrAccess;
|
|
204
|
+
private grantPullThroughCacheAccess;
|
|
205
|
+
private grantBuildLogsAccess;
|
|
206
|
+
}
|