mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 18:28:13 +00:00 
			
		
		
		
	Merge pull request #105164 from ardaguclu/kubectl-diff-prune
Introduce new prune parameter into diff command
This commit is contained in:
		| @@ -22,6 +22,7 @@ import ( | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
|  | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| 	"k8s.io/apimachinery/pkg/api/meta" | ||||
| @@ -42,6 +43,7 @@ import ( | ||||
| 	"k8s.io/kubectl/pkg/util" | ||||
| 	"k8s.io/kubectl/pkg/util/i18n" | ||||
| 	"k8s.io/kubectl/pkg/util/openapi" | ||||
| 	"k8s.io/kubectl/pkg/util/prune" | ||||
| 	"k8s.io/kubectl/pkg/util/templates" | ||||
| 	"k8s.io/kubectl/pkg/validation" | ||||
| ) | ||||
| @@ -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 | ||||
| 		} | ||||
|   | ||||
| @@ -20,16 +20,15 @@ import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"strings" | ||||
|  | ||||
| 	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" | ||||
| 	cmdutil "k8s.io/kubectl/pkg/cmd/util" | ||||
| 	"k8s.io/kubectl/pkg/util/prune" | ||||
| ) | ||||
|  | ||||
| type pruner struct { | ||||
| @@ -71,7 +70,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 +157,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 <group/version/kind>", 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 | ||||
| } | ||||
|   | ||||
| @@ -44,6 +44,7 @@ import ( | ||||
| 	"k8s.io/kubectl/pkg/util" | ||||
| 	"k8s.io/kubectl/pkg/util/i18n" | ||||
| 	"k8s.io/kubectl/pkg/util/openapi" | ||||
| 	"k8s.io/kubectl/pkg/util/prune" | ||||
| 	"k8s.io/kubectl/pkg/util/templates" | ||||
| 	"k8s.io/utils/exec" | ||||
| 	"sigs.k8s.io/yaml" | ||||
| @@ -116,6 +117,7 @@ type DiffOptions struct { | ||||
| 	EnforceNamespace bool | ||||
| 	Builder          *resource.Builder | ||||
| 	Diff             *DiffProgram | ||||
| 	pruner           *pruner | ||||
| } | ||||
|  | ||||
| func validateArgs(cmd *cobra.Command, args []string) error { | ||||
| @@ -170,6 +172,8 @@ 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().StringArray("prune-allowlist", []string{}, "Overwrite the default whitelist with <group/version/kind> for --prune") | ||||
| 	cmd.Flags().Bool("prune", false, "Include resources that would be deleted by pruning. Can be used with -l and default shows all resources would be pruned") | ||||
| 	cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) | ||||
| 	cmdutil.AddServerSideApplyFlags(cmd) | ||||
| 	cmdutil.AddFieldManagerFlagVar(cmd, &options.FieldManager, apply.FieldManagerClientSideApply) | ||||
| @@ -642,6 +646,19 @@ func (o *DiffOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if cmdutil.GetFlagBool(cmd, "prune") { | ||||
| 		mapper, err := f.ToRESTMapper() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		resources, err := prune.ParseResources(mapper, cmdutil.GetFlagStringArray(cmd, "prune-allowlist")) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		o.pruner = newPruner(o.DynamicClient, mapper, resources) | ||||
| 	} | ||||
|  | ||||
| 	o.Builder = f.NewBuilder() | ||||
| 	return nil | ||||
| } | ||||
| @@ -707,6 +724,10 @@ func (o *DiffOptions) Run() error { | ||||
| 				IOStreams:       o.Diff.IOStreams, | ||||
| 			} | ||||
|  | ||||
| 			if o.pruner != nil { | ||||
| 				o.pruner.MarkVisited(info) | ||||
| 			} | ||||
|  | ||||
| 			err = differ.Diff(obj, printer) | ||||
| 			if !isConflict(err) { | ||||
| 				break | ||||
| @@ -717,9 +738,52 @@ func (o *DiffOptions) Run() error { | ||||
|  | ||||
| 		return err | ||||
| 	}) | ||||
|  | ||||
| 	if o.pruner != nil { | ||||
| 		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 { | ||||
| 			name, err := getObjectName(p) | ||||
| 			if err != nil { | ||||
| 				klog.Warningf("pruning failed and object name could not be retrieved: %v", err) | ||||
| 				continue | ||||
| 			} | ||||
| 			if err := differ.From.Print(name, p, printer); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return differ.Run(o.Diff) | ||||
| } | ||||
|  | ||||
| func getObjectName(obj runtime.Object) (string, error) { | ||||
| 	gvk := obj.GetObjectKind().GroupVersionKind() | ||||
| 	metadata, err := meta.Accessor(obj) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	name := metadata.GetName() | ||||
| 	ns := metadata.GetNamespace() | ||||
|  | ||||
| 	group := "" | ||||
| 	if gvk.Group != "" { | ||||
| 		group = fmt.Sprintf("%v.", gvk.Group) | ||||
| 	} | ||||
| 	return group + fmt.Sprintf( | ||||
| 		"%v.%v.%v.%v", | ||||
| 		gvk.Version, | ||||
| 		gvk.Kind, | ||||
| 		ns, | ||||
| 		name, | ||||
| 	), nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										126
									
								
								staging/src/k8s.io/kubectl/pkg/cmd/diff/prune.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								staging/src/k8s.io/kubectl/pkg/cmd/diff/prune.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| /* | ||||
| Copyright 2021 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package diff | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
|  | ||||
| 	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" | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/cli-runtime/pkg/resource" | ||||
| 	"k8s.io/client-go/dynamic" | ||||
| 	"k8s.io/kubectl/pkg/util/prune" | ||||
| ) | ||||
|  | ||||
| type pruner struct { | ||||
| 	mapper        meta.RESTMapper | ||||
| 	dynamicClient dynamic.Interface | ||||
|  | ||||
| 	visitedUids       sets.String | ||||
| 	visitedNamespaces sets.String | ||||
| 	labelSelector     string | ||||
| 	resources         []prune.Resource | ||||
| } | ||||
|  | ||||
| func newPruner(dc dynamic.Interface, m meta.RESTMapper, r []prune.Resource) *pruner { | ||||
| 	return &pruner{ | ||||
| 		visitedUids:       sets.NewString(), | ||||
| 		visitedNamespaces: sets.NewString(), | ||||
| 		dynamicClient:     dc, | ||||
| 		mapper:            m, | ||||
| 		resources:         r, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *pruner) pruneAll() ([]runtime.Object, error) { | ||||
| 	var allPruned []runtime.Object | ||||
| 	namespacedRESTMappings, nonNamespacedRESTMappings, err := prune.GetRESTMappings(p.mapper, p.resources) | ||||
| 	if err != nil { | ||||
| 		return allPruned, fmt.Errorf("error retrieving RESTMappings to prune: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	for n := range p.visitedNamespaces { | ||||
| 		for _, m := range namespacedRESTMappings { | ||||
| 			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 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 allPruned, nil | ||||
| } | ||||
|  | ||||
| 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, | ||||
| 		}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	objs, err := meta.ExtractList(objList) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var pobjs []runtime.Object | ||||
| 	for _, obj := range objs { | ||||
| 		metadata, err := meta.Accessor(obj) | ||||
| 		if err != nil { | ||||
| 			return pobjs, err | ||||
| 		} | ||||
| 		annots := metadata.GetAnnotations() | ||||
| 		if _, ok := annots[corev1.LastAppliedConfigAnnotation]; !ok { | ||||
| 			continue | ||||
| 		} | ||||
| 		uid := metadata.GetUID() | ||||
| 		if p.visitedUids.Has(string(uid)) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		pobjs = append(pobjs, obj) | ||||
| 	} | ||||
| 	return pobjs, nil | ||||
| } | ||||
|  | ||||
| // MarkVisited marks visited namespaces and uids | ||||
| func (p *pruner) MarkVisited(info *resource.Info) { | ||||
| 	if info.Namespaced() { | ||||
| 		p.visitedNamespaces.Insert(info.Namespace) | ||||
| 	} | ||||
|  | ||||
| 	metadata, err := meta.Accessor(info.Object) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	p.visitedUids.Insert(string(metadata.GetUID())) | ||||
| } | ||||
							
								
								
									
										105
									
								
								staging/src/k8s.io/kubectl/pkg/util/prune/prune.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								staging/src/k8s.io/kubectl/pkg/util/prune/prune.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| /* | ||||
| Copyright 2017 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| 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 <group/version/kind>", 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 | ||||
| } | ||||
							
								
								
									
										125
									
								
								staging/src/k8s.io/kubectl/pkg/util/prune/prune_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								staging/src/k8s.io/kubectl/pkg/util/prune/prune_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| /* | ||||
| Copyright 2017 The Kubernetes Authors. | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
|  | ||||
|     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
|  | ||||
| package prune | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/api/meta" | ||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||
| ) | ||||
|  | ||||
| type testRESTMapper struct { | ||||
| 	meta.RESTMapper | ||||
| 	scope meta.RESTScope | ||||
| } | ||||
|  | ||||
| func (m *testRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) { | ||||
| 	return &meta.RESTMapping{ | ||||
| 		Resource: schema.GroupVersionResource{ | ||||
| 			Group:    gk.Group, | ||||
| 			Version:  "", | ||||
| 			Resource: "", | ||||
| 		}, | ||||
| 		GroupVersionKind: schema.GroupVersionKind{ | ||||
| 			Group:   gk.Group, | ||||
| 			Version: "", | ||||
| 			Kind:    gk.Kind, | ||||
| 		}, | ||||
| 		Scope: m.scope, | ||||
| 	}, 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 []Resource | ||||
| 		err      bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			mapper: &testRESTMapper{ | ||||
| 				scope: meta.RESTScopeNamespace, | ||||
| 			}, | ||||
| 			gvks:     nil, | ||||
| 			expected: []Resource{}, | ||||
| 			err:      false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			mapper: &testRESTMapper{ | ||||
| 				scope: meta.RESTScopeNamespace, | ||||
| 			}, | ||||
| 			gvks:     []string{"group/kind/version/test"}, | ||||
| 			expected: []Resource{}, | ||||
| 			err:      true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			mapper: &testRESTMapper{ | ||||
| 				scope: meta.RESTScopeNamespace, | ||||
| 			}, | ||||
| 			gvks:     []string{"group/kind/version"}, | ||||
| 			expected: []Resource{{group: "group", version: "kind", kind: "version", namespaced: true}}, | ||||
| 			err:      false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			mapper: &testRESTMapper{ | ||||
| 				scope: meta.RESTScopeRoot, | ||||
| 			}, | ||||
| 			gvks:     []string{"group/kind/version"}, | ||||
| 			expected: []Resource{{group: "group", version: "kind", kind: "version", namespaced: false}}, | ||||
| 			err:      false, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tc := range tests { | ||||
| 		actual, err := ParseResources(tc.mapper, tc.gvks) | ||||
| 		if tc.err { | ||||
| 			assert.NotEmptyf(t, err, "parsePruneResources error expected but not fired") | ||||
| 		} else { | ||||
| 			assert.Equal(t, actual, tc.expected, "parsePruneResources failed expected %v actual %v", tc.expected, actual) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -88,8 +88,29 @@ run_kubectl_diff_tests() { | ||||
|     output_message=$(kubectl diff --server-side -f hack/testdata/pod-changed.yaml || test $? -eq 1) | ||||
|     kube::test::if_has_string "${output_message}" 'k8s.gcr.io/pause:3.4' | ||||
|  | ||||
|     ## kubectl diff --prune | ||||
|     kubectl create ns nsb | ||||
|     kubectl apply --namespace nsb -l prune-group=true -f hack/testdata/prune/a.yaml | ||||
|     kube::test::get_object_assert 'pods a -n nsb' "{{${id_field:?}}}" 'a' | ||||
|     # Make sure that kubectl diff does not return pod 'a' without prune flag | ||||
|     output_message=$(kubectl diff -l prune-group=true -f hack/testdata/prune/b.yaml || test $? -eq 1) | ||||
|     kube::test::if_has_not_string "${output_message}" "name: a" | ||||
|     # Make sure that for kubectl diff --prune: | ||||
|     # 1. the exit code for diff is 1 because it found a difference | ||||
|     # 2. the difference contains the pruned pod | ||||
|     output_message=$(kubectl diff --prune -l prune-group=true -f hack/testdata/prune/b.yaml || test $? -eq 1) | ||||
|     # pod 'a' should be in output, it is pruned | ||||
|     kube::test::if_has_string "${output_message}" 'name: a' | ||||
|     # apply b with namespace | ||||
|     kubectl apply --prune --namespace nsb -l prune-group=true -f hack/testdata/prune/b.yaml | ||||
|     # check right pod exists and wrong pod doesn't exist | ||||
|     kube::test::wait_object_assert 'pods -n nsb' "{{range.items}}{{${id_field:?}}}:{{end}}" 'b:' | ||||
|     # Make sure that diff --prune returns nothing (0 exit code) for 'b'. | ||||
|     kubectl diff --prune -l prune-group=true -f hack/testdata/prune/b.yaml | ||||
|  | ||||
|     # Cleanup | ||||
|     kubectl delete -f hack/testdata/pod.yaml | ||||
|     kubectl delete -f hack/testdata/prune/b.yaml | ||||
|  | ||||
|     set +o nounset | ||||
|     set +o errexit | ||||
|   | ||||
							
								
								
									
										1
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							| @@ -2153,6 +2153,7 @@ k8s.io/kubectl/pkg/util/openapi | ||||
| k8s.io/kubectl/pkg/util/openapi/testing | ||||
| k8s.io/kubectl/pkg/util/openapi/validation | ||||
| k8s.io/kubectl/pkg/util/podutils | ||||
| k8s.io/kubectl/pkg/util/prune | ||||
| k8s.io/kubectl/pkg/util/qos | ||||
| k8s.io/kubectl/pkg/util/rbac | ||||
| k8s.io/kubectl/pkg/util/resource | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Kubernetes Prow Robot
					Kubernetes Prow Robot