Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⭐️ inventory template support #3940

Merged
merged 1 commit into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions apps/cnquery/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,16 +214,12 @@ func getCobraScanConfig(cmd *cobra.Command, runtime *providers.Runtime, cliRes *
config.DisplayUsedConfig()

props := viper.GetStringMapString("props")
annotations := viper.GetStringMapString("annotation")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the binding was wrong, so this never worked before


// merge the config and the user-provided annotations with the latter having precedence
optAnnotations := opts.Annotations
if optAnnotations == nil {
optAnnotations = map[string]string{}
}
for k, v := range annotations {
optAnnotations[k] = v
}

assetName := viper.GetString("asset-name")
if assetName != "" && cliRes.Asset != nil {
Expand Down
9 changes: 6 additions & 3 deletions apps/cnquery/cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ func init() {
_ = scanCmd.Flags().String("platform-id", "", "Select a specific target asset by providing its platform ID.")

_ = scanCmd.Flags().String("inventory-file", "", "Set the path to the inventory file.")
_ = scanCmd.Flags().String("inventory-template", "", "Set the path to the inventory template.")
_ = scanCmd.Flags().MarkHidden("inventory-template")

_ = scanCmd.Flags().Bool("inventory-format-ansible", false, "Set the inventory format to Ansible.")
// "inventory-ansible" is deprecated, use "inventory-format-ansible" instead
Expand Down Expand Up @@ -89,6 +91,7 @@ To manually configure a query pack, use this:

_ = viper.BindPFlag("platform-id", cmd.Flags().Lookup("platform-id"))
_ = viper.BindPFlag("inventory-file", cmd.Flags().Lookup("inventory-file"))
_ = viper.BindPFlag("inventory-template", cmd.Flags().Lookup("inventory-template"))
_ = viper.BindPFlag("inventory-ansible", cmd.Flags().Lookup("inventory-ansible"))
_ = viper.BindPFlag("inventory-domainlist", cmd.Flags().Lookup("inventory-domainlist"))
_ = viper.BindPFlag("querypack-bundle", cmd.Flags().Lookup("querypack-bundle"))
Expand All @@ -97,15 +100,15 @@ To manually configure a query pack, use this:
_ = viper.BindPFlag("trace-id", cmd.Flags().Lookup("trace-id"))
_ = viper.BindPFlag("category", cmd.Flags().Lookup("category"))

_ = viper.BindPFlag("annotations", cmd.Flags().Lookup("annotations"))
_ = viper.BindPFlag("props", cmd.Flags().Lookup("props"))

// for all assets
_ = viper.BindPFlag("incognito", cmd.Flags().Lookup("incognito"))
_ = viper.BindPFlag("insecure", cmd.Flags().Lookup("insecure"))
_ = viper.BindPFlag("querypacks", cmd.Flags().Lookup("querypack"))
_ = viper.BindPFlag("sudo.active", cmd.Flags().Lookup("sudo"))
_ = viper.BindPFlag("record", cmd.Flags().Lookup("record"))
// NOTE: we may "annotation" to "annotations" to align it with the internal config struct
_ = viper.BindPFlag("annotations", cmd.Flags().Lookup("annotation"))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

required since the options use "annotations"

_ = viper.BindPFlag("props", cmd.Flags().Lookup("props"))

_ = viper.BindPFlag("output", cmd.Flags().Lookup("output"))
_ = viper.BindPFlag("json", cmd.Flags().Lookup("json"))
Expand Down
61 changes: 49 additions & 12 deletions cli/inventoryloader/inventory.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io"
"os"
"runtime"
"text/template"

"github.com/cockroachdb/errors"
"github.com/rs/zerolog/log"
Expand Down Expand Up @@ -52,32 +53,68 @@ func loadDataPipe() ([]byte, bool) {
return nil, false
}

func renderTemplate(data []byte) ([]byte, error) {
type InventoryTemplateVariables struct{}
conf := InventoryTemplateVariables{}

// allows users to access environment variables in templates
funcMap := template.FuncMap{
"getenv": func(varName string) string { return os.Getenv(varName) },
}

tmpl, err := template.New("inventory-template").Funcs(funcMap).Parse(string(data))
if err != nil {
return nil, errors.Wrap(err, "could not parse inventory template")
}
buf := &bytes.Buffer{}
err = tmpl.Execute(buf, conf)
return buf.Bytes(), err
}

// Parse uses the viper flags for `--inventory-file` to load the inventory
// - if `--inventory-file` is set to "-" it will read from stdin
// - if `--inventory-template` is set it injects environment variables into the inventory before execution
func Parse() (*inventory.Inventory, error) {
var data []byte
var err error

// a pre-rendered inventory file has always precedence over the inventory template
inventoryFilePath := viper.GetString("inventory-file")
inventoryTemplate := viper.GetString("inventory-template")

// check in an inventory file was provided
if inventoryFilePath == "" {
if inventoryFilePath == "" && inventoryTemplate == "" {
return inventory.New(), nil
}

var data []byte
var err error

// for we just read the data from the input file
if inventoryFilePath != "-" {
log.Info().Str("inventory-file", inventoryFilePath).Msg("load inventory")
data, err = os.ReadFile(inventoryFilePath)
if err != nil {
return nil, err
}
} else {
if inventoryFilePath == "-" {
// read data from stdin
log.Info().Msg("load inventory from piped input")
var ok bool
data, ok = loadDataPipe()
if !ok {
return nil, errors.New("could not read inventory from piped input")
}
} else if inventoryFilePath != "" {
// read the data from the input file
log.Info().Str("inventory-file", inventoryFilePath).Msg("load inventory")
data, err = os.ReadFile(inventoryFilePath)
if err != nil {
return nil, err
}
} else if inventoryTemplate != "" {
// render inventory template first, then continue with generated inventory file
log.Info().Str("inventory-template", inventoryTemplate).Msg("load inventory template")
templateData, err := os.ReadFile(inventoryTemplate)
if err != nil {
return nil, err
}
data, err = renderTemplate(templateData)
if err != nil {
return nil, err
}
} else {
return nil, errors.New("no inventory file or template provided")
}

// force detection
Expand Down
35 changes: 35 additions & 0 deletions cli/inventoryloader/inventory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Mondoo, Inc.
// SPDX-License-Identifier: BUSL-1.1

package inventoryloader

import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"os"
"testing"
)

func TestInventoryTemplate(t *testing.T) {
os.Setenv("MY_APPLICATION", "app1")
os.Setenv("OPERATING_ENVIRONMENT", "dev")
inventoryTemplate := `
spec:
assets:
- name: Scenario TF {{ getenv "OPERATING_ENVIRONMENT" }}
connections:
- type: terraform-hcl
options:
ignore-dot-terraform: "false"
path: okta.tf
annotations:
Application: {{ getenv "MY_APPLICATION" }}
OperatingEnv: {{ getenv "OPERATING_ENVIRONMENT" }}
`

data, err := renderTemplate([]byte(inventoryTemplate))
require.NoError(t, err)

assert.Contains(t, string(data), "Application: app1")
assert.Contains(t, string(data), "Scenario TF dev")
}
Loading