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 }