go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/mql/mql.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package mql
     5  
     6  import (
     7  	"errors"
     8  
     9  	"github.com/rs/zerolog/log"
    10  	"go.mondoo.com/cnquery"
    11  	"go.mondoo.com/cnquery/llx"
    12  	"go.mondoo.com/cnquery/mql/internal"
    13  	"go.mondoo.com/cnquery/mqlc"
    14  )
    15  
    16  // New creates a new MQL executor instance. It allows you to easily run multiple queries against the
    17  // same runtime
    18  func New(runtime llx.Runtime, features cnquery.Features) *Executor {
    19  	return &Executor{
    20  		runtime:  runtime,
    21  		features: features,
    22  	}
    23  }
    24  
    25  type Executor struct {
    26  	runtime  llx.Runtime
    27  	features cnquery.Features
    28  }
    29  
    30  // Exec runs a query with properties against the runtime
    31  func (e *Executor) Exec(query string, props map[string]*llx.Primitive) (*llx.RawData, error) {
    32  	return Exec(query, e.runtime, e.features, props)
    33  }
    34  
    35  func Exec(query string, runtime llx.Runtime, features cnquery.Features, props map[string]*llx.Primitive) (*llx.RawData, error) {
    36  	bundle, err := mqlc.Compile(query, props, mqlc.NewConfig(runtime.Schema(), features))
    37  	if err != nil {
    38  		return nil, errors.New("failed to compile: " + err.Error())
    39  	}
    40  
    41  	var results []*llx.RawResult
    42  
    43  	if len(bundle.CodeV2.Entrypoints()) == 0 {
    44  		return llx.NilData, nil
    45  	}
    46  
    47  	if !bundle.CodeV2.Blocks[0].SingleValue {
    48  		log.Warn().Str("query", query).Msg("mql> Code must only return one value, but it has many configured. Only returning last result.")
    49  	}
    50  
    51  	raw, err := ExecuteCode(runtime, bundle, props, features)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	results = llx.ReturnValuesV2(bundle, func(checksum string) (*llx.RawResult, bool) {
    57  		res, ok := raw[checksum]
    58  		return res, ok
    59  	})
    60  
    61  	if len(results) > 1 {
    62  		return nil, errors.New("too many results received")
    63  	}
    64  
    65  	rawres := results[0]
    66  	res, err := rawres.Data.Dereference(rawres.CodeID, bundle)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	return res, nil
    72  }
    73  
    74  func ExecuteCode(runtime llx.Runtime, codeBundle *llx.CodeBundle, props map[string]*llx.Primitive, features cnquery.Features) (map[string]*llx.RawResult, error) {
    75  	builder := internal.NewBuilder()
    76  	builder.WithFeatureBoolAssertions(features.IsActive(cnquery.BoolAssertions))
    77  
    78  	builder.AddQuery(codeBundle, nil, props)
    79  	for _, checksum := range internal.CodepointChecksums(codeBundle) {
    80  		builder.CollectDatapoint(checksum)
    81  	}
    82  
    83  	resultMap := map[string]*llx.RawResult{}
    84  	collector := &internal.FuncCollector{
    85  		SinkDataFunc: func(results []*llx.RawResult) {
    86  			for _, d := range results {
    87  				resultMap[d.CodeID] = d
    88  			}
    89  		},
    90  	}
    91  	builder.AddDatapointCollector(collector)
    92  
    93  	ge, err := builder.Build(runtime.Schema(), runtime, "")
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	if err := ge.Execute(); err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	return resultMap, nil
   103  }