| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: BUSL-1.1 |
| |
| package rpcapi |
| |
| import ( |
| "context" |
| "fmt" |
| |
| "github.com/hashicorp/hcl/v2" |
| "github.com/hashicorp/hcl/v2/hclsyntax" |
| "github.com/zclconf/go-cty/cty" |
| "google.golang.org/grpc/codes" |
| "google.golang.org/grpc/status" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/hashicorp/terraform/internal/providers" |
| "github.com/hashicorp/terraform/internal/rpcapi/terraform1/stacks" |
| "github.com/hashicorp/terraform/internal/stacks/stackaddrs" |
| "github.com/hashicorp/terraform/internal/stacks/stackconfig" |
| "github.com/hashicorp/terraform/internal/stacks/stackruntime" |
| "github.com/hashicorp/terraform/internal/stacks/stackstate" |
| "github.com/hashicorp/terraform/internal/tfdiags" |
| ) |
| |
| // stacksInspector is the backing representation of a "stack inspector handle" |
| // as exposed in the stacks part of the RPC API, which allows a caller to |
| // provide what they want to inspect just once and then perform any number |
| // of subsequent inspection actions against it. |
| type stacksInspector struct { |
| Config *stackconfig.Config |
| State *stackstate.State |
| ProviderFactories map[addrs.Provider]providers.Factory |
| InputValues map[stackaddrs.InputVariable]stackruntime.ExternalInputValue |
| ExperimentsAllowed bool |
| } |
| |
| // InspectExpressionResult evaluates a given expression string in the |
| // inspection environment represented by the receiver. |
| func (i *stacksInspector) InspectExpressionResult(ctx context.Context, req *stacks.InspectExpressionResult_Request) (*stacks.InspectExpressionResult_Response, error) { |
| var diags tfdiags.Diagnostics |
| |
| expr, hclDiags := hclsyntax.ParseExpression(req.ExpressionSrc, "<external expression>", hcl.InitialPos) |
| diags = diags.Append(hclDiags) |
| if diags.HasErrors() { |
| return &stacks.InspectExpressionResult_Response{ |
| Diagnostics: diagnosticsToProto(diags), |
| }, nil |
| } |
| |
| stackAddr := stackaddrs.RootStackInstance |
| if req.StackAddr != "" { |
| // FIXME: Support this later. We don't currently have a stack instance |
| // address parser to parse this input with, but we could build one |
| // in future. |
| return nil, status.Error(codes.InvalidArgument, "the InspectExpressionResult operation currently only supports evaluating in the topmost stack") |
| } |
| |
| val, moreDiags := stackruntime.EvalExpr(ctx, expr, &stackruntime.EvalExprRequest{ |
| Config: i.Config, |
| State: i.State, |
| EvalStackInstance: stackAddr, |
| InputValues: i.InputValues, |
| ProviderFactories: i.ProviderFactories, |
| ExperimentsAllowed: i.ExperimentsAllowed, |
| }) |
| diags = diags.Append(moreDiags) |
| if val == cty.NilVal { |
| // Too invalid to return any value at all, then. |
| return &stacks.InspectExpressionResult_Response{ |
| Diagnostics: diagnosticsToProto(diags), |
| }, nil |
| } |
| |
| result, err := stacks.ToDynamicValue(val, cty.DynamicPseudoType) |
| if err != nil { |
| // We might get here if the result was of a type we cannot send |
| // over the wire, such as a reference to a provider configuration. |
| diags = diags.Append(tfdiags.Sourceless( |
| tfdiags.Error, |
| "Result is not serializable", |
| fmt.Sprintf("Cannot return the result of the given expression: %s.", err), |
| )) |
| } |
| |
| return &stacks.InspectExpressionResult_Response{ |
| Result: result, |
| Diagnostics: diagnosticsToProto(diags), |
| }, nil |
| } |