go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/mqlc/builtin_resource.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package mqlc 5 6 import ( 7 "errors" 8 "strconv" 9 10 "go.mondoo.com/cnquery/llx" 11 "go.mondoo.com/cnquery/mqlc/parser" 12 "go.mondoo.com/cnquery/providers-sdk/v1/resources" 13 "go.mondoo.com/cnquery/types" 14 "go.mondoo.com/cnquery/utils/multierr" 15 ) 16 17 func compileResourceDefault(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { 18 name := typ.ResourceName() 19 resource := c.Schema.Lookup(name) 20 if resource == nil { 21 return types.Nil, errors.New("cannot find resource '" + name + "' when compiling field '" + id + "'") 22 } 23 24 // special case that we can optimize: the previous call was a resource 25 // without any call arguments + the combined type is a resource itself 26 // in that case save the outer call and go for the resource directly 27 prevRef := c.tailRef() 28 prev := c.Result.CodeV2.Chunk(prevRef) 29 if prev.Call == llx.Chunk_FUNCTION && prev.Function == nil { 30 name := prev.Id + "." + id 31 resourceinfo := c.Schema.Lookup(name) 32 if resourceinfo != nil { 33 c.popChunk() 34 return c.addResource(name, resourceinfo, call) 35 } 36 } 37 38 fieldPath, fieldinfos, ok := c.findField(resource, id) 39 if !ok { 40 addFieldSuggestions(publicFieldsInfo(c, resource), id, c.Result) 41 return "", errors.New("cannot find field '" + id + "' in resource " + resource.Name) 42 } 43 44 lastRef := ref 45 for i, p := range fieldPath { 46 c.addChunk(&llx.Chunk{ 47 Call: llx.Chunk_FUNCTION, 48 Id: p, 49 Function: &llx.Function{ 50 Type: fieldinfos[i].Type, 51 Binding: lastRef, 52 // no Args for field calls yet 53 }, 54 }) 55 lastRef = c.tailRef() 56 } 57 58 return types.Type(fieldinfos[len(fieldinfos)-1].Type), nil 59 } 60 61 // FunctionSignature of any function type 62 type FunctionSignature struct { 63 Required int 64 Args []types.Type 65 } 66 67 func (f *FunctionSignature) expected2string() string { 68 if f.Required == len(f.Args) { 69 return strconv.Itoa(f.Required) 70 } 71 return strconv.Itoa(f.Required) + "-" + strconv.Itoa(len(f.Args)) 72 } 73 74 // Validate the field call against the signature. Returns nil if valid and 75 // an error message otherwise 76 func (f *FunctionSignature) Validate(args []*llx.Primitive, c *compiler) error { 77 max := len(f.Args) 78 given := len(args) 79 80 if given == 0 { 81 if f.Required > 0 { 82 return errors.New("no arguments given (expected " + f.expected2string() + ")") 83 } 84 return nil 85 } 86 87 if given < f.Required { 88 return errors.New("not enough arguments (expected " + f.expected2string() + ", got " + strconv.Itoa(given) + ")") 89 } 90 if given > max { 91 return errors.New("too many arguments (expected " + f.expected2string() + ", got " + strconv.Itoa(given) + ")") 92 } 93 94 for i := range args { 95 req := f.Args[i] 96 argT := types.Type(args[i].Type) 97 98 var err error 99 if argT == types.Ref { 100 argT, err = c.dereferenceType(args[i]) 101 if err != nil { 102 return multierr.Wrap(err, "failed to dereference argument in validating function signature") 103 } 104 } 105 106 // TODO: find out the real type from these REF types 107 if argT == req || req == types.Any { 108 continue 109 } 110 111 return errors.New("incorrect argument " + strconv.Itoa(i) + ": expected " + req.Label() + " got " + argT.Label()) 112 } 113 return nil 114 } 115 116 func listResource(c *compiler, typ types.Type) (*resources.ResourceInfo, error) { 117 name := typ.ResourceName() 118 resource := c.Schema.Lookup(name) 119 if resource == nil { 120 return nil, errors.New("cannot find resource '" + name + "'") 121 } 122 if resource.ListType == "" { 123 return nil, errors.New("resource '" + name + "' is not a list type") 124 } 125 return resource, nil 126 } 127 128 func compileResourceWhere(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { 129 resource, err := listResource(c, typ) 130 if err != nil { 131 return types.Nil, multierr.Wrap(err, "failed to compile "+id) 132 } 133 134 if call == nil { 135 return types.Nil, errors.New("missing filter argument for calling '" + id + "'") 136 } 137 if len(call.Function) > 1 { 138 return types.Nil, errors.New("too many arguments when calling '" + id + "', only 1 is supported") 139 } 140 141 // if the where function is called without arguments, we don't have to do anything 142 // so we just return the caller type as no additional step in the compiler is necessary 143 if len(call.Function) == 0 { 144 return typ, nil 145 } 146 147 arg := call.Function[0] 148 if arg.Name != "" { 149 return types.Nil, errors.New("called '" + id + "' function with a named parameter, which is not supported") 150 } 151 152 refs, err := c.blockExpressions([]*parser.Expression{arg.Value}, types.Array(types.Type(resource.ListType)), ref) 153 if err != nil { 154 return types.Nil, err 155 } 156 if refs.block == 0 { 157 return types.Nil, errors.New("called '" + id + "' clause without a function block") 158 } 159 ref = refs.binding 160 161 resourceRef := c.tailRef() 162 163 listType, err := compileResourceDefault(c, typ, ref, "list", nil) 164 if err != nil { 165 return listType, err 166 } 167 listRef := c.tailRef() 168 169 args := []*llx.Primitive{ 170 llx.RefPrimitiveV2(listRef), 171 llx.FunctionPrimitive(refs.block), 172 } 173 for _, v := range refs.deps { 174 if c.isInMyBlock(v) { 175 args = append(args, llx.RefPrimitiveV2(v)) 176 } 177 } 178 c.blockDeps = append(c.blockDeps, refs.deps...) 179 180 c.addChunk(&llx.Chunk{ 181 Call: llx.Chunk_FUNCTION, 182 Id: id, 183 Function: &llx.Function{ 184 Type: string(types.Resource(resource.Name)), 185 Binding: resourceRef, 186 Args: args, 187 }, 188 }) 189 return typ, nil 190 } 191 192 func compileResourceMap(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { 193 resource, err := listResource(c, typ) 194 if err != nil { 195 return types.Nil, multierr.Wrap(err, "failed to compile "+id) 196 } 197 198 if call == nil { 199 return types.Nil, errors.New("missing filter argument for calling '" + id + "'") 200 } 201 if len(call.Function) > 1 { 202 return types.Nil, errors.New("too many arguments when calling '" + id + "', only 1 is supported") 203 } 204 205 // if the where function is called without arguments, we don't have to do anything 206 // so we just return the caller type as no additional step in the compiler is necessary 207 if len(call.Function) == 0 { 208 return typ, nil 209 } 210 211 arg := call.Function[0] 212 if arg.Name != "" { 213 return types.Nil, errors.New("called '" + id + "' function with a named parameter, which is not supported") 214 } 215 216 refs, err := c.blockExpressions([]*parser.Expression{arg.Value}, types.Array(types.Type(resource.ListType)), ref) 217 if err != nil { 218 return types.Nil, err 219 } 220 if refs.block == 0 { 221 return types.Nil, errors.New("called '" + id + "' clause without a function block") 222 } 223 ref = refs.binding 224 225 mappedType, err := c.blockType(refs.block) 226 if err != nil { 227 return types.Nil, multierr.Wrap(err, "called '"+id+"' with a bad function block, types don't match") 228 } 229 230 resourceRef := c.tailRef() 231 232 listType, err := compileResourceDefault(c, typ, ref, "list", nil) 233 if err != nil { 234 return listType, err 235 } 236 listRef := c.tailRef() 237 238 args := []*llx.Primitive{ 239 llx.RefPrimitiveV2(listRef), 240 llx.FunctionPrimitive(refs.block), 241 } 242 for _, v := range refs.deps { 243 if c.isInMyBlock(v) { 244 args = append(args, llx.RefPrimitiveV2(v)) 245 } 246 } 247 c.blockDeps = append(c.blockDeps, refs.deps...) 248 249 c.addChunk(&llx.Chunk{ 250 Call: llx.Chunk_FUNCTION, 251 Id: id, 252 Function: &llx.Function{ 253 Type: string(types.Array(mappedType)), 254 Binding: resourceRef, 255 Args: args, 256 }, 257 }) 258 259 return types.Array(mappedType), nil 260 } 261 262 func compileResourceContains(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { 263 // resource.where 264 _, err := compileResourceWhere(c, typ, ref, "where", call) 265 if err != nil { 266 return types.Nil, err 267 } 268 resourceRef := c.tailRef() 269 270 // .list 271 t, err := compileResourceDefault(c, typ, resourceRef, "list", nil) 272 if err != nil { 273 return t, err 274 } 275 listRef := c.tailRef() 276 277 // .length 278 c.addChunk(&llx.Chunk{ 279 Call: llx.Chunk_FUNCTION, 280 Id: "length", 281 Function: &llx.Function{ 282 Type: string(types.Int), 283 Binding: listRef, 284 }, 285 }) 286 287 // > 0 288 c.addChunk(&llx.Chunk{ 289 Call: llx.Chunk_FUNCTION, 290 Id: string(">" + types.Int), 291 Function: &llx.Function{ 292 Type: string(types.Bool), 293 Binding: c.tailRef(), 294 Args: []*llx.Primitive{ 295 llx.IntPrimitive(0), 296 }, 297 }, 298 }) 299 300 checksum := c.Result.CodeV2.Checksums[c.tailRef()] 301 c.Result.Labels.Labels[checksum] = typ.ResourceName() + ".contains()" 302 303 return types.Bool, nil 304 } 305 306 func compileResourceAll(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { 307 // resource.$whereNot 308 _, err := compileResourceWhere(c, typ, ref, "$whereNot", call) 309 if err != nil { 310 return types.Nil, err 311 } 312 whereRef := c.tailRef() 313 314 listType, err := compileResourceDefault(c, typ, c.tailRef(), "list", nil) 315 if err != nil { 316 return listType, err 317 } 318 listRef := c.tailRef() 319 320 if err := compileListAssertionMsg(c, listType, whereRef-1, listRef, listRef); err != nil { 321 return types.Nil, err 322 } 323 324 c.addChunk(&llx.Chunk{ 325 Call: llx.Chunk_FUNCTION, 326 Id: "$all", 327 Function: &llx.Function{ 328 Type: string(types.Bool), 329 Binding: listRef, 330 }, 331 }) 332 333 checksum := c.Result.CodeV2.Checksums[c.tailRef()] 334 c.Result.Labels.Labels[checksum] = typ.ResourceName() + ".all()" 335 336 return types.Bool, nil 337 } 338 339 func compileResourceAny(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { 340 // resource.where 341 _, err := compileResourceWhere(c, typ, ref, "where", call) 342 if err != nil { 343 return types.Nil, err 344 } 345 whereRef := c.tailRef() 346 347 listType, err := compileResourceDefault(c, typ, whereRef, "list", nil) 348 if err != nil { 349 return listType, err 350 } 351 listRef := c.tailRef() 352 353 if err := compileListAssertionMsg(c, listType, whereRef-1, whereRef-1, listRef); err != nil { 354 return types.Nil, err 355 } 356 357 c.addChunk(&llx.Chunk{ 358 Call: llx.Chunk_FUNCTION, 359 Id: "$any", 360 Function: &llx.Function{ 361 Type: string(types.Bool), 362 Binding: listRef, 363 }, 364 }) 365 366 checksum := c.Result.CodeV2.Checksums[c.tailRef()] 367 c.Result.Labels.Labels[checksum] = typ.ResourceName() + ".any()" 368 369 return types.Bool, nil 370 } 371 372 func compileResourceOne(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { 373 // resource.where 374 _, err := compileResourceWhere(c, typ, ref, "where", call) 375 if err != nil { 376 return types.Nil, err 377 } 378 whereRef := c.tailRef() 379 380 listType, err := compileResourceDefault(c, typ, c.tailRef(), "list", nil) 381 if err != nil { 382 return listType, err 383 } 384 listRef := c.tailRef() 385 386 if err := compileListAssertionMsg(c, listType, whereRef-1, listRef, listRef); err != nil { 387 return types.Nil, err 388 } 389 390 c.addChunk(&llx.Chunk{ 391 Call: llx.Chunk_FUNCTION, 392 Id: "$one", 393 Function: &llx.Function{ 394 Type: string(types.Bool), 395 Binding: listRef, 396 }, 397 }) 398 399 checksum := c.Result.CodeV2.Checksums[c.tailRef()] 400 c.Result.Labels.Labels[checksum] = typ.ResourceName() + ".one()" 401 402 return types.Bool, nil 403 } 404 405 func compileResourceNone(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { 406 // resource.where 407 _, err := compileResourceWhere(c, typ, ref, "where", call) 408 if err != nil { 409 return types.Nil, err 410 } 411 whereRef := c.tailRef() 412 413 listType, err := compileResourceDefault(c, typ, c.tailRef(), "list", nil) 414 if err != nil { 415 return listType, err 416 } 417 listRef := c.tailRef() 418 419 if err := compileListAssertionMsg(c, listType, whereRef-1, listRef, listRef); err != nil { 420 return types.Nil, err 421 } 422 423 c.addChunk(&llx.Chunk{ 424 Call: llx.Chunk_FUNCTION, 425 Id: "$none", 426 Function: &llx.Function{ 427 Type: string(types.Bool), 428 Binding: listRef, 429 }, 430 }) 431 432 checksum := c.Result.CodeV2.Checksums[c.tailRef()] 433 c.Result.Labels.Labels[checksum] = typ.ResourceName() + ".none()" 434 435 return types.Bool, nil 436 } 437 438 func compileResourceLength(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { 439 if call != nil && len(call.Function) > 0 { 440 return types.Nil, errors.New("function " + id + " does not take arguments") 441 } 442 443 _, err := listResource(c, typ) 444 if err != nil { 445 return types.Nil, multierr.Wrap(err, "failed to compile "+id) 446 } 447 448 resourceRef := c.tailRef() 449 450 t, err := compileResourceDefault(c, typ, ref, "list", nil) 451 if err != nil { 452 return t, err 453 } 454 listRef := c.tailRef() 455 456 c.addChunk(&llx.Chunk{ 457 Call: llx.Chunk_FUNCTION, 458 Id: id, 459 Function: &llx.Function{ 460 Type: string(types.Int), 461 Binding: resourceRef, 462 Args: []*llx.Primitive{ 463 llx.RefPrimitiveV2(listRef), 464 }, 465 }, 466 }) 467 return typ, nil 468 } 469 470 func compileResourceParseDate(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { 471 if call == nil { 472 return types.Nil, errors.New("missing arguments to parse date") 473 } 474 475 functionID := string(typ) + "." + id 476 477 init := &resources.Init{ 478 Args: []*resources.TypedArg{ 479 {Name: "value", Type: string(types.String)}, 480 {Name: "format", Type: string(types.String)}, 481 }, 482 } 483 args, err := c.unnamedArgs("parse."+id, init, call.Function) 484 if err != nil { 485 return types.Nil, err 486 } 487 488 rawArgs := make([]*llx.Primitive, len(call.Function)) 489 for i := range call.Function { 490 rawArgs[i] = args[i*2+1] 491 } 492 493 if len(rawArgs) == 0 { 494 return types.Nil, errors.New("missing arguments to parse date") 495 } 496 497 c.addChunk(&llx.Chunk{ 498 Call: llx.Chunk_FUNCTION, 499 Id: functionID, 500 Function: &llx.Function{ 501 Type: string(types.Time), 502 Binding: ref, 503 Args: rawArgs, 504 }, 505 }) 506 return types.Time, nil 507 }