Skip to content

Commit 2637f13

Browse files
committed
feat(cli): support hotswap for AWS::BedrockAgentCore::Runtime
add tweak refactor docs
1 parent 2a6f8d3 commit 2637f13

File tree

7 files changed

+670
-30
lines changed

7 files changed

+670
-30
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: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
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+
} : undefined;
144+
145+
return {
146+
codeConfiguration: {
147+
code: s3Location ? { s3: s3Location } : {},
148+
runtime: await evaluateCfnTemplate.evaluateCfnExpression(codeConfig.Runtime),
149+
entryPoint: await evaluateCfnTemplate.evaluateCfnExpression(codeConfig.EntryPoint),
150+
},
151+
};
152+
}
153+
154+
if (artifactValue.ContainerConfiguration) {
155+
return {
156+
containerConfiguration: {
157+
containerUri: await evaluateCfnTemplate.evaluateCfnExpression(
158+
artifactValue.ContainerConfiguration.ContainerUri,
159+
),
160+
},
161+
};
162+
}
163+
164+
return undefined;
165+
}
166+
167+
function toSdkAgentRuntimeArtifact(artifact: AgentRuntimeArtifact): SdkAgentRuntimeArtifact {
168+
if (artifact.codeConfiguration) {
169+
const code = artifact.codeConfiguration.code.s3
170+
? { s3: artifact.codeConfiguration.code.s3 }
171+
: undefined;
172+
173+
return {
174+
codeConfiguration: {
175+
code,
176+
runtime: artifact.codeConfiguration.runtime as AgentManagedRuntimeType,
177+
entryPoint: artifact.codeConfiguration.entryPoint,
178+
},
179+
};
180+
}
181+
182+
if (artifact.containerConfiguration) {
183+
return {
184+
containerConfiguration: artifact.containerConfiguration,
185+
};
186+
}
187+
188+
// never reached
189+
throw new ToolkitError('AgentRuntimeArtifact must have either codeConfiguration or containerConfiguration');
190+
}
191+
192+
interface CfnAgentRuntimeArtifact {
193+
readonly CodeConfiguration?: {
194+
readonly Code: {
195+
readonly S3?: {
196+
readonly Bucket: unknown;
197+
readonly Prefix: unknown;
198+
};
199+
};
200+
readonly Runtime: unknown;
201+
readonly EntryPoint: unknown;
202+
};
203+
readonly ContainerConfiguration?: {
204+
readonly ContainerUri: unknown;
205+
};
206+
}
207+
208+
interface AgentRuntimeArtifact {
209+
readonly codeConfiguration?: {
210+
readonly code: {
211+
readonly s3?: {
212+
readonly bucket: string;
213+
readonly prefix: string;
214+
};
215+
};
216+
readonly runtime: string;
217+
readonly entryPoint: string[];
218+
};
219+
readonly containerConfiguration?: {
220+
readonly containerUri: string;
221+
};
222+
}
223+
224+
interface BedrockAgentCoreRuntimeChange {
225+
artifact?: AgentRuntimeArtifact;
226+
description?: string;
227+
environmentVariables?: Record<string, string>;
228+
}

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)