From ff8941c27b15e47e32babc631aebd216bfdceb24 Mon Sep 17 00:00:00 2001 From: raittes Date: Tue, 31 Mar 2020 20:28:59 -0300 Subject: [PATCH 1/5] feat(state) support multiple files in args --- cmd/common.go | 4 ++-- cmd/diff.go | 8 ++++---- cmd/sync.go | 8 ++++---- cmd/validate.go | 10 +++++----- file/reader.go | 8 ++++---- file/readfile.go | 20 +++++++++++++------- 6 files changed, 32 insertions(+), 26 deletions(-) diff --git a/cmd/common.go b/cmd/common.go index a86c9652f..ead652c33 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -59,7 +59,7 @@ func checkWorkspace(config utils.KongClientConfig) error { return nil } -func syncMain(filename string, dry bool, parallelism int) error { +func syncMain(filenames []string, dry bool, parallelism int) error { // load Kong version before workspace kongVersion, err := kongVersion(config) @@ -68,7 +68,7 @@ func syncMain(filename string, dry bool, parallelism int) error { } // read target file - targetContent, err := file.GetContentFromFile(filename) + targetContent, err := file.GetContentFromFiles(filenames) if err != nil { return err } diff --git a/cmd/diff.go b/cmd/diff.go index b5b276b91..99f088342 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -6,7 +6,7 @@ import ( ) var ( - diffCmdKongStateFile string + diffCmdKongStateFile []string diffCmdParallelism int diffCmdNonZeroExitCode bool ) @@ -25,7 +25,7 @@ that will be created or updated or deleted. return syncMain(diffCmdKongStateFile, true, diffCmdParallelism) }, PreRunE: func(cmd *cobra.Command, args []string) error { - if diffCmdKongStateFile == "" { + if len(diffCmdKongStateFile) == 0 { return errors.New("A state file with Kong's configuration " + "must be specified using -s/--state flag.") } @@ -35,8 +35,8 @@ that will be created or updated or deleted. func init() { rootCmd.AddCommand(diffCmd) - diffCmd.Flags().StringVarP(&diffCmdKongStateFile, - "state", "s", "kong.yaml", "file containing Kong's configuration. "+ + diffCmd.Flags().StringSliceVarP(&diffCmdKongStateFile, + "state", "s", []string{"kong.yaml"}, "file containing Kong's configuration. "+ "Use '-' to read from stdin.") diffCmd.Flags().BoolVar(&dumpConfig.SkipConsumers, "skip-consumers", false, "do not diff consumers or "+ diff --git a/cmd/sync.go b/cmd/sync.go index ecfef5c03..70a327725 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -6,7 +6,7 @@ import ( ) var ( - syncCmdKongStateFile string + syncCmdKongStateFile []string syncCmdParallelism int ) @@ -21,7 +21,7 @@ to get Kong's state in sync with the input state.`, return syncMain(syncCmdKongStateFile, false, syncCmdParallelism) }, PreRunE: func(cmd *cobra.Command, args []string) error { - if syncCmdKongStateFile == "" { + if len(syncCmdKongStateFile) == 0 { return errors.New("A state file with Kong's configuration " + "must be specified using -s/--state flag.") } @@ -31,8 +31,8 @@ to get Kong's state in sync with the input state.`, func init() { rootCmd.AddCommand(syncCmd) - syncCmd.Flags().StringVarP(&syncCmdKongStateFile, - "state", "s", "kong.yaml", "file containing Kong's configuration. "+ + syncCmd.Flags().StringSliceVarP(&syncCmdKongStateFile, + "state", "s", []string{"kong.yaml"}, "file containing Kong's configuration. "+ "Use '-' to read from stdin.") syncCmd.Flags().BoolVar(&dumpConfig.SkipConsumers, "skip-consumers", false, "do not diff consumers or "+ diff --git a/cmd/validate.go b/cmd/validate.go index 51364f23a..0ca174ea4 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -8,7 +8,7 @@ import ( ) var ( - validateCmdKongStateFile string + validateCmdKongStateFile []string ) // validateCmd represents the diff command @@ -26,7 +26,7 @@ this command. RunE: func(cmd *cobra.Command, args []string) error { // read target file // this does json schema validation as well - targetContent, err := file.GetContentFromFile(validateCmdKongStateFile) + targetContent, err := file.GetContentFromFiles(validateCmdKongStateFile) if err != nil { return err } @@ -51,7 +51,7 @@ this command. return nil }, PreRunE: func(cmd *cobra.Command, args []string) error { - if validateCmdKongStateFile == "" { + if len(validateCmdKongStateFile) == 0 { return errors.New("A state file with Kong's configuration " + "must be specified using -s/--state flag.") } @@ -61,7 +61,7 @@ this command. func init() { rootCmd.AddCommand(validateCmd) - validateCmd.Flags().StringVarP(&validateCmdKongStateFile, - "state", "s", "kong.yaml", "file containing Kong's configuration. "+ + validateCmd.Flags().StringSliceVarP(&validateCmdKongStateFile, + "state", "s", []string{"kong.yaml"}, "file containing Kong's configuration. "+ "Use '-' to read from stdin.") } diff --git a/file/reader.go b/file/reader.go index f06349983..301c860fe 100644 --- a/file/reader.go +++ b/file/reader.go @@ -16,7 +16,7 @@ type RenderConfig struct { KongVersion semver.Version } -// GetContentFromFile reads in a file with filename and constructs +// GetContentFromFiles reads in a file with a slice of filenames and constructs // a state. If filename is `-`, then it will read from os.Stdin. // If filename represents a directory, it will traverse the tree // rooted at filename, read all the files with .yaml, .yml and .json extensions @@ -24,12 +24,12 @@ type RenderConfig struct { // // It will return an error if the file representation is invalid // or if there is any error during processing. -func GetContentFromFile(filename string) (*Content, error) { - if filename == "" { +func GetContentFromFiles(filenames []string) (*Content, error) { + if len(filenames) == 0 { return nil, errors.New("filename cannot be empty") } - return getContent(filename) + return getContent(filenames) } // Get process the fileContent and renders a RawState. diff --git a/file/readfile.go b/file/readfile.go index cfefafc75..a56cc1db1 100644 --- a/file/readfile.go +++ b/file/readfile.go @@ -13,16 +13,22 @@ import ( yaml "gopkg.in/yaml.v2" ) -// getContent reads reads all the YAML and JSON files in the directory or the -// file, depending on what fileOrDir represents, merges the content of +// getContent reads all the YAML and JSON files in the directory or the +// file, depending on the type of each item in filenames, merges the content of // these files and renders a Content. -func getContent(fileOrDir string) (*Content, error) { - readers, err := getReaders(fileOrDir) - if err != nil { - return nil, err +func getContent(filenames []string) (*Content, error) { + var allReaders []io.Reader + for _, fileOrDir := range filenames { + readers, err := getReaders(fileOrDir) + if err != nil { + return nil, err + } + for _, r := range readers { + allReaders = append(allReaders, r) + } } var res Content - for _, r := range readers { + for _, r := range allReaders { content, err := readContent(r) if err != nil { return nil, errors.Wrap(err, "reading file") From 74eebfe72253e654989969a6f4e18d592e7dbb9e Mon Sep 17 00:00:00 2001 From: raittes Date: Tue, 31 Mar 2020 21:11:54 -0300 Subject: [PATCH 2/5] tests(reader) fix tests to support multiple args in state flag --- file/reader_test.go | 12 ++++++------ file/readfile_test.go | 18 +++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/file/reader_test.go b/file/reader_test.go index a37b4a312..1cdaaf9b5 100644 --- a/file/reader_test.go +++ b/file/reader_test.go @@ -40,9 +40,9 @@ func Test_ensureJSON(t *testing.T) { } func TestReadKongStateFromStdinFailsToParseText(t *testing.T) { - var filename = "-" + var filenames = []string{"-"} assert := assert.New(t) - assert.Equal("-", filename) + assert.Equal("-", filenames[0]) var content bytes.Buffer content.Write([]byte("hunter2\n")) @@ -66,15 +66,15 @@ func TestReadKongStateFromStdinFailsToParseText(t *testing.T) { os.Stdin = tmpfile - c, err := GetContentFromFile(filename) + c, err := GetContentFromFiles(filenames) assert.NotNil(err) assert.Nil(c) } func TestReadKongStateFromStdin(t *testing.T) { - var filename = "-" + var filenames = []string{"-"} assert := assert.New(t) - assert.Equal("-", filename) + assert.Equal("-", filenames[0]) var content bytes.Buffer content.Write([]byte("services:\n- host: test.com\n name: test service\n")) @@ -98,7 +98,7 @@ func TestReadKongStateFromStdin(t *testing.T) { os.Stdin = tmpfile - c, err := GetContentFromFile(filename) + c, err := GetContentFromFiles(filenames) assert.NotNil(c) assert.Nil(err) diff --git a/file/readfile_test.go b/file/readfile_test.go index eb6fe278d..ce0572266 100644 --- a/file/readfile_test.go +++ b/file/readfile_test.go @@ -126,7 +126,7 @@ func Test_getReaders(t *testing.T) { func Test_getContent(t *testing.T) { type args struct { - fileOrDir string + filenames []string } tests := []struct { name string @@ -136,37 +136,37 @@ func Test_getContent(t *testing.T) { }{ { name: "directory does not exist", - args: args{"testdata/does-not-exist"}, + args: args{[]string{"testdata/does-not-exist"}}, want: nil, wantErr: true, }, { name: "empty directory", - args: args{"testdata/emptydir"}, + args: args{[]string{"testdata/emptydir"}}, want: &Content{}, wantErr: false, }, { name: "directory with empty files", - args: args{"testdata/emptyfiles"}, + args: args{[]string{"testdata/emptyfiles"}}, want: &Content{}, wantErr: false, }, { name: "bad yaml", - args: args{"testdata/badyaml"}, + args: args{[]string{"testdata/badyaml"}}, want: nil, wantErr: true, }, { name: "bad JSON", - args: args{"testdata/badjson"}, + args: args{[]string{"testdata/badjson"}}, want: nil, wantErr: true, }, { name: "single file", - args: args{"testdata/file.yaml"}, + args: args{[]string{"testdata/file.yaml"}}, want: &Content{ Services: []FService{ { @@ -196,7 +196,7 @@ func Test_getContent(t *testing.T) { }, { name: "valid directory", - args: args{"testdata/valid"}, + args: args{[]string{"testdata/valid"}}, want: &Content{ Info: &Info{ SelectorTags: []string{"tag1"}, @@ -262,7 +262,7 @@ func Test_getContent(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := getContent(tt.args.fileOrDir) + got, err := getContent(tt.args.filenames) if (err != nil) != tt.wantErr { t.Errorf("getContent() error = %v, wantErr %v", err, tt.wantErr) return From 6e1395532f9aa5e272f52e0d4d96e72bd70c500d Mon Sep 17 00:00:00 2001 From: raittes Date: Wed, 1 Apr 2020 01:24:57 -0300 Subject: [PATCH 3/5] tests(file) add test for multiple state files --- file/readfile_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/file/readfile_test.go b/file/readfile_test.go index ce0572266..2da20ad08 100644 --- a/file/readfile_test.go +++ b/file/readfile_test.go @@ -194,6 +194,48 @@ func Test_getContent(t *testing.T) { }, wantErr: false, }, + { + name: "multiple files", + args: args{[]string{"testdata/file.yaml", "testdata/file.json"}}, + want: &Content{ + Services: []FService{ + { + Service: kong.Service{ + Name: kong.String("svc2"), + Host: kong.String("2.example.com"), + }, + Routes: []*FRoute{ + { + Route: kong.Route{ + Name: kong.String("r2"), + Paths: kong.StringSlice("/r2"), + }, + }, + }, + }, + }, + Plugins: []FPlugin{ + { + Plugin: kong.Plugin{ + Name: kong.String("prometheus"), + }, + }, + }, + Consumers: []FConsumer{ + { + Consumer: kong.Consumer{ + Username: kong.String("foo"), + }, + }, + { + Consumer: kong.Consumer{ + Username: kong.String("bar"), + }, + }, + }, + }, + wantErr: false, + }, { name: "valid directory", args: args{[]string{"testdata/valid"}}, From 24271cfbdf7856c0bd0567b81a0aafe2ab82169d Mon Sep 17 00:00:00 2001 From: raittes Date: Wed, 1 Apr 2020 01:35:47 -0300 Subject: [PATCH 4/5] docs(state) state flag with multiple files --- docs/guides/multi-file-state.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/guides/multi-file-state.md b/docs/guides/multi-file-state.md index d8a80ac3d..847216c2c 100644 --- a/docs/guides/multi-file-state.md +++ b/docs/guides/multi-file-state.md @@ -14,6 +14,9 @@ multiple files if: You can specify an entire directory for decK to consumer using the `--state` flag. +You also can specify multiple files, comma-separated (`--state file.yml,file2.yml,directory`) +or using the flag many times (`-s file.yml -s file2.yml -s directory`) + Under the hood, decK combines the YAML/JSON files in a very dumb fashion, meaning it just concatenates the various arrays in the file together, before starting to process the state. From ebd7efb074692ee505a21be8cfea1bd10af5d57a Mon Sep 17 00:00:00 2001 From: raittes Date: Thu, 2 Apr 2020 22:29:38 -0300 Subject: [PATCH 5/5] docs(cmd) update help for state flag with multiple files --- cmd/diff.go | 3 ++- cmd/sync.go | 3 ++- cmd/validate.go | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cmd/diff.go b/cmd/diff.go index 99f088342..be7a5d71f 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -36,7 +36,8 @@ that will be created or updated or deleted. func init() { rootCmd.AddCommand(diffCmd) diffCmd.Flags().StringSliceVarP(&diffCmdKongStateFile, - "state", "s", []string{"kong.yaml"}, "file containing Kong's configuration. "+ + "state", "s", []string{"kong.yaml"}, "file(s) containing Kong's configuration.\n"+ + "This flag can be specified multiple times for multiple files.\n"+ "Use '-' to read from stdin.") diffCmd.Flags().BoolVar(&dumpConfig.SkipConsumers, "skip-consumers", false, "do not diff consumers or "+ diff --git a/cmd/sync.go b/cmd/sync.go index 70a327725..8bcfa6659 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -32,7 +32,8 @@ to get Kong's state in sync with the input state.`, func init() { rootCmd.AddCommand(syncCmd) syncCmd.Flags().StringSliceVarP(&syncCmdKongStateFile, - "state", "s", []string{"kong.yaml"}, "file containing Kong's configuration. "+ + "state", "s", []string{"kong.yaml"}, "file(s) containing Kong's configuration.\n"+ + "This flag can be specified multiple times for multiple files.\n"+ "Use '-' to read from stdin.") syncCmd.Flags().BoolVar(&dumpConfig.SkipConsumers, "skip-consumers", false, "do not diff consumers or "+ diff --git a/cmd/validate.go b/cmd/validate.go index 0ca174ea4..6a24f0782 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -62,6 +62,7 @@ this command. func init() { rootCmd.AddCommand(validateCmd) validateCmd.Flags().StringSliceVarP(&validateCmdKongStateFile, - "state", "s", []string{"kong.yaml"}, "file containing Kong's configuration. "+ + "state", "s", []string{"kong.yaml"}, "file(s) containing Kong's configuration.\n"+ + "This flag can be specified multiple times for multiple files.\n"+ "Use '-' to read from stdin.") }