From a9492d6dcfc993279e8557991fd2287776ea550d Mon Sep 17 00:00:00 2001 From: Dominik Richter Date: Sun, 9 Feb 2025 11:39:18 -0800 Subject: [PATCH] =?UTF-8?q?=E2=AD=90=20print=20file=20context=20to=20CLI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Specialized printer for file context for resources that support it: ``` sshd.config.blocks: [ 0: sshd.config.matchBlock criteria="" in /pub/go/src/go.mondoo.com/cnquery/sshd_config:1-172 1: # # 2: # Ansible managed ... 171: RevokedKeys /etc/ssh/revoked_keys 172: 1: sshd.config.matchBlock criteria="Group sftp-users" in /pub/go/src/go.mondoo.com/cnquery/sshd_config:173-177 173: Match Group sftp-users 174: X11Forwarding no 175: PermitRootLogin no 176: AllowTCPForwarding yes 177: 2: sshd.config.matchBlock criteria="User myservice" in /pub/go/src/go.mondoo.com/cnquery/sshd_config:178-181 178: Match User myservice 179: 180: ] ``` --- cli/printer/mql.go | 89 +++++++++++++++++++++++++++++++++++- feature_string.go | 5 +- featureflags.go | 6 +++ llx/range.go | 6 ++- mqlc/mqlc.go | 56 +++++++++++++++++++---- mqlc/mqlc_test.go | 9 +++- providers/os/resources/os.lr | 2 +- 7 files changed, 156 insertions(+), 17 deletions(-) diff --git a/cli/printer/mql.go b/cli/printer/mql.go index 5d9ab3a1cb..194a710a45 100644 --- a/cli/printer/mql.go +++ b/cli/printer/mql.go @@ -515,6 +515,68 @@ func isCodeBlock(codeID string, bundle *llx.CodeBundle) bool { return ok } +func (print *Printer) resourceContext(data any, checksum string, bundle *llx.CodeBundle, indent string) (string, bool) { + m, ok := data.(map[string]any) + if !ok { + return "", false + } + + var path string + var rnge llx.Range + var content string + + for k, v := range m { + label, ok := bundle.Labels.Labels[k] + if !ok { + continue + } + vv, ok := v.(*llx.RawData) + if !ok { + continue + } + + switch label { + case "content": + if vv.Type == types.String { + content, _ = vv.Value.(string) + } + case "range": + if vv.Type == types.Range { + rnge, _ = vv.Value.(llx.Range) + } + case "path", "file.path": + if vv.Type == types.String { + path, _ = vv.Value.(string) + } + } + } + + var res strings.Builder + if path == "" { + if !rnge.IsEmpty() { + res.WriteString(":") + res.WriteString(rnge.String()) + } + } else { + res.WriteString(path) + if !rnge.IsEmpty() { + res.WriteByte(':') + res.WriteString(rnge.String()) + } + } + if content != "" { + res.WriteByte('\n') + res.WriteString(indent) + res.WriteString(indentBlock(content, indent)) + } + + r := res.String() + if r == "" { + return "", false + } + return r, true +} + func (print *Printer) autoExpand(blockRef uint64, data interface{}, bundle *llx.CodeBundle, indent string) string { var res strings.Builder @@ -526,9 +588,11 @@ func (print *Printer) autoExpand(blockRef uint64, data interface{}, bundle *llx. prefix := indent + " " res.WriteString("[\n") for i := range arr { - c := print.autoExpand(blockRef, arr[i], bundle, prefix) + num := strconv.Itoa(i) + autoIndent := prefix + strings.Repeat(" ", len(num)+2) + c := print.autoExpand(blockRef, arr[i], bundle, autoIndent) res.WriteString(prefix) - res.WriteString(strconv.Itoa(i)) + res.WriteString(num) res.WriteString(": ") res.WriteString(c) res.WriteByte('\n') @@ -565,6 +629,12 @@ func (print *Printer) autoExpand(blockRef uint64, data interface{}, bundle *llx. res.WriteString(name) + // hasContext := false + // resourceInfo := print.schema.Lookup(name) + // if resourceInfo != nil && resourceInfo.Context != "" { + // hasContext = true + // } + if block != nil { // important to process them in this order for _, ref := range block.Entrypoints { @@ -579,6 +649,21 @@ func (print *Printer) autoExpand(blockRef uint64, data interface{}, bundle *llx. } label := bundle.Labels.Labels[checksum] + + // Note (Dom): We don't have precise matching on the resource context + // just yet. Here we handle it via best-effort, i.e. if it's called + // context, a block, and it's part of the resource's auto-expand, then we + // treat it as a kind of resource context. + if label == "context" && types.Type(vv.Type) == types.Block { + if data, ok := print.resourceContext(vv.Value, checksum, bundle, indent); ok { + res.WriteByte('\n') + res.WriteString(indent) + res.WriteString("in ") + res.WriteString(data) + continue + } + } + val := print.Data(vv.Type, vv.Value, checksum, bundle, indent) res.WriteByte(' ') res.WriteString(label) diff --git a/feature_string.go b/feature_string.go index 0bffa79e9e..9048f3efc7 100644 --- a/feature_string.go +++ b/feature_string.go @@ -18,11 +18,12 @@ func _() { _ = x[FineGrainedAssets-8] _ = x[SerialNumberAsID-9] _ = x[ForceShellCompletion-10] + _ = x[ResourceContext-11] } -const _Feature_name = "MassQueriesPiperCodeBoolAssertionsK8sNodeDiscoveryMQLAssetContextErrorsAsFailuresStoreResourcesDataFineGrainedAssetsSerialNumberAsIDForceShellCompletion" +const _Feature_name = "MassQueriesPiperCodeBoolAssertionsK8sNodeDiscoveryMQLAssetContextErrorsAsFailuresStoreResourcesDataFineGrainedAssetsSerialNumberAsIDForceShellCompletionResourceContext" -var _Feature_index = [...]uint8{0, 11, 20, 34, 50, 65, 81, 99, 116, 132, 152} +var _Feature_index = [...]uint8{0, 11, 20, 34, 50, 65, 81, 99, 116, 132, 152, 167} func (i Feature) String() string { i -= 1 diff --git a/featureflags.go b/featureflags.go index 407c17c902..4b91295181 100644 --- a/featureflags.go +++ b/featureflags.go @@ -99,6 +99,11 @@ const ( // desc: Forces shell completion to be enabled (for windows) // start: v11.x ForceShellCompletion + + // ResourceContext feature flag + // desc: Automatically add resource context to results and prints it + // start: v11.x + ResourceContext ) // FeaturesValue is a map from feature name to feature flag @@ -112,6 +117,7 @@ var FeaturesValue = map[string]Feature{ FineGrainedAssets.String(): FineGrainedAssets, SerialNumberAsID.String(): SerialNumberAsID, ForceShellCompletion.String(): ForceShellCompletion, + ResourceContext.String(): ResourceContext, } // DefaultFeatures are a set of default flags that are active diff --git a/llx/range.go b/llx/range.go index 392c5af1fd..d3a44cee1c 100644 --- a/llx/range.go +++ b/llx/range.go @@ -171,6 +171,10 @@ func (r Range) ExtractAll() [][]uint32 { return res } +func (r Range) IsEmpty() bool { + return len(r) == 0 +} + func (r Range) String() string { var res strings.Builder @@ -221,7 +225,7 @@ type ExtractConfig struct { } var DefaultExtractConfig = ExtractConfig{ - MaxLines: 8, + MaxLines: 5, MaxColumns: 100, ShowLineNumbers: true, LineNumberPadding: 2, diff --git a/mqlc/mqlc.go b/mqlc/mqlc.go index c0f6f2280b..047749a548 100644 --- a/mqlc/mqlc.go +++ b/mqlc/mqlc.go @@ -90,6 +90,7 @@ type CompilerConfig struct { Schema resources.ResourcesSchema UseAssetContext bool Stats CompilerStats + Features cnquery.Features } func (c *CompilerConfig) EnableStats() { @@ -105,6 +106,7 @@ func NewConfig(schema resources.ResourcesSchema, features cnquery.Features) Comp Schema: schema, UseAssetContext: features.IsActive(cnquery.MQLAssetContext), Stats: compilerStatsNull{}, + Features: features, } } @@ -604,8 +606,8 @@ type blockRefs struct { } // evaluates the given expressions on a non-array resource (eg: no `[]int` nor `groups`) -// and creates a function, whose reference is returned -func (c *compiler) blockOnResource(expressions []*parser.Expression, typ types.Type, binding uint64, bindingName string) (blockRefs, error) { +// and creates a function, returning the entire block compiler after completion +func (c *compiler) blockcompileOnResource(expressions []*parser.Expression, typ types.Type, binding uint64, bindingName string) (*compiler, error) { blockCompiler := c.newBlockCompiler(nil) blockCompiler.block.AddArgumentPlaceholder(blockCompiler.Result.CodeV2, blockCompiler.blockRef, typ, blockCompiler.Result.CodeV2.Checksums[binding]) @@ -621,18 +623,25 @@ func (c *compiler) blockOnResource(expressions []*parser.Expression, typ types.T err := blockCompiler.compileExpressions(expressions) if err != nil { - return blockRefs{}, err + return &blockCompiler, err } blockCompiler.updateEntrypoints(false) blockCompiler.updateLabels() + return &blockCompiler, nil +} + +// evaluates the given expressions on a non-array resource (eg: no `[]int` nor `groups`) +// and creates a function, returning resource references after completion +func (c *compiler) blockOnResource(expressions []*parser.Expression, typ types.Type, binding uint64, bindingName string) (blockRefs, error) { + blockCompiler, err := c.blockcompileOnResource(expressions, typ, binding, bindingName) return blockRefs{ block: blockCompiler.blockRef, deps: blockCompiler.blockDeps, isStandalone: blockCompiler.standalone, binding: binding, - }, nil + }, err } // blockExpressions evaluates the given expressions as if called by a block and @@ -1906,7 +1915,10 @@ func (c *compiler) expandResourceFields(chunk *llx.Chunk, typ types.Type, ref ui } info := c.Schema.Lookup(typ.ResourceName()) - if info == nil || info.Defaults == "" { + if info == nil { + return false + } + if info.Defaults == "" { return false } @@ -1916,22 +1928,46 @@ func (c *compiler) expandResourceFields(chunk *llx.Chunk, typ types.Type, ref ui return false } - refs, err := c.blockOnResource(ast.Expressions, types.Resource(info.Name), ref, "_") + blockCompiler, err := c.blockcompileOnResource(ast.Expressions, types.Resource(info.Name), ref, "_") if err != nil { log.Error().Err(err).Msg("failed to compile default for " + info.Name) } - if len(refs.deps) != 0 { + if len(blockCompiler.blockDeps) != 0 { log.Warn().Msg("defaults somehow included external dependencies for resource " + info.Name) } - args := []*llx.Primitive{llx.FunctionPrimitive(refs.block)} + if c.CompilerConfig.Features.IsActive(cnquery.ResourceContext) && info.Context != "" { + // (Dom) Note: This is the very first expansion block implementation, so there are some + // serious limitations while we figure things out. + // 1. We can only expand a resource that has defaults defined. As soon as you add + // a resource without defaults that needs an expansion, please adjust the above code to + // provide a function block we can attach to AND don't exit early on defaults==empty. + // One way could be to just create a new defaults code and add context to it. + // 2. The `context` field may be part of defaults and the actual `@context`. Obviously we + // only ever need and want one. This needs fixing in LR. + + ctxType := types.Resource(info.Context) + blockCompiler.addChunk(&llx.Chunk{ + Call: llx.Chunk_FUNCTION, + Id: "context", + Function: &llx.Function{ + Type: string(ctxType), + Binding: blockCompiler.block.HeadRef(blockCompiler.blockRef), + Args: []*llx.Primitive{}, + }, + }) + blockCompiler.expandResourceFields(blockCompiler.block.LastChunk(), ctxType, blockCompiler.tailRef()) + blockCompiler.block.Entrypoints = append(blockCompiler.block.Entrypoints, blockCompiler.tailRef()) + } + + args := []*llx.Primitive{llx.FunctionPrimitive(blockCompiler.blockRef)} block := c.Result.CodeV2.Block(ref) block.AddChunk(c.Result.CodeV2, ref, &llx.Chunk{ Call: llx.Chunk_FUNCTION, Id: "{}", Function: &llx.Function{ Type: string(resultType), - Binding: refs.binding, + Binding: ref, Args: args, }, }) @@ -1939,7 +1975,7 @@ func (c *compiler) expandResourceFields(chunk *llx.Chunk, typ types.Type, ref ui block.ReplaceEntrypoint(ref, ep) ref = ep - c.Result.AutoExpand[c.Result.CodeV2.Checksums[ref]] = refs.block + c.Result.AutoExpand[c.Result.CodeV2.Checksums[ref]] = blockCompiler.blockRef return true } diff --git a/mqlc/mqlc_test.go b/mqlc/mqlc_test.go index 8416bd6344..813a36dfa3 100644 --- a/mqlc/mqlc_test.go +++ b/mqlc/mqlc_test.go @@ -21,7 +21,7 @@ import ( ) var ( - features = cnquery.Features{} + features = cnquery.Features{byte(cnquery.ResourceContext)} core_schema = testutils.MustLoadSchema(testutils.SchemaProvider{Provider: "core"}) os_schema = testutils.MustLoadSchema(testutils.SchemaProvider{Provider: "os"}) conf = mqlc.NewConfig( @@ -1111,6 +1111,13 @@ func TestCompiler_ResourceMapLength(t *testing.T) { func TestCompiler_ResourceExpansion(t *testing.T) { var cmd string + cmd = "sshd.config.blocks" + t.Run(cmd, func(t *testing.T) { + compileT(t, cmd, func(res *llx.CodeBundle) { + panic("STOP") + }) + }) + cmd = "mondoo" t.Run(cmd, func(t *testing.T) { compileT(t, cmd, func(res *llx.CodeBundle) { diff --git a/providers/os/resources/os.lr b/providers/os/resources/os.lr index 1878fb0f83..62bfa685b4 100644 --- a/providers/os/resources/os.lr +++ b/providers/os/resources/os.lr @@ -374,7 +374,7 @@ file @defaults("path size permissions.string") { } // File context is a range of lines/columns in a file -private file.context @defaults("file.path range") { +private file.context @defaults("file.path range content") { // File referenced by this file context file file // Range of content in the file