From 3af87400c6f1e44f50eac3441d46f6621c8e16d3 Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Wed, 5 May 2021 14:39:34 -0400 Subject: [PATCH] fix CLI interface, now with subcommands 'all' and 'crdsplit' --- .gitignore | 1 + ksplit/cmd/allsplit.go | 35 +++++++++ ksplit/cmd/crdsplit.go | 35 +++++++++ ksplit/main.go | 30 ++----- pkg/ksplit.go | 65 ++++++++++----- pkg/ksplit_test.go | 175 ++++++++++++++++++++++++++++++++++++++--- 6 files changed, 286 insertions(+), 55 deletions(-) create mode 100644 ksplit/cmd/allsplit.go create mode 100644 ksplit/cmd/crdsplit.go diff --git a/.gitignore b/.gitignore index 349fac1b..a0ee9930 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .state bin +test diff --git a/ksplit/cmd/allsplit.go b/ksplit/cmd/allsplit.go new file mode 100644 index 00000000..885e22e8 --- /dev/null +++ b/ksplit/cmd/allsplit.go @@ -0,0 +1,35 @@ +package cmd + +import ( + "github.com/go-ksplit/ksplit/pkg" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func AllSplitCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "all", + Short: "split kubernetes yaml within a directory", + Long: `ksplit reformats generated kubernetes yaml into a more easily readable file format`, + Example: "ksplit all myk8syamldir/", + SilenceErrors: true, + PreRun: func(cmd *cobra.Command, args []string) { + viper.BindPFlags(cmd.Flags()) + }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + cmd.Help() + return errors.New("Please supply a directory") + } + + err := pkg.MaybeSplitMultidocYamlFs(args[0]) + if err != nil { + return errors.Wrap(err, "allsplit cmd") + } + return nil + }, + } + + return cmd +} diff --git a/ksplit/cmd/crdsplit.go b/ksplit/cmd/crdsplit.go new file mode 100644 index 00000000..24f6f1de --- /dev/null +++ b/ksplit/cmd/crdsplit.go @@ -0,0 +1,35 @@ +package cmd + +import ( + "github.com/go-ksplit/ksplit/pkg" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func CrdSplitCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "crdsplit", + Short: "split CRDs from non-CRD files", + Long: `...`, + Example: "ksplit crdsplit myk8syamldir/", + SilenceErrors: true, + PreRun: func(cmd *cobra.Command, args []string) { + viper.BindPFlags(cmd.Flags()) + }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + cmd.Help() + return errors.New("Please supply a directory") + } + + err := pkg.MaybeSplitCRDsFs(args[0]) + if err != nil { + return errors.Wrap(err, "crdsplit cmd") + } + return nil + }, + } + + return cmd +} diff --git a/ksplit/main.go b/ksplit/main.go index 3555c382..701cbd37 100644 --- a/ksplit/main.go +++ b/ksplit/main.go @@ -1,15 +1,13 @@ -package ksplit +package main import ( "fmt" "os" "strings" - "github.com/pkg/errors" + cmd2 "github.com/go-ksplit/ksplit/ksplit/cmd" "github.com/spf13/cobra" "github.com/spf13/viper" - - "github.com/go-ksplit/ksplit/pkg" ) func main() { @@ -20,29 +18,13 @@ func main() { } func RootCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "ksplit directory", - Short: "split kubernetes yaml within a directory", - Long: `ksplit reformats generated kubernetes yaml into a more easily readable file format`, - Example: "ksplit myk8syamldir/", - SilenceUsage: true, - SilenceErrors: true, - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - cmd.Help() - return errors.New("Please supply a directory") - } - - err := pkg.MaybeSplitMultidocYamlFs(args[0]) - if err != nil { - return errors.Wrap(err, "root cmd") - } - return nil - }, - } + cmd := &cobra.Command{} cmd.PersistentFlags().String("log-level", "off", "Log level") + cmd.AddCommand(cmd2.CrdSplitCmd()) + cmd.AddCommand(cmd2.AllSplitCmd()) + _ = viper.BindPFlags(cmd.Flags()) _ = viper.BindPFlags(cmd.PersistentFlags()) viper.AutomaticEnv() diff --git a/pkg/ksplit.go b/pkg/ksplit.go index 804e20fa..c54bbdad 100644 --- a/pkg/ksplit.go +++ b/pkg/ksplit.go @@ -14,8 +14,9 @@ import ( ) type outputYaml struct { - name string - contents string + name string + contents string + overridePath string } type MinimalK8sYaml struct { @@ -36,19 +37,30 @@ type ListK8sYaml struct { func MaybeSplitMultidocYamlFs(localpath string) error { fs := afero.NewOsFs() - return MaybeSplitMultidocYaml(afero.Afero{Fs: fs}, localpath) + return MaybeSplitMultidocYaml(afero.Afero{Fs: fs}, localpath, false) +} + +func MaybeSplitCRDsFs(localpath string) error { + fs := afero.NewOsFs() + return MaybeSplitMultidocYaml(afero.Afero{Fs: fs}, localpath, true) } // this function is not perfect, and has known limitations. One of these is that it does not account for `\n---\n` in multiline strings. -func MaybeSplitMultidocYaml(fs afero.Afero, localPath string) error { +func MaybeSplitMultidocYaml(fs afero.Afero, localPath string, combineNonCRDs bool) error { files, err := fs.ReadDir(localPath) if err != nil { return errors.Wrapf(err, "read files in %s", localPath) } + allOutputFiles := []outputYaml{} + allCrds := []string{} + for _, file := range files { + outputFiles := []outputYaml{} + crds := []string{} + if file.IsDir() { - if err := MaybeSplitMultidocYaml(fs, filepath.Join(localPath, file.Name())); err != nil { + if err := MaybeSplitMultidocYaml(fs, filepath.Join(localPath, file.Name()), combineNonCRDs); err != nil { return err } } @@ -63,9 +75,7 @@ func MaybeSplitMultidocYaml(fs afero.Afero, localPath string) error { return errors.Wrapf(err, "read %s", filepath.Join(localPath, file.Name())) } - outputFiles := []outputYaml{} filesStrings := strings.Split(string(inFileBytes), "\n---\n") - crds := []string{} // generate replacement yaml files for idx, fileString := range filesStrings { @@ -79,14 +89,8 @@ func MaybeSplitMultidocYaml(fs afero.Afero, localPath string) error { crds = append(crds, newCRDs...) } - if len(crds) > 0 { - crdsFile := outputYaml{contents: strings.Join(crds, "\n---\n"), name: "CustomResourceDefinitions"} - outputFiles = append(outputFiles, crdsFile) - } - - if len(outputFiles) < 2 { - // not a multidoc yaml, or at least not a multidoc kubernetes yaml - continue + if len(outputFiles)+len(crds) <= 1 { // don't rename files if we don't have to + outputFiles[0].overridePath = file.Name() } // delete multidoc yaml file @@ -95,12 +99,33 @@ func MaybeSplitMultidocYaml(fs afero.Afero, localPath string) error { return errors.Wrapf(err, "unable to remove %s", filepath.Join(localPath, file.Name())) } - // write replacement yaml - for _, outputFile := range outputFiles { + allOutputFiles = append(allOutputFiles, outputFiles...) + allCrds = append(allCrds, crds...) + } + + if combineNonCRDs { + allOutputStrings := []string{} + for _, outputFile := range allOutputFiles { + allOutputStrings = append(allOutputStrings, outputFile.contents) + } + nonCrdsFile := outputYaml{contents: strings.Join(allOutputStrings, "\n---\n"), name: "AllResorces"} + allOutputFiles = []outputYaml{nonCrdsFile} + } + + if len(allCrds) > 0 { + crdsFile := outputYaml{contents: strings.Join(allCrds, "\n---\n"), name: "CustomResourceDefinitions"} + allOutputFiles = append(allOutputFiles, crdsFile) + } + + // write replacement yaml + for _, outputFile := range allOutputFiles { + if outputFile.overridePath != "" { + err = fs.WriteFile(filepath.Join(localPath, outputFile.overridePath), []byte(outputFile.contents), os.FileMode(0644)) + } else { err = fs.WriteFile(filepath.Join(localPath, outputFile.name+".yaml"), []byte(outputFile.contents), os.FileMode(0644)) - if err != nil { - return errors.Wrapf(err, "write %s", outputFile.name) - } + } + if err != nil { + return errors.Wrapf(err, "write %s", outputFile.name) } } diff --git a/pkg/ksplit_test.go b/pkg/ksplit_test.go index 5c91d5fc..bbb14a6e 100644 --- a/pkg/ksplit_test.go +++ b/pkg/ksplit_test.go @@ -16,11 +16,12 @@ func TestResolver_maybeSplitMultidocYaml(t *testing.T) { } tests := []struct { - name string - localPath string - wantErr bool - inputFiles []fileStruct - outputFiles []fileStruct + name string + localPath string + wantErr bool + combineNonCrds bool + inputFiles []fileStruct + outputFiles []fileStruct }{ { name: "one doc", @@ -301,11 +302,7 @@ metadata: outputFiles: []fileStruct{ { name: "/comment-before/account.yaml", - data: ` -# The service account, cluster roles, and cluster role binding are -# only needed for Kubernetes with role-based access control (RBAC). ---- -apiVersion: v1 + data: `apiVersion: v1 kind: ServiceAccount metadata: labels: @@ -812,6 +809,162 @@ spec: --- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: test.stable.example.com +spec: + group: stable.example.com + versions: + - name: v1 + served: true + storage: true + scope: Namespaced +`, + }, + }, + }, + { + name: "crds", + localPath: "/test", + wantErr: false, + combineNonCrds: true, + inputFiles: []fileStruct{ + { + name: "/test/multidoc.yaml", + data: ` +#A Test Comment +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: jaeger-collector + labels: + app: jaeger + jaeger-infra: collector-deployment + spec: + replicas: 1 + strategy: + type: Recreate + +--- + +apiVersion: v1 +kind: Service +metadata: + name: jaeger-collector + labels: + app: jaeger + jaeger-infra: collector-service +spec: + ports: + - name: jaeger-collector-tchannel + port: 14267 + protocol: TCP + targetPort: 14267 + - name: jaeger-collector-http + port: 14268 + protocol: TCP + targetPort: 14268 + - name: jaeger-collector-zipkin + port: 9411 + protocol: TCP + targetPort: 9411 + selector: + jaeger-infra: collector-pod + type: ClusterIP + +--- + +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: crontabs.stable.example.com +spec: + group: stable.example.com + versions: + - name: v1 + served: true + storage: true + scope: Namespaced + +--- + +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: test.stable.example.com +spec: + group: stable.example.com + versions: + - name: v1 + served: true + storage: true + scope: Namespaced +`, + }, + }, + outputFiles: []fileStruct{ + { + name: "/test/AllResorces.yaml", + data: ` +#A Test Comment +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: jaeger-collector + labels: + app: jaeger + jaeger-infra: collector-deployment + spec: + replicas: 1 + strategy: + type: Recreate + +--- + +apiVersion: v1 +kind: Service +metadata: + name: jaeger-collector + labels: + app: jaeger + jaeger-infra: collector-service +spec: + ports: + - name: jaeger-collector-tchannel + port: 14267 + protocol: TCP + targetPort: 14267 + - name: jaeger-collector-http + port: 14268 + protocol: TCP + targetPort: 14268 + - name: jaeger-collector-zipkin + port: 9411 + protocol: TCP + targetPort: 9411 + selector: + jaeger-infra: collector-pod + type: ClusterIP +`, + }, + { + name: "/test/CustomResourceDefinitions.yaml", + data: ` +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: crontabs.stable.example.com +spec: + group: stable.example.com + versions: + - name: v1 + served: true + storage: true + scope: Namespaced + +--- + apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: @@ -840,7 +993,7 @@ spec: } // run split function - if err := MaybeSplitMultidocYaml(mockFs, tt.localPath); (err != nil) != tt.wantErr { + if err := MaybeSplitMultidocYaml(mockFs, tt.localPath, tt.combineNonCrds); (err != nil) != tt.wantErr { t.Errorf("Resolver.maybeSplitMultidocYaml() error = %v, wantErr %v", err, tt.wantErr) }