From 743ff4c81b10fd40f3801a100964090bface4b64 Mon Sep 17 00:00:00 2001 From: Geun-Oh Date: Thu, 23 Oct 2025 23:04:25 +0900 Subject: [PATCH 1/4] chore: add xray sdk import --- internal/service/bedrockagentcore/agent_runtime.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/service/bedrockagentcore/agent_runtime.go b/internal/service/bedrockagentcore/agent_runtime.go index 4a698467dae3..dc09ba3c0f24 100644 --- a/internal/service/bedrockagentcore/agent_runtime.go +++ b/internal/service/bedrockagentcore/agent_runtime.go @@ -13,6 +13,8 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/bedrockagentcorecontrol" awstypes "github.com/aws/aws-sdk-go-v2/service/bedrockagentcorecontrol/types" + "github.com/aws/aws-sdk-go-v2/service/xray" + xraytypes "github.com/aws/aws-sdk-go-v2/service/xray/types" "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" From d02a052aaee0aa88f135caf8f80390baceafb100 Mon Sep 17 00:00:00 2001 From: Geun-Oh Date: Thu, 23 Oct 2025 23:05:44 +0900 Subject: [PATCH 2/4] feat: add bedrock agentcore observability block option --- .../service/bedrockagentcore/agent_runtime.go | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/internal/service/bedrockagentcore/agent_runtime.go b/internal/service/bedrockagentcore/agent_runtime.go index dc09ba3c0f24..0b08e092c4b3 100644 --- a/internal/service/bedrockagentcore/agent_runtime.go +++ b/internal/service/bedrockagentcore/agent_runtime.go @@ -218,6 +218,19 @@ func (r *agentRuntimeResource) Schema(ctx context.Context, request resource.Sche }, }, }, + "observability": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[observabilityConfigurationModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Required: true, + }, + }, + }, + }, names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ Create: true, Update: true, @@ -285,11 +298,34 @@ func (r *agentRuntimeResource) Create(ctx context.Context, request resource.Crea return } + if !data.Observability.IsNull() { + obsConfig, d := data.Observability.ToPtr(ctx) + smerr.EnrichAppend(ctx, &response.Diagnostics, d) + if response.Diagnostics.HasError() { + return + } + + if obsConfig != nil && obsConfig.Enabled.ValueBool() { + if err := configureObservability(ctx, conn, r.Meta().XRayClient(ctx), runtime, data.EnvironmentVariables); err != nil { + smerr.AddError(ctx, &response.Diagnostics, err, smerr.ID, agentRuntimeID) + return + } + + runtime, err = findAgentRuntimeByID(ctx, conn, agentRuntimeID) + if err != nil { + smerr.AddError(ctx, &response.Diagnostics, err, smerr.ID, agentRuntimeID) + return + } + } + } + // Set values for unknowns. + userEnvVars := data.EnvironmentVariables smerr.EnrichAppend(ctx, &response.Diagnostics, fwflex.Flatten(ctx, runtime, &data, fwflex.WithFieldNamePrefix("AgentRuntime"))) if response.Diagnostics.HasError() { return } + data.EnvironmentVariables = userEnvVars smerr.EnrichAppend(ctx, &response.Diagnostics, response.State.Set(ctx, data)) } @@ -315,11 +351,20 @@ func (r *agentRuntimeResource) Read(ctx context.Context, request resource.ReadRe return } + userEnvVars := data.EnvironmentVariables smerr.EnrichAppend(ctx, &response.Diagnostics, fwflex.Flatten(ctx, out, &data, fwflex.WithFieldNamePrefix("AgentRuntime"))) if response.Diagnostics.HasError() { return } + if !data.Observability.IsNull() { + obsConfig, d := data.Observability.ToPtr(ctx) + smerr.EnrichAppend(ctx, &response.Diagnostics, d) + if !response.Diagnostics.HasError() && obsConfig != nil && obsConfig.Enabled.ValueBool() { + data.EnvironmentVariables = userEnvVars + } + } + smerr.EnrichAppend(ctx, &response.Diagnostics, response.State.Set(ctx, &data)) } @@ -518,12 +563,17 @@ type agentRuntimeResourceModel struct { TagsAll tftags.Map `tfsdk:"tags_all"` Timeouts timeouts.Value `tfsdk:"timeouts"` WorkloadIdentityDetails fwtypes.ListNestedObjectValueOf[workloadIdentityDetailsModel] `tfsdk:"workload_identity_details"` + Observability fwtypes.ListNestedObjectValueOf[observabilityConfigurationModel] `tfsdk:"observability"` } type agentRuntimeArtifactModel struct { ContainerConfiguration fwtypes.ListNestedObjectValueOf[containerConfigurationModel] `tfsdk:"container_configuration"` } +type observabilityConfigurationModel struct { + Enabled types.Bool `tfsdk:"enabled"` +} + var ( _ fwflex.Expander = agentRuntimeArtifactModel{} _ fwflex.Flattener = &agentRuntimeArtifactModel{} @@ -681,6 +731,75 @@ func (m requestHeaderConfigurationModel) Expand(ctx context.Context) (any, diag. return nil, diags } +func configureObservability(ctx context.Context, conn *bedrockagentcorecontrol.Client, xrayConn *xray.Client, runtime *bedrockagentcorecontrol.GetAgentRuntimeOutput, existingEnvVars fwtypes.MapOfString) error { + runtimeID := aws.ToString(runtime.AgentRuntimeId) + runtimeName := aws.ToString(runtime.AgentRuntimeName) + logGroup := fmt.Sprintf("/aws/bedrock-agentcore/runtimes/%s", runtimeID) + + obsEnvVars := map[string]string{ + "AGENT_OBSERVABILITY_ENABLED": "true", + "OTEL_PYTHON_DISTRO": "aws_distro", + "OTEL_PYTHON_CONFIGURATOR": "aws_configurator", + "OTEL_RESOURCE_ATTRIBUTES": fmt.Sprintf("service.name=%s,aws.log.group.names=%s", runtimeName, logGroup), + "OTEL_EXPORTER_OTLP_LOGS_HEADERS": fmt.Sprintf("x-aws-log-group=%s,x-aws-log-stream=runtime-logs,x-aws-metric-namespace=bedrock-agentcore", logGroup), + "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf", + "OTEL_TRACES_EXPORTER": "otlp", + } + + mergedEnvVars := make(map[string]string) + + if !existingEnvVars.IsNull() { + for k, v := range existingEnvVars.Elements() { + if strVal, ok := v.(types.String); ok { + mergedEnvVars[k] = strVal.ValueString() + } + } + } + + for k, v := range obsEnvVars { + mergedEnvVars[k] = v + } + + updateInput := &bedrockagentcorecontrol.UpdateAgentRuntimeInput{ + AgentRuntimeId: aws.String(runtimeID), + AgentRuntimeArtifact: runtime.AgentRuntimeArtifact, + RoleArn: runtime.RoleArn, + NetworkConfiguration: runtime.NetworkConfiguration, + EnvironmentVariables: mergedEnvVars, + AuthorizerConfiguration: runtime.AuthorizerConfiguration, + Description: runtime.Description, + ProtocolConfiguration: runtime.ProtocolConfiguration, + } + + if _, err := conn.UpdateAgentRuntime(ctx, updateInput); err != nil { + return fmt.Errorf("updating runtime with observability env vars: %w", err) + } + + if err := configureXRayForObservability(ctx, xrayConn, runtimeID); err != nil { + return fmt.Errorf("configuring XRay: %w", err) + } + + return nil +} + +func configureXRayForObservability(ctx context.Context, xrayConn *xray.Client, runtimeName string) error { + getOutput, err := xrayConn.GetTraceSegmentDestination(ctx, &xray.GetTraceSegmentDestinationInput{}) + if err != nil { + return fmt.Errorf("getting trace segment destination: %w", err) + } + + if getOutput.Destination != xraytypes.TraceSegmentDestinationCloudWatchLogs { + _, err = xrayConn.UpdateTraceSegmentDestination(ctx, &xray.UpdateTraceSegmentDestinationInput{ + Destination: xraytypes.TraceSegmentDestinationCloudWatchLogs, + }) + if err != nil { + return fmt.Errorf("updating trace segment destination: %w", err) + } + } + + return nil +} + type workloadIdentityDetailsModel struct { WorkloadIdentityARN types.String `tfsdk:"workload_identity_arn"` } From 3fb21280dcac503e41e6dd809fde8779843d83e6 Mon Sep 17 00:00:00 2001 From: Geun-Oh Date: Fri, 24 Oct 2025 01:26:47 +0900 Subject: [PATCH 3/4] chore: add cloudwatch import --- internal/service/bedrockagentcore/agent_runtime.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/service/bedrockagentcore/agent_runtime.go b/internal/service/bedrockagentcore/agent_runtime.go index 0b08e092c4b3..2d6099254290 100644 --- a/internal/service/bedrockagentcore/agent_runtime.go +++ b/internal/service/bedrockagentcore/agent_runtime.go @@ -13,6 +13,8 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/bedrockagentcorecontrol" awstypes "github.com/aws/aws-sdk-go-v2/service/bedrockagentcorecontrol/types" + "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" + logstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" "github.com/aws/aws-sdk-go-v2/service/xray" xraytypes "github.com/aws/aws-sdk-go-v2/service/xray/types" "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr" From efba88499d995d302f5b058727caec47bdfa96aa Mon Sep 17 00:00:00 2001 From: Geun-Oh Date: Fri, 24 Oct 2025 01:28:09 +0900 Subject: [PATCH 4/4] feat: add cloudwatch transaction search activation & xray permission update for log transmission --- .../service/bedrockagentcore/agent_runtime.go | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/internal/service/bedrockagentcore/agent_runtime.go b/internal/service/bedrockagentcore/agent_runtime.go index 2d6099254290..4e63d332930a 100644 --- a/internal/service/bedrockagentcore/agent_runtime.go +++ b/internal/service/bedrockagentcore/agent_runtime.go @@ -308,6 +308,17 @@ func (r *agentRuntimeResource) Create(ctx context.Context, request resource.Crea } if obsConfig != nil && obsConfig.Enabled.ValueBool() { + + if err := r.ensureXRayResourcePolicy(ctx); err != nil { + smerr.AddError(ctx, &response.Diagnostics, err, smerr.ID, agentRuntimeID) + return + } + + if err := r.waitForXRayResourcePolicy(ctx, propagationTimeout); err != nil { + smerr.AddError(ctx, &response.Diagnostics, fmt.Errorf("waiting for X-Ray resource policy: %w", err), smerr.ID, agentRuntimeID) + return + } + if err := configureObservability(ctx, conn, r.Meta().XRayClient(ctx), runtime, data.EnvironmentVariables); err != nil { smerr.AddError(ctx, &response.Diagnostics, err, smerr.ID, agentRuntimeID) return @@ -802,6 +813,83 @@ func configureXRayForObservability(ctx context.Context, xrayConn *xray.Client, r return nil } +const xrayResourcePolicyName = "BedrockAgentXRayPolicy" + +func (r *agentRuntimeResource) ensureXRayResourcePolicy(ctx context.Context) error { + meta := r.Meta() + + logsconn := meta.LogsClient(ctx) + region := meta.Region(ctx) + accountID := meta.AccountID(ctx) + + policyDocument := fmt.Sprintf(`{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "TransactionSearchXRayAccess", + "Effect": "Allow", + "Principal": { + "Service": "xray.amazonaws.com" + }, + "Action": "logs:PutLogEvents", + "Resource": [ + "arn:aws:logs:%[1]s:%[2]s:log-group:aws/spans:*", + "arn:aws:logs:%[1]s:%[2]s:log-group:/aws/application-signals/data:*" + ], + "Condition": { + "ArnLike": { + "aws:SourceArn": "arn:aws:xray:%[1]s:%[2]s:*" + }, + "StringEquals": { + "aws:SourceAccount": "%[2]s" + } + } + } + ] + }`, region, accountID) + + _, err := logsconn.PutResourcePolicy(ctx, + &cloudwatchlogs.PutResourcePolicyInput{ + PolicyName: aws.String(xrayResourcePolicyName), + PolicyDocument: aws.String(policyDocument), + }) + + if errs.IsA[*logstypes.ResourceAlreadyExistsException](err) { + return nil + } + + if err != nil { + return fmt.Errorf("error putting CloudWatch Logs resource policy for X-Ray: %w", err) + } + + return nil +} + +func (r *agentRuntimeResource) waitForXRayResourcePolicy(ctx context.Context, timeout time.Duration) error { + stateConf := &sdkretry.StateChangeConf{ + Pending: []string{"notfound"}, + Target: []string{"found"}, + Timeout: timeout, + Refresh: func() (interface{}, string, error) { + logsconn := r.Meta().LogsClient(ctx) + out, err := logsconn.DescribeResourcePolicies(ctx, &cloudwatchlogs.DescribeResourcePoliciesInput{ + Limit: aws.Int32(50), + }) + if err != nil { + return nil, "", err + } + for _, policy := range out.ResourcePolicies { + if aws.ToString(policy.PolicyName) == xrayResourcePolicyName { + return policy, "found", nil + } + } + return nil, "notfound", nil + }, + } + _, err := stateConf.WaitForStateContext(ctx) + return err +} + type workloadIdentityDetailsModel struct { WorkloadIdentityARN types.String `tfsdk:"workload_identity_arn"` }