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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions internal/policy/callback/callback.go
Original file line number Diff line number Diff line change
@@ -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)
}
85 changes: 85 additions & 0 deletions internal/policy/callback/server.go
Original file line number Diff line number Diff line change
@@ -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()
}
}
Loading
Loading