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() {