diff --git a/src/cli/aws/__tests__/target-region.test.ts b/src/cli/aws/__tests__/target-region.test.ts index 21ec6c8af..3dd617d63 100644 --- a/src/cli/aws/__tests__/target-region.test.ts +++ b/src/cli/aws/__tests__/target-region.test.ts @@ -1,4 +1,5 @@ -import { applyTargetRegionToEnv, withTargetRegion } from '../target-region.js'; +import type { AwsDeploymentTarget } from '../../../schema'; +import { applyTargetRegionToEnv, runWithTargetRegion, withTargetRegion } from '../target-region.js'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; describe('target-region', () => { @@ -96,4 +97,72 @@ describe('target-region', () => { expect(result).toBe(42); }); }); + + describe('runWithTargetRegion', () => { + const buildTarget = (region: string): AwsDeploymentTarget => ({ + name: 'default', + account: '123456789012', + region: region as AwsDeploymentTarget['region'], + }); + + it('applies the resolved target region inside the callback and restores afterwards', async () => { + let seenRegion: string | undefined; + const target = buildTarget('ap-southeast-2'); + + const result = await runWithTargetRegion( + () => Promise.resolve(target), + resolved => { + seenRegion = process.env.AWS_REGION; + expect(resolved).toBe(target); + return Promise.resolve('ok'); + } + ); + + expect(seenRegion).toBe('ap-southeast-2'); + expect(result).toBe('ok'); + expect(process.env.AWS_REGION).toBeUndefined(); + expect(process.env.AWS_DEFAULT_REGION).toBeUndefined(); + }); + + it('skips the env override when no target is resolved', async () => { + let seenRegion: string | undefined; + + await runWithTargetRegion( + () => Promise.resolve(undefined), + resolved => { + seenRegion = process.env.AWS_REGION; + expect(resolved).toBeUndefined(); + return Promise.resolve(); + } + ); + + expect(seenRegion).toBeUndefined(); + expect(process.env.AWS_REGION).toBeUndefined(); + }); + + it('restores env vars even when the callback throws', async () => { + process.env.AWS_REGION = 'us-east-1'; + + await expect( + runWithTargetRegion( + () => Promise.resolve(buildTarget('sa-east-1')), + () => Promise.reject(new Error('boom')) + ) + ).rejects.toThrow('boom'); + + expect(process.env.AWS_REGION).toBe('us-east-1'); + }); + + it('propagates errors from the target resolver without mutating env', async () => { + await expect( + runWithTargetRegion( + () => Promise.reject(new Error('cannot resolve')), + () => Promise.resolve('unreached') + ) + ).rejects.toThrow('cannot resolve'); + + expect(process.env.AWS_REGION).toBeUndefined(); + expect(process.env.AWS_DEFAULT_REGION).toBeUndefined(); + }); + }); }); diff --git a/src/cli/aws/index.ts b/src/cli/aws/index.ts index c44ff37f4..6ff13872e 100644 --- a/src/cli/aws/index.ts +++ b/src/cli/aws/index.ts @@ -2,7 +2,7 @@ export { detectAwsContext, type AwsContext } from './aws-context'; export { detectAccount, getCredentialProvider } from './account'; export { getPartition, arnPrefix, dnsSuffix, serviceEndpoint, consoleDomain } from './partition'; export { detectRegion, type RegionDetectionResult } from './region'; -export { applyTargetRegionToEnv, withTargetRegion } from './target-region'; +export { applyTargetRegionToEnv, withTargetRegion, runWithTargetRegion } from './target-region'; export { invokeBedrockSync, invokeClaude, diff --git a/src/cli/aws/target-region.ts b/src/cli/aws/target-region.ts index b5d9f837a..ad4e59c45 100644 --- a/src/cli/aws/target-region.ts +++ b/src/cli/aws/target-region.ts @@ -10,7 +10,32 @@ * Without this override, a user with a non-default region in aws-targets.json * but no AWS_DEFAULT_REGION set would see resources created in the SDK's default * region — see https://github.com/aws/agentcore-cli/issues/924. + * + * -------------------------------------------------------------------------- + * Policy for new CLI entry points + * -------------------------------------------------------------------------- + * Any new CLI command handler (anything in `src/cli/commands/*` or + * `src/cli/operations/*` that may be invoked from a CLI / TUI entry point) that + * could end up constructing AWS SDK clients without an explicit `region` option + * MUST wrap its body so that `AWS_REGION` / `AWS_DEFAULT_REGION` reflect the + * resolved deployment target's region for the duration of the call. + * + * Use one of: + * - `withTargetRegion(region, fn)` when work fits inside a single + * callback (preferred — cannot leak). + * - `runWithTargetRegion(getTarget, fn)` when target resolution and the + * work itself live in the same scope. + * - `applyTargetRegionToEnv(region)` for handlers whose control flow spans + * helpers that can't easily be wrapped + * in a callback. The returned restore + * function MUST be invoked from a + * `finally` block. + * + * Commands that already pass `region: targetConfig.region` explicitly to every + * SDK client they construct don't strictly need this, but adding the env + * override is cheap and prevents regressions when new helpers are added later. */ +import type { AwsDeploymentTarget } from '../../schema'; type RestoreEnv = () => void; @@ -53,3 +78,31 @@ export async function withTargetRegion(region: string, fn: () => Promise): restore(); } } + +/** + * Resolve a deployment target via `getTarget`, apply its region to the + * environment, and run `fn(target)` with that override in effect. Restores the + * prior environment on return — including when target resolution itself throws, + * when no target is found (the override is simply skipped in that case), or + * when `fn` throws. + * + * Use this in command handlers where the target resolution and the AWS-SDK + * work both live in the same lexical scope. For handlers that span many + * helpers across `try`/`catch`/`finally` blocks, prefer the lower-level + * `applyTargetRegionToEnv` + manual `finally` instead. + */ +export async function runWithTargetRegion( + getTarget: () => Promise, + fn: (target: AwsDeploymentTarget | undefined) => Promise +): Promise { + const target = await getTarget(); + if (!target?.region) { + return fn(target); + } + const restore = applyTargetRegionToEnv(target.region); + try { + return await fn(target); + } finally { + restore(); + } +} diff --git a/src/cli/commands/abtest/command.ts b/src/cli/commands/abtest/command.ts index cc236cdb3..f65a4182e 100644 --- a/src/cli/commands/abtest/command.ts +++ b/src/cli/commands/abtest/command.ts @@ -5,6 +5,7 @@ * from the data plane API, including evaluation scores/metrics. */ import { ConfigIO } from '../../../lib'; +import { applyTargetRegionToEnv } from '../../aws'; import { getABTest, listABTests } from '../../aws/agentcore-ab-tests'; import type { GetABTestResult } from '../../aws/agentcore-ab-tests'; import { dnsSuffix } from '../../aws/partition'; @@ -15,16 +16,39 @@ import type { Command } from '@commander-js/extra-typings'; // Helpers // ============================================================================ +/** + * Resolve the region for AB-test API calls, preferring (in order): + * 1. an explicit `--region` flag + * 2. the first target in `aws-targets.json` + * 3. AWS_DEFAULT_REGION / AWS_REGION env vars + * 4. `us-east-1` + * + * As a side effect, also promotes the resolved region onto AWS_REGION / + * AWS_DEFAULT_REGION so any downstream SDK client constructed without an + * explicit `region` option behaves consistently. See + * https://github.com/aws/agentcore-cli/issues/924. + */ async function getRegion(cliRegion?: string): Promise { - if (cliRegion) return cliRegion; - try { - const configIO = new ConfigIO(); - const targets = await configIO.resolveAWSDeploymentTargets(); - if (targets.length > 0) return targets[0]!.region; - } catch { - // Fall through to env vars + let region: string; + if (cliRegion) { + region = cliRegion; + } else { + let targetRegion: string | undefined; + try { + const configIO = new ConfigIO(); + const targets = await configIO.resolveAWSDeploymentTargets(); + if (targets.length > 0) targetRegion = targets[0]!.region; + } catch { + // Fall through to env vars + } + region = targetRegion ?? process.env.AWS_DEFAULT_REGION ?? process.env.AWS_REGION ?? 'us-east-1'; } - return process.env.AWS_DEFAULT_REGION ?? process.env.AWS_REGION ?? 'us-east-1'; + // Intentionally not restored — the abtest command runs to completion and + // exits the process; restoring would require threading a teardown through + // every caller. The override is safe because it only sets env vars to the + // same region we'd otherwise pass explicitly to every SDK client below. + applyTargetRegionToEnv(region); + return region; } async function resolveABTestId( diff --git a/src/cli/commands/deploy/__tests__/region-override.test.ts b/src/cli/commands/deploy/__tests__/region-override.test.ts new file mode 100644 index 000000000..53829f82d --- /dev/null +++ b/src/cli/commands/deploy/__tests__/region-override.test.ts @@ -0,0 +1,191 @@ +/** + * Region override test (issue #924) + * + * Verifies that `handleDeploy` makes the resolved deployment target's region + * authoritative on the environment for the duration of the call (so AWS SDK + * clients and the CDK toolkit-lib internals — which fall back to AWS_REGION + * / AWS_DEFAULT_REGION when no explicit region is configured — pick up the + * target's region) and restores any prior env values afterwards, including on + * the error path. + */ +// ── Imports under test (must come after mocks) ─────────────────────────── +import { handleDeploy } from '../actions.js'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +// ── Module mocks ────────────────────────────────────────────────────────── + +const mockResolveAWSDeploymentTargets = vi.fn(); +const mockReadProjectSpec = vi.fn(); + +vi.mock('../../../../lib', () => ({ + ConfigIO: class MockConfigIO { + resolveAWSDeploymentTargets = mockResolveAWSDeploymentTargets; + readProjectSpec = mockReadProjectSpec; + }, + SecureCredentials: class MockSecureCredentials { + constructor(public values: Record) {} + }, +})); + +const mockValidateAwsCredentials = vi.fn(); +vi.mock('../../../aws/account', () => ({ + validateAwsCredentials: (...args: unknown[]) => mockValidateAwsCredentials(...args), +})); + +vi.mock('../../../cdk/toolkit-lib', () => ({ + createSwitchableIoHost: () => ({ host: {}, switchToVerbose: vi.fn(), dispose: vi.fn() }), +})); + +vi.mock('../../../cloudformation', () => ({ + buildDeployedState: vi.fn(), + getStackOutputs: vi.fn(), + parseAgentOutputs: vi.fn(), + parseEvaluatorOutputs: vi.fn(), + parseGatewayOutputs: vi.fn(), + parseMemoryOutputs: vi.fn(), + parseOnlineEvalOutputs: vi.fn(), + parsePolicyEngineOutputs: vi.fn(), + parsePolicyOutputs: vi.fn(), + parseRuntimeEndpointOutputs: vi.fn(), +})); + +const mockValidateProject = vi.fn(); +vi.mock('../../../operations/deploy', () => ({ + bootstrapEnvironment: vi.fn(), + buildCdkProject: vi.fn(), + checkBootstrapNeeded: vi.fn(), + checkStackDeployability: vi.fn(), + getAllCredentials: vi.fn().mockReturnValue([]), + hasIdentityApiProviders: vi.fn().mockReturnValue(false), + hasIdentityOAuthProviders: vi.fn().mockReturnValue(false), + performStackTeardown: vi.fn(), + setupApiKeyProviders: vi.fn(), + setupOAuth2Providers: vi.fn(), + setupTransactionSearch: vi.fn(), + synthesizeCdk: vi.fn(), + validateProject: (...args: unknown[]) => mockValidateProject(...args), +})); + +vi.mock('../../../operations/deploy/gateway-status', () => ({ + formatTargetStatus: vi.fn(), + getGatewayTargetStatuses: vi.fn(), +})); + +vi.mock('../../../operations/deploy/post-deploy-ab-tests', () => ({ + deleteOrphanedABTests: vi.fn(), + setupABTests: vi.fn(), +})); + +vi.mock('../../../operations/deploy/post-deploy-config-bundles', () => ({ + resolveConfigBundleComponentKeys: vi.fn(), + setupConfigBundles: vi.fn(), +})); + +vi.mock('../../../operations/deploy/post-deploy-http-gateways', () => ({ + setupHttpGateways: vi.fn(), +})); + +vi.mock('../../../operations/deploy/post-deploy-online-evals', () => ({ + enableOnlineEvalConfigs: vi.fn(), +})); + +vi.mock('../../../logging', () => ({ + ExecLogger: class MockExecLogger { + startStep = vi.fn(); + endStep = vi.fn(); + log = vi.fn(); + finalize = vi.fn(); + getRelativeLogPath = vi.fn().mockReturnValue('agentcore/.cli/logs/deploy/deploy-mock.log'); + logFilePath = 'agentcore/.cli/logs/deploy/deploy-mock.log'; + }, +})); + +// ── Tests ──────────────────────────────────────────────────────────────── + +const TARGET_REGION = 'ap-southeast-2'; +const TARGET_ACCOUNT = '111122223333'; + +describe('handleDeploy — region env override (issue #924)', () => { + let prevRegion: string | undefined; + let prevDefaultRegion: string | undefined; + + beforeEach(() => { + vi.clearAllMocks(); + prevRegion = process.env.AWS_REGION; + prevDefaultRegion = process.env.AWS_DEFAULT_REGION; + delete process.env.AWS_REGION; + delete process.env.AWS_DEFAULT_REGION; + + mockResolveAWSDeploymentTargets.mockResolvedValue([ + { name: 'default', account: TARGET_ACCOUNT, region: TARGET_REGION }, + ]); + mockReadProjectSpec.mockResolvedValue({ + name: 'TestProject', + version: 1, + runtimes: [], + memories: [], + credentials: [], + }); + }); + + afterEach(() => { + if (prevRegion === undefined) delete process.env.AWS_REGION; + else process.env.AWS_REGION = prevRegion; + if (prevDefaultRegion === undefined) delete process.env.AWS_DEFAULT_REGION; + else process.env.AWS_DEFAULT_REGION = prevDefaultRegion; + }); + + it('promotes the target region onto AWS_REGION/AWS_DEFAULT_REGION before downstream operations run', async () => { + let observedRegion: string | undefined; + let observedDefault: string | undefined; + + // validateProject is the first downstream operation after the target is + // loaded — capturing env state here proves the override is in effect by + // the time any SDK client could be constructed by deeper helpers. + mockValidateProject.mockImplementation(() => { + observedRegion = process.env.AWS_REGION; + observedDefault = process.env.AWS_DEFAULT_REGION; + // Return a context that triggers immediate exit (no resources). + throw new Error('halt-after-validate'); + }); + + const result = await handleDeploy({ target: 'default' }); + + expect(observedRegion).toBe(TARGET_REGION); + expect(observedDefault).toBe(TARGET_REGION); + // Error path returned a failure result; env is restored. + expect(result.success).toBe(false); + expect(process.env.AWS_REGION).toBeUndefined(); + expect(process.env.AWS_DEFAULT_REGION).toBeUndefined(); + }); + + it('restores prior AWS_REGION / AWS_DEFAULT_REGION values on the error path', async () => { + process.env.AWS_REGION = 'us-east-1'; + process.env.AWS_DEFAULT_REGION = 'us-east-1'; + + mockValidateProject.mockImplementation(() => { + // Override active inside the call. + expect(process.env.AWS_REGION).toBe(TARGET_REGION); + throw new Error('boom'); + }); + + const result = await handleDeploy({ target: 'default' }); + + expect(result.success).toBe(false); + expect(process.env.AWS_REGION).toBe('us-east-1'); + expect(process.env.AWS_DEFAULT_REGION).toBe('us-east-1'); + }); + + it('does not mutate env when the target cannot be resolved', async () => { + mockResolveAWSDeploymentTargets.mockResolvedValue([]); + + const result = await handleDeploy({ target: 'default' }); + + // Target lookup failed → early return, validateProject was never called, + // and env was never touched. + expect(mockValidateProject).not.toHaveBeenCalled(); + expect(result.success).toBe(false); + expect(process.env.AWS_REGION).toBeUndefined(); + expect(process.env.AWS_DEFAULT_REGION).toBeUndefined(); + }); +}); diff --git a/src/cli/commands/import/__tests__/region-override.test.ts b/src/cli/commands/import/__tests__/region-override.test.ts new file mode 100644 index 000000000..56d1b419c --- /dev/null +++ b/src/cli/commands/import/__tests__/region-override.test.ts @@ -0,0 +1,205 @@ +/** + * Region override test (issue #924) + * + * Verifies that `handleImport` makes the resolved deployment target's region + * authoritative on the environment for the duration of the call (so AWS SDK + * clients constructed without an explicit `region` option pick it up) and + * restores any prior values afterwards — including on the error path. + */ +// ── Imports under test (must come after mocks) ─────────────────────────── +import { handleImport } from '../actions.js'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +// ── Module mocks ────────────────────────────────────────────────────────── + +const mockReadProjectSpec = vi.fn(); +const mockWriteProjectSpec = vi.fn(); +const mockReadAWSDeploymentTargets = vi.fn(); +const mockWriteAWSDeploymentTargets = vi.fn(); +const mockReadDeployedState = vi.fn(); +const mockWriteDeployedState = vi.fn(); +const mockFindConfigRoot = vi.fn(); + +vi.mock('../../../../lib', () => ({ + APP_DIR: 'app', + ConfigIO: class MockConfigIO { + readProjectSpec = mockReadProjectSpec; + writeProjectSpec = mockWriteProjectSpec; + readAWSDeploymentTargets = mockReadAWSDeploymentTargets; + writeAWSDeploymentTargets = mockWriteAWSDeploymentTargets; + readDeployedState = mockReadDeployedState; + writeDeployedState = mockWriteDeployedState; + }, + findConfigRoot: (...args: unknown[]) => mockFindConfigRoot(...args), +})); + +const mockValidateAwsCredentials = vi.fn(); +vi.mock('../../../aws/account', () => ({ + validateAwsCredentials: (...args: unknown[]) => mockValidateAwsCredentials(...args), +})); + +vi.mock('../../../logging', () => ({ + ExecLogger: class MockExecLogger { + startStep = vi.fn(); + endStep = vi.fn(); + log = vi.fn(); + finalize = vi.fn(); + getRelativeLogPath = vi.fn().mockReturnValue('agentcore/.cli/logs/import/import-region-mock.log'); + logFilePath = 'agentcore/.cli/logs/import/import-region-mock.log'; + }, +})); + +const mockExecuteCdkImportPipeline = vi.fn(); +vi.mock('../import-pipeline', () => ({ + executeCdkImportPipeline: (...args: unknown[]) => mockExecuteCdkImportPipeline(...args), +})); + +const mockSetupPythonProject = vi.fn().mockResolvedValue({ status: 'success' }); +vi.mock('../../../operations/python/setup', () => ({ + setupPythonProject: (...args: unknown[]) => mockSetupPythonProject(...args), +})); + +// ── Helpers ────────────────────────────────────────────────────────────── + +const TARGET_REGION = 'ap-southeast-2'; +const TARGET_ACCOUNT = '111122223333'; + +function writeFixtureYaml(dir: string, region: string, account: string, withPhysicalIds: boolean): string { + const yaml = `default_agent: my_agent +agents: + my_agent: + name: my_agent + entrypoint: main.py + deployment_type: direct_code_deploy + runtime_type: PYTHON_3_12 + language: python + aws: + account: '${account}' + region: ${region} + network_configuration: + network_mode: PUBLIC + protocol_configuration: + server_protocol: HTTP + observability: + enabled: true + bedrock_agentcore: +${withPhysicalIds ? ` agent_id: agent-abc-123\n agent_arn: arn:aws:bedrock-agentcore:${region}:${account}:runtime/agent-abc-123` : ' agent_id: null\n agent_arn: null'} +`; + const filePath = path.join(dir, 'config.yaml'); + fs.writeFileSync(filePath, yaml); + return filePath; +} + +function makeProjectSpec() { + return { + name: 'TestProject', + version: 1, + runtimes: [], + memories: [], + credentials: [], + }; +} + +// ── Tests ──────────────────────────────────────────────────────────────── + +describe('handleImport — region env override (issue #924)', () => { + let tmpDir: string; + let prevRegion: string | undefined; + let prevDefaultRegion: string | undefined; + + beforeEach(() => { + vi.clearAllMocks(); + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'import-region-')); + + // Snapshot env so each test starts clean + prevRegion = process.env.AWS_REGION; + prevDefaultRegion = process.env.AWS_DEFAULT_REGION; + delete process.env.AWS_REGION; + delete process.env.AWS_DEFAULT_REGION; + + // Default mocks + mockFindConfigRoot.mockReturnValue(path.join(tmpDir, 'agentcore')); + mockReadProjectSpec.mockResolvedValue(makeProjectSpec()); + mockReadAWSDeploymentTargets.mockResolvedValue([ + { name: 'default', account: TARGET_ACCOUNT, region: TARGET_REGION }, + ]); + mockValidateAwsCredentials.mockResolvedValue(undefined); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + if (prevRegion === undefined) delete process.env.AWS_REGION; + else process.env.AWS_REGION = prevRegion; + if (prevDefaultRegion === undefined) delete process.env.AWS_DEFAULT_REGION; + else process.env.AWS_DEFAULT_REGION = prevDefaultRegion; + }); + + it('promotes the resolved target region onto AWS_REGION/AWS_DEFAULT_REGION during execution and restores afterwards', async () => { + const observed: { region?: string; defaultRegion?: string } = {}; + + // Capture env state at the deepest mocked downstream step so we know the + // override is active by the time SDK clients would be constructed. + mockExecuteCdkImportPipeline.mockImplementation(() => { + observed.region = process.env.AWS_REGION; + observed.defaultRegion = process.env.AWS_DEFAULT_REGION; + return Promise.resolve({ success: true, stackName: 'TestStack-default' }); + }); + + const yamlPath = writeFixtureYaml(tmpDir, TARGET_REGION, TARGET_ACCOUNT, /* withPhysicalIds */ true); + + const result = await handleImport({ source: yamlPath }); + + // Region override was active during the import pipeline call + expect(observed.region).toBe(TARGET_REGION); + expect(observed.defaultRegion).toBe(TARGET_REGION); + // Pipeline was reached (sanity) + expect(mockExecuteCdkImportPipeline).toHaveBeenCalled(); + // Env was restored on exit (we deleted both before the call) + expect(process.env.AWS_REGION).toBeUndefined(); + expect(process.env.AWS_DEFAULT_REGION).toBeUndefined(); + // Sanity: success or at least did not blow up before the pipeline + expect(result.logPath).toBeDefined(); + }); + + it('restores env vars even when an error is thrown mid-flight', async () => { + process.env.AWS_REGION = 'us-east-1'; + process.env.AWS_DEFAULT_REGION = 'us-east-1'; + + mockExecuteCdkImportPipeline.mockImplementation(() => { + // Confirm override is active before throwing + expect(process.env.AWS_REGION).toBe(TARGET_REGION); + throw new Error('synthetic failure'); + }); + + const yamlPath = writeFixtureYaml(tmpDir, TARGET_REGION, TARGET_ACCOUNT, /* withPhysicalIds */ true); + + const result = await handleImport({ source: yamlPath }); + + // The thrown error is caught and surfaced via the result, not re-thrown + expect(result.success).toBe(false); + // Prior env values are restored + expect(process.env.AWS_REGION).toBe('us-east-1'); + expect(process.env.AWS_DEFAULT_REGION).toBe('us-east-1'); + }); + + it('restores env even when there are no physical IDs (light path)', async () => { + // No physical IDs → the import skips the CFN pipeline entirely, but should + // still apply + restore the override based on the resolved single target. + let observedDuring: string | undefined; + mockSetupPythonProject.mockImplementation(() => { + observedDuring = process.env.AWS_REGION; + return Promise.resolve({ status: 'success' as const }); + }); + + const yamlPath = writeFixtureYaml(tmpDir, TARGET_REGION, TARGET_ACCOUNT, /* withPhysicalIds */ false); + + await handleImport({ source: yamlPath }); + + expect(observedDuring).toBe(TARGET_REGION); + expect(process.env.AWS_REGION).toBeUndefined(); + expect(process.env.AWS_DEFAULT_REGION).toBeUndefined(); + }); +}); diff --git a/src/cli/commands/import/actions.ts b/src/cli/commands/import/actions.ts index 71eb70f83..9a0161783 100644 --- a/src/cli/commands/import/actions.ts +++ b/src/cli/commands/import/actions.ts @@ -7,6 +7,7 @@ import type { Credential, Memory, } from '../../../schema'; +import { applyTargetRegionToEnv } from '../../aws'; import { validateAwsCredentials } from '../../aws/account'; import { arnPrefix } from '../../aws/partition'; import { ExecLogger } from '../../logging'; @@ -108,6 +109,22 @@ export async function handleImport(options: ImportOptions): Promise void) | null = null; + const setRegionEnv = (region: string): void => { + // Replace any prior override so we always carry the most authoritative + // region forward; the inner restore would otherwise undo our latest set. + if (restoreEnv) { + restoreEnv(); + } + restoreEnv = applyTargetRegionToEnv(region); + }; + const rollbackConfig = async () => { if (!configWritten || !configIO) return; try { @@ -184,9 +201,10 @@ export async function handleImport(options: ImportOptions): Promise t.name === options.target); } // If still no target, that's fine — we'll use 'default' for the stackName + if (target?.region) { + // Promote target region onto env for any downstream SDK clients. + // See https://github.com/aws/agentcore-cli/issues/924. + setRegionEnv(target.region); + } } logger.endStep('success'); @@ -659,5 +688,12 @@ export async function handleImport(options: ImportOptions): Promise