From 2ade65be6e03453b48a9a2cc517f27f49aad2173 Mon Sep 17 00:00:00 2001 From: Dominik Richter Date: Tue, 3 Oct 2023 13:21:30 -0700 Subject: [PATCH] =?UTF-8?q?=E2=AD=90=20terraform.resources(=20filter=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support both string and regex filters for easily accessing all the resources that you need via their label. Signed-off-by: Dominik Richter --- mqlc/mqlc.go | 2 +- providers-sdk/v1/lr/go.go | 18 +++ providers-sdk/v1/plugin/runtime.go | 7 ++ providers/terraform/resources/hcl.go | 67 ++++++++++- providers/terraform/resources/terraform.lr | 8 +- providers/terraform/resources/terraform.lr.go | 104 ++++++++++++++---- .../resources/terraform.lr.manifest.yaml | 8 ++ 7 files changed, 187 insertions(+), 27 deletions(-) diff --git a/mqlc/mqlc.go b/mqlc/mqlc.go index 2730af14cc..b4c2ed3057 100644 --- a/mqlc/mqlc.go +++ b/mqlc/mqlc.go @@ -759,7 +759,7 @@ func (c *compiler) unnamedArgs(callerLabel string, init *resources.Init, args [] // This needs massive improvements to dynamically cast them in LLX. // For a full description see: https://gitlab.com/mondoolabs/mondoo/-/issues/241 // This is ONLY a temporary workaround which works in a few cases: - if vType == types.Dict && expectedType == types.String { + if (vType == types.Dict && expectedType == types.String) || expectedType == types.Any { // we are good, LLX will handle it } else { return nil, errors.New("Incorrect type on argument " + strconv.Itoa(idx) + diff --git a/providers-sdk/v1/lr/go.go b/providers-sdk/v1/lr/go.go index 4f505187c1..54f9557291 100644 --- a/providers-sdk/v1/lr/go.go +++ b/providers-sdk/v1/lr/go.go @@ -271,6 +271,18 @@ func (b *goBuilder) goSetData(r []*Resource) { continue } + if field.BasicField.Type.isAny() { + x := fmt.Sprintf(`"%s.%s": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*%s).%s, ok = plugin.RawDataToAnyTValue(v, v.Error) + return + },`, + resource.ID, field.BasicField.ID, + resource.structName(b), field.BasicField.methodname(), + ) + fields = append(fields, x) + continue + } + x := fmt.Sprintf(`"%s.%s": func(r plugin.Resource, v *llx.RawData) (ok bool) { r.(*%s).%s, ok = plugin.RawToTValue[%s](v.Value, v.Error) return @@ -625,6 +637,8 @@ func (t *SimpleType) typeItems(ast *LR) types.Type { return types.Time case "dict": return types.Dict + case "any": + return types.Any default: return resourceType(t.Type, ast) } @@ -706,6 +720,10 @@ func (t *SimpleType) mondooTypeItems(b *goBuilder) string { // panic("Cannot convert type '" + t.Type + "' to mondoo type") } +func (t *Type) isAny() bool { + return t != nil && t.SimpleType != nil && t.SimpleType.Type == "any" +} + func (t *Type) containsResource(b *goBuilder) bool { if t.ListType != nil { return t.ListType.Type.containsResource(b) diff --git a/providers-sdk/v1/plugin/runtime.go b/providers-sdk/v1/plugin/runtime.go index 702011934e..19de8601ea 100644 --- a/providers-sdk/v1/plugin/runtime.go +++ b/providers-sdk/v1/plugin/runtime.go @@ -209,6 +209,13 @@ func RawToTValue[T any](value interface{}, err error) (TValue[T], bool) { return TValue[T]{Data: tv, State: StateIsSet, Error: err}, true } +func RawDataToAnyTValue(value *llx.RawData, err error) (TValue[interface{}], bool) { + if value == nil { + return TValue[interface{}]{State: StateIsNull | StateIsSet, Error: err}, true + } + return TValue[interface{}]{Data: value, State: StateIsSet, Error: err}, true +} + type State byte type notReady struct{} diff --git a/providers/terraform/resources/hcl.go b/providers/terraform/resources/hcl.go index d8f7f0fb88..de71f4abb1 100644 --- a/providers/terraform/resources/hcl.go +++ b/providers/terraform/resources/hcl.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "regexp" "strconv" "strings" @@ -130,10 +131,6 @@ func (t *mqlTerraform) datasources() ([]interface{}, error) { return filterBlockByType(t.MqlRuntime, "data") } -func (t *mqlTerraform) resources() ([]interface{}, error) { - return filterBlockByType(t.MqlRuntime, "resource") -} - func (t *mqlTerraform) variables() ([]interface{}, error) { return filterBlockByType(t.MqlRuntime, "variable") } @@ -170,6 +167,68 @@ func extractHclCodeSnippet(file *hcl.File, fileRange hcl.Range) string { return sb.String() } +func (c *mqlTerraformResources) id() (string, error) { + filter := c.Filter.Data + if filter == nil { + return "", nil + } + return filter.(*llx.RawData).String(), nil +} + +func (c *mqlTerraformResources) list() ([]interface{}, error) { + blocks, err := filterBlockByType(c.MqlRuntime, "resource") + if err != nil { + return nil, err + } + + filter := c.Filter.Data + if filter == nil { + return blocks, err + } + + var isOK func(s string) bool + raw, ok := filter.(*llx.RawData) + if !ok { + return blocks, errors.New("can't use filters, incompatible internal typ") + } + + switch raw.Type { + case types.String, types.Dict: + expected, ok := raw.Value.(string) + if !ok { + return blocks, errors.New("can't use filters, it should be a simple string") + } + isOK = func(s string) bool { + return s == expected + } + case types.Regex: + expected := raw.Value.(string) + re, err := regexp.Compile(expected) + if err != nil { + return blocks, errors.New("failed to compile regex for filter: " + expected) + } + isOK = func(s string) bool { + return re.MatchString(s) + } + default: + return blocks, err + } + + var res []interface{} + for i := range blocks { + block := blocks[i].(*mqlTerraformBlock) + labels := block.Labels.Data + if len(labels) == 0 { + continue + } + if isOK(labels[0].(string)) { + res = append(res, block) + } + } + + return res, nil +} + func newMqlHclBlock(runtime *plugin.Runtime, block *hcl.Block, file *hcl.File) (plugin.Resource, error) { start, end, err := newFilePosRange(runtime, block.TypeRange) if err != nil { diff --git a/providers/terraform/resources/terraform.lr b/providers/terraform/resources/terraform.lr index 9cbe19e3b0..2b7ecf7c70 100644 --- a/providers/terraform/resources/terraform.lr +++ b/providers/terraform/resources/terraform.lr @@ -18,14 +18,18 @@ terraform { providers() []terraform.block // Data sources blocks datasources() []terraform.block - // All blocks with type resource - resources() []terraform.block // Variable blocks variables() []terraform.block // Output blocks outputs() []terraform.block } +terraform.resources { + []terraform.block + init(filter any) + filter any +} + // Terraform Configuration File represents a .tf or .tf.json file terraform.file { // tf or tf.json file diff --git a/providers/terraform/resources/terraform.lr.go b/providers/terraform/resources/terraform.lr.go index 3f8e811d8b..2c50e510e8 100644 --- a/providers/terraform/resources/terraform.lr.go +++ b/providers/terraform/resources/terraform.lr.go @@ -21,6 +21,10 @@ func init() { // to override args, implement: initTerraform(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) Create: createTerraform, }, + "terraform.resources": { + // to override args, implement: initTerraformResources(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) + Create: createTerraformResources, + }, "terraform.file": { // to override args, implement: initTerraformFile(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) Create: createTerraformFile, @@ -159,15 +163,18 @@ var getDataFields = map[string]func(r plugin.Resource) *plugin.DataRes{ "terraform.datasources": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlTerraform).GetDatasources()).ToDataRes(types.Array(types.Resource("terraform.block"))) }, - "terraform.resources": func(r plugin.Resource) *plugin.DataRes { - return (r.(*mqlTerraform).GetResources()).ToDataRes(types.Array(types.Resource("terraform.block"))) - }, "terraform.variables": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlTerraform).GetVariables()).ToDataRes(types.Array(types.Resource("terraform.block"))) }, "terraform.outputs": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlTerraform).GetOutputs()).ToDataRes(types.Array(types.Resource("terraform.block"))) }, + "terraform.resources.filter": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlTerraformResources).GetFilter()).ToDataRes(types.Resource("any")) + }, + "terraform.resources.list": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlTerraformResources).GetList()).ToDataRes(types.Array(types.Resource("terraform.block"))) + }, "terraform.file.path": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlTerraformFile).GetPath()).ToDataRes(types.String) }, @@ -415,10 +422,6 @@ var setDataFields = map[string]func(r plugin.Resource, v *llx.RawData) bool { r.(*mqlTerraform).Datasources, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) return }, - "terraform.resources": func(r plugin.Resource, v *llx.RawData) (ok bool) { - r.(*mqlTerraform).Resources, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) - return - }, "terraform.variables": func(r plugin.Resource, v *llx.RawData) (ok bool) { r.(*mqlTerraform).Variables, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) return @@ -427,6 +430,18 @@ var setDataFields = map[string]func(r plugin.Resource, v *llx.RawData) bool { r.(*mqlTerraform).Outputs, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) return }, + "terraform.resources.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlTerraformResources).__id, ok = v.Value.(string) + return + }, + "terraform.resources.filter": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlTerraformResources).Filter, ok = plugin.RawDataToAnyTValue(v, v.Error) + return + }, + "terraform.resources.list": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlTerraformResources).List, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) + return + }, "terraform.file.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { r.(*mqlTerraformFile).__id, ok = v.Value.(string) return @@ -790,7 +805,6 @@ type mqlTerraform struct { Blocks plugin.TValue[[]interface{}] Providers plugin.TValue[[]interface{}] Datasources plugin.TValue[[]interface{}] - Resources plugin.TValue[[]interface{}] Variables plugin.TValue[[]interface{}] Outputs plugin.TValue[[]interface{}] } @@ -918,10 +932,10 @@ func (c *mqlTerraform) GetDatasources() *plugin.TValue[[]interface{}] { }) } -func (c *mqlTerraform) GetResources() *plugin.TValue[[]interface{}] { - return plugin.GetOrCompute[[]interface{}](&c.Resources, func() ([]interface{}, error) { +func (c *mqlTerraform) GetVariables() *plugin.TValue[[]interface{}] { + return plugin.GetOrCompute[[]interface{}](&c.Variables, func() ([]interface{}, error) { if c.MqlRuntime.HasRecording { - d, err := c.MqlRuntime.FieldResourceFromRecording("terraform", c.__id, "resources") + d, err := c.MqlRuntime.FieldResourceFromRecording("terraform", c.__id, "variables") if err != nil { return nil, err } @@ -930,14 +944,14 @@ func (c *mqlTerraform) GetResources() *plugin.TValue[[]interface{}] { } } - return c.resources() + return c.variables() }) } -func (c *mqlTerraform) GetVariables() *plugin.TValue[[]interface{}] { - return plugin.GetOrCompute[[]interface{}](&c.Variables, func() ([]interface{}, error) { +func (c *mqlTerraform) GetOutputs() *plugin.TValue[[]interface{}] { + return plugin.GetOrCompute[[]interface{}](&c.Outputs, func() ([]interface{}, error) { if c.MqlRuntime.HasRecording { - d, err := c.MqlRuntime.FieldResourceFromRecording("terraform", c.__id, "variables") + d, err := c.MqlRuntime.FieldResourceFromRecording("terraform", c.__id, "outputs") if err != nil { return nil, err } @@ -946,14 +960,64 @@ func (c *mqlTerraform) GetVariables() *plugin.TValue[[]interface{}] { } } - return c.variables() + return c.outputs() }) } -func (c *mqlTerraform) GetOutputs() *plugin.TValue[[]interface{}] { - return plugin.GetOrCompute[[]interface{}](&c.Outputs, func() ([]interface{}, error) { +// mqlTerraformResources for the terraform.resources resource +type mqlTerraformResources struct { + MqlRuntime *plugin.Runtime + __id string + // optional: if you define mqlTerraformResourcesInternal it will be used here + Filter plugin.TValue[interface{}] + List plugin.TValue[[]interface{}] +} + +// createTerraformResources creates a new instance of this resource +func createTerraformResources(runtime *plugin.Runtime, args map[string]*llx.RawData) (plugin.Resource, error) { + res := &mqlTerraformResources{ + MqlRuntime: runtime, + } + + err := SetAllData(res, args) + if err != nil { + return res, err + } + + if res.__id == "" { + res.__id, err = res.id() + if err != nil { + return nil, err + } + } + + if runtime.HasRecording { + args, err = runtime.ResourceFromRecording("terraform.resources", res.__id) + if err != nil || args == nil { + return res, err + } + return res, SetAllData(res, args) + } + + return res, nil +} + +func (c *mqlTerraformResources) MqlName() string { + return "terraform.resources" +} + +func (c *mqlTerraformResources) MqlID() string { + return c.__id +} + +func (c *mqlTerraformResources) GetFilter() *plugin.TValue[interface{}] { + return &c.Filter +} + +func (c *mqlTerraformResources) GetList() *plugin.TValue[[]interface{}] { + return plugin.GetOrCompute[[]interface{}](&c.List, func() ([]interface{}, error) { if c.MqlRuntime.HasRecording { - d, err := c.MqlRuntime.FieldResourceFromRecording("terraform", c.__id, "outputs") + d, err := c.MqlRuntime.FieldResourceFromRecording("terraform.resources", c.__id, "list") if err != nil { return nil, err } @@ -962,7 +1026,7 @@ func (c *mqlTerraform) GetOutputs() *plugin.TValue[[]interface{}] { } } - return c.outputs() + return c.list() }) } diff --git a/providers/terraform/resources/terraform.lr.manifest.yaml b/providers/terraform/resources/terraform.lr.manifest.yaml index e4b4d33f32..d80b108193 100755 --- a/providers/terraform/resources/terraform.lr.manifest.yaml +++ b/providers/terraform/resources/terraform.lr.manifest.yaml @@ -130,6 +130,14 @@ resources: platform: name: - terraform + terraform.resources: + fields: + filter: {} + list: {} + min_mondoo_version: 9.0.0 + platform: + name: + - terraform terraform.settings: fields: backend: