diff --git a/pkg/client/client.go b/pkg/client/client.go index 646d86a87e..43facbfba6 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -171,10 +171,19 @@ type statusWriter struct { var _ StatusWriter = &statusWriter{} // Update implements client.StatusWriter -func (sw *statusWriter) Update(ctx context.Context, obj runtime.Object) error { +func (sw *statusWriter) Update(ctx context.Context, obj runtime.Object, opts ...UpdateOptionFunc) error { _, ok := obj.(*unstructured.Unstructured) if ok { - return sw.client.unstructuredClient.UpdateStatus(ctx, obj) + return sw.client.unstructuredClient.UpdateStatus(ctx, obj, opts...) } - return sw.client.typedClient.UpdateStatus(ctx, obj) + return sw.client.typedClient.UpdateStatus(ctx, obj, opts...) +} + +// Patch implements client.Client +func (sw *statusWriter) Patch(ctx context.Context, obj runtime.Object, patch Patch, opts ...PatchOptionFunc) error { + _, ok := obj.(*unstructured.Unstructured) + if ok { + return sw.client.unstructuredClient.PatchStatus(ctx, obj, patch, opts...) + } + return sw.client.typedClient.PatchStatus(ctx, obj, patch, opts...) } diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 6ebc26e083..ad96f4ccb4 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -276,7 +276,7 @@ var _ = Describe("Client", func() { Expect(cl).NotTo(BeNil()) By("creating the object (with DryRun)") - err = cl.Create(context.TODO(), dep, client.CreateDryRunAll()) + err = cl.Create(context.TODO(), dep, client.CreateDryRunAll) Expect(err).NotTo(HaveOccurred()) actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{}) @@ -415,7 +415,7 @@ var _ = Describe("Client", func() { }) By("creating the object") - err = cl.Create(context.TODO(), u, client.CreateDryRunAll()) + err = cl.Create(context.TODO(), u, client.CreateDryRunAll) Expect(err).NotTo(HaveOccurred()) actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{}) @@ -1074,7 +1074,7 @@ var _ = Describe("Client", func() { Expect(err).NotTo(HaveOccurred()) By("patching the Deployment with dry-run") - err = cl.Patch(context.TODO(), dep, client.ConstantPatch(types.MergePatchType, mergePatch), client.PatchDryRunAll()) + err = cl.Patch(context.TODO(), dep, client.ConstantPatch(types.MergePatchType, mergePatch), client.PatchDryRunAll) Expect(err).NotTo(HaveOccurred()) By("validating patched Deployment doesn't have the new annotation") @@ -1183,7 +1183,7 @@ var _ = Describe("Client", func() { Kind: "Deployment", Version: "v1", }) - err = cl.Patch(context.TODO(), u, client.ConstantPatch(types.MergePatchType, mergePatch), client.PatchDryRunAll()) + err = cl.Patch(context.TODO(), u, client.ConstantPatch(types.MergePatchType, mergePatch), client.PatchDryRunAll) Expect(err).NotTo(HaveOccurred()) By("validating patched Deployment does not have the new annotation") @@ -2000,7 +2000,7 @@ var _ = Describe("Client", func() { Describe("CreateOptions", func() { It("should allow setting DryRun to 'all'", func() { co := &client.CreateOptions{} - client.CreateDryRunAll()(co) + client.CreateDryRunAll(co) all := []string{metav1.DryRunAll} Expect(co.AsCreateOptions().DryRun).To(Equal(all)) }) @@ -2141,7 +2141,7 @@ var _ = Describe("Client", func() { Describe("UpdateOptions", func() { It("should allow setting DryRun to 'all'", func() { uo := &client.UpdateOptions{} - client.UpdateDryRunAll()(uo) + client.UpdateDryRunAll(uo) all := []string{metav1.DryRunAll} Expect(uo.AsUpdateOptions().DryRun).To(Equal(all)) }) @@ -2157,19 +2157,25 @@ var _ = Describe("Client", func() { Describe("PatchOptions", func() { It("should allow setting DryRun to 'all'", func() { po := &client.PatchOptions{} - client.PatchDryRunAll()(po) + client.PatchDryRunAll(po) all := []string{metav1.DryRunAll} Expect(po.AsPatchOptions().DryRun).To(Equal(all)) }) It("should allow setting Force to 'true'", func() { po := &client.PatchOptions{} - client.PatchWithForce()(po) + client.ForceOwnership(po) mpo := po.AsPatchOptions() Expect(mpo.Force).NotTo(BeNil()) Expect(*mpo.Force).To(BeTrue()) }) + It("should allow setting the field manager", func() { + po := &client.PatchOptions{} + client.FieldOwner("some-owner")(po) + Expect(po.AsPatchOptions().FieldManager).To(Equal("some-owner")) + }) + It("should produce empty metav1.PatchOptions if nil", func() { var po *client.PatchOptions Expect(po.AsPatchOptions()).To(Equal(&metav1.PatchOptions{})) diff --git a/pkg/client/fake/client.go b/pkg/client/fake/client.go index d3e6f747dc..7a348a5867 100644 --- a/pkg/client/fake/client.go +++ b/pkg/client/fake/client.go @@ -244,8 +244,14 @@ type fakeStatusWriter struct { client *fakeClient } -func (sw *fakeStatusWriter) Update(ctx context.Context, obj runtime.Object) error { +func (sw *fakeStatusWriter) Update(ctx context.Context, obj runtime.Object, opts ...client.UpdateOptionFunc) error { // TODO(droot): This results in full update of the obj (spec + status). Need // a way to update status field only. - return sw.client.Update(ctx, obj) + return sw.client.Update(ctx, obj, opts...) +} + +func (sw *fakeStatusWriter) Patch(ctx context.Context, obj runtime.Object, patch client.Patch, opts ...client.PatchOptionFunc) error { + // TODO(droot): This results in full update of the obj (spec + status). Need + // a way to update status field only. + return sw.client.Patch(ctx, obj, patch, opts...) } diff --git a/pkg/client/fake/client_test.go b/pkg/client/fake/client_test.go index c65c24620c..ad173fae01 100644 --- a/pkg/client/fake/client_test.go +++ b/pkg/client/fake/client_test.go @@ -167,7 +167,7 @@ var _ = Describe("Fake client", func() { Namespace: "ns2", }, } - err := cl.Create(nil, newcm, client.CreateDryRunAll()) + err := cl.Create(nil, newcm, client.CreateDryRunAll) Expect(err).To(BeNil()) By("Getting the new configmap") @@ -193,7 +193,7 @@ var _ = Describe("Fake client", func() { "test-key": "new-value", }, } - err := cl.Update(nil, newcm, client.UpdateDryRunAll()) + err := cl.Update(nil, newcm, client.UpdateDryRunAll) Expect(err).To(BeNil()) By("Getting the new configmap") diff --git a/pkg/client/interfaces.go b/pkg/client/interfaces.go index 2f4994033a..e4ca8be85c 100644 --- a/pkg/client/interfaces.go +++ b/pkg/client/interfaces.go @@ -22,9 +22,6 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ) @@ -92,7 +89,12 @@ type StatusWriter interface { // Update updates the fields corresponding to the status subresource for the // given obj. obj must be a struct pointer so that obj can be updated // with the content returned by the Server. - Update(ctx context.Context, obj runtime.Object) error + Update(ctx context.Context, obj runtime.Object, opts ...UpdateOptionFunc) error + + // Patch patches the given object's subresource. obj must be a struct + // pointer so that obj can be updated with the content returned by the + // Server. + Patch(ctx context.Context, obj runtime.Object, patch Patch, opts ...PatchOptionFunc) error } // Client knows how to perform CRUD operations on Kubernetes objects. @@ -120,393 +122,6 @@ type FieldIndexer interface { IndexField(obj runtime.Object, field string, extractValue IndexerFunc) error } -// CreateOptions contains options for create requests. It's generally a subset -// of metav1.CreateOptions. -type CreateOptions struct { - // When present, indicates that modifications should not be - // persisted. An invalid or unrecognized dryRun directive will - // result in an error response and no further processing of the - // request. Valid values are: - // - All: all dry run stages will be processed - DryRun []string - - // Raw represents raw CreateOptions, as passed to the API server. - Raw *metav1.CreateOptions -} - -// AsCreateOptions returns these options as a metav1.CreateOptions. -// This may mutate the Raw field. -func (o *CreateOptions) AsCreateOptions() *metav1.CreateOptions { - - if o == nil { - return &metav1.CreateOptions{} - } - if o.Raw == nil { - o.Raw = &metav1.CreateOptions{} - } - - o.Raw.DryRun = o.DryRun - return o.Raw -} - -// ApplyOptions executes the given CreateOptionFuncs and returns the mutated -// CreateOptions. -func (o *CreateOptions) ApplyOptions(optFuncs []CreateOptionFunc) *CreateOptions { - for _, optFunc := range optFuncs { - optFunc(o) - } - return o -} - -// CreateOptionFunc is a function that mutates a CreateOptions struct. It implements -// the functional options pattern. See -// https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md. -type CreateOptionFunc func(*CreateOptions) - -// CreateDryRunAll is a functional option that sets the DryRun -// field of a CreateOptions struct to metav1.DryRunAll. -func CreateDryRunAll() CreateOptionFunc { - return func(opts *CreateOptions) { - opts.DryRun = []string{metav1.DryRunAll} - } -} - -// DeleteOptions contains options for delete requests. It's generally a subset -// of metav1.DeleteOptions. -type DeleteOptions struct { - // GracePeriodSeconds is the duration in seconds before the object should be - // deleted. Value must be non-negative integer. The value zero indicates - // delete immediately. If this value is nil, the default grace period for the - // specified type will be used. - GracePeriodSeconds *int64 - - // Preconditions must be fulfilled before a deletion is carried out. If not - // possible, a 409 Conflict status will be returned. - Preconditions *metav1.Preconditions - - // PropagationPolicy determined whether and how garbage collection will be - // performed. Either this field or OrphanDependents may be set, but not both. - // The default policy is decided by the existing finalizer set in the - // metadata.finalizers and the resource-specific default policy. - // Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - - // allow the garbage collector to delete the dependents in the background; - // 'Foreground' - a cascading policy that deletes all dependents in the - // foreground. - PropagationPolicy *metav1.DeletionPropagation - - // Raw represents raw DeleteOptions, as passed to the API server. - Raw *metav1.DeleteOptions -} - -// AsDeleteOptions returns these options as a metav1.DeleteOptions. -// This may mutate the Raw field. -func (o *DeleteOptions) AsDeleteOptions() *metav1.DeleteOptions { - - if o == nil { - return &metav1.DeleteOptions{} - } - if o.Raw == nil { - o.Raw = &metav1.DeleteOptions{} - } - - o.Raw.GracePeriodSeconds = o.GracePeriodSeconds - o.Raw.Preconditions = o.Preconditions - o.Raw.PropagationPolicy = o.PropagationPolicy - return o.Raw -} - -// ApplyOptions executes the given DeleteOptionFuncs and returns the mutated -// DeleteOptions. -func (o *DeleteOptions) ApplyOptions(optFuncs []DeleteOptionFunc) *DeleteOptions { - for _, optFunc := range optFuncs { - optFunc(o) - } - return o -} - -// DeleteOptionFunc is a function that mutates a DeleteOptions struct. It implements -// the functional options pattern. See -// https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md. -type DeleteOptionFunc func(*DeleteOptions) - -// GracePeriodSeconds is a functional option that sets the GracePeriodSeconds -// field of a DeleteOptions struct. -func GracePeriodSeconds(gp int64) DeleteOptionFunc { - return func(opts *DeleteOptions) { - opts.GracePeriodSeconds = &gp - } -} - -// Preconditions is a functional option that sets the Preconditions field of a -// DeleteOptions struct. -func Preconditions(p *metav1.Preconditions) DeleteOptionFunc { - return func(opts *DeleteOptions) { - opts.Preconditions = p - } -} - -// PropagationPolicy is a functional option that sets the PropagationPolicy -// field of a DeleteOptions struct. -func PropagationPolicy(p metav1.DeletionPropagation) DeleteOptionFunc { - return func(opts *DeleteOptions) { - opts.PropagationPolicy = &p - } -} - -// ListOptions contains options for limiting or filtering results. -// It's generally a subset of metav1.ListOptions, with support for -// pre-parsed selectors (since generally, selectors will be executed -// against the cache). -type ListOptions struct { - // LabelSelector filters results by label. Use SetLabelSelector to - // set from raw string form. - LabelSelector labels.Selector - // FieldSelector filters results by a particular field. In order - // to use this with cache-based implementations, restrict usage to - // a single field-value pair that's been added to the indexers. - FieldSelector fields.Selector - - // Namespace represents the namespace to list for, or empty for - // non-namespaced objects, or to list across all namespaces. - Namespace string - - // Raw represents raw ListOptions, as passed to the API server. Note - // that these may not be respected by all implementations of interface, - // and the LabelSelector and FieldSelector fields are ignored. - Raw *metav1.ListOptions -} - -// SetLabelSelector sets this the label selector of these options -// from a string form of the selector. -func (o *ListOptions) SetLabelSelector(selRaw string) error { - sel, err := labels.Parse(selRaw) - if err != nil { - return err - } - o.LabelSelector = sel - return nil -} - -// SetFieldSelector sets this the label selector of these options -// from a string form of the selector. -func (o *ListOptions) SetFieldSelector(selRaw string) error { - sel, err := fields.ParseSelector(selRaw) - if err != nil { - return err - } - o.FieldSelector = sel - return nil -} - -// AsListOptions returns these options as a flattened metav1.ListOptions. -// This may mutate the Raw field. -func (o *ListOptions) AsListOptions() *metav1.ListOptions { - if o == nil { - return &metav1.ListOptions{} - } - if o.Raw == nil { - o.Raw = &metav1.ListOptions{} - } - if o.LabelSelector != nil { - o.Raw.LabelSelector = o.LabelSelector.String() - } - if o.FieldSelector != nil { - o.Raw.FieldSelector = o.FieldSelector.String() - } - return o.Raw -} - -// ApplyOptions executes the given ListOptionFuncs and returns the mutated -// ListOptions. -func (o *ListOptions) ApplyOptions(optFuncs []ListOptionFunc) *ListOptions { - for _, optFunc := range optFuncs { - optFunc(o) - } - return o -} - -// ListOptionFunc is a function that mutates a ListOptions struct. It implements -// the functional options pattern. See -// https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md. -type ListOptionFunc func(*ListOptions) - -// MatchingLabels is a convenience function that sets the label selector -// to match the given labels, and then returns the options. -// It mutates the list options. -func (o *ListOptions) MatchingLabels(lbls map[string]string) *ListOptions { - sel := labels.SelectorFromSet(lbls) - o.LabelSelector = sel - return o -} - -// MatchingField is a convenience function that sets the field selector -// to match the given field, and then returns the options. -// It mutates the list options. -func (o *ListOptions) MatchingField(name, val string) *ListOptions { - sel := fields.SelectorFromSet(fields.Set{name: val}) - o.FieldSelector = sel - return o -} - -// InNamespace is a convenience function that sets the namespace, -// and then returns the options. It mutates the list options. -func (o *ListOptions) InNamespace(ns string) *ListOptions { - o.Namespace = ns - return o -} - -// MatchingLabels is a functional option that sets the LabelSelector field of -// a ListOptions struct. -func MatchingLabels(lbls map[string]string) ListOptionFunc { - sel := labels.SelectorFromSet(lbls) - return func(opts *ListOptions) { - opts.LabelSelector = sel - } -} - -// MatchingField is a functional option that sets the FieldSelector field of -// a ListOptions struct. -func MatchingField(name, val string) ListOptionFunc { - sel := fields.SelectorFromSet(fields.Set{name: val}) - return func(opts *ListOptions) { - opts.FieldSelector = sel - } -} - -// InNamespace is a functional option that sets the Namespace field of -// a ListOptions struct. -func InNamespace(ns string) ListOptionFunc { - return func(opts *ListOptions) { - opts.Namespace = ns - } -} - -// UseListOptions is a functional option that replaces the fields of a -// ListOptions struct with those of a different ListOptions struct. -// -// Example: -// cl.List(ctx, list, client.UseListOptions(lo.InNamespace(ns).MatchingLabels(labels))) -func UseListOptions(newOpts *ListOptions) ListOptionFunc { - return func(opts *ListOptions) { - *opts = *newOpts - } -} - -// UpdateOptions contains options for create requests. It's generally a subset -// of metav1.UpdateOptions. -type UpdateOptions struct { - // When present, indicates that modifications should not be - // persisted. An invalid or unrecognized dryRun directive will - // result in an error response and no further processing of the - // request. Valid values are: - // - All: all dry run stages will be processed - DryRun []string - - // Raw represents raw UpdateOptions, as passed to the API server. - Raw *metav1.UpdateOptions -} - -// AsUpdateOptions returns these options as a metav1.UpdateOptions. -// This may mutate the Raw field. -func (o *UpdateOptions) AsUpdateOptions() *metav1.UpdateOptions { - - if o == nil { - return &metav1.UpdateOptions{} - } - if o.Raw == nil { - o.Raw = &metav1.UpdateOptions{} - } - - o.Raw.DryRun = o.DryRun - return o.Raw -} - -// ApplyOptions executes the given UpdateOptionFuncs and returns the mutated -// UpdateOptions. -func (o *UpdateOptions) ApplyOptions(optFuncs []UpdateOptionFunc) *UpdateOptions { - for _, optFunc := range optFuncs { - optFunc(o) - } - return o -} - -// UpdateOptionFunc is a function that mutates a UpdateOptions struct. It implements -// the functional options pattern. See -// https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md. -type UpdateOptionFunc func(*UpdateOptions) - -// UpdateDryRunAll is a functional option that sets the DryRun -// field of a UpdateOptions struct to metav1.DryRunAll. -func UpdateDryRunAll() UpdateOptionFunc { - return func(opts *UpdateOptions) { - opts.DryRun = []string{metav1.DryRunAll} - } -} - -// PatchOptions contains options for patch requests. -type PatchOptions struct { - // When present, indicates that modifications should not be - // persisted. An invalid or unrecognized dryRun directive will - // result in an error response and no further processing of the - // request. Valid values are: - // - All: all dry run stages will be processed - DryRun []string - // Force is going to "force" Apply requests. It means user will - // re-acquire conflicting fields owned by other people. Force - // flag must be unset for non-apply patch requests. - // +optional - Force *bool - - // Raw represents raw PatchOptions, as passed to the API server. - Raw *metav1.PatchOptions -} - -// ApplyOptions executes the given PatchOptionFuncs, mutating these PatchOptions. -// It returns the mutated PatchOptions for convenience. -func (o *PatchOptions) ApplyOptions(optFuncs []PatchOptionFunc) *PatchOptions { - for _, optFunc := range optFuncs { - optFunc(o) - } - return o -} - -// AsPatchOptions returns these options as a metav1.PatchOptions. -// This may mutate the Raw field. -func (o *PatchOptions) AsPatchOptions() *metav1.PatchOptions { - if o == nil { - return &metav1.PatchOptions{} - } - if o.Raw == nil { - o.Raw = &metav1.PatchOptions{} - } - - o.Raw.DryRun = o.DryRun - o.Raw.Force = o.Force - return o.Raw -} - -// PatchOptionFunc is a function that mutates a PatchOptions struct. It implements -// the functional options pattern. See -// https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md. -type PatchOptionFunc func(*PatchOptions) - -// PatchDryRunAll is a functional option that sets the DryRun -// field of a PatchOptions struct to metav1.DryRunAll. -func PatchDryRunAll() PatchOptionFunc { - return func(opts *PatchOptions) { - opts.DryRun = []string{metav1.DryRunAll} - } -} - -// PatchWithForce is a functional option that sets the Force -// field of a PatchOptions struct to true. -func PatchWithForce() PatchOptionFunc { - force := true - return func(opts *PatchOptions) { - opts.Force = &force - } -} - // IgnoreNotFound returns nil on NotFound errors. // All other values that are not NotFound errors or nil are returned unmodified. func IgnoreNotFound(err error) error { diff --git a/pkg/client/options.go b/pkg/client/options.go new file mode 100644 index 0000000000..46ddf66f97 --- /dev/null +++ b/pkg/client/options.go @@ -0,0 +1,417 @@ +/* +Copyright 2018 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 client + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" +) + +// CreateOptions contains options for create requests. It's generally a subset +// of metav1.CreateOptions. +type CreateOptions struct { + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + DryRun []string + + // Raw represents raw CreateOptions, as passed to the API server. + Raw *metav1.CreateOptions +} + +// AsCreateOptions returns these options as a metav1.CreateOptions. +// This may mutate the Raw field. +func (o *CreateOptions) AsCreateOptions() *metav1.CreateOptions { + + if o == nil { + return &metav1.CreateOptions{} + } + if o.Raw == nil { + o.Raw = &metav1.CreateOptions{} + } + + o.Raw.DryRun = o.DryRun + return o.Raw +} + +// ApplyOptions executes the given CreateOptionFuncs and returns the mutated +// CreateOptions. +func (o *CreateOptions) ApplyOptions(optFuncs []CreateOptionFunc) *CreateOptions { + for _, optFunc := range optFuncs { + optFunc(o) + } + return o +} + +// CreateOptionFunc is a function that mutates a CreateOptions struct. It implements +// the functional options pattern. See +// https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md. +type CreateOptionFunc func(*CreateOptions) + +// CreateDryRunAll is a functional option that sets the DryRun +// field of a CreateOptions struct to metav1.DryRunAll. +var CreateDryRunAll CreateOptionFunc = func(opts *CreateOptions) { + opts.DryRun = []string{metav1.DryRunAll} +} + +// DeleteOptions contains options for delete requests. It's generally a subset +// of metav1.DeleteOptions. +type DeleteOptions struct { + // GracePeriodSeconds is the duration in seconds before the object should be + // deleted. Value must be non-negative integer. The value zero indicates + // delete immediately. If this value is nil, the default grace period for the + // specified type will be used. + GracePeriodSeconds *int64 + + // Preconditions must be fulfilled before a deletion is carried out. If not + // possible, a 409 Conflict status will be returned. + Preconditions *metav1.Preconditions + + // PropagationPolicy determined whether and how garbage collection will be + // performed. Either this field or OrphanDependents may be set, but not both. + // The default policy is decided by the existing finalizer set in the + // metadata.finalizers and the resource-specific default policy. + // Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - + // allow the garbage collector to delete the dependents in the background; + // 'Foreground' - a cascading policy that deletes all dependents in the + // foreground. + PropagationPolicy *metav1.DeletionPropagation + + // Raw represents raw DeleteOptions, as passed to the API server. + Raw *metav1.DeleteOptions +} + +// AsDeleteOptions returns these options as a metav1.DeleteOptions. +// This may mutate the Raw field. +func (o *DeleteOptions) AsDeleteOptions() *metav1.DeleteOptions { + + if o == nil { + return &metav1.DeleteOptions{} + } + if o.Raw == nil { + o.Raw = &metav1.DeleteOptions{} + } + + o.Raw.GracePeriodSeconds = o.GracePeriodSeconds + o.Raw.Preconditions = o.Preconditions + o.Raw.PropagationPolicy = o.PropagationPolicy + return o.Raw +} + +// ApplyOptions executes the given DeleteOptionFuncs and returns the mutated +// DeleteOptions. +func (o *DeleteOptions) ApplyOptions(optFuncs []DeleteOptionFunc) *DeleteOptions { + for _, optFunc := range optFuncs { + optFunc(o) + } + return o +} + +// DeleteOptionFunc is a function that mutates a DeleteOptions struct. It implements +// the functional options pattern. See +// https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md. +type DeleteOptionFunc func(*DeleteOptions) + +// GracePeriodSeconds is a functional option that sets the GracePeriodSeconds +// field of a DeleteOptions struct. +func GracePeriodSeconds(gp int64) DeleteOptionFunc { + return func(opts *DeleteOptions) { + opts.GracePeriodSeconds = &gp + } +} + +// Preconditions is a functional option that sets the Preconditions field of a +// DeleteOptions struct. +func Preconditions(p *metav1.Preconditions) DeleteOptionFunc { + return func(opts *DeleteOptions) { + opts.Preconditions = p + } +} + +// PropagationPolicy is a functional option that sets the PropagationPolicy +// field of a DeleteOptions struct. +func PropagationPolicy(p metav1.DeletionPropagation) DeleteOptionFunc { + return func(opts *DeleteOptions) { + opts.PropagationPolicy = &p + } +} + +// ListOptions contains options for limiting or filtering results. +// It's generally a subset of metav1.ListOptions, with support for +// pre-parsed selectors (since generally, selectors will be executed +// against the cache). +type ListOptions struct { + // LabelSelector filters results by label. Use SetLabelSelector to + // set from raw string form. + LabelSelector labels.Selector + // FieldSelector filters results by a particular field. In order + // to use this with cache-based implementations, restrict usage to + // a single field-value pair that's been added to the indexers. + FieldSelector fields.Selector + + // Namespace represents the namespace to list for, or empty for + // non-namespaced objects, or to list across all namespaces. + Namespace string + + // Raw represents raw ListOptions, as passed to the API server. Note + // that these may not be respected by all implementations of interface, + // and the LabelSelector and FieldSelector fields are ignored. + Raw *metav1.ListOptions +} + +// SetLabelSelector sets this the label selector of these options +// from a string form of the selector. +func (o *ListOptions) SetLabelSelector(selRaw string) error { + sel, err := labels.Parse(selRaw) + if err != nil { + return err + } + o.LabelSelector = sel + return nil +} + +// SetFieldSelector sets this the label selector of these options +// from a string form of the selector. +func (o *ListOptions) SetFieldSelector(selRaw string) error { + sel, err := fields.ParseSelector(selRaw) + if err != nil { + return err + } + o.FieldSelector = sel + return nil +} + +// AsListOptions returns these options as a flattened metav1.ListOptions. +// This may mutate the Raw field. +func (o *ListOptions) AsListOptions() *metav1.ListOptions { + if o == nil { + return &metav1.ListOptions{} + } + if o.Raw == nil { + o.Raw = &metav1.ListOptions{} + } + if o.LabelSelector != nil { + o.Raw.LabelSelector = o.LabelSelector.String() + } + if o.FieldSelector != nil { + o.Raw.FieldSelector = o.FieldSelector.String() + } + return o.Raw +} + +// ApplyOptions executes the given ListOptionFuncs and returns the mutated +// ListOptions. +func (o *ListOptions) ApplyOptions(optFuncs []ListOptionFunc) *ListOptions { + for _, optFunc := range optFuncs { + optFunc(o) + } + return o +} + +// ListOptionFunc is a function that mutates a ListOptions struct. It implements +// the functional options pattern. See +// https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md. +type ListOptionFunc func(*ListOptions) + +// MatchingLabels is a convenience function that sets the label selector +// to match the given labels, and then returns the options. +// It mutates the list options. +func (o *ListOptions) MatchingLabels(lbls map[string]string) *ListOptions { + sel := labels.SelectorFromSet(lbls) + o.LabelSelector = sel + return o +} + +// MatchingField is a convenience function that sets the field selector +// to match the given field, and then returns the options. +// It mutates the list options. +func (o *ListOptions) MatchingField(name, val string) *ListOptions { + sel := fields.SelectorFromSet(fields.Set{name: val}) + o.FieldSelector = sel + return o +} + +// InNamespace is a convenience function that sets the namespace, +// and then returns the options. It mutates the list options. +func (o *ListOptions) InNamespace(ns string) *ListOptions { + o.Namespace = ns + return o +} + +// MatchingLabels is a functional option that sets the LabelSelector field of +// a ListOptions struct. +func MatchingLabels(lbls map[string]string) ListOptionFunc { + sel := labels.SelectorFromSet(lbls) + return func(opts *ListOptions) { + opts.LabelSelector = sel + } +} + +// MatchingField is a functional option that sets the FieldSelector field of +// a ListOptions struct. +func MatchingField(name, val string) ListOptionFunc { + sel := fields.SelectorFromSet(fields.Set{name: val}) + return func(opts *ListOptions) { + opts.FieldSelector = sel + } +} + +// InNamespace is a functional option that sets the Namespace field of +// a ListOptions struct. +func InNamespace(ns string) ListOptionFunc { + return func(opts *ListOptions) { + opts.Namespace = ns + } +} + +// UseListOptions is a functional option that replaces the fields of a +// ListOptions struct with those of a different ListOptions struct. +// +// Example: +// cl.List(ctx, list, client.UseListOptions(lo.InNamespace(ns).MatchingLabels(labels))) +func UseListOptions(newOpts *ListOptions) ListOptionFunc { + return func(opts *ListOptions) { + *opts = *newOpts + } +} + +// UpdateOptions contains options for create requests. It's generally a subset +// of metav1.UpdateOptions. +type UpdateOptions struct { + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + DryRun []string + + // Raw represents raw UpdateOptions, as passed to the API server. + Raw *metav1.UpdateOptions +} + +// AsUpdateOptions returns these options as a metav1.UpdateOptions. +// This may mutate the Raw field. +func (o *UpdateOptions) AsUpdateOptions() *metav1.UpdateOptions { + + if o == nil { + return &metav1.UpdateOptions{} + } + if o.Raw == nil { + o.Raw = &metav1.UpdateOptions{} + } + + o.Raw.DryRun = o.DryRun + return o.Raw +} + +// ApplyOptions executes the given UpdateOptionFuncs and returns the mutated +// UpdateOptions. +func (o *UpdateOptions) ApplyOptions(optFuncs []UpdateOptionFunc) *UpdateOptions { + for _, optFunc := range optFuncs { + optFunc(o) + } + return o +} + +// UpdateOptionFunc is a function that mutates a UpdateOptions struct. It implements +// the functional options pattern. See +// https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md. +type UpdateOptionFunc func(*UpdateOptions) + +// UpdateDryRunAll is a functional option that sets the DryRun +// field of a UpdateOptions struct to metav1.DryRunAll. +var UpdateDryRunAll UpdateOptionFunc = func(opts *UpdateOptions) { + opts.DryRun = []string{metav1.DryRunAll} +} + +// PatchOptions contains options for patch requests. +type PatchOptions struct { + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + DryRun []string + + // Force is going to "force" Apply requests. It means user will + // re-acquire conflicting fields owned by other people. Force + // flag must be unset for non-apply patch requests. + // +optional + Force *bool + + // FieldManager is the name of the user or component submitting + // this request. It must be set with server-side apply. + FieldManager string + + // Raw represents raw PatchOptions, as passed to the API server. + Raw *metav1.PatchOptions +} + +// ApplyOptions executes the given PatchOptionFuncs, mutating these PatchOptions. +// It returns the mutated PatchOptions for convenience. +func (o *PatchOptions) ApplyOptions(optFuncs []PatchOptionFunc) *PatchOptions { + for _, optFunc := range optFuncs { + optFunc(o) + } + return o +} + +// AsPatchOptions returns these options as a metav1.PatchOptions. +// This may mutate the Raw field. +func (o *PatchOptions) AsPatchOptions() *metav1.PatchOptions { + if o == nil { + return &metav1.PatchOptions{} + } + if o.Raw == nil { + o.Raw = &metav1.PatchOptions{} + } + + o.Raw.DryRun = o.DryRun + o.Raw.Force = o.Force + o.Raw.FieldManager = o.FieldManager + return o.Raw +} + +// PatchOptionFunc is a function that mutates a PatchOptions struct. It implements +// the functional options pattern. See +// https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md. +type PatchOptionFunc func(*PatchOptions) + +// ForceOwnership sets the Force option, indicating that +// in case of conflicts with server-side apply, the client should +// acquire ownership of the conflicting field. Most controllers +// should use this. +var ForceOwnership PatchOptionFunc = func(opts *PatchOptions) { + definitelyTrue := true + opts.Force = &definitelyTrue +} + +// PatchDryRunAll is a functional option that sets the DryRun +// field of a PatchOptions struct to metav1.DryRunAll. +var PatchDryRunAll PatchOptionFunc = func(opts *PatchOptions) { + opts.DryRun = []string{metav1.DryRunAll} +} + +// FieldOwner set the field manager name for the given server-side apply patch. +func FieldOwner(name string) PatchOptionFunc { + return func(opts *PatchOptions) { + opts.FieldManager = name + } +} diff --git a/pkg/client/patch.go b/pkg/client/patch.go index 8951f2e5fe..ab7efc287b 100644 --- a/pkg/client/patch.go +++ b/pkg/client/patch.go @@ -23,6 +23,11 @@ import ( "k8s.io/apimachinery/pkg/util/json" ) +var ( + // Apply uses server-side apply to patch the given object. + Apply = applyPatch{} +) + type patch struct { patchType types.PatchType data []byte @@ -71,3 +76,20 @@ func (s *mergeFromPatch) Data(obj runtime.Object) ([]byte, error) { func MergeFrom(obj runtime.Object) Patch { return &mergeFromPatch{obj} } + +// applyPatch uses server-side apply to patch the object. +type applyPatch struct{} + +// Type implements Patch. +func (p applyPatch) Type() types.PatchType { + return types.ApplyPatchType +} + +// Data implements Patch. +func (p applyPatch) Data(obj runtime.Object) ([]byte, error) { + // NB(directxman12): we might techically want to be using an actual encoder + // here (in case some more performant encoder is introduced) but this is + // correct and sufficient for our uses (it's what the JSON serializer in + // client-go does, more-or-less). + return json.Marshal(obj) +} diff --git a/pkg/client/typed_client.go b/pkg/client/typed_client.go index 50f2817001..76f429b650 100644 --- a/pkg/client/typed_client.go +++ b/pkg/client/typed_client.go @@ -141,7 +141,7 @@ func (c *typedClient) List(ctx context.Context, obj runtime.Object, opts ...List } // UpdateStatus used by StatusWriter to write status. -func (c *typedClient) UpdateStatus(ctx context.Context, obj runtime.Object) error { +func (c *typedClient) UpdateStatus(ctx context.Context, obj runtime.Object, opts ...UpdateOptionFunc) error { o, err := c.cache.getObjMeta(obj) if err != nil { return err @@ -156,6 +156,32 @@ func (c *typedClient) UpdateStatus(ctx context.Context, obj runtime.Object) erro Name(o.GetName()). SubResource("status"). Body(obj). + VersionedParams((&UpdateOptions{}).ApplyOptions(opts).AsUpdateOptions(), c.paramCodec). + Context(ctx). + Do(). + Into(obj) +} + +// PatchStatus used by StatusWriter to write status. +func (c *typedClient) PatchStatus(ctx context.Context, obj runtime.Object, patch Patch, opts ...PatchOptionFunc) error { + o, err := c.cache.getObjMeta(obj) + if err != nil { + return err + } + + data, err := patch.Data(obj) + if err != nil { + return err + } + + patchOpts := &PatchOptions{} + return o.Patch(patch.Type()). + NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). + Resource(o.resource()). + Name(o.GetName()). + SubResource("status"). + Body(data). + VersionedParams(patchOpts.ApplyOptions(opts).AsPatchOptions(), c.paramCodec). Context(ctx). Do(). Into(obj) diff --git a/pkg/client/unstructured_client.go b/pkg/client/unstructured_client.go index d182bf3552..f13dd18854 100644 --- a/pkg/client/unstructured_client.go +++ b/pkg/client/unstructured_client.go @@ -160,7 +160,7 @@ func (uc *unstructuredClient) List(_ context.Context, obj runtime.Object, opts . return nil } -func (uc *unstructuredClient) UpdateStatus(_ context.Context, obj runtime.Object) error { +func (uc *unstructuredClient) UpdateStatus(_ context.Context, obj runtime.Object, opts ...UpdateOptionFunc) error { u, ok := obj.(*unstructured.Unstructured) if !ok { return fmt.Errorf("unstructured client did not understand object: %T", obj) @@ -169,7 +169,30 @@ func (uc *unstructuredClient) UpdateStatus(_ context.Context, obj runtime.Object if err != nil { return err } - i, err := r.UpdateStatus(u, metav1.UpdateOptions{}) + i, err := r.UpdateStatus(u, *(&UpdateOptions{}).ApplyOptions(opts).AsUpdateOptions()) + if err != nil { + return err + } + u.Object = i.Object + return nil +} + +func (uc *unstructuredClient) PatchStatus(_ context.Context, obj runtime.Object, patch Patch, opts ...PatchOptionFunc) error { + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("unstructured client did not understand object: %T", obj) + } + r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace()) + if err != nil { + return err + } + + data, err := patch.Data(obj) + if err != nil { + return err + } + + i, err := r.Patch(u.GetName(), patch.Type(), data, *(&PatchOptions{}).ApplyOptions(opts).AsPatchOptions(), "status") if err != nil { return err }