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  }