github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/k8s/resource.go (about)

     1  package k8s
     2  
     3  import (
     4  	"io"
     5  	"strings"
     6  
     7  	"helm.sh/helm/v3/pkg/kube"
     8  	"k8s.io/apimachinery/pkg/types"
     9  	"k8s.io/apimachinery/pkg/util/sets"
    10  	"k8s.io/cli-runtime/pkg/genericclioptions"
    11  	"k8s.io/cli-runtime/pkg/printers"
    12  	"k8s.io/cli-runtime/pkg/resource"
    13  	"k8s.io/kubectl/pkg/cmd/apply"
    14  	"k8s.io/kubectl/pkg/cmd/delete"
    15  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    16  )
    17  
    18  // We've adapted Helm's kubernetes client for our needs
    19  type ResourceClient interface {
    20  	Apply(target kube.ResourceList) (*kube.Result, error)
    21  	CreateOrReplace(target kube.ResourceList) (*kube.Result, error)
    22  	Delete(existing kube.ResourceList) (*kube.Result, []error)
    23  	Create(l kube.ResourceList) (*kube.Result, error)
    24  	Build(r io.Reader, validate bool) (kube.ResourceList, error)
    25  }
    26  
    27  type resourceClient struct {
    28  	*kube.Client
    29  	factory cmdutil.Factory
    30  }
    31  
    32  // Helm's update function doesn't really work for us,
    33  // so we use the kubectl apply code directly.
    34  func (c *resourceClient) Apply(target kube.ResourceList) (*kube.Result, error) {
    35  	f := c.factory
    36  	iostreams := genericclioptions.IOStreams{
    37  		In:     strings.NewReader(""),
    38  		Out:    io.Discard,
    39  		ErrOut: io.Discard,
    40  	}
    41  	flags := apply.NewApplyFlags(iostreams)
    42  
    43  	dynamicClient, err := f.DynamicClient()
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	recorder, err := genericclioptions.NewRecordFlags().ToRecorder()
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  	deleteOptions, err := delete.NewDeleteFlags("").ToOptions(dynamicClient, iostreams)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	toPrinter := func(s string) (printers.ResourcePrinter, error) {
    57  		return genericclioptions.NewPrintFlags("created").ToPrinter()
    58  	}
    59  	builder := f.NewBuilder()
    60  	mapper, err := f.ToRESTMapper()
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	namespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	o := &apply.ApplyOptions{
    71  		PrintFlags: flags.PrintFlags,
    72  
    73  		DeleteOptions:  deleteOptions,
    74  		ToPrinter:      toPrinter,
    75  		Selector:       flags.Selector,
    76  		Prune:          flags.Prune,
    77  		PruneResources: flags.PruneResources,
    78  		All:            flags.All,
    79  		Overwrite:      flags.Overwrite,
    80  		OpenAPIPatch:   flags.OpenAPIPatch,
    81  
    82  		Recorder:         recorder,
    83  		Namespace:        namespace,
    84  		EnforceNamespace: enforceNamespace,
    85  		Builder:          builder,
    86  		Mapper:           mapper,
    87  		DynamicClient:    dynamicClient,
    88  
    89  		IOStreams: flags.IOStreams,
    90  
    91  		VisitedUids:       sets.New[types.UID](),
    92  		VisitedNamespaces: sets.New[string](),
    93  	}
    94  
    95  	o.SetObjects(target)
    96  	err = o.Run()
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	return &kube.Result{Updated: target}, nil
   101  }
   102  
   103  // A simplified implementation that creates or replaces the whole object.
   104  func (c *resourceClient) CreateOrReplace(target kube.ResourceList) (*kube.Result, error) {
   105  	for _, info := range target {
   106  		obj, err := resource.
   107  			NewHelper(info.Client, info.Mapping).
   108  			Create(info.Namespace, true, info.Object)
   109  
   110  		if err != nil && strings.Contains(err.Error(), "already exists") {
   111  			obj, err = resource.
   112  				NewHelper(info.Client, info.Mapping).
   113  				Replace(info.Namespace, info.Name, true, info.Object)
   114  		}
   115  
   116  		if err != nil {
   117  			return nil, cmdutil.AddSourceToErr("create/replace", info.Source, err)
   118  		}
   119  
   120  		err = info.Refresh(obj, true)
   121  		if err != nil {
   122  			return nil, cmdutil.AddSourceToErr("create/replace", info.Source, err)
   123  		}
   124  	}
   125  
   126  	return &kube.Result{Updated: target}, nil
   127  }
   128  
   129  var helmNopLogger = func(_ string, _ ...interface{}) {}
   130  
   131  func newResourceClient(c *K8sClient) ResourceClient {
   132  	f := cmdutil.NewFactory(c)
   133  
   134  	// Don't use kube.New() here, because it modifies globals in
   135  	// a way that breaks tests.
   136  	return &resourceClient{
   137  		Client: &kube.Client{
   138  			Factory: f,
   139  			Log:     helmNopLogger,
   140  		},
   141  		factory: f,
   142  	}
   143  }