diff --git a/app.go b/app.go index 4c17586..270cf01 100644 --- a/app.go +++ b/app.go @@ -110,6 +110,8 @@ func root() *cobra.Command { rootCommand.Flags().BoolVar(&opts.SortByKind, "sort-by-kind", false, "if enabled, resources are sorted by Kind, a la Helm, before saving them to disk") rootCommand.Flags().BoolVar(&opts.OutputToStdout, "stdout", false, "if enabled, no resource is written to disk and all resources are printed to stdout instead") rootCommand.Flags().StringVarP(&configFile, "config", "c", "", "path to the config file") + rootCommand.Flags().BoolVar(&opts.AllowEmptyKinds, "allow-empty-kinds", false, "if enabled, resources with empty kinds don't produce an error when filtering") + rootCommand.Flags().BoolVar(&opts.AllowEmptyNames, "allow-empty-names", false, "if enabled, resources with empty names don't produce an error when filtering") _ = rootCommand.Flags().MarkHidden("debug") return rootCommand diff --git a/slice/process.go b/slice/process.go index 198541b..583dc5b 100644 --- a/slice/process.go +++ b/slice/process.go @@ -57,12 +57,12 @@ func (s *Split) parseYAMLManifest(contents []byte) (yamlFile, error) { s.log.Printf("Kubernetes metadata found -> %#v", k8smeta) // Check if we have a Kubernetes kind and we're requesting inclusion or exclusion - if k8smeta.Kind == "" && (hasIncluded || hasExcluded) { + if k8smeta.Kind == "" && !s.opts.AllowEmptyKinds && (hasIncluded || hasExcluded) { return yamlFile{}, fmt.Errorf("unable to find Kubernetes \"kind\" field in file number %d", s.fileCount) } // Check if we have a Kubernetes name and we're requesting inclusion or exclusion - if k8smeta.Name == "" && (hasIncluded || hasExcluded) { + if k8smeta.Name == "" && !s.opts.AllowEmptyNames && (hasIncluded || hasExcluded) { return yamlFile{}, fmt.Errorf("unable to find Kubernetes \"metadata.name\" field in file number %d", s.fileCount) } diff --git a/slice/process_test.go b/slice/process_test.go index 9cac370..c7e172e 100644 --- a/slice/process_test.go +++ b/slice/process_test.go @@ -347,3 +347,118 @@ kind: Foo }) } } + +func TestSplit_parseYamlManifestAllowingEmpties(t *testing.T) { + tests := []struct { + name string + contents []byte + skipEmptyName bool + skipEmptyKind bool + includeKind string + includeName string + want yamlFile + wantErr bool + }{ + { + name: "include name and kind", + contents: []byte(`--- +apiVersion: v1 +kind: Foo +metadata: + name: bar +`), + want: yamlFile{ + filename: "foo-bar.yaml", + meta: kubeObjectMeta{APIVersion: "v1", Kind: "Foo", Name: "bar"}, + }, + includeKind: "Foo", + skipEmptyName: false, + skipEmptyKind: false, + }, + { + name: "allow empty kind", + contents: []byte(`--- +apiVersion: v1 +kind: "" +metadata: + name: bar +`), + want: yamlFile{ + filename: "-bar.yaml", + meta: kubeObjectMeta{APIVersion: "v1", Kind: "", Name: "bar"}, + }, + includeName: "bar", + skipEmptyName: false, + skipEmptyKind: true, + }, + { + name: "dont allow empty kind", + contents: []byte(`--- +apiVersion: v1 +metadata: + name: bar +`), + wantErr: true, + includeName: "bar", + skipEmptyName: false, + skipEmptyKind: false, + }, + { + name: "allow empty name", + contents: []byte(`--- +apiVersion: v1 +kind: Foo +metadata: + name: "" +`), + want: yamlFile{ + filename: "foo-.yaml", + meta: kubeObjectMeta{APIVersion: "v1", Kind: "Foo", Name: ""}, + }, + includeKind: "Foo", + skipEmptyName: true, + skipEmptyKind: false, + }, + { + name: "dont allow empty name", + contents: []byte(`--- +apiVersion: v1 +kind: Foo +`), + wantErr: true, + includeKind: "Foo", + skipEmptyName: false, + skipEmptyKind: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + s := &Split{ + log: nolog, + template: template.Must(template.New(DefaultTemplateName).Funcs(local.Functions).Parse(DefaultTemplateName)), + } + + if len(tt.includeKind) > 0 { + s.opts.IncludedKinds = []string{tt.includeKind} + } + + if len(tt.includeName) > 0 { + s.opts.IncludedNames = []string{tt.includeName} + } + + s.opts.AllowEmptyKinds = tt.skipEmptyKind + s.opts.AllowEmptyNames = tt.skipEmptyName + + if err := s.validateFilters(); err != nil { + t.Fatalf("not expecting error validating filters, got: %s", err) + } + + got, err := s.parseYAMLManifest(tt.contents) + requireErrorIf(t, tt.wantErr, err) + t.Logf("got: %#v", got) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/slice/split.go b/slice/split.go index 8207788..c5f171d 100644 --- a/slice/split.go +++ b/slice/split.go @@ -80,4 +80,7 @@ type Options struct { StrictKubernetes bool // if true, any YAMLs that don't contain at least an "apiVersion", "kind" and "metadata.name" will be excluded SortByKind bool // if true, it will sort the resources by kind + + AllowEmptyNames bool + AllowEmptyKinds bool } diff --git a/slice/validate.go b/slice/validate.go index 3f87606..031555a 100644 --- a/slice/validate.go +++ b/slice/validate.go @@ -34,6 +34,22 @@ func (s *Split) init() error { } func (s *Split) validateFilters() error { + if len(s.opts.IncludedKinds) > 0 && s.opts.AllowEmptyKinds { + return fmt.Errorf("cannot specify both included kinds and allow empty kinds") + } + + if len(s.opts.ExcludedKinds) > 0 && s.opts.AllowEmptyKinds { + return fmt.Errorf("cannot specify both excluded kinds and allow empty kinds") + } + + if len(s.opts.IncludedNames) > 0 && s.opts.AllowEmptyNames { + return fmt.Errorf("cannot specify both included names and allow empty names") + } + + if len(s.opts.ExcludedNames) > 0 && s.opts.AllowEmptyNames { + return fmt.Errorf("cannot specify both excluded names and allow empty names") + } + if len(s.opts.IncludedKinds) > 0 && len(s.opts.ExcludedKinds) > 0 { return fmt.Errorf("cannot specify both included and excluded kinds") } diff --git a/slice/validate_test.go b/slice/validate_test.go new file mode 100644 index 0000000..12bd4b1 --- /dev/null +++ b/slice/validate_test.go @@ -0,0 +1,62 @@ +package slice + +import ( + "testing" +) + +func TestSplit_validateFilters(t *testing.T) { + tests := []struct { + name string + opts Options + wantErr bool + }{ + { + name: "prevent using allow skipping kind while using included kinds", + opts: Options{ + AllowEmptyKinds: true, + IncludedKinds: []string{"foo"}, + }, + wantErr: true, + }, + { + name: "prevent using allow skipping kind while using excluded kinds", + opts: Options{ + AllowEmptyKinds: true, + ExcludedKinds: []string{"foo"}, + }, + wantErr: true, + }, + { + name: "prevent using allow skipping name while using included names", + opts: Options{ + AllowEmptyNames: true, + IncludedNames: []string{"foo"}, + }, + wantErr: true, + }, + { + name: "prevent using allow skipping name while using excluded names", + opts: Options{ + AllowEmptyNames: true, + ExcludedNames: []string{"foo"}, + }, + wantErr: true, + }, + { + name: "cannot specify included and excluded kinds", + opts: Options{ + IncludedKinds: []string{"foo"}, + ExcludedKinds: []string{"bar"}, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Split{opts: tt.opts} + if err := s.validateFilters(); (err != nil) != tt.wantErr { + t.Errorf("Split.validateFilters() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +}