-
Notifications
You must be signed in to change notification settings - Fork 55
Expand file tree
/
Copy pathcontainer.ts
More file actions
103 lines (93 loc) · 3.63 KB
/
Copy pathcontainer.ts
File metadata and controls
103 lines (93 loc) · 3.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import type { AgentEnvSpec } from '../../schema';
import { CONTAINER_RUNTIMES, DOCKERFILE_NAME, ONE_GB, getDockerfilePath } from '../constants';
import { getUvBuildArgs } from './build-args';
import { PackagingError } from './errors';
import { resolveCodeLocation } from './helpers';
import type { ArtifactResult, PackageOptions, RuntimePackager } from './types/packaging';
import { spawnSync } from 'child_process';
import { existsSync } from 'fs';
/**
* Detect container runtime synchronously.
* Checks runtimes in CONTAINER_RUNTIMES order; returns the first available binary name.
*/
function detectContainerRuntimeSync(): string | null {
for (const runtime of CONTAINER_RUNTIMES) {
const result = spawnSync('which', [runtime], { stdio: 'pipe' });
if (result.status === 0) {
const versionResult = spawnSync(runtime, ['--version'], { stdio: 'pipe' });
if (versionResult.status === 0) return runtime;
}
}
return null;
}
/**
* Packager for Container agents.
* Builds a container image locally and validates its size.
*/
export class ContainerPackager implements RuntimePackager {
pack(spec: AgentEnvSpec, options: PackageOptions = {}): Promise<ArtifactResult> {
if (spec.build !== 'Container') {
return Promise.reject(new PackagingError('ContainerPackager only supports Container build type.'));
}
const agentName = options.agentName ?? spec.name;
const configBaseDir = options.artifactDir ?? options.projectRoot ?? process.cwd();
const codeLocation = resolveCodeLocation(spec.codeLocation, configBaseDir);
const dockerfilePath = getDockerfilePath(codeLocation, spec.dockerfile);
const buildContext = spec.buildContextPath
? resolveCodeLocation(spec.buildContextPath, configBaseDir)
: codeLocation;
// Preflight: Dockerfile must exist
if (!existsSync(dockerfilePath)) {
return Promise.reject(
new PackagingError(
`${spec.dockerfile ?? DOCKERFILE_NAME} not found at ${dockerfilePath}. Container agents require a Dockerfile.`
)
);
}
// Detect container runtime
const runtime = detectContainerRuntimeSync();
if (!runtime) {
// No runtime available — skip local build validation (deploy will use CodeBuild)
return Promise.resolve({
artifactPath: '',
sizeBytes: 0,
stagingPath: codeLocation,
});
}
// Build locally
const imageName = `agentcore-package-${agentName}`;
const buildArgFlags = Object.entries(spec.customDockerBuildArgs ?? {}).flatMap(([k, v]) => [
'--build-arg',
`${k}=${v}`,
]);
const buildResult = spawnSync(
runtime,
['build', '-t', imageName, '-f', dockerfilePath, ...getUvBuildArgs(), ...buildArgFlags, buildContext],
{
stdio: 'pipe',
}
);
if (buildResult.status !== 0) {
return Promise.reject(new PackagingError(`Container build failed:\n${buildResult.stderr?.toString()}`));
}
// Validate size (1GB limit)
const inspectResult = spawnSync(runtime, ['image', 'inspect', imageName, '--format', '{{.Size}}'], {
stdio: 'pipe',
});
const sizeBytes = parseInt(inspectResult.stdout?.toString().trim() ?? '0', 10);
if (sizeBytes > ONE_GB) {
const sizeMb = (sizeBytes / (1024 * 1024)).toFixed(2);
return Promise.reject(
new PackagingError(
`Container image exceeds 1GB limit (${sizeMb}MB). ` +
'Optimize your Dockerfile: use multi-stage builds, minimize dependencies, add .dockerignore.'
)
);
}
return Promise.resolve({
artifactPath: `${runtime}://${imageName}`,
sizeBytes,
stagingPath: codeLocation,
});
}
}