diff --git a/cli/printer/mql.go b/cli/printer/mql.go index 5d9ab3a1c..194a710a4 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 0bffa79e9..9048f3efc 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 407c17c90..4b9129518 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 392c5af1f..d3a44cee1 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 c0f6f2280..047749a54 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 8416bd634..3e5e4fd86 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,46 @@ 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) { + require.Len(t, res.CodeV2.Blocks, 3) + + require.Len(t, res.CodeV2.Blocks[0].Chunks, 3) + require.Equal(t, "{}", res.CodeV2.Blocks[0].Chunks[2].Id) + + require.Len(t, res.CodeV2.Blocks[1].Chunks, 4) + assertFunction(t, "context", &llx.Function{ + Type: string(types.Resource("file.context")), + Binding: (2 << 32) | 1, + Args: []*llx.Primitive{}, + }, res.CodeV2.Blocks[1].Chunks[2]) + assertFunction(t, "{}", &llx.Function{ + Type: string(types.Block), + Binding: (2 << 32) | 3, + Args: []*llx.Primitive{llx.FunctionPrimitive(3 << 32)}, + }, res.CodeV2.Blocks[1].Chunks[3]) + + require.Len(t, res.CodeV2.Blocks[2].Chunks, 5) + assertFunction(t, "file", &llx.Function{ + Type: string(types.Resource("file")), + Binding: (3 << 32) | 1, + }, res.CodeV2.Blocks[2].Chunks[1]) + assertFunction(t, "path", &llx.Function{ + Type: string(types.String), + Binding: (3 << 32) | 2, + }, res.CodeV2.Blocks[2].Chunks[2]) + assertFunction(t, "range", &llx.Function{ + Type: string(types.Range), + Binding: (3 << 32) | 1, + }, res.CodeV2.Blocks[2].Chunks[3]) + assertFunction(t, "content", &llx.Function{ + Type: string(types.String), + Binding: (3 << 32) | 1, + }, res.CodeV2.Blocks[2].Chunks[4]) + }) + }) + 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 1878fb0f8..62bfa685b 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