diff --git a/pkg/cmd/apply/apply.go b/pkg/cmd/apply/apply.go index fe3961785..56c689b68 100644 --- a/pkg/cmd/apply/apply.go +++ b/pkg/cmd/apply/apply.go @@ -21,6 +21,8 @@ import ( "io" "net/http" + "k8s.io/kubectl/pkg/util/prune" + "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -60,7 +62,7 @@ type ApplyFlags struct { FieldManager string Selector string Prune bool - PruneResources []pruneResource + PruneResources []prune.Resource All bool Overwrite bool OpenAPIPatch bool @@ -85,7 +87,7 @@ type ApplyOptions struct { DryRunStrategy cmdutil.DryRunStrategy DryRunVerifier *resource.DryRunVerifier Prune bool - PruneResources []pruneResource + PruneResources []prune.Resource cmdBaseName string All bool Overwrite bool @@ -278,7 +280,7 @@ func (flags *ApplyFlags) ToOptions(cmd *cobra.Command, baseName string, args []s } if flags.Prune { - flags.PruneResources, err = parsePruneResources(mapper, flags.PruneWhitelist) + flags.PruneResources, err = prune.ParseResources(mapper, flags.PruneWhitelist) if err != nil { return nil, err } diff --git a/pkg/cmd/apply/prune.go b/pkg/cmd/apply/prune.go index 1b65a3eb9..609f45e5a 100644 --- a/pkg/cmd/apply/prune.go +++ b/pkg/cmd/apply/prune.go @@ -20,12 +20,12 @@ import ( "context" "fmt" "io" - "strings" + + "k8s.io/kubectl/pkg/util/prune" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/cli-runtime/pkg/printers" "k8s.io/client-go/dynamic" @@ -71,7 +71,7 @@ func newPruner(o *ApplyOptions) pruner { func (p *pruner) pruneAll(o *ApplyOptions) error { - namespacedRESTMappings, nonNamespacedRESTMappings, err := getRESTMappings(o.Mapper, &(o.PruneResources)) + namespacedRESTMappings, nonNamespacedRESTMappings, err := prune.GetRESTMappings(o.Mapper, &(o.PruneResources)) if err != nil { return fmt.Errorf("error retrieving RESTMappings to prune: %v", err) } @@ -158,83 +158,3 @@ func asDeleteOptions(cascadingStrategy metav1.DeletionPropagation, gracePeriod i options.PropagationPolicy = &cascadingStrategy return options } - -type pruneResource struct { - group string - version string - kind string - namespaced bool -} - -func (pr pruneResource) String() string { - return fmt.Sprintf("%v/%v, Kind=%v, Namespaced=%v", pr.group, pr.version, pr.kind, pr.namespaced) -} - -func getRESTMappings(mapper meta.RESTMapper, pruneResources *[]pruneResource) (namespaced, nonNamespaced []*meta.RESTMapping, err error) { - if len(*pruneResources) == 0 { - // default allowlist - *pruneResources = []pruneResource{ - {"", "v1", "ConfigMap", true}, - {"", "v1", "Endpoints", true}, - {"", "v1", "Namespace", false}, - {"", "v1", "PersistentVolumeClaim", true}, - {"", "v1", "PersistentVolume", false}, - {"", "v1", "Pod", true}, - {"", "v1", "ReplicationController", true}, - {"", "v1", "Secret", true}, - {"", "v1", "Service", true}, - {"batch", "v1", "Job", true}, - {"batch", "v1", "CronJob", true}, - {"networking.k8s.io", "v1", "Ingress", true}, - {"apps", "v1", "DaemonSet", true}, - {"apps", "v1", "Deployment", true}, - {"apps", "v1", "ReplicaSet", true}, - {"apps", "v1", "StatefulSet", true}, - } - } - - for _, resource := range *pruneResources { - addedMapping, err := mapper.RESTMapping(schema.GroupKind{Group: resource.group, Kind: resource.kind}, resource.version) - if err != nil { - return nil, nil, fmt.Errorf("invalid resource %v: %v", resource, err) - } - if resource.namespaced { - namespaced = append(namespaced, addedMapping) - } else { - nonNamespaced = append(nonNamespaced, addedMapping) - } - } - - return namespaced, nonNamespaced, nil -} - -func parsePruneResources(mapper meta.RESTMapper, gvks []string) ([]pruneResource, error) { - pruneResources := []pruneResource{} - for _, groupVersionKind := range gvks { - gvk := strings.Split(groupVersionKind, "/") - if len(gvk) != 3 { - return nil, fmt.Errorf("invalid GroupVersionKind format: %v, please follow ", groupVersionKind) - } - - if gvk[0] == "core" { - gvk[0] = "" - } - mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk[0], Kind: gvk[2]}, gvk[1]) - if err != nil { - return pruneResources, err - } - var namespaced bool - namespaceScope := mapping.Scope.Name() - switch namespaceScope { - case meta.RESTScopeNameNamespace: - namespaced = true - case meta.RESTScopeNameRoot: - namespaced = false - default: - return pruneResources, fmt.Errorf("Unknown namespace scope: %q", namespaceScope) - } - - pruneResources = append(pruneResources, pruneResource{gvk[0], gvk[1], gvk[2], namespaced}) - } - return pruneResources, nil -} diff --git a/pkg/cmd/diff/diff.go b/pkg/cmd/diff/diff.go index 9da6888f1..90e25042e 100644 --- a/pkg/cmd/diff/diff.go +++ b/pkg/cmd/diff/diff.go @@ -25,8 +25,7 @@ import ( "regexp" "strings" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/cli-runtime/pkg/printers" + "k8s.io/kubectl/pkg/util/prune" "github.com/jonboulle/clockwork" "github.com/spf13/cobra" @@ -36,6 +35,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/discovery" @@ -110,25 +110,18 @@ type DiffOptions struct { FieldManager string ForceConflicts bool - Selector string - OpenAPISchema openapi.Resources - DiscoveryClient discovery.DiscoveryInterface - DynamicClient dynamic.Interface - DryRunVerifier *resource.DryRunVerifier - CmdNamespace string - EnforceNamespace bool - Builder *resource.Builder - Diff *DiffProgram - Mapper meta.RESTMapper - Prune bool - PruneResources []pruneResource - VisitedUids sets.String - VisitedNamespaces sets.String - ToPrinter func(string) (printers.ResourcePrinter, error) - PrintFlags *genericclioptions.PrintFlags - All bool - PruneWhitelist []string - genericclioptions.IOStreams + Selector string + OpenAPISchema openapi.Resources + DiscoveryClient discovery.DiscoveryInterface + DynamicClient dynamic.Interface + DryRunVerifier *resource.DryRunVerifier + CmdNamespace string + EnforceNamespace bool + Builder *resource.Builder + Diff *DiffProgram + Prune bool + PruneWhitelist []string + pruner *pruner } func validateArgs(cmd *cobra.Command, args []string) error { @@ -144,10 +137,10 @@ func NewDiffOptions(ioStreams genericclioptions.IOStreams) *DiffOptions { Exec: exec.New(), IOStreams: ioStreams, }, - VisitedUids: sets.NewString(), - VisitedNamespaces: sets.NewString(), - PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), - IOStreams: ioStreams, + pruner: &pruner{ + visitedUids: sets.NewString(), + visitedNamespaces: sets.NewString(), + }, } } @@ -162,7 +155,6 @@ func NewCmdDiff(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckDiffErr(options.Complete(f, cmd)) cmdutil.CheckDiffErr(validateArgs(cmd, args)) - cmdutil.CheckErr(validatePruneAll(options.Prune, options.All, options.Selector)) // `kubectl diff` propagates the error code from // diff or `KUBECTL_EXTERNAL_DIFF`. Also, we // don't want to print an error if diff returns @@ -189,8 +181,7 @@ func NewCmdDiff(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C usage := "contains the configuration to diff" cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") cmd.Flags().StringArrayVar(&options.PruneWhitelist, "prune-whitelist", options.PruneWhitelist, "Overwrite the default whitelist with for --prune") - cmd.Flags().BoolVar(&options.Prune, "prune", options.Prune, "Automatically diff for possibly will be deleted resource objects, Should be used with either -l or --all.") - cmd.Flags().BoolVar(&options.All, "all", options.All, "Select all resources in the namespace of the specified resource types.") + cmd.Flags().BoolVar(&options.Prune, "prune", options.Prune, "Automatically diff for possibly will be deleted resource objects, Should be used with either -l or --prune-all.") cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) cmdutil.AddServerSideApplyFlags(cmd) cmdutil.AddFieldManagerFlagVar(cmd, &options.FieldManager, apply.FieldManagerClientSideApply) @@ -198,16 +189,6 @@ func NewCmdDiff(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C return cmd } -func validatePruneAll(prune, all bool, selector string) error { - if all && len(selector) > 0 { - return fmt.Errorf("cannot set --all and --selector at the same time") - } - if prune && !all && selector == "" { - return fmt.Errorf("all resources selected for prune without explicitly passing --all. To prune all resources, pass the --all flag. If you did not mean to prune all resources, specify a label selector") - } - return nil -} - // DiffProgram finds and run the diff program. The value of // KUBECTL_EXTERNAL_DIFF environment variable will be used a diff // program. By default, `diff(1)` will be used. @@ -649,12 +630,6 @@ func (o *DiffOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { return fmt.Errorf("--force-conflicts only works with --server-side") } - o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) { - o.PrintFlags.NamePrintFlags.Operation = operation - cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, cmdutil.DryRunServer) - return o.PrintFlags.ToPrinter() - } - if !o.ServerSideApply { o.OpenAPISchema, err = f.OpenAPISchema() if err != nil { @@ -680,12 +655,13 @@ func (o *DiffOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { } if o.Prune { - o.Mapper, err = f.ToRESTMapper() + o.pruner.dynamicClient = o.DynamicClient + o.pruner.mapper, err = f.ToRESTMapper() if err != nil { return err } - o.PruneResources, err = parsePruneResources(o.Mapper, o.PruneWhitelist) + o.pruner.resources, err = prune.ParseResources(o.pruner.mapper, o.PruneWhitelist) if err != nil { return err } @@ -756,9 +732,9 @@ func (o *DiffOptions) Run() error { IOStreams: o.Diff.IOStreams, } + o.markVisited(info) + err = differ.Diff(obj, printer) - o.MarkNamespaceVisited(info) - o.MarkObjectVisited(info) if !isConflict(err) { break } @@ -770,8 +746,18 @@ func (o *DiffOptions) Run() error { }) if o.Prune { - prune := newPruner(o) - prune.pruneAll(o) + prunedObjs, err := o.pruner.pruneAll() + if err != nil { + klog.Warningf("pruning failed and could not be evaluated err: %v", err) + } + + // Print pruned objects into old file and thus, diff + // command will show them as pruned. + for _, p := range prunedObjs { + if err := differ.From.Print(o.pruner.GetObjectName(p), p, printer); err != nil { + return err + } + } } if err != nil { @@ -781,21 +767,14 @@ func (o *DiffOptions) Run() error { return differ.Run(o.Diff) } -// MarkObjectVisited keeps track of UIDs of the applied -// objects. Used for pruning. -func (o *DiffOptions) MarkObjectVisited(info *resource.Info) error { +func (o *DiffOptions) markVisited(info *resource.Info) { + if info.Namespaced() { + o.pruner.visitedNamespaces.Insert(info.Namespace) + } + metadata, err := meta.Accessor(info.Object) if err != nil { - return err - } - o.VisitedUids.Insert(string(metadata.GetUID())) - return nil -} - -// MarkNamespaceVisited keeps track of which namespaces the applied -// objects belong to. Used for pruning. -func (o *DiffOptions) MarkNamespaceVisited(info *resource.Info) { - if info.Namespaced() { - o.VisitedNamespaces.Insert(info.Namespace) + return } + o.pruner.visitedUids.Insert(string(metadata.GetUID())) } diff --git a/pkg/cmd/diff/prune.go b/pkg/cmd/diff/prune.go index 990864290..be39b8493 100644 --- a/pkg/cmd/diff/prune.go +++ b/pkg/cmd/diff/prune.go @@ -19,15 +19,17 @@ package diff import ( "context" "fmt" - "io" - "strings" + + "k8s.io/apimachinery/pkg/util/uuid" + + "k8s.io/apimachinery/pkg/runtime" + + "k8s.io/kubectl/pkg/util/prune" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/cli-runtime/pkg/printers" "k8s.io/client-go/dynamic" ) @@ -38,199 +40,73 @@ type pruner struct { visitedUids sets.String visitedNamespaces sets.String labelSelector string - fieldSelector string - - cascadingStrategy metav1.DeletionPropagation - gracePeriod int - - toPrinter func(string) (printers.ResourcePrinter, error) - - out io.Writer + resources []prune.Resource } -func newPruner(o *DiffOptions) pruner { - return pruner{ - mapper: o.Mapper, - dynamicClient: o.DynamicClient, - - labelSelector: o.Selector, - visitedUids: o.VisitedUids, - visitedNamespaces: o.VisitedNamespaces, - - toPrinter: o.ToPrinter, - - cascadingStrategy: metav1.DeletePropagationBackground, - gracePeriod: -1, - - out: o.ErrOut, - } -} - -func (p *pruner) pruneAll(o *DiffOptions) error { - - namespacedRESTMappings, nonNamespacedRESTMappings, err := getRESTMappings(o.Mapper, &(o.PruneResources)) +func (p *pruner) pruneAll() ([]runtime.Object, error) { + var allPruned []runtime.Object + namespacedRESTMappings, nonNamespacedRESTMappings, err := prune.GetRESTMappings(p.mapper, &(p.resources)) if err != nil { - return fmt.Errorf("error retrieving RESTMappings to prune: %v", err) + return allPruned, fmt.Errorf("error retrieving RESTMappings to prune: %v", err) } for n := range p.visitedNamespaces { for _, m := range namespacedRESTMappings { - if err := p.prune(n, m); err != nil { - return fmt.Errorf("error pruning namespaced object %v: %v", m.GroupVersionKind, err) + if pobjs, err := p.prune(n, m); err != nil { + return pobjs, fmt.Errorf("error pruning namespaced object %v: %v", m.GroupVersionKind, err) + } else { + allPruned = append(allPruned, pobjs...) } } } for _, m := range nonNamespacedRESTMappings { - if err := p.prune(metav1.NamespaceNone, m); err != nil { - return fmt.Errorf("error pruning nonNamespaced object %v: %v", m.GroupVersionKind, err) + if pobjs, err := p.prune(metav1.NamespaceNone, m); err != nil { + return allPruned, fmt.Errorf("error pruning nonNamespaced object %v: %v", m.GroupVersionKind, err) + } else { + allPruned = append(allPruned, pobjs...) } } - return nil + return allPruned, nil } -func (p *pruner) prune(namespace string, mapping *meta.RESTMapping) error { +func (p *pruner) prune(namespace string, mapping *meta.RESTMapping) ([]runtime.Object, error) { objList, err := p.dynamicClient.Resource(mapping.Resource). Namespace(namespace). List(context.TODO(), metav1.ListOptions{ LabelSelector: p.labelSelector, - FieldSelector: p.fieldSelector, }) if err != nil { - return err + return nil, err } objs, err := meta.ExtractList(objList) if err != nil { - return err + return nil, err } + var pobjs []runtime.Object for _, obj := range objs { metadata, err := meta.Accessor(obj) if err != nil { - return err + return pobjs, err } annots := metadata.GetAnnotations() if _, ok := annots[corev1.LastAppliedConfigAnnotation]; !ok { - // don't prune resources not created with apply continue } uid := metadata.GetUID() if p.visitedUids.Has(string(uid)) { continue } - name := metadata.GetName() - if err := p.delete(namespace, name, mapping); err != nil { - return err - } - printer, err := p.toPrinter("pruned") - if err != nil { - return err - } - - printer.PrintObj(obj, p.out) + pobjs = append(pobjs, obj) } - return nil + return pobjs, nil } -func (p *pruner) delete(namespace, name string, mapping *meta.RESTMapping) error { - return runDelete(namespace, name, mapping, p.dynamicClient, p.cascadingStrategy, p.gracePeriod, true) -} - -func runDelete(namespace, name string, mapping *meta.RESTMapping, c dynamic.Interface, cascadingStrategy metav1.DeletionPropagation, gracePeriod int, serverDryRun bool) error { - options := asDeleteOptions(cascadingStrategy, gracePeriod) - if serverDryRun { - options.DryRun = []string{metav1.DryRunAll} - } - return c.Resource(mapping.Resource).Namespace(namespace).Delete(context.TODO(), name, options) -} - -func asDeleteOptions(cascadingStrategy metav1.DeletionPropagation, gracePeriod int) metav1.DeleteOptions { - options := metav1.DeleteOptions{} - if gracePeriod >= 0 { - options = *metav1.NewDeleteOptions(int64(gracePeriod)) - } - options.PropagationPolicy = &cascadingStrategy - return options -} - -type pruneResource struct { - group string - version string - kind string - namespaced bool -} - -func (pr pruneResource) String() string { - return fmt.Sprintf("%v/%v, Kind=%v, Namespaced=%v", pr.group, pr.version, pr.kind, pr.namespaced) -} - -func getRESTMappings(mapper meta.RESTMapper, pruneResources *[]pruneResource) (namespaced, nonNamespaced []*meta.RESTMapping, err error) { - if len(*pruneResources) == 0 { - // default allowlist - *pruneResources = []pruneResource{ - {"", "v1", "ConfigMap", true}, - {"", "v1", "Endpoints", true}, - {"", "v1", "Namespace", false}, - {"", "v1", "PersistentVolumeClaim", true}, - {"", "v1", "PersistentVolume", false}, - {"", "v1", "Pod", true}, - {"", "v1", "ReplicationController", true}, - {"", "v1", "Secret", true}, - {"", "v1", "Service", true}, - {"batch", "v1", "Job", true}, - {"batch", "v1", "CronJob", true}, - {"networking.k8s.io", "v1", "Ingress", true}, - {"apps", "v1", "DaemonSet", true}, - {"apps", "v1", "Deployment", true}, - {"apps", "v1", "ReplicaSet", true}, - {"apps", "v1", "StatefulSet", true}, - } - } - - for _, resource := range *pruneResources { - addedMapping, err := mapper.RESTMapping(schema.GroupKind{Group: resource.group, Kind: resource.kind}, resource.version) - if err != nil { - return nil, nil, fmt.Errorf("invalid resource %v: %v", resource, err) - } - if resource.namespaced { - namespaced = append(namespaced, addedMapping) - } else { - nonNamespaced = append(nonNamespaced, addedMapping) - } - } - - return namespaced, nonNamespaced, nil -} - -func parsePruneResources(mapper meta.RESTMapper, gvks []string) ([]pruneResource, error) { - pruneResources := []pruneResource{} - for _, groupVersionKind := range gvks { - gvk := strings.Split(groupVersionKind, "/") - if len(gvk) != 3 { - return nil, fmt.Errorf("invalid GroupVersionKind format: %v, please follow ", groupVersionKind) - } - - if gvk[0] == "core" { - gvk[0] = "" - } - mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk[0], Kind: gvk[2]}, gvk[1]) - if err != nil { - return pruneResources, err - } - var namespaced bool - namespaceScope := mapping.Scope.Name() - switch namespaceScope { - case meta.RESTScopeNameNamespace: - namespaced = true - case meta.RESTScopeNameRoot: - namespaced = false - default: - return pruneResources, fmt.Errorf("Unknown namespace scope: %q", namespaceScope) - } - - pruneResources = append(pruneResources, pruneResource{gvk[0], gvk[1], gvk[2], namespaced}) - } - return pruneResources, nil +func (p *pruner) GetObjectName(obj runtime.Object) string { + // Not compare anything, it is safe to assign random + // object name. + return string(uuid.NewUUID()) } diff --git a/pkg/util/prune/prune.go b/pkg/util/prune/prune.go new file mode 100644 index 000000000..a05abdc85 --- /dev/null +++ b/pkg/util/prune/prune.go @@ -0,0 +1,89 @@ +package prune + +import ( + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type Resource struct { + group string + version string + kind string + namespaced bool +} + +func (pr Resource) String() string { + return fmt.Sprintf("%v/%v, Kind=%v, Namespaced=%v", pr.group, pr.version, pr.kind, pr.namespaced) +} + +func GetRESTMappings(mapper meta.RESTMapper, pruneResources *[]Resource) (namespaced, nonNamespaced []*meta.RESTMapping, err error) { + if len(*pruneResources) == 0 { + // default allowlist + *pruneResources = []Resource{ + {"", "v1", "ConfigMap", true}, + {"", "v1", "Endpoints", true}, + {"", "v1", "Namespace", false}, + {"", "v1", "PersistentVolumeClaim", true}, + {"", "v1", "PersistentVolume", false}, + {"", "v1", "Pod", true}, + {"", "v1", "ReplicationController", true}, + {"", "v1", "Secret", true}, + {"", "v1", "Service", true}, + {"batch", "v1", "Job", true}, + {"batch", "v1", "CronJob", true}, + {"networking.k8s.io", "v1", "Ingress", true}, + {"apps", "v1", "DaemonSet", true}, + {"apps", "v1", "Deployment", true}, + {"apps", "v1", "ReplicaSet", true}, + {"apps", "v1", "StatefulSet", true}, + } + } + + for _, resource := range *pruneResources { + addedMapping, err := mapper.RESTMapping(schema.GroupKind{Group: resource.group, Kind: resource.kind}, resource.version) + if err != nil { + return nil, nil, fmt.Errorf("invalid resource %v: %v", resource, err) + } + if resource.namespaced { + namespaced = append(namespaced, addedMapping) + } else { + nonNamespaced = append(nonNamespaced, addedMapping) + } + } + + return namespaced, nonNamespaced, nil +} + +func ParseResources(mapper meta.RESTMapper, gvks []string) ([]Resource, error) { + pruneResources := []Resource{} + for _, groupVersionKind := range gvks { + gvk := strings.Split(groupVersionKind, "/") + if len(gvk) != 3 { + return nil, fmt.Errorf("invalid GroupVersionKind format: %v, please follow ", groupVersionKind) + } + + if gvk[0] == "core" { + gvk[0] = "" + } + mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk[0], Kind: gvk[2]}, gvk[1]) + if err != nil { + return pruneResources, err + } + var namespaced bool + namespaceScope := mapping.Scope.Name() + switch namespaceScope { + case meta.RESTScopeNameNamespace: + namespaced = true + case meta.RESTScopeNameRoot: + namespaced = false + default: + return pruneResources, fmt.Errorf("Unknown namespace scope: %q", namespaceScope) + } + + pruneResources = append(pruneResources, Resource{gvk[0], gvk[1], gvk[2], namespaced}) + } + return pruneResources, nil +} diff --git a/pkg/cmd/diff/prune_test.go b/pkg/util/prune/prune_test.go similarity index 64% rename from pkg/cmd/diff/prune_test.go rename to pkg/util/prune/prune_test.go index b6a59c95c..54f5d4c76 100644 --- a/pkg/cmd/diff/prune_test.go +++ b/pkg/util/prune/prune_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package diff +package prune import ( "testing" @@ -46,11 +46,38 @@ func (m *testRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (* }, nil } +func TestGetRESTMappings(t *testing.T) { + tests := []struct { + mapper *testRESTMapper + pr *[]Resource + expectedns int + expectednns int + expectederr error + }{ + { + mapper: &testRESTMapper{}, + pr: &[]Resource{}, + expectedns: 14, + expectednns: 2, + expectederr: nil, + }, + } + + for _, tc := range tests { + actualns, actualnns, actualerr := GetRESTMappings(tc.mapper, tc.pr) + if tc.expectederr != nil { + assert.NotEmptyf(t, actualerr, "getRESTMappings error expected but not fired") + } + assert.Equal(t, len(actualns), tc.expectedns, "getRESTMappings failed expected number namespaced %d actual %d", tc.expectedns, len(actualns)) + assert.Equal(t, len(actualnns), tc.expectednns, "getRESTMappings failed expected number nonnamespaced %d actual %d", tc.expectednns, len(actualnns)) + } +} + func TestParsePruneResources(t *testing.T) { tests := []struct { mapper *testRESTMapper gvks []string - expected []pruneResource + expected []Resource err bool }{ { @@ -58,7 +85,7 @@ func TestParsePruneResources(t *testing.T) { scope: meta.RESTScopeNamespace, }, gvks: nil, - expected: []pruneResource{}, + expected: []Resource{}, err: false, }, { @@ -66,7 +93,7 @@ func TestParsePruneResources(t *testing.T) { scope: meta.RESTScopeNamespace, }, gvks: []string{"group/kind/version/test"}, - expected: []pruneResource{}, + expected: []Resource{}, err: true, }, { @@ -74,7 +101,7 @@ func TestParsePruneResources(t *testing.T) { scope: meta.RESTScopeNamespace, }, gvks: []string{"group/kind/version"}, - expected: []pruneResource{{group: "group", version: "kind", kind: "version", namespaced: true}}, + expected: []Resource{{group: "group", version: "kind", kind: "version", namespaced: true}}, err: false, }, { @@ -82,13 +109,13 @@ func TestParsePruneResources(t *testing.T) { scope: meta.RESTScopeRoot, }, gvks: []string{"group/kind/version"}, - expected: []pruneResource{{group: "group", version: "kind", kind: "version", namespaced: false}}, + expected: []Resource{{group: "group", version: "kind", kind: "version", namespaced: false}}, err: false, }, } for _, tc := range tests { - actual, err := parsePruneResources(tc.mapper, tc.gvks) + actual, err := ParseResources(tc.mapper, tc.gvks) if tc.err { assert.NotEmptyf(t, err, "parsePruneResources error expected but not fired") } else {