Skip to content

Commit 65c6431

Browse files
committed
fix: stop rewriting agentcore.json on deploy and clean up evo resources on teardown
- Preflight: apply config bundle type discriminator in-memory only, removing the writeFileSync that caused surprise git diffs - Teardown: delete config bundles and AB tests alongside HTTP gateways when destroying a stack, preventing orphaned AWS resources Closes #1070
1 parent 9ccf802 commit 65c6431

2 files changed

Lines changed: 47 additions & 25 deletions

File tree

src/cli/operations/deploy/preflight.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { CdkToolkitWrapper, createCdkToolkitWrapper, silentIoHost } from '../../
66
import { checkBootstrapStatus, checkStacksStatus, formatCdkEnvironment } from '../../cloudformation';
77
import { cleanupStaleLockFiles } from '../../tui/utils';
88
import type { IIoHost } from '@aws-cdk/toolkit-lib';
9-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
9+
import { existsSync } from 'node:fs';
1010
import * as path from 'node:path';
1111

1212
export interface PreflightContext {
@@ -71,25 +71,17 @@ export async function validateProject(): Promise<PreflightContext> {
7171

7272
const configIO = new ConfigIO({ baseDir: configRoot });
7373

74-
// Patch config bundles on disk to include `type: "ConfigurationBundle"` if missing.
75-
// The CDK package now requires this discriminator during synthesis validation.
76-
const specPath = configIO.getPathResolver().getAgentConfigPath();
77-
const rawJson = JSON.parse(readFileSync(specPath, 'utf-8')) as Record<string, unknown>;
78-
const rawBundles = rawJson.configBundles;
79-
if (Array.isArray(rawBundles) && rawBundles.length > 0) {
80-
let patched = false;
81-
for (const b of rawBundles as Record<string, unknown>[]) {
82-
if (!b.type) {
83-
b.type = 'ConfigurationBundle';
84-
patched = true;
74+
const projectSpec = await configIO.readProjectSpec();
75+
76+
// Ensure config bundles have the `type` discriminator the CDK requires.
77+
// Applied in-memory only — no disk write to avoid surprise git diffs.
78+
if (projectSpec.configBundles) {
79+
for (const b of projectSpec.configBundles) {
80+
if (!(b as Record<string, unknown>).type) {
81+
(b as Record<string, unknown>).type = 'ConfigurationBundle';
8582
}
8683
}
87-
if (patched) {
88-
writeFileSync(specPath, JSON.stringify(rawJson, null, 2), 'utf-8');
89-
}
9084
}
91-
92-
const projectSpec = await configIO.readProjectSpec();
9385
const awsTargets = await configIO.resolveAWSDeploymentTargets();
9486

9587
// Validate that at least one agent or gateway is defined, unless this is a teardown deploy.

src/cli/operations/deploy/teardown.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { CONFIG_DIR, ConfigIO } from '../../../lib';
22
import type { AwsDeploymentTarget } from '../../../schema';
33
import { withTargetRegion } from '../../aws';
4+
import { deleteABTest } from '../../aws/agentcore-ab-tests';
5+
import { deleteConfigurationBundle } from '../../aws/agentcore-config-bundles';
46
import { CdkToolkitWrapper, silentIoHost } from '../../cdk/toolkit-lib';
57
import { type DiscoveredStack, findStack } from '../../cloudformation/stack-discovery';
68
import { deleteHttpGatewayWithTargets } from './post-deploy-http-gateways';
@@ -113,11 +115,15 @@ export async function performStackTeardown(targetName: string): Promise<StackTea
113115
const discovered = await discoverDeployedTargets();
114116
const deployedTarget = discovered.deployedTargets.find(dt => dt.target.name === targetName);
115117

116-
// Clean up imperatively-created HTTP gateways before stack destruction
118+
// Clean up imperatively-created resources before stack destruction
117119
try {
118120
const deployedState = await configIO.readDeployedState();
119-
const httpGateways = deployedState.targets?.[targetName]?.resources?.httpGateways;
120-
if (httpGateways) {
121+
const resources = deployedState.targets?.[targetName]?.resources;
122+
const httpGateways = resources?.httpGateways;
123+
const configBundles = resources?.configBundles;
124+
const abTests = resources?.abTests;
125+
126+
if (httpGateways || configBundles || abTests) {
121127
let region = deployedTarget?.target.region;
122128
if (!region) {
123129
try {
@@ -129,12 +135,10 @@ export async function performStackTeardown(targetName: string): Promise<StackTea
129135
}
130136
}
131137
if (!region) {
132-
console.warn(
133-
'Warning: Could not determine region for HTTP gateway cleanup — gateways may need manual deletion'
134-
);
138+
console.warn('Warning: Could not determine region for resource cleanup — resources may need manual deletion');
135139
}
136140
if (region) {
137-
for (const [gwName, gwState] of Object.entries(httpGateways)) {
141+
for (const [gwName, gwState] of Object.entries(httpGateways ?? {})) {
138142
try {
139143
const result = await deleteHttpGatewayWithTargets({
140144
region,
@@ -155,13 +159,39 @@ export async function performStackTeardown(targetName: string): Promise<StackTea
155159
);
156160
}
157161
}
162+
163+
for (const [bundleName, bundleState] of Object.entries(configBundles ?? {})) {
164+
try {
165+
await deleteConfigurationBundle({ region, bundleId: bundleState.bundleId });
166+
console.log(`Deleted config bundle "${bundleName}"`);
167+
} catch (err) {
168+
console.warn(
169+
`Warning: Error during config bundle "${bundleName}" cleanup: ${err instanceof Error ? err.message : String(err)}`
170+
);
171+
}
172+
}
173+
174+
for (const [testName, testState] of Object.entries(abTests ?? {})) {
175+
try {
176+
const result = await deleteABTest({ region, abTestId: testState.abTestId });
177+
if (result.success) {
178+
console.log(`Deleted AB test "${testName}"`);
179+
} else {
180+
console.warn(`Warning: Failed to delete AB test "${testName}": ${result.error}`);
181+
}
182+
} catch (err) {
183+
console.warn(
184+
`Warning: Error during AB test "${testName}" cleanup: ${err instanceof Error ? err.message : String(err)}`
185+
);
186+
}
187+
}
158188
}
159189
}
160190
} catch (err) {
161191
// Only suppress "file not found" — other errors (corrupt state, permissions) should warn
162192
const msg = err instanceof Error ? err.message : String(err);
163193
if (!msg.includes('ENOENT') && !msg.includes('not found') && !msg.includes('does not exist')) {
164-
console.warn(`Warning: Could not read deployed state for HTTP gateway cleanup: ${msg}`);
194+
console.warn(`Warning: Could not read deployed state for resource cleanup: ${msg}`);
165195
}
166196
}
167197

0 commit comments

Comments
 (0)