Skip to content

Commit

Permalink
feat(state) support multiple files in args
Browse files Browse the repository at this point in the history
From #137
  • Loading branch information
raittes committed Apr 3, 2020
1 parent fb9eaaf commit c50656c
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 41 deletions.
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])

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

0 comments on commit c50656c

Please sign in to comment.