github.com/rajeev159/opa@v0.45.0/resolver/wasm/wasm.go (about)

     1  // Copyright 2020 The OPA Authors.  All rights reserved.
     2  // Use of this source code is governed by an Apache2
     3  // license that can be found in the LICENSE file.
     4  
     5  package wasm
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"strconv"
    11  
    12  	"github.com/open-policy-agent/opa/ast"
    13  	"github.com/open-policy-agent/opa/internal/rego/opa"
    14  	"github.com/open-policy-agent/opa/resolver"
    15  )
    16  
    17  // New creates a new Resolver instance which is using the Wasm module
    18  // policy for the given entrypoint ref.
    19  func New(entrypoints []ast.Ref, policy []byte, data interface{}) (*Resolver, error) {
    20  	e, err := opa.LookupEngine("wasm")
    21  	if err != nil {
    22  		return nil, err
    23  	}
    24  	o, err := e.New().
    25  		WithPolicyBytes(policy).
    26  		WithDataJSON(data).
    27  		Init()
    28  	if err != nil {
    29  		return nil, err
    30  	}
    31  
    32  	// Construct a quick lookup table of ref -> entrypoint ID
    33  	// for handling evaluations. Only the entrypoints provided
    34  	// by the caller will be constructed, this may be a subset
    35  	// of entrypoints available in the Wasm module, however
    36  	// only the configured ones will be used when Eval() is
    37  	// called.
    38  	entrypointRefToID := ast.NewValueMap()
    39  	epIDs, err := o.Entrypoints(context.Background())
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	for path, id := range epIDs {
    44  		for _, ref := range entrypoints {
    45  			refPtr, err := ref.Ptr()
    46  			if err != nil {
    47  				return nil, err
    48  			}
    49  			if refPtr == path {
    50  				entrypointRefToID.Put(ref, ast.Number(strconv.Itoa(int(id))))
    51  			}
    52  		}
    53  	}
    54  
    55  	return &Resolver{
    56  		entrypoints:   entrypoints,
    57  		entrypointIDs: entrypointRefToID,
    58  		o:             o,
    59  	}, nil
    60  }
    61  
    62  // Resolver implements the resolver.Resolver interface
    63  // using Wasm modules to perform an evaluation.
    64  type Resolver struct {
    65  	entrypoints   []ast.Ref
    66  	entrypointIDs *ast.ValueMap
    67  	o             opa.EvalEngine
    68  }
    69  
    70  // Entrypoints returns a list of entrypoints this resolver is configured to
    71  // perform evaluations on.
    72  func (r *Resolver) Entrypoints() []ast.Ref {
    73  	return r.entrypoints
    74  }
    75  
    76  // Close shuts down the resolver.
    77  func (r *Resolver) Close() {
    78  	r.o.Close()
    79  }
    80  
    81  // Eval performs an evaluation using the provided input and the Wasm module
    82  // associated with this Resolver instance.
    83  func (r *Resolver) Eval(ctx context.Context, input resolver.Input) (resolver.Result, error) {
    84  	v := r.entrypointIDs.Get(input.Ref)
    85  	if v == nil {
    86  		return resolver.Result{}, fmt.Errorf("unknown entrypoint %s", input.Ref)
    87  	}
    88  
    89  	numValue, ok := v.(ast.Number)
    90  	if !ok {
    91  		return resolver.Result{}, fmt.Errorf("internal error: invalid entrypoint id %s", numValue)
    92  	}
    93  
    94  	epID, ok := numValue.Int()
    95  	if !ok {
    96  		return resolver.Result{}, fmt.Errorf("internal error: invalid entrypoint id %s", numValue)
    97  	}
    98  
    99  	var in *interface{}
   100  	if input.Input != nil {
   101  		var str interface{} = []byte(input.Input.String())
   102  		in = &str
   103  	}
   104  
   105  	opts := opa.EvalOpts{
   106  		Input:      in,
   107  		Entrypoint: int32(epID),
   108  		Metrics:    input.Metrics,
   109  	}
   110  	out, err := r.o.Eval(ctx, opts)
   111  	if err != nil {
   112  		return resolver.Result{}, err
   113  	}
   114  
   115  	result, err := getResult(out)
   116  	if err != nil {
   117  		return resolver.Result{}, err
   118  	}
   119  
   120  	return resolver.Result{Value: result}, nil
   121  }
   122  
   123  // SetData will update the external data for the Wasm instance.
   124  func (r *Resolver) SetData(ctx context.Context, data interface{}) error {
   125  	return r.o.SetData(ctx, data)
   126  }
   127  
   128  // SetDataPath will set the provided data on the wasm instance at the specified path.
   129  func (r *Resolver) SetDataPath(ctx context.Context, path []string, data interface{}) error {
   130  	return r.o.SetDataPath(ctx, path, data)
   131  }
   132  
   133  // RemoveDataPath will remove any data at the specified path.
   134  func (r *Resolver) RemoveDataPath(ctx context.Context, path []string) error {
   135  	return r.o.RemoveDataPath(ctx, path)
   136  }
   137  
   138  func getResult(evalResult *opa.Result) (ast.Value, error) {
   139  
   140  	parsed, err := ast.ParseTerm(string(evalResult.Result))
   141  	if err != nil {
   142  		return nil, fmt.Errorf("failed to parse wasm result: %s", err)
   143  	}
   144  
   145  	resultSet, ok := parsed.Value.(ast.Set)
   146  	if !ok {
   147  		return nil, fmt.Errorf("illegal result type")
   148  	}
   149  
   150  	if resultSet.Len() == 0 {
   151  		return nil, nil
   152  	}
   153  
   154  	if resultSet.Len() > 1 {
   155  		return nil, fmt.Errorf("illegal result type")
   156  	}
   157  
   158  	var obj ast.Object
   159  	err = resultSet.Iter(func(term *ast.Term) error {
   160  		obj, ok = term.Value.(ast.Object)
   161  		if !ok || obj.Len() != 1 {
   162  			return fmt.Errorf("illegal result type")
   163  		}
   164  		return nil
   165  	})
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	result := obj.Get(ast.StringTerm("result"))
   171  
   172  	return result.Value, nil
   173  }