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

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package explorer
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  
    10  	"github.com/rs/zerolog/log"
    11  	"go.mondoo.com/cnquery"
    12  	"go.mondoo.com/cnquery/checksums"
    13  	llx "go.mondoo.com/cnquery/llx"
    14  	"go.mondoo.com/cnquery/mqlc"
    15  	"go.mondoo.com/cnquery/mrn"
    16  	"go.mondoo.com/cnquery/types"
    17  	"go.mondoo.com/cnquery/utils/multierr"
    18  	"google.golang.org/protobuf/proto"
    19  )
    20  
    21  // RefreshMRN computes a MRN from the UID or validates the existing MRN.
    22  // Both of these need to fit the ownerMRN. It also removes the UID.
    23  func (p *Property) RefreshMRN(ownerMRN string) error {
    24  	nu, err := RefreshMRN(ownerMRN, p.Mrn, MRN_RESOURCE_QUERY, p.Uid)
    25  	if err != nil {
    26  		log.Error().Err(err).Str("owner", ownerMRN).Str("uid", p.Uid).Msg("failed to refresh mrn")
    27  		return multierr.Wrap(err, "failed to refresh mrn for query "+p.Title)
    28  	}
    29  
    30  	p.Mrn = nu
    31  	p.Uid = ""
    32  	return nil
    33  }
    34  
    35  // Compile a given property and return the bundle.
    36  func (p *Property) Compile(props map[string]*llx.Primitive, schema llx.Schema) (*llx.CodeBundle, error) {
    37  	return mqlc.Compile(p.Mql, props, mqlc.NewConfig(schema, cnquery.DefaultFeatures))
    38  }
    39  
    40  // RefreshChecksumAndType by compiling the query and updating the Checksum field
    41  func (p *Property) RefreshChecksumAndType(schema llx.Schema) (*llx.CodeBundle, error) {
    42  	return p.refreshChecksumAndType(schema)
    43  }
    44  
    45  func (p *Property) refreshChecksumAndType(schema llx.Schema) (*llx.CodeBundle, error) {
    46  	bundle, err := p.Compile(nil, schema)
    47  	if err != nil {
    48  		return bundle, multierr.Wrap(err, "failed to compile property '"+p.Mql+"'")
    49  	}
    50  
    51  	if bundle.GetCodeV2().GetId() == "" {
    52  		return bundle, errors.New("failed to compile query: received empty result values")
    53  	}
    54  
    55  	// We think its ok to always use the new code id
    56  	p.CodeId = bundle.CodeV2.Id
    57  
    58  	// the compile step also dedents the code
    59  	p.Mql = bundle.Source
    60  
    61  	// TODO: record multiple entrypoints and types
    62  	// TODO(jaym): is it possible that the 2 could produce different types
    63  	if entrypoints := bundle.CodeV2.Entrypoints(); len(entrypoints) == 1 {
    64  		ep := entrypoints[0]
    65  		chunk := bundle.CodeV2.Chunk(ep)
    66  		typ := chunk.Type()
    67  		p.Type = string(typ)
    68  	} else {
    69  		p.Type = string(types.Any)
    70  	}
    71  
    72  	c := checksums.New.
    73  		Add(p.Mql).
    74  		Add(p.CodeId).
    75  		Add(p.Mrn).
    76  		Add(p.Type).
    77  		Add(p.Context).
    78  		Add(p.Title).Add("v2").
    79  		Add(p.Desc)
    80  
    81  	for i := range p.For {
    82  		f := p.For[i]
    83  		c = c.Add(f.Mrn)
    84  	}
    85  
    86  	p.Checksum = c.String()
    87  
    88  	return bundle, nil
    89  }
    90  
    91  func (p *Property) Merge(base *Property) {
    92  	if p.Mql == "" {
    93  		p.Mql = base.Mql
    94  	}
    95  	if p.Type == "" {
    96  		p.Type = base.Type
    97  	}
    98  	if p.Context == "" {
    99  		p.Context = base.Context
   100  	}
   101  	if p.Title == "" {
   102  		p.Title = base.Title
   103  	}
   104  	if p.Desc == "" {
   105  		p.Desc = base.Desc
   106  	}
   107  	if len(p.For) == 0 {
   108  		p.For = base.For
   109  	}
   110  }
   111  
   112  type PropsCache struct {
   113  	cache        map[string]*Property
   114  	uidOnlyProps map[string]*Property
   115  }
   116  
   117  func NewPropsCache() PropsCache {
   118  	return PropsCache{
   119  		cache:        map[string]*Property{},
   120  		uidOnlyProps: map[string]*Property{},
   121  	}
   122  }
   123  
   124  // Add properties, NOT overwriting existing ones (instead we add them as base)
   125  func (c PropsCache) Add(props ...*Property) {
   126  	for i := range props {
   127  		base := props[i]
   128  		if base.Uid != "" && base.Mrn == "" {
   129  			// keep track of properties that were specified by uid only.
   130  			// we will merge them in later if we find a matching mrn
   131  			c.uidOnlyProps[base.Uid] = base
   132  			continue
   133  		}
   134  		// All properties at this point should have a mrn
   135  		if base.Mrn != "" {
   136  			if existingProp, ok := c.cache[base.Mrn]; ok {
   137  				existingProp.Merge(base)
   138  			} else {
   139  				c.cache[base.Mrn] = base
   140  			}
   141  		}
   142  	}
   143  }
   144  
   145  // try to Get the mrn, will also return uid-based
   146  // properties if they exist first
   147  func (c PropsCache) Get(ctx context.Context, propMrn string) (*Property, string, error) {
   148  	if res, ok := c.cache[propMrn]; ok {
   149  		name, err := mrn.GetResource(propMrn, MRN_RESOURCE_QUERY)
   150  		if err != nil {
   151  			return nil, "", errors.New("failed to get property name")
   152  		}
   153  		if uidProp, ok := c.uidOnlyProps[name]; ok {
   154  			// We have a property that was specified by uid only. We need to merge it in
   155  			// to get the full property.
   156  			p := proto.Clone(uidProp).(*Property)
   157  			p.Merge(res)
   158  			return p, name, nil
   159  		} else {
   160  			// Everything was specified by mrn
   161  			return res, name, nil
   162  		}
   163  	}
   164  
   165  	// We currently don't grab properties from upstream. This requires further investigation.
   166  	return nil, "", errors.New("property " + propMrn + " not found")
   167  }