go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/internal/datalakes/inmemory/query_conductor.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package inmemory
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  
    11  	"github.com/rs/zerolog/log"
    12  	"go.mondoo.com/cnquery/explorer"
    13  	"go.mondoo.com/cnquery/llx"
    14  	"go.mondoo.com/cnquery/types"
    15  	"go.mondoo.com/cnquery/utils/multierr"
    16  )
    17  
    18  type wrapResolved struct {
    19  	*explorer.ResolvedPack
    20  	filtersChecksum string
    21  	assetMRN        string
    22  }
    23  
    24  func (db *Db) SetResolvedPack(mrn string, filtersChecksum string, resolved *explorer.ResolvedPack) error {
    25  	v := wrapResolved{resolved, filtersChecksum, mrn}
    26  	ok := db.cache.Set(dbIDresolvedPack+mrn, v, 1)
    27  	if !ok {
    28  		return errors.New("failed to save query '" + mrn + "' to cache")
    29  	}
    30  	return nil
    31  }
    32  
    33  func (db *Db) SetAssetResolvedPack(ctx context.Context, assetMrn string, resolved *explorer.ResolvedPack, version explorer.ResolvedVersion) error {
    34  	x, ok := db.cache.Get(dbIDAsset + assetMrn)
    35  	if !ok {
    36  		return errors.New("cannot find asset '" + assetMrn + "'")
    37  	}
    38  	assetw := x.(wrapAsset)
    39  
    40  	if assetw.ResolvedPack != nil && assetw.ResolvedPack.GraphExecutionChecksum == resolved.GraphExecutionChecksum && string(assetw.ResolvedVersion) == string(version) {
    41  		log.Debug().
    42  			Str("asset", assetMrn).
    43  			Msg("resolverj.db> asset resolved query pack is already cached (and unchanged)")
    44  		return nil
    45  	}
    46  
    47  	assetw.ResolvedPack = resolved
    48  	assetw.ResolvedVersion = version
    49  
    50  	var err error
    51  	job := resolved.ExecutionJob
    52  	for checksum, info := range job.Datapoints {
    53  		err = db.initDataValue(ctx, assetMrn, checksum, types.Type(info.Type))
    54  		if err != nil {
    55  			log.Error().
    56  				Err(err).
    57  				Str("asset", assetMrn).
    58  				Str("query checksum", checksum).
    59  				Msg("resolver.db> failed to set asset resolved pack, failed to initialize data value")
    60  			return errors.New("failed to create asset scoring job (failed to init data)")
    61  		}
    62  	}
    63  
    64  	ok = db.cache.Set(dbIDAsset+assetMrn, assetw, 1)
    65  	if !ok {
    66  		return errors.New("failed to save resolved pack for asset '" + assetMrn + "'")
    67  	}
    68  
    69  	return nil
    70  }
    71  
    72  func (db *Db) GetResolvedPack(mrn string) (*explorer.ResolvedPack, error) {
    73  	q, ok := db.cache.Get(dbIDresolvedPack + mrn)
    74  	if !ok {
    75  		return nil, errors.New("query '" + mrn + "' not found")
    76  	}
    77  	return (q.(wrapResolved)).ResolvedPack, nil
    78  }
    79  
    80  var errTypesDontMatch = errors.New("types don't match")
    81  
    82  // UpdateData sets the list of data value for a given asset and returns a list of updated IDs
    83  func (db *Db) UpdateData(ctx context.Context, assetMrn string, data map[string]*llx.Result) (map[string]types.Type, error) {
    84  	resolved, err := db.GetResolvedPack(assetMrn)
    85  	if err != nil {
    86  		return nil, errors.New("cannot find collectorJob to store data: " + err.Error())
    87  	}
    88  	executionJob := resolved.ExecutionJob
    89  
    90  	res := make(map[string]types.Type, len(data))
    91  	var errs multierr.Errors
    92  	for dpChecksum, val := range data {
    93  		info, ok := executionJob.Datapoints[dpChecksum]
    94  		if !ok {
    95  			return nil, errors.New("cannot find this datapoint to store values: " + dpChecksum)
    96  		}
    97  
    98  		if val.Data != nil && !val.Data.IsNil() && val.Data.Type != "" &&
    99  			val.Data.Type != info.Type && types.Type(info.Type) != types.Unset {
   100  			log.Warn().
   101  				Str("checksum", dpChecksum).
   102  				Str("asset", assetMrn).
   103  				Interface("data", val.Data).
   104  				Str("expected", types.Type(info.Type).Label()).
   105  				Str("received", types.Type(val.Data.Type).Label()).
   106  				Msg("resolver.db> failed to store data, types don't match")
   107  
   108  			errs.Add(fmt.Errorf("failed to store data for %q, %w: expected %s, got %s",
   109  				dpChecksum, errTypesDontMatch, types.Type(info.Type).Label(), types.Type(val.Data.Type).Label()))
   110  
   111  			continue
   112  		}
   113  
   114  		err := db.setDatum(ctx, assetMrn, dpChecksum, val)
   115  		if err != nil {
   116  			errs.Add(err)
   117  			continue
   118  		}
   119  
   120  		// TODO: we don't know which data was updated and which wasn't yet, so
   121  		// we currently always notify...
   122  		res[dpChecksum] = types.Type(info.Type)
   123  	}
   124  
   125  	if !errs.IsEmpty() {
   126  		return nil, errs.Deduplicate()
   127  	}
   128  	return res, nil
   129  }
   130  
   131  func (db *Db) setDatum(ctx context.Context, assetMrn string, checksum string, value *llx.Result) error {
   132  	id := dbIDData + assetMrn + "\x00" + checksum
   133  	ok := db.cache.Set(id, value, 1)
   134  	if !ok {
   135  		return errors.New("failed to save asset data for asset '" + assetMrn + "' and checksum '" + checksum + "'")
   136  	}
   137  	return nil
   138  }
   139  
   140  // GetReport retrieves all scores and data for a given asset
   141  func (db *Db) GetReport(ctx context.Context, assetMrn string, packMrn string) (*explorer.Report, error) {
   142  	x, ok := db.cache.Get(dbIDAsset + assetMrn)
   143  	if !ok {
   144  		return nil, errors.New("cannot find asset '" + assetMrn + "'")
   145  	}
   146  	assetw := x.(wrapAsset)
   147  	resolvedPack := assetw.ResolvedPack
   148  
   149  	data := map[string]*llx.Result{}
   150  	for id := range resolvedPack.ExecutionJob.Datapoints {
   151  		datum, ok := db.cache.Get(dbIDData + assetMrn + "\x00" + id)
   152  		if !ok {
   153  			continue
   154  		}
   155  		if datum == nil {
   156  			data[id] = &llx.Result{
   157  				Data:   llx.NilPrimitive,
   158  				CodeId: id,
   159  			}
   160  		}
   161  		data[id] = datum.(*llx.Result)
   162  	}
   163  
   164  	return &explorer.Report{
   165  		PackMrn:   packMrn,
   166  		EntityMrn: assetMrn,
   167  		Data:      data,
   168  	}, nil
   169  }
   170  
   171  func (db *Db) initDataValue(ctx context.Context, assetMrn string, checksum string, typ types.Type) error {
   172  	id := dbIDData + assetMrn + "\x00" + checksum
   173  	_, ok := db.cache.Get(id)
   174  	if ok {
   175  		return nil
   176  	}
   177  
   178  	ok = db.cache.Set(id, nil, 1)
   179  	if !ok {
   180  		return errors.New("failed to initialize data value for asset '" + assetMrn + "' with checksum '" + checksum + "'")
   181  	}
   182  	return nil
   183  }
   184  
   185  // SetProps will override properties for a given entity (asset, space, org)
   186  func (db *Db) SetProps(ctx context.Context, req *explorer.PropsReq) error {
   187  	x, ok := db.cache.Get(dbIDAsset + req.EntityMrn)
   188  	if !ok {
   189  		return errors.New("failed to find entity " + req.EntityMrn)
   190  	}
   191  	asset := x.(wrapAsset)
   192  
   193  	if asset.Bundle == nil {
   194  		return errors.New("found an asset without a bundle configured in the DB")
   195  	}
   196  
   197  	allProps := make(map[string]*explorer.Property, len(asset.Bundle.Props))
   198  	for i := range asset.Bundle.Props {
   199  		cur := asset.Bundle.Props[i]
   200  		if cur.Mrn != "" {
   201  			allProps[cur.Mrn] = cur
   202  		}
   203  		if cur.Uid != "" {
   204  			allProps[cur.Uid] = cur
   205  		}
   206  	}
   207  
   208  	for i := range req.Props {
   209  		cur := req.Props[i]
   210  		id := cur.Mrn
   211  		if id == "" {
   212  			id = cur.Uid
   213  		}
   214  		if id == "" {
   215  			return errors.New("cannot set property without MRN: " + cur.Mql)
   216  		}
   217  
   218  		if cur.Mql == "" {
   219  			delete(allProps, id)
   220  		}
   221  		allProps[id] = cur
   222  	}
   223  
   224  	asset.Bundle.Props = []*explorer.Property{}
   225  	for k, v := range allProps {
   226  		// since props can be in the list with both UIDs and MRNs, in the case
   227  		// where a property sets both we want to ignore one entry to avoid duplicates
   228  		if v.Mrn != "" && v.Uid != "" && k == v.Uid {
   229  			continue
   230  		}
   231  		asset.Bundle.Props = append(asset.Bundle.Props, v)
   232  	}
   233  
   234  	db.cache.Set(dbIDAsset+req.EntityMrn, asset, 1)
   235  
   236  	return nil
   237  }