go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers-sdk/v1/plugin/runtime.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package plugin
     5  
     6  import (
     7  	"errors"
     8  
     9  	"go.mondoo.com/cnquery/llx"
    10  	"go.mondoo.com/cnquery/providers-sdk/v1/upstream"
    11  	"go.mondoo.com/cnquery/types"
    12  	"go.mondoo.com/cnquery/utils/syncx"
    13  )
    14  
    15  type Runtime struct {
    16  	Connection     Connection
    17  	Resources      syncx.Map[Resource]
    18  	Callback       ProviderCallback
    19  	HasRecording   bool
    20  	CreateResource CreateNamedResource
    21  	Upstream       *upstream.UpstreamClient
    22  }
    23  
    24  type Connection interface{}
    25  
    26  type CreateNamedResource func(runtime *Runtime, name string, args map[string]*llx.RawData) (Resource, error)
    27  
    28  type Resource interface {
    29  	MqlID() string
    30  	MqlName() string
    31  }
    32  
    33  func (r *Runtime) ResourceFromRecording(name string, id string) (map[string]*llx.RawData, error) {
    34  	data, err := r.Callback.GetRecording(&DataReq{
    35  		Resource:   name,
    36  		ResourceId: id,
    37  	})
    38  	if err != nil || data == nil {
    39  		return nil, err
    40  	}
    41  
    42  	// We don't want resources at this stage, because they have to be requested and
    43  	// initialized recursively. Instead callers can request these fields from the
    44  	// recording and initialize them.
    45  	// TODO: we could use the provided information for a later request.
    46  	for k, v := range data.Fields {
    47  		if types.Type(v.Data.Type).ContainsResource() {
    48  			delete(data.Fields, k)
    49  		}
    50  	}
    51  
    52  	return ProtoArgsToRawDataArgs(data.Fields)
    53  }
    54  
    55  // FieldResourceFromRecording loads a field which is a resource from a recording.
    56  // These are not immediately initialized when the recording is loaded, to avoid
    57  // having to recursively initialize too many things that won't be used. Once
    58  // it's time, this function is called to initialize the resource.
    59  func (r *Runtime) FieldResourceFromRecording(resource string, id string, field string) (*llx.RawData, error) {
    60  	data, err := r.Callback.GetRecording(&DataReq{
    61  		Resource:   resource,
    62  		ResourceId: id,
    63  		Field:      field,
    64  	})
    65  	if err != nil || data == nil {
    66  		return nil, err
    67  	}
    68  
    69  	fieldObj, ok := data.Fields[field]
    70  	if !ok {
    71  		return nil, nil
    72  	}
    73  
    74  	raw := fieldObj.RawData()
    75  	raw.Value, err = r.initResourcesFromRecording(raw.Value, raw.Type)
    76  	return raw, err
    77  }
    78  
    79  func (r *Runtime) initResourcesFromRecording(val interface{}, typ types.Type) (interface{}, error) {
    80  	switch {
    81  	case typ.IsArray():
    82  		arr := val.([]interface{})
    83  		ct := typ.Child()
    84  		var err error
    85  		for i := range arr {
    86  			arr[i], err = r.initResourcesFromRecording(arr[i], ct)
    87  			if err != nil {
    88  				return nil, err
    89  			}
    90  		}
    91  		return arr, nil
    92  
    93  	case typ.IsMap():
    94  		m := val.(map[string]interface{})
    95  		ct := typ.Child()
    96  		var err error
    97  		for k, v := range m {
    98  			m[k], err = r.initResourcesFromRecording(v, ct)
    99  			if err != nil {
   100  				return nil, err
   101  			}
   102  		}
   103  		return m, nil
   104  
   105  	case typ.IsResource():
   106  		// It has to be a mock resource if we loaded it from recording.
   107  		// We also do this as a kind of safety check (instead of using the interface)
   108  
   109  		resource := val.(*llx.MockResource)
   110  		args, err := r.ResourceFromRecording(resource.Name, resource.ID)
   111  		if err != nil {
   112  			return nil, err
   113  		}
   114  
   115  		return r.CreateResource(r, resource.Name, args)
   116  
   117  	default:
   118  		return val, nil
   119  	}
   120  }
   121  
   122  func (r *Runtime) CreateSharedResource(resource string, args map[string]*llx.RawData) (Resource, error) {
   123  	pargs, err := RawDataArgsToPrimitiveArgs(args)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	res, err := r.Callback.GetData(&DataReq{
   129  		Resource: resource,
   130  		Args:     pargs,
   131  	})
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	if res.Error != "" {
   137  		return nil, errors.New(res.Error)
   138  	}
   139  	raw := res.Data.RawData()
   140  	if !raw.Type.IsResource() {
   141  		return nil, errors.New("failed to create shared resource '" + resource + "' (non-resource return)")
   142  	}
   143  	return raw.Value.(Resource), nil
   144  }
   145  
   146  func (r *Runtime) GetSharedData(resource string, resourceID string, field string) (*llx.RawData, error) {
   147  	res, err := r.Callback.GetData(&DataReq{
   148  		Resource:   resource,
   149  		ResourceId: resourceID,
   150  		Field:      field,
   151  	})
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	if res.Error != "" {
   157  		return nil, errors.New(res.Error)
   158  	}
   159  	return res.Data.RawData(), nil
   160  }
   161  
   162  type TValue[T any] struct {
   163  	Data  T
   164  	State State
   165  	Error error
   166  }
   167  
   168  func (x *TValue[T]) ToDataRes(typ types.Type) *DataRes {
   169  	if x.State&StateIsSet == 0 {
   170  		return &DataRes{}
   171  	}
   172  	if x.State&StateIsNull != 0 {
   173  		if x.Error != nil {
   174  			return &DataRes{
   175  				Error: x.Error.Error(),
   176  				Data:  &llx.Primitive{Type: string(typ)},
   177  			}
   178  		}
   179  
   180  		return &DataRes{
   181  			Data: &llx.Primitive{Type: string(types.Nil)},
   182  		}
   183  	}
   184  	raw := llx.RawData{Type: typ, Value: x.Data, Error: x.Error}
   185  	res := raw.Result()
   186  	return &DataRes{Data: res.Data, Error: res.Error}
   187  }
   188  
   189  func PrimitiveToTValue[T any](p *llx.Primitive) TValue[T] {
   190  	raw := p.RawData()
   191  	if raw.Value == nil {
   192  		return TValue[T]{State: StateIsNull}
   193  	}
   194  	return TValue[T]{Data: raw.Value.(T), State: StateIsSet}
   195  }
   196  
   197  // RawToTValue converts a raw (interface{}) value into a typed value
   198  // and returns true if the type was correct.
   199  func RawToTValue[T any](value interface{}, err error) (TValue[T], bool) {
   200  	if value == nil {
   201  		return TValue[T]{State: StateIsNull | StateIsSet, Error: err}, true
   202  	}
   203  
   204  	tv, ok := value.(T)
   205  	if !ok {
   206  		return TValue[T]{}, false
   207  	}
   208  
   209  	return TValue[T]{Data: tv, State: StateIsSet, Error: err}, true
   210  }
   211  
   212  type State byte
   213  
   214  type notReady struct{}
   215  
   216  func (n notReady) Error() string {
   217  	return "NotReady"
   218  }
   219  
   220  var NotReady = notReady{}
   221  
   222  const (
   223  	StateIsSet State = 0x1 << iota
   224  	StateIsNull
   225  )
   226  
   227  func GetOrCompute[T any](cached *TValue[T], compute func() (T, error)) *TValue[T] {
   228  	if cached.State&StateIsSet != 0 {
   229  		return cached
   230  	}
   231  
   232  	x, err := compute()
   233  	if err != nil {
   234  		res := &TValue[T]{Data: x, Error: err}
   235  		if err != NotReady {
   236  			res.State = StateIsSet | StateIsNull
   237  		}
   238  		return res
   239  	}
   240  
   241  	// this only happens if the function set the field proactively, in which
   242  	// case we grab the value from the cached entry for consistency
   243  	if cached.State&StateIsSet != 0 {
   244  		return cached
   245  	}
   246  
   247  	(*cached) = TValue[T]{Data: x, State: StateIsSet, Error: err}
   248  	return cached
   249  }
   250  
   251  func PrimitiveArgsToRawDataArgs(pargs map[string]*llx.Primitive, runtime *Runtime) map[string]*llx.RawData {
   252  	res := make(map[string]*llx.RawData, len(pargs))
   253  	for k, v := range pargs {
   254  		// If it's an internal resource to this runtime, we need to look it up,
   255  		// since we are only handed references to resources, never the native
   256  		// resources themselves. Resources must exist before referencing them.
   257  		if typ := types.Type(v.Type); typ.IsResource() {
   258  			name := typ.ResourceName()
   259  			id := string(v.Value)
   260  			resource, _ := runtime.Resources.Get(name + "\x00" + id)
   261  			if resource != nil {
   262  				res[k] = llx.ResourceData(resource, name)
   263  				continue
   264  			}
   265  			// If it's not an internal resource, we can only reference it vv
   266  		}
   267  
   268  		res[k] = v.RawData()
   269  	}
   270  	return res
   271  }
   272  
   273  func RawDataArgsToPrimitiveArgs(pargs map[string]*llx.RawData) (map[string]*llx.Primitive, error) {
   274  	res := make(map[string]*llx.Primitive, len(pargs))
   275  	for k, v := range pargs {
   276  		vr := v.Result()
   277  		if vr.Error != "" {
   278  			return nil, errors.New("failed to serialize, error in raw data '" + k + "'")
   279  		}
   280  
   281  		res[k] = vr.Data
   282  	}
   283  	return res, nil
   284  }
   285  
   286  func ProtoArgsToRawDataArgs(pargs map[string]*llx.Result) (map[string]*llx.RawData, error) {
   287  	res := make(map[string]*llx.RawData, len(pargs))
   288  	var err error
   289  	for k, v := range pargs {
   290  		res[k] = v.RawData()
   291  	}
   292  
   293  	return res, err
   294  }
   295  
   296  func NonErrorArgs(pargs map[string]*llx.RawData) map[string]*llx.RawData {
   297  	if len(pargs) == 0 {
   298  		return map[string]*llx.RawData{}
   299  	}
   300  
   301  	res := map[string]*llx.RawData{}
   302  	for k, v := range pargs {
   303  		if v.Error != nil {
   304  			continue
   305  		}
   306  		res[k] = v
   307  	}
   308  	return res
   309  }