Skip to content

Commit 0e38e9e

Browse files
authored
fix: remove dead preflight patch and use proper teardown for evo resources (#1072)
- Preflight: remove config bundle type patching entirely — the CDK schema does not have a type field, so neither the on-disk write (original) nor the in-memory patch was needed - Teardown: delegate to deleteOrphanedABTests and deleteOrphanedHttpGateways instead of raw deleteABTest/deleteHttpGatewayWithTargets — reuses stop/poll/delete/role-cleanup logic. Correct ordering: AB tests first (they create rules on gateways), then gateways, then config bundles. Closes #1070
1 parent 9ccf802 commit 0e38e9e

2 files changed

Lines changed: 50 additions & 42 deletions

File tree

src/cli/operations/deploy/preflight.ts

Lines changed: 1 addition & 19 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,24 +71,6 @@ 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;
85-
}
86-
}
87-
if (patched) {
88-
writeFileSync(specPath, JSON.stringify(rawJson, null, 2), 'utf-8');
89-
}
90-
}
91-
9274
const projectSpec = await configIO.readProjectSpec();
9375
const awsTargets = await configIO.resolveAWSDeploymentTargets();
9476

src/cli/operations/deploy/teardown.ts

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { CONFIG_DIR, ConfigIO } from '../../../lib';
22
import type { AwsDeploymentTarget } from '../../../schema';
33
import { withTargetRegion } from '../../aws';
4+
import { deleteConfigurationBundle } from '../../aws/agentcore-config-bundles';
45
import { CdkToolkitWrapper, silentIoHost } from '../../cdk/toolkit-lib';
56
import { type DiscoveredStack, findStack } from '../../cloudformation/stack-discovery';
6-
import { deleteHttpGatewayWithTargets } from './post-deploy-http-gateways';
7+
import { deleteOrphanedABTests } from './post-deploy-ab-tests';
8+
import { deleteOrphanedHttpGateways } from './post-deploy-http-gateways';
79
import { StackSelectionStrategy } from '@aws-cdk/toolkit-lib';
810
import { existsSync } from 'fs';
911
import { join } from 'path';
@@ -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.
119+
// Ordering: AB tests first (they create rules on gateways), then gateways, then bundles.
120+
// Delegates to the existing orphan-cleanup functions with an empty spec so everything
121+
// is treated as orphaned — reuses stop/poll/delete/role-cleanup logic without duplication.
117122
try {
118123
const deployedState = await configIO.readDeployedState();
119-
const httpGateways = deployedState.targets?.[targetName]?.resources?.httpGateways;
120-
if (httpGateways) {
124+
const resources = deployedState.targets?.[targetName]?.resources;
125+
126+
if (resources?.httpGateways || resources?.configBundles || resources?.abTests) {
121127
let region = deployedTarget?.target.region;
122128
if (!region) {
123129
try {
@@ -129,29 +135,49 @@ 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)) {
138-
try {
139-
const result = await deleteHttpGatewayWithTargets({
140-
region,
141-
gatewayId: gwState.gatewayId,
142-
gatewayName: gwName,
143-
knownTargetId: gwState.targetId,
144-
roleArn: gwState.roleArn,
145-
roleCreatedByCli: gwState.roleCreatedByCli,
146-
});
147-
if (result.success) {
148-
console.log(`Deleted HTTP gateway "${gwName}"`);
149-
} else {
150-
console.warn(`Warning: Failed to delete HTTP gateway "${gwName}": ${result.error}`);
141+
const projectSpec = await configIO.readProjectSpec();
142+
const emptySpec = { ...projectSpec, abTests: [], httpGateways: [] };
143+
144+
if (resources.abTests) {
145+
const abResult = await deleteOrphanedABTests({
146+
region,
147+
projectSpec: emptySpec,
148+
existingABTests: resources.abTests,
149+
});
150+
for (const r of abResult.results) {
151+
if (r.status === 'deleted') {
152+
console.log(`Deleted AB test "${r.testName}"`);
153+
} else if (r.error) {
154+
console.warn(`Warning: Failed to delete AB test "${r.testName}": ${r.error}`);
155+
}
156+
}
157+
}
158+
159+
if (resources.httpGateways) {
160+
const gwResult = await deleteOrphanedHttpGateways({
161+
region,
162+
projectSpec: emptySpec,
163+
existingHttpGateways: resources.httpGateways,
164+
});
165+
for (const r of gwResult.results) {
166+
if (r.status === 'deleted') {
167+
console.log(`Deleted HTTP gateway "${r.gatewayName}"`);
168+
} else if (r.error) {
169+
console.warn(`Warning: Failed to delete HTTP gateway "${r.gatewayName}": ${r.error}`);
151170
}
171+
}
172+
}
173+
174+
for (const [bundleName, bundleState] of Object.entries(resources.configBundles ?? {})) {
175+
try {
176+
await deleteConfigurationBundle({ region, bundleId: bundleState.bundleId });
177+
console.log(`Deleted config bundle "${bundleName}"`);
152178
} catch (err) {
153179
console.warn(
154-
`Warning: Error during HTTP gateway "${gwName}" cleanup: ${err instanceof Error ? err.message : String(err)}`
180+
`Warning: Error during config bundle "${bundleName}" cleanup: ${err instanceof Error ? err.message : String(err)}`
155181
);
156182
}
157183
}
@@ -161,7 +187,7 @@ export async function performStackTeardown(targetName: string): Promise<StackTea
161187
// Only suppress "file not found" — other errors (corrupt state, permissions) should warn
162188
const msg = err instanceof Error ? err.message : String(err);
163189
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}`);
190+
console.warn(`Warning: Could not read deployed state for resource cleanup: ${msg}`);
165191
}
166192
}
167193

0 commit comments

Comments
 (0)