Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/46916.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_bedrockagentcore_gateway_target: Add `target_configuration.mcp.api_gateway` configuration block
```
116 changes: 112 additions & 4 deletions internal/service/bedrockagentcore/gateway_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,68 @@ func (r *gatewayTargetResource) Schema(ctx context.Context, request resource.Sch
},
NestedObject: schema.NestedBlockObject{
Blocks: map[string]schema.Block{
"api_gateway": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[mcpApiGatewayConfigurationModel](ctx),
Validators: []validator.List{
listvalidator.SizeAtMost(1),
},
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"rest_api_id": schema.StringAttribute{
Required: true,
},
names.AttrStage: schema.StringAttribute{
Required: true,
},
},
Blocks: map[string]schema.Block{
"api_gateway_tool_configuration": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[mcpApiGatewayToolConfigurationModel](ctx),
Validators: []validator.List{
listvalidator.SizeAtMost(1),
},
NestedObject: schema.NestedBlockObject{
Blocks: map[string]schema.Block{
"tool_filter": schema.SetNestedBlock{
CustomType: fwtypes.NewSetNestedObjectTypeOf[mcpApiGatewayToolFilterModel](ctx),
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"filter_path": schema.StringAttribute{
Required: true,
},
"methods": schema.SetAttribute{
ElementType: fwtypes.StringEnumType[awstypes.RestApiMethod](),
Required: true,
},
},
},
},
"tool_override": schema.SetNestedBlock{
CustomType: fwtypes.NewSetNestedObjectTypeOf[mcpApiGatewayToolOverrideModel](ctx),
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
names.AttrDescription: schema.StringAttribute{
Optional: true,
},
"method": schema.StringAttribute{
CustomType: fwtypes.StringEnumType[awstypes.RestApiMethod](),
Required: true,
},
names.AttrName: schema.StringAttribute{
Required: true,
},
names.AttrPath: schema.StringAttribute{
Required: true,
},
},
},
},
},
},
},
},
},
},
"lambda": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[mcpLambdaConfigurationModel](ctx),
Validators: []validator.List{
Expand Down Expand Up @@ -1168,10 +1230,11 @@ func (m targetConfigurationModel) Expand(ctx context.Context) (any, diag.Diagnos
}

type mcpConfigurationModel struct {
Lambda fwtypes.ListNestedObjectValueOf[mcpLambdaConfigurationModel] `tfsdk:"lambda"`
MCPServer fwtypes.ListNestedObjectValueOf[mcpServerConfigurationModel] `tfsdk:"mcp_server"`
SmithyModel fwtypes.ListNestedObjectValueOf[apiSchemaConfigurationModel] `tfsdk:"smithy_model"`
OpenApiSchema fwtypes.ListNestedObjectValueOf[apiSchemaConfigurationModel] `tfsdk:"open_api_schema"`
ApiGateway fwtypes.ListNestedObjectValueOf[mcpApiGatewayConfigurationModel] `tfsdk:"api_gateway"`
Lambda fwtypes.ListNestedObjectValueOf[mcpLambdaConfigurationModel] `tfsdk:"lambda"`
MCPServer fwtypes.ListNestedObjectValueOf[mcpServerConfigurationModel] `tfsdk:"mcp_server"`
SmithyModel fwtypes.ListNestedObjectValueOf[apiSchemaConfigurationModel] `tfsdk:"smithy_model"`
OpenApiSchema fwtypes.ListNestedObjectValueOf[apiSchemaConfigurationModel] `tfsdk:"open_api_schema"`
}

var (
Expand All @@ -1182,6 +1245,14 @@ var (
func (m *mcpConfigurationModel) Flatten(ctx context.Context, v any) diag.Diagnostics {
var diags diag.Diagnostics
switch t := v.(type) {
case awstypes.McpTargetConfigurationMemberApiGateway:
var model mcpApiGatewayConfigurationModel
smerr.AddEnrich(ctx, &diags, fwflex.Flatten(ctx, t.Value, &model))
if diags.HasError() {
return diags
}
m.ApiGateway = fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &model)

case awstypes.McpTargetConfigurationMemberLambda:
var model mcpLambdaConfigurationModel
smerr.AddEnrich(ctx, &diags, fwflex.Flatten(ctx, t.Value, &model))
Expand Down Expand Up @@ -1226,6 +1297,20 @@ func (m *mcpConfigurationModel) Flatten(ctx context.Context, v any) diag.Diagnos
func (m mcpConfigurationModel) Expand(ctx context.Context) (any, diag.Diagnostics) {
var diags diag.Diagnostics
switch {
case !m.ApiGateway.IsNull():
apiGatewayMCPConfigurationData, d := m.ApiGateway.ToPtr(ctx)
smerr.AddEnrich(ctx, &diags, d)
if diags.HasError() {
return nil, diags
}

var r awstypes.McpTargetConfigurationMemberApiGateway
smerr.AddEnrich(ctx, &diags, fwflex.Expand(ctx, apiGatewayMCPConfigurationData, &r.Value))
if diags.HasError() {
return nil, diags
}
return &r, diags

case !m.Lambda.IsNull():
lambdaMCPConfigurationData, d := m.Lambda.ToPtr(ctx)
smerr.AddEnrich(ctx, &diags, d)
Expand Down Expand Up @@ -1303,6 +1388,29 @@ type gatewayIAMRoleProviderModel struct {
// Empty struct - Gateway IAM Role provider requires no configuration
}

type mcpApiGatewayConfigurationModel struct {
ApiGatewayToolConfiguration fwtypes.ListNestedObjectValueOf[mcpApiGatewayToolConfigurationModel] `tfsdk:"api_gateway_tool_configuration"`
RestApiID types.String `tfsdk:"rest_api_id"`
Stage types.String `tfsdk:"stage"`
}

type mcpApiGatewayToolConfigurationModel struct {
ToolFilter fwtypes.SetNestedObjectValueOf[mcpApiGatewayToolFilterModel] `tfsdk:"tool_filter"`
ToolOverride fwtypes.SetNestedObjectValueOf[mcpApiGatewayToolOverrideModel] `tfsdk:"tool_override"`
}

type mcpApiGatewayToolFilterModel struct {
FilterPath types.String `tfsdk:"filter_path"`
Methods fwtypes.SetOfStringEnum[awstypes.RestApiMethod] `tfsdk:"methods"`
}

type mcpApiGatewayToolOverrideModel struct {
Description types.String `tfsdk:"description"`
Method fwtypes.StringEnum[awstypes.RestApiMethod] `tfsdk:"method"`
Name types.String `tfsdk:"name"`
Path types.String `tfsdk:"path"`
}

type mcpLambdaConfigurationModel struct {
LambdaArn types.String `tfsdk:"lambda_arn"`
ToolSchema fwtypes.ListNestedObjectValueOf[toolSchemaModel] `tfsdk:"tool_schema"`
Expand Down
201 changes: 201 additions & 0 deletions internal/service/bedrockagentcore/gateway_target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,102 @@ func TestAccBedrockAgentCoreGatewayTarget_targetConfigurationMCPServer(t *testin
})
}

func TestAccBedrockAgentCoreGatewayTarget_targetConfigurationAPIGateway(t *testing.T) {
ctx := acctest.Context(t)
var gatewayTarget bedrockagentcorecontrol.GetGatewayTargetOutput
rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix)
resourceName := "aws_bedrockagentcore_gateway_target.test"

acctest.ParallelTest(ctx, t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(ctx, t)
acctest.PreCheckPartitionHasService(t, names.BedrockEndpointID)
},
ErrorCheck: acctest.ErrorCheck(t, names.BedrockAgentCoreServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckGatewayTargetDestroy(ctx, t),
Steps: []resource.TestStep{
{
Config: testAccGatewayTargetConfig_targetConfigurationAPIGateway(rName, ""),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckGatewayTargetExists(ctx, t, resourceName, &gatewayTarget),
resource.TestCheckResourceAttr(resourceName, names.AttrName, rName),
resource.TestCheckResourceAttr(resourceName, "target_configuration.#", "1"),
resource.TestCheckResourceAttr(resourceName, "target_configuration.0.mcp.#", "1"),
resource.TestCheckResourceAttr(resourceName, "target_configuration.0.mcp.0.api_gateway.#", "1"),
resource.TestCheckResourceAttrPair(resourceName, "target_configuration.0.mcp.0.api_gateway.0.rest_api_id", "aws_api_gateway_rest_api.test", names.AttrID),
resource.TestCheckResourceAttrPair(resourceName, "target_configuration.0.mcp.0.api_gateway.0.stage", "aws_api_gateway_stage.test", "stage_name"),
resource.TestCheckResourceAttr(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.#", "1"),
resource.TestCheckResourceAttr(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.0.tool_filter.#", "1"),
resource.TestCheckResourceAttr(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.0.tool_filter.0.filter_path", "/pets"),
resource.TestCheckResourceAttr(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.0.tool_filter.0.methods.#", "2"),
resource.TestCheckTypeSetElemAttr(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.0.tool_filter.0.methods.*", "GET"),
resource.TestCheckTypeSetElemAttr(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.0.tool_filter.0.methods.*", "POST"),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.0.tool_override.*", map[string]string{
names.AttrName: "ListPets",
names.AttrPath: "/pets",
"method": "GET",
names.AttrDescription: "Retrieves all available pets",
}),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.0.tool_override.*", map[string]string{
names.AttrName: "RegisterPets",
names.AttrPath: "/pets",
"method": "POST",
names.AttrDescription: "Register pets",
}),
),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate),
},
},
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateIdFunc: acctest.AttrsImportStateIdFunc(resourceName, ",", "gateway_identifier", "target_id"),
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "target_id",
},
{
Config: testAccGatewayTargetConfig_targetConfigurationAPIGateway(rName, "2"),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckGatewayTargetExists(ctx, t, resourceName, &gatewayTarget),
resource.TestCheckResourceAttr(resourceName, names.AttrName, rName),
resource.TestCheckResourceAttr(resourceName, "target_configuration.#", "1"),
resource.TestCheckResourceAttr(resourceName, "target_configuration.0.mcp.#", "1"),
resource.TestCheckResourceAttr(resourceName, "target_configuration.0.mcp.0.api_gateway.#", "1"),
resource.TestCheckResourceAttrPair(resourceName, "target_configuration.0.mcp.0.api_gateway.0.rest_api_id", "aws_api_gateway_rest_api.test", names.AttrID),
resource.TestCheckResourceAttrPair(resourceName, "target_configuration.0.mcp.0.api_gateway.0.stage", "aws_api_gateway_stage.test", "stage_name"),
resource.TestCheckResourceAttr(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.#", "1"),
resource.TestCheckResourceAttr(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.0.tool_filter.#", "1"),
resource.TestCheckResourceAttr(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.0.tool_filter.0.filter_path", "/pets"),
resource.TestCheckResourceAttr(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.0.tool_filter.0.methods.#", "2"),
resource.TestCheckTypeSetElemAttr(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.0.tool_filter.0.methods.*", "GET"),
resource.TestCheckTypeSetElemAttr(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.0.tool_filter.0.methods.*", "POST"),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.0.tool_override.*", map[string]string{
names.AttrName: "ListPets2",
names.AttrPath: "/pets",
"method": "GET",
names.AttrDescription: "Retrieves all available pets2",
}),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "target_configuration.0.mcp.0.api_gateway.0.api_gateway_tool_configuration.0.tool_override.*", map[string]string{
names.AttrName: "RegisterPets2",
names.AttrPath: "/pets",
"method": "POST",
names.AttrDescription: "Register pets2",
}),
),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate),
},
},
},
},
})
}

func TestAccBedrockAgentCoreGatewayTarget_credentialProvider(t *testing.T) {
ctx := acctest.Context(t)
var gatewayTarget, gatewayTargetPrev bedrockagentcorecontrol.GetGatewayTargetOutput
Expand Down Expand Up @@ -1175,3 +1271,108 @@ resource "aws_bedrockagentcore_gateway_target" "test" {
}
`, rName, headerName))
}

