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 }