Skip to content

Commit

Permalink
feat(app): Implement include and exclude option - close #22. (#62)
Browse files Browse the repository at this point in the history
* feat(app): Implement include and exclude option - close #22.

* Update slice/validate.go

Co-authored-by: Patrick D'appollonio <[email protected]>

* Update slice/validate.go

Co-authored-by: Patrick D'appollonio <[email protected]>

* Update slice/validate.go

formating error

Co-authored-by: Patrick D'appollonio <[email protected]>

* fix(app): Compile regexp.

Co-authored-by: Patrick D'appollonio <[email protected]>
  • Loading branch information
douglasmakey and patrickdappollonio authored Oct 23, 2022
1 parent 92a4057 commit a8319d2
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 43 deletions.
4 changes: 4 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ var examples = []string{
"kubectl-slice -f foo.yaml -o ./ --exclude-kind Pod",
"kubectl-slice -f foo.yaml -o ./ --exclude-name *-svc",
"kubectl-slice -f foo.yaml --exclude-name *-svc --stdout",
"kubectl-slice -f foo.yaml --include Pod/* --stdout",
"kubectl-slice -f foo.yaml --exclude deployment/kube* --stdout",
}

func generateExamples([]string) string {
Expand Down Expand Up @@ -80,6 +82,8 @@ func root() *coral.Command {
rootCommand.Flags().StringSliceVar(&opts.ExcludedKinds, "exclude-kind", nil, "resource kind to exclude in the output (singular, case insensitive, glob supported)")
rootCommand.Flags().StringSliceVar(&opts.IncludedNames, "include-name", nil, "resource name to include in the output (singular, case insensitive, glob supported)")
rootCommand.Flags().StringSliceVar(&opts.ExcludedNames, "exclude-name", nil, "resource name to exclude in the output (singular, case insensitive, glob supported)")
rootCommand.Flags().StringSliceVar(&opts.Included, "include", nil, "resource name to include in the output (format <kind>/<name>, case insensitive, glob supported)")
rootCommand.Flags().StringSliceVar(&opts.Excluded, "exclude", nil, "resource name to exclude in the output (format <kind>/<name>, case insensitive, glob supported)")
rootCommand.Flags().BoolVarP(&opts.StrictKubernetes, "skip-non-k8s", "s", false, "if enabled, any YAMLs that don't contain at least an \"apiVersion\", \"kind\" and \"metadata.name\" will be excluded from the split")
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")
Expand Down
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
49 changes: 44 additions & 5 deletions slice/execute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import (

func TestSplit_processSingleFile(t *testing.T) {
tests := []struct {
name string
fields Options
fileInput string
wantErr bool
fileOutput *yamlFile
name string
fields Options
fileInput string
wantErr bool
wantFilterErr bool
fileOutput *yamlFile
}{
{
name: "basic pod",
Expand Down Expand Up @@ -42,6 +43,26 @@ metadata:
fileInput: `
apiVersion: v1
kind: Pod
metadata:
name: nginx-ingress
`,
fileOutput: &yamlFile{
filename: "pod-nginx-ingress.yaml",
meta: kubeObjectMeta{
APIVersion: "v1",
Kind: "Pod",
Name: "nginx-ingress",
},
},
},
{
name: "include Pod using include option",
fields: Options{
Included: []string{"Pod/*"},
},
fileInput: `
apiVersion: v1
kind: Pod
metadata:
name: nginx-ingress
`,
Expand Down Expand Up @@ -129,6 +150,20 @@ kind: "Namespace
`,
wantErr: true,
},
{
name: "invalid excluded",
fields: Options{
Excluded: []string{"Pod/Namespace/*"},
},
wantFilterErr: true,
},
{
name: "invalid included",
fields: Options{
Included: []string{"Pod"},
},
wantFilterErr: true,
},
}

for _, tt := range tests {
Expand All @@ -139,6 +174,10 @@ kind: "Namespace
template: template.Must(template.New("split").Funcs(templateFuncs).Parse(DefaultTemplateName)),
}

if err := s.validateFilters(); (err != nil) != tt.wantFilterErr {
t.Errorf("error = %v, wantErr %v", err, tt.wantFilterErr)
}

if err := s.processSingleFile([]byte(tt.fileInput)); (err != nil) != tt.wantErr {
t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
}
Expand Down
40 changes: 13 additions & 27 deletions slice/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,49 +49,35 @@ func (s *Split) parseYAMLManifest(contents []byte) (yamlFile, error) {

// Check before handling if we're about to filter resources
var (
hasKindIncluded = len(s.opts.IncludedKinds) > 0
hasKindExcluded = len(s.opts.ExcludedKinds) > 0
hasNameIncluded = len(s.opts.IncludedNames) > 0
hasNameExcluded = len(s.opts.ExcludedNames) > 0
hasIncluded = len(s.opts.Included) > 0
hasExcluded = len(s.opts.Excluded) > 0
)

s.log.Printf(
"Applying filters -> kindIncluded: %v; kindExcluded: %v; nameIncluded: %v; nameExcluded: %v",
s.opts.IncludedKinds, s.opts.ExcludedKinds, s.opts.IncludedNames, s.opts.ExcludedNames,
)
"Applying filters -> Included: %v; Excluded: %v", s.opts.Included, s.opts.Excluded)

s.log.Printf("Found K8s meta -> %#v", k8smeta)

// Check if we have a Kubernetes kind and we're requesting inclusion or exclusion
if k8smeta.Kind == "" && (hasKindIncluded || hasKindExcluded) {
if k8smeta.Kind == "" && (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 == "" && (hasNameIncluded || hasNameExcluded) {
if k8smeta.Name == "" && (hasIncluded || hasExcluded) {
return yamlFile{}, fmt.Errorf("unable to find Kubernetes \"metadata.name\" field in file number %d", s.fileCount)
}

// We need to check if the file is skipped by kind
if hasKindIncluded || hasKindExcluded || hasNameIncluded || hasNameExcluded {
// If we're working with including only specific kinds, then filter by it
if hasKindIncluded && !inSliceIgnoreCaseGlob(s.opts.IncludedKinds, k8smeta.Kind) {
return yamlFile{}, &skipErr{kind: "kind", name: k8smeta.Kind}
}

// Otherwise exclude kinds based on the parameter received
if hasKindExcluded && inSliceIgnoreCaseGlob(s.opts.ExcludedKinds, k8smeta.Kind) {
return yamlFile{}, &skipErr{kind: "kind", name: k8smeta.Kind}
}

// If we're working with including only specific names, then filter by it
if hasNameIncluded && !inSliceIgnoreCaseGlob(s.opts.IncludedNames, k8smeta.Name) {
return yamlFile{}, &skipErr{kind: "name", name: k8smeta.Name}
// We need to check if the file should be skipped
if hasExcluded || hasIncluded {
// If we're working with including only specific resources, then filter by them
if hasIncluded && !inSliceIgnoreCaseGlob(s.opts.Included, fmt.Sprintf("%s/%s", k8smeta.Kind, k8smeta.Name)) {
return yamlFile{}, &skipErr{kind: "kind/name", name: fmt.Sprintf("%s/%s", k8smeta.Kind, k8smeta.Name)}
}

// Otherwise exclude names based on the parameter received
if hasNameExcluded && inSliceIgnoreCaseGlob(s.opts.ExcludedNames, k8smeta.Name) {
return yamlFile{}, &skipErr{kind: "name", name: k8smeta.Name}
// Otherwise exclude resources based on the parameter received
if hasExcluded && inSliceIgnoreCaseGlob(s.opts.Excluded, fmt.Sprintf("%s/%s", k8smeta.Kind, k8smeta.Name)) {
return yamlFile{}, &skipErr{kind: "kind/name", name: fmt.Sprintf("%s/%s", k8smeta.Kind, k8smeta.Name)}
}
}

Expand Down
2 changes: 2 additions & 0 deletions slice/split.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ type Options struct {
ExcludedKinds []string
IncludedNames []string
ExcludedNames []string
Included []string
Excluded []string
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
Expand Down
56 changes: 49 additions & 7 deletions slice/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@ package slice

import (
"fmt"
"regexp"
)

func (s *Split) init() error {
if len(s.opts.IncludedKinds) > 0 && len(s.opts.ExcludedKinds) > 0 {
return fmt.Errorf("cannot specify both included and excluded kinds")
}
var regKN = regexp.MustCompile(`^[^/]+/[^/]+$`)

if len(s.opts.IncludedNames) > 0 && len(s.opts.ExcludedNames) > 0 {
return fmt.Errorf("cannot specify both included and excluded names")
}

func (s *Split) init() error {
s.log.Printf("Loading file %s", s.opts.InputFile)
buf, err := loadfile(s.opts.InputFile)
if err != nil {
Expand All @@ -35,5 +31,51 @@ func (s *Split) init() error {
return err
}

return s.validateFilters()
}

func (s *Split) validateFilters() error {
if len(s.opts.IncludedKinds) > 0 && len(s.opts.ExcludedKinds) > 0 {
return fmt.Errorf("cannot specify both included and excluded kinds")
}

if len(s.opts.IncludedNames) > 0 && len(s.opts.ExcludedNames) > 0 {
return fmt.Errorf("cannot specify both included and excluded names")
}

if len(s.opts.Included) > 0 && len(s.opts.Excluded) > 0 {
return fmt.Errorf("cannot specify both included and excluded")
}

// Merge all filters into excluded and included.
for _, v := range s.opts.IncludedKinds {
s.opts.Included = append(s.opts.Included, fmt.Sprintf("%s/*", v))
}

for _, v := range s.opts.ExcludedKinds {
s.opts.Excluded = append(s.opts.Excluded, fmt.Sprintf("%s/*", v))
}

for _, v := range s.opts.IncludedNames {
s.opts.Included = append(s.opts.Included, fmt.Sprintf("*/%s", v))
}

for _, v := range s.opts.ExcludedNames {
s.opts.Excluded = append(s.opts.Excluded, fmt.Sprintf("*/%s", v))
}

// Validate included and excluded filters.
for _, included := range s.opts.Included {
if !regKN.MatchString(included) {
return fmt.Errorf("invalid included pattern %q should be <kind>/<name>", included)
}
}

for _, excluded := range s.opts.Excluded {
if !regKN.MatchString(excluded) {
return fmt.Errorf("invalid excluded pattern %q should be <kind>/<name>", excluded)
}
}

return nil
}

0 comments on commit a8319d2

Please sign in to comment.