From e0c86a2c1e37fd8b82cdd984da8c564e17702018 Mon Sep 17 00:00:00 2001 From: Samsondeen Dare Date: Mon, 4 May 2026 15:51:05 +0200 Subject: [PATCH 1/7] Add policy client and protocol plumbing --- internal/policy/callback/callback.go | 54 + internal/policy/callback/server.go | 85 ++ internal/policy/client.go | 310 ++++++ internal/policy/client_test.go | 140 +++ internal/policy/diagnostics.go | 151 +++ internal/policy/evaluateresult_string.go | 29 + internal/policy/mock.go | 120 +++ internal/policy/policy.go | 194 ++++ internal/policy/proto/callback.pb.go | 313 ++++++ internal/policy/proto/callback.proto | 37 + internal/policy/proto/callback_grpc.pb.go | 162 +++ internal/policy/proto/diagnostic_extra.go | 80 ++ internal/policy/proto/diagnostics.go | 180 ++++ internal/policy/proto/diagnostics.pb.go | 861 ++++++++++++++++ internal/policy/proto/diagnostics.proto | 98 ++ internal/policy/proto/policy.pb.go | 1042 ++++++++++++++++++++ internal/policy/proto/policy.proto | 206 ++++ internal/policy/proto/policy_grpc.pb.go | 264 +++++ internal/policy/proto/types.pb.go | 462 +++++++++ internal/policy/proto/types.proto | 76 ++ internal/policy/result.go | 39 + internal/policy/testing.go | 18 + internal/tfdiags/hcl.go | 7 + tools/protobuf-compile/protobuf-compile.go | 40 + 24 files changed, 4968 insertions(+) create mode 100644 internal/policy/callback/callback.go create mode 100644 internal/policy/callback/server.go create mode 100644 internal/policy/client.go create mode 100644 internal/policy/client_test.go create mode 100644 internal/policy/diagnostics.go create mode 100644 internal/policy/evaluateresult_string.go create mode 100644 internal/policy/mock.go create mode 100644 internal/policy/policy.go create mode 100644 internal/policy/proto/callback.pb.go create mode 100644 internal/policy/proto/callback.proto create mode 100644 internal/policy/proto/callback_grpc.pb.go create mode 100644 internal/policy/proto/diagnostic_extra.go create mode 100644 internal/policy/proto/diagnostics.go create mode 100644 internal/policy/proto/diagnostics.pb.go create mode 100644 internal/policy/proto/diagnostics.proto create mode 100644 internal/policy/proto/policy.pb.go create mode 100644 internal/policy/proto/policy.proto create mode 100644 internal/policy/proto/policy_grpc.pb.go create mode 100644 internal/policy/proto/types.pb.go create mode 100644 internal/policy/proto/types.proto create mode 100644 internal/policy/result.go create mode 100644 internal/policy/testing.go diff --git a/internal/policy/callback/callback.go b/internal/policy/callback/callback.go new file mode 100644 index 000000000000..93f220571293 --- /dev/null +++ b/internal/policy/callback/callback.go @@ -0,0 +1,54 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package callback + +import ( + "sync" + "sync/atomic" + + "github.com/zclconf/go-cty/cty" +) + +type Functions struct { + GetResources func(resource string, attrs cty.Value) ([]cty.Value, error) + GetDataSource func(datasource string, attrs cty.Value) (cty.Value, error) +} + +// InternalRegistry stores a mapping of evaluation IDs to callback functions, +// allowing resources to register functions that will be called during their +// policy evaluation. +type InternalRegistry struct { + lock sync.RWMutex + provider map[uint32]Functions + counter uint32 +} + +func NewRegistry() *InternalRegistry { + return &InternalRegistry{ + provider: make(map[uint32]Functions), + } +} + +func (s *InternalRegistry) Register(id uint32, fns Functions) { + s.lock.Lock() + defer s.lock.Unlock() + s.provider[id] = fns +} + +func (s *InternalRegistry) Unregister(id uint32) { + s.lock.Lock() + defer s.lock.Unlock() + delete(s.provider, id) +} + +func (s *InternalRegistry) Get(id uint32) (Functions, bool) { + s.lock.RLock() + defer s.lock.RUnlock() + fns, ok := s.provider[id] + return fns, ok +} + +func (s *InternalRegistry) NextID() uint32 { + return atomic.AddUint32(&s.counter, 1) +} diff --git a/internal/policy/callback/server.go b/internal/policy/callback/server.go new file mode 100644 index 000000000000..c43e7f498aa6 --- /dev/null +++ b/internal/policy/callback/server.go @@ -0,0 +1,85 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package callback + +import ( + "context" + "fmt" + + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/msgpack" + "google.golang.org/grpc" + + "github.com/hashicorp/terraform/internal/policy/proto" +) + +var ( + _ proto.CallbackServiceServer = (*Server)(nil) +) + +type Server struct { + ID uint32 + Registry *InternalRegistry + Grpc *grpc.Server + proto.UnimplementedCallbackServiceServer +} + +func (s *Server) GetResources(_ context.Context, request *proto.GetResourcesRequest) (*proto.GetResourcesResponse, error) { + attrs, err := msgpack.Unmarshal(request.Data, cty.DynamicPseudoType) + if err != nil { + return nil, fmt.Errorf("failed to unserialize data: %w", err) + } + functions, ok := s.Registry.Get(request.EvaluationRequestId) + if !ok { + return nil, fmt.Errorf("no callback registered for ID %d (request type: %s)", request.EvaluationRequestId, request.Type) + } + resources, err := functions.GetResources(request.Type, attrs) + if err != nil { + return nil, err + } + + results := make([][]byte, 0, len(resources)) + for _, resource := range resources { + result, err := msgpack.Marshal(resource, cty.DynamicPseudoType) + if err != nil { + return nil, fmt.Errorf("failed to serialize data: %w", err) + } + results = append(results, result) + } + + return &proto.GetResourcesResponse{ + Results: results, + }, nil +} + +func (s *Server) GetDataSource(_ context.Context, request *proto.GetDataSourceRequest) (*proto.GetDataSourceResponse, error) { + attrs, err := msgpack.Unmarshal(request.Data, cty.DynamicPseudoType) + if err != nil { + return nil, fmt.Errorf("failed to unserialize data: %w", err) + } + + functions, ok := s.Registry.Get(request.EvaluationRequestId) + if !ok { + return nil, fmt.Errorf("no callback registered for ID %d (request type: %s)", request.EvaluationRequestId, request.Type) + } + datasource, err := functions.GetDataSource(request.Type, attrs) + if err != nil { + return nil, err + } + + result, err := msgpack.Marshal(datasource, cty.DynamicPseudoType) + if err != nil { + return nil, fmt.Errorf("failed to serialize data: %w", err) + } + + return &proto.GetDataSourceResponse{ + Result: result, + }, nil +} + +func (s *Server) Stop() { + if s.Grpc != nil { + s.Grpc.GracefulStop() + } +} diff --git a/internal/policy/client.go b/internal/policy/client.go new file mode 100644 index 000000000000..d730aacc0dfb --- /dev/null +++ b/internal/policy/client.go @@ -0,0 +1,310 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package policy + +import ( + "context" + "fmt" + "log" + "os" + "os/exec" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/msgpack" + "google.golang.org/grpc" + + "github.com/hashicorp/terraform/internal/policy/callback" + "github.com/hashicorp/terraform/internal/policy/proto" +) + +const ( + TerraformPolicyPluginEnvVar = "TF_POLICY_PLUGIN" + TerraformPolicyLogLevelEnvVar = "TF_POLICY_LOG_LEVEL" + callbackServiceTimeout = 10 * time.Second +) + +var _ CallbackService = (*client)(nil) +var _ Client = (*client)(nil) + +func Connect(ctx context.Context) (Client, error) { + + pgm := "tfpolicy-plugin" // by default, just use this if it's in the path + if envvar := os.Getenv(TerraformPolicyPluginEnvVar); len(envvar) > 0 { + pgm = envvar + } + + cmd := exec.CommandContext(ctx, pgm, "rpcapi") + plugin := plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "TF_POLICY_PLUGIN", + MagicCookieValue: "6F11ED78A2AB", + }, + Plugins: map[string]plugin.Plugin{ + "policy": new(policy), + }, + Cmd: cmd, + AllowedProtocols: []plugin.Protocol{ + plugin.ProtocolGRPC, + }, + Logger: hclog.New(&hclog.LoggerOptions{ + Level: func() hclog.Level { + level := hclog.LevelFromString(os.Getenv(TerraformPolicyLogLevelEnvVar)) + if level == hclog.NoLevel { + return hclog.Error + } + return level + }(), + }), + }) + + rpc, err := plugin.Client() + if err != nil { + plugin.Kill() + return nil, fmt.Errorf("failed to connect to plugin: %v", err) + } + + raw, err := rpc.Dispense("policy") + if err != nil { + plugin.Kill() + return nil, fmt.Errorf("failed to dispense plugin: %v", err) + } + + sc := raw.(*client) + sc.plugin = plugin + return sc, nil +} + +type client struct { + plugin *plugin.Client + + broker *plugin.GRPCBroker + client proto.PolicyClient + callbackRegistry *callback.InternalRegistry + cbServer *callback.Server +} + +func (c *client) RegisterCallbackService(ctx context.Context) (*callback.Server, Diagnostics) { + if c.cbServer != nil { + panic("callback service already registered") + } + + cbServiceID := c.broker.NextId() + c.cbServer = &callback.Server{ + ID: cbServiceID, + Registry: c.callbackRegistry, + } + + serverCh := make(chan *grpc.Server, 1) + + // Start the callback service server on the broker. + go c.broker.AcceptAndServe(cbServiceID, func(opts []grpc.ServerOption) *grpc.Server { + server := grpc.NewServer(opts...) + proto.RegisterCallbackServiceServer(server, c.cbServer) + serverCh <- server + return server + }) + + select { + // Wait for the server to be ready before returning. + case server := <-serverCh: + c.cbServer.Grpc = server + // If the context is done, return early with an error. + case <-ctx.Done(): + return nil, Diagnostics{ + NewErrorDiagnostic("Failed to register callback service", + fmt.Sprintf("Failed to register callback service: %v.", ctx.Err()), + SetupErrorResult, + ), + } + + // If the wait has exceeded the timeout, return early with an error. + case <-time.After(callbackServiceTimeout): + return nil, Diagnostics{ + NewErrorDiagnostic("Failed to register callback service", + fmt.Sprintf("Failed to register callback service: timed out after %s.", callbackServiceTimeout), + SetupErrorResult, + ), + } + } + + return c.cbServer, nil +} + +func (c *client) Setup(ctx context.Context, req SetupRequest) SetupResponse { + log.Printf("[DEBUG] Setting up Terraform Policy connection") + response, err := c.client.Setup(ctx, &proto.PolicySetupRequest{ + ClientCapabilities: new(proto.PolicySetupRequest_ClientCapabilities), + SourceLocations: req.SourceLocations, + CallbackService: req.CallbackService, + }) + if err != nil { + return SetupResponse{Diagnostics: Diagnostics{ + NewErrorDiagnostic("Failed to setup Terraform Policy connection", + fmt.Sprintf("Failed to setup Terraform Policy connection: %v.", err), + SetupErrorResult, + ), + }} + } + + return SetupResponse{ + serverCapabilities: response.ServerCapabilities, + Diagnostics: DiagsFromProto(response.Diagnostics, nil), + } +} + +func (c *client) Evaluate(ctx context.Context, req EvaluationRequest[*proto.ResourceMetadata]) EvaluationResponse { + log.Printf("[DEBUG] Evaluating policy for resource %s", req.Target) + var diags []*proto.Diagnostic + + req = normalizeRequest(req) + + attrsBytes, err := msgpack.Marshal(req.Attrs, cty.DynamicPseudoType) + if err != nil { + return ErrorEvalFromDiags(append(diags, &proto.Diagnostic{ + Severity: proto.Severity_ERROR, + Summary: "Failed to serialize attributes", + Detail: fmt.Sprintf("Failed to serialize attributes: %v.", err), + })) + } + + priorAttrsBytes, err := msgpack.Marshal(req.PriorAttrs, cty.DynamicPseudoType) + if err != nil { + return ErrorEvalFromDiags(append(diags, &proto.Diagnostic{ + Severity: proto.Severity_ERROR, + Summary: "Failed to serialize prior attributes", + Detail: fmt.Sprintf("Failed to serialize prior attributes: %v.", err), + })) + } + + evalID := c.callbackRegistry.NextID() + request := &proto.PolicyEvaluateResourceRequest{ + EvaluationId: evalID, + Resource: req.Target, + Attrs: attrsBytes, + Metadata: req.Meta, + PriorAttrs: priorAttrsBytes, + } + + // Register the callback functions with the callback service, so that they are available + // for use during evaluation. + c.callbackRegistry.Register(evalID, req.Callbacks) + + // We can unregister the callback functions after the evaluation is complete. + defer c.callbackRegistry.Unregister(evalID) + + response, err := c.client.EvaluateResource(ctx, request) + if err != nil { + return ErrorEvalFromDiags(append(diags, &proto.Diagnostic{ + Severity: proto.Severity_ERROR, + Summary: "Failed to evaluate Terraform Policy", + Detail: fmt.Sprintf("Failed to evaluate Terraform Policy: %v.", err), + })) + } + + return EvaluationFromProtoResponse(response.Result, response.PolicyDetails) +} + +func (c *client) EvaluateProvider(ctx context.Context, req EvaluationRequest[*proto.ProviderMetadata]) EvaluationResponse { + log.Printf("[DEBUG] Evaluating policy for provider %s", req.Target) + var diags []*proto.Diagnostic + req = normalizeRequest(req) + + attrsBytes, err := msgpack.Marshal(req.Attrs, cty.DynamicPseudoType) + if err != nil { + return ErrorEvalFromDiags(append(diags, &proto.Diagnostic{ + Severity: proto.Severity_ERROR, + Summary: "Failed to serialize attributes", + Detail: fmt.Sprintf("Failed to serialize attributes: %v.", err), + })) + } + + request := &proto.PolicyEvaluateProviderRequest{ + ProviderType: req.Target, + Attrs: attrsBytes, + Metadata: req.Meta, + } + + response, err := c.client.EvaluateProvider(ctx, request) + if err != nil { + return ErrorEvalFromDiags(append(diags, &proto.Diagnostic{ + Severity: proto.Severity_ERROR, + Summary: "Failed to evaluate Terraform Policy", + Detail: fmt.Sprintf("Failed to evaluate Terraform Policy: %v.", err), + })) + } + + return EvaluationFromProtoResponse(response.Result, response.PolicyDetails) +} + +func (c *client) EvaluateModule(ctx context.Context, req EvaluationRequest[*proto.ModuleMetadata]) EvaluationResponse { + log.Printf("[DEBUG] Evaluating policy for module %s", req.Target) + var diags []*proto.Diagnostic + + req = normalizeRequest(req) + + request := &proto.PolicyEvaluateModuleRequest{ + ModuleSource: req.Target, + Metadata: req.Meta, + } + + response, err := c.client.EvaluateModule(ctx, request) + if err != nil { + return ErrorEvalFromDiags(append(diags, &proto.Diagnostic{ + Severity: proto.Severity_ERROR, + Summary: "Failed to evaluate Terraform Policy", + Detail: fmt.Sprintf("Failed to evaluate Terraform Policy: %v.", err), + })) + } + + return EvaluationFromProtoResponse(response.Result, response.PolicyDetails) +} + +func (c *client) Stop() { + if c.cbServer != nil { + c.cbServer.Stop() + } + c.plugin.Kill() +} + +func normalizeRequest[T any](req EvaluationRequest[T]) EvaluationRequest[T] { + attrs := req.Attrs + priorAttrs := req.PriorAttrs + if attrs == cty.NilVal { + attrs = cty.EmptyObjectVal + } + if priorAttrs == cty.NilVal { + priorAttrs = cty.EmptyObjectVal + } + + return EvaluationRequest[T]{ + Target: req.Target, + Attrs: attrs, + PriorAttrs: priorAttrs, + Meta: req.Meta, + Callbacks: req.Callbacks, + } +} + +type policy struct { + plugin.NetRPCUnsupportedPlugin +} + +func (s *policy) GRPCServer(*plugin.GRPCBroker, *grpc.Server) error { + // This package is only implementing the client side of the Terraform Policy + // plugin. + return fmt.Errorf("server configuration not supported") +} + +func (s *policy) GRPCClient(_ context.Context, broker *plugin.GRPCBroker, conn *grpc.ClientConn) (interface{}, error) { + return &client{ + plugin: nil, // this will be set by the Connect function + broker: broker, + client: proto.NewPolicyClient(conn), + callbackRegistry: callback.NewRegistry(), + }, nil +} diff --git a/internal/policy/client_test.go b/internal/policy/client_test.go new file mode 100644 index 000000000000..49a5ec526d13 --- /dev/null +++ b/internal/policy/client_test.go @@ -0,0 +1,140 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package policy + +import ( + "context" + "testing" + + "github.com/zclconf/go-cty/cty" + "google.golang.org/grpc" + + "github.com/hashicorp/terraform/internal/policy/callback" + "github.com/hashicorp/terraform/internal/policy/proto" +) + +type stubPolicyClient struct { + proto.PolicyClient + + evaluateResourceFn func(*proto.PolicyEvaluateResourceRequest) (*proto.PolicyEvaluateResourceResponse, error) + evaluateProviderFn func(*proto.PolicyEvaluateProviderRequest) (*proto.PolicyEvaluateProviderResponse, error) + evaluateModuleFn func(*proto.PolicyEvaluateModuleRequest) (*proto.PolicyEvaluateModuleResponse, error) +} + +func (s *stubPolicyClient) EvaluateResource(ctx context.Context, req *proto.PolicyEvaluateResourceRequest, _ ...grpc.CallOption) (*proto.PolicyEvaluateResourceResponse, error) { + return s.evaluateResourceFn(req) +} + +func (s *stubPolicyClient) EvaluateProvider(ctx context.Context, req *proto.PolicyEvaluateProviderRequest, _ ...grpc.CallOption) (*proto.PolicyEvaluateProviderResponse, error) { + return s.evaluateProviderFn(req) +} + +func (s *stubPolicyClient) EvaluateModule(ctx context.Context, req *proto.PolicyEvaluateModuleRequest, _ ...grpc.CallOption) (*proto.PolicyEvaluateModuleResponse, error) { + return s.evaluateModuleFn(req) +} + +func TestClientEvaluate(t *testing.T) { + ctx := context.Background() + + var gotReq *proto.PolicyEvaluateResourceRequest + c := &client{ + client: &stubPolicyClient{ + evaluateResourceFn: func(req *proto.PolicyEvaluateResourceRequest) (*proto.PolicyEvaluateResourceResponse, error) { + gotReq = req + return &proto.PolicyEvaluateResourceResponse{ + Result: proto.EvaluateResult_ALLOW_EVALUATE_RESULT, + }, nil + }, + }, + callbackRegistry: callback.NewRegistry(), + } + + resp := c.Evaluate(ctx, EvaluationRequest[*proto.ResourceMetadata]{ + Target: "test_resource", + Attrs: cty.NilVal, + PriorAttrs: cty.NilVal, + }) + + if resp.Overall != AllowResult { + t.Fatalf("unexpected result: got %s, want %s", resp.Overall, AllowResult) + } + if len(resp.Diagnostics) != 0 { + t.Fatalf("unexpected diagnostics: %#v", resp.Diagnostics) + } + if gotReq == nil { + t.Fatal("expected EvaluateResource RPC to be called") + } + if gotReq.EvaluationId == 0 { + t.Fatal("expected non-zero evaluation id") + } +} + +func TestClientEvaluateProvider(t *testing.T) { + ctx := context.Background() + + var gotReq *proto.PolicyEvaluateProviderRequest + c := &client{ + client: &stubPolicyClient{ + evaluateProviderFn: func(req *proto.PolicyEvaluateProviderRequest) (*proto.PolicyEvaluateProviderResponse, error) { + gotReq = req + return &proto.PolicyEvaluateProviderResponse{ + Result: proto.EvaluateResult_ALLOW_EVALUATE_RESULT, + }, nil + }, + }, + callbackRegistry: callback.NewRegistry(), + } + + resp := c.EvaluateProvider(ctx, EvaluationRequest[*proto.ProviderMetadata]{ + Target: "test_provider", + Attrs: cty.NilVal, + }) + + if resp.Overall != AllowResult { + t.Fatalf("unexpected result: got %s, want %s", resp.Overall, AllowResult) + } + if len(resp.Diagnostics) != 0 { + t.Fatalf("unexpected diagnostics: %#v", resp.Diagnostics) + } + if gotReq == nil { + t.Fatal("expected EvaluateProvider RPC to be called") + } + if gotReq.ProviderType != "test_provider" { + t.Fatalf("unexpected provider type: got %q, want %q", gotReq.ProviderType, "test_provider") + } +} + +func TestClientEvaluateModule(t *testing.T) { + ctx := context.Background() + + var gotReq *proto.PolicyEvaluateModuleRequest + c := &client{ + client: &stubPolicyClient{ + evaluateModuleFn: func(req *proto.PolicyEvaluateModuleRequest) (*proto.PolicyEvaluateModuleResponse, error) { + gotReq = req + return &proto.PolicyEvaluateModuleResponse{ + Result: proto.EvaluateResult_ALLOW_EVALUATE_RESULT, + }, nil + }, + }, + callbackRegistry: callback.NewRegistry(), + } + + resp := c.EvaluateModule(ctx, EvaluationRequest[*proto.ModuleMetadata]{ + Target: "./child", + }) + + if resp.Overall != AllowResult { + t.Fatalf("unexpected result: got %s, want %s", resp.Overall, AllowResult) + } + if len(resp.Diagnostics) != 0 { + t.Fatalf("unexpected diagnostics: %#v", resp.Diagnostics) + } + if gotReq == nil { + t.Fatal("expected EvaluateModule RPC to be called") + } + if gotReq.ModuleSource != "./child" { + t.Fatalf("unexpected module source: got %q, want %q", gotReq.ModuleSource, "./child") + } +} diff --git a/internal/policy/diagnostics.go b/internal/policy/diagnostics.go new file mode 100644 index 000000000000..3e0656a9c66c --- /dev/null +++ b/internal/policy/diagnostics.go @@ -0,0 +1,151 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package policy + +import ( + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/terraform/internal/policy/proto" + "github.com/hashicorp/terraform/internal/tfdiags" + "github.com/zclconf/go-cty/cty" +) + +// PolicyExtra is a wrapper for policy information that can be used as a diagnostic extra. +// It also combines all the different extra information in the original proto diagnostic. +type PolicyExtra struct { + Policy + Severity hcl.DiagnosticSeverity + Result EvaluateResult + + // EnforceIndex is the index of the enforce block that generated this diagnostic. + // This is only set if the diagnostic was generated by an enforce block. + EnforceIndex *int32 + + Snippet *proto.Snippet + Range *proto.RangeExtra + ExpressionValues []*proto.ExpressionValue + Attribute cty.Path +} + +// DiagsFromProto converts a slice of proto.Diagnostic to tfdiags.Diagnostics, while wrapping +// the policy information in the diagnostic extra info. +func DiagsFromProto(protoDiags []*proto.Diagnostic, policy *Policy) Diagnostics { + var ret Diagnostics + if len(protoDiags) == 0 { + return ret + } + diags := proto.ToHCLDiagnostics(protoDiags) + for _, diag := range diags { + ret = append(ret, newPolicyDiagnostic(diag, policy)) + } + + return ret +} + +func newPolicyDiagnostic(diag *hcl.Diagnostic, policy *Policy) Diagnostic { + // policy diags are represented as hcl diagnostics, with the diagnostic containing + // source information related to the policy files. We convert them to a terraform diag here. + hclDiag := tfdiags.FromHCL(diag) + return Diagnostic{original: hclDiag, extra: policyExtra(diag, policy)} +} + +func policyExtra(diag *hcl.Diagnostic, policy *Policy) *PolicyExtra { + diagExtra := &PolicyExtra{Severity: diag.Severity} + if policy != nil { + diagExtra.Policy = *policy + } + if extra := extraInfo[*proto.EvaluateResultExtra](diag.Extra); extra != nil { + diagExtra.Result = ResultFromProto(extra.EvaluateResult) + } + if extra := extraInfo[*proto.SnippetExtra](diag.Extra); extra != nil { + diagExtra.Snippet = extra.Snippet + } + if extra := extraInfo[*proto.RangeExtra](diag.Extra); extra != nil { + diagExtra.Range = extra + } + if extra := extraInfo[*proto.ExpressionValuesExtra](diag.Extra); extra != nil { + diagExtra.ExpressionValues = extra.ExpressionValues + } + if extra := extraInfo[*proto.AttributeExtra](diag.Extra); extra != nil { + diagExtra.Attribute = extra.Attribute + } + + // if we have no policy information, we can use the little we have within the diagnostic itself. + if extra := extraInfo[*proto.PolicyExtra](diag.Extra); extra != nil && policy == nil { + diagExtra.Policy = Policy{ + PolicySetName: extra.PolicySet.Name, + Directory: extra.PolicySet.Path, + } + } + + return diagExtra +} + +func extraInfo[T any](extra any) T { + if extra, ok := extra.(T); ok { + return extra + } + return tfdiags.ExtraInfoNext[T](extra) +} + +// Diagnostic contains the diagnostic produced by the policy engine, as well as +// extra information within it. +type Diagnostic struct { + original tfdiags.Diagnostic + extra *PolicyExtra + + // localRange is the range of the terraform object related to the policy being evaluated + localRange *hcl.Range +} + +type Diagnostics []Diagnostic + +func (o Diagnostic) Severity() tfdiags.Severity { + return o.original.Severity() +} + +func (o Diagnostic) Description() tfdiags.Description { + return o.original.Description() +} + +// Source returns the local terraform source information. +// The policy source information should be obtained separately from the policy extra information. +func (o Diagnostic) Source() tfdiags.Source { + var ret tfdiags.Source + if o.localRange != nil { + rng := tfdiags.SourceRangeFromHCL(*o.localRange) + ret.Subject = &rng + // TODO: get exact context + ret.Context = &rng + } + return ret +} + +func (o Diagnostic) FromExpr() *tfdiags.FromExpr { + return o.original.FromExpr() +} + +func (o Diagnostic) ExtraInfo() any { + return o.extra +} + +// WithLocalRange sets the local terraform range, which will be used as the diagnostic's source information +func (o Diagnostic) WithLocalRange(rng *hcl.Range) Diagnostic { + o.localRange = rng + return o +} + +func NewErrorDiagnostic(summary, detail string, result EvaluateResult) Diagnostic { + return Diagnostic{ + original: tfdiags.Sourceless(tfdiags.Error, summary, detail), + extra: &PolicyExtra{Severity: hcl.DiagError, Result: result}, + } +} + +func (diags Diagnostics) AsTerraformDiags() tfdiags.Diagnostics { + var ret tfdiags.Diagnostics + for _, diag := range diags { + ret = ret.Append(diag) + } + return ret +} diff --git a/internal/policy/evaluateresult_string.go b/internal/policy/evaluateresult_string.go new file mode 100644 index 000000000000..4def8687c330 --- /dev/null +++ b/internal/policy/evaluateresult_string.go @@ -0,0 +1,29 @@ +// Code generated by "stringer -type=EvaluateResult"; DO NOT EDIT. + +package policy + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[InvalidResult-0] + _ = x[UnknownResult-1] + _ = x[PolicyErrorResult-2] + _ = x[AllowResult-3] + _ = x[DenyResult-4] + _ = x[SetupErrorResult-5] +} + +const _EvaluateResult_name = "InvalidResultUnknownResultPolicyErrorResultAllowResultDenyResultSetupErrorResult" + +var _EvaluateResult_index = [...]uint8{0, 13, 26, 43, 54, 64, 80} + +func (i EvaluateResult) String() string { + idx := int(i) - 0 + if i < 0 || idx >= len(_EvaluateResult_index)-1 { + return "EvaluateResult(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _EvaluateResult_name[_EvaluateResult_index[idx]:_EvaluateResult_index[idx+1]] +} diff --git a/internal/policy/mock.go b/internal/policy/mock.go new file mode 100644 index 000000000000..b89722adc01a --- /dev/null +++ b/internal/policy/mock.go @@ -0,0 +1,120 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package policy + +import ( + "context" + "sync" + + "github.com/hashicorp/terraform/internal/policy/proto" +) + +var _ Client = (*MockClient)(nil) + +// MockClient implements the Client interface, but mocks out all the +// calls for testing purposes. +type MockClient struct { + mu sync.Mutex + + // Setup method tracking + SetupCalled bool + SetupResponse *SetupResponse + SetupRequest SetupRequest + SetupFn func(context.Context, SetupRequest) SetupResponse + + // Evaluate method tracking + EvaluateCalled bool + EvaluateResponse *EvaluationResponse + EvaluateRequest EvaluationRequest[*proto.ResourceMetadata] + EvaluateFn func(context.Context, EvaluationRequest[*proto.ResourceMetadata]) EvaluationResponse + + // EvaluateProvider method tracking + EvaluateProviderCalled bool + EvaluateProviderResponse *EvaluationResponse + EvaluateProviderRequest EvaluationRequest[*proto.ProviderMetadata] + EvaluateProviderFn func(context.Context, EvaluationRequest[*proto.ProviderMetadata]) EvaluationResponse + + // EvaluateModule method tracking + EvaluateModuleCalled bool + EvaluateModuleResponse *EvaluationResponse + EvaluateModuleRequest EvaluationRequest[*proto.ModuleMetadata] + EvaluateModuleFn func(context.Context, EvaluationRequest[*proto.ModuleMetadata]) EvaluationResponse + + // Stop method tracking + StopCalled bool +} + +func (p *MockClient) beginWrite() func() { + p.mu.Lock() + return p.mu.Unlock +} + +func (p *MockClient) Setup(ctx context.Context, req SetupRequest) (resp SetupResponse) { + defer p.beginWrite()() + + p.SetupCalled = true + p.SetupRequest = req + if p.SetupFn != nil { + return p.SetupFn(ctx, req) + } + + if p.SetupResponse != nil { + return *p.SetupResponse + } + + return resp +} + +func (p *MockClient) Evaluate(ctx context.Context, r EvaluationRequest[*proto.ResourceMetadata]) (resp EvaluationResponse) { + defer p.beginWrite()() + + p.EvaluateCalled = true + p.EvaluateRequest = r + if p.EvaluateFn != nil { + return p.EvaluateFn(ctx, r) + } + + if p.EvaluateResponse != nil { + return *p.EvaluateResponse + } + + return resp +} + +func (p *MockClient) EvaluateProvider(ctx context.Context, r EvaluationRequest[*proto.ProviderMetadata]) (resp EvaluationResponse) { + defer p.beginWrite()() + + p.EvaluateProviderCalled = true + p.EvaluateProviderRequest = r + if p.EvaluateProviderFn != nil { + return p.EvaluateProviderFn(ctx, r) + } + + if p.EvaluateProviderResponse != nil { + return *p.EvaluateProviderResponse + } + + return resp +} + +func (p *MockClient) EvaluateModule(ctx context.Context, r EvaluationRequest[*proto.ModuleMetadata]) (resp EvaluationResponse) { + defer p.beginWrite()() + + p.EvaluateModuleCalled = true + p.EvaluateModuleRequest = r + if p.EvaluateModuleFn != nil { + return p.EvaluateModuleFn(ctx, r) + } + + if p.EvaluateModuleResponse != nil { + return *p.EvaluateModuleResponse + } + + return resp +} + +func (p *MockClient) Stop() { + defer p.beginWrite()() + p.StopCalled = true +} diff --git a/internal/policy/policy.go b/internal/policy/policy.go new file mode 100644 index 000000000000..bb63f78bd8d4 --- /dev/null +++ b/internal/policy/policy.go @@ -0,0 +1,194 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package policy + +import ( + "context" + "path/filepath" + + "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/terraform/internal/policy/callback" + "github.com/hashicorp/terraform/internal/policy/proto" +) + +// Client is an interface for interacting with a policy engine. +type Client interface { + Setup(context.Context, SetupRequest) SetupResponse + Evaluate(context.Context, EvaluationRequest[*proto.ResourceMetadata]) EvaluationResponse + EvaluateProvider(context.Context, EvaluationRequest[*proto.ProviderMetadata]) EvaluationResponse + EvaluateModule(context.Context, EvaluationRequest[*proto.ModuleMetadata]) EvaluationResponse + Stop() +} + +// CallbackService is an interface for registering a callback service with a policy engine. +type CallbackService interface { + RegisterCallbackService(context.Context) (*callback.Server, Diagnostics) +} + +type ( + SetupResponse struct { + // serverCapabilities contains the map of a policy path to the capabilities of the server. + serverCapabilities *proto.PolicySetupResponse_ServerCapabilities + + Diagnostics Diagnostics + } + + ServerConfiguration struct { + // File is the path to the policy file. + File string + + // RequiredVersion is the required version of the policy file. + RequiredVersion string + } +) + +func (s *SetupResponse) ServerConfigurations() []ServerConfiguration { + if s.serverCapabilities == nil { + return nil + } + ret := make([]ServerConfiguration, 0, len(s.serverCapabilities.Configurations)) + for file, capabilities := range s.serverCapabilities.Configurations { + ret = append(ret, ServerConfiguration{ + File: file, + RequiredVersion: capabilities.RequiredVersion, + }) + } + return ret +} + +type ( + SetupRequest struct { + // SourceLocations is the list of source locations to load policies from. + SourceLocations []string + + // CallbackService is the callback service to use for policy evaluation. + CallbackService uint32 + } + + EvaluationRequest[T any] struct { + // Target is the object being evaluated. + Target string + + // Attrs contains the attributes of the object being evaluated. + Attrs cty.Value + + // PriorAttrs contains the state of the object prior to the current operation. + PriorAttrs cty.Value + + // Meta is additional metadata required for evaluation. + Meta T + + Callbacks callback.Functions + } + + EnforcementResult struct { + Result EvaluateResult + Message string + Range *hcl.Range + Snippet *proto.Snippet + Policy *Policy + + // BlockIndex is the index of the enforce block within the policy originating policy. + BlockIndex int32 + + // LocalRange is the range of the terraform object being evaluated + LocalRange *hcl.Range + } + + // Policy contains information about a policy block + Policy struct { + Result EvaluateResult + Address string + + // Directory is the full path to the policy file. + Directory string + + Filename string + + Range *hcl.Range + PolicySetName string + EnforcementLevel string + } + + // EvaluationResponse contains response from a single Evaluate RPC request. + EvaluationResponse struct { + // Overall is a result of all enforcements evaluated in a single Evaluate RPC request. + Overall EvaluateResult + + // Enforcements is a slice of each enforce result in all the policies evaluated. + Enforcements []EnforcementResult + + // Policies are the policies which were evaluated for the targeted resource. + Policies []*Policy + + // A combination of Policy- and Enforcement-level diagnostics. + Diagnostics Diagnostics + } +) + +func EvaluationFromProtoResponse(overall proto.EvaluateResult, policyDetails []*proto.PolicyEvaluationDetail) EvaluationResponse { + ret := EvaluationResponse{ + Overall: ResultFromProto(overall), + Enforcements: make([]EnforcementResult, 0, len(policyDetails)), + Diagnostics: Diagnostics{}, + Policies: make([]*Policy, 0), + } + for _, protoPolicy := range policyDetails { + rng := protoPolicy.DefRange.ToHclRange() + policy := &Policy{ + Result: ResultFromProto(protoPolicy.Result), + Address: protoPolicy.Address, + Directory: protoPolicy.File, + PolicySetName: protoPolicy.PolicySetName, + Filename: filepath.Base(rng.Filename), + EnforcementLevel: protoPolicy.PolicySetEnforcement, + Range: rng.Ptr(), + } + + // We go through each diagnostic and attach the originating policy to it as an extra + policyDiags := DiagsFromProto(protoPolicy.Diagnostics, policy) + ret.Diagnostics = append(ret.Diagnostics, policyDiags...) + ret.Policies = append(ret.Policies, policy) + + for _, enforcement := range protoPolicy.EnforceResults { + result := EnforcementResult{ + Result: ResultFromProto(enforcement.Result), + Message: enforcement.Message, + Range: enforcement.Range.ToHclRange().Ptr(), + Snippet: enforcement.Snippet, + Policy: policy, + BlockIndex: enforcement.BlockIndex, + } + ret.Enforcements = append(ret.Enforcements, result) + + // Attach the enforce index to any diagnostics from the enforce block + policyDiags := DiagsFromProto(enforcement.Diagnostics, policy) + for idx := range policyDiags { + policyDiags[idx].extra.EnforceIndex = &enforcement.BlockIndex + } + ret.Diagnostics = append(ret.Diagnostics, policyDiags...) + } + } + + return ret +} + +func (r EvaluationResponse) Empty() bool { + // The policy engine sends an allow result when the object has no matched policy, consequently + // impliciting allowing it. However, such object really had no policy, and may not need to be rendered. + if r.Overall == AllowResult && len(r.Diagnostics) == 0 && len(r.Enforcements) == 0 { + return true + } + + return false +} + +func ErrorEvalFromDiags(diags []*proto.Diagnostic) EvaluationResponse { + return EvaluationResponse{ + Overall: PolicyErrorResult, + Diagnostics: DiagsFromProto(diags, nil), + } +} diff --git a/internal/policy/proto/callback.pb.go b/internal/policy/proto/callback.pb.go new file mode 100644 index 000000000000..025278c6a0dd --- /dev/null +++ b/internal/policy/proto/callback.pb.go @@ -0,0 +1,313 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v5.29.3 +// source: callback.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GetResourcesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + // evaluation_request_id is the ID of the policy evaluation request that is + // making this callback request. + EvaluationRequestId uint32 `protobuf:"varint,3,opt,name=evaluation_request_id,json=evaluationRequestId,proto3" json:"evaluation_request_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetResourcesRequest) Reset() { + *x = GetResourcesRequest{} + mi := &file_callback_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetResourcesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetResourcesRequest) ProtoMessage() {} + +func (x *GetResourcesRequest) ProtoReflect() protoreflect.Message { + mi := &file_callback_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetResourcesRequest.ProtoReflect.Descriptor instead. +func (*GetResourcesRequest) Descriptor() ([]byte, []int) { + return file_callback_proto_rawDescGZIP(), []int{0} +} + +func (x *GetResourcesRequest) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *GetResourcesRequest) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +func (x *GetResourcesRequest) GetEvaluationRequestId() uint32 { + if x != nil { + return x.EvaluationRequestId + } + return 0 +} + +type GetResourcesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Results [][]byte `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetResourcesResponse) Reset() { + *x = GetResourcesResponse{} + mi := &file_callback_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetResourcesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetResourcesResponse) ProtoMessage() {} + +func (x *GetResourcesResponse) ProtoReflect() protoreflect.Message { + mi := &file_callback_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetResourcesResponse.ProtoReflect.Descriptor instead. +func (*GetResourcesResponse) Descriptor() ([]byte, []int) { + return file_callback_proto_rawDescGZIP(), []int{1} +} + +func (x *GetResourcesResponse) GetResults() [][]byte { + if x != nil { + return x.Results + } + return nil +} + +type GetDataSourceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + // evaluation_request_id is the ID of the policy evaluation request that is + // making this callback request. + EvaluationRequestId uint32 `protobuf:"varint,3,opt,name=evaluation_request_id,json=evaluationRequestId,proto3" json:"evaluation_request_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDataSourceRequest) Reset() { + *x = GetDataSourceRequest{} + mi := &file_callback_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDataSourceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDataSourceRequest) ProtoMessage() {} + +func (x *GetDataSourceRequest) ProtoReflect() protoreflect.Message { + mi := &file_callback_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDataSourceRequest.ProtoReflect.Descriptor instead. +func (*GetDataSourceRequest) Descriptor() ([]byte, []int) { + return file_callback_proto_rawDescGZIP(), []int{2} +} + +func (x *GetDataSourceRequest) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *GetDataSourceRequest) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +func (x *GetDataSourceRequest) GetEvaluationRequestId() uint32 { + if x != nil { + return x.EvaluationRequestId + } + return 0 +} + +type GetDataSourceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result []byte `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDataSourceResponse) Reset() { + *x = GetDataSourceResponse{} + mi := &file_callback_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDataSourceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDataSourceResponse) ProtoMessage() {} + +func (x *GetDataSourceResponse) ProtoReflect() protoreflect.Message { + mi := &file_callback_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDataSourceResponse.ProtoReflect.Descriptor instead. +func (*GetDataSourceResponse) Descriptor() ([]byte, []int) { + return file_callback_proto_rawDescGZIP(), []int{3} +} + +func (x *GetDataSourceResponse) GetResult() []byte { + if x != nil { + return x.Result + } + return nil +} + +var File_callback_proto protoreflect.FileDescriptor + +const file_callback_proto_rawDesc = "" + + "\n" + + "\x0ecallback.proto\x12\x05proto\"q\n" + + "\x13GetResourcesRequest\x12\x12\n" + + "\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" + + "\x04data\x18\x02 \x01(\fR\x04data\x122\n" + + "\x15evaluation_request_id\x18\x03 \x01(\rR\x13evaluationRequestId\"0\n" + + "\x14GetResourcesResponse\x12\x18\n" + + "\aresults\x18\x01 \x03(\fR\aresults\"r\n" + + "\x14GetDataSourceRequest\x12\x12\n" + + "\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" + + "\x04data\x18\x02 \x01(\fR\x04data\x122\n" + + "\x15evaluation_request_id\x18\x03 \x01(\rR\x13evaluationRequestId\"/\n" + + "\x15GetDataSourceResponse\x12\x16\n" + + "\x06result\x18\x01 \x01(\fR\x06result2\xa6\x01\n" + + "\x0fCallbackService\x12G\n" + + "\fGetResources\x12\x1a.proto.GetResourcesRequest\x1a\x1b.proto.GetResourcesResponse\x12J\n" + + "\rGetDataSource\x12\x1b.proto.GetDataSourceRequest\x1a\x1c.proto.GetDataSourceResponseB4Z2github.com/hashicorp/terraform-policy-plugin/protob\x06proto3" + +var ( + file_callback_proto_rawDescOnce sync.Once + file_callback_proto_rawDescData []byte +) + +func file_callback_proto_rawDescGZIP() []byte { + file_callback_proto_rawDescOnce.Do(func() { + file_callback_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_callback_proto_rawDesc), len(file_callback_proto_rawDesc))) + }) + return file_callback_proto_rawDescData +} + +var file_callback_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_callback_proto_goTypes = []any{ + (*GetResourcesRequest)(nil), // 0: proto.GetResourcesRequest + (*GetResourcesResponse)(nil), // 1: proto.GetResourcesResponse + (*GetDataSourceRequest)(nil), // 2: proto.GetDataSourceRequest + (*GetDataSourceResponse)(nil), // 3: proto.GetDataSourceResponse +} +var file_callback_proto_depIdxs = []int32{ + 0, // 0: proto.CallbackService.GetResources:input_type -> proto.GetResourcesRequest + 2, // 1: proto.CallbackService.GetDataSource:input_type -> proto.GetDataSourceRequest + 1, // 2: proto.CallbackService.GetResources:output_type -> proto.GetResourcesResponse + 3, // 3: proto.CallbackService.GetDataSource:output_type -> proto.GetDataSourceResponse + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_callback_proto_init() } +func file_callback_proto_init() { + if File_callback_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_callback_proto_rawDesc), len(file_callback_proto_rawDesc)), + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_callback_proto_goTypes, + DependencyIndexes: file_callback_proto_depIdxs, + MessageInfos: file_callback_proto_msgTypes, + }.Build() + File_callback_proto = out.File + file_callback_proto_goTypes = nil + file_callback_proto_depIdxs = nil +} diff --git a/internal/policy/proto/callback.proto b/internal/policy/proto/callback.proto new file mode 100644 index 000000000000..986378537b85 --- /dev/null +++ b/internal/policy/proto/callback.proto @@ -0,0 +1,37 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +syntax = "proto3"; + +package proto; + +option go_package = "github.com/hashicorp/terraform-policy-plugin/proto"; + +service CallbackService { + rpc GetResources(GetResourcesRequest) returns (GetResourcesResponse); + rpc GetDataSource(GetDataSourceRequest) returns (GetDataSourceResponse); +} + +message GetResourcesRequest { + string type = 1; + bytes data = 2; + // evaluation_request_id is the ID of the policy evaluation request that is + // making this callback request. + uint32 evaluation_request_id = 3; +} + +message GetResourcesResponse { + repeated bytes results = 1; +} + +message GetDataSourceRequest { + string type = 1; + bytes data = 2; + // evaluation_request_id is the ID of the policy evaluation request that is + // making this callback request. + uint32 evaluation_request_id = 3; +} + +message GetDataSourceResponse { + bytes result = 1; +} diff --git a/internal/policy/proto/callback_grpc.pb.go b/internal/policy/proto/callback_grpc.pb.go new file mode 100644 index 000000000000..1e945dcd8118 --- /dev/null +++ b/internal/policy/proto/callback_grpc.pb.go @@ -0,0 +1,162 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.29.3 +// source: callback.proto + +package proto + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + CallbackService_GetResources_FullMethodName = "/proto.CallbackService/GetResources" + CallbackService_GetDataSource_FullMethodName = "/proto.CallbackService/GetDataSource" +) + +// CallbackServiceClient is the client API for CallbackService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CallbackServiceClient interface { + GetResources(ctx context.Context, in *GetResourcesRequest, opts ...grpc.CallOption) (*GetResourcesResponse, error) + GetDataSource(ctx context.Context, in *GetDataSourceRequest, opts ...grpc.CallOption) (*GetDataSourceResponse, error) +} + +type callbackServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCallbackServiceClient(cc grpc.ClientConnInterface) CallbackServiceClient { + return &callbackServiceClient{cc} +} + +func (c *callbackServiceClient) GetResources(ctx context.Context, in *GetResourcesRequest, opts ...grpc.CallOption) (*GetResourcesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetResourcesResponse) + err := c.cc.Invoke(ctx, CallbackService_GetResources_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *callbackServiceClient) GetDataSource(ctx context.Context, in *GetDataSourceRequest, opts ...grpc.CallOption) (*GetDataSourceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetDataSourceResponse) + err := c.cc.Invoke(ctx, CallbackService_GetDataSource_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CallbackServiceServer is the server API for CallbackService service. +// All implementations must embed UnimplementedCallbackServiceServer +// for forward compatibility. +type CallbackServiceServer interface { + GetResources(context.Context, *GetResourcesRequest) (*GetResourcesResponse, error) + GetDataSource(context.Context, *GetDataSourceRequest) (*GetDataSourceResponse, error) + mustEmbedUnimplementedCallbackServiceServer() +} + +// UnimplementedCallbackServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCallbackServiceServer struct{} + +func (UnimplementedCallbackServiceServer) GetResources(context.Context, *GetResourcesRequest) (*GetResourcesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetResources not implemented") +} +func (UnimplementedCallbackServiceServer) GetDataSource(context.Context, *GetDataSourceRequest) (*GetDataSourceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetDataSource not implemented") +} +func (UnimplementedCallbackServiceServer) mustEmbedUnimplementedCallbackServiceServer() {} +func (UnimplementedCallbackServiceServer) testEmbeddedByValue() {} + +// UnsafeCallbackServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CallbackServiceServer will +// result in compilation errors. +type UnsafeCallbackServiceServer interface { + mustEmbedUnimplementedCallbackServiceServer() +} + +func RegisterCallbackServiceServer(s grpc.ServiceRegistrar, srv CallbackServiceServer) { + // If the following call pancis, it indicates UnimplementedCallbackServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CallbackService_ServiceDesc, srv) +} + +func _CallbackService_GetResources_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetResourcesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CallbackServiceServer).GetResources(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CallbackService_GetResources_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CallbackServiceServer).GetResources(ctx, req.(*GetResourcesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CallbackService_GetDataSource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDataSourceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CallbackServiceServer).GetDataSource(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CallbackService_GetDataSource_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CallbackServiceServer).GetDataSource(ctx, req.(*GetDataSourceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CallbackService_ServiceDesc is the grpc.ServiceDesc for CallbackService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CallbackService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "proto.CallbackService", + HandlerType: (*CallbackServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetResources", + Handler: _CallbackService_GetResources_Handler, + }, + { + MethodName: "GetDataSource", + Handler: _CallbackService_GetDataSource_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "callback.proto", +} diff --git a/internal/policy/proto/diagnostic_extra.go b/internal/policy/proto/diagnostic_extra.go new file mode 100644 index 000000000000..96ef51521178 --- /dev/null +++ b/internal/policy/proto/diagnostic_extra.go @@ -0,0 +1,80 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package proto + +import ( + "github.com/hashicorp/terraform/internal/tfdiags" + "github.com/zclconf/go-cty/cty" +) + +var ( + _ tfdiags.DiagnosticExtraUnwrapper = (*diagnosticExtra)(nil) +) + +type diagnosticExtra struct { + next interface{} +} + +func (d *diagnosticExtra) UnwrapDiagnosticExtra() interface{} { + return d.next +} + +// SnippetExtra is an extra containing a code snippet. As source information +// is lost when the diagnostic is translated to a protocol buffer, this extra +// captures the relevant parts of the source code. +type SnippetExtra struct { + diagnosticExtra + Snippet *Snippet +} + +// RangeExtra is an extra containing the file information about the policy +// that produced this diagnostic. +type RangeExtra struct { + diagnosticExtra + Subject *Range + Context *Range +} + +// ExpressionValuesExtra is an extra containing expression values. As HCL +// evaluation contexts are lost when the diagnostic is translated to a protocol +// buffer, this extra captures the expression values from the context. +type ExpressionValuesExtra struct { + diagnosticExtra + ExpressionValues []*ExpressionValue +} + +// FunctionCallExtra is an extra containing a function call. As HCL evaluation +// contexts are lost when the diagnostic is translated to a protocol buffer, +// this extra captures the function call from the context. +type FunctionCallExtra struct { + diagnosticExtra + FunctionCall string +} + +// EvaluateResultExtra is an extra containing the evaluate result that this +// particular diagnostic would cause. +type EvaluateResultExtra struct { + diagnosticExtra + EvaluateResult EvaluateResult +} + +// PolicyExtra simply marks a diagnostic as having been produced by the policy +// engine. +type PolicyExtra struct { + diagnosticExtra + + PolicySet PolicySetMeta +} + +type PolicySetMeta struct { + diagnosticExtra + + Name string + Path string +} + +type AttributeExtra struct { + diagnosticExtra + Attribute cty.Path +} diff --git a/internal/policy/proto/diagnostics.go b/internal/policy/proto/diagnostics.go new file mode 100644 index 000000000000..45bbf1b258cb --- /dev/null +++ b/internal/policy/proto/diagnostics.go @@ -0,0 +1,180 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package proto + +import ( + "fmt" + + "github.com/hashicorp/hcl/v2" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/msgpack" +) + +func ToHCLDiagnostics(diagnostics []*Diagnostic) hcl.Diagnostics { + var diags hcl.Diagnostics + for _, diag := range diagnostics { + diags = diags.Append(diag.ToHCL()) + } + return diags +} + +func (diagnostic *Diagnostic) ToHCL() *hcl.Diagnostic { + + // every diagnostic we make will be identified as a "policy" diagnostic, and + // we might add extra metadata as well. + var extra any + + if diagnostic.PolicySet != nil { + extra = &PolicyExtra{ + PolicySet: PolicySetMeta{ + Name: diagnostic.PolicySet.Name, + Path: diagnostic.PolicySet.Path, + }, + } + } else { + extra = new(PolicyExtra) + } + + if diagnostic.Result != nil { + extra = &EvaluateResultExtra{ + diagnosticExtra: diagnosticExtra{ + next: extra, + }, + EvaluateResult: diagnostic.Result.Result, + } + } + + if diagnostic.Snippet != nil { + extra = &SnippetExtra{ + diagnosticExtra: diagnosticExtra{ + next: extra, + }, + Snippet: diagnostic.Snippet, + } + } + + if len(diagnostic.ExpressionValues) > 0 { + extra = &ExpressionValuesExtra{ + diagnosticExtra: diagnosticExtra{ + next: extra, + }, + ExpressionValues: diagnostic.ExpressionValues, + } + } + + if len(diagnostic.FunctionCall) > 0 { + extra = &FunctionCallExtra{ + diagnosticExtra: diagnosticExtra{ + next: extra, + }, + FunctionCall: diagnostic.FunctionCall, + } + } + + diag := &hcl.Diagnostic{ + Severity: diagnostic.Severity.ToHclSeverity(), + Summary: diagnostic.Summary, + Detail: diagnostic.Detail, + Extra: extra, + } + + if diagnostic.Context != nil && diag.Subject == nil { + // only set the context if the local range wasn't used. + diag.Context = diagnostic.Context.ToHclRange().Ptr() + } + + if diagnostic.Subject != nil { + + // whatever's happened, we'll record the subject and context of the + // original diagnostic in an extra. + diag.Extra = &RangeExtra{ + diagnosticExtra: diagnosticExtra{ + next: diag.Extra, + }, + Subject: diagnostic.Subject, + Context: diagnostic.Context, + } + } + + if diagnostic.Attribute != nil { + attribute, err := diagnostic.Attribute.ToCtyPath() + if err == nil { + diag.Extra = &AttributeExtra{ + diagnosticExtra: diagnosticExtra{ + next: diag.Extra, + }, + Attribute: attribute, + } + } + + // otherwise, we'll just render a diagnostic with slightly less + // information, no big deal + } + + return diag +} + +func (severity Severity) ToHclSeverity() hcl.DiagnosticSeverity { + switch severity { + case Severity_ERROR: + return hcl.DiagError + case Severity_WARNING: + return hcl.DiagWarning + default: + return hcl.DiagInvalid + } +} + +func (rng *Range) ToHclRange() hcl.Range { + if rng == nil { + return hcl.Range{} + } + return hcl.Range{ + Filename: rng.Filename, + Start: rng.Start.ToHclPos(), + End: rng.End.ToHclPos(), + } +} + +func (pos *Position) ToHclPos() hcl.Pos { + return hcl.Pos{ + Byte: int(pos.Byte), + Line: int(pos.Line), + Column: int(pos.Column), + } +} + +// ToCtyPath converts a Path to a cty.Path. +func (path *AttributePath) ToCtyPath() (cty.Path, error) { + var steps []cty.PathStep + for _, step := range path.Steps { + s, err := step.ToCtyPathStep() + if err != nil { + return nil, err + } + steps = append(steps, s) + } + return steps, nil +} + +// ToCtyPathStep converts a Step to a cty.PathStep. +func (step *AttributePath_Step) ToCtyPathStep() (cty.PathStep, error) { + switch step := step.Step.(type) { + case *AttributePath_Step_Attribute: + return cty.GetAttrStep{ + Name: step.Attribute, + }, nil + case *AttributePath_Step_Index: + index, err := msgpack.Unmarshal(step.Index, cty.DynamicPseudoType) + if err != nil { + return nil, err + } + + return cty.IndexStep{ + Key: index, + }, nil + default: + panic(fmt.Errorf("unsupported Step type: %T", step)) + } +} diff --git a/internal/policy/proto/diagnostics.pb.go b/internal/policy/proto/diagnostics.pb.go new file mode 100644 index 000000000000..e2ffe67d2ce0 --- /dev/null +++ b/internal/policy/proto/diagnostics.pb.go @@ -0,0 +1,861 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v5.29.3 +// source: diagnostics.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Severity int32 + +const ( + Severity_INVALID Severity = 0 + Severity_WARNING Severity = 1 + Severity_ERROR Severity = 2 +) + +// Enum value maps for Severity. +var ( + Severity_name = map[int32]string{ + 0: "INVALID", + 1: "WARNING", + 2: "ERROR", + } + Severity_value = map[string]int32{ + "INVALID": 0, + "WARNING": 1, + "ERROR": 2, + } +) + +func (x Severity) Enum() *Severity { + p := new(Severity) + *p = x + return p +} + +func (x Severity) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Severity) Descriptor() protoreflect.EnumDescriptor { + return file_diagnostics_proto_enumTypes[0].Descriptor() +} + +func (Severity) Type() protoreflect.EnumType { + return &file_diagnostics_proto_enumTypes[0] +} + +func (x Severity) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Severity.Descriptor instead. +func (Severity) EnumDescriptor() ([]byte, []int) { + return file_diagnostics_proto_rawDescGZIP(), []int{0} +} + +// Diagnostic is a message that represents a diagnostic message that can be +// returned by the Terraform Policy server. +type Diagnostic struct { + state protoimpl.MessageState `protogen:"open.v1"` + Severity Severity `protobuf:"varint,1,opt,name=severity,proto3,enum=proto.Severity" json:"severity,omitempty"` + Summary string `protobuf:"bytes,2,opt,name=summary,proto3" json:"summary,omitempty"` + Detail string `protobuf:"bytes,3,opt,name=detail,proto3" json:"detail,omitempty"` + Subject *Range `protobuf:"bytes,4,opt,name=subject,proto3" json:"subject,omitempty"` + Context *Range `protobuf:"bytes,5,opt,name=context,proto3" json:"context,omitempty"` + // the result allows Terraform to differentiate between a diagnostic + // originating from an error or a denied evaluation. + Result *DiagnosticResult `protobuf:"bytes,6,opt,name=result,proto3" json:"result,omitempty"` + // a diagnostic can be associated with a specific attribute in the Terraform + // resource. + Attribute *AttributePath `protobuf:"bytes,7,opt,name=attribute,proto3" json:"attribute,omitempty"` + // if the server had access to the source code that generated the diagnostic, + // it can provide a snippet of the code that caused the diagnostic. + Snippet *Snippet `protobuf:"bytes,8,opt,name=snippet,proto3" json:"snippet,omitempty"` + // if the diagnostic is associated with an expression, the values for + // variables in the expression can be provided. + ExpressionValues []*ExpressionValue `protobuf:"bytes,9,rep,name=expression_values,json=expressionValues,proto3" json:"expression_values,omitempty"` + // if the diagnostic originated inside a function call, we can provide that + // metadata as well. + FunctionCall string `protobuf:"bytes,10,opt,name=function_call,json=functionCall,proto3" json:"function_call,omitempty"` + // policy set information for the diagnostic + PolicySet *PolicySet `protobuf:"bytes,11,opt,name=policy_set,json=policySet,proto3" json:"policy_set,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Diagnostic) Reset() { + *x = Diagnostic{} + mi := &file_diagnostics_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Diagnostic) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Diagnostic) ProtoMessage() {} + +func (x *Diagnostic) ProtoReflect() protoreflect.Message { + mi := &file_diagnostics_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Diagnostic.ProtoReflect.Descriptor instead. +func (*Diagnostic) Descriptor() ([]byte, []int) { + return file_diagnostics_proto_rawDescGZIP(), []int{0} +} + +func (x *Diagnostic) GetSeverity() Severity { + if x != nil { + return x.Severity + } + return Severity_INVALID +} + +func (x *Diagnostic) GetSummary() string { + if x != nil { + return x.Summary + } + return "" +} + +func (x *Diagnostic) GetDetail() string { + if x != nil { + return x.Detail + } + return "" +} + +func (x *Diagnostic) GetSubject() *Range { + if x != nil { + return x.Subject + } + return nil +} + +func (x *Diagnostic) GetContext() *Range { + if x != nil { + return x.Context + } + return nil +} + +func (x *Diagnostic) GetResult() *DiagnosticResult { + if x != nil { + return x.Result + } + return nil +} + +func (x *Diagnostic) GetAttribute() *AttributePath { + if x != nil { + return x.Attribute + } + return nil +} + +func (x *Diagnostic) GetSnippet() *Snippet { + if x != nil { + return x.Snippet + } + return nil +} + +func (x *Diagnostic) GetExpressionValues() []*ExpressionValue { + if x != nil { + return x.ExpressionValues + } + return nil +} + +func (x *Diagnostic) GetFunctionCall() string { + if x != nil { + return x.FunctionCall + } + return "" +} + +func (x *Diagnostic) GetPolicySet() *PolicySet { + if x != nil { + return x.PolicySet + } + return nil +} + +type PolicySet struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicySet) Reset() { + *x = PolicySet{} + mi := &file_diagnostics_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicySet) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicySet) ProtoMessage() {} + +func (x *PolicySet) ProtoReflect() protoreflect.Message { + mi := &file_diagnostics_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicySet.ProtoReflect.Descriptor instead. +func (*PolicySet) Descriptor() ([]byte, []int) { + return file_diagnostics_proto_rawDescGZIP(), []int{1} +} + +func (x *PolicySet) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *PolicySet) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +type Range struct { + state protoimpl.MessageState `protogen:"open.v1"` + Filename string `protobuf:"bytes,1,opt,name=filename,proto3" json:"filename,omitempty"` + Start *Position `protobuf:"bytes,2,opt,name=start,proto3" json:"start,omitempty"` + End *Position `protobuf:"bytes,3,opt,name=end,proto3" json:"end,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Range) Reset() { + *x = Range{} + mi := &file_diagnostics_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Range) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Range) ProtoMessage() {} + +func (x *Range) ProtoReflect() protoreflect.Message { + mi := &file_diagnostics_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Range.ProtoReflect.Descriptor instead. +func (*Range) Descriptor() ([]byte, []int) { + return file_diagnostics_proto_rawDescGZIP(), []int{2} +} + +func (x *Range) GetFilename() string { + if x != nil { + return x.Filename + } + return "" +} + +func (x *Range) GetStart() *Position { + if x != nil { + return x.Start + } + return nil +} + +func (x *Range) GetEnd() *Position { + if x != nil { + return x.End + } + return nil +} + +type Position struct { + state protoimpl.MessageState `protogen:"open.v1"` + Line int64 `protobuf:"varint,1,opt,name=line,proto3" json:"line,omitempty"` + Column int64 `protobuf:"varint,2,opt,name=column,proto3" json:"column,omitempty"` + Byte int64 `protobuf:"varint,3,opt,name=byte,proto3" json:"byte,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Position) Reset() { + *x = Position{} + mi := &file_diagnostics_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Position) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Position) ProtoMessage() {} + +func (x *Position) ProtoReflect() protoreflect.Message { + mi := &file_diagnostics_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Position.ProtoReflect.Descriptor instead. +func (*Position) Descriptor() ([]byte, []int) { + return file_diagnostics_proto_rawDescGZIP(), []int{3} +} + +func (x *Position) GetLine() int64 { + if x != nil { + return x.Line + } + return 0 +} + +func (x *Position) GetColumn() int64 { + if x != nil { + return x.Column + } + return 0 +} + +func (x *Position) GetByte() int64 { + if x != nil { + return x.Byte + } + return 0 +} + +type Snippet struct { + state protoimpl.MessageState `protogen:"open.v1"` + Context *Snippet_Context `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` + Code string `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"` + StartLine int64 `protobuf:"varint,3,opt,name=start_line,json=startLine,proto3" json:"start_line,omitempty"` + HighlightStartOffset int64 `protobuf:"varint,4,opt,name=highlight_start_offset,json=highlightStartOffset,proto3" json:"highlight_start_offset,omitempty"` + HighlightEndOffset int64 `protobuf:"varint,5,opt,name=highlight_end_offset,json=highlightEndOffset,proto3" json:"highlight_end_offset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Snippet) Reset() { + *x = Snippet{} + mi := &file_diagnostics_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Snippet) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Snippet) ProtoMessage() {} + +func (x *Snippet) ProtoReflect() protoreflect.Message { + mi := &file_diagnostics_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Snippet.ProtoReflect.Descriptor instead. +func (*Snippet) Descriptor() ([]byte, []int) { + return file_diagnostics_proto_rawDescGZIP(), []int{4} +} + +func (x *Snippet) GetContext() *Snippet_Context { + if x != nil { + return x.Context + } + return nil +} + +func (x *Snippet) GetCode() string { + if x != nil { + return x.Code + } + return "" +} + +func (x *Snippet) GetStartLine() int64 { + if x != nil { + return x.StartLine + } + return 0 +} + +func (x *Snippet) GetHighlightStartOffset() int64 { + if x != nil { + return x.HighlightStartOffset + } + return 0 +} + +func (x *Snippet) GetHighlightEndOffset() int64 { + if x != nil { + return x.HighlightEndOffset + } + return 0 +} + +type AttributePath struct { + state protoimpl.MessageState `protogen:"open.v1"` + Steps []*AttributePath_Step `protobuf:"bytes,1,rep,name=steps,proto3" json:"steps,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AttributePath) Reset() { + *x = AttributePath{} + mi := &file_diagnostics_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AttributePath) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributePath) ProtoMessage() {} + +func (x *AttributePath) ProtoReflect() protoreflect.Message { + mi := &file_diagnostics_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributePath.ProtoReflect.Descriptor instead. +func (*AttributePath) Descriptor() ([]byte, []int) { + return file_diagnostics_proto_rawDescGZIP(), []int{5} +} + +func (x *AttributePath) GetSteps() []*AttributePath_Step { + if x != nil { + return x.Steps + } + return nil +} + +type ExpressionValue struct { + state protoimpl.MessageState `protogen:"open.v1"` + Path *AttributePath `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExpressionValue) Reset() { + *x = ExpressionValue{} + mi := &file_diagnostics_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExpressionValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExpressionValue) ProtoMessage() {} + +func (x *ExpressionValue) ProtoReflect() protoreflect.Message { + mi := &file_diagnostics_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExpressionValue.ProtoReflect.Descriptor instead. +func (*ExpressionValue) Descriptor() ([]byte, []int) { + return file_diagnostics_proto_rawDescGZIP(), []int{6} +} + +func (x *ExpressionValue) GetPath() *AttributePath { + if x != nil { + return x.Path + } + return nil +} + +func (x *ExpressionValue) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +type DiagnosticResult struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result EvaluateResult `protobuf:"varint,1,opt,name=result,proto3,enum=proto.EvaluateResult" json:"result,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DiagnosticResult) Reset() { + *x = DiagnosticResult{} + mi := &file_diagnostics_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DiagnosticResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiagnosticResult) ProtoMessage() {} + +func (x *DiagnosticResult) ProtoReflect() protoreflect.Message { + mi := &file_diagnostics_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiagnosticResult.ProtoReflect.Descriptor instead. +func (*DiagnosticResult) Descriptor() ([]byte, []int) { + return file_diagnostics_proto_rawDescGZIP(), []int{7} +} + +func (x *DiagnosticResult) GetResult() EvaluateResult { + if x != nil { + return x.Result + } + return EvaluateResult_INVALID_EVALUATE_RESULT +} + +type Snippet_Context struct { + state protoimpl.MessageState `protogen:"open.v1"` + Context string `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Snippet_Context) Reset() { + *x = Snippet_Context{} + mi := &file_diagnostics_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Snippet_Context) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Snippet_Context) ProtoMessage() {} + +func (x *Snippet_Context) ProtoReflect() protoreflect.Message { + mi := &file_diagnostics_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Snippet_Context.ProtoReflect.Descriptor instead. +func (*Snippet_Context) Descriptor() ([]byte, []int) { + return file_diagnostics_proto_rawDescGZIP(), []int{4, 0} +} + +func (x *Snippet_Context) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +type AttributePath_Step struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Step: + // + // *AttributePath_Step_Attribute + // *AttributePath_Step_Index + Step isAttributePath_Step_Step `protobuf_oneof:"step"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AttributePath_Step) Reset() { + *x = AttributePath_Step{} + mi := &file_diagnostics_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AttributePath_Step) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributePath_Step) ProtoMessage() {} + +func (x *AttributePath_Step) ProtoReflect() protoreflect.Message { + mi := &file_diagnostics_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributePath_Step.ProtoReflect.Descriptor instead. +func (*AttributePath_Step) Descriptor() ([]byte, []int) { + return file_diagnostics_proto_rawDescGZIP(), []int{5, 0} +} + +func (x *AttributePath_Step) GetStep() isAttributePath_Step_Step { + if x != nil { + return x.Step + } + return nil +} + +func (x *AttributePath_Step) GetAttribute() string { + if x != nil { + if x, ok := x.Step.(*AttributePath_Step_Attribute); ok { + return x.Attribute + } + } + return "" +} + +func (x *AttributePath_Step) GetIndex() []byte { + if x != nil { + if x, ok := x.Step.(*AttributePath_Step_Index); ok { + return x.Index + } + } + return nil +} + +type isAttributePath_Step_Step interface { + isAttributePath_Step_Step() +} + +type AttributePath_Step_Attribute struct { + Attribute string `protobuf:"bytes,1,opt,name=attribute,proto3,oneof"` +} + +type AttributePath_Step_Index struct { + Index []byte `protobuf:"bytes,2,opt,name=index,proto3,oneof"` +} + +func (*AttributePath_Step_Attribute) isAttributePath_Step_Step() {} + +func (*AttributePath_Step_Index) isAttributePath_Step_Step() {} + +var File_diagnostics_proto protoreflect.FileDescriptor + +const file_diagnostics_proto_rawDesc = "" + + "\n" + + "\x11diagnostics.proto\x12\x05proto\x1a\vtypes.proto\"\xe5\x03\n" + + "\n" + + "Diagnostic\x12+\n" + + "\bseverity\x18\x01 \x01(\x0e2\x0f.proto.SeverityR\bseverity\x12\x18\n" + + "\asummary\x18\x02 \x01(\tR\asummary\x12\x16\n" + + "\x06detail\x18\x03 \x01(\tR\x06detail\x12&\n" + + "\asubject\x18\x04 \x01(\v2\f.proto.RangeR\asubject\x12&\n" + + "\acontext\x18\x05 \x01(\v2\f.proto.RangeR\acontext\x12/\n" + + "\x06result\x18\x06 \x01(\v2\x17.proto.DiagnosticResultR\x06result\x122\n" + + "\tattribute\x18\a \x01(\v2\x14.proto.AttributePathR\tattribute\x12(\n" + + "\asnippet\x18\b \x01(\v2\x0e.proto.SnippetR\asnippet\x12C\n" + + "\x11expression_values\x18\t \x03(\v2\x16.proto.ExpressionValueR\x10expressionValues\x12#\n" + + "\rfunction_call\x18\n" + + " \x01(\tR\ffunctionCall\x12/\n" + + "\n" + + "policy_set\x18\v \x01(\v2\x10.proto.PolicySetR\tpolicySet\"3\n" + + "\tPolicySet\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + + "\x04path\x18\x02 \x01(\tR\x04path\"m\n" + + "\x05Range\x12\x1a\n" + + "\bfilename\x18\x01 \x01(\tR\bfilename\x12%\n" + + "\x05start\x18\x02 \x01(\v2\x0f.proto.PositionR\x05start\x12!\n" + + "\x03end\x18\x03 \x01(\v2\x0f.proto.PositionR\x03end\"J\n" + + "\bPosition\x12\x12\n" + + "\x04line\x18\x01 \x01(\x03R\x04line\x12\x16\n" + + "\x06column\x18\x02 \x01(\x03R\x06column\x12\x12\n" + + "\x04byte\x18\x03 \x01(\x03R\x04byte\"\xfb\x01\n" + + "\aSnippet\x120\n" + + "\acontext\x18\x01 \x01(\v2\x16.proto.Snippet.ContextR\acontext\x12\x12\n" + + "\x04code\x18\x02 \x01(\tR\x04code\x12\x1d\n" + + "\n" + + "start_line\x18\x03 \x01(\x03R\tstartLine\x124\n" + + "\x16highlight_start_offset\x18\x04 \x01(\x03R\x14highlightStartOffset\x120\n" + + "\x14highlight_end_offset\x18\x05 \x01(\x03R\x12highlightEndOffset\x1a#\n" + + "\aContext\x12\x18\n" + + "\acontext\x18\x01 \x01(\tR\acontext\"\x88\x01\n" + + "\rAttributePath\x12/\n" + + "\x05steps\x18\x01 \x03(\v2\x19.proto.AttributePath.StepR\x05steps\x1aF\n" + + "\x04Step\x12\x1e\n" + + "\tattribute\x18\x01 \x01(\tH\x00R\tattribute\x12\x16\n" + + "\x05index\x18\x02 \x01(\fH\x00R\x05indexB\x06\n" + + "\x04step\"Q\n" + + "\x0fExpressionValue\x12(\n" + + "\x04path\x18\x01 \x01(\v2\x14.proto.AttributePathR\x04path\x12\x14\n" + + "\x05value\x18\x02 \x01(\fR\x05value\"A\n" + + "\x10DiagnosticResult\x12-\n" + + "\x06result\x18\x01 \x01(\x0e2\x15.proto.EvaluateResultR\x06result*/\n" + + "\bSeverity\x12\v\n" + + "\aINVALID\x10\x00\x12\v\n" + + "\aWARNING\x10\x01\x12\t\n" + + "\x05ERROR\x10\x02B4Z2github.com/hashicorp/terraform-policy-plugin/protob\x06proto3" + +var ( + file_diagnostics_proto_rawDescOnce sync.Once + file_diagnostics_proto_rawDescData []byte +) + +func file_diagnostics_proto_rawDescGZIP() []byte { + file_diagnostics_proto_rawDescOnce.Do(func() { + file_diagnostics_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_diagnostics_proto_rawDesc), len(file_diagnostics_proto_rawDesc))) + }) + return file_diagnostics_proto_rawDescData +} + +var file_diagnostics_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_diagnostics_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_diagnostics_proto_goTypes = []any{ + (Severity)(0), // 0: proto.Severity + (*Diagnostic)(nil), // 1: proto.Diagnostic + (*PolicySet)(nil), // 2: proto.PolicySet + (*Range)(nil), // 3: proto.Range + (*Position)(nil), // 4: proto.Position + (*Snippet)(nil), // 5: proto.Snippet + (*AttributePath)(nil), // 6: proto.AttributePath + (*ExpressionValue)(nil), // 7: proto.ExpressionValue + (*DiagnosticResult)(nil), // 8: proto.DiagnosticResult + (*Snippet_Context)(nil), // 9: proto.Snippet.Context + (*AttributePath_Step)(nil), // 10: proto.AttributePath.Step + (EvaluateResult)(0), // 11: proto.EvaluateResult +} +var file_diagnostics_proto_depIdxs = []int32{ + 0, // 0: proto.Diagnostic.severity:type_name -> proto.Severity + 3, // 1: proto.Diagnostic.subject:type_name -> proto.Range + 3, // 2: proto.Diagnostic.context:type_name -> proto.Range + 8, // 3: proto.Diagnostic.result:type_name -> proto.DiagnosticResult + 6, // 4: proto.Diagnostic.attribute:type_name -> proto.AttributePath + 5, // 5: proto.Diagnostic.snippet:type_name -> proto.Snippet + 7, // 6: proto.Diagnostic.expression_values:type_name -> proto.ExpressionValue + 2, // 7: proto.Diagnostic.policy_set:type_name -> proto.PolicySet + 4, // 8: proto.Range.start:type_name -> proto.Position + 4, // 9: proto.Range.end:type_name -> proto.Position + 9, // 10: proto.Snippet.context:type_name -> proto.Snippet.Context + 10, // 11: proto.AttributePath.steps:type_name -> proto.AttributePath.Step + 6, // 12: proto.ExpressionValue.path:type_name -> proto.AttributePath + 11, // 13: proto.DiagnosticResult.result:type_name -> proto.EvaluateResult + 14, // [14:14] is the sub-list for method output_type + 14, // [14:14] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name +} + +func init() { file_diagnostics_proto_init() } +func file_diagnostics_proto_init() { + if File_diagnostics_proto != nil { + return + } + file_types_proto_init() + file_diagnostics_proto_msgTypes[9].OneofWrappers = []any{ + (*AttributePath_Step_Attribute)(nil), + (*AttributePath_Step_Index)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_diagnostics_proto_rawDesc), len(file_diagnostics_proto_rawDesc)), + NumEnums: 1, + NumMessages: 10, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_diagnostics_proto_goTypes, + DependencyIndexes: file_diagnostics_proto_depIdxs, + EnumInfos: file_diagnostics_proto_enumTypes, + MessageInfos: file_diagnostics_proto_msgTypes, + }.Build() + File_diagnostics_proto = out.File + file_diagnostics_proto_goTypes = nil + file_diagnostics_proto_depIdxs = nil +} diff --git a/internal/policy/proto/diagnostics.proto b/internal/policy/proto/diagnostics.proto new file mode 100644 index 000000000000..0327031d8612 --- /dev/null +++ b/internal/policy/proto/diagnostics.proto @@ -0,0 +1,98 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +syntax = "proto3"; + +package proto; + +option go_package = "github.com/hashicorp/terraform-policy-plugin/proto"; + +import "types.proto"; + +enum Severity { + INVALID = 0; + WARNING = 1; + ERROR = 2; +} + +// Diagnostic is a message that represents a diagnostic message that can be +// returned by the Terraform Policy server. +message Diagnostic { + Severity severity = 1; + string summary = 2; + string detail = 3; + + Range subject = 4; + Range context = 5; + + // the result allows Terraform to differentiate between a diagnostic + // originating from an error or a denied evaluation. + DiagnosticResult result = 6; + + // a diagnostic can be associated with a specific attribute in the Terraform + // resource. + AttributePath attribute = 7; + + // if the server had access to the source code that generated the diagnostic, + // it can provide a snippet of the code that caused the diagnostic. + Snippet snippet = 8; + + // if the diagnostic is associated with an expression, the values for + // variables in the expression can be provided. + repeated ExpressionValue expression_values = 9; + + // if the diagnostic originated inside a function call, we can provide that + // metadata as well. + string function_call = 10; + + // policy set information for the diagnostic + PolicySet policy_set = 11; +} + +message PolicySet { + string name = 1; + string path = 2; +} + +message Range { + string filename = 1; + Position start = 2; + Position end = 3; +} + +message Position { + int64 line = 1; + int64 column = 2; + int64 byte = 3; +} + +message Snippet { + message Context { + string context = 1; + } + + Context context = 1; + string code = 2; + int64 start_line = 3; + int64 highlight_start_offset = 4; + int64 highlight_end_offset = 5; +} + +message AttributePath { + message Step { + oneof step { + string attribute = 1; + bytes index = 2; + } + } + repeated Step steps = 1; +} + +message ExpressionValue { + AttributePath path = 1; + bytes value = 2; +} + +message DiagnosticResult { + EvaluateResult result = 1; +} diff --git a/internal/policy/proto/policy.pb.go b/internal/policy/proto/policy.pb.go new file mode 100644 index 000000000000..cfad4d74925d --- /dev/null +++ b/internal/policy/proto/policy.pb.go @@ -0,0 +1,1042 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v5.29.3 +// source: policy.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// SetupRequest is the message body for the Setup RPC. +type PolicySetupRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // client_capabilities should be populated by the client to indicate which + // behaviours the client is aware of. + ClientCapabilities *PolicySetupRequest_ClientCapabilities `protobuf:"bytes,1,opt,name=client_capabilities,json=clientCapabilities,proto3" json:"client_capabilities,omitempty"` + // source_locations is the list of locations that Policy should use to + // source policies. At launch, this will just be local directories but could + // in future be extended to include remote sources. + SourceLocations []string `protobuf:"bytes,2,rep,name=source_locations,json=sourceLocations,proto3" json:"source_locations,omitempty"` + // callback_service allows Terraform Policy to use the Callback Service API. + CallbackService uint32 `protobuf:"varint,3,opt,name=callback_service,json=callbackService,proto3" json:"callback_service,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicySetupRequest) Reset() { + *x = PolicySetupRequest{} + mi := &file_policy_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicySetupRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicySetupRequest) ProtoMessage() {} + +func (x *PolicySetupRequest) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicySetupRequest.ProtoReflect.Descriptor instead. +func (*PolicySetupRequest) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{0} +} + +func (x *PolicySetupRequest) GetClientCapabilities() *PolicySetupRequest_ClientCapabilities { + if x != nil { + return x.ClientCapabilities + } + return nil +} + +func (x *PolicySetupRequest) GetSourceLocations() []string { + if x != nil { + return x.SourceLocations + } + return nil +} + +func (x *PolicySetupRequest) GetCallbackService() uint32 { + if x != nil { + return x.CallbackService + } + return 0 +} + +// SetupResponse is the message response for the Setup RPC. +type PolicySetupResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // service_capabilities will be populated by the server to indicate which + // behaviours the client should expect from the server. + ServerCapabilities *PolicySetupResponse_ServerCapabilities `protobuf:"bytes,1,opt,name=server_capabilities,json=serverCapabilities,proto3" json:"server_capabilities,omitempty"` + // The diagnostics will contain any errors or warnings as a result of loading + // the Terraform Policy files. + Diagnostics []*Diagnostic `protobuf:"bytes,2,rep,name=diagnostics,proto3" json:"diagnostics,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicySetupResponse) Reset() { + *x = PolicySetupResponse{} + mi := &file_policy_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicySetupResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicySetupResponse) ProtoMessage() {} + +func (x *PolicySetupResponse) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicySetupResponse.ProtoReflect.Descriptor instead. +func (*PolicySetupResponse) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{1} +} + +func (x *PolicySetupResponse) GetServerCapabilities() *PolicySetupResponse_ServerCapabilities { + if x != nil { + return x.ServerCapabilities + } + return nil +} + +func (x *PolicySetupResponse) GetDiagnostics() []*Diagnostic { + if x != nil { + return x.Diagnostics + } + return nil +} + +// EvaluateResourceRequest is the message body for the EvaluateResource RPC. +type PolicyEvaluateResourceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // evaluation_id uniquely identifies the policy evaluation request. + // The callback service also uses this ID to retrieve the functions + // that will be called during this resource's policy evaluation. + EvaluationId uint32 `protobuf:"varint,1,opt,name=evaluation_id,json=evaluationId,proto3" json:"evaluation_id,omitempty"` + // resource is the resource type that the value represents. + Resource string `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` + // attrs contains the data that will eventually be made available within the + // resource_value data referenced within Terraform Policy files. + Attrs []byte `protobuf:"bytes,3,opt,name=attrs,proto3" json:"attrs,omitempty"` + // metadata contains the data that will eventually be made available within + // the meta object referenced within Terraform Policy files. + Metadata *ResourceMetadata `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"` + // prior_attrs contains the state of the resource prior to the current operation. + PriorAttrs []byte `protobuf:"bytes,5,opt,name=prior_attrs,json=priorAttrs,proto3" json:"prior_attrs,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyEvaluateResourceRequest) Reset() { + *x = PolicyEvaluateResourceRequest{} + mi := &file_policy_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyEvaluateResourceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyEvaluateResourceRequest) ProtoMessage() {} + +func (x *PolicyEvaluateResourceRequest) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyEvaluateResourceRequest.ProtoReflect.Descriptor instead. +func (*PolicyEvaluateResourceRequest) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{2} +} + +func (x *PolicyEvaluateResourceRequest) GetEvaluationId() uint32 { + if x != nil { + return x.EvaluationId + } + return 0 +} + +func (x *PolicyEvaluateResourceRequest) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *PolicyEvaluateResourceRequest) GetAttrs() []byte { + if x != nil { + return x.Attrs + } + return nil +} + +func (x *PolicyEvaluateResourceRequest) GetMetadata() *ResourceMetadata { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *PolicyEvaluateResourceRequest) GetPriorAttrs() []byte { + if x != nil { + return x.PriorAttrs + } + return nil +} + +// PolicyEvaluationDetail contains detailed information about a single policy evaluation +type PolicyEvaluationDetail struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Address contains the policy address/identifier + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + // Result contains the evaluation result for this specific policy + Result EvaluateResult `protobuf:"varint,2,opt,name=result,proto3,enum=proto.EvaluateResult" json:"result,omitempty"` + // File contains the source file path for this policy + File string `protobuf:"bytes,3,opt,name=file,proto3" json:"file,omitempty"` + // DefRange contains the range of the entire policy block (for highlighting when policy passes) + DefRange *Range `protobuf:"bytes,4,opt,name=def_range,json=defRange,proto3" json:"def_range,omitempty"` + // PolicySetName contains the policy set name from metadata (optional) + PolicySetName string `protobuf:"bytes,5,opt,name=policy_set_name,json=policySetName,proto3" json:"policy_set_name,omitempty"` + // PolicySetEnforcement contains framework-level enforcement preference (optional) + PolicySetEnforcement string `protobuf:"bytes,6,opt,name=policy_set_enforcement,json=policySetEnforcement,proto3" json:"policy_set_enforcement,omitempty"` + // EnforceResults contains results for each enforce block in the policy + EnforceResults []*EnforceBlockResult `protobuf:"bytes,7,rep,name=enforce_results,json=enforceResults,proto3" json:"enforce_results,omitempty"` + // Diagnostics contains policy-specific diagnostics + Diagnostics []*Diagnostic `protobuf:"bytes,8,rep,name=diagnostics,proto3" json:"diagnostics,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyEvaluationDetail) Reset() { + *x = PolicyEvaluationDetail{} + mi := &file_policy_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyEvaluationDetail) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyEvaluationDetail) ProtoMessage() {} + +func (x *PolicyEvaluationDetail) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyEvaluationDetail.ProtoReflect.Descriptor instead. +func (*PolicyEvaluationDetail) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{3} +} + +func (x *PolicyEvaluationDetail) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *PolicyEvaluationDetail) GetResult() EvaluateResult { + if x != nil { + return x.Result + } + return EvaluateResult_INVALID_EVALUATE_RESULT +} + +func (x *PolicyEvaluationDetail) GetFile() string { + if x != nil { + return x.File + } + return "" +} + +func (x *PolicyEvaluationDetail) GetDefRange() *Range { + if x != nil { + return x.DefRange + } + return nil +} + +func (x *PolicyEvaluationDetail) GetPolicySetName() string { + if x != nil { + return x.PolicySetName + } + return "" +} + +func (x *PolicyEvaluationDetail) GetPolicySetEnforcement() string { + if x != nil { + return x.PolicySetEnforcement + } + return "" +} + +func (x *PolicyEvaluationDetail) GetEnforceResults() []*EnforceBlockResult { + if x != nil { + return x.EnforceResults + } + return nil +} + +func (x *PolicyEvaluationDetail) GetDiagnostics() []*Diagnostic { + if x != nil { + return x.Diagnostics + } + return nil +} + +// EnforceBlockResult contains information about a single enforce block evaluation +type EnforceBlockResult struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Result contains the evaluation result for this enforce block + Result EvaluateResult `protobuf:"varint,1,opt,name=result,proto3,enum=proto.EvaluateResult" json:"result,omitempty"` + // ConditionPassed indicates whether the condition evaluated to true + ConditionPassed bool `protobuf:"varint,2,opt,name=condition_passed,json=conditionPassed,proto3" json:"condition_passed,omitempty"` + // Message contains info message (from info_message attribute) + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` + // Range contains the range of the enforce block (for highlighting when enforce fails) + Range *Range `protobuf:"bytes,4,opt,name=range,proto3" json:"range,omitempty"` + // Diagnostics contains enforce block specific diagnostics + Diagnostics []*Diagnostic `protobuf:"bytes,5,rep,name=diagnostics,proto3" json:"diagnostics,omitempty"` + // Snippet contains code snippet of the enforce block + Snippet *Snippet `protobuf:"bytes,6,opt,name=snippet,proto3" json:"snippet,omitempty"` + // BlockIndex contains the index of this enforce block within the policy + BlockIndex int32 `protobuf:"varint,7,opt,name=block_index,json=blockIndex,proto3" json:"block_index,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnforceBlockResult) Reset() { + *x = EnforceBlockResult{} + mi := &file_policy_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnforceBlockResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnforceBlockResult) ProtoMessage() {} + +func (x *EnforceBlockResult) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnforceBlockResult.ProtoReflect.Descriptor instead. +func (*EnforceBlockResult) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{4} +} + +func (x *EnforceBlockResult) GetResult() EvaluateResult { + if x != nil { + return x.Result + } + return EvaluateResult_INVALID_EVALUATE_RESULT +} + +func (x *EnforceBlockResult) GetConditionPassed() bool { + if x != nil { + return x.ConditionPassed + } + return false +} + +func (x *EnforceBlockResult) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *EnforceBlockResult) GetRange() *Range { + if x != nil { + return x.Range + } + return nil +} + +func (x *EnforceBlockResult) GetDiagnostics() []*Diagnostic { + if x != nil { + return x.Diagnostics + } + return nil +} + +func (x *EnforceBlockResult) GetSnippet() *Snippet { + if x != nil { + return x.Snippet + } + return nil +} + +func (x *EnforceBlockResult) GetBlockIndex() int32 { + if x != nil { + return x.BlockIndex + } + return 0 +} + +// EvaluateResourceResponse is the message response for the EvaluateResource RPC. +type PolicyEvaluateResourceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Result contains a single value for the overall result of the evaluate RPC. + // This allows users to differentiate between different types of diagnostics. + Result EvaluateResult `protobuf:"varint,1,opt,name=result,proto3,enum=proto.EvaluateResult" json:"result,omitempty"` + // PolicyDetails contains detailed information about each policy evaluation + PolicyDetails []*PolicyEvaluationDetail `protobuf:"bytes,2,rep,name=policy_details,json=policyDetails,proto3" json:"policy_details,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyEvaluateResourceResponse) Reset() { + *x = PolicyEvaluateResourceResponse{} + mi := &file_policy_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyEvaluateResourceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyEvaluateResourceResponse) ProtoMessage() {} + +func (x *PolicyEvaluateResourceResponse) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyEvaluateResourceResponse.ProtoReflect.Descriptor instead. +func (*PolicyEvaluateResourceResponse) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{5} +} + +func (x *PolicyEvaluateResourceResponse) GetResult() EvaluateResult { + if x != nil { + return x.Result + } + return EvaluateResult_INVALID_EVALUATE_RESULT +} + +func (x *PolicyEvaluateResourceResponse) GetPolicyDetails() []*PolicyEvaluationDetail { + if x != nil { + return x.PolicyDetails + } + return nil +} + +// EvaluateProviderRequest is the message body for the EvaluateProvider RPC. +type PolicyEvaluateProviderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // provider_type is the type of the provider (e.g., "aws"). + ProviderType string `protobuf:"bytes,1,opt,name=provider_type,json=providerType,proto3" json:"provider_type,omitempty"` + // attrs contains the provider configuration data that will be made available within the + // provider_value data referenced within Terraform Policy files. + Attrs []byte `protobuf:"bytes,2,opt,name=attrs,proto3" json:"attrs,omitempty"` + // metadata contains the provider metadata that will be made available within the + // provider_metadata data referenced within Terraform Policy files. + Metadata *ProviderMetadata `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyEvaluateProviderRequest) Reset() { + *x = PolicyEvaluateProviderRequest{} + mi := &file_policy_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyEvaluateProviderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyEvaluateProviderRequest) ProtoMessage() {} + +func (x *PolicyEvaluateProviderRequest) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyEvaluateProviderRequest.ProtoReflect.Descriptor instead. +func (*PolicyEvaluateProviderRequest) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{6} +} + +func (x *PolicyEvaluateProviderRequest) GetProviderType() string { + if x != nil { + return x.ProviderType + } + return "" +} + +func (x *PolicyEvaluateProviderRequest) GetAttrs() []byte { + if x != nil { + return x.Attrs + } + return nil +} + +func (x *PolicyEvaluateProviderRequest) GetMetadata() *ProviderMetadata { + if x != nil { + return x.Metadata + } + return nil +} + +// EvaluateProviderResponse is the message response for the EvaluateProvider RPC. +type PolicyEvaluateProviderResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Result contains a single value for the overall result of the evaluate provider RPC. + // This allows users to differentiate between different types of diagnostics. + Result EvaluateResult `protobuf:"varint,1,opt,name=result,proto3,enum=proto.EvaluateResult" json:"result,omitempty"` + // PolicyDetails contains detailed information about each policy evaluation + PolicyDetails []*PolicyEvaluationDetail `protobuf:"bytes,2,rep,name=policy_details,json=policyDetails,proto3" json:"policy_details,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyEvaluateProviderResponse) Reset() { + *x = PolicyEvaluateProviderResponse{} + mi := &file_policy_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyEvaluateProviderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyEvaluateProviderResponse) ProtoMessage() {} + +func (x *PolicyEvaluateProviderResponse) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyEvaluateProviderResponse.ProtoReflect.Descriptor instead. +func (*PolicyEvaluateProviderResponse) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{7} +} + +func (x *PolicyEvaluateProviderResponse) GetResult() EvaluateResult { + if x != nil { + return x.Result + } + return EvaluateResult_INVALID_EVALUATE_RESULT +} + +func (x *PolicyEvaluateProviderResponse) GetPolicyDetails() []*PolicyEvaluationDetail { + if x != nil { + return x.PolicyDetails + } + return nil +} + +// EvaluateModuleRequest is the message body for the EvaluateModule RPC. +type PolicyEvaluateModuleRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // module_source is the source for the module that is used (e.g., "./modules/s3_bucket") + ModuleSource string `protobuf:"bytes,1,opt,name=module_source,json=moduleSource,proto3" json:"module_source,omitempty"` + // attrs contains the module configuration data that will be made available within the + // module_value data referenced within Terraform Policy files. + Attrs []byte `protobuf:"bytes,2,opt,name=attrs,proto3" json:"attrs,omitempty"` + // metadata contains the module metadata that will be made available within the + // meta object referenced within module policy blocks. + Metadata *ModuleMetadata `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyEvaluateModuleRequest) Reset() { + *x = PolicyEvaluateModuleRequest{} + mi := &file_policy_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyEvaluateModuleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyEvaluateModuleRequest) ProtoMessage() {} + +func (x *PolicyEvaluateModuleRequest) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyEvaluateModuleRequest.ProtoReflect.Descriptor instead. +func (*PolicyEvaluateModuleRequest) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{8} +} + +func (x *PolicyEvaluateModuleRequest) GetModuleSource() string { + if x != nil { + return x.ModuleSource + } + return "" +} + +func (x *PolicyEvaluateModuleRequest) GetAttrs() []byte { + if x != nil { + return x.Attrs + } + return nil +} + +func (x *PolicyEvaluateModuleRequest) GetMetadata() *ModuleMetadata { + if x != nil { + return x.Metadata + } + return nil +} + +// EvaluateModuleResponse is the message response for the EvaluateModule RPC. +type PolicyEvaluateModuleResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Result contains a single value for the overall result of the evaluate module RPC. + // This allows users to differentiate between different types of diagnostics. + Result EvaluateResult `protobuf:"varint,1,opt,name=result,proto3,enum=proto.EvaluateResult" json:"result,omitempty"` + // PolicyDetails contains detailed information about each policy evaluation + PolicyDetails []*PolicyEvaluationDetail `protobuf:"bytes,2,rep,name=policy_details,json=policyDetails,proto3" json:"policy_details,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyEvaluateModuleResponse) Reset() { + *x = PolicyEvaluateModuleResponse{} + mi := &file_policy_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyEvaluateModuleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyEvaluateModuleResponse) ProtoMessage() {} + +func (x *PolicyEvaluateModuleResponse) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyEvaluateModuleResponse.ProtoReflect.Descriptor instead. +func (*PolicyEvaluateModuleResponse) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{9} +} + +func (x *PolicyEvaluateModuleResponse) GetResult() EvaluateResult { + if x != nil { + return x.Result + } + return EvaluateResult_INVALID_EVALUATE_RESULT +} + +func (x *PolicyEvaluateModuleResponse) GetPolicyDetails() []*PolicyEvaluationDetail { + if x != nil { + return x.PolicyDetails + } + return nil +} + +// ClientCapabilities are the set of capabilities the client supports. +// At launch, this is empty as we don't have any backwards or forwards +// compatibility concerns to worry about. +type PolicySetupRequest_ClientCapabilities struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicySetupRequest_ClientCapabilities) Reset() { + *x = PolicySetupRequest_ClientCapabilities{} + mi := &file_policy_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicySetupRequest_ClientCapabilities) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicySetupRequest_ClientCapabilities) ProtoMessage() {} + +func (x *PolicySetupRequest_ClientCapabilities) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicySetupRequest_ClientCapabilities.ProtoReflect.Descriptor instead. +func (*PolicySetupRequest_ClientCapabilities) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{0, 0} +} + +// A single terraform_configuration block for a Policy file. +type PolicySetupResponse_TerraformConfiguration struct { + state protoimpl.MessageState `protogen:"open.v1"` + RequiredVersion string `protobuf:"bytes,1,opt,name=required_version,json=requiredVersion,proto3" json:"required_version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicySetupResponse_TerraformConfiguration) Reset() { + *x = PolicySetupResponse_TerraformConfiguration{} + mi := &file_policy_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicySetupResponse_TerraformConfiguration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicySetupResponse_TerraformConfiguration) ProtoMessage() {} + +func (x *PolicySetupResponse_TerraformConfiguration) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicySetupResponse_TerraformConfiguration.ProtoReflect.Descriptor instead. +func (*PolicySetupResponse_TerraformConfiguration) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{1, 0} +} + +func (x *PolicySetupResponse_TerraformConfiguration) GetRequiredVersion() string { + if x != nil { + return x.RequiredVersion + } + return "" +} + +// ServerCapabilities are the set of capabilities the server supports. +type PolicySetupResponse_ServerCapabilities struct { + state protoimpl.MessageState `protogen:"open.v1"` + // All loaded terraform_configuration blocks keyed by Policy file path. + Configurations map[string]*PolicySetupResponse_TerraformConfiguration `protobuf:"bytes,1,rep,name=configurations,proto3" json:"configurations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicySetupResponse_ServerCapabilities) Reset() { + *x = PolicySetupResponse_ServerCapabilities{} + mi := &file_policy_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicySetupResponse_ServerCapabilities) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicySetupResponse_ServerCapabilities) ProtoMessage() {} + +func (x *PolicySetupResponse_ServerCapabilities) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicySetupResponse_ServerCapabilities.ProtoReflect.Descriptor instead. +func (*PolicySetupResponse_ServerCapabilities) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{1, 1} +} + +func (x *PolicySetupResponse_ServerCapabilities) GetConfigurations() map[string]*PolicySetupResponse_TerraformConfiguration { + if x != nil { + return x.Configurations + } + return nil +} + +var File_policy_proto protoreflect.FileDescriptor + +const file_policy_proto_rawDesc = "" + + "\n" + + "\fpolicy.proto\x12\x05proto\x1a\x11diagnostics.proto\x1a\vtypes.proto\"\xdf\x01\n" + + "\x12PolicySetupRequest\x12]\n" + + "\x13client_capabilities\x18\x01 \x01(\v2,.proto.PolicySetupRequest.ClientCapabilitiesR\x12clientCapabilities\x12)\n" + + "\x10source_locations\x18\x02 \x03(\tR\x0fsourceLocations\x12)\n" + + "\x10callback_service\x18\x03 \x01(\rR\x0fcallbackService\x1a\x14\n" + + "\x12ClientCapabilities\"\xe7\x03\n" + + "\x13PolicySetupResponse\x12^\n" + + "\x13server_capabilities\x18\x01 \x01(\v2-.proto.PolicySetupResponse.ServerCapabilitiesR\x12serverCapabilities\x123\n" + + "\vdiagnostics\x18\x02 \x03(\v2\x11.proto.DiagnosticR\vdiagnostics\x1aC\n" + + "\x16TerraformConfiguration\x12)\n" + + "\x10required_version\x18\x01 \x01(\tR\x0frequiredVersion\x1a\xf5\x01\n" + + "\x12ServerCapabilities\x12i\n" + + "\x0econfigurations\x18\x01 \x03(\v2A.proto.PolicySetupResponse.ServerCapabilities.ConfigurationsEntryR\x0econfigurations\x1at\n" + + "\x13ConfigurationsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12G\n" + + "\x05value\x18\x02 \x01(\v21.proto.PolicySetupResponse.TerraformConfigurationR\x05value:\x028\x01\"\xcc\x01\n" + + "\x1dPolicyEvaluateResourceRequest\x12#\n" + + "\revaluation_id\x18\x01 \x01(\rR\fevaluationId\x12\x1a\n" + + "\bresource\x18\x02 \x01(\tR\bresource\x12\x14\n" + + "\x05attrs\x18\x03 \x01(\fR\x05attrs\x123\n" + + "\bmetadata\x18\x04 \x01(\v2\x17.proto.ResourceMetadataR\bmetadata\x12\x1f\n" + + "\vprior_attrs\x18\x05 \x01(\fR\n" + + "priorAttrs\"\xf7\x02\n" + + "\x16PolicyEvaluationDetail\x12\x18\n" + + "\aaddress\x18\x01 \x01(\tR\aaddress\x12-\n" + + "\x06result\x18\x02 \x01(\x0e2\x15.proto.EvaluateResultR\x06result\x12\x12\n" + + "\x04file\x18\x03 \x01(\tR\x04file\x12)\n" + + "\tdef_range\x18\x04 \x01(\v2\f.proto.RangeR\bdefRange\x12&\n" + + "\x0fpolicy_set_name\x18\x05 \x01(\tR\rpolicySetName\x124\n" + + "\x16policy_set_enforcement\x18\x06 \x01(\tR\x14policySetEnforcement\x12B\n" + + "\x0fenforce_results\x18\a \x03(\v2\x19.proto.EnforceBlockResultR\x0eenforceResults\x123\n" + + "\vdiagnostics\x18\b \x03(\v2\x11.proto.DiagnosticR\vdiagnostics\"\xac\x02\n" + + "\x12EnforceBlockResult\x12-\n" + + "\x06result\x18\x01 \x01(\x0e2\x15.proto.EvaluateResultR\x06result\x12)\n" + + "\x10condition_passed\x18\x02 \x01(\bR\x0fconditionPassed\x12\x18\n" + + "\amessage\x18\x03 \x01(\tR\amessage\x12\"\n" + + "\x05range\x18\x04 \x01(\v2\f.proto.RangeR\x05range\x123\n" + + "\vdiagnostics\x18\x05 \x03(\v2\x11.proto.DiagnosticR\vdiagnostics\x12(\n" + + "\asnippet\x18\x06 \x01(\v2\x0e.proto.SnippetR\asnippet\x12\x1f\n" + + "\vblock_index\x18\a \x01(\x05R\n" + + "blockIndex\"\x95\x01\n" + + "\x1ePolicyEvaluateResourceResponse\x12-\n" + + "\x06result\x18\x01 \x01(\x0e2\x15.proto.EvaluateResultR\x06result\x12D\n" + + "\x0epolicy_details\x18\x02 \x03(\v2\x1d.proto.PolicyEvaluationDetailR\rpolicyDetails\"\x8f\x01\n" + + "\x1dPolicyEvaluateProviderRequest\x12#\n" + + "\rprovider_type\x18\x01 \x01(\tR\fproviderType\x12\x14\n" + + "\x05attrs\x18\x02 \x01(\fR\x05attrs\x123\n" + + "\bmetadata\x18\x03 \x01(\v2\x17.proto.ProviderMetadataR\bmetadata\"\x95\x01\n" + + "\x1ePolicyEvaluateProviderResponse\x12-\n" + + "\x06result\x18\x01 \x01(\x0e2\x15.proto.EvaluateResultR\x06result\x12D\n" + + "\x0epolicy_details\x18\x02 \x03(\v2\x1d.proto.PolicyEvaluationDetailR\rpolicyDetails\"\x8b\x01\n" + + "\x1bPolicyEvaluateModuleRequest\x12#\n" + + "\rmodule_source\x18\x01 \x01(\tR\fmoduleSource\x12\x14\n" + + "\x05attrs\x18\x02 \x01(\fR\x05attrs\x121\n" + + "\bmetadata\x18\x03 \x01(\v2\x15.proto.ModuleMetadataR\bmetadata\"\x93\x01\n" + + "\x1cPolicyEvaluateModuleResponse\x12-\n" + + "\x06result\x18\x01 \x01(\x0e2\x15.proto.EvaluateResultR\x06result\x12D\n" + + "\x0epolicy_details\x18\x02 \x03(\v2\x1d.proto.PolicyEvaluationDetailR\rpolicyDetails2\xed\x02\n" + + "\x06Policy\x12@\n" + + "\x05Setup\x12\x19.proto.PolicySetupRequest\x1a\x1a.proto.PolicySetupResponse\"\x00\x12a\n" + + "\x10EvaluateResource\x12$.proto.PolicyEvaluateResourceRequest\x1a%.proto.PolicyEvaluateResourceResponse\"\x00\x12a\n" + + "\x10EvaluateProvider\x12$.proto.PolicyEvaluateProviderRequest\x1a%.proto.PolicyEvaluateProviderResponse\"\x00\x12[\n" + + "\x0eEvaluateModule\x12\".proto.PolicyEvaluateModuleRequest\x1a#.proto.PolicyEvaluateModuleResponse\"\x00B4Z2github.com/hashicorp/terraform-policy-plugin/protob\x06proto3" + +var ( + file_policy_proto_rawDescOnce sync.Once + file_policy_proto_rawDescData []byte +) + +func file_policy_proto_rawDescGZIP() []byte { + file_policy_proto_rawDescOnce.Do(func() { + file_policy_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_policy_proto_rawDesc), len(file_policy_proto_rawDesc))) + }) + return file_policy_proto_rawDescData +} + +var file_policy_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_policy_proto_goTypes = []any{ + (*PolicySetupRequest)(nil), // 0: proto.PolicySetupRequest + (*PolicySetupResponse)(nil), // 1: proto.PolicySetupResponse + (*PolicyEvaluateResourceRequest)(nil), // 2: proto.PolicyEvaluateResourceRequest + (*PolicyEvaluationDetail)(nil), // 3: proto.PolicyEvaluationDetail + (*EnforceBlockResult)(nil), // 4: proto.EnforceBlockResult + (*PolicyEvaluateResourceResponse)(nil), // 5: proto.PolicyEvaluateResourceResponse + (*PolicyEvaluateProviderRequest)(nil), // 6: proto.PolicyEvaluateProviderRequest + (*PolicyEvaluateProviderResponse)(nil), // 7: proto.PolicyEvaluateProviderResponse + (*PolicyEvaluateModuleRequest)(nil), // 8: proto.PolicyEvaluateModuleRequest + (*PolicyEvaluateModuleResponse)(nil), // 9: proto.PolicyEvaluateModuleResponse + (*PolicySetupRequest_ClientCapabilities)(nil), // 10: proto.PolicySetupRequest.ClientCapabilities + (*PolicySetupResponse_TerraformConfiguration)(nil), // 11: proto.PolicySetupResponse.TerraformConfiguration + (*PolicySetupResponse_ServerCapabilities)(nil), // 12: proto.PolicySetupResponse.ServerCapabilities + nil, // 13: proto.PolicySetupResponse.ServerCapabilities.ConfigurationsEntry + (*Diagnostic)(nil), // 14: proto.Diagnostic + (*ResourceMetadata)(nil), // 15: proto.ResourceMetadata + (EvaluateResult)(0), // 16: proto.EvaluateResult + (*Range)(nil), // 17: proto.Range + (*Snippet)(nil), // 18: proto.Snippet + (*ProviderMetadata)(nil), // 19: proto.ProviderMetadata + (*ModuleMetadata)(nil), // 20: proto.ModuleMetadata +} +var file_policy_proto_depIdxs = []int32{ + 10, // 0: proto.PolicySetupRequest.client_capabilities:type_name -> proto.PolicySetupRequest.ClientCapabilities + 12, // 1: proto.PolicySetupResponse.server_capabilities:type_name -> proto.PolicySetupResponse.ServerCapabilities + 14, // 2: proto.PolicySetupResponse.diagnostics:type_name -> proto.Diagnostic + 15, // 3: proto.PolicyEvaluateResourceRequest.metadata:type_name -> proto.ResourceMetadata + 16, // 4: proto.PolicyEvaluationDetail.result:type_name -> proto.EvaluateResult + 17, // 5: proto.PolicyEvaluationDetail.def_range:type_name -> proto.Range + 4, // 6: proto.PolicyEvaluationDetail.enforce_results:type_name -> proto.EnforceBlockResult + 14, // 7: proto.PolicyEvaluationDetail.diagnostics:type_name -> proto.Diagnostic + 16, // 8: proto.EnforceBlockResult.result:type_name -> proto.EvaluateResult + 17, // 9: proto.EnforceBlockResult.range:type_name -> proto.Range + 14, // 10: proto.EnforceBlockResult.diagnostics:type_name -> proto.Diagnostic + 18, // 11: proto.EnforceBlockResult.snippet:type_name -> proto.Snippet + 16, // 12: proto.PolicyEvaluateResourceResponse.result:type_name -> proto.EvaluateResult + 3, // 13: proto.PolicyEvaluateResourceResponse.policy_details:type_name -> proto.PolicyEvaluationDetail + 19, // 14: proto.PolicyEvaluateProviderRequest.metadata:type_name -> proto.ProviderMetadata + 16, // 15: proto.PolicyEvaluateProviderResponse.result:type_name -> proto.EvaluateResult + 3, // 16: proto.PolicyEvaluateProviderResponse.policy_details:type_name -> proto.PolicyEvaluationDetail + 20, // 17: proto.PolicyEvaluateModuleRequest.metadata:type_name -> proto.ModuleMetadata + 16, // 18: proto.PolicyEvaluateModuleResponse.result:type_name -> proto.EvaluateResult + 3, // 19: proto.PolicyEvaluateModuleResponse.policy_details:type_name -> proto.PolicyEvaluationDetail + 13, // 20: proto.PolicySetupResponse.ServerCapabilities.configurations:type_name -> proto.PolicySetupResponse.ServerCapabilities.ConfigurationsEntry + 11, // 21: proto.PolicySetupResponse.ServerCapabilities.ConfigurationsEntry.value:type_name -> proto.PolicySetupResponse.TerraformConfiguration + 0, // 22: proto.Policy.Setup:input_type -> proto.PolicySetupRequest + 2, // 23: proto.Policy.EvaluateResource:input_type -> proto.PolicyEvaluateResourceRequest + 6, // 24: proto.Policy.EvaluateProvider:input_type -> proto.PolicyEvaluateProviderRequest + 8, // 25: proto.Policy.EvaluateModule:input_type -> proto.PolicyEvaluateModuleRequest + 1, // 26: proto.Policy.Setup:output_type -> proto.PolicySetupResponse + 5, // 27: proto.Policy.EvaluateResource:output_type -> proto.PolicyEvaluateResourceResponse + 7, // 28: proto.Policy.EvaluateProvider:output_type -> proto.PolicyEvaluateProviderResponse + 9, // 29: proto.Policy.EvaluateModule:output_type -> proto.PolicyEvaluateModuleResponse + 26, // [26:30] is the sub-list for method output_type + 22, // [22:26] is the sub-list for method input_type + 22, // [22:22] is the sub-list for extension type_name + 22, // [22:22] is the sub-list for extension extendee + 0, // [0:22] is the sub-list for field type_name +} + +func init() { file_policy_proto_init() } +func file_policy_proto_init() { + if File_policy_proto != nil { + return + } + file_diagnostics_proto_init() + file_types_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_policy_proto_rawDesc), len(file_policy_proto_rawDesc)), + NumEnums: 0, + NumMessages: 14, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_policy_proto_goTypes, + DependencyIndexes: file_policy_proto_depIdxs, + MessageInfos: file_policy_proto_msgTypes, + }.Build() + File_policy_proto = out.File + file_policy_proto_goTypes = nil + file_policy_proto_depIdxs = nil +} diff --git a/internal/policy/proto/policy.proto b/internal/policy/proto/policy.proto new file mode 100644 index 000000000000..c85859caee12 --- /dev/null +++ b/internal/policy/proto/policy.proto @@ -0,0 +1,206 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +syntax = "proto3"; + +package proto; + +option go_package = "github.com/hashicorp/terraform-policy-plugin/proto"; + +import "diagnostics.proto"; +import "types.proto"; + +// Policy is the main service published by the plugin. +service Policy { + // Setup allows the client and server to exchange known capabilities so they + // trigger alternate behaviour to maintain forwards and backwards + // compatibility. In addition, this call tells the plugin where to load the + // Terraform Policy files from. + rpc Setup(PolicySetupRequest) returns (PolicySetupResponse) {} + // Evaluate evaluates a resource against the store policies. Essentially, + // this function simply calls the equivalent Evaluate function on the + // internal Policy go-library. + rpc EvaluateResource(PolicyEvaluateResourceRequest) returns (PolicyEvaluateResourceResponse) {} + // EvaluateProvider evaluates a provider configuration against the store policies. + // This method is specifically designed for provider-level policy evaluation. + rpc EvaluateProvider(PolicyEvaluateProviderRequest) returns (PolicyEvaluateProviderResponse) {} + // EvaluateModule evaluates a module configuration against the store modules. + // This method is specifically designed for module-level policy evaluation. + rpc EvaluateModule(PolicyEvaluateModuleRequest) returns (PolicyEvaluateModuleResponse) {} +} + +// SetupRequest is the message body for the Setup RPC. +message PolicySetupRequest { + + // ClientCapabilities are the set of capabilities the client supports. + // At launch, this is empty as we don't have any backwards or forwards + // compatibility concerns to worry about. + message ClientCapabilities {} + + // client_capabilities should be populated by the client to indicate which + // behaviours the client is aware of. + ClientCapabilities client_capabilities = 1; + + // source_locations is the list of locations that Policy should use to + // source policies. At launch, this will just be local directories but could + // in future be extended to include remote sources. + repeated string source_locations = 2; + + // callback_service allows Terraform Policy to use the Callback Service API. + uint32 callback_service = 3; +} + +// SetupResponse is the message response for the Setup RPC. +message PolicySetupResponse { + + // A single terraform_configuration block for a Policy file. + message TerraformConfiguration { + string required_version = 1; + } + + // ServerCapabilities are the set of capabilities the server supports. + message ServerCapabilities { + // All loaded terraform_configuration blocks keyed by Policy file path. + map configurations = 1; + } + + // service_capabilities will be populated by the server to indicate which + // behaviours the client should expect from the server. + ServerCapabilities server_capabilities = 1; + + // The diagnostics will contain any errors or warnings as a result of loading + // the Terraform Policy files. + repeated Diagnostic diagnostics = 2; +} + + +// EvaluateResourceRequest is the message body for the EvaluateResource RPC. +message PolicyEvaluateResourceRequest { + // evaluation_id uniquely identifies the policy evaluation request. + // The callback service also uses this ID to retrieve the functions + // that will be called during this resource's policy evaluation. + uint32 evaluation_id = 1; + + // resource is the resource type that the value represents. + string resource = 2; + + // attrs contains the data that will eventually be made available within the + // resource_value data referenced within Terraform Policy files. + bytes attrs = 3; + + // metadata contains the data that will eventually be made available within + // the meta object referenced within Terraform Policy files. + ResourceMetadata metadata = 4; + + // prior_attrs contains the state of the resource prior to the current operation. + bytes prior_attrs = 5; +} + +// PolicyEvaluationDetail contains detailed information about a single policy evaluation +message PolicyEvaluationDetail { + // Address contains the policy address/identifier + string address = 1; + + // Result contains the evaluation result for this specific policy + EvaluateResult result = 2; + + // File contains the source file path for this policy + string file = 3; + + // DefRange contains the range of the entire policy block (for highlighting when policy passes) + Range def_range = 4; + + // PolicySetName contains the policy set name from metadata (optional) + string policy_set_name = 5; + + // PolicySetEnforcement contains framework-level enforcement preference (optional) + string policy_set_enforcement = 6; + + // EnforceResults contains results for each enforce block in the policy + repeated EnforceBlockResult enforce_results = 7; + + // Diagnostics contains policy-specific diagnostics + repeated Diagnostic diagnostics = 8; +} + +// EnforceBlockResult contains information about a single enforce block evaluation +message EnforceBlockResult { + // Result contains the evaluation result for this enforce block + EvaluateResult result = 1; + + // ConditionPassed indicates whether the condition evaluated to true + bool condition_passed = 2; + + // Message contains info message (from info_message attribute) + string message = 3; + + // Range contains the range of the enforce block (for highlighting when enforce fails) + Range range = 4; + + // Diagnostics contains enforce block specific diagnostics + repeated Diagnostic diagnostics = 5; + + // Snippet contains code snippet of the enforce block + Snippet snippet = 6; + + // BlockIndex contains the index of this enforce block within the policy + int32 block_index = 7; +} + +// EvaluateResourceResponse is the message response for the EvaluateResource RPC. +message PolicyEvaluateResourceResponse { + // Result contains a single value for the overall result of the evaluate RPC. + // This allows users to differentiate between different types of diagnostics. + EvaluateResult result = 1; + + // PolicyDetails contains detailed information about each policy evaluation + repeated PolicyEvaluationDetail policy_details = 2; +} + +// EvaluateProviderRequest is the message body for the EvaluateProvider RPC. +message PolicyEvaluateProviderRequest { + // provider_type is the type of the provider (e.g., "aws"). + string provider_type = 1; + + // attrs contains the provider configuration data that will be made available within the + // provider_value data referenced within Terraform Policy files. + bytes attrs = 2; + + // metadata contains the provider metadata that will be made available within the + // provider_metadata data referenced within Terraform Policy files. + ProviderMetadata metadata = 3; +} + +// EvaluateProviderResponse is the message response for the EvaluateProvider RPC. +message PolicyEvaluateProviderResponse { + // Result contains a single value for the overall result of the evaluate provider RPC. + // This allows users to differentiate between different types of diagnostics. + EvaluateResult result = 1; + + // PolicyDetails contains detailed information about each policy evaluation + repeated PolicyEvaluationDetail policy_details = 2; +} + +// EvaluateModuleRequest is the message body for the EvaluateModule RPC. +message PolicyEvaluateModuleRequest { + // module_source is the source for the module that is used (e.g., "./modules/s3_bucket") + string module_source = 1; + + // attrs contains the module configuration data that will be made available within the + // module_value data referenced within Terraform Policy files. + bytes attrs = 2; + + // metadata contains the module metadata that will be made available within the + // meta object referenced within module policy blocks. + ModuleMetadata metadata = 3; +} + +// EvaluateModuleResponse is the message response for the EvaluateModule RPC. +message PolicyEvaluateModuleResponse { + // Result contains a single value for the overall result of the evaluate module RPC. + // This allows users to differentiate between different types of diagnostics. + EvaluateResult result = 1; + + // PolicyDetails contains detailed information about each policy evaluation + repeated PolicyEvaluationDetail policy_details = 2; +} diff --git a/internal/policy/proto/policy_grpc.pb.go b/internal/policy/proto/policy_grpc.pb.go new file mode 100644 index 000000000000..1fd6ac527b15 --- /dev/null +++ b/internal/policy/proto/policy_grpc.pb.go @@ -0,0 +1,264 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.29.3 +// source: policy.proto + +package proto + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Policy_Setup_FullMethodName = "/proto.Policy/Setup" + Policy_EvaluateResource_FullMethodName = "/proto.Policy/EvaluateResource" + Policy_EvaluateProvider_FullMethodName = "/proto.Policy/EvaluateProvider" + Policy_EvaluateModule_FullMethodName = "/proto.Policy/EvaluateModule" +) + +// PolicyClient is the client API for Policy service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// Policy is the main service published by the plugin. +type PolicyClient interface { + // Setup allows the client and server to exchange known capabilities so they + // trigger alternate behaviour to maintain forwards and backwards + // compatibility. In addition, this call tells the plugin where to load the + // Terraform Policy files from. + Setup(ctx context.Context, in *PolicySetupRequest, opts ...grpc.CallOption) (*PolicySetupResponse, error) + // Evaluate evaluates a resource against the store policies. Essentially, + // this function simply calls the equivalent Evaluate function on the + // internal Policy go-library. + EvaluateResource(ctx context.Context, in *PolicyEvaluateResourceRequest, opts ...grpc.CallOption) (*PolicyEvaluateResourceResponse, error) + // EvaluateProvider evaluates a provider configuration against the store policies. + // This method is specifically designed for provider-level policy evaluation. + EvaluateProvider(ctx context.Context, in *PolicyEvaluateProviderRequest, opts ...grpc.CallOption) (*PolicyEvaluateProviderResponse, error) + // EvaluateModule evaluates a module configuration against the store modules. + // This method is specifically designed for module-level policy evaluation. + EvaluateModule(ctx context.Context, in *PolicyEvaluateModuleRequest, opts ...grpc.CallOption) (*PolicyEvaluateModuleResponse, error) +} + +type policyClient struct { + cc grpc.ClientConnInterface +} + +func NewPolicyClient(cc grpc.ClientConnInterface) PolicyClient { + return &policyClient{cc} +} + +func (c *policyClient) Setup(ctx context.Context, in *PolicySetupRequest, opts ...grpc.CallOption) (*PolicySetupResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PolicySetupResponse) + err := c.cc.Invoke(ctx, Policy_Setup_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *policyClient) EvaluateResource(ctx context.Context, in *PolicyEvaluateResourceRequest, opts ...grpc.CallOption) (*PolicyEvaluateResourceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PolicyEvaluateResourceResponse) + err := c.cc.Invoke(ctx, Policy_EvaluateResource_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *policyClient) EvaluateProvider(ctx context.Context, in *PolicyEvaluateProviderRequest, opts ...grpc.CallOption) (*PolicyEvaluateProviderResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PolicyEvaluateProviderResponse) + err := c.cc.Invoke(ctx, Policy_EvaluateProvider_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *policyClient) EvaluateModule(ctx context.Context, in *PolicyEvaluateModuleRequest, opts ...grpc.CallOption) (*PolicyEvaluateModuleResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PolicyEvaluateModuleResponse) + err := c.cc.Invoke(ctx, Policy_EvaluateModule_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PolicyServer is the server API for Policy service. +// All implementations must embed UnimplementedPolicyServer +// for forward compatibility. +// +// Policy is the main service published by the plugin. +type PolicyServer interface { + // Setup allows the client and server to exchange known capabilities so they + // trigger alternate behaviour to maintain forwards and backwards + // compatibility. In addition, this call tells the plugin where to load the + // Terraform Policy files from. + Setup(context.Context, *PolicySetupRequest) (*PolicySetupResponse, error) + // Evaluate evaluates a resource against the store policies. Essentially, + // this function simply calls the equivalent Evaluate function on the + // internal Policy go-library. + EvaluateResource(context.Context, *PolicyEvaluateResourceRequest) (*PolicyEvaluateResourceResponse, error) + // EvaluateProvider evaluates a provider configuration against the store policies. + // This method is specifically designed for provider-level policy evaluation. + EvaluateProvider(context.Context, *PolicyEvaluateProviderRequest) (*PolicyEvaluateProviderResponse, error) + // EvaluateModule evaluates a module configuration against the store modules. + // This method is specifically designed for module-level policy evaluation. + EvaluateModule(context.Context, *PolicyEvaluateModuleRequest) (*PolicyEvaluateModuleResponse, error) + mustEmbedUnimplementedPolicyServer() +} + +// UnimplementedPolicyServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedPolicyServer struct{} + +func (UnimplementedPolicyServer) Setup(context.Context, *PolicySetupRequest) (*PolicySetupResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Setup not implemented") +} +func (UnimplementedPolicyServer) EvaluateResource(context.Context, *PolicyEvaluateResourceRequest) (*PolicyEvaluateResourceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EvaluateResource not implemented") +} +func (UnimplementedPolicyServer) EvaluateProvider(context.Context, *PolicyEvaluateProviderRequest) (*PolicyEvaluateProviderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EvaluateProvider not implemented") +} +func (UnimplementedPolicyServer) EvaluateModule(context.Context, *PolicyEvaluateModuleRequest) (*PolicyEvaluateModuleResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EvaluateModule not implemented") +} +func (UnimplementedPolicyServer) mustEmbedUnimplementedPolicyServer() {} +func (UnimplementedPolicyServer) testEmbeddedByValue() {} + +// UnsafePolicyServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PolicyServer will +// result in compilation errors. +type UnsafePolicyServer interface { + mustEmbedUnimplementedPolicyServer() +} + +func RegisterPolicyServer(s grpc.ServiceRegistrar, srv PolicyServer) { + // If the following call pancis, it indicates UnimplementedPolicyServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Policy_ServiceDesc, srv) +} + +func _Policy_Setup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PolicySetupRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PolicyServer).Setup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Policy_Setup_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PolicyServer).Setup(ctx, req.(*PolicySetupRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Policy_EvaluateResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PolicyEvaluateResourceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PolicyServer).EvaluateResource(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Policy_EvaluateResource_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PolicyServer).EvaluateResource(ctx, req.(*PolicyEvaluateResourceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Policy_EvaluateProvider_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PolicyEvaluateProviderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PolicyServer).EvaluateProvider(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Policy_EvaluateProvider_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PolicyServer).EvaluateProvider(ctx, req.(*PolicyEvaluateProviderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Policy_EvaluateModule_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PolicyEvaluateModuleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PolicyServer).EvaluateModule(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Policy_EvaluateModule_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PolicyServer).EvaluateModule(ctx, req.(*PolicyEvaluateModuleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Policy_ServiceDesc is the grpc.ServiceDesc for Policy service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Policy_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "proto.Policy", + HandlerType: (*PolicyServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Setup", + Handler: _Policy_Setup_Handler, + }, + { + MethodName: "EvaluateResource", + Handler: _Policy_EvaluateResource_Handler, + }, + { + MethodName: "EvaluateProvider", + Handler: _Policy_EvaluateProvider_Handler, + }, + { + MethodName: "EvaluateModule", + Handler: _Policy_EvaluateModule_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "policy.proto", +} diff --git a/internal/policy/proto/types.pb.go b/internal/policy/proto/types.pb.go new file mode 100644 index 000000000000..9c76eeea83f0 --- /dev/null +++ b/internal/policy/proto/types.pb.go @@ -0,0 +1,462 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v5.29.3 +// source: types.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// EvaluateResult contains a single value for the overall result of the evaluate +// RPC. This allows users to differentiate between different types of +// diagnostics. +type EvaluateResult int32 + +const ( + // INVALID means the result that was set by the server wasn't recognized by + // the client. This means a future version of Terraform Policy has introduced + // a new result type, and the client isn't aware of this. Clients should + // handle this gracefully. + EvaluateResult_INVALID_EVALUATE_RESULT EvaluateResult = 0 + // UNKNOWN means the result could not be verified at this time. This means + // the client sent unknown values to the server as part of the request, and + // within Terraform means the request happened at plan time. The + // diagnostics will contain only warnings explaning which policies could + // not be evaluated. + EvaluateResult_UNKNOWN_EVALUATE_RESULT EvaluateResult = 1 + // ERROR means the policy files themselves were invalid in some way, and so + // the policies could not be evaluated. This would cover things like + // invalid references or invalid syntax. The diagnostics will contain + // errors explaining exactly what went wrong. + EvaluateResult_ERROR_EVALUATE_RESULT EvaluateResult = 2 + // ALLOW means the resource passed all relevant policies. The diagnostics + // will not contain any errors, but might contain warnings. + EvaluateResult_ALLOW_EVALUATE_RESULT EvaluateResult = 3 + // DENY means the resource failed at least one relevant policy. The + // diagnostics will contain exact explanations as to why and should be + // displayed to the user. + EvaluateResult_DENY_EVALUATE_RESULT EvaluateResult = 4 + // SETUP_ERROR means the policy engine failed to set up properly. This + // could be due to a missing or invalid configuration file, or a problem + // with the policy engine itself. The diagnostics will contain errors + // explaining exactly what went wrong. + EvaluateResult_SETUP_ERROR_EVALUATE_RESULT EvaluateResult = 5 +) + +// Enum value maps for EvaluateResult. +var ( + EvaluateResult_name = map[int32]string{ + 0: "INVALID_EVALUATE_RESULT", + 1: "UNKNOWN_EVALUATE_RESULT", + 2: "ERROR_EVALUATE_RESULT", + 3: "ALLOW_EVALUATE_RESULT", + 4: "DENY_EVALUATE_RESULT", + 5: "SETUP_ERROR_EVALUATE_RESULT", + } + EvaluateResult_value = map[string]int32{ + "INVALID_EVALUATE_RESULT": 0, + "UNKNOWN_EVALUATE_RESULT": 1, + "ERROR_EVALUATE_RESULT": 2, + "ALLOW_EVALUATE_RESULT": 3, + "DENY_EVALUATE_RESULT": 4, + "SETUP_ERROR_EVALUATE_RESULT": 5, + } +) + +func (x EvaluateResult) Enum() *EvaluateResult { + p := new(EvaluateResult) + *p = x + return p +} + +func (x EvaluateResult) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (EvaluateResult) Descriptor() protoreflect.EnumDescriptor { + return file_types_proto_enumTypes[0].Descriptor() +} + +func (EvaluateResult) Type() protoreflect.EnumType { + return &file_types_proto_enumTypes[0] +} + +func (x EvaluateResult) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use EvaluateResult.Descriptor instead. +func (EvaluateResult) EnumDescriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{0} +} + +// Operation represents the Terraform operation being performed on a resource. +type Operation int32 + +const ( + Operation_CREATE Operation = 0 + Operation_UPDATE Operation = 1 + Operation_DELETE Operation = 2 +) + +// Enum value maps for Operation. +var ( + Operation_name = map[int32]string{ + 0: "CREATE", + 1: "UPDATE", + 2: "DELETE", + } + Operation_value = map[string]int32{ + "CREATE": 0, + "UPDATE": 1, + "DELETE": 2, + } +) + +func (x Operation) Enum() *Operation { + p := new(Operation) + *p = x + return p +} + +func (x Operation) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Operation) Descriptor() protoreflect.EnumDescriptor { + return file_types_proto_enumTypes[1].Descriptor() +} + +func (Operation) Type() protoreflect.EnumType { + return &file_types_proto_enumTypes[1] +} + +func (x Operation) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Operation.Descriptor instead. +func (Operation) EnumDescriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{1} +} + +type ResourceMetadata struct { + state protoimpl.MessageState `protogen:"open.v1"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + ProviderType string `protobuf:"bytes,2,opt,name=provider_type,json=providerType,proto3" json:"provider_type,omitempty"` + Operation Operation `protobuf:"varint,3,opt,name=operation,proto3,enum=proto.Operation" json:"operation,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResourceMetadata) Reset() { + *x = ResourceMetadata{} + mi := &file_types_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResourceMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceMetadata) ProtoMessage() {} + +func (x *ResourceMetadata) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceMetadata.ProtoReflect.Descriptor instead. +func (*ResourceMetadata) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{0} +} + +func (x *ResourceMetadata) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *ResourceMetadata) GetProviderType() string { + if x != nil { + return x.ProviderType + } + return "" +} + +func (x *ResourceMetadata) GetOperation() Operation { + if x != nil { + return x.Operation + } + return Operation_CREATE +} + +type ModuleMetadata struct { + state protoimpl.MessageState `protogen:"open.v1"` + Source string `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` + Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ModuleMetadata) Reset() { + *x = ModuleMetadata{} + mi := &file_types_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ModuleMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModuleMetadata) ProtoMessage() {} + +func (x *ModuleMetadata) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModuleMetadata.ProtoReflect.Descriptor instead. +func (*ModuleMetadata) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{1} +} + +func (x *ModuleMetadata) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *ModuleMetadata) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *ModuleMetadata) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +type ProviderMetadata struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Alias string `protobuf:"bytes,2,opt,name=alias,proto3" json:"alias,omitempty"` + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + Namespace string `protobuf:"bytes,4,opt,name=namespace,proto3" json:"namespace,omitempty"` + Source string `protobuf:"bytes,5,opt,name=source,proto3" json:"source,omitempty"` + ModulePath string `protobuf:"bytes,6,opt,name=module_path,json=modulePath,proto3" json:"module_path,omitempty"` + Version string `protobuf:"bytes,7,opt,name=version,proto3" json:"version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderMetadata) Reset() { + *x = ProviderMetadata{} + mi := &file_types_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderMetadata) ProtoMessage() {} + +func (x *ProviderMetadata) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderMetadata.ProtoReflect.Descriptor instead. +func (*ProviderMetadata) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{2} +} + +func (x *ProviderMetadata) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ProviderMetadata) GetAlias() string { + if x != nil { + return x.Alias + } + return "" +} + +func (x *ProviderMetadata) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *ProviderMetadata) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *ProviderMetadata) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *ProviderMetadata) GetModulePath() string { + if x != nil { + return x.ModulePath + } + return "" +} + +func (x *ProviderMetadata) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +var File_types_proto protoreflect.FileDescriptor + +const file_types_proto_rawDesc = "" + + "\n" + + "\vtypes.proto\x12\x05proto\"{\n" + + "\x10ResourceMetadata\x12\x12\n" + + "\x04type\x18\x01 \x01(\tR\x04type\x12#\n" + + "\rprovider_type\x18\x02 \x01(\tR\fproviderType\x12.\n" + + "\toperation\x18\x03 \x01(\x0e2\x10.proto.OperationR\toperation\"\\\n" + + "\x0eModuleMetadata\x12\x16\n" + + "\x06source\x18\x01 \x01(\tR\x06source\x12\x18\n" + + "\aversion\x18\x02 \x01(\tR\aversion\x12\x18\n" + + "\aaddress\x18\x03 \x01(\tR\aaddress\"\xc1\x01\n" + + "\x10ProviderMetadata\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + + "\x05alias\x18\x02 \x01(\tR\x05alias\x12\x12\n" + + "\x04type\x18\x03 \x01(\tR\x04type\x12\x1c\n" + + "\tnamespace\x18\x04 \x01(\tR\tnamespace\x12\x16\n" + + "\x06source\x18\x05 \x01(\tR\x06source\x12\x1f\n" + + "\vmodule_path\x18\x06 \x01(\tR\n" + + "modulePath\x12\x18\n" + + "\aversion\x18\a \x01(\tR\aversion*\xbb\x01\n" + + "\x0eEvaluateResult\x12\x1b\n" + + "\x17INVALID_EVALUATE_RESULT\x10\x00\x12\x1b\n" + + "\x17UNKNOWN_EVALUATE_RESULT\x10\x01\x12\x19\n" + + "\x15ERROR_EVALUATE_RESULT\x10\x02\x12\x19\n" + + "\x15ALLOW_EVALUATE_RESULT\x10\x03\x12\x18\n" + + "\x14DENY_EVALUATE_RESULT\x10\x04\x12\x1f\n" + + "\x1bSETUP_ERROR_EVALUATE_RESULT\x10\x05*/\n" + + "\tOperation\x12\n" + + "\n" + + "\x06CREATE\x10\x00\x12\n" + + "\n" + + "\x06UPDATE\x10\x01\x12\n" + + "\n" + + "\x06DELETE\x10\x02B4Z2github.com/hashicorp/terraform-policy-plugin/protob\x06proto3" + +var ( + file_types_proto_rawDescOnce sync.Once + file_types_proto_rawDescData []byte +) + +func file_types_proto_rawDescGZIP() []byte { + file_types_proto_rawDescOnce.Do(func() { + file_types_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_types_proto_rawDesc), len(file_types_proto_rawDesc))) + }) + return file_types_proto_rawDescData +} + +var file_types_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_types_proto_goTypes = []any{ + (EvaluateResult)(0), // 0: proto.EvaluateResult + (Operation)(0), // 1: proto.Operation + (*ResourceMetadata)(nil), // 2: proto.ResourceMetadata + (*ModuleMetadata)(nil), // 3: proto.ModuleMetadata + (*ProviderMetadata)(nil), // 4: proto.ProviderMetadata +} +var file_types_proto_depIdxs = []int32{ + 1, // 0: proto.ResourceMetadata.operation:type_name -> proto.Operation + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_types_proto_init() } +func file_types_proto_init() { + if File_types_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_types_proto_rawDesc), len(file_types_proto_rawDesc)), + NumEnums: 2, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_types_proto_goTypes, + DependencyIndexes: file_types_proto_depIdxs, + EnumInfos: file_types_proto_enumTypes, + MessageInfos: file_types_proto_msgTypes, + }.Build() + File_types_proto = out.File + file_types_proto_goTypes = nil + file_types_proto_depIdxs = nil +} diff --git a/internal/policy/proto/types.proto b/internal/policy/proto/types.proto new file mode 100644 index 000000000000..52f7395969bc --- /dev/null +++ b/internal/policy/proto/types.proto @@ -0,0 +1,76 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +syntax = "proto3"; + +package proto; + +option go_package = "github.com/hashicorp/terraform-policy-plugin/proto"; + +// EvaluateResult contains a single value for the overall result of the evaluate +// RPC. This allows users to differentiate between different types of +// diagnostics. +enum EvaluateResult { + // INVALID means the result that was set by the server wasn't recognized by + // the client. This means a future version of Terraform Policy has introduced + // a new result type, and the client isn't aware of this. Clients should + // handle this gracefully. + INVALID_EVALUATE_RESULT = 0; + + // UNKNOWN means the result could not be verified at this time. This means + // the client sent unknown values to the server as part of the request, and + // within Terraform means the request happened at plan time. The + // diagnostics will contain only warnings explaning which policies could + // not be evaluated. + UNKNOWN_EVALUATE_RESULT = 1; + + // ERROR means the policy files themselves were invalid in some way, and so + // the policies could not be evaluated. This would cover things like + // invalid references or invalid syntax. The diagnostics will contain + // errors explaining exactly what went wrong. + ERROR_EVALUATE_RESULT = 2; + + // ALLOW means the resource passed all relevant policies. The diagnostics + // will not contain any errors, but might contain warnings. + ALLOW_EVALUATE_RESULT = 3; + + // DENY means the resource failed at least one relevant policy. The + // diagnostics will contain exact explanations as to why and should be + // displayed to the user. + DENY_EVALUATE_RESULT = 4; + + // SETUP_ERROR means the policy engine failed to set up properly. This + // could be due to a missing or invalid configuration file, or a problem + // with the policy engine itself. The diagnostics will contain errors + // explaining exactly what went wrong. + SETUP_ERROR_EVALUATE_RESULT = 5; +} + +// Operation represents the Terraform operation being performed on a resource. +enum Operation { + CREATE = 0; + UPDATE = 1; + DELETE = 2; +} + +message ResourceMetadata { + string type = 1; + string provider_type = 2; + Operation operation = 3; +} + +message ModuleMetadata { + string source = 1; + string version = 2; + string address = 3; +} + +message ProviderMetadata { + string name = 1; + string alias = 2; + string type = 3; + string namespace = 4; + string source = 5; + string module_path = 6; + string version = 7; +} diff --git a/internal/policy/result.go b/internal/policy/result.go new file mode 100644 index 000000000000..0a62bbd66df0 --- /dev/null +++ b/internal/policy/result.go @@ -0,0 +1,39 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package policy + +import "github.com/hashicorp/terraform/internal/policy/proto" + +type EvaluateResult int + +//go:generate go tool golang.org/x/tools/cmd/stringer -type=EvaluateResult + +const ( + InvalidResult EvaluateResult = iota + UnknownResult + PolicyErrorResult + AllowResult + DenyResult + SetupErrorResult +) + +func ResultFromProto(result proto.EvaluateResult) EvaluateResult { + switch result { + case proto.EvaluateResult_INVALID_EVALUATE_RESULT: + return InvalidResult + case proto.EvaluateResult_UNKNOWN_EVALUATE_RESULT: + return UnknownResult + case proto.EvaluateResult_ERROR_EVALUATE_RESULT: + return PolicyErrorResult + case proto.EvaluateResult_ALLOW_EVALUATE_RESULT: + return AllowResult + case proto.EvaluateResult_DENY_EVALUATE_RESULT: + return DenyResult + case proto.EvaluateResult_SETUP_ERROR_EVALUATE_RESULT: + return SetupErrorResult + default: + // should be exhaustive + panic("unhandled EvaluateResult") + } +} diff --git a/internal/policy/testing.go b/internal/policy/testing.go new file mode 100644 index 000000000000..bc4163f035ae --- /dev/null +++ b/internal/policy/testing.go @@ -0,0 +1,18 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package policy + +import ( + "testing" +) + +func NewTestMockClient(t *testing.T) *MockClient { + + ret := &MockClient{} + + ret.EvaluateProviderResponse = &EvaluationResponse{Overall: AllowResult} + ret.EvaluateResponse = &EvaluationResponse{Overall: AllowResult} + ret.EvaluateModuleResponse = &EvaluationResponse{Overall: AllowResult} + return ret +} diff --git a/internal/tfdiags/hcl.go b/internal/tfdiags/hcl.go index c0ea12a85c95..b8914eae266b 100644 --- a/internal/tfdiags/hcl.go +++ b/internal/tfdiags/hcl.go @@ -137,6 +137,13 @@ func (r SourceRange) ToHCL() hcl.Range { } } +// FromHCL converts an hcl.Diagnostic into a Diagnostic implementation. +func FromHCL(diag *hcl.Diagnostic) Diagnostic { + return hclDiagnostic{ + diag: diag, + } +} + // ToHCL constructs a hcl.Diagnostics containing the same diagnostic messages // as the receiving tfdiags.Diagnostics. // diff --git a/tools/protobuf-compile/protobuf-compile.go b/tools/protobuf-compile/protobuf-compile.go index 7f4ffda9d554..a91f5f816345 100644 --- a/tools/protobuf-compile/protobuf-compile.go +++ b/tools/protobuf-compile/protobuf-compile.go @@ -184,6 +184,46 @@ var protocSteps = []protocStep{ "stacksproto1.proto", }, }, + { + "Terraform Policy RPC API", + "internal/policy/proto", + []string{ + "--go_out=.", + "--go_opt=paths=source_relative", + "--go-grpc_out=.", + "--go-grpc_opt=paths=source_relative", + "-I./", + "./policy.proto"}, + }, + { + "Terraform Policy Callback RPC API", + "internal/policy/proto", + []string{ + "--go_out=.", + "--go_opt=paths=source_relative", + "--go-grpc_out=.", + "--go-grpc_opt=paths=source_relative", + "-I./", + "./callback.proto"}, + }, + { + "Terraform Policy Diagnostics", + "internal/policy/proto", + []string{ + "--go_out=.", + "--go_opt=paths=source_relative", + "-I./", + "./diagnostics.proto"}, + }, + { + "Terraform Policy Shared Types", + "internal/policy/proto", + []string{ + "--go_out=.", + "--go_opt=paths=source_relative", + "-I./", + "./types.proto"}, + }, } func main() { From 44344bca8833191999377e7fe6b5cd4bb6d5fb60 Mon Sep 17 00:00:00 2001 From: Samsondeen Dare Date: Tue, 19 May 2026 13:36:07 +0200 Subject: [PATCH 2/7] Address code review - More testing for the callbacks and policy client - setup a mock callback registry - Rename Evaluate to EvaluateResource --- internal/policy/callback/callback.go | 9 + internal/policy/callback/server.go | 2 +- internal/policy/callback/testing.go | 64 ++++ internal/policy/client.go | 4 +- internal/policy/client_test.go | 425 ++++++++++++++++++---- internal/policy/mock.go | 2 +- internal/policy/policy.go | 9 +- internal/policy/proto/diagnostics_test.go | 150 ++++++++ 8 files changed, 593 insertions(+), 72 deletions(-) create mode 100644 internal/policy/callback/testing.go create mode 100644 internal/policy/proto/diagnostics_test.go diff --git a/internal/policy/callback/callback.go b/internal/policy/callback/callback.go index 93f220571293..6df99ba5de48 100644 --- a/internal/policy/callback/callback.go +++ b/internal/policy/callback/callback.go @@ -15,6 +15,15 @@ type Functions struct { GetDataSource func(datasource string, attrs cty.Value) (cty.Value, error) } +type Registry interface { + Get(id uint32) (Functions, bool) + NextID() uint32 + Register(id uint32, fns Functions) + Unregister(id uint32) +} + +var _ Registry = (*InternalRegistry)(nil) + // InternalRegistry stores a mapping of evaluation IDs to callback functions, // allowing resources to register functions that will be called during their // policy evaluation. diff --git a/internal/policy/callback/server.go b/internal/policy/callback/server.go index c43e7f498aa6..33c86a2b2a3a 100644 --- a/internal/policy/callback/server.go +++ b/internal/policy/callback/server.go @@ -20,7 +20,7 @@ var ( type Server struct { ID uint32 - Registry *InternalRegistry + Registry Registry Grpc *grpc.Server proto.UnimplementedCallbackServiceServer } diff --git a/internal/policy/callback/testing.go b/internal/policy/callback/testing.go new file mode 100644 index 000000000000..299c32102ab7 --- /dev/null +++ b/internal/policy/callback/testing.go @@ -0,0 +1,64 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package callback + +import "sync" + +var _ Registry = (*MockRegistry)(nil) + +// MockRegistry implements Registry for testing purposes. +type MockRegistry struct { + mu sync.Mutex + + NextIDCalled bool + NextIDValue uint32 + + RegisterCalled bool + FunctionsStore map[uint32]Functions + UnregisterCalled bool + GetCalled bool +} + +func (m *MockRegistry) Register(id uint32, fns Functions) { + m.mu.Lock() + defer m.mu.Unlock() + m.RegisterCalled = true + if m.FunctionsStore == nil { + m.FunctionsStore = make(map[uint32]Functions) + } + m.FunctionsStore[id] = fns +} + +func (m *MockRegistry) Unregister(id uint32) { + m.mu.Lock() + defer m.mu.Unlock() + m.UnregisterCalled = true + if m.FunctionsStore != nil { + delete(m.FunctionsStore, id) + } +} + +func (m *MockRegistry) Get(id uint32) (Functions, bool) { + m.mu.Lock() + defer m.mu.Unlock() + + m.GetCalled = true + if m.FunctionsStore == nil { + return Functions{}, false + } + fns, ok := m.FunctionsStore[id] + return fns, ok +} + +func (m *MockRegistry) NextID() uint32 { + m.mu.Lock() + defer m.mu.Unlock() + + m.NextIDCalled = true + if m.NextIDValue != 0 { + return m.NextIDValue + } + m.NextIDValue++ + return m.NextIDValue +} diff --git a/internal/policy/client.go b/internal/policy/client.go index d730aacc0dfb..c99ad923a412 100644 --- a/internal/policy/client.go +++ b/internal/policy/client.go @@ -84,7 +84,7 @@ type client struct { broker *plugin.GRPCBroker client proto.PolicyClient - callbackRegistry *callback.InternalRegistry + callbackRegistry callback.Registry cbServer *callback.Server } @@ -157,7 +157,7 @@ func (c *client) Setup(ctx context.Context, req SetupRequest) SetupResponse { } } -func (c *client) Evaluate(ctx context.Context, req EvaluationRequest[*proto.ResourceMetadata]) EvaluationResponse { +func (c *client) EvaluateResource(ctx context.Context, req EvaluationRequest[*proto.ResourceMetadata]) EvaluationResponse { log.Printf("[DEBUG] Evaluating policy for resource %s", req.Target) var diags []*proto.Diagnostic diff --git a/internal/policy/client_test.go b/internal/policy/client_test.go index 49a5ec526d13..0074c7d87216 100644 --- a/internal/policy/client_test.go +++ b/internal/policy/client_test.go @@ -7,6 +7,9 @@ import ( "context" "testing" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/terraform/internal/tfdiags" "github.com/zclconf/go-cty/cty" "google.golang.org/grpc" @@ -35,106 +38,396 @@ func (s *stubPolicyClient) EvaluateModule(ctx context.Context, req *proto.Policy } func TestClientEvaluate(t *testing.T) { - ctx := context.Background() + ctx := t.Context() - var gotReq *proto.PolicyEvaluateResourceRequest - c := &client{ - client: &stubPolicyClient{ + tests := []struct { + name string + attrs cty.Value + priorAttrs cty.Value + + // an optional function to override the default evaluateResourceFn + evaluateResourceFn func(*proto.PolicyEvaluateResourceRequest) (*proto.PolicyEvaluateResourceResponse, error) + + // assertResponse is a helper function for each case to further assert the response of an evaluation + assertResponse func(*testing.T, *callback.MockRegistry, *proto.PolicyEvaluateResourceRequest, EvaluationResponse) + }{ + { + name: "nil attrs and prior attrs", + attrs: cty.NilVal, + priorAttrs: cty.NilVal, + assertResponse: func(t *testing.T, registry *callback.MockRegistry, req *proto.PolicyEvaluateResourceRequest, resp EvaluationResponse) { + t.Helper() + if resp.Overall != AllowResult { + t.Fatalf("unexpected result: got %s, want %s", resp.Overall, AllowResult) + } + if len(resp.Diagnostics) != 0 { + t.Fatalf("unexpected diagnostics: %#v", resp.Diagnostics) + } + if req == nil { + t.Fatal("expected request, got nil") + } + }, + }, + { + name: "non-nil attrs and prior attrs", + attrs: cty.ObjectVal(map[string]cty.Value{"name": cty.StringVal("test")}), + priorAttrs: cty.ObjectVal(map[string]cty.Value{"name": cty.StringVal("prior")}), + assertResponse: func(t *testing.T, registry *callback.MockRegistry, req *proto.PolicyEvaluateResourceRequest, resp EvaluationResponse) { + t.Helper() + if resp.Overall != AllowResult { + t.Fatalf("unexpected result: got %s, want %s", resp.Overall, AllowResult) + } + if len(resp.Diagnostics) != 0 { + t.Fatalf("unexpected diagnostics: %#v", resp.Diagnostics) + } + }, + }, + { + name: "transforms diagnostics from response", + attrs: cty.NilVal, + priorAttrs: cty.NilVal, evaluateResourceFn: func(req *proto.PolicyEvaluateResourceRequest) (*proto.PolicyEvaluateResourceResponse, error) { - gotReq = req return &proto.PolicyEvaluateResourceResponse{ - Result: proto.EvaluateResult_ALLOW_EVALUATE_RESULT, + Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, + PolicyDetails: []*proto.PolicyEvaluationDetail{{ + Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, + Diagnostics: []*proto.Diagnostic{{ + Severity: proto.Severity_WARNING, + Summary: "policy warning", + Detail: "transformed warning detail", + Result: &proto.DiagnosticResult{ + Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, + }, + }}, + }}, }, nil }, + assertResponse: func(t *testing.T, registry *callback.MockRegistry, req *proto.PolicyEvaluateResourceRequest, resp EvaluationResponse) { + t.Helper() + if resp.Overall != DenyResult { + t.Fatalf("unexpected result: got %s, want %s", resp.Overall, DenyResult) + } + if len(resp.Diagnostics) != 1 { + t.Fatalf("unexpected diagnostics count: got %d, want 1", len(resp.Diagnostics)) + } + + diag := resp.Diagnostics[0] + if diag.Severity() != tfdiags.Warning { + t.Fatalf("unexpected diagnostic severity: got %s, want %s", diag.Severity(), tfdiags.Warning) + } + desc := diag.Description() + if desc.Summary != "policy warning" { + t.Fatalf("unexpected diagnostic summary: got %q, want %q", desc.Summary, "policy warning") + } + if desc.Detail != "transformed warning detail" { + t.Fatalf("unexpected diagnostic detail: got %q, want %q", desc.Detail, "transformed warning detail") + } + + extra := tfdiags.ExtraInfo[*PolicyExtra](diag) + expectedExtra := &PolicyExtra{ + Severity: hcl.DiagWarning, + Result: DenyResult, + Policy: Policy{ + Result: DenyResult, + Range: &hcl.Range{}, + }, + } + if diff := cmp.Diff(extra, expectedExtra); diff != "" { + t.Fatalf("unexpected diagnostic extra: %s", diff) + } + }, }, - callbackRegistry: callback.NewRegistry(), } - resp := c.Evaluate(ctx, EvaluationRequest[*proto.ResourceMetadata]{ - Target: "test_resource", - Attrs: cty.NilVal, - PriorAttrs: cty.NilVal, - }) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var gotReq *proto.PolicyEvaluateResourceRequest + registry := &callback.MockRegistry{NextIDValue: 23} + c := &client{ + client: &stubPolicyClient{ + evaluateResourceFn: func(req *proto.PolicyEvaluateResourceRequest) (*proto.PolicyEvaluateResourceResponse, error) { + gotReq = req - if resp.Overall != AllowResult { - t.Fatalf("unexpected result: got %s, want %s", resp.Overall, AllowResult) - } - if len(resp.Diagnostics) != 0 { - t.Fatalf("unexpected diagnostics: %#v", resp.Diagnostics) - } - if gotReq == nil { - t.Fatal("expected EvaluateResource RPC to be called") - } - if gotReq.EvaluationId == 0 { - t.Fatal("expected non-zero evaluation id") + // assert that the evaluation id is registered with the callback registry + _, ok := registry.FunctionsStore[req.EvaluationId] + if !ok { + t.Fatalf("expected evaluation id %d to be registered", req.EvaluationId) + } + + if test.evaluateResourceFn != nil { + return test.evaluateResourceFn(req) + } + return &proto.PolicyEvaluateResourceResponse{ + Result: proto.EvaluateResult_ALLOW_EVALUATE_RESULT, + }, nil + }, + }, + callbackRegistry: registry, + } + + resp := c.EvaluateResource(ctx, EvaluationRequest[*proto.ResourceMetadata]{ + Target: "test_resource", + Attrs: test.attrs, + PriorAttrs: test.priorAttrs, + }) + + test.assertResponse(t, registry, gotReq, resp) + if gotReq == nil { + t.Fatal("expected EvaluateResource RPC to be called") + } + if gotReq.EvaluationId == 0 { + t.Fatal("expected non-zero evaluation id") + } + + // assert the registry functions that should have been called + if !registry.NextIDCalled { + t.Fatal("expected callback registry NextID to be called") + } + if !registry.RegisterCalled { + t.Fatal("expected callback registry Register to be called") + } + if !registry.UnregisterCalled { + t.Fatal("expected callback registry Unregister to be called") + } + + // after the evaluation, the callback registry should have been cleaned up + _, ok := registry.FunctionsStore[gotReq.EvaluationId] + if ok { + t.Fatalf("expected evaluation id %d to be unregistered", gotReq.EvaluationId) + } + }) } } func TestClientEvaluateProvider(t *testing.T) { - ctx := context.Background() + ctx := t.Context() - var gotReq *proto.PolicyEvaluateProviderRequest - c := &client{ - client: &stubPolicyClient{ + tests := []struct { + name string + attrs cty.Value + evaluateProviderFn func(*proto.PolicyEvaluateProviderRequest) (*proto.PolicyEvaluateProviderResponse, error) + assertResponse func(*testing.T, EvaluationResponse) + }{ + { + name: "nil attrs", + attrs: cty.NilVal, + assertResponse: func(t *testing.T, resp EvaluationResponse) { + t.Helper() + if resp.Overall != AllowResult { + t.Fatalf("unexpected result: got %s, want %s", resp.Overall, AllowResult) + } + if len(resp.Diagnostics) != 0 { + t.Fatalf("unexpected diagnostics: %#v", resp.Diagnostics) + } + }, + }, + { + name: "non-nil attrs", + attrs: cty.ObjectVal(map[string]cty.Value{"name": cty.StringVal("test")}), + assertResponse: func(t *testing.T, resp EvaluationResponse) { + t.Helper() + if resp.Overall != AllowResult { + t.Fatalf("unexpected result: got %s, want %s", resp.Overall, AllowResult) + } + if len(resp.Diagnostics) != 0 { + t.Fatalf("unexpected diagnostics: %#v", resp.Diagnostics) + } + }, + }, + { + name: "transforms diagnostics from response", + attrs: cty.NilVal, evaluateProviderFn: func(req *proto.PolicyEvaluateProviderRequest) (*proto.PolicyEvaluateProviderResponse, error) { - gotReq = req return &proto.PolicyEvaluateProviderResponse{ - Result: proto.EvaluateResult_ALLOW_EVALUATE_RESULT, + Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, + PolicyDetails: []*proto.PolicyEvaluationDetail{{ + Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, + Diagnostics: []*proto.Diagnostic{{ + Severity: proto.Severity_WARNING, + Summary: "policy warning", + Detail: "transformed warning detail", + Result: &proto.DiagnosticResult{ + Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, + }, + }}, + }}, }, nil }, + assertResponse: func(t *testing.T, resp EvaluationResponse) { + t.Helper() + if resp.Overall != DenyResult { + t.Fatalf("unexpected result: got %s, want %s", resp.Overall, DenyResult) + } + if len(resp.Diagnostics) != 1 { + t.Fatalf("unexpected diagnostics count: got %d, want 1", len(resp.Diagnostics)) + } + + diag := resp.Diagnostics[0] + if diag.Severity() != tfdiags.Warning { + t.Fatalf("unexpected diagnostic severity: got %s, want %s", diag.Severity(), tfdiags.Warning) + } + desc := diag.Description() + if desc.Summary != "policy warning" { + t.Fatalf("unexpected diagnostic summary: got %q, want %q", desc.Summary, "policy warning") + } + if desc.Detail != "transformed warning detail" { + t.Fatalf("unexpected diagnostic detail: got %q, want %q", desc.Detail, "transformed warning detail") + } + + extra := tfdiags.ExtraInfo[*PolicyExtra](diag) + expectedExtra := &PolicyExtra{ + Severity: hcl.DiagWarning, + Result: DenyResult, + Policy: Policy{ + Result: DenyResult, + Range: &hcl.Range{}, + }, + } + if diff := cmp.Diff(extra, expectedExtra); diff != "" { + t.Fatalf("unexpected diagnostic extra: %s", diff) + } + }, }, - callbackRegistry: callback.NewRegistry(), } - resp := c.EvaluateProvider(ctx, EvaluationRequest[*proto.ProviderMetadata]{ - Target: "test_provider", - Attrs: cty.NilVal, - }) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var gotReq *proto.PolicyEvaluateProviderRequest + c := &client{ + client: &stubPolicyClient{ + evaluateProviderFn: func(req *proto.PolicyEvaluateProviderRequest) (*proto.PolicyEvaluateProviderResponse, error) { + gotReq = req + if test.evaluateProviderFn != nil { + return test.evaluateProviderFn(req) + } + return &proto.PolicyEvaluateProviderResponse{ + Result: proto.EvaluateResult_ALLOW_EVALUATE_RESULT, + }, nil + }, + }, + callbackRegistry: callback.NewRegistry(), + } - if resp.Overall != AllowResult { - t.Fatalf("unexpected result: got %s, want %s", resp.Overall, AllowResult) - } - if len(resp.Diagnostics) != 0 { - t.Fatalf("unexpected diagnostics: %#v", resp.Diagnostics) - } - if gotReq == nil { - t.Fatal("expected EvaluateProvider RPC to be called") - } - if gotReq.ProviderType != "test_provider" { - t.Fatalf("unexpected provider type: got %q, want %q", gotReq.ProviderType, "test_provider") + resp := c.EvaluateProvider(ctx, EvaluationRequest[*proto.ProviderMetadata]{ + Target: "test_provider", + Attrs: test.attrs, + }) + + test.assertResponse(t, resp) + if gotReq == nil { + t.Fatal("expected EvaluateProvider RPC to be called") + } + if gotReq.ProviderType != "test_provider" { + t.Fatalf("unexpected provider type: got %q, want %q", gotReq.ProviderType, "test_provider") + } + }) } } func TestClientEvaluateModule(t *testing.T) { - ctx := context.Background() + ctx := t.Context() - var gotReq *proto.PolicyEvaluateModuleRequest - c := &client{ - client: &stubPolicyClient{ + tests := []struct { + name string + evaluateModuleFn func(*proto.PolicyEvaluateModuleRequest) (*proto.PolicyEvaluateModuleResponse, error) + assertResponse func(*testing.T, EvaluationResponse) + }{ + { + name: "allow response", + assertResponse: func(t *testing.T, resp EvaluationResponse) { + t.Helper() + if resp.Overall != AllowResult { + t.Fatalf("unexpected result: got %s, want %s", resp.Overall, AllowResult) + } + if len(resp.Diagnostics) != 0 { + t.Fatalf("unexpected diagnostics: %#v", resp.Diagnostics) + } + }, + }, + { + name: "transforms diagnostics from response", evaluateModuleFn: func(req *proto.PolicyEvaluateModuleRequest) (*proto.PolicyEvaluateModuleResponse, error) { - gotReq = req return &proto.PolicyEvaluateModuleResponse{ - Result: proto.EvaluateResult_ALLOW_EVALUATE_RESULT, + Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, + PolicyDetails: []*proto.PolicyEvaluationDetail{{ + Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, + Diagnostics: []*proto.Diagnostic{{ + Severity: proto.Severity_WARNING, + Summary: "policy warning", + Detail: "transformed warning detail", + Result: &proto.DiagnosticResult{ + Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, + }, + }}, + }}, }, nil }, + assertResponse: func(t *testing.T, resp EvaluationResponse) { + t.Helper() + if resp.Overall != DenyResult { + t.Fatalf("unexpected result: got %s, want %s", resp.Overall, DenyResult) + } + if len(resp.Diagnostics) != 1 { + t.Fatalf("unexpected diagnostics count: got %d, want 1", len(resp.Diagnostics)) + } + + diag := resp.Diagnostics[0] + if diag.Severity() != tfdiags.Warning { + t.Fatalf("unexpected diagnostic severity: got %s, want %s", diag.Severity(), tfdiags.Warning) + } + desc := diag.Description() + if desc.Summary != "policy warning" { + t.Fatalf("unexpected diagnostic summary: got %q, want %q", desc.Summary, "policy warning") + } + if desc.Detail != "transformed warning detail" { + t.Fatalf("unexpected diagnostic detail: got %q, want %q", desc.Detail, "transformed warning detail") + } + + extra := tfdiags.ExtraInfo[*PolicyExtra](diag) + expectedExtra := &PolicyExtra{ + Severity: hcl.DiagWarning, + Result: DenyResult, + Policy: Policy{ + Result: DenyResult, + Range: &hcl.Range{}, + }, + } + if diff := cmp.Diff(extra, expectedExtra); diff != "" { + t.Fatalf("unexpected diagnostic extra: %s", diff) + } + }, }, - callbackRegistry: callback.NewRegistry(), } - resp := c.EvaluateModule(ctx, EvaluationRequest[*proto.ModuleMetadata]{ - Target: "./child", - }) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var gotReq *proto.PolicyEvaluateModuleRequest + c := &client{ + client: &stubPolicyClient{ + evaluateModuleFn: func(req *proto.PolicyEvaluateModuleRequest) (*proto.PolicyEvaluateModuleResponse, error) { + gotReq = req + if test.evaluateModuleFn != nil { + return test.evaluateModuleFn(req) + } + return &proto.PolicyEvaluateModuleResponse{ + Result: proto.EvaluateResult_ALLOW_EVALUATE_RESULT, + }, nil + }, + }, + callbackRegistry: callback.NewRegistry(), + } - if resp.Overall != AllowResult { - t.Fatalf("unexpected result: got %s, want %s", resp.Overall, AllowResult) - } - if len(resp.Diagnostics) != 0 { - t.Fatalf("unexpected diagnostics: %#v", resp.Diagnostics) - } - if gotReq == nil { - t.Fatal("expected EvaluateModule RPC to be called") - } - if gotReq.ModuleSource != "./child" { - t.Fatalf("unexpected module source: got %q, want %q", gotReq.ModuleSource, "./child") + resp := c.EvaluateModule(ctx, EvaluationRequest[*proto.ModuleMetadata]{ + Target: "./child", + }) + + test.assertResponse(t, resp) + if gotReq == nil { + t.Fatal("expected EvaluateModule RPC to be called") + } + if gotReq.ModuleSource != "./child" { + t.Fatalf("unexpected module source: got %q, want %q", gotReq.ModuleSource, "./child") + } + }) } } diff --git a/internal/policy/mock.go b/internal/policy/mock.go index b89722adc01a..0d05ac48ccf4 100644 --- a/internal/policy/mock.go +++ b/internal/policy/mock.go @@ -66,7 +66,7 @@ func (p *MockClient) Setup(ctx context.Context, req SetupRequest) (resp SetupRes return resp } -func (p *MockClient) Evaluate(ctx context.Context, r EvaluationRequest[*proto.ResourceMetadata]) (resp EvaluationResponse) { +func (p *MockClient) EvaluateResource(ctx context.Context, r EvaluationRequest[*proto.ResourceMetadata]) (resp EvaluationResponse) { defer p.beginWrite()() p.EvaluateCalled = true diff --git a/internal/policy/policy.go b/internal/policy/policy.go index bb63f78bd8d4..8e8b08d71208 100644 --- a/internal/policy/policy.go +++ b/internal/policy/policy.go @@ -17,7 +17,7 @@ import ( // Client is an interface for interacting with a policy engine. type Client interface { Setup(context.Context, SetupRequest) SetupResponse - Evaluate(context.Context, EvaluationRequest[*proto.ResourceMetadata]) EvaluationResponse + EvaluateResource(context.Context, EvaluationRequest[*proto.ResourceMetadata]) EvaluationResponse EvaluateProvider(context.Context, EvaluationRequest[*proto.ProviderMetadata]) EvaluationResponse EvaluateModule(context.Context, EvaluationRequest[*proto.ModuleMetadata]) EvaluationResponse Stop() @@ -138,12 +138,17 @@ func EvaluationFromProtoResponse(overall proto.EvaluateResult, policyDetails []* } for _, protoPolicy := range policyDetails { rng := protoPolicy.DefRange.ToHclRange() + var filename string + if rng.Filename != "" { + filename = filepath.Base(rng.Filename) + } + policy := &Policy{ Result: ResultFromProto(protoPolicy.Result), Address: protoPolicy.Address, Directory: protoPolicy.File, PolicySetName: protoPolicy.PolicySetName, - Filename: filepath.Base(rng.Filename), + Filename: filename, EnforcementLevel: protoPolicy.PolicySetEnforcement, Range: rng.Ptr(), } diff --git a/internal/policy/proto/diagnostics_test.go b/internal/policy/proto/diagnostics_test.go new file mode 100644 index 000000000000..3fad2a4c1114 --- /dev/null +++ b/internal/policy/proto/diagnostics_test.go @@ -0,0 +1,150 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package proto + +import ( + "testing" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/terraform/internal/tfdiags" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/msgpack" +) + +func TestDiagnosticToHCL(t *testing.T) { + index, _ := msgpack.Marshal(cty.StringVal("name"), cty.DynamicPseudoType) + + protoDiag := &Diagnostic{ + Severity: Severity_WARNING, + Summary: "policy warning", + Detail: "diagnostic detail", + Subject: &Range{ + Filename: "policy.tfpolicy.hcl", + Start: &Position{Byte: 10, Line: 2, Column: 3}, + End: &Position{Byte: 20, Line: 2, Column: 13}, + }, + Context: &Range{ + Filename: "policy.tfpolicy.hcl", + Start: &Position{Byte: 1, Line: 1, Column: 1}, + End: &Position{Byte: 30, Line: 3, Column: 5}, + }, + Result: &DiagnosticResult{ + Result: EvaluateResult_DENY_EVALUATE_RESULT, + }, + Attribute: &AttributePath{ + Steps: []*AttributePath_Step{ + {Step: &AttributePath_Step_Attribute{Attribute: "tags"}}, + {Step: &AttributePath_Step_Index{Index: index}}, + }, + }, + Snippet: &Snippet{Code: "some policy code snippet"}, + ExpressionValues: []*ExpressionValue{{ + Path: &AttributePath{ + Steps: []*AttributePath_Step{{Step: &AttributePath_Step_Attribute{Attribute: "example"}}}, + }, + Value: []byte("value-bytes"), + }}, + FunctionCall: "getresources", + PolicySet: &PolicySet{ + Name: "policy-set", + Path: "/tmp/policies", + }, + } + + diag := protoDiag.ToHCL() + tfDiag := tfdiags.FromHCL(diag) + // assert the basic diagnostic fields + tfdiags.AssertDiagnosticMatch(t, tfDiag, tfdiags.FromHCL(&hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "policy warning", + Detail: "diagnostic detail", + })) + + // assert the extra information nested in the diagnostic + t.Run("attribute extra", func(t *testing.T) { + attributeExtra := tfdiags.ExtraInfo[*AttributeExtra](tfDiag) + if attributeExtra == nil { + t.Fatalf("expected attribute extra, got nil") + } + expectedPath := cty.Path{ + cty.GetAttrStep{Name: "tags"}, + cty.IndexStep{Key: cty.StringVal("name")}, + } + if !expectedPath.Equals(attributeExtra.Attribute) { + t.Fatalf("unexpected attribute path: got %#v, want %#v", attributeExtra.Attribute, expectedPath) + } + }) + + t.Run("range extra", func(t *testing.T) { + rangeExtra := tfdiags.ExtraInfo[*RangeExtra](tfDiag) + if rangeExtra == nil { + t.Fatalf("expected range extra, got nil") + } + if rangeExtra.Subject == nil || rangeExtra.Subject.Filename != "policy.tfpolicy.hcl" { + t.Fatalf("unexpected range subject: %#v", rangeExtra.Subject) + } + if rangeExtra.Context == nil || rangeExtra.Context.Start.Line != 1 { + t.Fatalf("unexpected range context: %#v", rangeExtra.Context) + } + }) + + t.Run("function call extra", func(t *testing.T) { + functionCallExtra := tfdiags.ExtraInfo[*FunctionCallExtra](tfDiag) + if functionCallExtra == nil { + t.Fatalf("expected function call extra, got nil") + } + if functionCallExtra.FunctionCall != "getresources" { + t.Fatalf("unexpected function call extra: got %q, want %q", functionCallExtra.FunctionCall, "getresources") + } + }) + + t.Run("expression values extra", func(t *testing.T) { + expressionValuesExtra := tfdiags.ExtraInfo[*ExpressionValuesExtra](tfDiag) + if expressionValuesExtra == nil { + t.Fatalf("expected expression values extra, got nil") + } + if len(expressionValuesExtra.ExpressionValues) != 1 { + t.Fatalf("unexpected expression values count: got %d, want 1", len(expressionValuesExtra.ExpressionValues)) + } + if expressionValuesExtra.ExpressionValues[0].Path == nil || len(expressionValuesExtra.ExpressionValues[0].Path.Steps) != 1 { + t.Fatalf("unexpected expression value path: %#v", expressionValuesExtra.ExpressionValues[0].Path) + } + if expressionValuesExtra.ExpressionValues[0].Value == nil { + t.Fatalf("expected expression value bytes to be present") + } + }) + + t.Run("snippet extra", func(t *testing.T) { + snippetExtra := tfdiags.ExtraInfo[*SnippetExtra](tfDiag) + if snippetExtra == nil { + t.Fatalf("expected snippet extra, got nil") + } + if snippetExtra.Snippet == nil || snippetExtra.Snippet.Code != "some policy code snippet" { + t.Fatalf("unexpected snippet extra: %#v", snippetExtra.Snippet) + } + }) + + t.Run("evaluate result extra", func(t *testing.T) { + resultExtra := tfdiags.ExtraInfo[*EvaluateResultExtra](tfDiag) + if resultExtra == nil { + t.Fatalf("expected evaluate result extra, got nil") + } + if resultExtra.EvaluateResult != EvaluateResult_DENY_EVALUATE_RESULT { + t.Fatalf("unexpected evaluate result: got %s, want %s", resultExtra.EvaluateResult, EvaluateResult_DENY_EVALUATE_RESULT) + } + }) + + t.Run("policy extra", func(t *testing.T) { + policyExtra := tfdiags.ExtraInfo[*PolicyExtra](tfDiag) + if policyExtra == nil { + t.Fatalf("expected policy extra, got nil") + } + if policyExtra.PolicySet.Name != "policy-set" { + t.Fatalf("unexpected policy set name: got %q, want %q", policyExtra.PolicySet.Name, "policy-set") + } + if policyExtra.PolicySet.Path != "/tmp/policies" { + t.Fatalf("unexpected policy set path: got %q, want %q", policyExtra.PolicySet.Path, "/tmp/policies") + } + }) +} From 8f901a2cc805eea4f2aadf3c0a90e20cf338e68a Mon Sep 17 00:00:00 2001 From: Samsondeen Dare Date: Tue, 19 May 2026 14:11:53 +0200 Subject: [PATCH 3/7] Detailed protocol definitions - More comments - Use optional for Snippet --- internal/policy/callback/server.go | 6 +- internal/policy/proto/callback.pb.go | 34 +++--- internal/policy/proto/callback.proto | 4 +- internal/policy/proto/diagnostics.pb.go | 142 ++++++++-------------- internal/policy/proto/diagnostics.proto | 22 +++- internal/policy/proto/diagnostics_test.go | 14 ++- 6 files changed, 103 insertions(+), 119 deletions(-) diff --git a/internal/policy/callback/server.go b/internal/policy/callback/server.go index 33c86a2b2a3a..05225d212d73 100644 --- a/internal/policy/callback/server.go +++ b/internal/policy/callback/server.go @@ -26,7 +26,7 @@ type Server struct { } func (s *Server) GetResources(_ context.Context, request *proto.GetResourcesRequest) (*proto.GetResourcesResponse, error) { - attrs, err := msgpack.Unmarshal(request.Data, cty.DynamicPseudoType) + attrs, err := msgpack.Unmarshal(request.Attributes, cty.DynamicPseudoType) if err != nil { return nil, fmt.Errorf("failed to unserialize data: %w", err) } @@ -54,7 +54,7 @@ func (s *Server) GetResources(_ context.Context, request *proto.GetResourcesRequ } func (s *Server) GetDataSource(_ context.Context, request *proto.GetDataSourceRequest) (*proto.GetDataSourceResponse, error) { - attrs, err := msgpack.Unmarshal(request.Data, cty.DynamicPseudoType) + config, err := msgpack.Unmarshal(request.Config, cty.DynamicPseudoType) if err != nil { return nil, fmt.Errorf("failed to unserialize data: %w", err) } @@ -63,7 +63,7 @@ func (s *Server) GetDataSource(_ context.Context, request *proto.GetDataSourceRe if !ok { return nil, fmt.Errorf("no callback registered for ID %d (request type: %s)", request.EvaluationRequestId, request.Type) } - datasource, err := functions.GetDataSource(request.Type, attrs) + datasource, err := functions.GetDataSource(request.Type, config) if err != nil { return nil, err } diff --git a/internal/policy/proto/callback.pb.go b/internal/policy/proto/callback.pb.go index 025278c6a0dd..c30284708f99 100644 --- a/internal/policy/proto/callback.pb.go +++ b/internal/policy/proto/callback.pb.go @@ -25,9 +25,9 @@ const ( ) type GetResourcesRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` - Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Attributes []byte `protobuf:"bytes,2,opt,name=attributes,proto3" json:"attributes,omitempty"` // evaluation_request_id is the ID of the policy evaluation request that is // making this callback request. EvaluationRequestId uint32 `protobuf:"varint,3,opt,name=evaluation_request_id,json=evaluationRequestId,proto3" json:"evaluation_request_id,omitempty"` @@ -72,9 +72,9 @@ func (x *GetResourcesRequest) GetType() string { return "" } -func (x *GetResourcesRequest) GetData() []byte { +func (x *GetResourcesRequest) GetAttributes() []byte { if x != nil { - return x.Data + return x.Attributes } return nil } @@ -131,9 +131,9 @@ func (x *GetResourcesResponse) GetResults() [][]byte { } type GetDataSourceRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` - Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Config []byte `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` // evaluation_request_id is the ID of the policy evaluation request that is // making this callback request. EvaluationRequestId uint32 `protobuf:"varint,3,opt,name=evaluation_request_id,json=evaluationRequestId,proto3" json:"evaluation_request_id,omitempty"` @@ -178,9 +178,9 @@ func (x *GetDataSourceRequest) GetType() string { return "" } -func (x *GetDataSourceRequest) GetData() []byte { +func (x *GetDataSourceRequest) GetConfig() []byte { if x != nil { - return x.Data + return x.Config } return nil } @@ -240,16 +240,18 @@ var File_callback_proto protoreflect.FileDescriptor const file_callback_proto_rawDesc = "" + "\n" + - "\x0ecallback.proto\x12\x05proto\"q\n" + + "\x0ecallback.proto\x12\x05proto\"}\n" + "\x13GetResourcesRequest\x12\x12\n" + - "\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" + - "\x04data\x18\x02 \x01(\fR\x04data\x122\n" + + "\x04type\x18\x01 \x01(\tR\x04type\x12\x1e\n" + + "\n" + + "attributes\x18\x02 \x01(\fR\n" + + "attributes\x122\n" + "\x15evaluation_request_id\x18\x03 \x01(\rR\x13evaluationRequestId\"0\n" + "\x14GetResourcesResponse\x12\x18\n" + - "\aresults\x18\x01 \x03(\fR\aresults\"r\n" + + "\aresults\x18\x01 \x03(\fR\aresults\"v\n" + "\x14GetDataSourceRequest\x12\x12\n" + - "\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" + - "\x04data\x18\x02 \x01(\fR\x04data\x122\n" + + "\x04type\x18\x01 \x01(\tR\x04type\x12\x16\n" + + "\x06config\x18\x02 \x01(\fR\x06config\x122\n" + "\x15evaluation_request_id\x18\x03 \x01(\rR\x13evaluationRequestId\"/\n" + "\x15GetDataSourceResponse\x12\x16\n" + "\x06result\x18\x01 \x01(\fR\x06result2\xa6\x01\n" + diff --git a/internal/policy/proto/callback.proto b/internal/policy/proto/callback.proto index 986378537b85..27f8a5ace689 100644 --- a/internal/policy/proto/callback.proto +++ b/internal/policy/proto/callback.proto @@ -14,7 +14,7 @@ service CallbackService { message GetResourcesRequest { string type = 1; - bytes data = 2; + bytes attributes = 2; // evaluation_request_id is the ID of the policy evaluation request that is // making this callback request. uint32 evaluation_request_id = 3; @@ -26,7 +26,7 @@ message GetResourcesResponse { message GetDataSourceRequest { string type = 1; - bytes data = 2; + bytes config = 2; // evaluation_request_id is the ID of the policy evaluation request that is // making this callback request. uint32 evaluation_request_id = 3; diff --git a/internal/policy/proto/diagnostics.pb.go b/internal/policy/proto/diagnostics.pb.go index e2ffe67d2ce0..7a77795aaa7c 100644 --- a/internal/policy/proto/diagnostics.pb.go +++ b/internal/policy/proto/diagnostics.pb.go @@ -211,9 +211,11 @@ func (x *Diagnostic) GetPolicySet() *PolicySet { } type PolicySet struct { - state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + // name is the name of the policy set. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // path is the path to the policy set directory. + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -382,13 +384,17 @@ func (x *Position) GetByte() int64 { return 0 } +// Snippet represents a snippet of code that is relevant to a diagnostic. type Snippet struct { - state protoimpl.MessageState `protogen:"open.v1"` - Context *Snippet_Context `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` - Code string `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"` - StartLine int64 `protobuf:"varint,3,opt,name=start_line,json=startLine,proto3" json:"start_line,omitempty"` - HighlightStartOffset int64 `protobuf:"varint,4,opt,name=highlight_start_offset,json=highlightStartOffset,proto3" json:"highlight_start_offset,omitempty"` - HighlightEndOffset int64 `protobuf:"varint,5,opt,name=highlight_end_offset,json=highlightEndOffset,proto3" json:"highlight_end_offset,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + // context is a high-level summary of the root context of the diagnostic: for example, + // the policy block in which an expression causes an error. + Context *string `protobuf:"bytes,1,opt,name=context,proto3,oneof" json:"context,omitempty"` + // code is the entire code that is relevant to the diagnostic. + Code string `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"` + StartLine int64 `protobuf:"varint,3,opt,name=start_line,json=startLine,proto3" json:"start_line,omitempty"` + HighlightStartOffset int64 `protobuf:"varint,4,opt,name=highlight_start_offset,json=highlightStartOffset,proto3" json:"highlight_start_offset,omitempty"` + HighlightEndOffset int64 `protobuf:"varint,5,opt,name=highlight_end_offset,json=highlightEndOffset,proto3" json:"highlight_end_offset,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -423,11 +429,11 @@ func (*Snippet) Descriptor() ([]byte, []int) { return file_diagnostics_proto_rawDescGZIP(), []int{4} } -func (x *Snippet) GetContext() *Snippet_Context { - if x != nil { - return x.Context +func (x *Snippet) GetContext() string { + if x != nil && x.Context != nil { + return *x.Context } - return nil + return "" } func (x *Snippet) GetCode() string { @@ -503,9 +509,11 @@ func (x *AttributePath) GetSteps() []*AttributePath_Step { } type ExpressionValue struct { - state protoimpl.MessageState `protogen:"open.v1"` - Path *AttributePath `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` - Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + // traversal is the path taken to reach this value in the object graph. + Traversal *AttributePath `protobuf:"bytes,1,opt,name=traversal,proto3" json:"traversal,omitempty"` + // value is the raw value of the expression. + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -540,9 +548,9 @@ func (*ExpressionValue) Descriptor() ([]byte, []int) { return file_diagnostics_proto_rawDescGZIP(), []int{6} } -func (x *ExpressionValue) GetPath() *AttributePath { +func (x *ExpressionValue) GetTraversal() *AttributePath { if x != nil { - return x.Path + return x.Traversal } return nil } @@ -598,50 +606,6 @@ func (x *DiagnosticResult) GetResult() EvaluateResult { return EvaluateResult_INVALID_EVALUATE_RESULT } -type Snippet_Context struct { - state protoimpl.MessageState `protogen:"open.v1"` - Context string `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Snippet_Context) Reset() { - *x = Snippet_Context{} - mi := &file_diagnostics_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Snippet_Context) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Snippet_Context) ProtoMessage() {} - -func (x *Snippet_Context) ProtoReflect() protoreflect.Message { - mi := &file_diagnostics_proto_msgTypes[8] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Snippet_Context.ProtoReflect.Descriptor instead. -func (*Snippet_Context) Descriptor() ([]byte, []int) { - return file_diagnostics_proto_rawDescGZIP(), []int{4, 0} -} - -func (x *Snippet_Context) GetContext() string { - if x != nil { - return x.Context - } - return "" -} - type AttributePath_Step struct { state protoimpl.MessageState `protogen:"open.v1"` // Types that are valid to be assigned to Step: @@ -655,7 +619,7 @@ type AttributePath_Step struct { func (x *AttributePath_Step) Reset() { *x = AttributePath_Step{} - mi := &file_diagnostics_proto_msgTypes[9] + mi := &file_diagnostics_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -667,7 +631,7 @@ func (x *AttributePath_Step) String() string { func (*AttributePath_Step) ProtoMessage() {} func (x *AttributePath_Step) ProtoReflect() protoreflect.Message { - mi := &file_diagnostics_proto_msgTypes[9] + mi := &file_diagnostics_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -713,10 +677,13 @@ type isAttributePath_Step_Step interface { } type AttributePath_Step_Attribute struct { + // attribute is used to look up an attribute in the object value. Attribute string `protobuf:"bytes,1,opt,name=attribute,proto3,oneof"` } type AttributePath_Step_Index struct { + // index is used to look up an element in an indexable collection type. + // The index can be a string or a number, depending on the collection type. Index []byte `protobuf:"bytes,2,opt,name=index,proto3,oneof"` } @@ -754,24 +721,24 @@ const file_diagnostics_proto_rawDesc = "" + "\bPosition\x12\x12\n" + "\x04line\x18\x01 \x01(\x03R\x04line\x12\x16\n" + "\x06column\x18\x02 \x01(\x03R\x06column\x12\x12\n" + - "\x04byte\x18\x03 \x01(\x03R\x04byte\"\xfb\x01\n" + - "\aSnippet\x120\n" + - "\acontext\x18\x01 \x01(\v2\x16.proto.Snippet.ContextR\acontext\x12\x12\n" + + "\x04byte\x18\x03 \x01(\x03R\x04byte\"\xcf\x01\n" + + "\aSnippet\x12\x1d\n" + + "\acontext\x18\x01 \x01(\tH\x00R\acontext\x88\x01\x01\x12\x12\n" + "\x04code\x18\x02 \x01(\tR\x04code\x12\x1d\n" + "\n" + "start_line\x18\x03 \x01(\x03R\tstartLine\x124\n" + "\x16highlight_start_offset\x18\x04 \x01(\x03R\x14highlightStartOffset\x120\n" + - "\x14highlight_end_offset\x18\x05 \x01(\x03R\x12highlightEndOffset\x1a#\n" + - "\aContext\x12\x18\n" + - "\acontext\x18\x01 \x01(\tR\acontext\"\x88\x01\n" + + "\x14highlight_end_offset\x18\x05 \x01(\x03R\x12highlightEndOffsetB\n" + + "\n" + + "\b_context\"\x88\x01\n" + "\rAttributePath\x12/\n" + "\x05steps\x18\x01 \x03(\v2\x19.proto.AttributePath.StepR\x05steps\x1aF\n" + "\x04Step\x12\x1e\n" + "\tattribute\x18\x01 \x01(\tH\x00R\tattribute\x12\x16\n" + "\x05index\x18\x02 \x01(\fH\x00R\x05indexB\x06\n" + - "\x04step\"Q\n" + - "\x0fExpressionValue\x12(\n" + - "\x04path\x18\x01 \x01(\v2\x14.proto.AttributePathR\x04path\x12\x14\n" + + "\x04step\"[\n" + + "\x0fExpressionValue\x122\n" + + "\ttraversal\x18\x01 \x01(\v2\x14.proto.AttributePathR\ttraversal\x12\x14\n" + "\x05value\x18\x02 \x01(\fR\x05value\"A\n" + "\x10DiagnosticResult\x12-\n" + "\x06result\x18\x01 \x01(\x0e2\x15.proto.EvaluateResultR\x06result*/\n" + @@ -793,7 +760,7 @@ func file_diagnostics_proto_rawDescGZIP() []byte { } var file_diagnostics_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_diagnostics_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_diagnostics_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_diagnostics_proto_goTypes = []any{ (Severity)(0), // 0: proto.Severity (*Diagnostic)(nil), // 1: proto.Diagnostic @@ -804,9 +771,8 @@ var file_diagnostics_proto_goTypes = []any{ (*AttributePath)(nil), // 6: proto.AttributePath (*ExpressionValue)(nil), // 7: proto.ExpressionValue (*DiagnosticResult)(nil), // 8: proto.DiagnosticResult - (*Snippet_Context)(nil), // 9: proto.Snippet.Context - (*AttributePath_Step)(nil), // 10: proto.AttributePath.Step - (EvaluateResult)(0), // 11: proto.EvaluateResult + (*AttributePath_Step)(nil), // 9: proto.AttributePath.Step + (EvaluateResult)(0), // 10: proto.EvaluateResult } var file_diagnostics_proto_depIdxs = []int32{ 0, // 0: proto.Diagnostic.severity:type_name -> proto.Severity @@ -819,15 +785,14 @@ var file_diagnostics_proto_depIdxs = []int32{ 2, // 7: proto.Diagnostic.policy_set:type_name -> proto.PolicySet 4, // 8: proto.Range.start:type_name -> proto.Position 4, // 9: proto.Range.end:type_name -> proto.Position - 9, // 10: proto.Snippet.context:type_name -> proto.Snippet.Context - 10, // 11: proto.AttributePath.steps:type_name -> proto.AttributePath.Step - 6, // 12: proto.ExpressionValue.path:type_name -> proto.AttributePath - 11, // 13: proto.DiagnosticResult.result:type_name -> proto.EvaluateResult - 14, // [14:14] is the sub-list for method output_type - 14, // [14:14] is the sub-list for method input_type - 14, // [14:14] is the sub-list for extension type_name - 14, // [14:14] is the sub-list for extension extendee - 0, // [0:14] is the sub-list for field type_name + 9, // 10: proto.AttributePath.steps:type_name -> proto.AttributePath.Step + 6, // 11: proto.ExpressionValue.traversal:type_name -> proto.AttributePath + 10, // 12: proto.DiagnosticResult.result:type_name -> proto.EvaluateResult + 13, // [13:13] is the sub-list for method output_type + 13, // [13:13] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_diagnostics_proto_init() } @@ -836,7 +801,8 @@ func file_diagnostics_proto_init() { return } file_types_proto_init() - file_diagnostics_proto_msgTypes[9].OneofWrappers = []any{ + file_diagnostics_proto_msgTypes[4].OneofWrappers = []any{} + file_diagnostics_proto_msgTypes[8].OneofWrappers = []any{ (*AttributePath_Step_Attribute)(nil), (*AttributePath_Step_Index)(nil), } @@ -846,7 +812,7 @@ func file_diagnostics_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_diagnostics_proto_rawDesc), len(file_diagnostics_proto_rawDesc)), NumEnums: 1, - NumMessages: 10, + NumMessages: 9, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/policy/proto/diagnostics.proto b/internal/policy/proto/diagnostics.proto index 0327031d8612..5a14476e7566 100644 --- a/internal/policy/proto/diagnostics.proto +++ b/internal/policy/proto/diagnostics.proto @@ -50,7 +50,10 @@ message Diagnostic { } message PolicySet { + // name is the name of the policy set. string name = 1; + + // path is the path to the policy set directory. string path = 2; } @@ -66,12 +69,14 @@ message Position { int64 byte = 3; } +// Snippet represents a snippet of code that is relevant to a diagnostic. message Snippet { - message Context { - string context = 1; - } - Context context = 1; + // context is a high-level summary of the root context of the diagnostic: for example, + // the policy block in which an expression causes an error. + optional string context = 1; + + // code is the entire code that is relevant to the diagnostic. string code = 2; int64 start_line = 3; int64 highlight_start_offset = 4; @@ -81,7 +86,11 @@ message Snippet { message AttributePath { message Step { oneof step { + // attribute is used to look up an attribute in the object value. string attribute = 1; + + // index is used to look up an element in an indexable collection type. + // The index can be a string or a number, depending on the collection type. bytes index = 2; } } @@ -89,7 +98,10 @@ message AttributePath { } message ExpressionValue { - AttributePath path = 1; + // traversal is the path taken to reach this value in the object graph. + AttributePath traversal = 1; + + // value is the raw value of the expression. bytes value = 2; } diff --git a/internal/policy/proto/diagnostics_test.go b/internal/policy/proto/diagnostics_test.go index 3fad2a4c1114..44458e4db7c9 100644 --- a/internal/policy/proto/diagnostics_test.go +++ b/internal/policy/proto/diagnostics_test.go @@ -14,7 +14,6 @@ import ( func TestDiagnosticToHCL(t *testing.T) { index, _ := msgpack.Marshal(cty.StringVal("name"), cty.DynamicPseudoType) - protoDiag := &Diagnostic{ Severity: Severity_WARNING, Summary: "policy warning", @@ -38,9 +37,14 @@ func TestDiagnosticToHCL(t *testing.T) { {Step: &AttributePath_Step_Index{Index: index}}, }, }, - Snippet: &Snippet{Code: "some policy code snippet"}, + Snippet: &Snippet{ + Context: func() *string { + ret := "Some context around the code" + return &ret + }(), + Code: "some policy code snippet"}, ExpressionValues: []*ExpressionValue{{ - Path: &AttributePath{ + Traversal: &AttributePath{ Steps: []*AttributePath_Step{{Step: &AttributePath_Step_Attribute{Attribute: "example"}}}, }, Value: []byte("value-bytes"), @@ -107,8 +111,8 @@ func TestDiagnosticToHCL(t *testing.T) { if len(expressionValuesExtra.ExpressionValues) != 1 { t.Fatalf("unexpected expression values count: got %d, want 1", len(expressionValuesExtra.ExpressionValues)) } - if expressionValuesExtra.ExpressionValues[0].Path == nil || len(expressionValuesExtra.ExpressionValues[0].Path.Steps) != 1 { - t.Fatalf("unexpected expression value path: %#v", expressionValuesExtra.ExpressionValues[0].Path) + if expressionValuesExtra.ExpressionValues[0].Traversal == nil || len(expressionValuesExtra.ExpressionValues[0].Traversal.Steps) != 1 { + t.Fatalf("unexpected expression value path: %#v", expressionValuesExtra.ExpressionValues[0].Traversal) } if expressionValuesExtra.ExpressionValues[0].Value == nil { t.Fatalf("expected expression value bytes to be present") From a607df38114727cc5184435bb48f7f0642556aa0 Mon Sep 17 00:00:00 2001 From: Samsondeen Dare Date: Tue, 19 May 2026 14:47:41 +0200 Subject: [PATCH 4/7] remove type from metadata --- internal/policy/proto/policy.pb.go | 3 +- internal/policy/proto/policy.proto | 3 +- internal/policy/proto/types.pb.go | 50 ++++++++++-------------------- internal/policy/proto/types.proto | 19 +++++------- 4 files changed, 26 insertions(+), 49 deletions(-) diff --git a/internal/policy/proto/policy.pb.go b/internal/policy/proto/policy.pb.go index cfad4d74925d..90c78a245ec2 100644 --- a/internal/policy/proto/policy.pb.go +++ b/internal/policy/proto/policy.pb.go @@ -31,8 +31,7 @@ type PolicySetupRequest struct { // behaviours the client is aware of. ClientCapabilities *PolicySetupRequest_ClientCapabilities `protobuf:"bytes,1,opt,name=client_capabilities,json=clientCapabilities,proto3" json:"client_capabilities,omitempty"` // source_locations is the list of locations that Policy should use to - // source policies. At launch, this will just be local directories but could - // in future be extended to include remote sources. + // source policies. SourceLocations []string `protobuf:"bytes,2,rep,name=source_locations,json=sourceLocations,proto3" json:"source_locations,omitempty"` // callback_service allows Terraform Policy to use the Callback Service API. CallbackService uint32 `protobuf:"varint,3,opt,name=callback_service,json=callbackService,proto3" json:"callback_service,omitempty"` diff --git a/internal/policy/proto/policy.proto b/internal/policy/proto/policy.proto index c85859caee12..745c5ff6a86b 100644 --- a/internal/policy/proto/policy.proto +++ b/internal/policy/proto/policy.proto @@ -42,8 +42,7 @@ message PolicySetupRequest { ClientCapabilities client_capabilities = 1; // source_locations is the list of locations that Policy should use to - // source policies. At launch, this will just be local directories but could - // in future be extended to include remote sources. + // source policies. repeated string source_locations = 2; // callback_service allows Terraform Policy to use the Callback Service API. diff --git a/internal/policy/proto/types.pb.go b/internal/policy/proto/types.pb.go index 9c76eeea83f0..8e9616245ecd 100644 --- a/internal/policy/proto/types.pb.go +++ b/internal/policy/proto/types.pb.go @@ -159,9 +159,8 @@ func (Operation) EnumDescriptor() ([]byte, []int) { type ResourceMetadata struct { state protoimpl.MessageState `protogen:"open.v1"` - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` - ProviderType string `protobuf:"bytes,2,opt,name=provider_type,json=providerType,proto3" json:"provider_type,omitempty"` - Operation Operation `protobuf:"varint,3,opt,name=operation,proto3,enum=proto.Operation" json:"operation,omitempty"` + ProviderType string `protobuf:"bytes,1,opt,name=provider_type,json=providerType,proto3" json:"provider_type,omitempty"` + Operation Operation `protobuf:"varint,2,opt,name=operation,proto3,enum=proto.Operation" json:"operation,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -196,13 +195,6 @@ func (*ResourceMetadata) Descriptor() ([]byte, []int) { return file_types_proto_rawDescGZIP(), []int{0} } -func (x *ResourceMetadata) GetType() string { - if x != nil { - return x.Type - } - return "" -} - func (x *ResourceMetadata) GetProviderType() string { if x != nil { return x.ProviderType @@ -281,11 +273,10 @@ type ProviderMetadata struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Alias string `protobuf:"bytes,2,opt,name=alias,proto3" json:"alias,omitempty"` - Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` - Namespace string `protobuf:"bytes,4,opt,name=namespace,proto3" json:"namespace,omitempty"` - Source string `protobuf:"bytes,5,opt,name=source,proto3" json:"source,omitempty"` - ModulePath string `protobuf:"bytes,6,opt,name=module_path,json=modulePath,proto3" json:"module_path,omitempty"` - Version string `protobuf:"bytes,7,opt,name=version,proto3" json:"version,omitempty"` + Namespace string `protobuf:"bytes,3,opt,name=namespace,proto3" json:"namespace,omitempty"` + Source string `protobuf:"bytes,4,opt,name=source,proto3" json:"source,omitempty"` + ModulePath string `protobuf:"bytes,5,opt,name=module_path,json=modulePath,proto3" json:"module_path,omitempty"` + Version string `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -334,13 +325,6 @@ func (x *ProviderMetadata) GetAlias() string { return "" } -func (x *ProviderMetadata) GetType() string { - if x != nil { - return x.Type - } - return "" -} - func (x *ProviderMetadata) GetNamespace() string { if x != nil { return x.Namespace @@ -373,24 +357,22 @@ var File_types_proto protoreflect.FileDescriptor const file_types_proto_rawDesc = "" + "\n" + - "\vtypes.proto\x12\x05proto\"{\n" + - "\x10ResourceMetadata\x12\x12\n" + - "\x04type\x18\x01 \x01(\tR\x04type\x12#\n" + - "\rprovider_type\x18\x02 \x01(\tR\fproviderType\x12.\n" + - "\toperation\x18\x03 \x01(\x0e2\x10.proto.OperationR\toperation\"\\\n" + + "\vtypes.proto\x12\x05proto\"g\n" + + "\x10ResourceMetadata\x12#\n" + + "\rprovider_type\x18\x01 \x01(\tR\fproviderType\x12.\n" + + "\toperation\x18\x02 \x01(\x0e2\x10.proto.OperationR\toperation\"\\\n" + "\x0eModuleMetadata\x12\x16\n" + "\x06source\x18\x01 \x01(\tR\x06source\x12\x18\n" + "\aversion\x18\x02 \x01(\tR\aversion\x12\x18\n" + - "\aaddress\x18\x03 \x01(\tR\aaddress\"\xc1\x01\n" + + "\aaddress\x18\x03 \x01(\tR\aaddress\"\xad\x01\n" + "\x10ProviderMetadata\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + - "\x05alias\x18\x02 \x01(\tR\x05alias\x12\x12\n" + - "\x04type\x18\x03 \x01(\tR\x04type\x12\x1c\n" + - "\tnamespace\x18\x04 \x01(\tR\tnamespace\x12\x16\n" + - "\x06source\x18\x05 \x01(\tR\x06source\x12\x1f\n" + - "\vmodule_path\x18\x06 \x01(\tR\n" + + "\x05alias\x18\x02 \x01(\tR\x05alias\x12\x1c\n" + + "\tnamespace\x18\x03 \x01(\tR\tnamespace\x12\x16\n" + + "\x06source\x18\x04 \x01(\tR\x06source\x12\x1f\n" + + "\vmodule_path\x18\x05 \x01(\tR\n" + "modulePath\x12\x18\n" + - "\aversion\x18\a \x01(\tR\aversion*\xbb\x01\n" + + "\aversion\x18\x06 \x01(\tR\aversion*\xbb\x01\n" + "\x0eEvaluateResult\x12\x1b\n" + "\x17INVALID_EVALUATE_RESULT\x10\x00\x12\x1b\n" + "\x17UNKNOWN_EVALUATE_RESULT\x10\x01\x12\x19\n" + diff --git a/internal/policy/proto/types.proto b/internal/policy/proto/types.proto index 52f7395969bc..30c5a2097adc 100644 --- a/internal/policy/proto/types.proto +++ b/internal/policy/proto/types.proto @@ -54,23 +54,20 @@ enum Operation { } message ResourceMetadata { - string type = 1; - string provider_type = 2; - Operation operation = 3; + string provider_type = 1; + Operation operation = 2; } message ModuleMetadata { - string source = 1; - string version = 2; - string address = 3; + string version = 1; + string address = 2; } message ProviderMetadata { string name = 1; string alias = 2; - string type = 3; - string namespace = 4; - string source = 5; - string module_path = 6; - string version = 7; + string namespace = 3; + string source = 4; + string module_path = 5; + string version = 6; } From 2c5d562f0adc87cf1d94dc0376dc78e9cc91ea36 Mon Sep 17 00:00:00 2001 From: Samsondeen Dare Date: Tue, 19 May 2026 15:28:48 +0200 Subject: [PATCH 5/7] more defensive nil checks --- internal/policy/proto/diagnostics.go | 12 ++++++++++-- internal/policy/proto/types.pb.go | 21 ++++++--------------- internal/policy/result.go | 8 ++++++-- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/internal/policy/proto/diagnostics.go b/internal/policy/proto/diagnostics.go index 45bbf1b258cb..0284f0d5d502 100644 --- a/internal/policy/proto/diagnostics.go +++ b/internal/policy/proto/diagnostics.go @@ -130,10 +130,17 @@ func (rng *Range) ToHclRange() hcl.Range { if rng == nil { return hcl.Range{} } + var start, end hcl.Pos + if rng.Start != nil { + start = rng.Start.ToHclPos() + } + if rng.End != nil { + end = rng.End.ToHclPos() + } return hcl.Range{ Filename: rng.Filename, - Start: rng.Start.ToHclPos(), - End: rng.End.ToHclPos(), + Start: start, + End: end, } } @@ -175,6 +182,7 @@ func (step *AttributePath_Step) ToCtyPathStep() (cty.PathStep, error) { Key: index, }, nil default: + // The switch case is exhaustive, so this should never happen panic(fmt.Errorf("unsupported Step type: %T", step)) } } diff --git a/internal/policy/proto/types.pb.go b/internal/policy/proto/types.pb.go index 8e9616245ecd..320ee177a5fd 100644 --- a/internal/policy/proto/types.pb.go +++ b/internal/policy/proto/types.pb.go @@ -211,9 +211,8 @@ func (x *ResourceMetadata) GetOperation() Operation { type ModuleMetadata struct { state protoimpl.MessageState `protogen:"open.v1"` - Source string `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` - Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` - Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -248,13 +247,6 @@ func (*ModuleMetadata) Descriptor() ([]byte, []int) { return file_types_proto_rawDescGZIP(), []int{1} } -func (x *ModuleMetadata) GetSource() string { - if x != nil { - return x.Source - } - return "" -} - func (x *ModuleMetadata) GetVersion() string { if x != nil { return x.Version @@ -360,11 +352,10 @@ const file_types_proto_rawDesc = "" + "\vtypes.proto\x12\x05proto\"g\n" + "\x10ResourceMetadata\x12#\n" + "\rprovider_type\x18\x01 \x01(\tR\fproviderType\x12.\n" + - "\toperation\x18\x02 \x01(\x0e2\x10.proto.OperationR\toperation\"\\\n" + - "\x0eModuleMetadata\x12\x16\n" + - "\x06source\x18\x01 \x01(\tR\x06source\x12\x18\n" + - "\aversion\x18\x02 \x01(\tR\aversion\x12\x18\n" + - "\aaddress\x18\x03 \x01(\tR\aaddress\"\xad\x01\n" + + "\toperation\x18\x02 \x01(\x0e2\x10.proto.OperationR\toperation\"D\n" + + "\x0eModuleMetadata\x12\x18\n" + + "\aversion\x18\x01 \x01(\tR\aversion\x12\x18\n" + + "\aaddress\x18\x02 \x01(\tR\aaddress\"\xad\x01\n" + "\x10ProviderMetadata\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + "\x05alias\x18\x02 \x01(\tR\x05alias\x12\x1c\n" + diff --git a/internal/policy/result.go b/internal/policy/result.go index 0a62bbd66df0..994743edde88 100644 --- a/internal/policy/result.go +++ b/internal/policy/result.go @@ -3,7 +3,11 @@ package policy -import "github.com/hashicorp/terraform/internal/policy/proto" +import ( + "fmt" + + "github.com/hashicorp/terraform/internal/policy/proto" +) type EvaluateResult int @@ -34,6 +38,6 @@ func ResultFromProto(result proto.EvaluateResult) EvaluateResult { return SetupErrorResult default: // should be exhaustive - panic("unhandled EvaluateResult") + panic(fmt.Errorf("unhandled EvaluateResult type: %T", result)) } } From ac8c7993ab02529124f7ffc15f022af876522146 Mon Sep 17 00:00:00 2001 From: Samsondeen Dare Date: Tue, 19 May 2026 15:47:51 +0200 Subject: [PATCH 6/7] nest metadata under the parent message --- internal/policy/client.go | 6 +- internal/policy/client_test.go | 6 +- internal/policy/mock.go | 18 +- internal/policy/policy.go | 6 +- internal/policy/proto/policy.pb.go | 310 ++++++++++++++++++++++++----- internal/policy/proto/policy.proto | 19 ++ internal/policy/proto/types.pb.go | 226 +-------------------- internal/policy/proto/types.proto | 19 -- 8 files changed, 303 insertions(+), 307 deletions(-) diff --git a/internal/policy/client.go b/internal/policy/client.go index c99ad923a412..33c186335347 100644 --- a/internal/policy/client.go +++ b/internal/policy/client.go @@ -157,7 +157,7 @@ func (c *client) Setup(ctx context.Context, req SetupRequest) SetupResponse { } } -func (c *client) EvaluateResource(ctx context.Context, req EvaluationRequest[*proto.ResourceMetadata]) EvaluationResponse { +func (c *client) EvaluateResource(ctx context.Context, req EvaluationRequest[*proto.PolicyEvaluateResourceRequest_ResourceMetadata]) EvaluationResponse { log.Printf("[DEBUG] Evaluating policy for resource %s", req.Target) var diags []*proto.Diagnostic @@ -209,7 +209,7 @@ func (c *client) EvaluateResource(ctx context.Context, req EvaluationRequest[*pr return EvaluationFromProtoResponse(response.Result, response.PolicyDetails) } -func (c *client) EvaluateProvider(ctx context.Context, req EvaluationRequest[*proto.ProviderMetadata]) EvaluationResponse { +func (c *client) EvaluateProvider(ctx context.Context, req EvaluationRequest[*proto.PolicyEvaluateProviderRequest_ProviderMetadata]) EvaluationResponse { log.Printf("[DEBUG] Evaluating policy for provider %s", req.Target) var diags []*proto.Diagnostic req = normalizeRequest(req) @@ -241,7 +241,7 @@ func (c *client) EvaluateProvider(ctx context.Context, req EvaluationRequest[*pr return EvaluationFromProtoResponse(response.Result, response.PolicyDetails) } -func (c *client) EvaluateModule(ctx context.Context, req EvaluationRequest[*proto.ModuleMetadata]) EvaluationResponse { +func (c *client) EvaluateModule(ctx context.Context, req EvaluationRequest[*proto.PolicyEvaluateModuleRequest_ModuleMetadata]) EvaluationResponse { log.Printf("[DEBUG] Evaluating policy for module %s", req.Target) var diags []*proto.Diagnostic diff --git a/internal/policy/client_test.go b/internal/policy/client_test.go index 0074c7d87216..4a268c756a7a 100644 --- a/internal/policy/client_test.go +++ b/internal/policy/client_test.go @@ -165,7 +165,7 @@ func TestClientEvaluate(t *testing.T) { callbackRegistry: registry, } - resp := c.EvaluateResource(ctx, EvaluationRequest[*proto.ResourceMetadata]{ + resp := c.EvaluateResource(ctx, EvaluationRequest[*proto.PolicyEvaluateResourceRequest_ResourceMetadata]{ Target: "test_resource", Attrs: test.attrs, PriorAttrs: test.priorAttrs, @@ -308,7 +308,7 @@ func TestClientEvaluateProvider(t *testing.T) { callbackRegistry: callback.NewRegistry(), } - resp := c.EvaluateProvider(ctx, EvaluationRequest[*proto.ProviderMetadata]{ + resp := c.EvaluateProvider(ctx, EvaluationRequest[*proto.PolicyEvaluateProviderRequest_ProviderMetadata]{ Target: "test_provider", Attrs: test.attrs, }) @@ -417,7 +417,7 @@ func TestClientEvaluateModule(t *testing.T) { callbackRegistry: callback.NewRegistry(), } - resp := c.EvaluateModule(ctx, EvaluationRequest[*proto.ModuleMetadata]{ + resp := c.EvaluateModule(ctx, EvaluationRequest[*proto.PolicyEvaluateModuleRequest_ModuleMetadata]{ Target: "./child", }) diff --git a/internal/policy/mock.go b/internal/policy/mock.go index 0d05ac48ccf4..aa23c3b5d8b3 100644 --- a/internal/policy/mock.go +++ b/internal/policy/mock.go @@ -26,20 +26,20 @@ type MockClient struct { // Evaluate method tracking EvaluateCalled bool EvaluateResponse *EvaluationResponse - EvaluateRequest EvaluationRequest[*proto.ResourceMetadata] - EvaluateFn func(context.Context, EvaluationRequest[*proto.ResourceMetadata]) EvaluationResponse + EvaluateRequest EvaluationRequest[*proto.PolicyEvaluateResourceRequest_ResourceMetadata] + EvaluateFn func(context.Context, EvaluationRequest[*proto.PolicyEvaluateResourceRequest_ResourceMetadata]) EvaluationResponse // EvaluateProvider method tracking EvaluateProviderCalled bool EvaluateProviderResponse *EvaluationResponse - EvaluateProviderRequest EvaluationRequest[*proto.ProviderMetadata] - EvaluateProviderFn func(context.Context, EvaluationRequest[*proto.ProviderMetadata]) EvaluationResponse + EvaluateProviderRequest EvaluationRequest[*proto.PolicyEvaluateProviderRequest_ProviderMetadata] + EvaluateProviderFn func(context.Context, EvaluationRequest[*proto.PolicyEvaluateProviderRequest_ProviderMetadata]) EvaluationResponse // EvaluateModule method tracking EvaluateModuleCalled bool EvaluateModuleResponse *EvaluationResponse - EvaluateModuleRequest EvaluationRequest[*proto.ModuleMetadata] - EvaluateModuleFn func(context.Context, EvaluationRequest[*proto.ModuleMetadata]) EvaluationResponse + EvaluateModuleRequest EvaluationRequest[*proto.PolicyEvaluateModuleRequest_ModuleMetadata] + EvaluateModuleFn func(context.Context, EvaluationRequest[*proto.PolicyEvaluateModuleRequest_ModuleMetadata]) EvaluationResponse // Stop method tracking StopCalled bool @@ -66,7 +66,7 @@ func (p *MockClient) Setup(ctx context.Context, req SetupRequest) (resp SetupRes return resp } -func (p *MockClient) EvaluateResource(ctx context.Context, r EvaluationRequest[*proto.ResourceMetadata]) (resp EvaluationResponse) { +func (p *MockClient) EvaluateResource(ctx context.Context, r EvaluationRequest[*proto.PolicyEvaluateResourceRequest_ResourceMetadata]) (resp EvaluationResponse) { defer p.beginWrite()() p.EvaluateCalled = true @@ -82,7 +82,7 @@ func (p *MockClient) EvaluateResource(ctx context.Context, r EvaluationRequest[* return resp } -func (p *MockClient) EvaluateProvider(ctx context.Context, r EvaluationRequest[*proto.ProviderMetadata]) (resp EvaluationResponse) { +func (p *MockClient) EvaluateProvider(ctx context.Context, r EvaluationRequest[*proto.PolicyEvaluateProviderRequest_ProviderMetadata]) (resp EvaluationResponse) { defer p.beginWrite()() p.EvaluateProviderCalled = true @@ -98,7 +98,7 @@ func (p *MockClient) EvaluateProvider(ctx context.Context, r EvaluationRequest[* return resp } -func (p *MockClient) EvaluateModule(ctx context.Context, r EvaluationRequest[*proto.ModuleMetadata]) (resp EvaluationResponse) { +func (p *MockClient) EvaluateModule(ctx context.Context, r EvaluationRequest[*proto.PolicyEvaluateModuleRequest_ModuleMetadata]) (resp EvaluationResponse) { defer p.beginWrite()() p.EvaluateModuleCalled = true diff --git a/internal/policy/policy.go b/internal/policy/policy.go index 8e8b08d71208..072d81e175be 100644 --- a/internal/policy/policy.go +++ b/internal/policy/policy.go @@ -17,9 +17,9 @@ import ( // Client is an interface for interacting with a policy engine. type Client interface { Setup(context.Context, SetupRequest) SetupResponse - EvaluateResource(context.Context, EvaluationRequest[*proto.ResourceMetadata]) EvaluationResponse - EvaluateProvider(context.Context, EvaluationRequest[*proto.ProviderMetadata]) EvaluationResponse - EvaluateModule(context.Context, EvaluationRequest[*proto.ModuleMetadata]) EvaluationResponse + EvaluateResource(context.Context, EvaluationRequest[*proto.PolicyEvaluateResourceRequest_ResourceMetadata]) EvaluationResponse + EvaluateProvider(context.Context, EvaluationRequest[*proto.PolicyEvaluateProviderRequest_ProviderMetadata]) EvaluationResponse + EvaluateModule(context.Context, EvaluationRequest[*proto.PolicyEvaluateModuleRequest_ModuleMetadata]) EvaluationResponse Stop() } diff --git a/internal/policy/proto/policy.pb.go b/internal/policy/proto/policy.pb.go index 90c78a245ec2..5a5b86706088 100644 --- a/internal/policy/proto/policy.pb.go +++ b/internal/policy/proto/policy.pb.go @@ -161,7 +161,7 @@ type PolicyEvaluateResourceRequest struct { Attrs []byte `protobuf:"bytes,3,opt,name=attrs,proto3" json:"attrs,omitempty"` // metadata contains the data that will eventually be made available within // the meta object referenced within Terraform Policy files. - Metadata *ResourceMetadata `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"` + Metadata *PolicyEvaluateResourceRequest_ResourceMetadata `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"` // prior_attrs contains the state of the resource prior to the current operation. PriorAttrs []byte `protobuf:"bytes,5,opt,name=prior_attrs,json=priorAttrs,proto3" json:"prior_attrs,omitempty"` unknownFields protoimpl.UnknownFields @@ -219,7 +219,7 @@ func (x *PolicyEvaluateResourceRequest) GetAttrs() []byte { return nil } -func (x *PolicyEvaluateResourceRequest) GetMetadata() *ResourceMetadata { +func (x *PolicyEvaluateResourceRequest) GetMetadata() *PolicyEvaluateResourceRequest_ResourceMetadata { if x != nil { return x.Metadata } @@ -508,7 +508,7 @@ type PolicyEvaluateProviderRequest struct { Attrs []byte `protobuf:"bytes,2,opt,name=attrs,proto3" json:"attrs,omitempty"` // metadata contains the provider metadata that will be made available within the // provider_metadata data referenced within Terraform Policy files. - Metadata *ProviderMetadata `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"` + Metadata *PolicyEvaluateProviderRequest_ProviderMetadata `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -557,7 +557,7 @@ func (x *PolicyEvaluateProviderRequest) GetAttrs() []byte { return nil } -func (x *PolicyEvaluateProviderRequest) GetMetadata() *ProviderMetadata { +func (x *PolicyEvaluateProviderRequest) GetMetadata() *PolicyEvaluateProviderRequest_ProviderMetadata { if x != nil { return x.Metadata } @@ -630,7 +630,7 @@ type PolicyEvaluateModuleRequest struct { Attrs []byte `protobuf:"bytes,2,opt,name=attrs,proto3" json:"attrs,omitempty"` // metadata contains the module metadata that will be made available within the // meta object referenced within module policy blocks. - Metadata *ModuleMetadata `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"` + Metadata *PolicyEvaluateModuleRequest_ModuleMetadata `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -679,7 +679,7 @@ func (x *PolicyEvaluateModuleRequest) GetAttrs() []byte { return nil } -func (x *PolicyEvaluateModuleRequest) GetMetadata() *ModuleMetadata { +func (x *PolicyEvaluateModuleRequest) GetMetadata() *PolicyEvaluateModuleRequest_ModuleMetadata { if x != nil { return x.Metadata } @@ -872,6 +872,194 @@ func (x *PolicySetupResponse_ServerCapabilities) GetConfigurations() map[string] return nil } +type PolicyEvaluateResourceRequest_ResourceMetadata struct { + state protoimpl.MessageState `protogen:"open.v1"` + ProviderType string `protobuf:"bytes,1,opt,name=provider_type,json=providerType,proto3" json:"provider_type,omitempty"` + Operation Operation `protobuf:"varint,2,opt,name=operation,proto3,enum=proto.Operation" json:"operation,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyEvaluateResourceRequest_ResourceMetadata) Reset() { + *x = PolicyEvaluateResourceRequest_ResourceMetadata{} + mi := &file_policy_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyEvaluateResourceRequest_ResourceMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyEvaluateResourceRequest_ResourceMetadata) ProtoMessage() {} + +func (x *PolicyEvaluateResourceRequest_ResourceMetadata) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyEvaluateResourceRequest_ResourceMetadata.ProtoReflect.Descriptor instead. +func (*PolicyEvaluateResourceRequest_ResourceMetadata) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *PolicyEvaluateResourceRequest_ResourceMetadata) GetProviderType() string { + if x != nil { + return x.ProviderType + } + return "" +} + +func (x *PolicyEvaluateResourceRequest_ResourceMetadata) GetOperation() Operation { + if x != nil { + return x.Operation + } + return Operation_CREATE +} + +type PolicyEvaluateProviderRequest_ProviderMetadata struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Alias string `protobuf:"bytes,2,opt,name=alias,proto3" json:"alias,omitempty"` + Namespace string `protobuf:"bytes,3,opt,name=namespace,proto3" json:"namespace,omitempty"` + Source string `protobuf:"bytes,4,opt,name=source,proto3" json:"source,omitempty"` + ModulePath string `protobuf:"bytes,5,opt,name=module_path,json=modulePath,proto3" json:"module_path,omitempty"` + Version string `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyEvaluateProviderRequest_ProviderMetadata) Reset() { + *x = PolicyEvaluateProviderRequest_ProviderMetadata{} + mi := &file_policy_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyEvaluateProviderRequest_ProviderMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyEvaluateProviderRequest_ProviderMetadata) ProtoMessage() {} + +func (x *PolicyEvaluateProviderRequest_ProviderMetadata) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyEvaluateProviderRequest_ProviderMetadata.ProtoReflect.Descriptor instead. +func (*PolicyEvaluateProviderRequest_ProviderMetadata) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{6, 0} +} + +func (x *PolicyEvaluateProviderRequest_ProviderMetadata) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *PolicyEvaluateProviderRequest_ProviderMetadata) GetAlias() string { + if x != nil { + return x.Alias + } + return "" +} + +func (x *PolicyEvaluateProviderRequest_ProviderMetadata) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *PolicyEvaluateProviderRequest_ProviderMetadata) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *PolicyEvaluateProviderRequest_ProviderMetadata) GetModulePath() string { + if x != nil { + return x.ModulePath + } + return "" +} + +func (x *PolicyEvaluateProviderRequest_ProviderMetadata) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +type PolicyEvaluateModuleRequest_ModuleMetadata struct { + state protoimpl.MessageState `protogen:"open.v1"` + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PolicyEvaluateModuleRequest_ModuleMetadata) Reset() { + *x = PolicyEvaluateModuleRequest_ModuleMetadata{} + mi := &file_policy_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PolicyEvaluateModuleRequest_ModuleMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PolicyEvaluateModuleRequest_ModuleMetadata) ProtoMessage() {} + +func (x *PolicyEvaluateModuleRequest_ModuleMetadata) ProtoReflect() protoreflect.Message { + mi := &file_policy_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PolicyEvaluateModuleRequest_ModuleMetadata.ProtoReflect.Descriptor instead. +func (*PolicyEvaluateModuleRequest_ModuleMetadata) Descriptor() ([]byte, []int) { + return file_policy_proto_rawDescGZIP(), []int{8, 0} +} + +func (x *PolicyEvaluateModuleRequest_ModuleMetadata) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *PolicyEvaluateModuleRequest_ModuleMetadata) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + var File_policy_proto protoreflect.FileDescriptor const file_policy_proto_rawDesc = "" + @@ -891,14 +1079,17 @@ const file_policy_proto_rawDesc = "" + "\x0econfigurations\x18\x01 \x03(\v2A.proto.PolicySetupResponse.ServerCapabilities.ConfigurationsEntryR\x0econfigurations\x1at\n" + "\x13ConfigurationsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12G\n" + - "\x05value\x18\x02 \x01(\v21.proto.PolicySetupResponse.TerraformConfigurationR\x05value:\x028\x01\"\xcc\x01\n" + + "\x05value\x18\x02 \x01(\v21.proto.PolicySetupResponse.TerraformConfigurationR\x05value:\x028\x01\"\xd3\x02\n" + "\x1dPolicyEvaluateResourceRequest\x12#\n" + "\revaluation_id\x18\x01 \x01(\rR\fevaluationId\x12\x1a\n" + "\bresource\x18\x02 \x01(\tR\bresource\x12\x14\n" + - "\x05attrs\x18\x03 \x01(\fR\x05attrs\x123\n" + - "\bmetadata\x18\x04 \x01(\v2\x17.proto.ResourceMetadataR\bmetadata\x12\x1f\n" + + "\x05attrs\x18\x03 \x01(\fR\x05attrs\x12Q\n" + + "\bmetadata\x18\x04 \x01(\v25.proto.PolicyEvaluateResourceRequest.ResourceMetadataR\bmetadata\x12\x1f\n" + "\vprior_attrs\x18\x05 \x01(\fR\n" + - "priorAttrs\"\xf7\x02\n" + + "priorAttrs\x1ag\n" + + "\x10ResourceMetadata\x12#\n" + + "\rprovider_type\x18\x01 \x01(\tR\fproviderType\x12.\n" + + "\toperation\x18\x02 \x01(\x0e2\x10.proto.OperationR\toperation\"\xf7\x02\n" + "\x16PolicyEvaluationDetail\x12\x18\n" + "\aaddress\x18\x01 \x01(\tR\aaddress\x12-\n" + "\x06result\x18\x02 \x01(\x0e2\x15.proto.EvaluateResultR\x06result\x12\x12\n" + @@ -919,18 +1110,29 @@ const file_policy_proto_rawDesc = "" + "blockIndex\"\x95\x01\n" + "\x1ePolicyEvaluateResourceResponse\x12-\n" + "\x06result\x18\x01 \x01(\x0e2\x15.proto.EvaluateResultR\x06result\x12D\n" + - "\x0epolicy_details\x18\x02 \x03(\v2\x1d.proto.PolicyEvaluationDetailR\rpolicyDetails\"\x8f\x01\n" + + "\x0epolicy_details\x18\x02 \x03(\v2\x1d.proto.PolicyEvaluationDetailR\rpolicyDetails\"\xdd\x02\n" + "\x1dPolicyEvaluateProviderRequest\x12#\n" + "\rprovider_type\x18\x01 \x01(\tR\fproviderType\x12\x14\n" + - "\x05attrs\x18\x02 \x01(\fR\x05attrs\x123\n" + - "\bmetadata\x18\x03 \x01(\v2\x17.proto.ProviderMetadataR\bmetadata\"\x95\x01\n" + + "\x05attrs\x18\x02 \x01(\fR\x05attrs\x12Q\n" + + "\bmetadata\x18\x03 \x01(\v25.proto.PolicyEvaluateProviderRequest.ProviderMetadataR\bmetadata\x1a\xad\x01\n" + + "\x10ProviderMetadata\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + + "\x05alias\x18\x02 \x01(\tR\x05alias\x12\x1c\n" + + "\tnamespace\x18\x03 \x01(\tR\tnamespace\x12\x16\n" + + "\x06source\x18\x04 \x01(\tR\x06source\x12\x1f\n" + + "\vmodule_path\x18\x05 \x01(\tR\n" + + "modulePath\x12\x18\n" + + "\aversion\x18\x06 \x01(\tR\aversion\"\x95\x01\n" + "\x1ePolicyEvaluateProviderResponse\x12-\n" + "\x06result\x18\x01 \x01(\x0e2\x15.proto.EvaluateResultR\x06result\x12D\n" + - "\x0epolicy_details\x18\x02 \x03(\v2\x1d.proto.PolicyEvaluationDetailR\rpolicyDetails\"\x8b\x01\n" + + "\x0epolicy_details\x18\x02 \x03(\v2\x1d.proto.PolicyEvaluationDetailR\rpolicyDetails\"\xed\x01\n" + "\x1bPolicyEvaluateModuleRequest\x12#\n" + "\rmodule_source\x18\x01 \x01(\tR\fmoduleSource\x12\x14\n" + - "\x05attrs\x18\x02 \x01(\fR\x05attrs\x121\n" + - "\bmetadata\x18\x03 \x01(\v2\x15.proto.ModuleMetadataR\bmetadata\"\x93\x01\n" + + "\x05attrs\x18\x02 \x01(\fR\x05attrs\x12M\n" + + "\bmetadata\x18\x03 \x01(\v21.proto.PolicyEvaluateModuleRequest.ModuleMetadataR\bmetadata\x1aD\n" + + "\x0eModuleMetadata\x12\x18\n" + + "\aversion\x18\x01 \x01(\tR\aversion\x12\x18\n" + + "\aaddress\x18\x02 \x01(\tR\aaddress\"\x93\x01\n" + "\x1cPolicyEvaluateModuleResponse\x12-\n" + "\x06result\x18\x01 \x01(\x0e2\x15.proto.EvaluateResultR\x06result\x12D\n" + "\x0epolicy_details\x18\x02 \x03(\v2\x1d.proto.PolicyEvaluationDetailR\rpolicyDetails2\xed\x02\n" + @@ -952,7 +1154,7 @@ func file_policy_proto_rawDescGZIP() []byte { return file_policy_proto_rawDescData } -var file_policy_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_policy_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_policy_proto_goTypes = []any{ (*PolicySetupRequest)(nil), // 0: proto.PolicySetupRequest (*PolicySetupResponse)(nil), // 1: proto.PolicySetupResponse @@ -967,51 +1169,53 @@ var file_policy_proto_goTypes = []any{ (*PolicySetupRequest_ClientCapabilities)(nil), // 10: proto.PolicySetupRequest.ClientCapabilities (*PolicySetupResponse_TerraformConfiguration)(nil), // 11: proto.PolicySetupResponse.TerraformConfiguration (*PolicySetupResponse_ServerCapabilities)(nil), // 12: proto.PolicySetupResponse.ServerCapabilities - nil, // 13: proto.PolicySetupResponse.ServerCapabilities.ConfigurationsEntry - (*Diagnostic)(nil), // 14: proto.Diagnostic - (*ResourceMetadata)(nil), // 15: proto.ResourceMetadata - (EvaluateResult)(0), // 16: proto.EvaluateResult - (*Range)(nil), // 17: proto.Range - (*Snippet)(nil), // 18: proto.Snippet - (*ProviderMetadata)(nil), // 19: proto.ProviderMetadata - (*ModuleMetadata)(nil), // 20: proto.ModuleMetadata + nil, // 13: proto.PolicySetupResponse.ServerCapabilities.ConfigurationsEntry + (*PolicyEvaluateResourceRequest_ResourceMetadata)(nil), // 14: proto.PolicyEvaluateResourceRequest.ResourceMetadata + (*PolicyEvaluateProviderRequest_ProviderMetadata)(nil), // 15: proto.PolicyEvaluateProviderRequest.ProviderMetadata + (*PolicyEvaluateModuleRequest_ModuleMetadata)(nil), // 16: proto.PolicyEvaluateModuleRequest.ModuleMetadata + (*Diagnostic)(nil), // 17: proto.Diagnostic + (EvaluateResult)(0), // 18: proto.EvaluateResult + (*Range)(nil), // 19: proto.Range + (*Snippet)(nil), // 20: proto.Snippet + (Operation)(0), // 21: proto.Operation } var file_policy_proto_depIdxs = []int32{ 10, // 0: proto.PolicySetupRequest.client_capabilities:type_name -> proto.PolicySetupRequest.ClientCapabilities 12, // 1: proto.PolicySetupResponse.server_capabilities:type_name -> proto.PolicySetupResponse.ServerCapabilities - 14, // 2: proto.PolicySetupResponse.diagnostics:type_name -> proto.Diagnostic - 15, // 3: proto.PolicyEvaluateResourceRequest.metadata:type_name -> proto.ResourceMetadata - 16, // 4: proto.PolicyEvaluationDetail.result:type_name -> proto.EvaluateResult - 17, // 5: proto.PolicyEvaluationDetail.def_range:type_name -> proto.Range + 17, // 2: proto.PolicySetupResponse.diagnostics:type_name -> proto.Diagnostic + 14, // 3: proto.PolicyEvaluateResourceRequest.metadata:type_name -> proto.PolicyEvaluateResourceRequest.ResourceMetadata + 18, // 4: proto.PolicyEvaluationDetail.result:type_name -> proto.EvaluateResult + 19, // 5: proto.PolicyEvaluationDetail.def_range:type_name -> proto.Range 4, // 6: proto.PolicyEvaluationDetail.enforce_results:type_name -> proto.EnforceBlockResult - 14, // 7: proto.PolicyEvaluationDetail.diagnostics:type_name -> proto.Diagnostic - 16, // 8: proto.EnforceBlockResult.result:type_name -> proto.EvaluateResult - 17, // 9: proto.EnforceBlockResult.range:type_name -> proto.Range - 14, // 10: proto.EnforceBlockResult.diagnostics:type_name -> proto.Diagnostic - 18, // 11: proto.EnforceBlockResult.snippet:type_name -> proto.Snippet - 16, // 12: proto.PolicyEvaluateResourceResponse.result:type_name -> proto.EvaluateResult + 17, // 7: proto.PolicyEvaluationDetail.diagnostics:type_name -> proto.Diagnostic + 18, // 8: proto.EnforceBlockResult.result:type_name -> proto.EvaluateResult + 19, // 9: proto.EnforceBlockResult.range:type_name -> proto.Range + 17, // 10: proto.EnforceBlockResult.diagnostics:type_name -> proto.Diagnostic + 20, // 11: proto.EnforceBlockResult.snippet:type_name -> proto.Snippet + 18, // 12: proto.PolicyEvaluateResourceResponse.result:type_name -> proto.EvaluateResult 3, // 13: proto.PolicyEvaluateResourceResponse.policy_details:type_name -> proto.PolicyEvaluationDetail - 19, // 14: proto.PolicyEvaluateProviderRequest.metadata:type_name -> proto.ProviderMetadata - 16, // 15: proto.PolicyEvaluateProviderResponse.result:type_name -> proto.EvaluateResult + 15, // 14: proto.PolicyEvaluateProviderRequest.metadata:type_name -> proto.PolicyEvaluateProviderRequest.ProviderMetadata + 18, // 15: proto.PolicyEvaluateProviderResponse.result:type_name -> proto.EvaluateResult 3, // 16: proto.PolicyEvaluateProviderResponse.policy_details:type_name -> proto.PolicyEvaluationDetail - 20, // 17: proto.PolicyEvaluateModuleRequest.metadata:type_name -> proto.ModuleMetadata - 16, // 18: proto.PolicyEvaluateModuleResponse.result:type_name -> proto.EvaluateResult + 16, // 17: proto.PolicyEvaluateModuleRequest.metadata:type_name -> proto.PolicyEvaluateModuleRequest.ModuleMetadata + 18, // 18: proto.PolicyEvaluateModuleResponse.result:type_name -> proto.EvaluateResult 3, // 19: proto.PolicyEvaluateModuleResponse.policy_details:type_name -> proto.PolicyEvaluationDetail 13, // 20: proto.PolicySetupResponse.ServerCapabilities.configurations:type_name -> proto.PolicySetupResponse.ServerCapabilities.ConfigurationsEntry 11, // 21: proto.PolicySetupResponse.ServerCapabilities.ConfigurationsEntry.value:type_name -> proto.PolicySetupResponse.TerraformConfiguration - 0, // 22: proto.Policy.Setup:input_type -> proto.PolicySetupRequest - 2, // 23: proto.Policy.EvaluateResource:input_type -> proto.PolicyEvaluateResourceRequest - 6, // 24: proto.Policy.EvaluateProvider:input_type -> proto.PolicyEvaluateProviderRequest - 8, // 25: proto.Policy.EvaluateModule:input_type -> proto.PolicyEvaluateModuleRequest - 1, // 26: proto.Policy.Setup:output_type -> proto.PolicySetupResponse - 5, // 27: proto.Policy.EvaluateResource:output_type -> proto.PolicyEvaluateResourceResponse - 7, // 28: proto.Policy.EvaluateProvider:output_type -> proto.PolicyEvaluateProviderResponse - 9, // 29: proto.Policy.EvaluateModule:output_type -> proto.PolicyEvaluateModuleResponse - 26, // [26:30] is the sub-list for method output_type - 22, // [22:26] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 21, // 22: proto.PolicyEvaluateResourceRequest.ResourceMetadata.operation:type_name -> proto.Operation + 0, // 23: proto.Policy.Setup:input_type -> proto.PolicySetupRequest + 2, // 24: proto.Policy.EvaluateResource:input_type -> proto.PolicyEvaluateResourceRequest + 6, // 25: proto.Policy.EvaluateProvider:input_type -> proto.PolicyEvaluateProviderRequest + 8, // 26: proto.Policy.EvaluateModule:input_type -> proto.PolicyEvaluateModuleRequest + 1, // 27: proto.Policy.Setup:output_type -> proto.PolicySetupResponse + 5, // 28: proto.Policy.EvaluateResource:output_type -> proto.PolicyEvaluateResourceResponse + 7, // 29: proto.Policy.EvaluateProvider:output_type -> proto.PolicyEvaluateProviderResponse + 9, // 30: proto.Policy.EvaluateModule:output_type -> proto.PolicyEvaluateModuleResponse + 27, // [27:31] is the sub-list for method output_type + 23, // [23:27] is the sub-list for method input_type + 23, // [23:23] is the sub-list for extension type_name + 23, // [23:23] is the sub-list for extension extendee + 0, // [0:23] is the sub-list for field type_name } func init() { file_policy_proto_init() } @@ -1027,7 +1231,7 @@ func file_policy_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_policy_proto_rawDesc), len(file_policy_proto_rawDesc)), NumEnums: 0, - NumMessages: 14, + NumMessages: 17, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/policy/proto/policy.proto b/internal/policy/proto/policy.proto index 745c5ff6a86b..a0a08ce493ea 100644 --- a/internal/policy/proto/policy.proto +++ b/internal/policy/proto/policy.proto @@ -93,6 +93,11 @@ message PolicyEvaluateResourceRequest { // prior_attrs contains the state of the resource prior to the current operation. bytes prior_attrs = 5; + + message ResourceMetadata { + string provider_type = 1; + Operation operation = 2; + } } // PolicyEvaluationDetail contains detailed information about a single policy evaluation @@ -168,6 +173,15 @@ message PolicyEvaluateProviderRequest { // metadata contains the provider metadata that will be made available within the // provider_metadata data referenced within Terraform Policy files. ProviderMetadata metadata = 3; + + message ProviderMetadata { + string name = 1; + string alias = 2; + string namespace = 3; + string source = 4; + string module_path = 5; + string version = 6; + } } // EvaluateProviderResponse is the message response for the EvaluateProvider RPC. @@ -192,6 +206,11 @@ message PolicyEvaluateModuleRequest { // metadata contains the module metadata that will be made available within the // meta object referenced within module policy blocks. ModuleMetadata metadata = 3; + + message ModuleMetadata { + string version = 1; + string address = 2; + } } // EvaluateModuleResponse is the message response for the EvaluateModule RPC. diff --git a/internal/policy/proto/types.pb.go b/internal/policy/proto/types.pb.go index 320ee177a5fd..efe786a69149 100644 --- a/internal/policy/proto/types.pb.go +++ b/internal/policy/proto/types.pb.go @@ -157,213 +157,11 @@ func (Operation) EnumDescriptor() ([]byte, []int) { return file_types_proto_rawDescGZIP(), []int{1} } -type ResourceMetadata struct { - state protoimpl.MessageState `protogen:"open.v1"` - ProviderType string `protobuf:"bytes,1,opt,name=provider_type,json=providerType,proto3" json:"provider_type,omitempty"` - Operation Operation `protobuf:"varint,2,opt,name=operation,proto3,enum=proto.Operation" json:"operation,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ResourceMetadata) Reset() { - *x = ResourceMetadata{} - mi := &file_types_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ResourceMetadata) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ResourceMetadata) ProtoMessage() {} - -func (x *ResourceMetadata) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ResourceMetadata.ProtoReflect.Descriptor instead. -func (*ResourceMetadata) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{0} -} - -func (x *ResourceMetadata) GetProviderType() string { - if x != nil { - return x.ProviderType - } - return "" -} - -func (x *ResourceMetadata) GetOperation() Operation { - if x != nil { - return x.Operation - } - return Operation_CREATE -} - -type ModuleMetadata struct { - state protoimpl.MessageState `protogen:"open.v1"` - Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` - Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ModuleMetadata) Reset() { - *x = ModuleMetadata{} - mi := &file_types_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ModuleMetadata) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ModuleMetadata) ProtoMessage() {} - -func (x *ModuleMetadata) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ModuleMetadata.ProtoReflect.Descriptor instead. -func (*ModuleMetadata) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{1} -} - -func (x *ModuleMetadata) GetVersion() string { - if x != nil { - return x.Version - } - return "" -} - -func (x *ModuleMetadata) GetAddress() string { - if x != nil { - return x.Address - } - return "" -} - -type ProviderMetadata struct { - state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Alias string `protobuf:"bytes,2,opt,name=alias,proto3" json:"alias,omitempty"` - Namespace string `protobuf:"bytes,3,opt,name=namespace,proto3" json:"namespace,omitempty"` - Source string `protobuf:"bytes,4,opt,name=source,proto3" json:"source,omitempty"` - ModulePath string `protobuf:"bytes,5,opt,name=module_path,json=modulePath,proto3" json:"module_path,omitempty"` - Version string `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ProviderMetadata) Reset() { - *x = ProviderMetadata{} - mi := &file_types_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ProviderMetadata) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ProviderMetadata) ProtoMessage() {} - -func (x *ProviderMetadata) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[2] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ProviderMetadata.ProtoReflect.Descriptor instead. -func (*ProviderMetadata) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{2} -} - -func (x *ProviderMetadata) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *ProviderMetadata) GetAlias() string { - if x != nil { - return x.Alias - } - return "" -} - -func (x *ProviderMetadata) GetNamespace() string { - if x != nil { - return x.Namespace - } - return "" -} - -func (x *ProviderMetadata) GetSource() string { - if x != nil { - return x.Source - } - return "" -} - -func (x *ProviderMetadata) GetModulePath() string { - if x != nil { - return x.ModulePath - } - return "" -} - -func (x *ProviderMetadata) GetVersion() string { - if x != nil { - return x.Version - } - return "" -} - var File_types_proto protoreflect.FileDescriptor const file_types_proto_rawDesc = "" + "\n" + - "\vtypes.proto\x12\x05proto\"g\n" + - "\x10ResourceMetadata\x12#\n" + - "\rprovider_type\x18\x01 \x01(\tR\fproviderType\x12.\n" + - "\toperation\x18\x02 \x01(\x0e2\x10.proto.OperationR\toperation\"D\n" + - "\x0eModuleMetadata\x12\x18\n" + - "\aversion\x18\x01 \x01(\tR\aversion\x12\x18\n" + - "\aaddress\x18\x02 \x01(\tR\aaddress\"\xad\x01\n" + - "\x10ProviderMetadata\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + - "\x05alias\x18\x02 \x01(\tR\x05alias\x12\x1c\n" + - "\tnamespace\x18\x03 \x01(\tR\tnamespace\x12\x16\n" + - "\x06source\x18\x04 \x01(\tR\x06source\x12\x1f\n" + - "\vmodule_path\x18\x05 \x01(\tR\n" + - "modulePath\x12\x18\n" + - "\aversion\x18\x06 \x01(\tR\aversion*\xbb\x01\n" + + "\vtypes.proto\x12\x05proto*\xbb\x01\n" + "\x0eEvaluateResult\x12\x1b\n" + "\x17INVALID_EVALUATE_RESULT\x10\x00\x12\x1b\n" + "\x17UNKNOWN_EVALUATE_RESULT\x10\x01\x12\x19\n" + @@ -392,21 +190,16 @@ func file_types_proto_rawDescGZIP() []byte { } var file_types_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_types_proto_goTypes = []any{ - (EvaluateResult)(0), // 0: proto.EvaluateResult - (Operation)(0), // 1: proto.Operation - (*ResourceMetadata)(nil), // 2: proto.ResourceMetadata - (*ModuleMetadata)(nil), // 3: proto.ModuleMetadata - (*ProviderMetadata)(nil), // 4: proto.ProviderMetadata + (EvaluateResult)(0), // 0: proto.EvaluateResult + (Operation)(0), // 1: proto.Operation } var file_types_proto_depIdxs = []int32{ - 1, // 0: proto.ResourceMetadata.operation:type_name -> proto.Operation - 1, // [1:1] is the sub-list for method output_type - 1, // [1:1] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name } func init() { file_types_proto_init() } @@ -420,14 +213,13 @@ func file_types_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_types_proto_rawDesc), len(file_types_proto_rawDesc)), NumEnums: 2, - NumMessages: 3, + NumMessages: 0, NumExtensions: 0, NumServices: 0, }, GoTypes: file_types_proto_goTypes, DependencyIndexes: file_types_proto_depIdxs, EnumInfos: file_types_proto_enumTypes, - MessageInfos: file_types_proto_msgTypes, }.Build() File_types_proto = out.File file_types_proto_goTypes = nil diff --git a/internal/policy/proto/types.proto b/internal/policy/proto/types.proto index 30c5a2097adc..e116fc24a4e7 100644 --- a/internal/policy/proto/types.proto +++ b/internal/policy/proto/types.proto @@ -52,22 +52,3 @@ enum Operation { UPDATE = 1; DELETE = 2; } - -message ResourceMetadata { - string provider_type = 1; - Operation operation = 2; -} - -message ModuleMetadata { - string version = 1; - string address = 2; -} - -message ProviderMetadata { - string name = 1; - string alias = 2; - string namespace = 3; - string source = 4; - string module_path = 5; - string version = 6; -} From 31d64557525740df1aac16efa8a9cb20a0da285b Mon Sep 17 00:00:00 2001 From: Samsondeen Dare Date: Tue, 19 May 2026 15:55:02 +0200 Subject: [PATCH 7/7] a couple more messaging fixes --- internal/policy/callback/callback.go | 2 ++ internal/policy/callback/server.go | 8 ++++---- internal/policy/callback/testing.go | 3 --- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/policy/callback/callback.go b/internal/policy/callback/callback.go index 6df99ba5de48..0be98fe0c9bb 100644 --- a/internal/policy/callback/callback.go +++ b/internal/policy/callback/callback.go @@ -15,6 +15,8 @@ type Functions struct { GetDataSource func(datasource string, attrs cty.Value) (cty.Value, error) } +// Registry is an interface for managing callback functions for resources and +// data sources during policy evaluation. type Registry interface { Get(id uint32) (Functions, bool) NextID() uint32 diff --git a/internal/policy/callback/server.go b/internal/policy/callback/server.go index 05225d212d73..d5a93ba10bcf 100644 --- a/internal/policy/callback/server.go +++ b/internal/policy/callback/server.go @@ -28,7 +28,7 @@ type Server struct { func (s *Server) GetResources(_ context.Context, request *proto.GetResourcesRequest) (*proto.GetResourcesResponse, error) { attrs, err := msgpack.Unmarshal(request.Attributes, cty.DynamicPseudoType) if err != nil { - return nil, fmt.Errorf("failed to unserialize data: %w", err) + return nil, fmt.Errorf("failed to unserialize attributes: %w", err) } functions, ok := s.Registry.Get(request.EvaluationRequestId) if !ok { @@ -43,7 +43,7 @@ func (s *Server) GetResources(_ context.Context, request *proto.GetResourcesRequ for _, resource := range resources { result, err := msgpack.Marshal(resource, cty.DynamicPseudoType) if err != nil { - return nil, fmt.Errorf("failed to serialize data: %w", err) + return nil, fmt.Errorf("failed to serialize resource: %w", err) } results = append(results, result) } @@ -56,7 +56,7 @@ func (s *Server) GetResources(_ context.Context, request *proto.GetResourcesRequ func (s *Server) GetDataSource(_ context.Context, request *proto.GetDataSourceRequest) (*proto.GetDataSourceResponse, error) { config, err := msgpack.Unmarshal(request.Config, cty.DynamicPseudoType) if err != nil { - return nil, fmt.Errorf("failed to unserialize data: %w", err) + return nil, fmt.Errorf("failed to unserialize config: %w", err) } functions, ok := s.Registry.Get(request.EvaluationRequestId) @@ -70,7 +70,7 @@ func (s *Server) GetDataSource(_ context.Context, request *proto.GetDataSourceRe result, err := msgpack.Marshal(datasource, cty.DynamicPseudoType) if err != nil { - return nil, fmt.Errorf("failed to serialize data: %w", err) + return nil, fmt.Errorf("failed to serialize datasource: %w", err) } return &proto.GetDataSourceResponse{ diff --git a/internal/policy/callback/testing.go b/internal/policy/callback/testing.go index 299c32102ab7..442e2edba148 100644 --- a/internal/policy/callback/testing.go +++ b/internal/policy/callback/testing.go @@ -56,9 +56,6 @@ func (m *MockRegistry) NextID() uint32 { defer m.mu.Unlock() m.NextIDCalled = true - if m.NextIDValue != 0 { - return m.NextIDValue - } m.NextIDValue++ return m.NextIDValue }