Skip to content

Feat: Make cli argument vars to be slice of string #1024

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
7 changes: 3 additions & 4 deletions cmd/goss/goss.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,9 @@ func newRuntimeConfigFromCLI(c *cli.Context) *util.Config {
Spec: c.GlobalString("gossfile"),
Timeout: c.Duration("timeout"),
Username: c.String("username"),
Vars: c.GlobalString("vars"),
VarsFiles: c.GlobalStringSlice("vars"),
VarsInline: c.GlobalString("vars-inline"),
}

if c.Bool("no-color") {
util.WithNoColor()(cfg)
}
Expand Down Expand Up @@ -83,9 +82,9 @@ func main() {
Usage: "Goss file to read from / write to",
EnvVar: "GOSS_FILE",
},
cli.StringFlag{
cli.StringSliceFlag{
Name: "vars",
Usage: "json/yaml file containing variables for template",
Usage: "json/yaml file containing variables for template. Can be specified multiple times. When specified multiple times it will load variables from all files. Non-empty map keys that overlap will be overriden by subsequent file that defines them.",
EnvVar: "GOSS_VARS",
},
cli.StringFlag{
Expand Down
4 changes: 2 additions & 2 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ COMMANDS:

GLOBAL OPTIONS:
--gossfile value, -g value Goss file to read from / write to (default: "./goss.yaml") [$GOSS_FILE]
--vars value json/yaml file containing variables for template [$GOSS_VARS]
--vars value json/yaml file containing variables for template. Can be specified multiple times. When specified multiple times it will load variables from all files. Non-empty map keys that overlap will be overriden by subsequent file that defines them. [$GOSS_VARS]
--vars-inline value json/yaml string containing variables for template (overwrites vars) [$GOSS_VARS_INLINE]
--package value Package type to use [rpm, deb, apk, pacman]
--help, -h show help
Expand All @@ -42,7 +42,7 @@ GLOBAL OPTIONS:
* `json`

`--vars <varfile>`
: The file to read variables from when rendering gossfile [templates](gossfile.md#templates).
: Files to read variables from when rendering gossfile [templates](gossfile.md#templates). Can be specified multiple times. When specified multiple times it will load variables from all files. Non-empty map keys that overlap will be overriden by subsequent file that defines them.

Valid formats:

Expand Down
2 changes: 1 addition & 1 deletion serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func newHealthHandler(c *util.Config) (*healthHandler, error) {
color.NoColor = true
cache := cache.New(c.Cache, 30*time.Second)

cfg, err := getGossConfig(c.Vars, c.VarsInline, c.Spec)
cfg, err := getGossConfig(c.VarsFiles, c.VarsInline, c.Spec)
if err != nil {
return nil, err
}
Expand Down
23 changes: 16 additions & 7 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"sort"
"strings"

"dario.cat/mergo"
"gopkg.in/yaml.v3"

"github.com/goss-org/goss/resource"
Expand Down Expand Up @@ -73,22 +74,30 @@ func (t *TmplVars) Env() map[string]string {
return env
}

func loadVars(varsFile string, varsInline string) (map[string]any, error) {
vars, err := varsFromFile(varsFile)
if err != nil {
return nil, fmt.Errorf("loading vars file '%s'\n%w", varsFile, err)
func loadVars(varsFiles []string, varsInline string) (map[string]any, error) {
mergedVars := map[string]any{}

// Later defined vars file overwrites values of the previous files
// in places where non-empty keys overlap.
for _, varsFile := range varsFiles {
vars, err := varsFromFile(varsFile)
if err != nil {
return nil, fmt.Errorf("loading vars file '%s'\n%w", varsFile, err)
}
mergo.Merge(&mergedVars, vars, mergo.WithOverride)
}

varsExtra, err := varsFromString(varsInline)
if err != nil {
return nil, fmt.Errorf("loading inline vars\n%w", err)
}

// Note: This algorithm replaces value under key even if it's nested map
for k, v := range varsExtra {
vars[k] = v
mergedVars[k] = v
}

return vars, nil
return mergedVars, nil
}

func varsFromFile(varsFile string) (map[string]any, error) {
Expand Down Expand Up @@ -162,7 +171,7 @@ func ReadJSONData(data []byte, detectFormat bool) (GossConfig, error) {
func RenderJSON(c *util.Config) (string, error) {
var err error
debug = c.Debug
currentTemplateFilter, err = NewTemplateFilter(c.Vars, c.VarsInline)
currentTemplateFilter, err = NewTemplateFilter(c.VarsFiles, c.VarsInline)
if err != nil {
return "", err
}
Expand Down
96 changes: 86 additions & 10 deletions store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,22 @@ func Test_loadVars(t *testing.T) {
fileNil, fileNilClose := fileMaker(``)
defer fileNilClose()

fileSimple, fileSimpleClose := fileMaker(`{a: a}`)
defer fileSimpleClose()
fileSimple1, fileSimpleClose1 := fileMaker(`{a: a}`)
defer fileSimpleClose1()
fileSimple2, fileSimpleClose2 := fileMaker(`{b: b}`)
defer fileSimpleClose2()
fileSimple3, fileSimpleClose3 := fileMaker(`{a: overriden, c: c}`)
defer fileSimpleClose3()

fileComplex1, fileComplexClose1 := fileMaker(`{vars: {a: a}}`)
defer fileComplexClose1()
fileComplex2, fileComplexClose2 := fileMaker(`{vars: {b: b}}`)
defer fileComplexClose2()
fileComplex3, fileComplexClose3 := fileMaker(`{vars: {a: overriden}}`)
defer fileComplexClose3()

type args struct {
varsFile string
varsFiles []string
varsInline string
}
tests := []struct {
Expand All @@ -145,7 +156,7 @@ func Test_loadVars(t *testing.T) {
{
name: "both_empty",
args: args{
varsFile: fileEmpty,
varsFiles: []string{fileEmpty},
varsInline: `{}`,
},
want: map[string]any{},
Expand All @@ -154,7 +165,7 @@ func Test_loadVars(t *testing.T) {
{
name: "both_nil",
args: args{
varsFile: fileNil,
varsFiles: []string{fileNil},
varsInline: `{}`,
},
want: map[string]any{},
Expand All @@ -163,7 +174,7 @@ func Test_loadVars(t *testing.T) {
{
name: "file_empty",
args: args{
varsFile: fileEmpty,
varsFiles: []string{fileEmpty},
varsInline: `{b: b}`,
},
want: map[string]any{
Expand All @@ -174,7 +185,7 @@ func Test_loadVars(t *testing.T) {
{
name: "inline_empty",
args: args{
varsFile: fileSimple,
varsFiles: []string{fileSimple1},
varsInline: `{}`,
},
want: map[string]any{
Expand All @@ -185,7 +196,7 @@ func Test_loadVars(t *testing.T) {
{
name: "no_overwrite",
args: args{
varsFile: fileSimple,
varsFiles: []string{fileSimple1},
varsInline: `{b: b}`,
},
want: map[string]any{
Expand All @@ -197,7 +208,7 @@ func Test_loadVars(t *testing.T) {
{
name: "overwrite",
args: args{
varsFile: fileSimple,
varsFiles: []string{fileSimple1},
varsInline: `{a: c, b: b}`,
},
want: map[string]any{
Expand All @@ -206,10 +217,75 @@ func Test_loadVars(t *testing.T) {
},
wantErr: false,
},
{
name: "multiple files, non-overlapped keys, no inline vars",
args: args{
varsFiles: []string{fileSimple1, fileSimple2},
varsInline: `{}`,
},
want: map[string]any{
"a": "a",
"b": "b",
},
wantErr: false,
},
{
name: "multiple files, overlapped keys, no inline vars",
args: args{
varsFiles: []string{fileSimple1, fileSimple2, fileSimple3},
varsInline: `{}`,
},
want: map[string]any{
"a": "overriden",
"b": "b",
"c": "c",
},
wantErr: false,
},
{
name: "multiple files, overlapped keys, inline vars",
args: args{
varsFiles: []string{fileSimple1, fileSimple2, fileSimple3},
varsInline: `{c: overriden, b: b}`,
},
want: map[string]any{
"a": "overriden",
"b": "b",
"c": "overriden",
},
wantErr: false,
},
{
name: "multiple nested files",
args: args{
varsFiles: []string{fileComplex1, fileComplex2, fileComplex3},
varsInline: `{}`,
},
want: map[string]any{
"vars": map[string]any{
"a": "overriden",
"b": "b",
},
},
wantErr: false,
},
{
name: "multiple nested files and inline variables",
args: args{
varsFiles: []string{fileComplex1, fileComplex2, fileComplex3},
varsInline: `{vars: { c: c }}`,
},
want: map[string]any{
"vars": map[string]any{
"c": "c",
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := loadVars(tt.args.varsFile, tt.args.varsInline)
got, err := loadVars(tt.args.varsFiles, tt.args.varsInline)

assert.Equal(t, tt.want, got, "map contents")
assert.Equal(t, tt.wantErr, err != nil, "has error")
Expand Down
6 changes: 3 additions & 3 deletions template.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import (
type TemplateFilter func([]byte) ([]byte, error)

// NewTemplateFilter creates a new Template Filter based in the file and inline variables.
func NewTemplateFilter(varsFile string, varsInline string) (func([]byte) ([]byte, error), error) {
vars, err := loadVars(varsFile, varsInline)
func NewTemplateFilter(varsFiles []string, varsInline string) (func([]byte) ([]byte, error), error) {
vars, err := loadVars(varsFiles, varsInline)
if err != nil {
return nil, fmt.Errorf("failed while loading vars file %q: %v", varsFile, err)
return nil, fmt.Errorf("failed while loading vars file %q: %v", varsFiles, err)
}

tVars := &TmplVars{Vars: vars}
Expand Down
10 changes: 5 additions & 5 deletions util/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type Config struct {
CAFile string
CertFile string
KeyFile string
Vars string
VarsFiles []string
VarsInline string
DisabledResourceTypes []string
}
Expand Down Expand Up @@ -90,7 +90,7 @@ func NewConfig(opts ...ConfigOption) (rc *Config, err error) {
Spec: "",
Timeout: 0,
Username: "",
Vars: "",
VarsFiles: []string{},
VarsInline: "",
}

Expand Down Expand Up @@ -206,10 +206,10 @@ func WithDebug() ConfigOption {
}
}

// WithVarsFile is a json or yaml file containing variables to pass to the validator
func WithVarsFile(file string) ConfigOption {
// WithVarsFiles are json or yaml files containing variables to pass to the validator
func WithVarsFiles(files []string) ConfigOption {
return func(c *Config) error {
c.Vars = file
c.VarsFiles = files
return nil
}
}
Expand Down
20 changes: 16 additions & 4 deletions util/config_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package util

import (
"reflect"
"testing"
)

Expand Down Expand Up @@ -28,14 +29,25 @@ func TestWithVarsString(t *testing.T) {
}
}

func TestWithVarsFile(t *testing.T) {
c, err := NewConfig(WithVarsFile("/nonexisting"))
func TestWithVarsFiles(t *testing.T) {
files := []string{"/nonexisting"}
c, err := NewConfig(WithVarsFiles(files))
if err != nil {
t.Fatal(err.Error())
}

if c.Vars != "/nonexisting" {
t.Fatalf("expected '/nonexisting' got %q", c.Vars)
if !reflect.DeepEqual(c.VarsFiles, files) {
t.Fatalf("expected %s got %q", files, c.VarsFiles)
}

files = []string{"/nonexisting", "/second", "third"}
c, err = NewConfig(WithVarsFiles(files))
if err != nil {
t.Fatal(err.Error())
}

if !reflect.DeepEqual(c.VarsFiles, files) {
t.Fatalf("expected %s got %q", files, c.VarsFiles)
}
}

Expand Down
8 changes: 4 additions & 4 deletions validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ import (
"github.com/goss-org/goss/util"
)

func getGossConfig(vars string, varsInline string, specFile string) (cfg *GossConfig, err error) {
func getGossConfig(varsFiles []string, varsInline string, specFile string) (cfg *GossConfig, err error) {
// handle stdin
var fh *os.File
var path, source string
var gossConfig GossConfig

currentTemplateFilter, err = NewTemplateFilter(vars, varsInline)
currentTemplateFilter, err = NewTemplateFilter(varsFiles, varsInline)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -85,7 +85,7 @@ func getOutputer(c *bool, format string) (outputs.Outputer, error) {
// ValidateResults performs validation and provides programmatic access to validation results
// no retries or outputs are supported
func ValidateResults(c *util.Config) (results <-chan []resource.TestResult, err error) {
gossConfig, err := getGossConfig(c.Vars, c.VarsInline, c.Spec)
gossConfig, err := getGossConfig(c.VarsFiles, c.VarsInline, c.Spec)
if err != nil {
return nil, err
}
Expand All @@ -104,7 +104,7 @@ func Validate(c *util.Config) (code int, err error) {
if err != nil {
return 1, err
}
gossConfig, err := getGossConfig(c.Vars, c.VarsInline, c.Spec)
gossConfig, err := getGossConfig(c.VarsFiles, c.VarsInline, c.Spec)
if err != nil {
return 78, err
}
Expand Down