Skip to content

Commit

Permalink
⭐ print file context to CLI
Browse files Browse the repository at this point in the history
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:
]
```
  • Loading branch information
arlimus committed Feb 13, 2025
1 parent 7c015ee commit 5e5f3bd
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 17 deletions.
89 changes: 87 additions & 2 deletions cli/printer/mql.go
Original file line number Diff line number Diff line change
Expand Up @@ -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("<unknown>:")
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

Expand All @@ -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')
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions feature_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions featureflags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
6 changes: 5 additions & 1 deletion llx/range.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -221,7 +225,7 @@ type ExtractConfig struct {
}

var DefaultExtractConfig = ExtractConfig{
MaxLines: 8,
MaxLines: 5,
MaxColumns: 100,
ShowLineNumbers: true,
LineNumberPadding: 2,
Expand Down
56 changes: 46 additions & 10 deletions mqlc/mqlc.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ type CompilerConfig struct {
Schema resources.ResourcesSchema
UseAssetContext bool
Stats CompilerStats
Features cnquery.Features
}

func (c *CompilerConfig) EnableStats() {
Expand All @@ -105,6 +106,7 @@ func NewConfig(schema resources.ResourcesSchema, features cnquery.Features) Comp
Schema: schema,
UseAssetContext: features.IsActive(cnquery.MQLAssetContext),
Stats: compilerStatsNull{},
Features: features,
}
}

Expand Down Expand Up @@ -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])
Expand All @@ -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
Expand Down Expand Up @@ -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
}

Expand All @@ -1916,30 +1928,54 @@ 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,
},
})
ep := block.TailRef(ref)
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
}

Expand Down
9 changes: 8 additions & 1 deletion mqlc/mqlc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion providers/os/resources/os.lr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 5e5f3bd

Please sign in to comment.