...

Source file src/k8s.io/kubectl/pkg/cmd/apply/applyset_pruner.go

Documentation: k8s.io/kubectl/pkg/cmd/apply

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package apply
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sync"
    23  
    24  	"k8s.io/apimachinery/pkg/api/meta"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/types"
    28  	"k8s.io/apimachinery/pkg/util/sets"
    29  	"k8s.io/cli-runtime/pkg/genericiooptions"
    30  	"k8s.io/cli-runtime/pkg/printers"
    31  	"k8s.io/client-go/dynamic"
    32  	"k8s.io/klog/v2"
    33  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    34  )
    35  
    36  type ApplySetDeleteOptions struct {
    37  	CascadingStrategy metav1.DeletionPropagation
    38  	DryRunStrategy    cmdutil.DryRunStrategy
    39  	GracePeriod       int
    40  
    41  	Printer printers.ResourcePrinter
    42  
    43  	IOStreams genericiooptions.IOStreams
    44  }
    45  
    46  // PruneObject is an apiserver object that should be deleted as part of prune.
    47  type PruneObject struct {
    48  	Name      string
    49  	Namespace string
    50  	Mapping   *meta.RESTMapping
    51  	Object    runtime.Object
    52  }
    53  
    54  // String returns a human-readable name of the object, for use in debug messages.
    55  func (p *PruneObject) String() string {
    56  	s := p.Mapping.GroupVersionKind.GroupKind().String()
    57  
    58  	if p.Namespace != "" {
    59  		s += " " + p.Namespace + "/" + p.Name
    60  	} else {
    61  		s += " " + p.Name
    62  	}
    63  	return s
    64  }
    65  
    66  // FindAllObjectsToPrune returns the list of objects that will be pruned.
    67  // Calling this instead of Prune can be useful for dry-run / diff behaviour.
    68  func (a *ApplySet) FindAllObjectsToPrune(ctx context.Context, dynamicClient dynamic.Interface, visitedUids sets.Set[types.UID]) ([]PruneObject, error) {
    69  	type task struct {
    70  		namespace   string
    71  		restMapping *meta.RESTMapping
    72  
    73  		err     error
    74  		results []PruneObject
    75  	}
    76  	var tasks []*task
    77  
    78  	// We run discovery in parallel, in as many goroutines as priority and fairness will allow
    79  	// (We don't expect many requests in real-world scenarios - maybe tens, unlikely to be hundreds)
    80  	for gvk, resource := range a.AllPrunableResources() {
    81  		scope := resource.restMapping.Scope
    82  
    83  		switch scope.Name() {
    84  		case meta.RESTScopeNameNamespace:
    85  			for _, namespace := range a.AllPrunableNamespaces() {
    86  				if namespace == "" {
    87  					// Just double-check because otherwise we get cryptic error messages
    88  					return nil, fmt.Errorf("unexpectedly encountered empty namespace during prune of namespace-scoped resource %v", gvk)
    89  				}
    90  				tasks = append(tasks, &task{
    91  					namespace:   namespace,
    92  					restMapping: resource.restMapping,
    93  				})
    94  			}
    95  
    96  		case meta.RESTScopeNameRoot:
    97  			tasks = append(tasks, &task{
    98  				restMapping: resource.restMapping,
    99  			})
   100  
   101  		default:
   102  			return nil, fmt.Errorf("unhandled scope %q", scope.Name())
   103  		}
   104  	}
   105  
   106  	var wg sync.WaitGroup
   107  
   108  	for i := range tasks {
   109  		task := tasks[i]
   110  		wg.Add(1)
   111  		go func() {
   112  			defer wg.Done()
   113  
   114  			results, err := a.findObjectsToPrune(ctx, dynamicClient, visitedUids, task.namespace, task.restMapping)
   115  			if err != nil {
   116  				task.err = fmt.Errorf("listing %v objects for pruning: %w", task.restMapping.GroupVersionKind.String(), err)
   117  			} else {
   118  				task.results = results
   119  			}
   120  		}()
   121  	}
   122  	// Wait for all the goroutines to finish
   123  	wg.Wait()
   124  
   125  	var allObjects []PruneObject
   126  	for _, task := range tasks {
   127  		if task.err != nil {
   128  			return nil, task.err
   129  		}
   130  		allObjects = append(allObjects, task.results...)
   131  	}
   132  	return allObjects, nil
   133  }
   134  
   135  func (a *ApplySet) pruneAll(ctx context.Context, dynamicClient dynamic.Interface, visitedUids sets.Set[types.UID], deleteOptions *ApplySetDeleteOptions) error {
   136  	allObjects, err := a.FindAllObjectsToPrune(ctx, dynamicClient, visitedUids)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	return a.deleteObjects(ctx, dynamicClient, allObjects, deleteOptions)
   142  }
   143  
   144  func (a *ApplySet) findObjectsToPrune(ctx context.Context, dynamicClient dynamic.Interface, visitedUids sets.Set[types.UID], namespace string, mapping *meta.RESTMapping) ([]PruneObject, error) {
   145  	applysetLabelSelector := a.LabelSelectorForMembers()
   146  
   147  	opt := metav1.ListOptions{
   148  		LabelSelector: applysetLabelSelector,
   149  	}
   150  
   151  	klog.V(2).Infof("listing objects for pruning; namespace=%q, resource=%v", namespace, mapping.Resource)
   152  	objects, err := dynamicClient.Resource(mapping.Resource).Namespace(namespace).List(ctx, opt)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	var pruneObjects []PruneObject
   158  	for i := range objects.Items {
   159  		obj := &objects.Items[i]
   160  
   161  		uid := obj.GetUID()
   162  		if visitedUids.Has(uid) {
   163  			continue
   164  		}
   165  		name := obj.GetName()
   166  		pruneObjects = append(pruneObjects, PruneObject{
   167  			Name:      name,
   168  			Namespace: namespace,
   169  			Mapping:   mapping,
   170  			Object:    obj,
   171  		})
   172  
   173  	}
   174  	return pruneObjects, nil
   175  }
   176  
   177  func (a *ApplySet) deleteObjects(ctx context.Context, dynamicClient dynamic.Interface, pruneObjects []PruneObject, opt *ApplySetDeleteOptions) error {
   178  	for i := range pruneObjects {
   179  		pruneObject := &pruneObjects[i]
   180  
   181  		name := pruneObject.Name
   182  		namespace := pruneObject.Namespace
   183  		mapping := pruneObject.Mapping
   184  
   185  		if opt.DryRunStrategy != cmdutil.DryRunClient {
   186  			if err := runDelete(ctx, namespace, name, mapping, dynamicClient, opt.CascadingStrategy, opt.GracePeriod, opt.DryRunStrategy == cmdutil.DryRunServer); err != nil {
   187  				return fmt.Errorf("pruning %v: %w", pruneObject.String(), err)
   188  			}
   189  		}
   190  
   191  		opt.Printer.PrintObj(pruneObject.Object, opt.IOStreams.Out)
   192  
   193  	}
   194  	return nil
   195  }
   196  

View as plain text