Skip to content

Commit 3b13a91

Browse files
committed
fix(import): address PR review feedback
- Validate entrypoint file exists inside --code directory - Improve --code help text to clarify it points to the entrypoint folder - Validate AWS credentials match target account via STS GetCallerIdentity - Fix project name prefix stripping to only strip known prefix, not any underscore - Rename sanitize() to replaceUnderscoresWithDashes() for clarity - Use existing Dockerfile template from assets instead of hardcoded duplicate
1 parent 4c3e717 commit 3b13a91

2 files changed

Lines changed: 45 additions & 39 deletions

File tree

src/cli/commands/import/import-runtime.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,9 @@ export async function handleImportRuntime(options: ImportResourceOptions): Promi
149149
// Derive local name: strip project prefix if present, or use --name override
150150
let localName = options.name ?? runtimeDetail.agentRuntimeName;
151151
// AgentCore runtime names are often prefixed with projectName_ — strip it
152-
if (localName.includes('_')) {
153-
const parts = localName.split('_');
154-
if (parts.length > 1) {
155-
localName = parts.slice(1).join('_');
156-
}
152+
const prefix = `${ctx.projectName}_`;
153+
if (localName.startsWith(prefix)) {
154+
localName = localName.slice(prefix.length);
157155
}
158156
onProgress(`Runtime: ${runtimeDetail.agentRuntimeName} → local name: ${localName}`);
159157
logger.endStep('success');
@@ -206,6 +204,20 @@ export async function handleImportRuntime(options: ImportResourceOptions): Promi
206204
logPath: logger.getRelativeLogPath(),
207205
};
208206
}
207+
// Validate entrypoint file exists inside source directory
208+
const entrypointPath = path.join(sourcePath, entrypoint);
209+
if (!fs.existsSync(entrypointPath)) {
210+
const error = `Entrypoint file '${entrypoint}' not found in ${sourcePath}. Ensure --code points to the directory containing your entrypoint file.`;
211+
logger.endStep('error', error);
212+
logger.finalize(false);
213+
return {
214+
success: false,
215+
error,
216+
resourceType: 'runtime',
217+
resourceName: localName,
218+
logPath: logger.getRelativeLogPath(),
219+
};
220+
}
209221
logger.endStep('success');
210222

211223
// 6. Check for duplicates
@@ -448,7 +460,10 @@ export function registerImportRuntime(importCmd: Command): void {
448460
.command('runtime')
449461
.description('Import an existing AgentCore Runtime from your AWS account')
450462
.option('--id <runtimeId>', 'Runtime ID to import')
451-
.requiredOption('--code <path>', 'Path to the agent source code directory')
463+
.requiredOption(
464+
'--code <path>',
465+
'Path to the directory containing the entrypoint file (e.g., the folder with main.py)'
466+
)
452467
.option('--entrypoint <file>', 'Entrypoint file (auto-detected from runtime, e.g. main.py)')
453468
.option('--target <target>', 'Deployment target name')
454469
.option('--name <name>', 'Local name for the imported runtime')

src/cli/commands/import/import-utils.ts

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { APP_DIR, ConfigIO, findConfigRoot } from '../../../lib';
22
import type { AwsDeploymentTarget } from '../../../schema';
3-
import { validateAwsCredentials } from '../../aws/account';
3+
import { detectAccount, validateAwsCredentials } from '../../aws/account';
44
import { ExecLogger } from '../../logging';
55
import { setupPythonProject } from '../../operations/python/setup';
6+
import { getTemplatePath } from '../../templates/templateRoot';
67
import * as fs from 'node:fs';
78
import * as path from 'node:path';
89

@@ -87,19 +88,27 @@ export async function resolveImportTarget(options: ResolveTargetOptions): Promis
8788
onProgress?.('Validating AWS credentials...');
8889
await validateAwsCredentials();
8990

91+
// Validate credentials match the target account
92+
const callerAccount = await detectAccount();
93+
if (callerAccount && target.account && callerAccount !== target.account) {
94+
throw new Error(
95+
`Your AWS credentials are for account ${callerAccount}, but the target "${target.name}" is configured for account ${target.account}.\nEnsure your credentials match the deployment target.`
96+
);
97+
}
98+
9099
return target;
91100
}
92101

93102
// ============================================================================
94103
// Stack Name
95104
// ============================================================================
96105

97-
function sanitize(name: string): string {
106+
function replaceUnderscoresWithDashes(name: string): string {
98107
return name.replace(/_/g, '-');
99108
}
100109

101110
export function toStackName(projectName: string, targetName: string): string {
102-
return `AgentCore-${sanitize(projectName)}-${sanitize(targetName)}`;
111+
return `AgentCore-${replaceUnderscoresWithDashes(projectName)}-${replaceUnderscoresWithDashes(targetName)}`;
103112
}
104113

105114
// ============================================================================
@@ -238,36 +247,18 @@ export async function copyAgentSource(options: CopyAgentSourceOptions): Promise<
238247
if (build === 'Container') {
239248
const destDockerfile = path.join(appDir, 'Dockerfile');
240249
if (!fs.existsSync(destDockerfile)) {
241-
onProgress?.('Generating Dockerfile for Container build');
242-
const entryModule = path.basename(options.entrypoint ?? 'main.py', '.py');
243-
fs.writeFileSync(
244-
destDockerfile,
245-
[
246-
'FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim',
247-
'WORKDIR /app',
248-
'',
249-
'ENV UV_SYSTEM_PYTHON=1 \\',
250-
' UV_COMPILE_BYTECODE=1 \\',
251-
' UV_NO_PROGRESS=1 \\',
252-
' PYTHONUNBUFFERED=1 \\',
253-
' DOCKER_CONTAINER=1',
254-
'',
255-
'RUN useradd -m -u 1000 bedrock_agentcore',
256-
'',
257-
'COPY pyproject.toml uv.lock ./',
258-
'RUN uv sync --frozen --no-dev --no-install-project',
259-
'',
260-
'COPY --chown=bedrock_agentcore:bedrock_agentcore . .',
261-
'RUN uv sync --frozen --no-dev',
262-
'',
263-
'USER bedrock_agentcore',
264-
'',
265-
'EXPOSE 8080 8000 9000',
266-
'',
267-
`CMD ["opentelemetry-instrument", "python", "-m", "${entryModule}"]`,
268-
'',
269-
].join('\n')
270-
);
250+
const isPython = options.entrypoint?.endsWith('.py') ?? true;
251+
if (isPython) {
252+
onProgress?.('Generating Dockerfile for Container build');
253+
const entryModule = path.basename(options.entrypoint ?? 'main.py', '.py');
254+
const templatePath = getTemplatePath('container', 'python', 'Dockerfile');
255+
const template = fs.readFileSync(templatePath, 'utf-8');
256+
fs.writeFileSync(destDockerfile, template.replace('{{entrypoint}}', entryModule));
257+
} else {
258+
onProgress?.(
259+
'No Dockerfile found. Please add a Dockerfile to the source directory for non-Python container builds.'
260+
);
261+
}
271262
}
272263
}
273264
} else {

0 commit comments

Comments
 (0)