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

feat(state) support multiple files in args #137

Merged
merged 5 commits into from
Apr 3, 2020
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: 2 additions & 2 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
Expand Down
9 changes: 5 additions & 4 deletions cmd/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

var (
diffCmdKongStateFile string
diffCmdKongStateFile []string
diffCmdParallelism int
diffCmdNonZeroExitCode bool
)
Expand All @@ -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.")
}
Expand All @@ -35,8 +35,9 @@ 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(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 "+
Expand Down
9 changes: 5 additions & 4 deletions cmd/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

var (
syncCmdKongStateFile string
syncCmdKongStateFile []string
syncCmdParallelism int
)

Expand All @@ -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.")
}
Expand All @@ -31,8 +31,9 @@ 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(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 "+
Expand Down
11 changes: 6 additions & 5 deletions cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

var (
validateCmdKongStateFile string
validateCmdKongStateFile []string
)

// validateCmd represents the diff command
Expand All @@ -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
}
Expand All @@ -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.")
}
Expand All @@ -61,7 +61,8 @@ 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(s) containing Kong's configuration.\n"+
"This flag can be specified multiple times for multiple files.\n"+
"Use '-' to read from stdin.")
}
3 changes: 3 additions & 0 deletions docs/guides/multi-file-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions file/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ 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
// and generate a content after a merge of the content from all the files.
//
// 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.
Expand Down
12 changes: 6 additions & 6 deletions file/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Copy link
Member

Choose a reason for hiding this comment

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

There is an interesting case where some configuration can be specified via Stdin and other via flag.
I was considering to restrict - as standalone, meaning you either specify only file(s) or only Stdin.
I think it is okay to allow it.
Just leaving a thought. No changes required.


var content bytes.Buffer
content.Write([]byte("hunter2\n"))
Expand All @@ -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"))
Expand All @@ -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)

Expand Down
20 changes: 13 additions & 7 deletions file/readfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
60 changes: 51 additions & 9 deletions file/readfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -136,37 +136,67 @@ 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{
{
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"),
},
},
},
},
wantErr: false,
},
{
name: "multiple files",
args: args{[]string{"testdata/file.yaml", "testdata/file.json"}},
want: &Content{
Services: []FService{
{
Expand All @@ -191,12 +221,24 @@ func Test_getContent(t *testing.T) {
},
},
},
Consumers: []FConsumer{
{
Consumer: kong.Consumer{
Username: kong.String("foo"),
},
},
{
Consumer: kong.Consumer{
Username: kong.String("bar"),
},
},
},
},
wantErr: false,
},
{
name: "valid directory",
args: args{"testdata/valid"},
args: args{[]string{"testdata/valid"}},
want: &Content{
Info: &Info{
SelectorTags: []string{"tag1"},
Expand Down Expand Up @@ -262,7 +304,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
Expand Down