diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/.eslintrc.js b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/.eslintrc.js new file mode 100644 index 0000000000000..73d2505a85a7f --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/.eslintrc.js @@ -0,0 +1,4 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +baseConfig.rules['import/order'] = 'off'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/.gitignore b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/.gitignore new file mode 100644 index 0000000000000..c8e007cc2cea3 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/.gitignore @@ -0,0 +1,23 @@ +*.js +!jest.config.js +*.d.ts +node_modules +dist +.LAST_PACKAGE +*.tsbuildinfo +.LAST_BUILD +*.snk +junit.xml +.nyc_output +coverage +nyc.config.js +!.eslintrc.js + +!**/*.snapshot/**/asset.*/*.js +!**/*.snapshot/**/asset.*/*.d.ts + +!**/*.snapshot/**/asset.*/** + +*.jsii +.jsii.tabl.json.gz +.jsiirc.json diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/.npmignore b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/.npmignore new file mode 100644 index 0000000000000..7782bcab1dc19 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/.npmignore @@ -0,0 +1,18 @@ +dist +.LAST_PACKAGE +*.tsbuildinfo +test/ +tsconfig.json +.eslintrc.js +coverage +.nyc_output +*.tgz +*.ts +!*.d.ts +!*.js +!*.lit.ts +!.jsii +.LAST_BUILD +*.snk +junit.xml +**/cdk.out diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/LICENSE b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/LICENSE new file mode 100644 index 0000000000000..5ccf0c6780bab --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/NOTICE b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/NOTICE new file mode 100644 index 0000000000000..cd0946c1cf193 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/README.md b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/README.md new file mode 100644 index 0000000000000..17b0286e09e9f --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/README.md @@ -0,0 +1,383 @@ +# Amazon Bedrock AgentCore Construct Library + + + +--- + +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. +> They are subject to non-backward compatible changes or removal in any future version. These are +> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be +> announced in the release notes. This means that while you may use them, you may need to update +> your source code when upgrading to a newer version of this package. + +--- + + + +| **Language** | **Package** | +| :--------------------------------------------------------------------------------------------- | --------------------------------------- | +| ![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) TypeScript | `@aws-cdk/aws-bedrock-agentcore-alpha` | + +[Amazon Bedrock AgentCore](https://aws.amazon.com/bedrock/agentcore/) enables you to deploy and operate highly capable AI agents securely, at scale. It offers infrastructure purpose-built for dynamic agent workloads, powerful tools to enhance agents, and essential controls for real-world deployment. AgentCore services can be used together or independently and work with any framework including CrewAI, LangGraph, LlamaIndex, and Strands Agents, as well as any foundation model in or outside of Amazon Bedrock, giving you ultimate flexibility. AgentCore eliminates the undifferentiated heavy lifting of building specialized agent infrastructure, so you can accelerate agents to production. + +This construct library facilitates the deployment of Bedrock AgentCore primitives, enabling you to create sophisticated AI applications that can interact with your systems and data sources. + +## Table of contents + +- [Browser Custom tool](#browser) + - [Browser properties](#browser-properties) + - [Browser Network modes](#browser-network-modes) + - [Basic Browser Creation](#basic-browser-creation) + - [Browser IAM permissions](#browser-iam-permissions) +- [Code Interpreter Custom tool](#code-interpreter) + - [Code Interpreter properties](#code-interpreter-properties) + - [Code Interpreter Network Modes](#code-interpreter-network-modes) + - [Basic Code Interpreter Creation](#basic-code-interpreter-creation) + - [Code Interpreter IAM permissions](#code-interpreter-iam-permissions) + +## Browser + +The Amazon Bedrock AgentCore Browser provides a secure, cloud-based browser that enables AI agents to interact with websites. It includes security features such as session isolation, built-in observability through live viewing, CloudTrail logging, and session replay capabilities. + +Additional information about the browser tool can be found in the [official documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/browser-tool.html) + +### Browser Network modes + +The Browser construct supports the following network modes: + +1. **Public Network Mode** (`BrowserNetworkMode.usingPublicNetwork()`) - Default + + - Allows internet access for web browsing and external API calls + - Suitable for scenarios where agents need to interact with publicly available websites + - Enables full web browsing capabilities + - VPC mode is not supported with this option + +2. **VPC (Virtual Private Cloud)** (`BrowserNetworkMode.usingVpc()`) + + - Select whether to run the browser in a virtual private cloud (VPC). + - By configuring VPC connectivity, you enable secure access to private resources such as databases, internal APIs, and services within your VPC. + + While the VPC itself is mandatory, these are optional: + - Subnets - if not provided, CDK will select appropriate subnets from the VPC + - Security Groups - if not provided, CDK will create a default security group + - Specific subnet selection criteria - you can let CDK choose automatically + +For more information on VPC connectivity for Amazon Bedrock AgentCore Browser, please refer to the [official documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-vpc.html). + +### Browser Properties + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `browserCustomName` | `string` | Yes | The name of the browser. Must start with a letter and can be up to 48 characters long. Pattern: `[a-zA-Z][a-zA-Z0-9_]{0,47}` | +| `description` | `string` | No | Optional description for the browser. Can have up to 200 characters | +| `networkConfiguration` | `BrowserNetworkConfiguration` | No | Network configuration for browser. Defaults to PUBLIC network mode | +| `recordingConfig` | `RecordingConfig` | No | Recording configuration for browser. Defaults to no recording | +| `executionRole` | `iam.IRole` | No | The IAM role that provides permissions for the browser to access AWS services. A new role will be created if not provided | +| `tags` | `{ [key: string]: string }` | No | Tags to apply to the browser resource | + +### Basic Browser Creation + +```typescript fixture=default +// Create a basic browser with public network access +const browser = new agentcore.BrowserCustom(this, "MyBrowser", { + browserCustomName: "my_browser", + description: "A browser for web automation", +}); +``` + +### Browser with Tags + +```typescript fixture=default +// Create a browser with custom tags +const browser = new agentcore.BrowserCustom(this, "MyBrowser", { + browserCustomName: "my_browser", + description: "A browser for web automation with tags", + networkConfiguration: agentcore.BrowserNetworkConfiguration.usingPublicNetwork(), + tags: { + Environment: "Production", + Team: "AI/ML", + Project: "AgentCore", + }, +}); +``` + +### Browser with VPC + +```typescript fixture=default +const browser = new agentcore.BrowserCustom(this, 'BrowserVpcWithRecording', { + browserCustomName: 'browser_recording', + networkConfiguration: agentcore.BrowserNetworkConfiguration.usingVpc(this, { + vpc: new ec2.Vpc(this, 'VPC', { restrictDefaultSecurityGroup: false }), + }), +}); +``` + +Browser exposes a [connections](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.Connections.html) property. This property returns a connections object, which simplifies the process of defining and managing ingress and egress rules for security groups in your AWS CDK applications. Instead of directly manipulating security group rules, you interact with the Connections object of a construct, which then translates your connectivity requirements into the appropriate security group rules. For instance: + +```typescript fixture=default +const vpc = new ec2.Vpc(this, 'testVPC'); + +const browser = new agentcore.BrowserCustom(this, 'test-browser', { + browserCustomName: 'test_browser', + networkConfiguration: agentcore.BrowserNetworkConfiguration.usingVpc(this, { + vpc: vpc, + }), +}); + +browser.connections.addSecurityGroup(new ec2.SecurityGroup(this, 'AdditionalGroup', { vpc })); +``` + +So security groups can be added after the browser construct creation. You can use methods like allowFrom() and allowTo() to grant ingress access to/egress access from a specified peer over a given portRange. The Connections object automatically adds the necessary ingress or egress rules to the security group(s) associated with the calling construct. + +### Browser with Recording Configuration + +```typescript fixture=default +// Create an S3 bucket for recordings +const recordingBucket = new s3.Bucket(this, "RecordingBucket", { + bucketName: "my-browser-recordings", + removalPolicy: RemovalPolicy.DESTROY, // For demo purposes +}); + +// Create browser with recording enabled +const browser = new agentcore.BrowserCustom(this, "MyBrowser", { + browserCustomName: "my_browser", + description: "Browser with recording enabled", + networkConfiguration: agentcore.BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: recordingBucket.bucketName, + objectKey: "browser-recordings/", + }, + }, +}); +``` + +### Browser with Custom Execution Role + +```typescript fixture=default +// Create a custom execution role +const executionRole = new iam.Role(this, "BrowserExecutionRole", { + assumedBy: new iam.ServicePrincipal("bedrock-agentcore.amazonaws.com"), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonBedrockAgentCoreBrowserExecutionRolePolicy"), + ], +}); + +// Create browser with custom execution role +const browser = new agentcore.BrowserCustom(this, "MyBrowser", { + browserCustomName: "my_browser", + description: "Browser with custom execution role", + networkConfiguration: agentcore.BrowserNetworkConfiguration.usingPublicNetwork(), + executionRole: executionRole, +}); +``` + +### Browser with S3 Recording and Permissions + +```typescript fixture=default +// Create an S3 bucket for recordings +const recordingBucket = new s3.Bucket(this, "RecordingBucket", { + bucketName: "my-browser-recordings", + removalPolicy: RemovalPolicy.DESTROY, // For demo purposes +}); + +// Create browser with recording enabled +const browser = new agentcore.BrowserCustom(this, "MyBrowser", { + browserCustomName: "my_browser", + description: "Browser with recording enabled", + networkConfiguration: agentcore.BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: recordingBucket.bucketName, + objectKey: "browser-recordings/", + }, + }, +}); + +// The browser construct automatically grants S3 permissions to the execution role +// when recording is enabled, so no additional IAM configuration is needed +``` + +### Browser IAM Permissions + +The Browser construct provides convenient methods for granting IAM permissions: + +```typescript fixture=default +// Create a browser +const browser = new agentcore.BrowserCustom(this, "MyBrowser", { + browserCustomName: "my_browser", + description: "Browser for web automation", + networkConfiguration: agentcore.BrowserNetworkConfiguration.usingPublicNetwork(), +}); + +// Create a role that needs access to the browser +const userRole = new iam.Role(this, "UserRole", { + assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), +}); + +// Grant read permissions (Get and List actions) +browser.grantRead(userRole); + +// Grant use permissions (Start, Update, Stop actions) +browser.grantUse(userRole); + +// Grant specific custom permissions +browser.grant(userRole, "bedrock-agentcore:GetBrowserSession"); +``` + +## Code Interpreter + +The Amazon Bedrock AgentCore Code Interpreter enables AI agents to write and execute code securely in sandbox environments, enhancing their accuracy and expanding their ability to solve complex end-to-end tasks. This is critical in Agentic AI applications where the agents may execute arbitrary code that can lead to data compromise or security risks. The AgentCore Code Interpreter tool provides secure code execution, which helps you avoid running into these issues. + +For more information about code interpreter, please refer to the [official documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-interpreter-tool.html) + +### Code Interpreter Network Modes + +The Code Interpreter construct supports the following network modes: + +1. **Public Network Mode** (`CodeInterpreterNetworkMode.usingPublicNetwork()`) - Default + + - Allows internet access for package installation and external API calls + - Suitable for development and testing environments + - Enables downloading Python packages from PyPI + +2. **Sandbox Network Mode** (`CodeInterpreterNetworkMode.usingSandboxNetwork()`) + - Isolated network environment with no internet access + - Suitable for production environments with strict security requirements + - Only allows access to pre-installed packages and local resources + +3. **VPC (Virtual Private Cloud)** (`CodeInterpreterNetworkMode.usingVpc()`) + - Select whether to run the browser in a virtual private cloud (VPC). + - By configuring VPC connectivity, you enable secure access to private resources such as databases, internal APIs, and services within your VPC. + + While the VPC itself is mandatory, these are optional: + - Subnets - if not provided, CDK will select appropriate subnets from the VPC + - Security Groups - if not provided, CDK will create a default security group + - Specific subnet selection criteria - you can let CDK choose automatically + +For more information on VPC connectivity for Amazon Bedrock AgentCore Browser, please refer to the [official documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-vpc.html). + +### Code Interpreter Properties + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `codeInterpreterCustomName` | `string` | Yes | The name of the code interpreter. Must start with a letter and can be up to 48 characters long. Pattern: `[a-zA-Z][a-zA-Z0-9_]{0,47}` | +| `description` | `string` | No | Optional description for the code interpreter. Can have up to 200 characters | +| `executionRole` | `iam.IRole` | No | The IAM role that provides permissions for the code interpreter to access AWS services. A new role will be created if not provided | +| `networkConfiguration` | `CodeInterpreterNetworkConfiguration` | No | Network configuration for code interpreter. Defaults to PUBLIC network mode | +| `tags` | `{ [key: string]: string }` | No | Tags to apply to the code interpreter resource | + +### Basic Code Interpreter Creation + +```typescript fixture=default +// Create a basic code interpreter with public network access +const codeInterpreter = new agentcore.CodeInterpreterCustom(this, "MyCodeInterpreter", { + codeInterpreterCustomName: "my_code_interpreter", + description: "A code interpreter for Python execution", +}); +``` + +### Code Interpreter with VPC + +```typescript fixture=default +const codeInterpreter = new agentcore.CodeInterpreterCustom(this, "MyCodeInterpreter", { + codeInterpreterCustomName: "my_sandbox_interpreter", + description: "Code interpreter with isolated network access", + networkConfiguration: agentcore.BrowserNetworkConfiguration.usingVpc(this, { + vpc: new ec2.Vpc(this, 'VPC', { restrictDefaultSecurityGroup: false }), + }), +}); +``` + +Code Interpreter exposes a [connections](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.Connections.html) property. This property returns a connections object, which simplifies the process of defining and managing ingress and egress rules for security groups in your AWS CDK applications. Instead of directly manipulating security group rules, you interact with the Connections object of a construct, which then translates your connectivity requirements into the appropriate security group rules. For instance: + +```typescript fixture=default +const vpc = new ec2.Vpc(this, 'testVPC'); + +const codeInterpreter = new agentcore.CodeInterpreterCustom(this, "MyCodeInterpreter", { + codeInterpreterCustomName: "my_sandbox_interpreter", + description: "Code interpreter with isolated network access", + networkConfiguration: agentcore.BrowserNetworkConfiguration.usingVpc(this, { + vpc: vpc, + }), +}); + +codeInterpreter.connections.addSecurityGroup(new ec2.SecurityGroup(this, 'AdditionalGroup', { vpc })); +``` + +So security groups can be added after the browser construct creation. You can use methods like allowFrom() and allowTo() to grant ingress access to/egress access from a specified peer over a given portRange. The Connections object automatically adds the necessary ingress or egress rules to the security group(s) associated with the calling construct. + +### Code Interpreter with Sandbox Network Mode + +```typescript fixture=default +// Create code interpreter with sandbox network mode (isolated) +const codeInterpreter = new agentcore.CodeInterpreterCustom(this, "MyCodeInterpreter", { + codeInterpreterCustomName: "my_sandbox_interpreter", + description: "Code interpreter with isolated network access", + networkConfiguration: agentcore.CodeInterpreterNetworkConfiguration.usingSandboxNetwork(), +}); +``` + +### Code Interpreter with Custom Execution Role + +```typescript fixture=default +// Create a custom execution role +const executionRole = new iam.Role(this, "CodeInterpreterExecutionRole", { + assumedBy: new iam.ServicePrincipal("bedrock-agentcore.amazonaws.com"), +}); + +// Create code interpreter with custom execution role +const codeInterpreter = new agentcore.CodeInterpreterCustom(this, "MyCodeInterpreter", { + codeInterpreterCustomName: "my_code_interpreter", + description: "Code interpreter with custom execution role", + networkConfiguration: agentcore.CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + executionRole: executionRole, +}); +``` + +### Code Interpreter IAM Permissions + +The Code Interpreter construct provides convenient methods for granting IAM permissions: + +```typescript fixture=default +// Create a code interpreter +const codeInterpreter = new agentcore.CodeInterpreterCustom(this, "MyCodeInterpreter", { + codeInterpreterCustomName: "my_code_interpreter", + description: "Code interpreter for Python execution", + networkConfiguration: agentcore.CodeInterpreterNetworkConfiguration.usingPublicNetwork(), +}); + +// Create a role that needs access to the code interpreter +const userRole = new iam.Role(this, "UserRole", { + assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), +}); + +// Grant read permissions (Get and List actions) +codeInterpreter.grantRead(userRole); + +// Grant use permissions (Start, Invoke, Stop actions) +codeInterpreter.grantUse(userRole); + +// Grant specific custom permissions +codeInterpreter.grant(userRole, "bedrock-agentcore:GetCodeInterpreterSession"); +``` + +### Code interpreter with tags + +```typescript fixture=default +// Create code interpreter with sandbox network mode (isolated) +const codeInterpreter = new agentcore.CodeInterpreterCustom(this, "MyCodeInterpreter", { + codeInterpreterCustomName: "my_sandbox_interpreter", + description: "Code interpreter with isolated network access", + networkConfiguration: agentcore.CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: { + Environment: "Production", + Team: "AI/ML", + Project: "AgentCore", + }, +}); +``` diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/index.ts b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/index.ts new file mode 100644 index 0000000000000..0e4605ee45c29 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/index.ts @@ -0,0 +1,10 @@ +// =================================== +// Network Configuration +// =================================== +export * from './network/network-configuration'; + +// =================================== +// Tools +// =================================== +export * from './tools/code-interpreter'; +export * from './tools/browser'; diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/network/network-configuration.ts b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/network/network-configuration.ts new file mode 100644 index 0000000000000..7dccfec4b58e3 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/network/network-configuration.ts @@ -0,0 +1,223 @@ +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +// Internal Libs +import { CfnBrowserCustom, CfnCodeInterpreterCustom } from 'aws-cdk-lib/aws-bedrockagentcore'; +import { Construct } from 'constructs'; + +/** + * VPC configuration properties. + * Only used when network mode is VPC. + */ +export interface VpcConfigProps { + /** + * The VPC to deploy the resource to. + */ + readonly vpc: ec2.IVpc; + /** + * Where to place the network interfaces within the VPC. + * + * This requires `vpc` to be specified in order for interfaces to actually be + * placed in the subnets. If `vpc` is not specify, this will raise an error. + * + * @default - the Vpc default strategy if not specified + */ + readonly vpcSubnets?: ec2.SubnetSelection; + /** + * The list of security groups to associate with the resource's network interfaces. + * + * Only used if 'vpc' is supplied. + * + * @default - If the resource is placed within a VPC and a security group is + * not specified by this prop, a dedicated security + * group will be created for this resource. + */ + readonly securityGroups?: ec2.ISecurityGroup[]; + /** + * Whether to allow the resource to send all network traffic (except ipv6) + * + * If set to false, you must individually add traffic rules to allow the + * resource to connect to network targets. + * + * Do not specify this property if the `securityGroups` property is set. + * Instead, configure `allowAllOutbound` directly on the security group. + * + * @default true + */ + readonly allowAllOutbound?: boolean; +} + +/** + * VPC configuration. + * Only used when network mode is VPC. + * @internal + */ +interface NetworkConfig { + /** + * The connections to the network. + */ + readonly connections: ec2.Connections | undefined; + /** + * The VPC subnets to use. + */ + readonly vpcSubnets: ec2.SelectedSubnets | undefined; +} + +/** + * Abstract base class for network configuration. + */ +export abstract class NetworkConfiguration { + /** + * The network mode to use. + * Configure the security level for agent + * execution to control access, isolate resources, and protect sensitive data. + */ + readonly networkMode: string; + /** + * The connections object to the network. + */ + readonly connections: ec2.Connections | undefined; + /** + * The scope to create the resource in. + */ + readonly scope?: Construct | undefined; + /** + * The VPC subnets to use. + */ + readonly vpcSubnets?: ec2.SubnetSelection; + /** + * Creates a new network configuration. + * @param mode - the network mode to use for the tool. + */ + protected constructor (mode: string, scope?: Construct, vpcConfig?: VpcConfigProps) { + this.scope = scope; + this.networkMode = mode; + + // Validate vpc config and configure connections + const networkConfig = this._validateAndConfigureVpcConfig(vpcConfig); + this.connections = networkConfig?.connections; + this.vpcSubnets = networkConfig?.vpcSubnets; + } + + /** + * Validates the vpc config. + */ + private _validateAndConfigureVpcConfig = (vpcConfig?: VpcConfigProps): NetworkConfig | undefined => { + if ((vpcConfig?.securityGroups || vpcConfig?.allowAllOutbound !== undefined) && !vpcConfig?.vpc) { + throw new Error('Cannot configure \'securityGroups\' or \'allowAllOutbound\' without configuring a VPC'); + } + + if (!vpcConfig?.vpc) { + return undefined; + } + + if ((vpcConfig?.securityGroups && vpcConfig?.securityGroups.length > 0) && vpcConfig?.allowAllOutbound !== undefined) { + throw new Error('Configure \'allowAllOutbound\' directly on the supplied SecurityGroups'); + } + + if (!this.scope) { + throw new Error('Scope is required to create the security group'); + } + + let securityGroups: ec2.ISecurityGroup[]; + if (vpcConfig.securityGroups && vpcConfig.securityGroups.length > 0) { + securityGroups = vpcConfig.securityGroups; + } else { + const securityGroup = new ec2.SecurityGroup(this.scope!, 'SecurityGroup', { + vpc: vpcConfig.vpc, + allowAllOutbound: vpcConfig.allowAllOutbound ?? true, + }); + securityGroups = [securityGroup]; + } + + const vpcSubnets = vpcConfig.vpcSubnets ? vpcConfig.vpc.selectSubnets(vpcConfig.vpcSubnets) : vpcConfig.vpc.selectSubnets(); + + return { + connections: new ec2.Connections({ securityGroups: securityGroups }), + vpcSubnets: vpcSubnets, + }; + }; +} + +/** + * Network configuration for the Browser tool. + */ +export class BrowserNetworkConfiguration extends NetworkConfiguration { + /** + * Creates a public network configuration. PUBLIC is the default network mode. + * @returns A BrowserNetworkConfiguration. + * Run this tool to operate in a public environment with internet access, suitable for less sensitive or open-use scenarios. + */ + public static usingPublicNetwork(): BrowserNetworkConfiguration { + return new BrowserNetworkConfiguration('PUBLIC'); + } + + /** + * Creates a network configuration from a VPC configuration. + * @param vpcConfig - The VPC configuration. + * @returns A BrowserNetworkConfiguration. + */ + public static usingVpc(scope: Construct, vpcConfig: VpcConfigProps): BrowserNetworkConfiguration { + return new BrowserNetworkConfiguration('VPC', scope, vpcConfig); + } + + /** + * Renders the network configuration as a CloudFormation property. + * @param browserConnections - The connections object to the browser. + * @internal This is an internal core function and should not be called directly. + */ + public _render(browserConnections?: ec2.Connections): CfnBrowserCustom.BrowserNetworkConfigurationProperty { + return { + networkMode: this.networkMode, + vpcConfig: (this.networkMode === 'VPC' && browserConnections) ? { + subnets: this.vpcSubnets?.subnets?.map(subnet => subnet.subnetId) ?? [], + securityGroups: browserConnections?.securityGroups?.map(s => s.securityGroupId) ?? [], + } : undefined, + }; + } +} + +/** + * Network configuration for the Code Interpreter tool. + */ +export class CodeInterpreterNetworkConfiguration extends NetworkConfiguration { + /** + * Creates a public network configuration. + * @returns A CodeInterpreterNetworkConfiguration. + * Run this tool to operate in a public environment with internet access, suitable for less sensitive or open-use scenarios. + */ + public static usingPublicNetwork(): CodeInterpreterNetworkConfiguration { + return new CodeInterpreterNetworkConfiguration('PUBLIC'); + } + + /** + * Creates a sandbox network configuration. + * @returns A CodeInterpreterNetworkConfiguration. + * Run this tool in a restricted environment with limited Permissions and Encryption to enhance safety and reduce potential risks. + */ + public static usingSandboxNetwork(): CodeInterpreterNetworkConfiguration { + return new CodeInterpreterNetworkConfiguration('SANDBOX'); + } + + /** + * Creates a network configuration from a VPC configuration. + * @param vpcConfig - The VPC configuration. + * @returns A CodeInterpreterNetworkConfiguration. + */ + public static usingVpc(scope: Construct, vpcConfig: VpcConfigProps): CodeInterpreterNetworkConfiguration { + return new CodeInterpreterNetworkConfiguration('VPC', scope, vpcConfig); + } + + /** + * Renders the network configuration as a CloudFormation property. + * @param codeInterpreterConnections - The connections object to the code interpreter. + * @internal This is an internal core function and should not be called directly. + */ + public _render(codeInterpreterConnections?: ec2.Connections): CfnCodeInterpreterCustom.CodeInterpreterNetworkConfigurationProperty { + return { + networkMode: this.networkMode, + vpcConfig: (this.networkMode === 'VPC' && codeInterpreterConnections) ? { + subnets: this.vpcSubnets?.subnets?.map(subnet => subnet.subnetId) ?? [], + securityGroups: codeInterpreterConnections?.securityGroups?.map(s => s.securityGroupId) ?? [], + } : undefined, + }; + } +} diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/tools/browser.ts b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/tools/browser.ts new file mode 100644 index 0000000000000..29da2c774a269 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/tools/browser.ts @@ -0,0 +1,895 @@ +import { + Arn, + ArnFormat, + IResource, + Lazy, + Resource, + Token, + Stack, +} from 'aws-cdk-lib'; +import { + DimensionsMap, + Metric, + MetricOptions, + MetricProps, + Stats, +} from 'aws-cdk-lib/aws-cloudwatch'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as agent_core from 'aws-cdk-lib/aws-bedrockagentcore'; +import { Location } from 'aws-cdk-lib/aws-s3'; +import { Construct } from 'constructs'; +import { addConstructMetadata } from 'aws-cdk-lib/core/lib/metadata-resource'; +import { propertyInjectable } from 'aws-cdk-lib/core/lib/prop-injectable'; +// Internal Libs +import * as perms from './perms'; +import { validateFieldPattern, validateStringFieldLength, throwIfInvalid } from './validation-helpers'; +import { BrowserNetworkConfiguration } from '../network/network-configuration'; + +/****************************************************************************** + * CONSTANTS + *****************************************************************************/ +/** + * Minimum length for browser name + * @internal + */ +const BROWSER_NAME_MIN_LENGTH = 1; + +/** + * Maximum length for browser name + * @internal + */ +const BROWSER_NAME_MAX_LENGTH = 48; + +/** + * Minimum length for browser tags + * @internal + */ +const BROWSER_TAG_MIN_LENGTH = 1; + +/** + * Maximum length for browser tags + * @internal + */ +const BROWSER_TAG_MAX_LENGTH = 256; + +/****************************************************************************** + * Interface + *****************************************************************************/ +/** + * Interface for Browser resources + */ +export interface IBrowserCustom extends IResource, iam.IGrantable, ec2.IConnectable { + /** + * The ARN of the browser resource + * @attribute + */ + readonly browserArn: string; + + /** + * The id of the browser + * @attribute + */ + readonly browserId: string; + + /** + * The IAM role that provides permissions for the browser to access AWS services + */ + readonly executionRole: iam.IRole; + + /** + * Timestamp when the browser was last updated + * @attribute + */ + readonly lastUpdatedAt?: string; + + /** + * The status of the browser + * @attribute + */ + readonly status?: string; + + /** + * Timestamp when the browser was created + * @attribute + */ + readonly createdAt?: string; + + /** + * Grants IAM actions to the IAM Principal + */ + grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; + /** + * Grants `Get` and `List` actions on the Browser + */ + grantRead(grantee: iam.IGrantable): iam.Grant; + /** + * Grants `Invoke`, `Start`, and `Update` actions on the Browser + */ + grantUse(grantee: iam.IGrantable): iam.Grant; + + // ------------------------------------------------------ + // Metrics + // ------------------------------------------------------ + /** + * Return the given named metric for this browser. + */ + metric(metricName: string, dimensions: DimensionsMap, props?: MetricOptions): Metric; + + /** + * Return the given named metric related to the API operation performed on this browser. + */ + metricForApiOperation(metricName: string, operation: string, props?: MetricOptions): Metric; + + /** + * Return a metric measuring the latency of a specific API operation performed on this browser. + */ + metricLatencyForApiOperation(operation: string, props?: MetricOptions): Metric; + + /** + * Return a metric containing the total number of API requests made for a specific browser operation. + */ + metricInvocationsForApiOperation(operation: string, props?: MetricOptions): Metric; + + /** + * Return a metric containing the number of errors for a specific API operation performed on this browser. + */ + metricErrorsForApiOperation(operation: string, props?: MetricOptions): Metric; + + /** + * Return a metric containing the number of throttled requests for a specific API operation performed on this browser. + */ + metricThrottlesForApiOperation(operation: string, props?: MetricOptions): Metric; + + /** + * Return a metric containing the number of system errors for a specific API operation performed on this browser. + */ + metricSystemErrorsForApiOperation(operation: string, props?: MetricOptions): Metric; + + /** + * Return a metric containing the number of user errors for a specific API operation performed on this browser. + */ + metricUserErrorsForApiOperation(operation: string, props?: MetricOptions): Metric; + + /** + * Return a metric measuring the duration of browser sessions. + */ + metricSessionDuration(props?: MetricOptions): Metric; + + /** + * Return a metric containing the number of user takeovers. + */ + metricTakeOverCount(props?: MetricOptions): Metric; + + /** + * Return a metric containing the number of user takeovers released. + */ + metricTakeOverReleaseCount(props?: MetricOptions): Metric; + + /** + * Return a metric measuring the duration of user takeovers. + */ + metricTakeOverDuration(props?: MetricOptions): Metric; +} + +/****************************************************************************** + * ABSTRACT BASE CLASS + *****************************************************************************/ +/** + * Abstract base class for a Browser. + * Contains methods and attributes valid for Browsers either created with CDK or imported. + */ +export abstract class BrowserCustomBase extends Resource implements IBrowserCustom { + public abstract readonly browserArn: string; + public abstract readonly browserId: string; + public abstract readonly lastUpdatedAt?: string; + public abstract readonly status?: string; + public abstract readonly createdAt?: string; + public abstract readonly executionRole: iam.IRole; + /** + * The principal to grant permissions to + */ + public abstract readonly grantPrincipal: iam.IPrincipal; + /** + * An accessor for the Connections object that will fail if this Browser does not have a VPC + * configured. + */ + public get connections(): ec2.Connections { + if (!this._connections) { + throw new Error('Cannot manage network access without configuring a VPC'); + } + return this._connections; + } + /** + * The actual Connections object for this Browser. This may be unset in the event that a VPC has not + * been configured. + * @internal + */ + protected _connections: ec2.Connections | undefined; + + constructor(scope: Construct, id: string) { + super(scope, id); + } + + /** + * Grants IAM actions to the IAM Principal + * @param grantee - The IAM principal to grant permissions to + * @param actions - The actions to grant + * @returns An IAM Grant object representing the granted permissions + */ + public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee: grantee, + resourceArns: [this.browserArn], + actions: actions, + }); + } + + /** + * Grant read permissions on this browser to an IAM principal. + * This includes both read permissions on the specific browser and list permissions on all browsers. + * + * @param grantee - The IAM principal to grant read permissions to + * @default - Default grant configuration: + * - actions: ['bedrock-agentcore:GetBrowser', 'bedrock-agentcore:GetBrowserSession'] on this.browserArn + * - actions: ['bedrock-agentcore:ListBrowsers', 'bedrock-agentcore:ListBrowserSessions'] on all resources (*) + * @returns An IAM Grant object representing the granted permissions + */ + public grantRead(grantee: iam.IGrantable): iam.Grant { + const resourceSpecificGrant = this.grant( + grantee, + ...perms.BROWSER_READ_PERMS, + ); + + const allResourceGrant = iam.Grant.addToPrincipal({ + grantee: grantee, + resourceArns: ['*'], + actions: perms.BROWSER_LIST_PERMS, + }); + // Return combined grant + return resourceSpecificGrant.combine(allResourceGrant); + } + + /** + * Grant invoke permissions on this browser to an IAM principal. + * + * @param grantee - The IAM principal to grant invoke permissions to + * @default - Default grant configuration: + * - actions: ['bedrock-agentcore:StartBrowserSession', 'bedrock-agentcore:UpdateBrowserStream', 'bedrock-agentcore:StopBrowserSession'] + * - resourceArns: [this.browserArn] + * @returns An IAM Grant object representing the granted permissions + */ + public grantUse(grantee: iam.IGrantable): iam.Grant { + return this.grant( + grantee, + ...perms.BROWSER_USE_PERMS, + ); + } + + // ------------------------------------------------------ + // Metrics + // ------------------------------------------------------ + /** + * Return the given named metric for this browser. + * + * By default, the metric will be calculated as a sum over a period of 5 minutes. + * You can customize this by using the `statistic` and `period` properties. + */ + public metric(metricName: string, dimensions: DimensionsMap, props?: MetricOptions): Metric { + const metricProps: MetricProps = { + namespace: 'AWS/Bedrock-AgentCore', + metricName, + dimensionsMap: { ...dimensions, Resource: this.browserArn }, + ...props, + }; + return this.configureMetric(metricProps); + } + + /** + * Creates a CloudWatch metric for tracking browser api operations.. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: metricName + * - dimensionsMap: { BrowserId: this.browserId } + * @returns A CloudWatch Metric configured for browser api operations + */ + public metricForApiOperation( + metricName: string, + operation: string, + props?: MetricOptions, + ): Metric { + return this.metric(metricName, { Operation: operation }, props); + } + + /** + * Creates a CloudWatch metric for tracking browser latencies. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: Latency + * @returns A CloudWatch Metric configured for browser latencies + */ + public metricLatencyForApiOperation(operation: string, props?: MetricOptions): Metric { + return this.metricForApiOperation('Latency', operation, { statistic: Stats.AVERAGE, ...props }); + } + + /** + * Creates a CloudWatch metric for tracking browser invocations. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: Invocations + * @returns A CloudWatch Metric configured for browser latencies + */ + public metricInvocationsForApiOperation(operation: string, props?: MetricOptions): Metric { + return this.metricForApiOperation('Invocations', operation, { + statistic: Stats.SUM, + ...props, + }); + } + + /** + * Creates a CloudWatch metric for tracking browser errors. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: Errors + * @returns A CloudWatch Metric configured for browser errors + */ + public metricErrorsForApiOperation(operation: string, props?: MetricOptions): Metric { + return this.metricForApiOperation('Errors', operation, { statistic: Stats.SUM, ...props }); + } + + /** + * Creates a CloudWatch metric for tracking browser throttles. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: Throttles + * @returns A CloudWatch Metric configured for browser throttles + */ + public metricThrottlesForApiOperation(operation: string, props?: MetricOptions): Metric { + return this.metricForApiOperation('Throttles', operation, { statistic: Stats.SUM, ...props }); + } + + /** + * Creates a CloudWatch metric for tracking browser system errors. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: SystemErrors + * @returns A CloudWatch Metric configured for browser system errors + */ + public metricSystemErrorsForApiOperation(operation: string, props?: MetricOptions): Metric { + return this.metricForApiOperation('SystemErrors', operation, { statistic: Stats.SUM, ...props }); + } + + /** + * Creates a CloudWatch metric for tracking browser user errors. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: UserErrors + * @returns A CloudWatch Metric configured for browser user errors + */ + public metricUserErrorsForApiOperation(operation: string, props?: MetricOptions): Metric { + return this.metricForApiOperation('UserErrors', operation, { statistic: Stats.SUM, ...props }); + } + + /** + * Creates a CloudWatch metric for tracking browser session duration. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: Duration + * @returns A CloudWatch Metric configured for browser session duration + */ + public metricSessionDuration(props?: MetricOptions): Metric { + return this.metric('Duration', { Operation: 'BrowserSession' }, { statistic: Stats.AVERAGE, ...props }); + } + + /** + * Creates a CloudWatch metric for tracking browser user takeovers. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: TakeOverCount + * @returns A CloudWatch Metric configured for browser user takeovers + */ + public metricTakeOverCount(props?: MetricOptions): Metric { + return this.metric('TakeOverCount', {}, { statistic: Stats.SUM, ...props }); + } + + /** + * Creates a CloudWatch metric for tracking browser user takeovers released. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: TakeOverReleaseCount + * @returns A CloudWatch Metric configured for browser user takeovers released + */ + public metricTakeOverReleaseCount(props?: MetricOptions): Metric { + return this.metric('TakeOverReleaseCount', {}, { statistic: Stats.SUM, ...props }); + } + + /** + * Creates a CloudWatch metric for tracking browser user takeovers duration. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: TakeOverDuration + * @returns A CloudWatch Metric configured for browser user takeovers duration + */ + public metricTakeOverDuration(props?: MetricOptions): Metric { + return this.metric('TakeOverDuration', {}, { statistic: Stats.AVERAGE, ...props }); + } + + /** + * Internal method to create a metric. + * + * @param props - Configuration options for the metric + * @returns A CloudWatch Metric configured for browser api operations + */ + private configureMetric(props: MetricProps) { + return new Metric({ + ...props, + region: props?.region ?? this.stack.region, + account: props?.account ?? this.stack.account, + }); + } +} + +/****************************************************************************** + * Recording Configuration + *****************************************************************************/ + +/** + * Recording configuration for browser + */ +export interface RecordingConfig { + /** + * Whether recording is enabled + * @default - false + */ + readonly enabled?: boolean; + + /** + * S3 Location Configuration + * @default - undefined + */ + readonly s3Location?: Location; +} + +/****************************************************************************** + * PROPS FOR NEW CONSTRUCT + *****************************************************************************/ +/** + * Properties for creating a Browser resource + */ +export interface BrowserCustomProps { + /** + * The name of the browser + * Valid characters are a-z, A-Z, 0-9, _ (underscore) + * The name must start with a letter and can be up to 48 characters long + * Pattern: [a-zA-Z][a-zA-Z0-9_]{0,47} + * @required - Yes + */ + readonly browserCustomName: string; + + /** + * Optional description for the browser + * Valid characters are a-z, A-Z, 0-9, _ (underscore), - (hyphen) and spaces + * The description can have up to 200 characters + * @default - No description + * @required - No + */ + readonly description?: string; + + /** + * Network configuration for browser + * @required - No + * @default - PUBLIC network mode + */ + readonly networkConfiguration?: BrowserNetworkConfiguration; + + /** + * Recording configuration for browser + * @required - No + * @default - No recording configuration + */ + readonly recordingConfig?: RecordingConfig; + + /** + * The IAM role that provides permissions for the browser to access AWS services + * @default - A new role will be created + * @required - No + */ + readonly executionRole?: iam.IRole; + + /** + * Tags (optional) + * A list of key:value pairs of tags to apply to this Browser resource + * + * @default {} - no tags + * @required - No + */ + readonly tags?: { [key: string]: string }; +} + +/****************************************************************************** + * ATTRS FOR IMPORTED CONSTRUCT + *****************************************************************************/ +/** + * Attributes for specifying an imported Browser Custom. + */ +export interface BrowserCustomAttributes { + /** + * The ARN of the agent. + * @attribute + */ + readonly browserArn: string; + /** + * The ARN of the IAM role associated to the browser. + * @attribute + */ + readonly roleArn: string; + /** + * When this browser was last updated. + * @default undefined - No last updated timestamp is provided + */ + readonly lastUpdatedAt?: string; + /** + * The status of the browser. + * @default undefined - No status is provided + */ + readonly status?: string; + /** + * The created timestamp of the browser. + * @default undefined - No created timestamp is provided + */ + readonly createdAt?: string; + /** + * The security groups for this browser, if in a VPC. + * + * @default - By default, the browser is not in a VPC. + */ + readonly securityGroups?: ec2.ISecurityGroup[]; +} + +/****************************************************************************** + * Class + *****************************************************************************/ +/** + * Browser resource for AWS Bedrock Agent Core. + * Provides a browser environment for web automation and interaction. + * + * @see https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/browser.html + * @resource AWS::BedrockAgentCore::BrowserCustom + */ +@propertyInjectable +export class BrowserCustom extends BrowserCustomBase { + /** Uniquely identifies this class. */ + public static readonly PROPERTY_INJECTION_ID: string = '@aws-cdk.aws-bedrock-agentcore-alpha.BrowserCustom'; + + /** + * Static Method for importing an existing Bedrock AgentCore Browser Custom. + */ + /** + * Creates an Browser Custom reference from an existing browser's attributes. + * + * @param scope - The construct scope + * @param id - Identifier of the construct + * @param attrs - Attributes of the existing browser custom + * @returns An IBrowserCustom reference to the existing browser + */ + public static fromBrowserCustomAttributes(scope: Construct, id: string, attrs: BrowserCustomAttributes): IBrowserCustom { + class Import extends BrowserCustomBase { + public readonly browserArn = attrs.browserArn; + public readonly browserId = Arn.split(attrs.browserArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; + public readonly executionRole = iam.Role.fromRoleArn(scope, `${id}Role`, attrs.roleArn); + public readonly lastUpdatedAt = attrs.lastUpdatedAt; + public readonly grantPrincipal = this.executionRole; + public readonly status = attrs.status; + public readonly createdAt = attrs.createdAt; + + constructor(s: Construct, i: string) { + super(s, i); + + this.grantPrincipal = this.executionRole || new iam.UnknownPrincipal({ resource: this }); + if (attrs.securityGroups) { + this._connections = new ec2.Connections({ + securityGroups: attrs.securityGroups, + }); + } + } + } + + // Return new Browser Custom + return new Import(scope, id); + } + // ------------------------------------------------------ + // Attributes + // ------------------------------------------------------ + /** + * The ARN of the browser resource. + * @attribute + */ + public readonly browserArn: string; + /** + * The id of the browser + * @attribute + */ + public readonly browserId: string; + /** + * The name of the browser + */ + public readonly name: string; + /** + * The description of the browser + */ + public readonly description?: string; + /** + * The last updated timestamp of the browser + * @attribute + */ + public readonly lastUpdatedAt?: string; + /** + * The status of the browser + * @attribute + */ + public readonly status?: string; + /** + * The created timestamp of the browser + * @attribute + */ + public readonly createdAt?: string; + /** + * The failure reason of the browser + * @attribute + */ + public readonly failureReason?: string; + /** + * The IAM role associated to the browser. + */ + public readonly executionRole: iam.IRole; + /** + * Tags applied to this browser resource + * A map of key-value pairs for resource tagging + * @default - No tags applied + */ + public readonly tags?: { [key: string]: string }; + /** + * The principal to grant permissions to + */ + public readonly grantPrincipal: iam.IPrincipal; + /** + * The network configuration of the browser + */ + public readonly networkConfiguration: BrowserNetworkConfiguration; + /** + * The recording configuration of the browser + */ + public readonly recordingConfig?: RecordingConfig; + + // ------------------------------------------------------ + // Internal Only + // ------------------------------------------------------ + private readonly __resource: agent_core.CfnBrowserCustom; + + // ------------------------------------------------------ + // CONSTRUCTOR + // ------------------------------------------------------ + constructor(scope: Construct, id: string, props: BrowserCustomProps) { + super(scope, id); + // Enhanced CDK Analytics Telemetry + addConstructMetadata(this, props); + + // ------------------------------------------------------ + // Set properties and defaults + // ------------------------------------------------------ + this.name = props.browserCustomName; + this.description = props.description; + this.networkConfiguration = props.networkConfiguration ?? BrowserNetworkConfiguration.usingPublicNetwork(); + this.recordingConfig = props.recordingConfig ?? { enabled: false }; + this.executionRole = props.executionRole ?? this._createBrowserRole(); + this.grantPrincipal = this.executionRole; + this.tags = props.tags; + + // Validate browser name + throwIfInvalid(this._validateBrowserName, this.name); + + // Validate browser tags + throwIfInvalid(this._validateBrowserTags, this.tags); + + // Validate recording configuration + throwIfInvalid(this._validateRecordingConfig, this.recordingConfig); + + // Network configuration and validation is done in the network configuration class + // So we don't need to validate it here + + // Set connections - create a shared connections object + if (this.networkConfiguration.connections) { + // Use the network configuration's connections as the shared object + this._connections = this.networkConfiguration.connections; + } + + // ------------------------------------------------------ + // CFN Props - With Lazy support + // ------------------------------------------------------ + const cfnProps: agent_core.CfnBrowserCustomProps = { + name: this.name, + description: this.description, + networkConfiguration: Lazy.any({ produce: () => this.networkConfiguration._render(this._connections) }), + recordingConfig: this._renderRecordingConfig(), + executionRoleArn: this.executionRole?.roleArn, + tags: this.tags, + }; + + // L1 instantiation + this.__resource = new agent_core.CfnBrowserCustom(this, 'Resource', cfnProps); + + // Get attributes directly from the CloudFormation resource + this.browserId = this.__resource.attrBrowserId; + this.browserArn = this.__resource.attrBrowserArn; + this.status = this.__resource.attrStatus; + this.createdAt = this.__resource.attrCreatedAt; + this.lastUpdatedAt = this.__resource.attrLastUpdatedAt; + this.failureReason = this.__resource.attrFailureReason; + + // if recording is configured, add permissions to the execution role + if (this.recordingConfig && this.recordingConfig.s3Location) { + if (!Token.isUnresolved(this.recordingConfig.s3Location.bucketName)) { + Stack.of(this).resolve(this.recordingConfig.s3Location.bucketName); + } + const bucket = s3.Bucket.fromBucketName( + this, + `${this.name}RecordingBucket`, + this.recordingConfig.s3Location.bucketName, + ); + // Ensure the policy is applied before the browser resource is created + const grant = bucket.grantReadWrite(this.executionRole); + grant.applyBefore(this.__resource); + } + } + + /** + * Render the recording configuration. + * + * @returns RecordingConfigProperty object in CloudFormation format, or undefined if no recording configuration is defined + * @default - undefined if no recording configuration is provided + * @internal This is an internal core function and should not be called directly. + */ + private _renderRecordingConfig(): agent_core.CfnBrowserCustom.RecordingConfigProperty | undefined { + return this.recordingConfig ? { + enabled: this.recordingConfig.enabled, + s3Location: this.recordingConfig.s3Location ? { + bucket: this.recordingConfig.s3Location.bucketName, + prefix: this.recordingConfig.s3Location.objectKey, + } : undefined, + } : undefined; + } + + /** + * Creates execution role needed for the browser to access AWS services + * @returns The created role + * @internal This is an internal core function and should not be called directly. + */ + private _createBrowserRole(): iam.IRole { + const role = new iam.Role(this, 'ServiceRole', { + assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'), + }); + + return role; + } + + // ------------------------------------------------------ + // Validators + // ------------------------------------------------------ + /** + * Validates the browser name format + * @param name The browser name to validate + * @returns Array of validation error messages, empty if valid + */ + private _validateBrowserName = (name: string): string[] => { + let errors: string[] = []; + + errors.push(...validateStringFieldLength({ + value: name, + fieldName: 'Browser name', + minLength: BROWSER_NAME_MIN_LENGTH, + maxLength: BROWSER_NAME_MAX_LENGTH, + })); + + // Check if name matches the AWS API pattern: [a-zA-Z][a-zA-Z0-9_]{0,47} + // Must start with a letter, followed by up to 47 letters, numbers, or underscores + const validNamePattern = /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/; + errors.push(...validateFieldPattern(name, 'Browser name', validNamePattern)); + + return errors; + }; + + /** + * Validates the browser tags format + * @param tags The tags object to validate + * @returns Array of validation error messages, empty if valid + */ + private _validateBrowserTags = (tags?: { [key: string]: string }): string[] => { + let errors: string[] = []; + if (!tags) { + return errors; // Tags are optional + } + + // Validate each tag key and value + for (const [key, value] of Object.entries(tags)) { + errors.push(...validateStringFieldLength({ + value: key, + fieldName: 'Tag key', + minLength: BROWSER_TAG_MIN_LENGTH, + maxLength: BROWSER_TAG_MAX_LENGTH, + })); + + // Validate tag key pattern: ^[a-zA-Z0-9\s._:/=+@-]*$ + const validKeyPattern = /^[a-zA-Z0-9\s._:/=+@-]*$/; + errors.push(...validateFieldPattern(key, 'Tag key', validKeyPattern)); + + // Validate tag value + errors.push(...validateStringFieldLength({ + value: value, + fieldName: 'Tag value', + minLength: BROWSER_TAG_MIN_LENGTH, + maxLength: BROWSER_TAG_MAX_LENGTH, + })); + + // Validate tag value pattern: ^[a-zA-Z0-9\s._:/=+@-]*$ + const validValuePattern = /^[a-zA-Z0-9\s._:/=+@-]*$/; + errors.push(...validateFieldPattern(value, 'Tag value', validValuePattern)); + } + + return errors; + }; + + /** + * Validates the recording configuration + * @param recordingConfig The recording configuration to validate + * @returns Array of validation error messages, empty if valid + */ + private _validateRecordingConfig = (recordingConfig?: RecordingConfig): string[] => { + let errors: string[] = []; + if (!recordingConfig) { + return errors; // No validation needed if no recording config is provided + } + + const s3Location = recordingConfig.s3Location; + + // Only validate S3 location if it's actually provided + if (s3Location) { + // Both bucket name and object key are required when S3 location is provided + if (!s3Location.bucketName) { + errors.push('S3 bucket name is required when S3 location is provided for recording configuration'); + } else { + // Validate bucket name pattern: ^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$ + const bucketNamePattern = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/; + errors.push(...validateFieldPattern(s3Location.bucketName, 'S3 bucket name', bucketNamePattern)); + } + + if (!s3Location.objectKey) { + errors.push('S3 object key (prefix) is required when S3 location is provided for recording configuration'); + } + } + + return errors; + }; +} diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/tools/code-interpreter.ts b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/tools/code-interpreter.ts new file mode 100644 index 0000000000000..4f74f151dec65 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/tools/code-interpreter.ts @@ -0,0 +1,753 @@ +import { + Arn, + ArnFormat, + IResource, + Lazy, + Resource, +} from 'aws-cdk-lib'; +import { + DimensionsMap, + Metric, + MetricOptions, + MetricProps, + Stats, +} from 'aws-cdk-lib/aws-cloudwatch'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as agent_core from 'aws-cdk-lib/aws-bedrockagentcore'; +import { Construct } from 'constructs'; +import { addConstructMetadata } from 'aws-cdk-lib/core/lib/metadata-resource'; +import { propertyInjectable } from 'aws-cdk-lib/core/lib/prop-injectable'; +// Internal Libs +import * as perms from './perms'; +import { validateFieldPattern, validateStringFieldLength, throwIfInvalid } from './validation-helpers'; +import { CodeInterpreterNetworkConfiguration } from '../network/network-configuration'; + +/****************************************************************************** + * CONSTANTS + *****************************************************************************/ + +/** + * Minimum length for code interpreter name + * @internal + */ +const CODE_INTERPRETER_NAME_MIN_LENGTH = 1; + +/** + * Maximum length for code interpreter name + * @internal + */ +const CODE_INTERPRETER_NAME_MAX_LENGTH = 48; + +/** + * Minimum length for code interpreter tag + * @internal + */ +const CODE_INTERPRETER_TAG_MIN_LENGTH = 1; + +/** + * Maximum length for code interpreter tag + * @internal + */ +const CODE_INTERPRETER_TAG_MAX_LENGTH = 256; + +/****************************************************************************** + * Interface + *****************************************************************************/ +/** + * Interface for CodeInterpreterCustom resources + */ +export interface ICodeInterpreterCustom extends IResource, iam.IGrantable, ec2.IConnectable { + /** + * The ARN of the code interpreter resource + * @attribute + */ + readonly codeInterpreterArn: string; + + /** + * The id of the code interpreter + * @attribute + */ + readonly codeInterpreterId: string; + + /** + * The status of the code interpreter + * @attribute + */ + readonly status?: string; + + /** + * Timestamp when the code interpreter was created + * @attribute + */ + readonly createdAt?: string; + + /** + * Timestamp when the code interpreter was last updated + * @attribute + */ + readonly lastUpdatedAt?: string; + + /** + * The IAM role that provides permissions for the code interpreter to access AWS services. + */ + readonly executionRole: iam.IRole; + + /** + * Grants IAM actions to the IAM Principal + */ + grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; + + /** + * Grants `Get` and `List` actions on the Code Interpreter + */ + grantRead(grantee: iam.IGrantable): iam.Grant; + + /** + * Grants `Invoke`, `Start`, and `Stop` actions on the Code Interpreter + */ + grantUse(grantee: iam.IGrantable): iam.Grant; + + // ------------------------------------------------------ + // Metrics + // ------------------------------------------------------ + /** + * Return the given named metric for this code interpreter. + */ + metric(metricName: string, dimensions: DimensionsMap, props?: MetricOptions): Metric; + + /** + * Return the given named metric related to the API operation performed on this code interpreter. + */ + metricForApiOperation(metricName: string, operation: string, props?: MetricOptions): Metric; + + /** + * Return a metric measuring the latency of a specific API operation performed on this code interpreter. + */ + metricLatencyForApiOperation(operation: string, props?: MetricOptions): Metric; + + /** + * Return a metric containing the total number of API requests made for a specific code interpreter operation. + */ + metricInvocationsForApiOperation(operation: string, props?: MetricOptions): Metric; + + /** + * Return a metric containing the number of errors for a specific API operation performed on this code interpreter. + */ + metricErrorsForApiOperation(operation: string, props?: MetricOptions): Metric; + + /** + * Return a metric containing the number of throttled requests for a specific API operation performed on this code interpreter. + */ + metricThrottlesForApiOperation(operation: string, props?: MetricOptions): Metric; + + /** + * Return a metric containing the number of system errors for a specific API operation performed on this code interpreter. + */ + metricSystemErrorsForApiOperation(operation: string, props?: MetricOptions): Metric; + + /** + * Return a metric containing the number of user errors for a specific API operation performed on this code interpreter. + */ + metricUserErrorsForApiOperation(operation: string, props?: MetricOptions): Metric; + + /** + * Return a metric measuring the duration of code interpreter sessions. + */ + metricSessionDuration(props?: MetricOptions): Metric; +} + +/****************************************************************************** + * ABSTRACT BASE CLASS + *****************************************************************************/ +/** + * Abstract base class for a Code Interpreter. + * Contains methods and attributes valid for Code Interpreters either created with CDK or imported. + */ +export abstract class CodeInterpreterCustomBase extends Resource implements ICodeInterpreterCustom { + public abstract readonly codeInterpreterArn: string; + public abstract readonly codeInterpreterId: string; + public abstract readonly status?: string; + public abstract readonly createdAt?: string; + public abstract readonly lastUpdatedAt?: string; + public abstract readonly executionRole: iam.IRole; + /** + * The principal to grant permissions to + */ + public abstract readonly grantPrincipal: iam.IPrincipal; + + /** + * An accessor for the Connections object that will fail if this Browser does not have a VPC + * configured. + */ + public get connections(): ec2.Connections { + if (!this._connections) { + throw new Error('Cannot manage network access without configuring a VPC'); + } + return this._connections; + } + /** + * The actual Connections object for this Browser. This may be unset in the event that a VPC has not + * been configured. + * @internal + */ + protected _connections: ec2.Connections | undefined; + + constructor(scope: Construct, id: string) { + super(scope, id); + } + + /** + * Grants IAM actions to the IAM Principal + * @param grantee - The IAM principal to grant permissions to + * @param actions - The actions to grant + * @returns An IAM Grant object representing the granted permissions + */ + public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee: grantee, + resourceArns: [this.codeInterpreterArn], + actions: actions, + }); + } + + /** + * Grant read permissions on this code interpreter to an IAM principal. + * This includes both read permissions on the specific code interpreter and list permissions on all code interpreters. + * + * @param grantee - The IAM principal to grant read permissions to + * @default - Default grant configuration: + * - actions: ['bedrock-agentcore:GetCodeInterpreter', 'bedrock-agentcore:GetCodeInterpreterSession'] on this.codeInterpreterArn + * - actions: ['bedrock-agentcore:ListCodeInterpreters', 'bedrock-agentcore:ListCodeInterpreterSessions'] on all resources (*) + * @returns An IAM Grant object representing the granted permissions + */ + public grantRead(grantee: iam.IGrantable): iam.Grant { + const resourceSpecificGrant = this.grant( + grantee, + ...perms.CODE_INTERPRETER_READ_PERMS, + ); + + const allResourceGrant = iam.Grant.addToPrincipal({ + grantee: grantee, + resourceArns: ['*'], + actions: perms.CODE_INTERPRETER_LIST_PERMS, + }); + // Return combined grant + return resourceSpecificGrant.combine(allResourceGrant); + } + + /** + * Grant invoke permissions on this code interpreter to an IAM principal. + * + * @param grantee - The IAM principal to grant invoke permissions to + * @default - Default grant configuration: + * - actions: ['bedrock-agentcore:StartCodeInterpreterSession', 'bedrock-agentcore:InvokeCodeInterpreter', 'bedrock-agentcore:StopCodeInterpreterSession'] + * - resourceArns: [this.codeInterpreterArn] + * @returns An IAM Grant object representing the granted permissions + */ + public grantUse(grantee: iam.IGrantable): iam.Grant { + return this.grant( + grantee, + ...perms.CODE_INTERPRETER_USE_PERMS, + ); + } + + /** + * Grant invoke permissions on this code interpreter to an IAM principal. + * + * @param grantee - The IAM principal to grant invoke permissions to + * @returns An IAM Grant object representing the granted permissions + * @default - Default grant configuration: + * - actions: ['bedrock-agentcore:InvokeCodeInterpreter'] + * - resourceArns: [this.codeInterpreterArn] + */ + public grantInvoke(grantee: iam.IGrantable): iam.Grant { + return this.grant(grantee, ...perms.CODE_INTERPRETER_INVOKE_PERMS); + } + + // ------------------------------------------------------ + // Metrics + // ------------------------------------------------------ + /** + * Return the given named metric for this code interpreter. + * + * By default, the metric will be calculated as a sum over a period of 5 minutes. + * You can customize this by using the `statistic` and `period` properties. + */ + public metric(metricName: string, dimensions: DimensionsMap, props?: MetricOptions): Metric { + const metricProps: MetricProps = { + namespace: 'AWS/Bedrock-AgentCore', + metricName, + dimensionsMap: { ...dimensions, Resource: this.codeInterpreterArn }, + ...props, + }; + return this.configureMetric(metricProps); + } + + /** + * Creates a CloudWatch metric for tracking code interpreter api operations.. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: metricName + * - dimensionsMap: { CodeInterpreterId: this.codeInterpreterId } + * @returns A CloudWatch Metric configured for code interpreter api operations + */ + public metricForApiOperation( + metricName: string, + operation: string, + props?: MetricOptions, + ): Metric { + return this.metric(metricName, { Operation: operation }, props); + } + + /** + * Creates a CloudWatch metric for tracking code interpreter latencies. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: Latency + * @returns A CloudWatch Metric configured for code interpreter latencies + */ + public metricLatencyForApiOperation(operation: string, props?: MetricOptions): Metric { + return this.metricForApiOperation('Latency', operation, { statistic: Stats.AVERAGE, ...props }); + } + + /** + * Creates a CloudWatch metric for tracking code interpreter invocations. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: Invocations + * @returns A CloudWatch Metric configured for code interpreter invocations + */ + public metricInvocationsForApiOperation(operation: string, props?: MetricOptions): Metric { + return this.metricForApiOperation('Invocations', operation, { + statistic: Stats.SUM, + ...props, + }); + } + + /** + * Creates a CloudWatch metric for tracking code interpreter errors. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: Errors + * @returns A CloudWatch Metric configured for code interpreter errors + */ + public metricErrorsForApiOperation(operation: string, props?: MetricOptions): Metric { + return this.metricForApiOperation('Errors', operation, { statistic: Stats.SUM, ...props }); + } + + /** + * Creates a CloudWatch metric for tracking code interpreter throttles. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: Throttles + * @returns A CloudWatch Metric configured for code interpreter throttles + */ + public metricThrottlesForApiOperation(operation: string, props?: MetricOptions): Metric { + return this.metricForApiOperation('Throttles', operation, { statistic: Stats.SUM, ...props }); + } + + /** + * Creates a CloudWatch metric for tracking code interpreter system errors. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: SystemErrors + * @returns A CloudWatch Metric configured for code interpreter system errors + */ + public metricSystemErrorsForApiOperation(operation: string, props?: MetricOptions): Metric { + return this.metricForApiOperation('SystemErrors', operation, { statistic: Stats.SUM, ...props }); + } + + /** + * Creates a CloudWatch metric for tracking code interpreter user errors. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: UserErrors + * @returns A CloudWatch Metric configured for code interpreter user errors + */ + public metricUserErrorsForApiOperation(operation: string, props?: MetricOptions): Metric { + return this.metricForApiOperation('UserErrors', operation, { statistic: Stats.SUM, ...props }); + } + + /** + * Creates a CloudWatch metric for tracking code interpreter session duration. + * + * @param props - Configuration options for the metric + * @default - Default metric configuration: + * - namespace: 'AWS/Bedrock-AgentCore' + * - metricName: Duration + * @returns A CloudWatch Metric configured for code interpreter session duration + */ + public metricSessionDuration(props?: MetricOptions): Metric { + return this.metric('Duration', { Operation: 'CodeInterpreterSession' }, { statistic: Stats.AVERAGE, ...props }); + } + + /** + * Internal method to create a metric. + * + * @param props - Configuration options for the metric + * @returns A CloudWatch Metric configured for code interpreter api operations + */ + private configureMetric(props: MetricProps) { + return new Metric({ + ...props, + region: props?.region ?? this.stack.region, + account: props?.account ?? this.stack.account, + }); + } +} + +/****************************************************************************** + * PROPS FOR NEW CONSTRUCT + *****************************************************************************/ +/** + * Properties for creating a CodeInterpreter resource + */ +export interface CodeInterpreterCustomProps { + /** + * The name of the code interpreter + * Valid characters are a-z, A-Z, 0-9, _ (underscore) + * The name must start with a letter and can be up to 48 characters long + * Pattern: [a-zA-Z][a-zA-Z0-9_]{0,47} + * @required - Yes + */ + readonly codeInterpreterCustomName: string; + + /** + * Optional description for the code interpreter + * Valid characters are a-z, A-Z, 0-9, _ (underscore), - (hyphen) and spaces + * The description can have up to 200 characters + * @default - No description + * @required - No + */ + readonly description?: string; + + /** + * The IAM role that provides permissions for the code interpreter to access AWS services. + * + * @default - A new role will be created. + * @required - No + */ + readonly executionRole?: iam.IRole; + + /** + * Network configuration for code interpreter + * @required - No + * @default - PUBLIC network mode + */ + readonly networkConfiguration?: CodeInterpreterNetworkConfiguration; + + /** + * Tags (optional) + * A list of key:value pairs of tags to apply to this Code Interpreter resource + * + * @default {} - no tags + * @required - No + */ + readonly tags?: { [key: string]: string }; +} + +/****************************************************************************** + * ATTRS FOR IMPORTED CONSTRUCT + *****************************************************************************/ +/** + * Attributes for specifying an imported Code Interpreter Custom. + */ +export interface CodeInterpreterCustomAttributes { + /** + * The ARN of the agent. + * @attribute + */ + readonly codeInterpreterArn: string; + /** + * The ARN of the IAM role associated to the code interpreter. + * @attribute + */ + readonly roleArn: string; + /** + * When this code interpreter was last updated. + * @default undefined - No last updated timestamp is provided + */ + readonly lastUpdatedAt?: string; + /** + * The status of the code interpreter. + * @default undefined - No status is provided + */ + readonly status?: string; + /** + * The created timestamp of the code interpreter. + * @default undefined - No created timestamp is provided + */ + readonly createdAt?: string; + /** + * The security groups for this code interpreter, if in a VPC. + * + * @default - By default, the code interpreter is not in a VPC. + */ + readonly securityGroups?: ec2.ISecurityGroup[]; +} + +/****************************************************************************** + * Class + *****************************************************************************/ +/** + * Custom code interpreter resource for AWS Bedrock Agent Core. + * Provides a sandboxed environment for code execution with configurable network access. + * + * @see https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-interpreter.html + * @resource AWS::BedrockAgentCore::CodeInterpreterCustom + */ +@propertyInjectable +export class CodeInterpreterCustom extends CodeInterpreterCustomBase { + /** Uniquely identifies this class. */ + public static readonly PROPERTY_INJECTION_ID: string = '@aws-cdk.aws-bedrock-agentcore-alpha.CodeInterpreterCustom'; + + /** + * Static Method for importing an existing Bedrock AgentCore Code Interpreter Custom. + */ + /** + * Creates an Code Interpreter Custom reference from an existing code interpreter's attributes. + * + * @param scope - The construct scope + * @param id - Identifier of the construct + * @param attrs - Attributes of the existing code interpreter custom + * @returns An ICodeInterpreterCustom reference to the existing code interpreter + */ + public static fromCodeInterpreterCustomAttributes(scope: Construct, id: string, attrs: CodeInterpreterCustomAttributes): ICodeInterpreterCustom { + class Import extends CodeInterpreterCustomBase { + public readonly codeInterpreterArn = attrs.codeInterpreterArn; + public readonly codeInterpreterId = Arn.split(attrs.codeInterpreterArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; + public readonly executionRole = iam.Role.fromRoleArn(scope, `${id}Role`, attrs.roleArn); + public readonly lastUpdatedAt = attrs.lastUpdatedAt; + public readonly grantPrincipal = this.executionRole; + public readonly status = attrs.status; + public readonly createdAt = attrs.createdAt; + + constructor(s: Construct, i: string) { + super(s, i); + + this.grantPrincipal = this.executionRole || new iam.UnknownPrincipal({ resource: this }); + if (attrs.securityGroups) { + this._connections = new ec2.Connections({ + securityGroups: attrs.securityGroups, + }); + } + } + } + + // Return new Code Interpreter Custom + return new Import(scope, id); + } + // ------------------------------------------------------ + // Attributes + // ------------------------------------------------------ + /** + * The ARN of the code interpreter resource + * @attribute + */ + public readonly codeInterpreterArn: string; + /** + * The id of the code interpreter + * @attribute + */ + public readonly codeInterpreterId: string; + /** + * The name of the code interpreter + */ + public readonly name: string; + /** + * The description of the code interpreter + */ + public readonly description?: string; + /** + * The network configuration of the code interpreter + */ + public readonly networkConfiguration: CodeInterpreterNetworkConfiguration; + /** + * The status of the code interpreter + * @attribute + */ + public readonly status?: string; + /** + * The created timestamp of the code interpreter + * @attribute + */ + public readonly createdAt?: string; + /** + * The last updated timestamp of the code interpreter + * @attribute + */ + public readonly lastUpdatedAt?: string; + /** + * The failure reason of the code interpreter + * @attribute + */ + public readonly failureReason?: string; + /** + * The IAM role that provides permissions for the code interpreter to access AWS services. + */ + public readonly executionRole: iam.IRole; + /** + * The principal to grant permissions to + */ + public readonly grantPrincipal: iam.IPrincipal; + /** + * Tags applied to this code interpreter resource + * A map of key-value pairs for resource tagging + * @default - No tags applied + */ + public readonly tags?: { [key: string]: string }; + + // ------------------------------------------------------ + // Internal Only + // ------------------------------------------------------ + private readonly __resource: agent_core.CfnCodeInterpreterCustom; + + constructor(scope: Construct, id: string, props: CodeInterpreterCustomProps) { + super(scope, id); + // Enhanced CDK Analytics Telemetry + addConstructMetadata(this, props); + + // ------------------------------------------------------ + // Set properties and defaults + // ------------------------------------------------------ + this.name = props.codeInterpreterCustomName; + this.description = props.description; + this.networkConfiguration = props.networkConfiguration ?? CodeInterpreterNetworkConfiguration.usingPublicNetwork(); + this.executionRole = props.executionRole ?? this._createCodeInterpreterRole(); + this.grantPrincipal = this.executionRole; + this.tags = props.tags; + + // Validate code interpreter name + throwIfInvalid(this._validateCodeInterpreterName, this.name); + + // Validate code interpreter tags + throwIfInvalid(this._validateCodeInterpreterTags, this.tags); + + // Network configuration and validation is done in the network configuration class + // So we don't need to validate it here + + // Set connections - create a shared connections object + if (this.networkConfiguration.connections) { + // Use the network configuration's connections as the shared object + this._connections = this.networkConfiguration.connections; + } + + // ------------------------------------------------------ + // CFN Props - With Lazy support + // ------------------------------------------------------ + const cfnProps: agent_core.CfnCodeInterpreterCustomProps = { + name: this.name, + description: this.description, + networkConfiguration: Lazy.any({ produce: () => this.networkConfiguration._render(this._connections) }), + executionRoleArn: this.executionRole?.roleArn, + tags: this.tags, + }; + + // L1 instantiation + this.__resource = new agent_core.CfnCodeInterpreterCustom(this, 'Resource', cfnProps); + + // Get attributes directly from the CloudFormation resource + this.codeInterpreterId = this.__resource.attrCodeInterpreterId; + this.codeInterpreterArn = this.__resource.attrCodeInterpreterArn; + this.status = this.__resource.attrStatus; + this.createdAt = this.__resource.attrCreatedAt; + this.lastUpdatedAt = this.__resource.attrLastUpdatedAt; + this.failureReason = this.__resource.attrFailureReason; + } + + // ------------------------------------------------------ + // Validators + // ------------------------------------------------------ + /** + * Validates the code interpreter name format + * @param name The code interpreter name to validate + * @returns Array of validation error messages, empty if valid + * @internal This is an internal core function and should not be called directly. + */ + private _validateCodeInterpreterName = (name: string): string[] => { + let errors: string[] = []; + + errors.push(...validateStringFieldLength({ + value: name, + fieldName: 'Code interpreter name', + minLength: CODE_INTERPRETER_NAME_MIN_LENGTH, + maxLength: CODE_INTERPRETER_NAME_MAX_LENGTH, + })); + + // Check if name matches the AWS API pattern: [a-zA-Z][a-zA-Z0-9_]{0,47} + // Must start with a letter, followed by up to 47 letters, numbers, or underscores + const validNamePattern = /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/; + errors.push(...validateFieldPattern(name, 'Code interpreter name', validNamePattern)); + + return errors; + }; + + /** + * Validates the code interpreter tags format + * @param tags The tags object to validate + * @returns Array of validation error messages, empty if valid + * @internal This is an internal core function and should not be called directly. + */ + private _validateCodeInterpreterTags = (tags?: { [key: string]: string }): string[] => { + let errors: string[] = []; + if (!tags) { + return errors; // Tags are optional + } + + // Validate each tag key and value + for (const [key, value] of Object.entries(tags)) { + errors.push(...validateStringFieldLength({ + value: key, + fieldName: 'Tag key', + minLength: CODE_INTERPRETER_TAG_MIN_LENGTH, + maxLength: CODE_INTERPRETER_TAG_MAX_LENGTH, + })); + + // Validate tag key pattern: ^[a-zA-Z0-9\s._:/=+@-]*$ + const validKeyPattern = /^[a-zA-Z0-9\s._:/=+@-]*$/; + errors.push(...validateFieldPattern(key, 'Tag key', validKeyPattern)); + + // Validate tag value + errors.push(...validateStringFieldLength({ + value: value, + fieldName: 'Tag value', + minLength: CODE_INTERPRETER_TAG_MIN_LENGTH, + maxLength: CODE_INTERPRETER_TAG_MAX_LENGTH, + })); + + // Validate tag value pattern: ^[a-zA-Z0-9\s._:/=+@-]*$ + const validValuePattern = /^[a-zA-Z0-9\s._:/=+@-]*$/; + errors.push(...validateFieldPattern(value, 'Tag value', validValuePattern)); + } + + return errors; + }; + + /** + * Creates execution role needed for the code interpreter to access AWS services + * @returns The created role + * @internal This is an internal core function and should not be called directly. + */ + private _createCodeInterpreterRole(): iam.IRole { + const role = new iam.Role(this, 'ServiceRole', { + assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'), + }); + + return role; + } +} diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/tools/perms.ts b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/tools/perms.ts new file mode 100644 index 0000000000000..8da8ffc9e85aa --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/tools/perms.ts @@ -0,0 +1,129 @@ +/****************************************************************************** + * BROWSER + *****************************************************************************/ +/** + * Permissions for the Browser tool + * See https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonbedrockagentcore.html + */ +/****************************************************************************** + * Data Plane Permissions + *****************************************************************************/ +/** + * Permissions to manage a specific browser session + */ +export const BROWSER_SESSION_PERMS = [ + 'bedrock-agentcore:GetBrowserSession', + 'bedrock-agentcore:ListBrowserSessions', + 'bedrock-agentcore:StartBrowserSession', + 'bedrock-agentcore:StopBrowserSession', +]; + +/** + * Permissions to connect to a browser live view or automation stream + */ +export const BROWSER_STREAM_PERMS = [ + 'bedrock-agentcore:UpdateBrowserStream', + 'bedrock-agentcore:ConnectBrowserAutomationStream', + 'bedrock-agentcore:ConnectBrowserLiveViewStream', +]; + +/****************************************************************************** + * Control Plane Permissions + *****************************************************************************/ +/** + * Grants control plane operations to manage the browser (CRUD) + */ +export const BROWSER_ADMIN_PERMS = [ + 'bedrock-agentcore:CreateBrowser', + 'bedrock-agentcore:DeleteBrowser', + 'bedrock-agentcore:GetBrowser', + 'bedrock-agentcore:ListBrowsers', +]; + +/** + * Permissions for reading browser information + */ +export const BROWSER_READ_PERMS = [ + 'bedrock-agentcore:GetBrowser', + 'bedrock-agentcore:GetBrowserSession', +]; + +/** + * Permissions for listing browser resources + */ +export const BROWSER_LIST_PERMS = [ + 'bedrock-agentcore:ListBrowsers', + 'bedrock-agentcore:ListBrowserSessions', +]; + +/** + * Permissions for using browser functionality + */ +export const BROWSER_USE_PERMS = [ + 'bedrock-agentcore:StartBrowserSession', + 'bedrock-agentcore:UpdateBrowserStream', + 'bedrock-agentcore:StopBrowserSession', +]; + +/****************************************************************************** + * CODE INTERPRETER + *****************************************************************************/ +/** + * Permissions for the Code Interpreter tool + * See https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonbedrockagentcore.html + */ +/****************************************************************************** + * Data Plane Permissions + *****************************************************************************/ +/** + * Permissions to manage a specific code interpreter session + */ +export const CODE_INTERPRETER_SESSION_PERMS = [ + 'bedrock-agentcore:GetCodeInterpreterSession', + 'bedrock-agentcore:ListCodeInterpreterSessions', + 'bedrock-agentcore:StartCodeInterpreterSession', + 'bedrock-agentcore:StopCodeInterpreterSession', +]; + +/** + * Permissions to invoke a code interpreter + */ +export const CODE_INTERPRETER_INVOKE_PERMS = ['bedrock-agentcore:InvokeCodeInterpreter']; + +/****************************************************************************** + * Control Plane Permissions + *****************************************************************************/ +/** + * Grants control plane operations to manage the code interpreter (CRUD) + */ +export const CODE_INTERPRETER_ADMIN_PERMS = [ + 'bedrock-agentcore:CreateCodeInterpreter', + 'bedrock-agentcore:DeleteCodeInterpreter', + 'bedrock-agentcore:GetCodeInterpreter', + 'bedrock-agentcore:ListCodeInterpreters', +]; + +/** + * Permissions for reading code interpreter information + */ +export const CODE_INTERPRETER_READ_PERMS = [ + 'bedrock-agentcore:GetCodeInterpreter', + 'bedrock-agentcore:GetCodeInterpreterSession', +]; + +/** + * Permissions for listing code interpreter resources + */ +export const CODE_INTERPRETER_LIST_PERMS = [ + 'bedrock-agentcore:ListCodeInterpreters', + 'bedrock-agentcore:ListCodeInterpreterSessions', +]; + +/** + * Permissions for using code interpreter functionality + */ +export const CODE_INTERPRETER_USE_PERMS = [ + 'bedrock-agentcore:StartCodeInterpreterSession', + 'bedrock-agentcore:InvokeCodeInterpreter', + 'bedrock-agentcore:StopCodeInterpreterSession', +]; diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/tools/validation-helpers.ts b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/tools/validation-helpers.ts new file mode 100644 index 0000000000000..31765d0211386 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/agentcore/tools/validation-helpers.ts @@ -0,0 +1,110 @@ +import { Token } from 'aws-cdk-lib'; + +/** + * Error thrown when validation fails + */ +export class ValidationError extends Error { + constructor(message: string) { + super(message); + this.name = 'ValidationError'; + } +} +interface IntervalValidation { + fieldName: string; + minLength: number; + maxLength: number; +} + +interface StringLengthValidation extends IntervalValidation { + value: string; +} + +/** + * Validates the length of a string field against minimum and maximum constraints. + * @param value - The string value to validate + * @param fieldName - Name of the field being validated (for error messages) + * @param minLength - Minimum allowed length (defaults to 0) + * @param maxLength - Maximum allowed length + * @returns true if validation passes + * @throws Error if validation fails with current length information + */ +export function validateStringFieldLength(params: StringLengthValidation): string[] { + const errors: string[] = []; + + // Handle null/undefined values + if (params.value == null) { + return errors; // Skip validation for null/undefined values + } + + const currentLength = params.value.length; + + // Evaluate only if it is not an unresolved Token + if (!Token.isUnresolved(params.fieldName)) { + if (params.value.length > params.maxLength) { + errors.push( + `The field ${params.fieldName} is ${currentLength} characters long but must be less than or equal to ${params.maxLength} characters`, + ); + } + + if (params.value.length < params.minLength) { + errors.push( + `The field ${params.fieldName} is ${currentLength} characters long but must be at least ${params.minLength} characters`, + ); + } + } + + return errors; +} + +/** + * Validates a string field against a regex pattern. + * @param value - The string value to validate + * @param fieldName - Name of the field being validated (for error messages) + * @param pattern - Regular expression pattern to test against + * @param customMessage - Optional custom error message + * @returns true if validation passes + * @throws Error if validation fails with detailed message + */ +export function validateFieldPattern( + value: string, + fieldName: string, + pattern: RegExp, + customMessage?: string, +): string[] { + const errors: string[] = []; + + // Handle null/undefined values + if (value == null) { + return errors; // Skip validation for null/undefined values + } + + // Evaluate only if it is not an unresolved Token + if (!Token.isUnresolved(value)) { + // Verify type + if (typeof value !== 'string') { + errors.push(`Expected string for ${fieldName}, got ${typeof value}`); + } + // Validate specified regex + if (!(pattern instanceof RegExp)) { + errors.push('Pattern must be a valid regular expression'); + } + + // Pattern validation + if (!pattern.test(value)) { + const defaultMessage = `The field ${fieldName} with value "${value}" does not match the required pattern ${pattern}`; + errors.push(customMessage || defaultMessage); + } + } + + return errors; +} + +export type ValidationFn = (param: T) => string[]; + +export function throwIfInvalid(validationFn: ValidationFn, param: T): T { + const errors = validationFn(param); + if (errors.length > 0) { + throw new ValidationError(errors.join('\n')); + } + return param; +} diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/jest.config.js b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/jest.config.js new file mode 100644 index 0000000000000..3a2fd93a1228a --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/lib/index.ts b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/lib/index.ts new file mode 100644 index 0000000000000..cbaeba85ccf94 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/lib/index.ts @@ -0,0 +1,6 @@ +// The index.ts files contains a list of files we want to +// include as part of the public API of this module. +// In general, all files including L2 classes will be listed here, +// while all files including only utility functions will be omitted from here. + +export * from '../agentcore'; diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/package.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/package.json new file mode 100644 index 0000000000000..1dec99c4d9f87 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/package.json @@ -0,0 +1,117 @@ +{ + "name": "@aws-cdk/aws-bedrock-agentcore-alpha", + "version": "0.0.0", + "private": false, + "description": "The CDK Construct Library for Amazon Bedrock", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.services.bedrock.agentcore.alpha", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "bedrock-agentcore-alpha" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.AWS.Bedrock.Agentcore.Alpha", + "packageId": "Amazon.CDK.AWS.Bedrock.Agentcore.Alpha", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/main/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-cdk.aws-bedrock-agentcore-alpha", + "module": "aws_cdk.aws_bedrock_agentcore_alpha", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 2" + ] + }, + "go": { + "moduleName": "github.com/aws/aws-cdk-go", + "packageName": "awsbedrockagentcorealpha" + } + }, + "metadata": { + "jsii": { + "rosetta": { + "strict": true + } + } + }, + "tsconfig": "tsconfig.json", + "validateTsconfig": "minimal" + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-bedrock-agentcore-alpha" + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "integ-runner --unstable=toolkit-lib-engine --language javascript", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "build+test": "yarn build && yarn test", + "build+test+package": "yarn build+test && yarn package", + "compat": "cdk-compat", + "rosetta:extract": "yarn --silent jsii-rosetta extract", + "build+extract": "yarn build && yarn rosetta:extract", + "build+test+extract": "yarn build+test && yarn rosetta:extract" + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "bedrock", + "agentcore" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "aws-cdk-lib": "0.0.0", + "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/integ-runner": "^2.190.2", + "@aws-cdk/pkglint": "0.0.0", + "@aws-cdk/integ-tests-alpha": "0.0.0", + "@types/jest": "^29.5.14", + "constructs": "^10.0.0", + "jest": "^29.7.0" + }, + "homepage": "https://github.com/aws/aws-cdk", + "peerDependencies": { + "aws-cdk-lib": "^0.0.0", + "constructs": "^10.0.0" + }, + "engines": { + "node": ">= 18.0.0" + }, + "stability": "experimental", + "maturity": "experimental", + "cdk-build": { + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } + }, + "publishConfig": { + "tag": "latest" + }, + "awscdkio": { + "announce": false + }, + "pkglint": { + "exclude": [ + "naming/package-matches-directory", + "assert/assert-dependency" + ] + } +} diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..8c74662b0dd5d --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/rosetta/default.ts-fixture @@ -0,0 +1,17 @@ +// Fixture with packages imported, but nothing else +import * as path from 'path'; +import { Construct } from 'constructs'; +import { Stack } from 'aws-cdk-lib'; +import { Duration, RemovalPolicy, aws_s3_deployment } from 'aws-cdk-lib'; +import * as agentcore from '@aws-cdk/aws-bedrock-agentcore-alpha'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + /// here + } +} diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/browser.test.ts b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/browser.test.ts new file mode 100644 index 0000000000000..b78fa0ad9b92e --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/browser.test.ts @@ -0,0 +1,1362 @@ +import * as cdk from 'aws-cdk-lib'; +import { App, Stack } from 'aws-cdk-lib'; +import { Template, Match } from 'aws-cdk-lib/assertions'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import { BrowserCustom } from '../../../agentcore/tools/browser'; +import { BrowserNetworkConfiguration } from '../../../agentcore/network/network-configuration'; + +describe('BrowserCustom default tests', () => { + let template: Template; + let app: cdk.App; + let stack: cdk.Stack; + // @ts-ignore + let browser: BrowserCustom; + + beforeAll(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + browser = new BrowserCustom(stack, 'test-browser', { + browserCustomName: 'test_browser', + description: 'A test browser for web automation', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + }); + + app.synth(); + template = Template.fromStack(stack); + }); + + test('Should have the correct resources', () => { + template.resourceCountIs('AWS::BedrockAgentCore::BrowserCustom', 1); + template.resourceCountIs('AWS::IAM::Role', 1); + }); + + test('Should have BrowserCustom resource with expected properties', () => { + template.hasResourceProperties('AWS::BedrockAgentCore::BrowserCustom', { + Name: 'test_browser', + NetworkConfiguration: { + NetworkMode: 'PUBLIC', + }, + }); + }); + + test('Should handle tags correctly when no tags are provided', () => { + // Verify that the BrowserCustom resource exists and has basic properties + const browserResource = template.findResources('AWS::BedrockAgentCore::BrowserCustom'); + const resourceId = Object.keys(browserResource)[0]; + const resource = browserResource[resourceId]; + + // The resource should have basic properties + expect(resource.Properties).toHaveProperty('Name'); + expect(resource.Properties).toHaveProperty('NetworkConfiguration'); + + // Tags property handling - the important thing is that the construct works + // The addPropertyOverride may or may not be visible in the template depending on CDK version + if (resource.Properties.Tags) { + expect(resource.Properties.Tags).toEqual({}); + } + }); +}); + +describe('BrowserCustom with VPC config tests', () => { + let template: Template; + let app: cdk.App; + let stack: cdk.Stack; + + beforeEach(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + }); + + test('Provide VPC and security groups, no security group created', () => { + const vpc = new ec2.Vpc(stack, 'testVPC'); + const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); + + new BrowserCustom(stack, 'test-browser', { + browserCustomName: 'test_browser', + description: 'A test browser for web automation', + networkConfiguration: BrowserNetworkConfiguration.usingVpc(stack, { + vpc: vpc, + securityGroups: [sg], + }), + }); + + template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::BedrockAgentCore::BrowserCustom', { + NetworkConfiguration: { + NetworkMode: 'VPC', + VpcConfig: { + Subnets: Match.anyValue(), + SecurityGroups: Match.anyValue(), + }, + }, + }); + }); + + test('Provide VPC and no security groups, a security group is created', () => { + const vpc = new ec2.Vpc(stack, 'testVPC'); + + new BrowserCustom(stack, 'test-browser', { + browserCustomName: 'test_browser', + description: 'A test browser for web automation', + networkConfiguration: BrowserNetworkConfiguration.usingVpc(stack, { + vpc: vpc, + }), + }); + + template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::BedrockAgentCore::BrowserCustom', { + NetworkConfiguration: { + NetworkMode: 'VPC', + VpcConfig: { + Subnets: Match.anyValue(), + SecurityGroups: Match.anyValue(), + }, + }, + }); + }); + + test('Both security groups and allowAllOutbound are specified, an exception is thrown', () => { + expect(() => { + const vpc = new ec2.Vpc(stack, 'testVPC'); + const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); + + new BrowserCustom(stack, 'test-browser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingVpc(stack, { + vpc: vpc, + securityGroups: [sg], + allowAllOutbound: false, + }), + }); + }).toThrow('Configure \'allowAllOutbound\' directly on the supplied SecurityGroups'); + }); + + test('Vpc specified but no scope, an exception is thrown', () => { + expect(() => { + const vpc = new ec2.Vpc(stack, 'testVPC'); + const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); + + new BrowserCustom(stack, 'test-browser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingVpc(undefined as any, { + vpc: vpc, + securityGroups: [sg], + }), + }); + }).toThrow('Scope is required to create the security group'); + }); + + test('Vpc not specified, an exception is thrown when accessing Connections object', () => { + const browser = new BrowserCustom(stack, 'test-browser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + }); + + const when = () => browser.connections; + expect(when).toThrow('Cannot manage network access without configuring a VPC'); + }); + + test('When adding security group after browser instantiation, it is reflected in VpcConfig of Browser Custom', () => { + const vpc = new ec2.Vpc(stack, 'testVPC'); + + const browser = new BrowserCustom(stack, 'test-browser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingVpc(stack, { + vpc: vpc, + }), + }); + + expect(browser.connections.securityGroups.length).toBe(1); + + browser.connections.addSecurityGroup(new ec2.SecurityGroup(stack, 'AdditionalGroup', { vpc })); + + expect(browser.connections.securityGroups.length).toBe(2); + + template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::BedrockAgentCore::BrowserCustom', { + NetworkConfiguration: { + NetworkMode: 'VPC', + VpcConfig: { + SecurityGroups: [ + { + 'Fn::GetAtt': [ + 'SecurityGroupDD263621', + 'GroupId', + ], + }, + { + 'Fn::GetAtt': [ + 'AdditionalGroup4973CFAA', + 'GroupId', + ], + }, + ], + }, + }, + }); + }); +}); + +describe('BrowserCustom static methods tests', () => { + // @ts-ignore + let template: Template; + let app: cdk.App; + let stack: cdk.Stack; + + beforeAll(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + app.synth(); + template = Template.fromStack(stack); + }); + + test('fromBrowserCustomAttributes should create a BrowserCustom reference from existing attributes', () => { + const browser = BrowserCustom.fromBrowserCustomAttributes(stack, 'test-browser', { + browserArn: 'arn:aws:bedrock-agentcore:us-east-1:123456789012:browser/test-browser', + roleArn: 'arn:aws:iam::123456789012:role/test-browser-role', + lastUpdatedAt: '2021-01-01T00:00:00Z', + status: 'ACTIVE', + createdAt: '2021-01-01T00:00:00Z', + }); + + expect(browser.browserArn).toBe('arn:aws:bedrock-agentcore:us-east-1:123456789012:browser/test-browser'); + expect(browser.executionRole).toBeDefined(); + expect(browser.lastUpdatedAt).toBe('2021-01-01T00:00:00Z'); + expect(browser.status).toBe('ACTIVE'); + expect(browser.createdAt).toBe('2021-01-01T00:00:00Z'); + }); + + test('fromBrowserCustomAttributes provides undefined values when not provided', () => { + const browser = BrowserCustom.fromBrowserCustomAttributes(stack, 'test-browser-2', { + browserArn: 'arn:aws:bedrock-agentcore:us-east-1:123456789012:browser/test-browser', + roleArn: 'arn:aws:iam::123456789012:role/test-browser-role', + }); + + expect(browser.browserArn).toBe('arn:aws:bedrock-agentcore:us-east-1:123456789012:browser/test-browser'); + expect(browser.executionRole).toBeDefined(); + expect(browser.lastUpdatedAt).toBeUndefined(); + expect(browser.status).toBeUndefined(); + expect(browser.createdAt).toBeUndefined(); + }); + + test('fromBrowserCustomAttributes with no security groups specified, an exception is thrown', () => { + // GIVEN + const browser = BrowserCustom.fromBrowserCustomAttributes(stack, 'test-browser-3', { + browserArn: 'arn:aws:bedrock-agentcore:us-east-1:123456789012:browser/test-browser', + roleArn: 'arn:aws:iam::123456789012:role/test-browser-role', + lastUpdatedAt: '2021-01-01T00:00:00Z', + status: 'ACTIVE', + createdAt: '2021-01-01T00:00:00Z', + }); + + // WHEN + const when = () => browser.connections; + + // THEN + expect(when).toThrow(/Cannot manage network access without configuring a VPC/); + }); +}); + +describe('BrowserCustom with recording config tests', () => { + let template: Template; + let app: cdk.App; + let stack: cdk.Stack; + let recordingBucket: s3.Bucket; + // @ts-ignore + let browser: BrowserCustom; + + beforeAll(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + recordingBucket = new s3.Bucket(stack, 'RecordingBucket', { + bucketName: 'test-browser-recordings', + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + browser = new BrowserCustom(stack, 'test-browser', { + browserCustomName: 'test_browser', + description: 'A test browser for web automation', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: recordingBucket.bucketName, + objectKey: 'test-browser-recordings/', + }, + }, + }); + + app.synth(); + template = Template.fromStack(stack); + }); + + test('Should have the correct resources', () => { + template.resourceCountIs('AWS::BedrockAgentCore::BrowserCustom', 1); + template.resourceCountIs('AWS::IAM::Role', 1); + template.resourceCountIs('AWS::S3::Bucket', 1); + }); + + test('Should have BrowserCustom resource with recording config', () => { + template.hasResourceProperties('AWS::BedrockAgentCore::BrowserCustom', { + Name: 'test_browser', + NetworkConfiguration: { + NetworkMode: 'PUBLIC', + }, + }); + }); +}); + +describe('BrowserCustom with custom execution role tests', () => { + let template: Template; + let app: cdk.App; + let stack: cdk.Stack; + let customRole: iam.Role; + // @ts-ignore + let browser: BrowserCustom; + + beforeAll(() => { + app = new cdk.App(); + + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + // Create a custom execution role + customRole = new iam.Role(stack, 'CustomExecutionRole', { + assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'), + roleName: 'custom-browser-execution-role', + }); + + browser = new BrowserCustom(stack, 'test-browser', { + browserCustomName: 'test_browser', + description: 'A test browser with custom execution role', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + executionRole: customRole, + }); + + app.synth(); + template = Template.fromStack(stack); + }); + + test('Should have the correct resources', () => { + template.resourceCountIs('AWS::BedrockAgentCore::BrowserCustom', 1); + template.resourceCountIs('AWS::IAM::Role', 1); + }); + + test('Should have BrowserCustom resource with custom execution role', () => { + template.hasResourceProperties('AWS::BedrockAgentCore::BrowserCustom', { + Name: 'test_browser', + NetworkConfiguration: { + NetworkMode: 'PUBLIC', + }, + }); + }); + + test('Should have custom execution role with correct properties', () => { + template.hasResourceProperties('AWS::IAM::Role', { + RoleName: 'custom-browser-execution-role', + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'bedrock-agentcore.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); + }); +}); + +describe('BrowserCustom name validation tests', () => { + let app: cdk.App; + let stack: cdk.Stack; + + beforeAll(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + }); + + test('Should accept name with hyphen (validation not enforced)', () => { + expect(() => { + new BrowserCustom(stack, 'test-browser', { + browserCustomName: 'test-browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + }); + }).toThrow('The field Browser name with value "test-browser" does not match the required pattern /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/'); + }); + + test('Should accept empty name (validation not enforced)', () => { + expect(() => { + new BrowserCustom(stack, 'empty-name', { + browserCustomName: '', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + }); + }).toThrow('The field Browser name is 0 characters long but must be at least 1 characters'); + }); + + test('Should accept name with spaces (validation not enforced)', () => { + expect(() => { + new BrowserCustom(stack, 'name-with-spaces', { + browserCustomName: 'test browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + }); + }).toThrow('The field Browser name with value "test browser" does not match the required pattern /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/'); + }); + + test('Should accept name with special characters (validation not enforced)', () => { + expect(() => { + new BrowserCustom(stack, 'name-with-special-chars', { + browserCustomName: 'test@browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + }); + }).toThrow('The field Browser name with value "test@browser" does not match the required pattern /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/'); + }); + + test('Should accept name exceeding 48 characters (validation not enforced)', () => { + const longName = 'a'.repeat(49); + expect(() => { + new BrowserCustom(stack, 'long-name', { + browserCustomName: longName, + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + }); + }).toThrow('The field Browser name is 49 characters long but must be less than or equal to 48 characters'); + }); + + test('Should accept valid name with underscores', () => { + expect(() => { + new BrowserCustom(stack, 'valid-name', { + browserCustomName: 'test_browser_123', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + }); + }).not.toThrow(); + }); + + test('Should accept valid name with only letters and numbers', () => { + expect(() => { + new BrowserCustom(stack, 'valid-name-2', { + browserCustomName: 'testBrowser123', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + }); + }).not.toThrow(); + }); + + test('Should use default PUBLIC network configuration when not provided', () => { + const browser = new BrowserCustom(stack, 'default-network', { + browserCustomName: 'test_browser_default', + }); + + expect(browser.networkConfiguration.networkMode).toBe('PUBLIC'); + }); +}); + +describe('BrowserCustom tags validation tests', () => { + let app: cdk.App; + let stack: cdk.Stack; + + beforeAll(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + }); + + test('Should accept valid tags', () => { + expect(() => { + new BrowserCustom(stack, 'valid-tags', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + tags: { + 'Environment': 'Production', + 'Team': 'AI/ML', + 'Project': 'AgentCore', + 'Cost-Center': '12345', + }, + }); + }).not.toThrow(); + }); + + test('Should accept tags with special characters', () => { + expect(() => { + new BrowserCustom(stack, 'special-chars-tags', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + tags: { + 'Environment': 'Production', + 'Team@Company': 'AI/ML', + 'Project:Name': 'AgentCore', + 'Cost-Center': '12345', + 'Description': 'Test browser with special chars', + }, + }); + }).not.toThrow(); + }); + + test('Should accept empty tag key (validation not enforced)', () => { + expect(() => { + new BrowserCustom(stack, 'empty-tag-key', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + tags: { + '': 'value', + }, + }); + }).toThrow('The field Tag key is 0 characters long but must be at least 1 characters'); + }); + + test('Should accept tag key exceeding 256 characters (validation not enforced)', () => { + const longKey = 'a'.repeat(257); + expect(() => { + new BrowserCustom(stack, 'long-tag-key', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + tags: { + [longKey]: 'value', + }, + }); + }).toThrow('The field Tag key is 257 characters long but must be less than or equal to 256 characters'); + }); + + test('Should accept tag value exceeding 256 characters (validation not enforced)', () => { + const longValue = 'a'.repeat(257); + expect(() => { + new BrowserCustom(stack, 'long-tag-value', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + tags: { + key: longValue, + }, + }); + }).toThrow('The field Tag value is 257 characters long but must be less than or equal to 256 characters'); + }); + + test('Should accept tag key with invalid characters (validation not enforced)', () => { + expect(() => { + new BrowserCustom(stack, 'invalid-tag-key', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + tags: { + 'key#invalid': 'value', + }, + }); + }).toThrow('The field Tag key with value "key#invalid" does not match the required pattern /^[a-zA-Z0-9\\s._:/=+@-]*$/'); + }); + + test('Should accept tag value with invalid characters (validation not enforced)', () => { + expect(() => { + new BrowserCustom(stack, 'invalid-tag-value', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + tags: { + key: 'value#invalid', + }, + }); + }).toThrow('The field Tag value with value "value#invalid" does not match the required pattern /^[a-zA-Z0-9\\s._:/=+@-]*$/'); + }); + + test('Should accept null tag value (validation not enforced)', () => { + expect(() => { + new BrowserCustom(stack, 'null-tag-value', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + tags: { + key: null as any, + }, + }); + }).not.toThrow(); + }); + + test('Should accept undefined tag value (validation not enforced)', () => { + expect(() => { + new BrowserCustom(stack, 'undefined-tag-value', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + tags: { + key: undefined as any, + }, + }); + }).not.toThrow(); + }); + + test('Should accept undefined tags', () => { + expect(() => { + new BrowserCustom(stack, 'undefined-tags', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + tags: undefined, + }); + }).not.toThrow(); + }); + + test('Should accept empty tags object', () => { + expect(() => { + new BrowserCustom(stack, 'empty-tags', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + tags: {}, + }); + }).not.toThrow(); + }); +}); + +describe('BrowserCustom with tags CloudFormation template tests', () => { + let template: Template; + let app: cdk.App; + let stack: cdk.Stack; + + beforeAll(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + new BrowserCustom(stack, 'test-browser-with-tags', { + browserCustomName: 'test_browser_with_tags', + description: 'A test browser with tags', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + tags: { + Environment: 'Production', + Team: 'AI/ML', + Project: 'AgentCore', + }, + }); + + app.synth(); + template = Template.fromStack(stack); + }); + + test('Should handle tags correctly when tags are provided', () => { + // Verify that the BrowserCustom resource exists and has basic properties + const browserResource = template.findResources('AWS::BedrockAgentCore::BrowserCustom'); + const resourceId = Object.keys(browserResource)[0]; + const resource = browserResource[resourceId]; + + // The resource should have basic properties + expect(resource.Properties).toHaveProperty('Name'); + expect(resource.Properties).toHaveProperty('NetworkConfiguration'); + + // Tags property handling - the important thing is that the construct works + // The addPropertyOverride may or may not be visible in the template depending on CDK version + if (resource.Properties.Tags) { + expect(resource.Properties.Tags).toHaveProperty('Environment'); + expect(resource.Properties.Tags).toHaveProperty('Team'); + expect(resource.Properties.Tags).toHaveProperty('Project'); + } + }); + + test('Should have correct resource count with tags', () => { + template.resourceCountIs('AWS::BedrockAgentCore::BrowserCustom', 1); + template.resourceCountIs('AWS::IAM::Role', 1); + }); +}); + +describe('BrowserCustom CloudFormation parameter validation tests', () => { + let app: App; + let stack: Stack; + let template: Template; + + test('Should pass bucket name string instead of S3 bucket resource', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + // Create an S3 bucket resource + const bucket = new s3.Bucket(stack, 'TestBucket', { + bucketName: 'test-bucket-name', + }); + + // Create browser with S3 bucket resource + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: bucket.bucketName, // Extract bucket name string + objectKey: 'recordings/', + }, + }, + }); + + app.synth(); + template = Template.fromStack(stack); + + // Verify that the CloudFormation template has the correct parameter structure + const browserResource = template.findResources('AWS::BedrockAgentCore::BrowserCustom'); + const resourceId = Object.keys(browserResource)[0]; + const resource = browserResource[resourceId]; + + // The RecordingConfig should be properly constructed + expect(resource.Properties).toHaveProperty('RecordingConfig'); + + // Verify that the RecordingConfig is properly structured + const recordingConfig = resource.Properties.RecordingConfig; + expect(recordingConfig).toHaveProperty('Enabled'); + expect(recordingConfig).toHaveProperty('S3Location'); + + // The current implementation doesn't create conditions + // Just verify that the RecordingConfig is properly structured + expect(recordingConfig.Enabled).toBe(true); + expect(recordingConfig.S3Location).toBeDefined(); + }); + + test('Should handle empty recording config with conditional logic', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + // No recording config provided + }); + + app.synth(); + template = Template.fromStack(stack); + + // Verify that the template has RecordingConfig with conditional logic + const browserResource = template.findResources('AWS::BedrockAgentCore::BrowserCustom'); + const resourceId = Object.keys(browserResource)[0]; + const resource = browserResource[resourceId]; + + // Should have RecordingConfig with enabled: false when not provided + expect(resource.Properties).toHaveProperty('RecordingConfig'); + expect(resource.Properties.RecordingConfig).toEqual({ Enabled: false }); + }); + + test('Should have recording disabled by default when not provided', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + const browser = new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + // No recording config provided - should default to disabled + }); + + app.synth(); + template = Template.fromStack(stack); + + // Verify that recordingConfig is set to disabled by default + expect(browser.recordingConfig).toBeDefined(); + expect(browser.recordingConfig?.enabled).toBe(false); + + // Verify that the CloudFormation template includes RecordingConfig with enabled: false + const browserResource = template.findResources('AWS::BedrockAgentCore::BrowserCustom'); + const resourceId = Object.keys(browserResource)[0]; + const resource = browserResource[resourceId]; + + // Should have RecordingConfig property with enabled: false when not provided + expect(resource.Properties).toHaveProperty('RecordingConfig'); + expect(resource.Properties.RecordingConfig).toEqual({ Enabled: false }); + }); + + test('Should validate CloudFormation template structure', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: 'test-bucket-name', // String bucket name + objectKey: 'recordings/', + }, + }, + tags: { + Environment: 'Test', + Team: 'AI/ML', + }, + }); + + app.synth(); + template = Template.fromStack(stack); + + // Verify that all conditions reference parameters, not resources + const conditions = template.findConditions('*'); + Object.values(conditions).forEach((condition: any) => { + // Check that conditions don't reference resources + const conditionStr = JSON.stringify(condition); + expect(conditionStr).not.toMatch(/AWS::S3::Bucket/); + expect(conditionStr).not.toMatch(/AWS::IAM::Role/); + expect(conditionStr).not.toMatch(/AWS::BedrockAgentCore::BrowserCustom/); + }); + + // Verify that the template has the expected structure + expect(template.toJSON()).toHaveProperty('Resources'); + // Conditions are not created in the current implementation + // Outputs are no longer used - attributes are accessed directly from the resource + }); + + test('Should handle execution role ARN correctly', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + const role = new iam.Role(stack, 'TestRole', { + assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'), + }); + + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + executionRole: role, + }); + + app.synth(); + template = Template.fromStack(stack); + + // Verify that the execution role ARN is properly referenced + const browserResource = template.findResources('AWS::BedrockAgentCore::BrowserCustom'); + const resourceId = Object.keys(browserResource)[0]; + const resource = browserResource[resourceId]; + + expect(resource.Properties).toHaveProperty('ExecutionRoleArn'); + expect(resource.Properties.ExecutionRoleArn).toHaveProperty('Fn::GetAtt'); + }); + + describe('Recording Configuration Validation', () => { + test('Should accept valid recording configuration', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + expect(() => { + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: 'valid-bucket-name-123', + objectKey: 'recordings/', + }, + }, + }); + }).not.toThrow(); + }); + + test('Should accept browser without recording configuration', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + expect(() => { + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + }); + }).not.toThrow(); + }); + + test('Should accept recording configuration without S3 location', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + expect(() => { + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + }, + }); + }).not.toThrow(); + }); + + test('Should accept S3 location without bucket name (validation not enforced)', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + expect(() => { + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: '', // Empty bucket name + objectKey: 'recordings/', + }, + }, + }); + }).toThrow('S3 bucket name is required when S3 location is provided for recording configuration'); + }); + + test('Should accept S3 location without object key (validation not enforced)', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + expect(() => { + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: 'valid-bucket-name-123', + objectKey: '', // Empty object key + }, + }, + }); + }).toThrow('S3 object key (prefix) is required when S3 location is provided for recording configuration'); + }); + + test('Should accept invalid bucket name - starts with uppercase (validation not enforced)', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + expect(() => { + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: 'Invalid-Bucket-Name', + objectKey: 'recordings/', + }, + }, + }); + }).toThrow('The field S3 bucket name with value "Invalid-Bucket-Name" does not match the required pattern /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/'); + }); + + test('Should accept invalid bucket name - starts with hyphen (validation not enforced)', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + expect(() => { + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: '-invalid-bucket-name', + objectKey: 'recordings/', + }, + }, + }); + }).toThrow('The field S3 bucket name with value "-invalid-bucket-name" does not match the required pattern /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/'); + }); + + test('Should accept invalid bucket name - ends with hyphen (validation not enforced)', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + expect(() => { + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: 'invalid-bucket-name-', + objectKey: 'recordings/', + }, + }, + }); + }).toThrow('The field S3 bucket name with value "invalid-bucket-name-" does not match the required pattern /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/'); + }); + + test('Should accept invalid bucket name - contains underscore (validation not enforced)', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + expect(() => { + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: 'invalid_bucket_name', + objectKey: 'recordings/', + }, + }, + }); + }).toThrow('The field S3 bucket name with value "invalid_bucket_name" does not match the required pattern /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/'); + }); + + test('Should accept invalid bucket name - too short (validation not enforced)', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + expect(() => { + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: 'a', + objectKey: 'recordings/', + }, + }, + }); + }).toThrow('The field S3 bucket name with value "a" does not match the required pattern /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/'); + }); + + test('Should accept invalid bucket name - too long (validation not enforced)', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + const longBucketName = 'a'.repeat(65); // 65 characters, exceeds the 63 character limit + + expect(() => { + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: longBucketName, + objectKey: 'recordings/', + }, + }, + }); + }).toThrow('The field S3 bucket name with value "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" does not match the required pattern /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/'); + }); + + test('Should accept empty object key (validation not enforced)', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + expect(() => { + new BrowserCustom(stack, 'TestBrowser', { + browserCustomName: 'test_browser', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: 'valid-bucket-name-123', + objectKey: '', + }, + }, + }); + }).toThrow('S3 object key (prefix) is required when S3 location is provided for recording configuration'); + }); + + test('Should accept valid bucket names with various valid characters', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + const validBucketNames = [ + 'valid-bucket-name', + 'valid.bucket.name', + 'valid-bucket-name-123', + 'valid.bucket.name.123', + 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6', + 'bucket123', + '123bucket', + 'bucket-name-123', + ]; + + validBucketNames.forEach((bucketName, index) => { + expect(() => { + new BrowserCustom(stack, `TestBrowser${index}`, { + browserCustomName: `test_browser_${index}`, + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: bucketName, + objectKey: 'recordings/', + }, + }, + }); + }).not.toThrow(); + }); + }); + + test('Should accept valid object keys', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + const validObjectKeys = [ + 'recordings/', + 'recordings', + 'a', + 'recordings/subfolder/', + 'recordings-2024/', + 'recordings.with.dots/', + ]; + + validObjectKeys.forEach((objectKey, index) => { + expect(() => { + new BrowserCustom(stack, `TestBrowser${index}`, { + browserCustomName: `test_browser_${index}`, + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: 'valid-bucket-name-123', + objectKey: objectKey, + }, + }, + }); + }).not.toThrow(); + }); + }); + }); +}); + +describe('BrowserCustom grant method tests', () => { + let app: cdk.App; + let stack: cdk.Stack; + let browser: BrowserCustom; + + beforeAll(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + browser = new BrowserCustom(stack, 'test-browser', { + browserCustomName: 'test_browser', + description: 'A test browser for grant testing', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + }); + }); + + test('Should grant custom actions to IAM principal', () => { + const user = new iam.User(stack, 'TestUser'); + const grant = browser.grant(user, 'bedrock-agentcore:GetBrowser', 'bedrock-agentcore:ListBrowsers'); + + expect(grant).toBeDefined(); + expect(grant.success).toBe(true); + expect(grant.principalStatements).toBeDefined(); + expect(grant.principalStatements.length).toBeGreaterThan(0); + }); + + test('Should grant read permissions to IAM principal', () => { + const user = new iam.User(stack, 'TestUser2'); + const grant = browser.grantRead(user); + + expect(grant).toBeDefined(); + expect(grant.success).toBe(true); + expect(grant.principalStatements).toBeDefined(); + expect(grant.principalStatements.length).toBeGreaterThan(0); + }); + + test('Should grant use permissions to IAM principal', () => { + const user = new iam.User(stack, 'TestUser3'); + const grant = browser.grantUse(user); + + expect(grant).toBeDefined(); + expect(grant.success).toBe(true); + expect(grant.principalStatements).toBeDefined(); + expect(grant.principalStatements.length).toBeGreaterThan(0); + }); + + test('Should grant permissions to IAM role', () => { + const role = new iam.Role(stack, 'TestRole', { + assumedBy: new iam.ServicePrincipal('bedrock.amazonaws.com'), + }); + + const grant = browser.grantRead(role); + + expect(grant).toBeDefined(); + expect(grant.success).toBe(true); + expect(grant.principalStatements).toBeDefined(); + expect(grant.principalStatements.length).toBeGreaterThan(0); + }); + + test('Should grant permissions to IAM group', () => { + const group = new iam.Group(stack, 'TestGroup'); + const grant = browser.grantUse(group); + + expect(grant).toBeDefined(); + expect(grant.success).toBe(true); + expect(grant.principalStatements).toBeDefined(); + expect(grant.principalStatements.length).toBeGreaterThan(0); + }); + + test('Should return a valid Grant object', () => { + const user = new iam.User(stack, 'TestUser4'); + const grant = browser.grantRead(user); + + expect(grant).toBeDefined(); + expect(grant.success).toBe(true); + expect(grant.principalStatements).toBeDefined(); + expect(grant.principalStatements.length).toBeGreaterThan(0); + }); +}); + +describe('BrowserCustom recording configuration with S3 location tests', () => { + test('Should grant S3 permissions when recording is enabled with S3 location', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + const recordingBucket = new s3.Bucket(stack, 'RecordingBucket', { + bucketName: 'test-browser-recordings', + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + new BrowserCustom(stack, 'test-browser-with-recording', { + browserCustomName: 'test_browser_with_recording', + description: 'A test browser with recording enabled', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: recordingBucket.bucketName, + objectKey: 'recordings/', + }, + }, + }); + + app.synth(); + const template = Template.fromStack(stack); + + // Should have the browser resource + template.resourceCountIs('AWS::BedrockAgentCore::BrowserCustom', 1); + template.resourceCountIs('AWS::S3::Bucket', 1); + template.resourceCountIs('AWS::IAM::Role', 1); + + // Should have RecordingConfig with S3 location + template.hasResourceProperties('AWS::BedrockAgentCore::BrowserCustom', { + RecordingConfig: { + Enabled: true, + S3Location: { + Bucket: { + Ref: Match.stringLikeRegexp('RecordingBucket.*'), + }, + Prefix: 'recordings/', + }, + }, + }); + }); + + test('Should handle recording config with enabled true but no S3 location', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + expect(() => { + new BrowserCustom(stack, 'test-browser-recording-no-s3', { + browserCustomName: 'test_browser_recording_no_s3', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + // No s3Location provided + }, + }); + }).not.toThrow(); + }); + + test('Should handle recording config with S3 location but enabled false', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + const recordingBucket = new s3.Bucket(stack, 'RecordingBucket2', { + bucketName: 'test-browser-recordings-2', + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + expect(() => { + new BrowserCustom(stack, 'test-browser-recording-disabled', { + browserCustomName: 'test_browser_recording_disabled', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: false, + s3Location: { + bucketName: recordingBucket.bucketName, + objectKey: 'recordings/', + }, + }, + }); + }).not.toThrow(); + }); + + test('Should test metric methods with different configurations', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + const browser = new BrowserCustom(stack, 'test-browser-metrics', { + browserCustomName: 'test_browser_metrics', + networkConfiguration: BrowserNetworkConfiguration.usingPublicNetwork(), + }); + + // Test various metric methods + const latencyMetric = browser.metricLatencyForApiOperation('TestOperation'); + const invocationsMetric = browser.metricInvocationsForApiOperation('TestOperation'); + const errorsMetric = browser.metricErrorsForApiOperation('TestOperation'); + const sessionDurationMetric = browser.metricSessionDuration(); + const takeOverCountMetric = browser.metricTakeOverCount(); + const takeOverReleaseCountMetric = browser.metricTakeOverReleaseCount(); + const takeOverDurationMetric = browser.metricTakeOverDuration(); + + expect(latencyMetric).toBeDefined(); + expect(invocationsMetric).toBeDefined(); + expect(errorsMetric).toBeDefined(); + expect(sessionDurationMetric).toBeDefined(); + expect(takeOverCountMetric).toBeDefined(); + expect(takeOverReleaseCountMetric).toBeDefined(); + expect(takeOverDurationMetric).toBeDefined(); + }); +}); diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/code-interpreter.test.ts b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/code-interpreter.test.ts new file mode 100644 index 0000000000000..3744f5b22768f --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/code-interpreter.test.ts @@ -0,0 +1,986 @@ +import * as cdk from 'aws-cdk-lib'; +import { App, Stack } from 'aws-cdk-lib'; +import { Template, Match } from 'aws-cdk-lib/assertions'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import { CodeInterpreterCustom } from '../../../agentcore/tools/code-interpreter'; +import { CodeInterpreterNetworkConfiguration } from '../../../agentcore/network/network-configuration'; + +describe('CodeInterpreterCustom default tests', () => { + let template: Template; + let app: cdk.App; + let stack: cdk.Stack; + // @ts-ignore + let codeInterpreter: CodeInterpreterCustom; + + beforeAll(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + codeInterpreter = new CodeInterpreterCustom(stack, 'test-code-interpreter', { + codeInterpreterCustomName: 'test_code_interpreter', + description: 'A test code interpreter for code execution', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + }); + + app.synth(); + template = Template.fromStack(stack); + }); + + test('Should have the correct resources', () => { + template.resourceCountIs('AWS::BedrockAgentCore::CodeInterpreterCustom', 1); + template.resourceCountIs('AWS::IAM::Role', 1); + }); + + test('Should have CodeInterpreterCustom resource with expected properties', () => { + template.hasResourceProperties('AWS::BedrockAgentCore::CodeInterpreterCustom', { + Name: 'test_code_interpreter', + NetworkConfiguration: { + NetworkMode: 'PUBLIC', + }, + }); + }); + + test('Should handle tags correctly when no tags are provided', () => { + // Verify that the CodeInterpreterCustom resource exists and has basic properties + const codeInterpreterResource = template.findResources('AWS::BedrockAgentCore::CodeInterpreterCustom'); + const resourceId = Object.keys(codeInterpreterResource)[0]; + const resource = codeInterpreterResource[resourceId]; + + // The resource should have basic properties + expect(resource.Properties).toHaveProperty('Name'); + expect(resource.Properties).toHaveProperty('NetworkConfiguration'); + + // Tags property handling - the important thing is that the construct works + // The addPropertyOverride may or may not be visible in the template depending on CDK version + if (resource.Properties.Tags) { + expect(resource.Properties.Tags).toEqual({}); + } + }); +}); + +describe('CodeInterpreterCustom with VPC config tests', () => { + let template: Template; + let app: cdk.App; + let stack: cdk.Stack; + + beforeEach(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + }); + + test('Provide VPC and security groups, no security group created', () => { + const vpc = new ec2.Vpc(stack, 'testVPC'); + const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); + + new CodeInterpreterCustom(stack, 'test-ci', { + codeInterpreterCustomName: 'test_ci', + description: 'A code interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingVpc(stack, { + vpc: vpc, + securityGroups: [sg], + }), + }); + + template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::BedrockAgentCore::CodeInterpreterCustom', { + NetworkConfiguration: { + NetworkMode: 'VPC', + VpcConfig: { + Subnets: Match.anyValue(), + SecurityGroups: Match.anyValue(), + }, + }, + }); + }); + + test('Provide VPC and no security groups, a security group is created', () => { + const vpc = new ec2.Vpc(stack, 'testVPC'); + + new CodeInterpreterCustom(stack, 'test-ci', { + codeInterpreterCustomName: 'test_ci', + description: 'A test code interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingVpc(stack, { + vpc: vpc, + }), + }); + + template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::BedrockAgentCore::CodeInterpreterCustom', { + NetworkConfiguration: { + NetworkMode: 'VPC', + VpcConfig: { + Subnets: Match.anyValue(), + SecurityGroups: Match.anyValue(), + }, + }, + }); + }); + + test('Both security groups and allowAllOutbound are specified, an exception is thrown', () => { + expect(() => { + const vpc = new ec2.Vpc(stack, 'testVPC'); + const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); + + new CodeInterpreterCustom(stack, 'test-ci', { + codeInterpreterCustomName: 'test_ci', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingVpc(stack, { + vpc: vpc, + securityGroups: [sg], + allowAllOutbound: false, + }), + }); + }).toThrow('Configure \'allowAllOutbound\' directly on the supplied SecurityGroups'); + }); + + test('Vpc specified but no scope, an exception is thrown', () => { + expect(() => { + const vpc = new ec2.Vpc(stack, 'testVPC'); + const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); + + new CodeInterpreterCustom(stack, 'test-ci', { + codeInterpreterCustomName: 'test_ci', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingVpc(undefined as any, { + vpc: vpc, + securityGroups: [sg], + }), + }); + }).toThrow('Scope is required to create the security group'); + }); + + test('Vpc not specified, an exception is thrown when accessing Connections object', () => { + const codeInterpreter = new CodeInterpreterCustom(stack, 'test-ci', { + codeInterpreterCustomName: 'test_ci', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + }); + + const when = () => codeInterpreter.connections; + expect(when).toThrow('Cannot manage network access without configuring a VPC'); + }); + + test('When adding security group after code interpreter instantiation, it is reflected in VpcConfig of Code Interpreter Custom', () => { + const vpc = new ec2.Vpc(stack, 'testVPC'); + + const codeInterpreter = new CodeInterpreterCustom(stack, 'test-ci', { + codeInterpreterCustomName: 'test_ci', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingVpc(stack, { + vpc: vpc, + }), + }); + + expect(codeInterpreter.connections.securityGroups.length).toBe(1); + + codeInterpreter.connections.addSecurityGroup(new ec2.SecurityGroup(stack, 'AdditionalGroup', { vpc })); + + expect(codeInterpreter.connections.securityGroups.length).toBe(2); + + template = Template.fromStack(stack); + + template.hasResourceProperties('AWS::BedrockAgentCore::CodeInterpreterCustom', { + NetworkConfiguration: { + NetworkMode: 'VPC', + VpcConfig: { + SecurityGroups: [ + { + 'Fn::GetAtt': [ + 'SecurityGroupDD263621', + 'GroupId', + ], + }, + { + 'Fn::GetAtt': [ + 'AdditionalGroup4973CFAA', + 'GroupId', + ], + }, + ], + }, + }, + }); + }); +}); + +describe('CodeInterpreterCustom static methods tests', () => { + // @ts-ignore + let template: Template; + let app: cdk.App; + let stack: cdk.Stack; + + beforeAll(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + app.synth(); + template = Template.fromStack(stack); + }); + + test('fromCodeInterpreterCustomAttributes should create a CodeInterpreterCustom reference from existing attributes', () => { + const codeInterpreter = CodeInterpreterCustom.fromCodeInterpreterCustomAttributes(stack, 'test-ci', { + codeInterpreterArn: 'arn:aws:bedrock-agentcore:us-east-1:123456789012:code-interpreter/test-ci', + roleArn: 'arn:aws:iam::123456789012:role/test-ci-role', + lastUpdatedAt: '2021-01-01T00:00:00Z', + status: 'ACTIVE', + createdAt: '2021-01-01T00:00:00Z', + }); + + expect(codeInterpreter.codeInterpreterArn).toBe('arn:aws:bedrock-agentcore:us-east-1:123456789012:code-interpreter/test-ci'); + expect(codeInterpreter.executionRole).toBeDefined(); + expect(codeInterpreter.lastUpdatedAt).toBe('2021-01-01T00:00:00Z'); + expect(codeInterpreter.status).toBe('ACTIVE'); + expect(codeInterpreter.createdAt).toBe('2021-01-01T00:00:00Z'); + }); + + test('fromCodeInterpreterCustomAttributes provides undefined values when not provided', () => { + const codeInterpreter = CodeInterpreterCustom.fromCodeInterpreterCustomAttributes(stack, 'test-ci-2', { + codeInterpreterArn: 'arn:aws:bedrock-agentcore:us-east-1:123456789012:code-interpreter/test-ci', + roleArn: 'arn:aws:iam::123456789012:role/test-ci-role', + }); + + expect(codeInterpreter.codeInterpreterArn).toBe('arn:aws:bedrock-agentcore:us-east-1:123456789012:code-interpreter/test-ci'); + expect(codeInterpreter.executionRole).toBeDefined(); + expect(codeInterpreter.lastUpdatedAt).toBeUndefined(); + expect(codeInterpreter.status).toBeUndefined(); + expect(codeInterpreter.createdAt).toBeUndefined(); + }); + + test('fromCodeInterpreterCustomAttributes with no security groups specified, an exception is thrown', () => { + // GIVEN + const codeInterpreter = CodeInterpreterCustom.fromCodeInterpreterCustomAttributes(stack, 'test-ci-3', { + codeInterpreterArn: 'arn:aws:bedrock-agentcore:us-east-1:123456789012:code-interpreter/test-ci', + roleArn: 'arn:aws:iam::123456789012:role/test-ci-role', + lastUpdatedAt: '2021-01-01T00:00:00Z', + status: 'ACTIVE', + createdAt: '2021-01-01T00:00:00Z', + }); + + // WHEN + const when = () => codeInterpreter.connections; + + // THEN + expect(when).toThrow(/Cannot manage network access without configuring a VPC/); + }); +}); + +describe('CodeInterpreterCustom with custom execution role tests', () => { + let template: Template; + let app: cdk.App; + let stack: cdk.Stack; + let customRole: iam.Role; + // @ts-ignore + let codeInterpreter: CodeInterpreterCustom; + + beforeAll(() => { + app = new cdk.App(); + + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + // Create a custom execution role + customRole = new iam.Role(stack, 'CustomExecutionRole', { + assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'), + roleName: 'custom-code-interpreter-execution-role', + }); + + codeInterpreter = new CodeInterpreterCustom(stack, 'test-code-interpreter', { + codeInterpreterCustomName: 'test_code_interpreter', + description: 'A test code interpreter with custom execution role', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + executionRole: customRole, + }); + + app.synth(); + template = Template.fromStack(stack); + }); + + test('Should have the correct resources', () => { + template.resourceCountIs('AWS::BedrockAgentCore::CodeInterpreterCustom', 1); + template.resourceCountIs('AWS::IAM::Role', 1); + }); + + test('Should have CodeInterpreterCustom resource with custom execution role', () => { + template.hasResourceProperties('AWS::BedrockAgentCore::CodeInterpreterCustom', { + Name: 'test_code_interpreter', + NetworkConfiguration: { + NetworkMode: 'PUBLIC', + }, + }); + }); + + test('Should have custom execution role with correct properties', () => { + template.hasResourceProperties('AWS::IAM::Role', { + RoleName: 'custom-code-interpreter-execution-role', + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'bedrock-agentcore.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); + }); +}); + +describe('CodeInterpreterCustom name validation tests', () => { + let app: cdk.App; + let stack: cdk.Stack; + + beforeAll(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + }); + + test('Should accept name with hyphen (validation not enforced)', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'test-code-interpreter', { + codeInterpreterCustomName: 'test-code-interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + }); + }).toThrow('The field Code interpreter name with value "test-code-interpreter" does not match the required pattern /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/'); + }); + + test('Should accept empty name (validation not enforced)', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'empty-name', { + codeInterpreterCustomName: '', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + }); + }).toThrow('The field Code interpreter name is 0 characters long but must be at least 1 characters'); + }); + + test('Should accept name with spaces (validation not enforced)', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'name-with-spaces', { + codeInterpreterCustomName: 'test code interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + }); + }).toThrow('The field Code interpreter name with value "test code interpreter" does not match the required pattern /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/'); + }); + + test('Should accept name with special characters (validation not enforced)', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'name-with-special-chars', { + codeInterpreterCustomName: 'test@code-interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + }); + }).toThrow('The field Code interpreter name with value "test@code-interpreter" does not match the required pattern /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/'); + }); + + test('Should accept name exceeding 48 characters (validation not enforced)', () => { + const longName = 'a'.repeat(49); + expect(() => { + new CodeInterpreterCustom(stack, 'long-name', { + codeInterpreterCustomName: longName, + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + }); + }).toThrow('The field Code interpreter name is 49 characters long but must be less than or equal to 48 characters'); + }); + + test('Should accept valid name with underscores', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'valid-name', { + codeInterpreterCustomName: 'test_code_interpreter_123', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + }); + }).not.toThrow(); + }); + + test('Should accept valid name with only letters and numbers', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'valid-name-2', { + codeInterpreterCustomName: 'testCodeInterpreter123', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + }); + }).not.toThrow(); + }); + + test('Should use default PUBLIC network configuration when not provided', () => { + const codeInterpreter = new CodeInterpreterCustom(stack, 'default-network', { + codeInterpreterCustomName: 'test_code_interpreter_default', + }); + + expect(codeInterpreter.networkConfiguration.networkMode).toBe('PUBLIC'); + }); +}); + +describe('CodeInterpreterCustom tags validation tests', () => { + let app: cdk.App; + let stack: cdk.Stack; + + beforeAll(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + }); + + test('Should accept valid tags', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'valid-tags', { + codeInterpreterCustomName: 'test_code_interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: { + 'Environment': 'Production', + 'Team': 'AI/ML', + 'Project': 'AgentCore', + 'Cost-Center': '12345', + }, + }); + }).not.toThrow(); + }); + + test('Should accept tags with special characters', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'special-chars-tags', { + codeInterpreterCustomName: 'test_code_interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: { + 'Environment': 'Production', + 'Team@Company': 'AI/ML', + 'Project:Name': 'AgentCore', + 'Cost-Center': '12345', + 'Description': 'Test code interpreter with special chars', + }, + }); + }).not.toThrow(); + }); + + test('Should accept empty tag key (validation not enforced)', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'empty-tag-key', { + codeInterpreterCustomName: 'test_code_interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: { + '': 'value', + }, + }); + }).toThrow('The field Tag key is 0 characters long but must be at least 1 characters'); + }); + + test('Should accept tag key exceeding 256 characters (validation not enforced)', () => { + const longKey = 'a'.repeat(257); + expect(() => { + new CodeInterpreterCustom(stack, 'long-tag-key', { + codeInterpreterCustomName: 'test_code_interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: { + [longKey]: 'value', + }, + }); + }).toThrow('The field Tag key is 257 characters long but must be less than or equal to 256 characters'); + }); + + test('Should accept tag value exceeding 256 characters (validation not enforced)', () => { + const longValue = 'a'.repeat(257); + expect(() => { + new CodeInterpreterCustom(stack, 'long-tag-value', { + codeInterpreterCustomName: 'test_code_interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: { + key: longValue, + }, + }); + }).toThrow('The field Tag value is 257 characters long but must be less than or equal to 256 characters'); + }); + + test('Should accept tag key with invalid characters (validation not enforced)', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'invalid-tag-key', { + codeInterpreterCustomName: 'test_code_interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: { + 'key#invalid': 'value', + }, + }); + }).toThrow('The field Tag key with value "key#invalid" does not match the required pattern /^[a-zA-Z0-9\\s._:/=+@-]*$/'); + }); + + test('Should accept tag value with invalid characters (validation not enforced)', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'invalid-tag-value', { + codeInterpreterCustomName: 'test_code_interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: { + key: 'value#invalid', + }, + }); + }).toThrow('The field Tag value with value "value#invalid" does not match the required pattern /^[a-zA-Z0-9\\s._:/=+@-]*$/'); + }); + + test('Should accept null tag value (validation not enforced)', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'null-tag-value', { + codeInterpreterCustomName: 'test_code_interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: { + key: null as any, + }, + }); + }).not.toThrow(); + }); + + test('Should accept undefined tag value (validation not enforced)', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'undefined-tag-value', { + codeInterpreterCustomName: 'test_code_interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: { + key: undefined as any, + }, + }); + }).not.toThrow(); + }); + + test('Should accept undefined tags', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'undefined-tags', { + codeInterpreterCustomName: 'test_code_interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: undefined, + }); + }).not.toThrow(); + }); + + test('Should accept empty tags object', () => { + expect(() => { + new CodeInterpreterCustom(stack, 'empty-tags', { + codeInterpreterCustomName: 'test_code_interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: {}, + }); + }).not.toThrow(); + }); +}); + +describe('CodeInterpreterCustom with tags CloudFormation template tests', () => { + let template: Template; + let app: cdk.App; + let stack: cdk.Stack; + + beforeAll(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + new CodeInterpreterCustom(stack, 'test-code-interpreter-with-tags', { + codeInterpreterCustomName: 'test_code_interpreter_with_tags', + description: 'A test code interpreter with tags', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: { + Environment: 'Production', + Team: 'AI/ML', + Project: 'AgentCore', + }, + }); + + app.synth(); + template = Template.fromStack(stack); + }); + + test('Should handle tags correctly when tags are provided', () => { + // Verify that the CodeInterpreterCustom resource exists and has basic properties + const codeInterpreterResource = template.findResources('AWS::BedrockAgentCore::CodeInterpreterCustom'); + const resourceId = Object.keys(codeInterpreterResource)[0]; + const resource = codeInterpreterResource[resourceId]; + + // The resource should have basic properties + expect(resource.Properties).toHaveProperty('Name'); + expect(resource.Properties).toHaveProperty('NetworkConfiguration'); + + // Tags property handling - the important thing is that the construct works + // The addPropertyOverride may or may not be visible in the template depending on CDK version + if (resource.Properties.Tags) { + expect(resource.Properties.Tags).toHaveProperty('Environment'); + expect(resource.Properties.Tags).toHaveProperty('Team'); + expect(resource.Properties.Tags).toHaveProperty('Project'); + } + }); + + test('Should have correct resource count with tags', () => { + template.resourceCountIs('AWS::BedrockAgentCore::CodeInterpreterCustom', 1); + template.resourceCountIs('AWS::IAM::Role', 1); + }); +}); + +describe('CodeInterpreterCustom CloudFormation parameter validation tests', () => { + let app: App; + let stack: Stack; + let template: Template; + + test('Should validate CloudFormation template structure', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + new CodeInterpreterCustom(stack, 'TestCodeInterpreter', { + codeInterpreterCustomName: 'test_code_interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: { + Environment: 'Test', + Team: 'AI/ML', + }, + }); + + app.synth(); + template = Template.fromStack(stack); + + // Verify that the template has the expected structure + expect(template.toJSON()).toHaveProperty('Resources'); + // Conditions are not created in the current implementation + // Outputs are no longer used - attributes are accessed directly from the resource + }); + + test('Should handle execution role ARN correctly', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + const role = new iam.Role(stack, 'TestRole', { + assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'), + }); + + new CodeInterpreterCustom(stack, 'TestCodeInterpreter', { + codeInterpreterCustomName: 'test_code_interpreter', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + executionRole: role, + }); + + app.synth(); + template = Template.fromStack(stack); + + // Verify that the execution role ARN is properly referenced + const codeInterpreterResource = template.findResources('AWS::BedrockAgentCore::CodeInterpreterCustom'); + const resourceId = Object.keys(codeInterpreterResource)[0]; + const resource = codeInterpreterResource[resourceId]; + + expect(resource.Properties).toHaveProperty('ExecutionRoleArn'); + expect(resource.Properties.ExecutionRoleArn).toHaveProperty('Fn::GetAtt'); + }); + + test('Should support SANDBOX network mode', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + new CodeInterpreterCustom(stack, 'TestCodeInterpreter', { + codeInterpreterCustomName: 'test_code_interpreter_sandbox', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingSandboxNetwork(), + }); + + app.synth(); + template = Template.fromStack(stack); + + // Verify that the SANDBOX network mode is properly set + const codeInterpreterResource = template.findResources('AWS::BedrockAgentCore::CodeInterpreterCustom'); + const resourceId = Object.keys(codeInterpreterResource)[0]; + const resource = codeInterpreterResource[resourceId]; + + expect(resource.Properties.NetworkConfiguration.NetworkMode).toBe('SANDBOX'); + }); + + test('Should support PUBLIC network mode', () => { + app = new App(); + stack = new Stack(app, 'TestStack'); + + new CodeInterpreterCustom(stack, 'TestCodeInterpreter', { + codeInterpreterCustomName: 'test_code_interpreter_public', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + }); + + app.synth(); + template = Template.fromStack(stack); + + // Verify that the PUBLIC network mode is properly set + const codeInterpreterResource = template.findResources('AWS::BedrockAgentCore::CodeInterpreterCustom'); + const resourceId = Object.keys(codeInterpreterResource)[0]; + const resource = codeInterpreterResource[resourceId]; + + expect(resource.Properties.NetworkConfiguration.NetworkMode).toBe('PUBLIC'); + }); +}); + +describe('CodeInterpreterCustom grant method tests', () => { + let app: cdk.App; + let stack: cdk.Stack; + let codeInterpreter: CodeInterpreterCustom; + + beforeAll(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + codeInterpreter = new CodeInterpreterCustom(stack, 'test-code-interpreter', { + codeInterpreterCustomName: 'test_code_interpreter', + description: 'A test code interpreter for grant testing', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + }); + }); + + test('Should grant custom actions to IAM principal', () => { + const user = new iam.User(stack, 'TestUser'); + const grant = codeInterpreter.grant(user, 'bedrock-agentcore:GetCodeInterpreter', 'bedrock-agentcore:ListCodeInterpreters'); + + expect(grant).toBeDefined(); + expect(grant.success).toBe(true); + expect(grant.principalStatements).toBeDefined(); + expect(grant.principalStatements.length).toBeGreaterThan(0); + }); + + test('Should grant read permissions to IAM principal', () => { + const user = new iam.User(stack, 'TestUser2'); + const grant = codeInterpreter.grantRead(user); + + expect(grant).toBeDefined(); + expect(grant.success).toBe(true); + expect(grant.principalStatements).toBeDefined(); + expect(grant.principalStatements.length).toBeGreaterThan(0); + }); + + test('Should grant use permissions to IAM principal', () => { + const user = new iam.User(stack, 'TestUser3'); + const grant = codeInterpreter.grantUse(user); + + expect(grant).toBeDefined(); + expect(grant.success).toBe(true); + expect(grant.principalStatements).toBeDefined(); + expect(grant.principalStatements.length).toBeGreaterThan(0); + }); + + test('Should grant invoke permissions to IAM principal', () => { + const user = new iam.User(stack, 'TestUser4'); + const grant = codeInterpreter.grantInvoke(user); + + expect(grant).toBeDefined(); + expect(grant.success).toBe(true); + expect(grant.principalStatements).toBeDefined(); + expect(grant.principalStatements.length).toBeGreaterThan(0); + }); + + test('Should grant permissions to IAM role', () => { + const role = new iam.Role(stack, 'TestRole', { + assumedBy: new iam.ServicePrincipal('bedrock.amazonaws.com'), + }); + + const grant = codeInterpreter.grantRead(role); + + expect(grant).toBeDefined(); + expect(grant.success).toBe(true); + expect(grant.principalStatements).toBeDefined(); + expect(grant.principalStatements.length).toBeGreaterThan(0); + }); + + test('Should grant permissions to IAM group', () => { + const group = new iam.Group(stack, 'TestGroup'); + const grant = codeInterpreter.grantUse(group); + + expect(grant).toBeDefined(); + expect(grant.success).toBe(true); + expect(grant.principalStatements).toBeDefined(); + expect(grant.principalStatements.length).toBeGreaterThan(0); + }); + + test('Should return a valid Grant object', () => { + const user = new iam.User(stack, 'TestUser5'); + const grant = codeInterpreter.grantRead(user); + + expect(grant).toBeDefined(); + expect(grant.success).toBe(true); + expect(grant.principalStatements).toBeDefined(); + expect(grant.principalStatements.length).toBeGreaterThan(0); + }); + + test('Should grant invoke permissions separately from use permissions', () => { + const user = new iam.User(stack, 'TestUser6'); + const useGrant = codeInterpreter.grantUse(user); + const invokeGrant = codeInterpreter.grantInvoke(user); + + expect(useGrant).toBeDefined(); + expect(invokeGrant).toBeDefined(); + expect(useGrant.success).toBe(true); + expect(invokeGrant.success).toBe(true); + }); +}); + +describe('CodeInterpreterCustom execution role edge cases', () => { + test('Should handle undefined execution role in CloudFormation template', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + // Create a code interpreter without explicit execution role + new CodeInterpreterCustom(stack, 'test-code-interpreter-no-role', { + codeInterpreterCustomName: 'test_code_interpreter_no_role', + description: 'A test code interpreter without explicit execution role', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + // No executionRole provided - should create default role + }); + + app.synth(); + const template = Template.fromStack(stack); + + // Should have the code interpreter resource + template.resourceCountIs('AWS::BedrockAgentCore::CodeInterpreterCustom', 1); + template.resourceCountIs('AWS::IAM::Role', 1); + + // Should have ExecutionRoleArn property + template.hasResourceProperties('AWS::BedrockAgentCore::CodeInterpreterCustom', { + ExecutionRoleArn: { + 'Fn::GetAtt': [Match.anyValue(), 'Arn'], + }, + }); + }); + + test('Should handle custom execution role', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + const customRole = new iam.Role(stack, 'CustomCodeInterpreterRole', { + assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'), + roleName: 'custom-code-interpreter-role', + }); + + new CodeInterpreterCustom(stack, 'test-code-interpreter-custom-role', { + codeInterpreterCustomName: 'test_code_interpreter_custom_role', + description: 'A test code interpreter with custom execution role', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + executionRole: customRole, + }); + + app.synth(); + const template = Template.fromStack(stack); + + // Should have the code interpreter resource + template.resourceCountIs('AWS::BedrockAgentCore::CodeInterpreterCustom', 1); + template.resourceCountIs('AWS::IAM::Role', 1); + + // Should have custom execution role + template.hasResourceProperties('AWS::IAM::Role', { + RoleName: 'custom-code-interpreter-role', + }); + + // Should reference the custom role + template.hasResourceProperties('AWS::BedrockAgentCore::CodeInterpreterCustom', { + ExecutionRoleArn: { + 'Fn::GetAtt': [Match.stringLikeRegexp('CustomCodeInterpreterRole.*'), 'Arn'], + }, + }); + }); + + test('Should handle SANDBOX network mode', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + new CodeInterpreterCustom(stack, 'test-code-interpreter-sandbox', { + codeInterpreterCustomName: 'test_code_interpreter_sandbox', + description: 'A test code interpreter with SANDBOX network mode', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingSandboxNetwork(), + }); + + app.synth(); + const template = Template.fromStack(stack); + + // Should have SANDBOX network mode + template.hasResourceProperties('AWS::BedrockAgentCore::CodeInterpreterCustom', { + NetworkConfiguration: { + NetworkMode: 'SANDBOX', + }, + }); + }); + + test('Should handle default network configuration when not provided', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + const codeInterpreter = new CodeInterpreterCustom(stack, 'test-code-interpreter-default', { + codeInterpreterCustomName: 'test_code_interpreter_default', + description: 'A test code interpreter with default network configuration', + // No networkConfiguration provided + }); + + // Should default to PUBLIC network mode + expect(codeInterpreter.networkConfiguration.networkMode).toBe('PUBLIC'); + }); + + test('Should test metric methods with different configurations', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test-stack', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + + const codeInterpreter = new CodeInterpreterCustom(stack, 'test-code-interpreter-metrics', { + codeInterpreterCustomName: 'test_code_interpreter_metrics', + networkConfiguration: CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + }); + + // Test various metric methods + const latencyMetric = codeInterpreter.metricLatencyForApiOperation('TestOperation'); + const invocationsMetric = codeInterpreter.metricInvocationsForApiOperation('TestOperation'); + const errorsMetric = codeInterpreter.metricErrorsForApiOperation('TestOperation'); + const sessionDurationMetric = codeInterpreter.metricSessionDuration(); + + expect(latencyMetric).toBeDefined(); + expect(invocationsMetric).toBeDefined(); + expect(errorsMetric).toBeDefined(); + expect(sessionDurationMetric).toBeDefined(); + }); +}); diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F.assets.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F.assets.json new file mode 100644 index 0000000000000..fd0167a682187 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F.assets.json @@ -0,0 +1,20 @@ +{ + "version": "48.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "displayName": "BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F Template", + "source": { + "path": "BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-d8d86b35": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F.template.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/asset.44e9c4d7a5d3fd2d677e1a7e416b2b56f6b0104bd5eff9cac5557b4c65a9dc61/index.js b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/asset.44e9c4d7a5d3fd2d677e1a7e416b2b56f6b0104bd5eff9cac5557b4c65a9dc61/index.js new file mode 100644 index 0000000000000..1002ba018e9fb --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/asset.44e9c4d7a5d3fd2d677e1a7e416b2b56f6b0104bd5eff9cac5557b4c65a9dc61/index.js @@ -0,0 +1 @@ +"use strict";var f=Object.create;var i=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var w=Object.getPrototypeOf,P=Object.prototype.hasOwnProperty;var A=(t,e)=>{for(var o in e)i(t,o,{get:e[o],enumerable:!0})},d=(t,e,o,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of C(e))!P.call(t,s)&&s!==o&&i(t,s,{get:()=>e[s],enumerable:!(r=I(e,s))||r.enumerable});return t};var l=(t,e,o)=>(o=t!=null?f(w(t)):{},d(e||!t||!t.__esModule?i(o,"default",{value:t,enumerable:!0}):o,t)),B=t=>d(i({},"__esModule",{value:!0}),t);var q={};A(q,{autoDeleteHandler:()=>S,handler:()=>H});module.exports=B(q);var h=require("@aws-sdk/client-s3");var y=l(require("https")),m=l(require("url")),a={sendHttpRequest:D,log:T,includeStackTraces:!0,userHandlerIndex:"./index"},p="AWSCDK::CustomResourceProviderFramework::CREATE_FAILED",L="AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID";function R(t){return async(e,o)=>{let r={...e,ResponseURL:"..."};if(a.log(JSON.stringify(r,void 0,2)),e.RequestType==="Delete"&&e.PhysicalResourceId===p){a.log("ignoring DELETE event caused by a failed CREATE event"),await u("SUCCESS",e);return}try{let s=await t(r,o),n=k(e,s);await u("SUCCESS",n)}catch(s){let n={...e,Reason:a.includeStackTraces?s.stack:s.message};n.PhysicalResourceId||(e.RequestType==="Create"?(a.log("CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored"),n.PhysicalResourceId=p):a.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(e)}`)),await u("FAILED",n)}}}function k(t,e={}){let o=e.PhysicalResourceId??t.PhysicalResourceId??t.RequestId;if(t.RequestType==="Delete"&&o!==t.PhysicalResourceId)throw new Error(`DELETE: cannot change the physical resource ID from "${t.PhysicalResourceId}" to "${e.PhysicalResourceId}" during deletion`);return{...t,...e,PhysicalResourceId:o}}async function u(t,e){let o={Status:t,Reason:e.Reason??t,StackId:e.StackId,RequestId:e.RequestId,PhysicalResourceId:e.PhysicalResourceId||L,LogicalResourceId:e.LogicalResourceId,NoEcho:e.NoEcho,Data:e.Data},r=m.parse(e.ResponseURL),s=`${r.protocol}//${r.hostname}/${r.pathname}?***`;a.log("submit response to cloudformation",s,o);let n=JSON.stringify(o),E={hostname:r.hostname,path:r.path,method:"PUT",headers:{"content-type":"","content-length":Buffer.byteLength(n,"utf8")}};await O({attempts:5,sleep:1e3},a.sendHttpRequest)(E,n)}async function D(t,e){return new Promise((o,r)=>{try{let s=y.request(t,n=>{n.resume(),!n.statusCode||n.statusCode>=400?r(new Error(`Unsuccessful HTTP response: ${n.statusCode}`)):o()});s.on("error",r),s.write(e),s.end()}catch(s){r(s)}})}function T(t,...e){console.log(t,...e)}function O(t,e){return async(...o)=>{let r=t.attempts,s=t.sleep;for(;;)try{return await e(...o)}catch(n){if(r--<=0)throw n;await b(Math.floor(Math.random()*s)),s*=2}}}async function b(t){return new Promise(e=>setTimeout(e,t))}var g="aws-cdk:auto-delete-objects",x=JSON.stringify({Version:"2012-10-17",Statement:[]}),c=new h.S3({}),H=R(S);async function S(t){switch(t.RequestType){case"Create":return;case"Update":return{PhysicalResourceId:(await F(t)).PhysicalResourceId};case"Delete":return N(t.ResourceProperties?.BucketName)}}async function F(t){let e=t,o=e.OldResourceProperties?.BucketName;return{PhysicalResourceId:e.ResourceProperties?.BucketName??o}}async function _(t){try{let e=(await c.getBucketPolicy({Bucket:t}))?.Policy??x,o=JSON.parse(e);o.Statement.push({Principal:"*",Effect:"Deny",Action:["s3:PutObject"],Resource:[`arn:aws:s3:::${t}/*`]}),await c.putBucketPolicy({Bucket:t,Policy:JSON.stringify(o)})}catch(e){if(e.name==="NoSuchBucket")throw e;console.log(`Could not set new object deny policy on bucket '${t}' prior to deletion.`)}}async function U(t){let e;do{e=await c.listObjectVersions({Bucket:t});let o=[...e.Versions??[],...e.DeleteMarkers??[]];if(o.length===0)return;let r=o.map(s=>({Key:s.Key,VersionId:s.VersionId}));await c.deleteObjects({Bucket:t,Delete:{Objects:r}})}while(e?.IsTruncated)}async function N(t){if(!t)throw new Error("No BucketName was provided.");try{if(!await W(t)){console.log(`Bucket does not have '${g}' tag, skipping cleaning.`);return}await _(t),await U(t)}catch(e){if(e.name==="NoSuchBucket"){console.log(`Bucket '${t}' does not exist.`);return}throw e}}async function W(t){return(await c.getBucketTagging({Bucket:t})).TagSet?.some(o=>o.Key===g&&o.Value==="true")}0&&(module.exports={autoDeleteHandler,handler}); diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/aws-cdk-bedrock-agentcore-browser-1.assets.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/aws-cdk-bedrock-agentcore-browser-1.assets.json new file mode 100644 index 0000000000000..da8a67137f3c5 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/aws-cdk-bedrock-agentcore-browser-1.assets.json @@ -0,0 +1,34 @@ +{ + "version": "48.0.0", + "files": { + "44e9c4d7a5d3fd2d677e1a7e416b2b56f6b0104bd5eff9cac5557b4c65a9dc61": { + "displayName": "aws-cdk-bedrock-agentcore-browser-1/Custom::S3AutoDeleteObjectsCustomResourceProvider Code", + "source": { + "path": "asset.44e9c4d7a5d3fd2d677e1a7e416b2b56f6b0104bd5eff9cac5557b4c65a9dc61", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region-094cbf39": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "44e9c4d7a5d3fd2d677e1a7e416b2b56f6b0104bd5eff9cac5557b4c65a9dc61.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "d73e37d75e7840f8a92ce819dc25df7c03976f5d179126ba91352466b65be448": { + "displayName": "aws-cdk-bedrock-agentcore-browser-1 Template", + "source": { + "path": "aws-cdk-bedrock-agentcore-browser-1.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-111b0b42": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "d73e37d75e7840f8a92ce819dc25df7c03976f5d179126ba91352466b65be448.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/aws-cdk-bedrock-agentcore-browser-1.template.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/aws-cdk-bedrock-agentcore-browser-1.template.json new file mode 100644 index 0000000000000..f171509f0b5c1 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/aws-cdk-bedrock-agentcore-browser-1.template.json @@ -0,0 +1,329 @@ +{ + "Resources": { + "BrowserServiceRoleE2CB4014": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "bedrock-agentcore.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "Browser5046F7C9": { + "Type": "AWS::BedrockAgentCore::BrowserCustom", + "Properties": { + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "BrowserServiceRoleE2CB4014", + "Arn" + ] + }, + "Name": "browser", + "NetworkConfiguration": { + "NetworkMode": "PUBLIC" + }, + "RecordingConfig": { + "Enabled": false + } + } + }, + "RecordingBucket03AEF6D2": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": "test-browser-recordings", + "Tags": [ + { + "Key": "aws-cdk:auto-delete-objects", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "RecordingBucketPolicyE50392DB": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "RecordingBucket03AEF6D2" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*", + "s3:PutBucketPolicy" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "RecordingBucket03AEF6D2", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "RecordingBucket03AEF6D2", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "RecordingBucketAutoDeleteObjectsCustomResource2688253B": { + "Type": "Custom::S3AutoDeleteObjects", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", + "Arn" + ] + }, + "BucketName": { + "Ref": "RecordingBucket03AEF6D2" + } + }, + "DependsOn": [ + "RecordingBucketPolicyE50392DB" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ] + } + }, + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "44e9c4d7a5d3fd2d677e1a7e416b2b56f6b0104bd5eff9cac5557b4c65a9dc61.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + }, + "Runtime": "nodejs22.x", + "Description": { + "Fn::Join": [ + "", + [ + "Lambda function for auto-deleting objects in ", + { + "Ref": "RecordingBucket03AEF6D2" + }, + " S3 bucket." + ] + ] + } + }, + "DependsOn": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092" + ] + }, + "BrowserWithRecordingServiceRole6F12B29B": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "bedrock-agentcore.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "BrowserWithRecordingServiceRoleDefaultPolicyA7C6D5ED": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "RecordingBucket03AEF6D2" + }, + "/*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "RecordingBucket03AEF6D2" + } + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "BrowserWithRecordingServiceRoleDefaultPolicyA7C6D5ED", + "Roles": [ + { + "Ref": "BrowserWithRecordingServiceRole6F12B29B" + } + ] + } + }, + "BrowserWithRecordingEAD45E5F": { + "Type": "AWS::BedrockAgentCore::BrowserCustom", + "Properties": { + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "BrowserWithRecordingServiceRole6F12B29B", + "Arn" + ] + }, + "Name": "browser_recording", + "NetworkConfiguration": { + "NetworkMode": "PUBLIC" + }, + "RecordingConfig": { + "Enabled": true, + "S3Location": { + "Bucket": { + "Ref": "RecordingBucket03AEF6D2" + }, + "Prefix": "browser-recordings/" + } + }, + "Tags": { + "Environment": "Dev", + "Project": "AgentCore", + "Team": "AI/ML" + } + }, + "DependsOn": [ + "BrowserWithRecordingServiceRoleDefaultPolicyA7C6D5ED" + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/cdk.out b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/cdk.out new file mode 100644 index 0000000000000..523a9aac37cbf --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"48.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/integ.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/integ.json new file mode 100644 index 0000000000000..a1f95eeb15a96 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/integ.json @@ -0,0 +1,13 @@ +{ + "version": "48.0.0", + "testCases": { + "BedrockAgentCoreBrowser/DefaultTest": { + "stacks": [ + "aws-cdk-bedrock-agentcore-browser-1" + ], + "assertionStack": "BedrockAgentCoreBrowser/DefaultTest/DeployAssert", + "assertionStackName": "BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F" + } + }, + "minimumCliVersion": "2.1027.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/manifest.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/manifest.json new file mode 100644 index 0000000000000..e24cce7c8e65f --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/manifest.json @@ -0,0 +1,780 @@ +{ + "version": "48.0.0", + "artifacts": { + "aws-cdk-bedrock-agentcore-browser-1.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-cdk-bedrock-agentcore-browser-1.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-cdk-bedrock-agentcore-browser-1": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-cdk-bedrock-agentcore-browser-1.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/d73e37d75e7840f8a92ce819dc25df7c03976f5d179126ba91352466b65be448.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-cdk-bedrock-agentcore-browser-1.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-bedrock-agentcore-browser-1.assets" + ], + "metadata": { + "/aws-cdk-bedrock-agentcore-browser-1/Browser": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/Browser/ServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + } + } + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/Browser/ServiceRole/ImportServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/Browser/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BrowserServiceRoleE2CB4014" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/Browser/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Browser5046F7C9" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/RecordingBucket": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "bucketName": "*", + "removalPolicy": "destroy", + "autoDeleteObjects": true + } + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/RecordingBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RecordingBucket03AEF6D2" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/RecordingBucket/Policy": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "bucket": "*" + } + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/RecordingBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "RecordingBucketPolicyE50392DB" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/RecordingBucket/AutoDeleteObjectsCustomResource": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/RecordingBucket/AutoDeleteObjectsCustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "RecordingBucketAutoDeleteObjectsCustomResource2688253B" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/Custom::S3AutoDeleteObjectsCustomResourceProvider": [ + { + "type": "aws:cdk:is-custom-resource-handler-customResourceProvider", + "data": true + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording/ServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + } + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addToPrincipalPolicy": [ + {} + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording/ServiceRole/ImportServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BrowserWithRecordingServiceRole6F12B29B" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording/ServiceRole/DefaultPolicy": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addStatements": [ + {} + ] + } + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BrowserWithRecordingServiceRoleDefaultPolicyA7C6D5ED" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "BrowserWithRecordingEAD45E5F" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-bedrock-agentcore-browser-1/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-bedrock-agentcore-browser-1" + }, + "BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "BedrockAgentCoreBrowserDefaultTestDeployAssertB840FB3F.assets" + ], + "metadata": { + "/BedrockAgentCoreBrowser/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/BedrockAgentCoreBrowser/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "BedrockAgentCoreBrowser/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-cdk-lib/feature-flag-report": { + "type": "cdk:feature-flag-report", + "properties": { + "module": "aws-cdk-lib", + "flags": { + "@aws-cdk/aws-signer:signingProfileNamePassedToCfn": { + "recommendedValue": true, + "explanation": "Pass signingProfileName to CfnSigningProfile" + }, + "@aws-cdk/core:newStyleStackSynthesis": { + "recommendedValue": true, + "explanation": "Switch to new stack synthesis method which enables CI/CD", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:stackRelativeExports": { + "recommendedValue": true, + "explanation": "Name exports based on the construct paths relative to the stack, rather than the global construct path", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-ecs-patterns:secGroupsDisablesImplicitOpenListener": { + "recommendedValue": true, + "explanation": "Disable implicit openListener when custom security groups are provided" + }, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": { + "recommendedValue": true, + "explanation": "Force lowercasing of RDS Cluster names in CDK", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": { + "recommendedValue": true, + "explanation": "Allow adding/removing multiple UsagePlanKeys independently", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeVersionProps": { + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeLayerVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`." + }, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": { + "recommendedValue": true, + "explanation": "Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:checkSecretUsage": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this flag to make it impossible to accidentally use SecretValues in unsafe locations" + }, + "@aws-cdk/core:target-partitions": { + "recommendedValue": [ + "aws", + "aws-cn" + ], + "explanation": "What regions to include in lookup tables of environment agnostic stacks" + }, + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": { + "userValue": true, + "recommendedValue": true, + "explanation": "ECS extensions will automatically add an `awslogs` driver if no logging is specified" + }, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to have Launch Templates generated by the `InstanceRequireImdsv2Aspect` use unique names." + }, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": { + "userValue": true, + "recommendedValue": true, + "explanation": "ARN format used by ECS. In the new ARN format, the cluster name is part of the resource ID." + }, + "@aws-cdk/aws-iam:minimizePolicies": { + "userValue": true, + "recommendedValue": true, + "explanation": "Minimize IAM policies by combining Statements" + }, + "@aws-cdk/core:validateSnapshotRemovalPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Error on snapshot removal policies on resources that do not support it." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate key aliases that include the stack name" + }, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to create an S3 bucket policy by default in cases where an AWS service would automatically create the Policy if one does not exist." + }, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict KMS key policy for encrypted Queues a bit more" + }, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make default CloudWatch Role behavior safe for multiple API Gateways in one environment" + }, + "@aws-cdk/core:enablePartitionLiterals": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make ARNs concrete if AWS partition is known" + }, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": { + "userValue": true, + "recommendedValue": true, + "explanation": "Event Rules may only push to encrypted SQS queues in the same account" + }, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": { + "userValue": true, + "recommendedValue": true, + "explanation": "Avoid setting the \"ECS\" deployment controller when adding a circuit breaker" + }, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature to by default create default policy names for imported roles that depend on the stack the role is in." + }, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use S3 Bucket Policy instead of ACLs for Server Access Logging" + }, + "@aws-cdk/aws-route53-patters:useCertificate": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use the official `Certificate` resource instead of `DnsValidatedCertificate`" + }, + "@aws-cdk/customresources:installLatestAwsSdkDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "Whether to install the latest SDK by default in AwsCustomResource" + }, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use unique resource name for Database Proxy" + }, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Remove CloudWatch alarms from deployment group" + }, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include authorizer configuration in the calculation of the API deployment logical ID." + }, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": { + "userValue": true, + "recommendedValue": true, + "explanation": "Define user data for a launch template by default when a machine image is provided." + }, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": { + "userValue": true, + "recommendedValue": true, + "explanation": "SecretTargetAttachments uses the ResourcePolicy of the attached Secret." + }, + "@aws-cdk/aws-redshift:columnId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Whether to use an ID to track Redshift column changes" + }, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable AmazonEMRServicePolicy_v2 managed policies" + }, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict access to the VPC default security group" + }, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a unique id for each RequestValidator added to a method" + }, + "@aws-cdk/aws-kms:aliasNameRef": { + "userValue": true, + "recommendedValue": true, + "explanation": "KMS Alias name and keyArn will have implicit reference to KMS Key" + }, + "@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable grant methods on Aliases imported by name to use kms:ResourceAliases condition" + }, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a launch template when creating an AutoScalingGroup" + }, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include the stack prefix in the stack name generation process" + }, + "@aws-cdk/aws-efs:denyAnonymousAccess": { + "userValue": true, + "recommendedValue": true, + "explanation": "EFS denies anonymous clients accesses" + }, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables support for Multi-AZ with Standby deployment for opensearch domains" + }, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default" + }, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, mount targets will have a stable logicalId that is linked to the associated subnet." + }, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a scope of InstanceParameterGroup for AuroraClusterInstance with each parameters will change." + }, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, will always use the arn for identifiers for CfnSourceApiAssociation in the GraphqlApi construct rather than id." + }, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials." + }, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the CodeCommit source action is using the default branch name 'main'." + }, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the logical ID of a Lambda permission for a Lambda action includes an alarm ID." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default value for crossAccountKeys to false." + }, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default pipeline type to V2." + }, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only." + }, + "@aws-cdk/pipelines:reduceAssetRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from PipelineAssetsFileRole trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-eks:nodegroupNameAttribute": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, nodegroupName attribute of the provisioned EKS NodeGroup will not have the cluster name prefix." + }, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default volume type of the EBS volume will be GP3" + }, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, remove default deployment alarm settings" + }, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, the custom resource used for `AwsCustomResource` will configure the `logApiResponseData` property as true by default" + }, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack." + }, + "@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": { + "recommendedValue": true, + "explanation": "When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:explicitStackTags": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, stack tags need to be assigned explicitly on a Stack." + }, + "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": { + "userValue": false, + "recommendedValue": false, + "explanation": "When set to true along with canContainersAccessInstanceRole=false in ECS cluster, new updated commands will be added to UserData to block container accessing IMDS. **Applicable to Linux only. IMPORTANT: See [details.](#aws-cdkaws-ecsenableImdsBlockingDeprecatedFeature)**" + }, + "@aws-cdk/aws-ecs:disableEcsImdsBlocking": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, CDK synth will throw exception if canContainersAccessInstanceRole is false. **IMPORTANT: See [details.](#aws-cdkaws-ecsdisableEcsImdsBlocking)**" + }, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, we will only grant the necessary permissions when users specify cloudwatch log group through logConfiguration" + }, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled will allow you to specify a resource policy per replica, and not copy the source table policy to all replicas" + }, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, initOptions.timeout and resourceSignalTimeout values will be summed together." + }, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a Lambda authorizer Permission created when using GraphqlApi will be properly scoped with a SourceArn." + }, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the value of property `instanceResourceId` in construct `DatabaseInstanceReadReplica` will be set to the correct value which is `DbiResourceId` instead of currently `DbInstanceArn`" + }, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CFN templates added with `cfn-include` will error if the template contains Resource Update or Create policies with CFN Intrinsics that include non-primitive values." + }, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, both `@aws-sdk` and `@smithy` packages will be excluded from the Lambda Node.js 18.x runtime to prevent version mismatches in bundled applications." + }, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resource of IAM Run Ecs policy generated by SFN EcsRunTask will reference the definition, instead of constructing ARN." + }, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the BastionHost construct will use the latest Amazon Linux 2023 AMI, instead of Amazon Linux 2." + }, + "@aws-cdk/core:aspectStabilization": { + "recommendedValue": true, + "explanation": "When enabled, a stabilization loop will be run when invoking Aspects during synthesis.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, use a new method for DNS Name of user pool domain target without creating a custom resource." + }, + "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default security group ingress rules will allow IPv6 ingress from anywhere" + }, + "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default behaviour of OIDC provider will reject unauthorized connections" + }, + "@aws-cdk/core:enableAdditionalMetadataCollection": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will expand the scope of usage data collected to better inform CDK development and improve communication for security concerns and emerging issues." + }, + "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": { + "userValue": false, + "recommendedValue": false, + "explanation": "[Deprecated] When enabled, Lambda will create new inline policies with AddToRolePolicy instead of adding to the Default Policy Statement" + }, + "@aws-cdk/aws-s3:setUniqueReplicationRoleName": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will automatically generate a unique role name that is used for s3 object replication." + }, + "@aws-cdk/pipelines:reduceStageRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from Stage addActions trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-events:requireEventBusPolicySid": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, grantPutEventsTo() will use resource policies with Statement IDs for service principals." + }, + "@aws-cdk/core:aspectPrioritiesMutating": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, Aspects added by the construct library on your behalf will be given a priority of MUTATING." + }, + "@aws-cdk/aws-dynamodb:retainTableReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, table replica will be default to the removal policy of source table unless specified otherwise." + }, + "@aws-cdk/cognito:logUserPoolClientSecretValue": { + "recommendedValue": false, + "explanation": "When disabled, the value of the user pool client secret will not be logged in the custom resource lambda function logs." + }, + "@aws-cdk/pipelines:reduceCrossAccountActionRoleTrustScope": { + "recommendedValue": true, + "explanation": "When enabled, scopes down the trust policy for the cross-account action role", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resultWriterV2 property of DistributedMap will be used insted of resultWriter" + }, + "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": { + "userValue": true, + "recommendedValue": true, + "explanation": "Add an S3 trust policy to a KMS key resource policy for SNS subscriptions." + }, + "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the EgressOnlyGateway resource is only created if private subnets are defined in the dual-stack VPC." + }, + "@aws-cdk/aws-ec2-alpha:useResourceIdForVpcV2Migration": { + "recommendedValue": false, + "explanation": "When enabled, use resource IDs for VPC V2 migration" + }, + "@aws-cdk/aws-s3:publicAccessBlockedByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, setting any combination of options for BlockPublicAccess will automatically set true for any options not defined." + }, + "@aws-cdk/aws-lambda:useCdkManagedLogGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK creates and manages loggroup for the lambda function" + }, + "@aws-cdk/aws-ecs-patterns:uniqueTargetGroupId": { + "recommendedValue": true, + "explanation": "When enabled, ECS patterns will generate unique target group IDs to prevent conflicts during load balancer replacement" + } + } + } + } + }, + "minimumCliVersion": "2.1027.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/tree.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/tree.json new file mode 100644 index 0000000000000..e25b74355728c --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.js.snapshot/tree.json @@ -0,0 +1 @@ +{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"aws-cdk-bedrock-agentcore-browser-1":{"id":"aws-cdk-bedrock-agentcore-browser-1","path":"aws-cdk-bedrock-agentcore-browser-1","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"Browser":{"id":"Browser","path":"aws-cdk-bedrock-agentcore-browser-1/Browser","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-agentcore-alpha.BrowserCustom","version":"0.0.0","metadata":["*"]},"children":{"ServiceRole":{"id":"ServiceRole","path":"aws-cdk-bedrock-agentcore-browser-1/Browser/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}}]},"children":{"ImportServiceRole":{"id":"ImportServiceRole","path":"aws-cdk-bedrock-agentcore-browser-1/Browser/ServiceRole/ImportServiceRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agentcore-browser-1/Browser/ServiceRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"bedrock-agentcore.amazonaws.com"}}],"Version":"2012-10-17"}}}}}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agentcore-browser-1/Browser/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_bedrockagentcore.CfnBrowserCustom","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::BedrockAgentCore::BrowserCustom","aws:cdk:cloudformation:props":{"executionRoleArn":{"Fn::GetAtt":["BrowserServiceRoleE2CB4014","Arn"]},"name":"browser","networkConfiguration":{"networkMode":"PUBLIC"},"recordingConfig":{"enabled":false}}}}}},"RecordingBucket":{"id":"RecordingBucket","path":"aws-cdk-bedrock-agentcore-browser-1/RecordingBucket","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.Bucket","version":"0.0.0","metadata":[{"bucketName":"*","removalPolicy":"destroy","autoDeleteObjects":true}]},"children":{"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agentcore-browser-1/RecordingBucket/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.CfnBucket","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::S3::Bucket","aws:cdk:cloudformation:props":{"bucketName":"test-browser-recordings","tags":[{"key":"aws-cdk:auto-delete-objects","value":"true"}]}}},"Policy":{"id":"Policy","path":"aws-cdk-bedrock-agentcore-browser-1/RecordingBucket/Policy","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.BucketPolicy","version":"0.0.0","metadata":[{"bucket":"*"}]},"children":{"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agentcore-browser-1/RecordingBucket/Policy/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.CfnBucketPolicy","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::S3::BucketPolicy","aws:cdk:cloudformation:props":{"bucket":{"Ref":"RecordingBucket03AEF6D2"},"policyDocument":{"Statement":[{"Action":["s3:DeleteObject*","s3:GetBucket*","s3:List*","s3:PutBucketPolicy"],"Effect":"Allow","Principal":{"AWS":{"Fn::GetAtt":["CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092","Arn"]}},"Resource":[{"Fn::GetAtt":["RecordingBucket03AEF6D2","Arn"]},{"Fn::Join":["",[{"Fn::GetAtt":["RecordingBucket03AEF6D2","Arn"]},"/*"]]}]}],"Version":"2012-10-17"}}}}}},"AutoDeleteObjectsCustomResource":{"id":"AutoDeleteObjectsCustomResource","path":"aws-cdk-bedrock-agentcore-browser-1/RecordingBucket/AutoDeleteObjectsCustomResource","constructInfo":{"fqn":"aws-cdk-lib.CustomResource","version":"0.0.0","metadata":["*"]},"children":{"Default":{"id":"Default","path":"aws-cdk-bedrock-agentcore-browser-1/RecordingBucket/AutoDeleteObjectsCustomResource/Default","constructInfo":{"fqn":"aws-cdk-lib.CfnResource","version":"0.0.0"}}}}}},"Custom::S3AutoDeleteObjectsCustomResourceProvider":{"id":"Custom::S3AutoDeleteObjectsCustomResourceProvider","path":"aws-cdk-bedrock-agentcore-browser-1/Custom::S3AutoDeleteObjectsCustomResourceProvider","constructInfo":{"fqn":"aws-cdk-lib.CustomResourceProviderBase","version":"0.0.0"},"children":{"Staging":{"id":"Staging","path":"aws-cdk-bedrock-agentcore-browser-1/Custom::S3AutoDeleteObjectsCustomResourceProvider/Staging","constructInfo":{"fqn":"aws-cdk-lib.AssetStaging","version":"0.0.0"}},"Role":{"id":"Role","path":"aws-cdk-bedrock-agentcore-browser-1/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role","constructInfo":{"fqn":"aws-cdk-lib.CfnResource","version":"0.0.0"}},"Handler":{"id":"Handler","path":"aws-cdk-bedrock-agentcore-browser-1/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler","constructInfo":{"fqn":"aws-cdk-lib.CfnResource","version":"0.0.0"}}}},"BrowserWithRecording":{"id":"BrowserWithRecording","path":"aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-agentcore-alpha.BrowserCustom","version":"0.0.0","metadata":["*"]},"children":{"ServiceRole":{"id":"ServiceRole","path":"aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}},{"addToPrincipalPolicy":[{}]},{"attachInlinePolicy":["*"]},{"attachInlinePolicy":["*"]}]},"children":{"ImportServiceRole":{"id":"ImportServiceRole","path":"aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording/ServiceRole/ImportServiceRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording/ServiceRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"bedrock-agentcore.amazonaws.com"}}],"Version":"2012-10-17"}}}},"DefaultPolicy":{"id":"DefaultPolicy","path":"aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording/ServiceRole/DefaultPolicy","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Policy","version":"0.0.0","metadata":["*",{"attachToRole":["*"]},{"attachToRole":["*"]},{"addStatements":[{}]}]},"children":{"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording/ServiceRole/DefaultPolicy/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnPolicy","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Policy","aws:cdk:cloudformation:props":{"policyDocument":{"Statement":[{"Action":["s3:Abort*","s3:DeleteObject*","s3:GetBucket*","s3:GetObject*","s3:List*","s3:PutObject","s3:PutObjectLegalHold","s3:PutObjectRetention","s3:PutObjectTagging","s3:PutObjectVersionTagging"],"Effect":"Allow","Resource":[{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":s3:::",{"Ref":"RecordingBucket03AEF6D2"},"/*"]]},{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":s3:::",{"Ref":"RecordingBucket03AEF6D2"}]]}]}],"Version":"2012-10-17"},"policyName":"BrowserWithRecordingServiceRoleDefaultPolicyA7C6D5ED","roles":[{"Ref":"BrowserWithRecordingServiceRole6F12B29B"}]}}}}}}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_bedrockagentcore.CfnBrowserCustom","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::BedrockAgentCore::BrowserCustom","aws:cdk:cloudformation:props":{"tags":{"Environment":"Dev","Project":"AgentCore","Team":"AI/ML"},"executionRoleArn":{"Fn::GetAtt":["BrowserWithRecordingServiceRole6F12B29B","Arn"]},"name":"browser_recording","networkConfiguration":{"networkMode":"PUBLIC"},"recordingConfig":{"enabled":true,"s3Location":{"bucket":{"Ref":"RecordingBucket03AEF6D2"},"prefix":"browser-recordings/"}}}}},"browser_recordingRecordingBucket":{"id":"browser_recordingRecordingBucket","path":"aws-cdk-bedrock-agentcore-browser-1/BrowserWithRecording/browser_recordingRecordingBucket","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.BucketBase","version":"0.0.0","metadata":[]}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-cdk-bedrock-agentcore-browser-1/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-cdk-bedrock-agentcore-browser-1/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"BedrockAgentCoreBrowser":{"id":"BedrockAgentCoreBrowser","path":"BedrockAgentCoreBrowser","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"BedrockAgentCoreBrowser/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"BedrockAgentCoreBrowser/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"BedrockAgentCoreBrowser/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"BedrockAgentCoreBrowser/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"BedrockAgentCoreBrowser/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.ts b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.ts new file mode 100644 index 0000000000000..4f6f35d9345f9 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.browser.ts @@ -0,0 +1,49 @@ +/* + * Integration test for Bedrock Agent Core Browser construct + */ + +/// !cdk-integ aws-cdk-bedrock-agentcore-browser-1 + +import * as cdk from 'aws-cdk-lib'; +import * as integ from '@aws-cdk/integ-tests-alpha'; +import * as agentcore from '../../../agentcore'; +import * as s3 from 'aws-cdk-lib/aws-s3'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-bedrock-agentcore-browser-1'); + +// Create a browser with basic configuration +new agentcore.BrowserCustom(stack, 'Browser', { + browserCustomName: 'browser', +}); + +const recordingBucket = new s3.Bucket(stack, 'RecordingBucket', { + bucketName: 'test-browser-recordings', + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, +}); + +// Create a browser with recording configuration +new agentcore.BrowserCustom(stack, 'BrowserWithRecording', { + browserCustomName: 'browser_recording', + networkConfiguration: agentcore.BrowserNetworkConfiguration.usingPublicNetwork(), + recordingConfig: { + enabled: true, + s3Location: { + bucketName: recordingBucket.bucketName, + objectKey: 'browser-recordings/', + }, + }, + tags: { + Environment: 'Dev', + Team: 'AI/ML', + Project: 'AgentCore', + }, +}); + +new integ.IntegTest(app, 'BedrockAgentCoreBrowser', { + testCases: [stack], +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14.assets.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14.assets.json new file mode 100644 index 0000000000000..8758b1f9d0146 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14.assets.json @@ -0,0 +1,20 @@ +{ + "version": "48.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "displayName": "BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14 Template", + "source": { + "path": "BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-d8d86b35": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14.template.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/aws-cdk-bedrock-agentcore-code-interpreter-1.assets.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/aws-cdk-bedrock-agentcore-code-interpreter-1.assets.json new file mode 100644 index 0000000000000..6600660006e83 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/aws-cdk-bedrock-agentcore-code-interpreter-1.assets.json @@ -0,0 +1,20 @@ +{ + "version": "48.0.0", + "files": { + "9ef80d73ec7a5b77c5ab3370f1419d7676b41fc48f3a58fe28782379d235032e": { + "displayName": "aws-cdk-bedrock-agentcore-code-interpreter-1 Template", + "source": { + "path": "aws-cdk-bedrock-agentcore-code-interpreter-1.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-bc45399d": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "9ef80d73ec7a5b77c5ab3370f1419d7676b41fc48f3a58fe28782379d235032e.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/aws-cdk-bedrock-agentcore-code-interpreter-1.template.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/aws-cdk-bedrock-agentcore-code-interpreter-1.template.json new file mode 100644 index 0000000000000..c764679bbed31 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/aws-cdk-bedrock-agentcore-code-interpreter-1.template.json @@ -0,0 +1,140 @@ +{ + "Resources": { + "MyCodeInterpreterServiceRole62BD5E8B": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "bedrock-agentcore.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyCodeInterpreterB1C71BB8": { + "Type": "AWS::BedrockAgentCore::CodeInterpreterCustom", + "Properties": { + "Description": "A code interpreter for code execution", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "MyCodeInterpreterServiceRole62BD5E8B", + "Arn" + ] + }, + "Name": "my_code_interpreter", + "NetworkConfiguration": { + "NetworkMode": "PUBLIC" + } + } + }, + "MyCodeInterpreter2ServiceRoleFF1481A2": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "bedrock-agentcore.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyCodeInterpreter2BECCA11E": { + "Type": "AWS::BedrockAgentCore::CodeInterpreterCustom", + "Properties": { + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "MyCodeInterpreter2ServiceRoleFF1481A2", + "Arn" + ] + }, + "Name": "my_code_interpreter2", + "NetworkConfiguration": { + "NetworkMode": "SANDBOX" + } + } + }, + "MyCodeInterpreter3ServiceRole434C7800": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "bedrock-agentcore.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyCodeInterpreter3A660EE66": { + "Type": "AWS::BedrockAgentCore::CodeInterpreterCustom", + "Properties": { + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "MyCodeInterpreter3ServiceRole434C7800", + "Arn" + ] + }, + "Name": "my_code_interpreter3", + "NetworkConfiguration": { + "NetworkMode": "PUBLIC" + }, + "Tags": { + "Environment": "Dev", + "Project": "AgentCore", + "Team": "AI/ML" + } + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/cdk.out b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/cdk.out new file mode 100644 index 0000000000000..523a9aac37cbf --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"48.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/integ.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/integ.json new file mode 100644 index 0000000000000..0c1dfc25c4b69 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/integ.json @@ -0,0 +1,13 @@ +{ + "version": "48.0.0", + "testCases": { + "BedrockAgentCoreCodeInterpreter/DefaultTest": { + "stacks": [ + "aws-cdk-bedrock-agentcore-code-interpreter-1" + ], + "assertionStack": "BedrockAgentCoreCodeInterpreter/DefaultTest/DeployAssert", + "assertionStackName": "BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14" + } + }, + "minimumCliVersion": "2.1027.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/manifest.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/manifest.json new file mode 100644 index 0000000000000..9f5451410976c --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/manifest.json @@ -0,0 +1,691 @@ +{ + "version": "48.0.0", + "artifacts": { + "aws-cdk-bedrock-agentcore-code-interpreter-1.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-cdk-bedrock-agentcore-code-interpreter-1.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-cdk-bedrock-agentcore-code-interpreter-1": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-cdk-bedrock-agentcore-code-interpreter-1.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/9ef80d73ec7a5b77c5ab3370f1419d7676b41fc48f3a58fe28782379d235032e.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-cdk-bedrock-agentcore-code-interpreter-1.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-bedrock-agentcore-code-interpreter-1.assets" + ], + "metadata": { + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter/ServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + } + } + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter/ServiceRole/ImportServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyCodeInterpreterServiceRole62BD5E8B" + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyCodeInterpreterB1C71BB8" + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter2": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter2/ServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + } + } + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter2/ServiceRole/ImportServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter2/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyCodeInterpreter2ServiceRoleFF1481A2" + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter2/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyCodeInterpreter2BECCA11E" + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter3": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter3/ServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + } + } + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter3/ServiceRole/ImportServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter3/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyCodeInterpreter3ServiceRole434C7800" + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter3/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyCodeInterpreter3A660EE66" + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-bedrock-agentcore-code-interpreter-1/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-bedrock-agentcore-code-interpreter-1" + }, + "BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "BedrockAgentCoreCodeInterpreterDefaultTestDeployAssertDF53FC14.assets" + ], + "metadata": { + "/BedrockAgentCoreCodeInterpreter/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/BedrockAgentCoreCodeInterpreter/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "BedrockAgentCoreCodeInterpreter/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-cdk-lib/feature-flag-report": { + "type": "cdk:feature-flag-report", + "properties": { + "module": "aws-cdk-lib", + "flags": { + "@aws-cdk/aws-signer:signingProfileNamePassedToCfn": { + "recommendedValue": true, + "explanation": "Pass signingProfileName to CfnSigningProfile" + }, + "@aws-cdk/core:newStyleStackSynthesis": { + "recommendedValue": true, + "explanation": "Switch to new stack synthesis method which enables CI/CD", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:stackRelativeExports": { + "recommendedValue": true, + "explanation": "Name exports based on the construct paths relative to the stack, rather than the global construct path", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-ecs-patterns:secGroupsDisablesImplicitOpenListener": { + "recommendedValue": true, + "explanation": "Disable implicit openListener when custom security groups are provided" + }, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": { + "recommendedValue": true, + "explanation": "Force lowercasing of RDS Cluster names in CDK", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": { + "recommendedValue": true, + "explanation": "Allow adding/removing multiple UsagePlanKeys independently", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeVersionProps": { + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeLayerVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`." + }, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": { + "recommendedValue": true, + "explanation": "Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:checkSecretUsage": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this flag to make it impossible to accidentally use SecretValues in unsafe locations" + }, + "@aws-cdk/core:target-partitions": { + "recommendedValue": [ + "aws", + "aws-cn" + ], + "explanation": "What regions to include in lookup tables of environment agnostic stacks" + }, + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": { + "userValue": true, + "recommendedValue": true, + "explanation": "ECS extensions will automatically add an `awslogs` driver if no logging is specified" + }, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to have Launch Templates generated by the `InstanceRequireImdsv2Aspect` use unique names." + }, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": { + "userValue": true, + "recommendedValue": true, + "explanation": "ARN format used by ECS. In the new ARN format, the cluster name is part of the resource ID." + }, + "@aws-cdk/aws-iam:minimizePolicies": { + "userValue": true, + "recommendedValue": true, + "explanation": "Minimize IAM policies by combining Statements" + }, + "@aws-cdk/core:validateSnapshotRemovalPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Error on snapshot removal policies on resources that do not support it." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate key aliases that include the stack name" + }, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to create an S3 bucket policy by default in cases where an AWS service would automatically create the Policy if one does not exist." + }, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict KMS key policy for encrypted Queues a bit more" + }, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make default CloudWatch Role behavior safe for multiple API Gateways in one environment" + }, + "@aws-cdk/core:enablePartitionLiterals": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make ARNs concrete if AWS partition is known" + }, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": { + "userValue": true, + "recommendedValue": true, + "explanation": "Event Rules may only push to encrypted SQS queues in the same account" + }, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": { + "userValue": true, + "recommendedValue": true, + "explanation": "Avoid setting the \"ECS\" deployment controller when adding a circuit breaker" + }, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature to by default create default policy names for imported roles that depend on the stack the role is in." + }, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use S3 Bucket Policy instead of ACLs for Server Access Logging" + }, + "@aws-cdk/aws-route53-patters:useCertificate": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use the official `Certificate` resource instead of `DnsValidatedCertificate`" + }, + "@aws-cdk/customresources:installLatestAwsSdkDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "Whether to install the latest SDK by default in AwsCustomResource" + }, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use unique resource name for Database Proxy" + }, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Remove CloudWatch alarms from deployment group" + }, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include authorizer configuration in the calculation of the API deployment logical ID." + }, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": { + "userValue": true, + "recommendedValue": true, + "explanation": "Define user data for a launch template by default when a machine image is provided." + }, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": { + "userValue": true, + "recommendedValue": true, + "explanation": "SecretTargetAttachments uses the ResourcePolicy of the attached Secret." + }, + "@aws-cdk/aws-redshift:columnId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Whether to use an ID to track Redshift column changes" + }, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable AmazonEMRServicePolicy_v2 managed policies" + }, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict access to the VPC default security group" + }, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a unique id for each RequestValidator added to a method" + }, + "@aws-cdk/aws-kms:aliasNameRef": { + "userValue": true, + "recommendedValue": true, + "explanation": "KMS Alias name and keyArn will have implicit reference to KMS Key" + }, + "@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable grant methods on Aliases imported by name to use kms:ResourceAliases condition" + }, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a launch template when creating an AutoScalingGroup" + }, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include the stack prefix in the stack name generation process" + }, + "@aws-cdk/aws-efs:denyAnonymousAccess": { + "userValue": true, + "recommendedValue": true, + "explanation": "EFS denies anonymous clients accesses" + }, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables support for Multi-AZ with Standby deployment for opensearch domains" + }, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default" + }, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, mount targets will have a stable logicalId that is linked to the associated subnet." + }, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a scope of InstanceParameterGroup for AuroraClusterInstance with each parameters will change." + }, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, will always use the arn for identifiers for CfnSourceApiAssociation in the GraphqlApi construct rather than id." + }, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials." + }, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the CodeCommit source action is using the default branch name 'main'." + }, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the logical ID of a Lambda permission for a Lambda action includes an alarm ID." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default value for crossAccountKeys to false." + }, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default pipeline type to V2." + }, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only." + }, + "@aws-cdk/pipelines:reduceAssetRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from PipelineAssetsFileRole trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-eks:nodegroupNameAttribute": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, nodegroupName attribute of the provisioned EKS NodeGroup will not have the cluster name prefix." + }, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default volume type of the EBS volume will be GP3" + }, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, remove default deployment alarm settings" + }, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, the custom resource used for `AwsCustomResource` will configure the `logApiResponseData` property as true by default" + }, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack." + }, + "@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": { + "recommendedValue": true, + "explanation": "When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:explicitStackTags": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, stack tags need to be assigned explicitly on a Stack." + }, + "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": { + "userValue": false, + "recommendedValue": false, + "explanation": "When set to true along with canContainersAccessInstanceRole=false in ECS cluster, new updated commands will be added to UserData to block container accessing IMDS. **Applicable to Linux only. IMPORTANT: See [details.](#aws-cdkaws-ecsenableImdsBlockingDeprecatedFeature)**" + }, + "@aws-cdk/aws-ecs:disableEcsImdsBlocking": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, CDK synth will throw exception if canContainersAccessInstanceRole is false. **IMPORTANT: See [details.](#aws-cdkaws-ecsdisableEcsImdsBlocking)**" + }, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, we will only grant the necessary permissions when users specify cloudwatch log group through logConfiguration" + }, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled will allow you to specify a resource policy per replica, and not copy the source table policy to all replicas" + }, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, initOptions.timeout and resourceSignalTimeout values will be summed together." + }, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a Lambda authorizer Permission created when using GraphqlApi will be properly scoped with a SourceArn." + }, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the value of property `instanceResourceId` in construct `DatabaseInstanceReadReplica` will be set to the correct value which is `DbiResourceId` instead of currently `DbInstanceArn`" + }, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CFN templates added with `cfn-include` will error if the template contains Resource Update or Create policies with CFN Intrinsics that include non-primitive values." + }, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, both `@aws-sdk` and `@smithy` packages will be excluded from the Lambda Node.js 18.x runtime to prevent version mismatches in bundled applications." + }, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resource of IAM Run Ecs policy generated by SFN EcsRunTask will reference the definition, instead of constructing ARN." + }, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the BastionHost construct will use the latest Amazon Linux 2023 AMI, instead of Amazon Linux 2." + }, + "@aws-cdk/core:aspectStabilization": { + "recommendedValue": true, + "explanation": "When enabled, a stabilization loop will be run when invoking Aspects during synthesis.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, use a new method for DNS Name of user pool domain target without creating a custom resource." + }, + "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default security group ingress rules will allow IPv6 ingress from anywhere" + }, + "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default behaviour of OIDC provider will reject unauthorized connections" + }, + "@aws-cdk/core:enableAdditionalMetadataCollection": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will expand the scope of usage data collected to better inform CDK development and improve communication for security concerns and emerging issues." + }, + "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": { + "userValue": false, + "recommendedValue": false, + "explanation": "[Deprecated] When enabled, Lambda will create new inline policies with AddToRolePolicy instead of adding to the Default Policy Statement" + }, + "@aws-cdk/aws-s3:setUniqueReplicationRoleName": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will automatically generate a unique role name that is used for s3 object replication." + }, + "@aws-cdk/pipelines:reduceStageRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from Stage addActions trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-events:requireEventBusPolicySid": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, grantPutEventsTo() will use resource policies with Statement IDs for service principals." + }, + "@aws-cdk/core:aspectPrioritiesMutating": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, Aspects added by the construct library on your behalf will be given a priority of MUTATING." + }, + "@aws-cdk/aws-dynamodb:retainTableReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, table replica will be default to the removal policy of source table unless specified otherwise." + }, + "@aws-cdk/cognito:logUserPoolClientSecretValue": { + "recommendedValue": false, + "explanation": "When disabled, the value of the user pool client secret will not be logged in the custom resource lambda function logs." + }, + "@aws-cdk/pipelines:reduceCrossAccountActionRoleTrustScope": { + "recommendedValue": true, + "explanation": "When enabled, scopes down the trust policy for the cross-account action role", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resultWriterV2 property of DistributedMap will be used insted of resultWriter" + }, + "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": { + "userValue": true, + "recommendedValue": true, + "explanation": "Add an S3 trust policy to a KMS key resource policy for SNS subscriptions." + }, + "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the EgressOnlyGateway resource is only created if private subnets are defined in the dual-stack VPC." + }, + "@aws-cdk/aws-ec2-alpha:useResourceIdForVpcV2Migration": { + "recommendedValue": false, + "explanation": "When enabled, use resource IDs for VPC V2 migration" + }, + "@aws-cdk/aws-s3:publicAccessBlockedByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, setting any combination of options for BlockPublicAccess will automatically set true for any options not defined." + }, + "@aws-cdk/aws-lambda:useCdkManagedLogGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK creates and manages loggroup for the lambda function" + } + } + } + } + }, + "minimumCliVersion": "2.1027.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/tree.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/tree.json new file mode 100644 index 0000000000000..b9c18a7b45930 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.js.snapshot/tree.json @@ -0,0 +1 @@ +{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"aws-cdk-bedrock-agentcore-code-interpreter-1":{"id":"aws-cdk-bedrock-agentcore-code-interpreter-1","path":"aws-cdk-bedrock-agentcore-code-interpreter-1","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"MyCodeInterpreter":{"id":"MyCodeInterpreter","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-agentcore-alpha.CodeInterpreterCustom","version":"0.0.0","metadata":["*"]},"children":{"ServiceRole":{"id":"ServiceRole","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}}]},"children":{"ImportServiceRole":{"id":"ImportServiceRole","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter/ServiceRole/ImportServiceRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter/ServiceRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"bedrock-agentcore.amazonaws.com"}}],"Version":"2012-10-17"}}}}}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_bedrockagentcore.CfnCodeInterpreterCustom","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::BedrockAgentCore::CodeInterpreterCustom","aws:cdk:cloudformation:props":{"description":"A code interpreter for code execution","executionRoleArn":{"Fn::GetAtt":["MyCodeInterpreterServiceRole62BD5E8B","Arn"]},"name":"my_code_interpreter","networkConfiguration":{"networkMode":"PUBLIC"}}}}}},"MyCodeInterpreter2":{"id":"MyCodeInterpreter2","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter2","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-agentcore-alpha.CodeInterpreterCustom","version":"0.0.0","metadata":["*"]},"children":{"ServiceRole":{"id":"ServiceRole","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter2/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}}]},"children":{"ImportServiceRole":{"id":"ImportServiceRole","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter2/ServiceRole/ImportServiceRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter2/ServiceRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"bedrock-agentcore.amazonaws.com"}}],"Version":"2012-10-17"}}}}}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter2/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_bedrockagentcore.CfnCodeInterpreterCustom","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::BedrockAgentCore::CodeInterpreterCustom","aws:cdk:cloudformation:props":{"executionRoleArn":{"Fn::GetAtt":["MyCodeInterpreter2ServiceRoleFF1481A2","Arn"]},"name":"my_code_interpreter2","networkConfiguration":{"networkMode":"SANDBOX"}}}}}},"MyCodeInterpreter3":{"id":"MyCodeInterpreter3","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter3","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-agentcore-alpha.CodeInterpreterCustom","version":"0.0.0","metadata":["*"]},"children":{"ServiceRole":{"id":"ServiceRole","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter3/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}}]},"children":{"ImportServiceRole":{"id":"ImportServiceRole","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter3/ServiceRole/ImportServiceRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter3/ServiceRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"bedrock-agentcore.amazonaws.com"}}],"Version":"2012-10-17"}}}}}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/MyCodeInterpreter3/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_bedrockagentcore.CfnCodeInterpreterCustom","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::BedrockAgentCore::CodeInterpreterCustom","aws:cdk:cloudformation:props":{"tags":{"Environment":"Dev","Project":"AgentCore","Team":"AI/ML"},"executionRoleArn":{"Fn::GetAtt":["MyCodeInterpreter3ServiceRole434C7800","Arn"]},"name":"my_code_interpreter3","networkConfiguration":{"networkMode":"PUBLIC"}}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-cdk-bedrock-agentcore-code-interpreter-1/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"BedrockAgentCoreCodeInterpreter":{"id":"BedrockAgentCoreCodeInterpreter","path":"BedrockAgentCoreCodeInterpreter","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"BedrockAgentCoreCodeInterpreter/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"BedrockAgentCoreCodeInterpreter/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"BedrockAgentCoreCodeInterpreter/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"BedrockAgentCoreCodeInterpreter/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"BedrockAgentCoreCodeInterpreter/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.ts b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.ts new file mode 100644 index 0000000000000..7ad7a2d6f5427 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/integ.code-interpreter.ts @@ -0,0 +1,42 @@ +/* + * Integration test for Bedrock Agent Core Browser construct + */ + +/// !cdk-integ aws-cdk-bedrock-agentcore-code-interpreter-1 + +import * as cdk from 'aws-cdk-lib'; +import * as integ from '@aws-cdk/integ-tests-alpha'; +import * as agentcore from '../../../agentcore'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-bedrock-agentcore-code-interpreter-1'); + +// Create a code interpreter with basic configuration +new agentcore.CodeInterpreterCustom(stack, 'MyCodeInterpreter', { + codeInterpreterCustomName: 'my_code_interpreter', + description: 'A code interpreter for code execution', +}); + +// Create a code interpreter with sandbox network configuration +new agentcore.CodeInterpreterCustom(stack, 'MyCodeInterpreter2', { + codeInterpreterCustomName: 'my_code_interpreter2', + networkConfiguration: agentcore.CodeInterpreterNetworkConfiguration.usingSandboxNetwork(), +}); + +// Create a code interpreter with tags +new agentcore.CodeInterpreterCustom(stack, 'MyCodeInterpreter3', { + codeInterpreterCustomName: 'my_code_interpreter3', + networkConfiguration: agentcore.CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + tags: { + Environment: 'Dev', + Team: 'AI/ML', + Project: 'AgentCore', + }, +}); + +new integ.IntegTest(app, 'BedrockAgentCoreCodeInterpreter', { + testCases: [stack], +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/validation-helpers.test.ts b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/validation-helpers.test.ts new file mode 100644 index 0000000000000..4c35e58885f78 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/tools/validation-helpers.test.ts @@ -0,0 +1,126 @@ +import { Token } from 'aws-cdk-lib'; +import { validateStringFieldLength, validateFieldPattern, throwIfInvalid } from '../../../agentcore/tools/validation-helpers'; + +describe('validation-helpers', () => { + describe('validateStringFieldLength', () => { + test('returns empty array for valid string length', () => { + const result = validateStringFieldLength({ + value: 'test', + fieldName: 'testField', + minLength: 2, + maxLength: 10, + }); + expect(result).toEqual([]); + }); + + test('returns error when string is too short', () => { + const result = validateStringFieldLength({ + value: 'a', + fieldName: 'testField', + minLength: 2, + maxLength: 10, + }); + expect(result).toHaveLength(1); + expect(result[0]).toContain('must be at least 2 characters'); + }); + + test('returns error when string is too long', () => { + const result = validateStringFieldLength({ + value: 'this is a very long string', + fieldName: 'testField', + minLength: 2, + maxLength: 10, + }); + expect(result).toHaveLength(1); + expect(result[0]).toContain('must be less than or equal to 10 characters'); + }); + + test('skips validation for unresolved tokens', () => { + const tokenValue = Token.asString({ Ref: 'SomeParameter' }); + const result = validateStringFieldLength({ + value: tokenValue, + fieldName: tokenValue, + minLength: 2, + maxLength: 10, + }); + expect(result).toEqual([]); + }); + }); + + describe('validateFieldPattern', () => { + test('returns empty array for valid pattern match', () => { + const result = validateFieldPattern( + 'test123', + 'testField', + /^[a-z0-9]+$/, + ); + expect(result).toEqual([]); + }); + + test('returns error for invalid pattern match', () => { + const result = validateFieldPattern( + 'test@123', + 'testField', + /^[a-z0-9]+$/, + ); + expect(result).toHaveLength(1); + expect(result[0]).toContain('does not match the required pattern'); + }); + + test('uses custom error message when provided', () => { + const customMessage = 'Custom validation error'; + const result = validateFieldPattern( + 'test@123', + 'testField', + /^[a-z0-9]+$/, + customMessage, + ); + expect(result).toEqual([customMessage]); + }); + + test('returns error for non-string value', () => { + const result = validateFieldPattern( + 42 as any, + 'testField', + /^[0-9]+$/, + ); + expect(result).toHaveLength(1); + expect(result[0]).toContain('Expected string'); + }); + + test('skips validation for unresolved tokens', () => { + const tokenValue = Token.asString({ Ref: 'SomeParameter' }); + const result = validateFieldPattern( + tokenValue, + 'testField', + /^[a-z0-9]+$/, + ); + expect(result).toEqual([]); + }); + }); + + describe('throwIfInvalid', () => { + test('returns parameter when validation passes', () => { + const param = { value: 'test', fieldName: 'testField', minLength: 2, maxLength: 10 }; + const result = throwIfInvalid(validateStringFieldLength, param); + expect(result).toBe(param); + }); + + test('throws error when validation fails', () => { + const param = { value: 'a', fieldName: 'testField', minLength: 2, maxLength: 10 }; + expect(() => { + throwIfInvalid(validateStringFieldLength, param); + }).toThrow('must be at least 2 characters'); + }); + + test('throws combined error messages when multiple validations fail', () => { + const validationFn = () => [ + 'Error 1', + 'Error 2', + ]; + expect(() => { + throwIfInvalid(validationFn, 'test'); + }).toThrow('Error 1\nError 2'); + }); + }); +}); diff --git a/packages/@aws-cdk/aws-bedrock-agentcore-alpha/tsconfig.json b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/tsconfig.json new file mode 100644 index 0000000000000..9c873476aa464 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-agentcore-alpha/tsconfig.json @@ -0,0 +1,51 @@ +{ + "compilerOptions": { + "declarationMap": false, + "inlineSourceMap": true, + "inlineSources": true, + "alwaysStrict": true, + "declaration": true, + "incremental": true, + "lib": [ + "es2022" + ], + "module": "commonjs", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "stripInternal": false, + "target": "es2022", + "composite": true, + "tsBuildInfoFile": "tsconfig.tsbuildinfo", + "esModuleInterop": false + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules" + ], + "references": [ + { + "path": "../../aws-cdk-lib" + }, + { + "path": "../../../tools/@aws-cdk/cdk-build-tools" + }, + { + "path": "../../../tools/@aws-cdk/pkglint" + }, + { + "path": "../integ-tests-alpha" + } + ] +} \ No newline at end of file