Skip to content

Commit 0510c85

Browse files
committed
feat(cli): support hotswap for AWS::BedrockAgentCore::Runtime
1 parent 2a6f8d3 commit 0510c85

File tree

6 files changed

+267
-2
lines changed

6 files changed

+267
-2
lines changed

packages/@aws-cdk/toolkit-lib/lib/api/aws-auth/sdk.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ import {
2222
UpdateFunctionCommand,
2323
UpdateResolverCommand,
2424
} from '@aws-sdk/client-appsync';
25+
import type {
26+
GetAgentRuntimeCommandInput,
27+
GetAgentRuntimeCommandOutput,
28+
UpdateAgentRuntimeCommandInput,
29+
UpdateAgentRuntimeCommandOutput,
30+
} from '@aws-sdk/client-bedrock-agentcore-control';
31+
import {
32+
BedrockAgentCoreControlClient,
33+
GetAgentRuntimeCommand,
34+
UpdateAgentRuntimeCommand,
35+
} from '@aws-sdk/client-bedrock-agentcore-control';
2536
import type {
2637
GetResourceCommandInput,
2738
GetResourceCommandOutput,
@@ -421,6 +432,11 @@ export interface IAppSyncClient {
421432
listFunctions(input: ListFunctionsCommandInput): Promise<FunctionConfiguration[]>;
422433
}
423434

435+
export interface IBedrockAgentCoreControlClient {
436+
getAgentRuntime(input: GetAgentRuntimeCommandInput): Promise<GetAgentRuntimeCommandOutput>;
437+
updateAgentRuntime(input: UpdateAgentRuntimeCommandInput): Promise<UpdateAgentRuntimeCommandOutput>;
438+
}
439+
424440
export interface ICloudControlClient {
425441
listResources(input: ListResourcesCommandInput): Promise<ListResourcesCommandOutput>;
426442
getResource(input: GetResourceCommandInput): Promise<GetResourceCommandOutput>;
@@ -673,6 +689,16 @@ export class SDK {
673689
};
674690
}
675691

692+
public bedrockAgentCoreControl(): IBedrockAgentCoreControlClient {
693+
const client = new BedrockAgentCoreControlClient(this.config);
694+
return {
695+
getAgentRuntime: (input: GetAgentRuntimeCommandInput): Promise<GetAgentRuntimeCommandOutput> =>
696+
client.send(new GetAgentRuntimeCommand(input)),
697+
updateAgentRuntime: (input: UpdateAgentRuntimeCommandInput): Promise<UpdateAgentRuntimeCommandOutput> =>
698+
client.send(new UpdateAgentRuntimeCommand(input)),
699+
};
700+
}
701+
676702
public cloudControl(): ICloudControlClient {
677703
const client = new CloudControlClient(this.config);
678704
return {
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import type { PropertyDifference } from '@aws-cdk/cloudformation-diff';
2+
import type {
3+
AgentRuntimeArtifact as SdkAgentRuntimeArtifact,
4+
AgentManagedRuntimeType,
5+
} from '@aws-sdk/client-bedrock-agentcore-control';
6+
import type { HotswapChange } from './common';
7+
import { classifyChanges } from './common';
8+
import type { ResourceChange } from '../../payloads/hotswap';
9+
import { ToolkitError } from '../../toolkit/toolkit-error';
10+
import type { SDK } from '../aws-auth/private';
11+
import type { EvaluateCloudFormationTemplate } from '../cloudformation';
12+
13+
export async function isHotswappableBedrockAgentCoreRuntimeChange(
14+
logicalId: string,
15+
change: ResourceChange,
16+
evaluateCfnTemplate: EvaluateCloudFormationTemplate,
17+
): Promise<HotswapChange[]> {
18+
if (change.newValue.Type !== 'AWS::BedrockAgentCore::Runtime') {
19+
return [];
20+
}
21+
22+
const ret: HotswapChange[] = [];
23+
const classifiedChanges = classifyChanges(change, [
24+
'AgentRuntimeArtifact',
25+
'EnvironmentVariables',
26+
'Description',
27+
]);
28+
classifiedChanges.reportNonHotswappablePropertyChanges(ret);
29+
30+
const namesOfHotswappableChanges = Object.keys(classifiedChanges.hotswappableProps);
31+
if (namesOfHotswappableChanges.length > 0) {
32+
const agentRuntimeId = await evaluateCfnTemplate.findPhysicalNameFor(logicalId);
33+
34+
if (!agentRuntimeId) {
35+
return ret;
36+
}
37+
const runtimeChange = await evaluateBedrockAgentCoreRuntimeProps(
38+
classifiedChanges.hotswappableProps,
39+
evaluateCfnTemplate,
40+
);
41+
42+
ret.push({
43+
change: {
44+
cause: change,
45+
resources: [{
46+
logicalId,
47+
resourceType: change.newValue.Type,
48+
physicalName: agentRuntimeId,
49+
metadata: evaluateCfnTemplate.metadataFor(logicalId),
50+
}],
51+
},
52+
hotswappable: true,
53+
service: 'bedrock-agentcore',
54+
apply: async (sdk: SDK) => {
55+
const bedrockAgentCore = sdk.bedrockAgentCoreControl();
56+
57+
const currentRuntime = await bedrockAgentCore.getAgentRuntime({
58+
agentRuntimeId: agentRuntimeId,
59+
});
60+
61+
// While UpdateAgentRuntimeRequest type allows undefined,
62+
// the API will fail at runtime if these required properties are not provided.
63+
if (!currentRuntime.agentRuntimeArtifact) {
64+
throw new ToolkitError('Current runtime does not have an artifact');
65+
}
66+
if (!currentRuntime.roleArn) {
67+
throw new ToolkitError('Current runtime does not have a roleArn');
68+
}
69+
if (!currentRuntime.networkConfiguration) {
70+
throw new ToolkitError('Current runtime does not have a networkConfiguration');
71+
}
72+
73+
// All properties must be explicitly specified, otherwise they will be reset to
74+
// default values. We pass all properties from the current runtime and override
75+
// only the ones that have changed.
76+
await bedrockAgentCore.updateAgentRuntime({
77+
agentRuntimeId: agentRuntimeId,
78+
agentRuntimeArtifact: runtimeChange.artifact
79+
? toSdkAgentRuntimeArtifact(runtimeChange.artifact)
80+
: currentRuntime.agentRuntimeArtifact,
81+
roleArn: currentRuntime.roleArn,
82+
networkConfiguration: currentRuntime.networkConfiguration,
83+
description: runtimeChange.description ?? currentRuntime.description,
84+
authorizerConfiguration: currentRuntime.authorizerConfiguration,
85+
requestHeaderConfiguration: currentRuntime.requestHeaderConfiguration,
86+
protocolConfiguration: currentRuntime.protocolConfiguration,
87+
lifecycleConfiguration: currentRuntime.lifecycleConfiguration,
88+
environmentVariables: runtimeChange.environmentVariables ?? currentRuntime.environmentVariables,
89+
});
90+
},
91+
});
92+
}
93+
94+
return ret;
95+
}
96+
97+
async function evaluateBedrockAgentCoreRuntimeProps(
98+
hotswappablePropChanges: Record<string, PropertyDifference<unknown>>,
99+
evaluateCfnTemplate: EvaluateCloudFormationTemplate,
100+
): Promise<BedrockAgentCoreRuntimeChange> {
101+
const runtimeChange: BedrockAgentCoreRuntimeChange = {};
102+
103+
for (const updatedPropName in hotswappablePropChanges) {
104+
const updatedProp = hotswappablePropChanges[updatedPropName];
105+
106+
switch (updatedPropName) {
107+
case 'AgentRuntimeArtifact':
108+
runtimeChange.artifact = await evaluateAgentRuntimeArtifact(
109+
updatedProp.newValue as CfnAgentRuntimeArtifact,
110+
evaluateCfnTemplate,
111+
);
112+
break;
113+
114+
case 'Description':
115+
runtimeChange.description = await evaluateCfnTemplate.evaluateCfnExpression(updatedProp.newValue);
116+
break;
117+
118+
case 'EnvironmentVariables':
119+
runtimeChange.environmentVariables = await evaluateCfnTemplate.evaluateCfnExpression(updatedProp.newValue);
120+
break;
121+
122+
default:
123+
throw new ToolkitError(
124+
'Unexpected hotswappable property for BedrockAgentCore Runtime. Please report this at github.com/aws/aws-cdk/issues/new/choose',
125+
);
126+
}
127+
}
128+
129+
return runtimeChange;
130+
}
131+
132+
async function evaluateAgentRuntimeArtifact(
133+
artifactValue: CfnAgentRuntimeArtifact,
134+
evaluateCfnTemplate: EvaluateCloudFormationTemplate,
135+
): Promise<AgentRuntimeArtifact | undefined> {
136+
if (artifactValue.CodeConfiguration) {
137+
const codeConfig = artifactValue.CodeConfiguration;
138+
const code = codeConfig.Code;
139+
140+
const s3Location = code.S3 ? {
141+
bucket: await evaluateCfnTemplate.evaluateCfnExpression(code.S3.Bucket),
142+
prefix: await evaluateCfnTemplate.evaluateCfnExpression(code.S3.Prefix),
143+
versionId: code.S3.VersionId
144+
? await evaluateCfnTemplate.evaluateCfnExpression(code.S3.VersionId)
145+
: undefined,
146+
} : undefined;
147+
148+
return {
149+
codeConfiguration: {
150+
code: s3Location ? { s3: s3Location } : {},
151+
runtime: await evaluateCfnTemplate.evaluateCfnExpression(codeConfig.Runtime),
152+
entryPoint: await evaluateCfnTemplate.evaluateCfnExpression(codeConfig.EntryPoint),
153+
},
154+
};
155+
}
156+
157+
if (artifactValue.ContainerConfiguration) {
158+
return {
159+
containerConfiguration: {
160+
containerUri: await evaluateCfnTemplate.evaluateCfnExpression(
161+
artifactValue.ContainerConfiguration.ContainerUri,
162+
),
163+
},
164+
};
165+
}
166+
167+
return undefined;
168+
}
169+
170+
function toSdkAgentRuntimeArtifact(artifact: AgentRuntimeArtifact): SdkAgentRuntimeArtifact {
171+
if (artifact.codeConfiguration) {
172+
const code = artifact.codeConfiguration.code.s3
173+
? { s3: artifact.codeConfiguration.code.s3 }
174+
: undefined;
175+
176+
return {
177+
codeConfiguration: {
178+
code,
179+
runtime: artifact.codeConfiguration.runtime as AgentManagedRuntimeType,
180+
entryPoint: artifact.codeConfiguration.entryPoint,
181+
},
182+
};
183+
}
184+
185+
if (artifact.containerConfiguration) {
186+
return {
187+
containerConfiguration: artifact.containerConfiguration,
188+
};
189+
}
190+
191+
// never reached
192+
throw new ToolkitError('AgentRuntimeArtifact must have either codeConfiguration or containerConfiguration');
193+
}
194+
195+
interface CfnAgentRuntimeArtifact {
196+
readonly CodeConfiguration?: {
197+
readonly Code: {
198+
readonly S3?: {
199+
readonly Bucket: unknown;
200+
readonly Prefix: unknown;
201+
readonly VersionId?: unknown;
202+
};
203+
};
204+
readonly Runtime: unknown;
205+
readonly EntryPoint: unknown;
206+
};
207+
readonly ContainerConfiguration?: {
208+
readonly ContainerUri: unknown;
209+
};
210+
}
211+
212+
interface AgentRuntimeArtifact {
213+
readonly codeConfiguration?: {
214+
readonly code: {
215+
readonly s3?: {
216+
readonly bucket: string;
217+
readonly prefix: string;
218+
readonly versionId?: string;
219+
};
220+
};
221+
readonly runtime: string;
222+
readonly entryPoint: string[];
223+
};
224+
readonly containerConfiguration?: {
225+
readonly containerUri: string;
226+
};
227+
}
228+
229+
interface BedrockAgentCoreRuntimeChange {
230+
artifact?: AgentRuntimeArtifact;
231+
description?: string;
232+
environmentVariables?: Record<string, string>;
233+
}

packages/@aws-cdk/toolkit-lib/lib/api/hotswap/hotswap-deployments.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { SDK, SdkProvider } from '../aws-auth/private';
1010
import type { CloudFormationStack, NestedStackTemplates } from '../cloudformation';
1111
import { loadCurrentTemplateWithNestedStacks, EvaluateCloudFormationTemplate } from '../cloudformation';
1212
import { isHotswappableAppSyncChange } from './appsync-mapping-templates';
13+
import { isHotswappableBedrockAgentCoreRuntimeChange } from './bedrock-agentcore-runtimes';
1314
import { isHotswappableCodeBuildProjectChange } from './code-build-projects';
1415
import type {
1516
HotswapChange,
@@ -59,6 +60,7 @@ const RESOURCE_DETECTORS: { [key: string]: HotswapDetector } = {
5960
'AWS::AppSync::GraphQLSchema': isHotswappableAppSyncChange,
6061
'AWS::AppSync::ApiKey': isHotswappableAppSyncChange,
6162

63+
'AWS::BedrockAgentCore::Runtime': isHotswappableBedrockAgentCoreRuntimeChange,
6264
'AWS::ECS::TaskDefinition': isHotswappableEcsServiceChange,
6365
'AWS::CodeBuild::Project': isHotswappableCodeBuildProjectChange,
6466
'AWS::StepFunctions::StateMachine': isHotswappableStateMachineChange,

packages/@aws-cdk/toolkit-lib/package.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/aws-cdk/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,8 @@ Hotswapping is currently supported for the following changes
517517
- Source and Environment changes of AWS CodeBuild Projects.
518518
- VTL mapping template changes for AppSync Resolvers and Functions.
519519
- Schema changes for AppSync GraphQL Apis.
520+
- Code files (S3-based) and container image (ECR-based) changes, along with environment variable
521+
and description changes of Amazon Bedrock AgentCore Runtimes.
520522

521523
You can optionally configure the behavior of your hotswap deployments. Currently you can only configure ECS hotswap behavior:
522524

packages/aws-cdk/package.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)