func testAccGatewayTargetConfig_targetConfigurationAPIGateway(rName, toolOverrideSuffix string) string {
return acctest.ConfigCompose(testAccGatewayTargetConfig_infra(rName), fmt.Sprintf(`
resource "aws_api_gateway_rest_api" "test" {
name = %[1]q
}

resource "aws_api_gateway_resource" "pets" {
rest_api_id = aws_api_gateway_rest_api.test.id
parent_id = aws_api_gateway_rest_api.test.root_resource_id
path_part = "pets"
}

resource "aws_api_gateway_method" "get_pets" {
rest_api_id = aws_api_gateway_rest_api.test.id
resource_id = aws_api_gateway_resource.pets.id
http_method = "GET"
authorization = "NONE"
}

resource "aws_api_gateway_method" "post_pets" {
rest_api_id = aws_api_gateway_rest_api.test.id
resource_id = aws_api_gateway_resource.pets.id
http_method = "POST"
authorization = "NONE"
}

resource "aws_api_gateway_method_response" "get_pets_200" {
rest_api_id = aws_api_gateway_rest_api.test.id
resource_id = aws_api_gateway_resource.pets.id
http_method = aws_api_gateway_method.get_pets.http_method
status_code = "200"
}

resource "aws_api_gateway_method_response" "post_pets_200" {
rest_api_id = aws_api_gateway_rest_api.test.id
resource_id = aws_api_gateway_resource.pets.id
http_method = aws_api_gateway_method.post_pets.http_method
status_code = "200"
}

resource "aws_api_gateway_integration" "get_pets" {
rest_api_id = aws_api_gateway_rest_api.test.id
resource_id = aws_api_gateway_resource.pets.id
http_method = aws_api_gateway_method.get_pets.http_method
type = "MOCK"
}

resource "aws_api_gateway_integration" "post_pets" {
rest_api_id = aws_api_gateway_rest_api.test.id
resource_id = aws_api_gateway_resource.pets.id
http_method = aws_api_gateway_method.post_pets.http_method
type = "MOCK"
}

resource "aws_api_gateway_deployment" "test" {
rest_api_id = aws_api_gateway_rest_api.test.id
depends_on = [
aws_api_gateway_integration.get_pets,
aws_api_gateway_integration.post_pets,
]
}

resource "aws_api_gateway_stage" "test" {
stage_name = "prod"
rest_api_id = aws_api_gateway_rest_api.test.id
deployment_id = aws_api_gateway_deployment.test.id
}

resource "aws_bedrockagentcore_gateway_target" "test" {
name = %[1]q
gateway_identifier = aws_bedrockagentcore_gateway.test.gateway_id

target_configuration {
mcp {
api_gateway {
rest_api_id = aws_api_gateway_rest_api.test.id
stage = aws_api_gateway_stage.test.stage_name

api_gateway_tool_configuration {
tool_filter {
filter_path = "/pets"
methods = ["GET", "POST"]
}

tool_override {
name = "ListPets%[2]s"
path = "/pets"
method = "GET"
description = "Retrieves all available pets%[2]s"
}

tool_override {
name = "RegisterPets%[2]s"
path = "/pets"
method = "POST"
description = "Register pets%[2]s"
}
}
}
}
}
}
`, rName, toolOverrideSuffix))
}
Loading