go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/internal/datalakes/inmemory/query_hub.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  	"strings"
    10  
    11  	"go.mondoo.com/cnquery/explorer"
    12  	"go.mondoo.com/cnquery/mrn"
    13  )
    14  
    15  type wrapQuery struct {
    16  	*explorer.Mquery
    17  }
    18  
    19  type wrapQueryPack struct {
    20  	*explorer.QueryPack
    21  	filters []*explorer.Mquery
    22  }
    23  
    24  // QueryExists checks if the given MRN exists
    25  func (db *Db) QueryExists(ctx context.Context, mrn string) (bool, error) {
    26  	_, ok := db.cache.Get(dbIDQuery + mrn)
    27  	return ok, nil
    28  }
    29  
    30  // GetQuery retrieves a given query
    31  func (db *Db) GetQuery(ctx context.Context, mrn string) (*explorer.Mquery, error) {
    32  	q, ok := db.cache.Get(dbIDQuery + mrn)
    33  	if !ok {
    34  		return nil, errors.New("query '" + mrn + "' not found")
    35  	}
    36  	return (q.(wrapQuery)).Mquery, nil
    37  }
    38  
    39  // SetQuery stores a given query
    40  // Note: the query must be defined, it cannot be nil
    41  func (db *Db) SetQuery(ctx context.Context, mrn string, mquery *explorer.Mquery) error {
    42  	v := wrapQuery{mquery}
    43  	ok := db.cache.Set(dbIDQuery+mrn, v, 1)
    44  	if !ok {
    45  		return errors.New("failed to save query '" + mrn + "' to cache")
    46  	}
    47  	return nil
    48  }
    49  
    50  // SetQueryPack stores a given pack in the datalake
    51  func (db *Db) SetQueryPack(ctx context.Context, obj *explorer.QueryPack, filters []*explorer.Mquery) error {
    52  	_, err := db.setQueryPack(ctx, obj, filters)
    53  	return err
    54  }
    55  
    56  // GetQueryPack retrieves the pack
    57  func (db *Db) GetQueryPack(ctx context.Context, mrn string) (*explorer.QueryPack, error) {
    58  	q, ok := db.cache.Get(dbIDQueryPack + mrn)
    59  	if !ok {
    60  		return nil, errors.New("query pack '" + mrn + "' not found")
    61  	}
    62  	return (q.(wrapQueryPack)).QueryPack, nil
    63  }
    64  
    65  // GetQueryPackFilters retrieves the query pack filters
    66  func (db *Db) GetQueryPackFilters(ctx context.Context, in string) ([]*explorer.Mquery, error) {
    67  	// if it's an asset
    68  	if _, err := mrn.GetResource(in, explorer.MRN_RESOURCE_ASSET); err != nil {
    69  		return nil, errors.New("can only retrieve query pack filters for assets")
    70  	}
    71  
    72  	x, ok := db.cache.Get(dbIDAsset + in)
    73  	if !ok {
    74  		return nil, errors.New("failed to find asset " + in)
    75  	}
    76  	asset := x.(wrapAsset)
    77  
    78  	return asset.Bundle.Filters(), nil
    79  }
    80  
    81  func (db *Db) setQueryPack(ctx context.Context, in *explorer.QueryPack, filters []*explorer.Mquery) (wrapQueryPack, error) {
    82  	var err error
    83  
    84  	for i := range filters {
    85  		filter := filters[i]
    86  		if err = db.SetQuery(ctx, filter.Mrn, filter); err != nil {
    87  			return wrapQueryPack{}, err
    88  		}
    89  	}
    90  
    91  	obj := wrapQueryPack{
    92  		QueryPack: in,
    93  		filters:   filters,
    94  	}
    95  
    96  	ok := db.cache.Set(dbIDQueryPack+obj.Mrn, obj, 2)
    97  	if !ok {
    98  		return wrapQueryPack{}, errors.New("failed to save query pack '" + in.Mrn + "' to cache")
    99  	}
   100  
   101  	list, err := db.listQueryPacks()
   102  	if err != nil {
   103  		return wrapQueryPack{}, err
   104  	}
   105  
   106  	list[in.Mrn] = struct{}{}
   107  	ok = db.cache.Set(dbIDListQueryPacks, list, 0)
   108  	if !ok {
   109  		return wrapQueryPack{}, errors.New("failed to update query pack list cache")
   110  	}
   111  
   112  	return obj, nil
   113  }
   114  
   115  // DeleteQueryPack removes a given mrn
   116  // Note: the MRN has to be valid
   117  func (db *Db) DeleteQueryPack(ctx context.Context, mrn string) error {
   118  	_, ok := db.cache.Get(dbIDQueryPack + mrn)
   119  	if !ok {
   120  		return nil
   121  	}
   122  
   123  	errors := strings.Builder{}
   124  
   125  	// list update
   126  	list, err := db.listQueryPacks()
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	delete(list, mrn)
   132  	ok = db.cache.Set(dbIDListQueryPacks, list, 0)
   133  	if !ok {
   134  		errors.WriteString("failed to update query packs list cache")
   135  	}
   136  
   137  	db.cache.Del(dbIDQueryPack + mrn)
   138  
   139  	return nil
   140  }
   141  
   142  // ListQueryPacks for a given owner
   143  // Note: Owner MRN is required
   144  func (db *Db) ListQueryPacks(ctx context.Context, ownerMrn string, name string) ([]*explorer.QueryPack, error) {
   145  	mrns, err := db.listQueryPacks()
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	res := []*explorer.QueryPack{}
   151  	for k := range mrns {
   152  		obj, err := db.GetQueryPack(ctx, k)
   153  		if err != nil {
   154  			return nil, err
   155  		}
   156  
   157  		if obj.OwnerMrn != ownerMrn {
   158  			continue
   159  		}
   160  
   161  		res = append(res, obj)
   162  	}
   163  
   164  	return res, nil
   165  }
   166  
   167  func (db *Db) listQueryPacks() (map[string]struct{}, error) {
   168  	x, ok := db.cache.Get(dbIDListQueryPacks)
   169  	if ok {
   170  		return x.(map[string]struct{}), nil
   171  	}
   172  
   173  	nu := map[string]struct{}{}
   174  	ok = db.cache.Set(dbIDListQueryPacks, nu, 0)
   175  	if !ok {
   176  		return nil, errors.New("failed to initialize query packs list cache")
   177  	}
   178  	return nu, nil
   179  }
   180  
   181  // GetBundle retrieves and if necessary updates the pack. Used for assets,
   182  // which have multiple query packs associated with them.
   183  func (db *Db) GetBundle(ctx context.Context, mrn string) (*explorer.Bundle, error) {
   184  	x, ok := db.cache.Get(dbIDAsset + mrn)
   185  	if !ok {
   186  		return nil, errors.New("failed to find asset " + mrn)
   187  	}
   188  
   189  	res := x.(wrapAsset).Bundle
   190  
   191  	// FIXME: we have to compensate for missing query variants in this function
   192  	// right now, because they don't get uploaded as full bundles right now.
   193  	// Overhaul this by uploading proper bundles, that include query variants.
   194  	// vv
   195  	skipMrn := map[string]struct{}{}
   196  	for i := range res.Packs {
   197  		pack := res.Packs[i]
   198  		for j := range pack.Groups {
   199  			group := pack.Groups[j]
   200  			for k := range group.Queries {
   201  				query := group.Queries[k]
   202  				if len(query.Variants) == 0 {
   203  					continue
   204  				}
   205  
   206  				// 🍝 darn spaghetti ... see above fixme
   207  				for l := range query.Variants {
   208  					mrn := query.Variants[l].Mrn
   209  					if _, ok := skipMrn[mrn]; ok {
   210  						continue
   211  					}
   212  
   213  					skipMrn[mrn] = struct{}{}
   214  					q, _ := db.GetQuery(ctx, query.Variants[l].Mrn)
   215  					if q != nil {
   216  						res.Queries = append(res.Queries, q)
   217  					}
   218  				}
   219  			}
   220  		}
   221  	}
   222  	// ^^
   223  
   224  	return res, nil
   225  }
   226  
   227  // MutateBundle runs the given mutation on a bundle, typically an asset.
   228  // If it cannot find the owner, it will create it.
   229  func (db *Db) MutateBundle(ctx context.Context, mutation *explorer.BundleMutationDelta, createIfMissing bool) (*explorer.Bundle, error) {
   230  	x, ok := db.cache.Get(dbIDAsset + mutation.OwnerMrn)
   231  	if !ok {
   232  		if !createIfMissing {
   233  			return nil, errors.New("failed to find asset " + mutation.OwnerMrn)
   234  		}
   235  
   236  		var err error
   237  		x, _, err = db.ensureAssetObject(ctx, mutation.OwnerMrn)
   238  		if err != nil {
   239  			return nil, err
   240  		}
   241  	}
   242  	asset := x.(wrapAsset)
   243  
   244  	if asset.Bundle == nil {
   245  		return nil, errors.New("found an asset without a bundle configured in the DB")
   246  	}
   247  
   248  	existing := map[string]*explorer.QueryPack{}
   249  	for i := range asset.Bundle.Packs {
   250  		cur := asset.Bundle.Packs[i]
   251  		existing[cur.Mrn] = cur
   252  	}
   253  
   254  	for _, delta := range mutation.Deltas {
   255  		switch delta.Action {
   256  		case explorer.AssignmentDelta_ADD:
   257  			pack, err := db.GetQueryPack(ctx, delta.Mrn)
   258  			if err != nil {
   259  				return nil, errors.New("failed to find query pack for assignment: " + delta.Mrn)
   260  			}
   261  
   262  			existing[delta.Mrn] = pack
   263  
   264  		case explorer.AssignmentDelta_DELETE:
   265  			delete(existing, delta.Mrn)
   266  
   267  		default:
   268  			return nil, errors.New("cannot mutate bundle, the action is unknown")
   269  		}
   270  	}
   271  
   272  	res := make([]*explorer.QueryPack, len(existing))
   273  	i := 0
   274  	for _, qp := range existing {
   275  		res[i] = qp
   276  		i++
   277  	}
   278  
   279  	asset.Bundle.Packs = res
   280  	db.cache.Set(dbIDAsset+mutation.OwnerMrn, asset, 1)
   281  
   282  	return asset.Bundle, nil
   283  }