diff --git a/Gopkg.lock b/Gopkg.lock index 7b740359..cfe7125c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -136,15 +136,14 @@ revision = "f2b4162afba35581b6d4a50d3b8f34e33c144682" [[projects]] - branch = "master" - digest = "1:0f5c0a93ea5a62ea721282a07746832546580707042f705710f6e9f9778a0598" + digest = "1:fa6e19b10f3088d6f290e32ba2f9735d2810dd8e6544028d4d0c842c162b83ef" name = "github.com/knative/pkg" packages = [ "apis", "kmeta", ] pruneopts = "NUT" - revision = "2a7e950c4e2d635d6e32ba3d050dd32ba7be5be3" + revision = "60fdcbcabd2faeb34328d8b2725dc76c59189453" [[projects]] branch = "master" diff --git a/Gopkg.toml b/Gopkg.toml index 821a66f6..4378ba90 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -28,6 +28,11 @@ required = [ name = "k8s.io/client-go" version = "kubernetes-1.12.6" +[[override]] + name = "github.com/knative/pkg" + # HEAD as of 2019-03-21 + revision = "60fdcbcabd2faeb34328d8b2725dc76c59189453" + [prune] go-tests = true unused-packages = true diff --git a/pkg/apis/caching/v1alpha1/image_defaults.go b/pkg/apis/caching/v1alpha1/image_defaults.go index d709e639..5aa4471c 100644 --- a/pkg/apis/caching/v1alpha1/image_defaults.go +++ b/pkg/apis/caching/v1alpha1/image_defaults.go @@ -16,6 +16,8 @@ limitations under the License. package v1alpha1 -func (r *Image) SetDefaults() { +import "context" + +func (r *Image) SetDefaults(ctx context.Context) { // TODO(mattmoor): This } diff --git a/pkg/apis/caching/v1alpha1/image_defaults_test.go b/pkg/apis/caching/v1alpha1/image_defaults_test.go index 7a97643d..5205a6a3 100644 --- a/pkg/apis/caching/v1alpha1/image_defaults_test.go +++ b/pkg/apis/caching/v1alpha1/image_defaults_test.go @@ -17,6 +17,7 @@ limitations under the License. package v1alpha1 import ( + "context" "testing" "github.com/google/go-cmp/cmp" @@ -36,7 +37,7 @@ func TestImageDefaulting(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { got := test.in - got.SetDefaults() + got.SetDefaults(context.Background()) if diff := cmp.Diff(test.want, got); diff != "" { t.Errorf("SetDefaults (-want, +got) = %v", diff) } diff --git a/pkg/apis/caching/v1alpha1/image_validation.go b/pkg/apis/caching/v1alpha1/image_validation.go index f604f47f..33a50578 100644 --- a/pkg/apis/caching/v1alpha1/image_validation.go +++ b/pkg/apis/caching/v1alpha1/image_validation.go @@ -17,6 +17,7 @@ limitations under the License. package v1alpha1 import ( + "context" "fmt" corev1 "k8s.io/api/core/v1" @@ -25,11 +26,11 @@ import ( "github.com/knative/pkg/apis" ) -func (rt *Image) Validate() *apis.FieldError { - return rt.Spec.Validate().ViaField("spec") +func (rt *Image) Validate(ctx context.Context) *apis.FieldError { + return rt.Spec.Validate(ctx).ViaField("spec") } -func (rs *ImageSpec) Validate() *apis.FieldError { +func (rs *ImageSpec) Validate(ctx context.Context) *apis.FieldError { if rs.Image == "" { return apis.ErrMissingField("image") } diff --git a/pkg/apis/caching/v1alpha1/image_validation_test.go b/pkg/apis/caching/v1alpha1/image_validation_test.go index 8906ec80..cc1037d9 100644 --- a/pkg/apis/caching/v1alpha1/image_validation_test.go +++ b/pkg/apis/caching/v1alpha1/image_validation_test.go @@ -17,9 +17,9 @@ limitations under the License. package v1alpha1 import ( + "context" "testing" - "github.com/google/go-cmp/cmp" corev1 "k8s.io/api/core/v1" "github.com/knative/pkg/apis" @@ -55,9 +55,12 @@ func TestImageValidation(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got := test.r.Validate() - if diff := cmp.Diff(test.want, got); diff != "" { - t.Errorf("Validate (-want, +got) = %v", diff) + got := test.r.Validate(context.Background()) + if (test.want == nil) != (got == nil) { + t.Fatalf("Validate() = %v, wanted %v", got, test.want) + } + if want, got := test.want.Error(), got.Error(); want != got { + t.Fatalf("Validate() = %v, wanted %v", got, want) } }) } diff --git a/vendor/github.com/knative/pkg/apis/field_error.go b/vendor/github.com/knative/pkg/apis/field_error.go index 50a9a251..e3c3ae4e 100644 --- a/vendor/github.com/knative/pkg/apis/field_error.go +++ b/vendor/github.com/knative/pkg/apis/field_error.go @@ -18,6 +18,7 @@ package apis import ( "fmt" + "sort" "strings" ) @@ -28,12 +29,15 @@ const CurrentField = "" // FieldError is used to propagate the context of errors pertaining to // specific fields in a manner suitable for use in a recursive walk, so // that errors contain the appropriate field context. -// +k8s:deepcopy-gen=false +// FieldError methods are non-mutating. +// +k8s:deepcopy-gen=true type FieldError struct { Message string Paths []string // Details contains an optional longer payload. + // +optional Details string + errors []FieldError } // FieldError implements error @@ -50,27 +54,234 @@ func (fe *FieldError) ViaField(prefix ...string) *FieldError { if fe == nil { return nil } - var newPaths []string + // Copy over message and details, paths will be updated and errors come + // along using .Also(). + newErr := &FieldError{ + Message: fe.Message, + Details: fe.Details, + } + + // Prepend the Prefix to existing errors. + newPaths := make([]string, 0, len(fe.Paths)) for _, oldPath := range fe.Paths { - if oldPath == CurrentField { - newPaths = append(newPaths, strings.Join(prefix, ".")) - } else { - newPaths = append(newPaths, - strings.Join(append(prefix, oldPath), ".")) + newPaths = append(newPaths, flatten(append(prefix, oldPath))) + } + newErr.Paths = newPaths + for _, e := range fe.errors { + newErr = newErr.Also(e.ViaField(prefix...)) + } + return newErr +} + +// ViaIndex is used to attach an index to the next ViaField provided. +// For example, if a type recursively validates a parameter that has a collection: +// for i, c := range spec.Collection { +// if err := doValidation(c); err != nil { +// return err.ViaIndex(i).ViaField("collection") +// } +// } +func (fe *FieldError) ViaIndex(index int) *FieldError { + return fe.ViaField(asIndex(index)) +} + +// ViaFieldIndex is the short way to chain: err.ViaIndex(bar).ViaField(foo) +func (fe *FieldError) ViaFieldIndex(field string, index int) *FieldError { + return fe.ViaIndex(index).ViaField(field) +} + +// ViaKey is used to attach a key to the next ViaField provided. +// For example, if a type recursively validates a parameter that has a collection: +// for k, v := range spec.Bag. { +// if err := doValidation(v); err != nil { +// return err.ViaKey(k).ViaField("bag") +// } +// } +func (fe *FieldError) ViaKey(key string) *FieldError { + return fe.ViaField(asKey(key)) +} + +// ViaFieldKey is the short way to chain: err.ViaKey(bar).ViaField(foo) +func (fe *FieldError) ViaFieldKey(field string, key string) *FieldError { + return fe.ViaKey(key).ViaField(field) +} + +// Also collects errors, returns a new collection of existing errors and new errors. +func (fe *FieldError) Also(errs ...*FieldError) *FieldError { + var newErr *FieldError + // collect the current objects errors, if it has any + if !fe.isEmpty() { + newErr = fe.DeepCopy() + } else { + newErr = &FieldError{} + } + // and then collect the passed in errors + for _, e := range errs { + if !e.isEmpty() { + newErr.errors = append(newErr.errors, *e) } } - fe.Paths = newPaths - return fe + if newErr.isEmpty() { + return nil + } + return newErr +} + +func (fe *FieldError) isEmpty() bool { + if fe == nil { + return true + } + return fe.Message == "" && fe.Details == "" && len(fe.errors) == 0 && len(fe.Paths) == 0 +} + +// normalized returns a flattened copy of all the errors. +func (fe *FieldError) normalized() []*FieldError { + // In case we call normalized on a nil object, return just an empty + // list. This can happen when .Error() is called on a nil object. + if fe == nil { + return []*FieldError(nil) + } + + // Allocate errors with at least as many objects as we'll get on the first pass. + errors := make([]*FieldError, 0, len(fe.errors)+1) + // If this FieldError is a leaf, add it. + if fe.Message != "" { + errors = append(errors, &FieldError{ + Message: fe.Message, + Paths: fe.Paths, + Details: fe.Details, + }) + } + // And then collect all other errors recursively. + for _, e := range fe.errors { + errors = append(errors, e.normalized()...) + } + return errors } // Error implements error func (fe *FieldError) Error() string { - if fe.Details == "" { - return fmt.Sprintf("%v: %v", fe.Message, strings.Join(fe.Paths, ", ")) + // Get the list of errors as a flat merged list. + normedErrors := merge(fe.normalized()) + errs := make([]string, 0, len(normedErrors)) + for _, e := range normedErrors { + if e.Details == "" { + errs = append(errs, fmt.Sprintf("%v: %v", e.Message, strings.Join(e.Paths, ", "))) + } else { + errs = append(errs, fmt.Sprintf("%v: %v\n%v", e.Message, strings.Join(e.Paths, ", "), e.Details)) + } } - return fmt.Sprintf("%v: %v\n%v", fe.Message, strings.Join(fe.Paths, ", "), fe.Details) + return strings.Join(errs, "\n") } +// Helpers --- + +func asIndex(index int) string { + return fmt.Sprintf("[%d]", index) +} + +func isIndex(part string) bool { + return strings.HasPrefix(part, "[") && strings.HasSuffix(part, "]") +} + +func asKey(key string) string { + return fmt.Sprintf("[%s]", key) +} + +// flatten takes in a array of path components and looks for chances to flatten +// objects that have index prefixes, examples: +// err([0]).ViaField(bar).ViaField(foo) -> foo.bar.[0] converts to foo.bar[0] +// err(bar).ViaIndex(0).ViaField(foo) -> foo.[0].bar converts to foo[0].bar +// err(bar).ViaField(foo).ViaIndex(0) -> [0].foo.bar converts to [0].foo.bar +// err(bar).ViaIndex(0).ViaIndex(1).ViaField(foo) -> foo.[1].[0].bar converts to foo[1][0].bar +func flatten(path []string) string { + var newPath []string + for _, part := range path { + for _, p := range strings.Split(part, ".") { + if p == CurrentField { + continue + } else if len(newPath) > 0 && isIndex(p) { + newPath[len(newPath)-1] += p + } else { + newPath = append(newPath, p) + } + } + } + return strings.Join(newPath, ".") +} + +// mergePaths takes in two string slices and returns the combination of them +// without any duplicate entries. +func mergePaths(a, b []string) []string { + newPaths := make([]string, 0, len(a)+len(b)) + newPaths = append(newPaths, a...) + for _, bi := range b { + if !containsString(newPaths, bi) { + newPaths = append(newPaths, bi) + } + } + return newPaths +} + +// containsString takes in a string slice and looks for the provided string +// within the slice. +func containsString(slice []string, s string) bool { + for _, item := range slice { + if item == s { + return true + } + } + return false +} + +// merge takes in a flat list of FieldErrors and returns back a merged list of +// FieldErrors. FieldErrors have their Paths combined (and de-duped) if their +// Message and Details are the same. Merge will not inspect FieldError.errors. +// Merge will also sort the .Path slice, and the errors slice before returning. +func merge(errs []*FieldError) []*FieldError { + // make a map big enough for all the errors. + m := make(map[string]*FieldError, len(errs)) + + // Convert errs to a map where the key is -
and the value + // is the error. If an error already exists in the map with the same key, + // then the paths will be merged. + for _, e := range errs { + k := key(e) + if v, ok := m[k]; ok { + // Found a match, merge the keys. + v.Paths = mergePaths(v.Paths, e.Paths) + } else { + // Does not exist in the map, save the error. + m[k] = e + } + } + + // Take the map made previously and flatten it back out again. + newErrs := make([]*FieldError, 0, len(m)) + for _, v := range m { + // While we have access to the merged paths, sort them too. + sort.Slice(v.Paths, func(i, j int) bool { return v.Paths[i] < v.Paths[j] }) + newErrs = append(newErrs, v) + } + + // Sort the flattened map. + sort.Slice(newErrs, func(i, j int) bool { + if newErrs[i].Message == newErrs[j].Message { + return newErrs[i].Details < newErrs[j].Details + } + return newErrs[i].Message < newErrs[j].Message + }) + + // return back the merged list of sorted errors. + return newErrs +} + +// key returns the key using the fields .Message and .Details. +func key(err *FieldError) string { + return fmt.Sprintf("%s-%s", err.Message, err.Details) +} + +// Public helpers --- + // ErrMissingField is a variadic helper method for constructing a FieldError for // a set of missing fields. func ErrMissingField(fieldPaths ...string) *FieldError { @@ -89,6 +300,12 @@ func ErrDisallowedFields(fieldPaths ...string) *FieldError { } } +// ErrInvalidArrayValue consturcts a FieldError for a repetetive `field` +// at `index` that has received an invalid string value. +func ErrInvalidArrayValue(value, field string, index int) *FieldError { + return ErrInvalidValue(value, CurrentField).ViaFieldIndex(field, index) +} + // ErrInvalidValue constructs a FieldError for a field that has received an // invalid string value. func ErrInvalidValue(value, fieldPath string) *FieldError { @@ -116,8 +333,8 @@ func ErrMultipleOneOf(fieldPaths ...string) *FieldError { } } -// ErrInvalidKeyName is a variadic helper method for constructing a -// FieldError that specifies a key name that is invalid. +// ErrInvalidKeyName is a variadic helper method for constructing a FieldError +// that specifies a key name that is invalid. func ErrInvalidKeyName(value, fieldPath string, details ...string) *FieldError { return &FieldError{ Message: fmt.Sprintf("invalid key name %q", value), @@ -125,3 +342,12 @@ func ErrInvalidKeyName(value, fieldPath string, details ...string) *FieldError { Details: strings.Join(details, ", "), } } + +// ErrOutOfBoundsValue constructs a FieldError for a field that has received an +// out of bound value. +func ErrOutOfBoundsValue(value, lower, upper, fieldPath string) *FieldError { + return &FieldError{ + Message: fmt.Sprintf("expected %s <= %s <= %s", lower, value, upper), + Paths: []string{fieldPath}, + } +} diff --git a/vendor/github.com/knative/pkg/apis/interfaces.go b/vendor/github.com/knative/pkg/apis/interfaces.go index f8c40f52..12ddd2aa 100644 --- a/vendor/github.com/knative/pkg/apis/interfaces.go +++ b/vendor/github.com/knative/pkg/apis/interfaces.go @@ -16,16 +16,23 @@ limitations under the License. package apis +import ( + "context" + + authenticationv1 "k8s.io/api/authentication/v1" + "k8s.io/apimachinery/pkg/runtime" +) + // Defaultable defines an interface for setting the defaults for the // uninitialized fields of this instance. type Defaultable interface { - SetDefaults() + SetDefaults(context.Context) } // Validatable indicates that a particular type may have its fields validated. type Validatable interface { // Validate checks the validity of this types fields. - Validate() *FieldError + Validate(context.Context) *FieldError } // Immutable indicates that a particular type has fields that should @@ -33,5 +40,18 @@ type Validatable interface { type Immutable interface { // CheckImmutableFields checks that the current instance's immutable // fields haven't changed from the provided original. - CheckImmutableFields(original Immutable) *FieldError + CheckImmutableFields(ctx context.Context, original Immutable) *FieldError +} + +// Listable indicates that a particular type can be returned via the returned +// list type by the API server. +type Listable interface { + runtime.Object + + GetListType() runtime.Object +} + +// Annotatable indicates that a particular type applies various annotations. +type Annotatable interface { + AnnotateUserInfo(ctx context.Context, previous Annotatable, ui *authenticationv1.UserInfo) } diff --git a/vendor/github.com/knative/pkg/apis/kind2resource.go b/vendor/github.com/knative/pkg/apis/kind2resource.go new file mode 100644 index 00000000..37ffe080 --- /dev/null +++ b/vendor/github.com/knative/pkg/apis/kind2resource.go @@ -0,0 +1,47 @@ +/* +Copyright 2018 The Knative 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 apis + +import ( + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// KindToResource converts a GroupVersionKind to a GroupVersionResource +// through the world's simplest (worst) pluralizer. +func KindToResource(gvk schema.GroupVersionKind) schema.GroupVersionResource { + return schema.GroupVersionResource{ + Group: gvk.Group, + Version: gvk.Version, + Resource: pluralizeKind(gvk.Kind), + } +} + +// Takes a kind and pluralizes it. This is super terrible, but I am +// not aware of a generic way to do this. +// I am not alone in thinking this and I haven't found a better solution: +// This seems relevant: +// https://github.com/kubernetes/kubernetes/issues/18622 +func pluralizeKind(kind string) string { + ret := strings.ToLower(kind) + if strings.HasSuffix(ret, "s") { + return fmt.Sprintf("%ses", ret) + } + return fmt.Sprintf("%ss", ret) +} diff --git a/vendor/github.com/knative/pkg/apis/zz_generated.deepcopy.go b/vendor/github.com/knative/pkg/apis/zz_generated.deepcopy.go index bacb0b70..76db35ce 100644 --- a/vendor/github.com/knative/pkg/apis/zz_generated.deepcopy.go +++ b/vendor/github.com/knative/pkg/apis/zz_generated.deepcopy.go @@ -20,6 +20,34 @@ limitations under the License. package apis +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FieldError) DeepCopyInto(out *FieldError) { + *out = *in + if in.Paths != nil { + in, out := &in.Paths, &out.Paths + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.errors != nil { + in, out := &in.errors, &out.errors + *out = make([]FieldError, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FieldError. +func (in *FieldError) DeepCopy() *FieldError { + if in == nil { + return nil + } + out := new(FieldError) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VolatileTime) DeepCopyInto(out *VolatileTime) { *out = *in diff --git a/vendor/github.com/knative/pkg/kmeta/accessor.go b/vendor/github.com/knative/pkg/kmeta/accessor.go new file mode 100644 index 00000000..07c69bed --- /dev/null +++ b/vendor/github.com/knative/pkg/kmeta/accessor.go @@ -0,0 +1,94 @@ +/* +Copyright 2018 The Knative 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 kmeta + +import ( + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/cache" +) + +// Accessor is a collection of interfaces from metav1.TypeMeta, +// runtime.Object and metav1.Object that Kubernetes API types +// registered with runtime.Scheme must support. +type Accessor interface { + // Interfaces for metav1.TypeMeta + GroupVersionKind() schema.GroupVersionKind + SetGroupVersionKind(gvk schema.GroupVersionKind) + + // Interfaces for runtime.Object + GetObjectKind() schema.ObjectKind + DeepCopyObject() runtime.Object + + // Interfaces for metav1.Object + GetNamespace() string + SetNamespace(namespace string) + GetName() string + SetName(name string) + GetGenerateName() string + SetGenerateName(name string) + GetUID() types.UID + SetUID(uid types.UID) + GetResourceVersion() string + SetResourceVersion(version string) + GetGeneration() int64 + SetGeneration(generation int64) + GetSelfLink() string + SetSelfLink(selfLink string) + GetCreationTimestamp() metav1.Time + SetCreationTimestamp(timestamp metav1.Time) + GetDeletionTimestamp() *metav1.Time + SetDeletionTimestamp(timestamp *metav1.Time) + GetDeletionGracePeriodSeconds() *int64 + SetDeletionGracePeriodSeconds(*int64) + GetLabels() map[string]string + SetLabels(labels map[string]string) + GetAnnotations() map[string]string + SetAnnotations(annotations map[string]string) + GetInitializers() *metav1.Initializers + SetInitializers(initializers *metav1.Initializers) + GetFinalizers() []string + SetFinalizers(finalizers []string) + GetOwnerReferences() []metav1.OwnerReference + SetOwnerReferences([]metav1.OwnerReference) + GetClusterName() string + SetClusterName(clusterName string) +} + +// DeletionHandlingAccessor tries to convert given interface into Accessor first; +// and to handle deletion, it try to fetch info from DeletedFinalStateUnknown on failure. +// The name is a reference to cache.DeletionHandlingMetaNamespaceKeyFunc +func DeletionHandlingAccessor(obj interface{}) (Accessor, error) { + accessor, ok := obj.(Accessor) + if !ok { + // To handle obj deletion, try to fetch info from DeletedFinalStateUnknown. + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + return nil, fmt.Errorf("Couldn't get Accessor from tombstone %#v", obj) + } + accessor, ok = tombstone.Obj.(Accessor) + if !ok { + return nil, fmt.Errorf("The object that Tombstone contained is not of kmeta.Accessor %#v", obj) + } + } + + return accessor, nil +} diff --git a/vendor/github.com/knative/pkg/kmeta/labels.go b/vendor/github.com/knative/pkg/kmeta/labels.go index e4cd1163..f9a72d8b 100644 --- a/vendor/github.com/knative/pkg/kmeta/labels.go +++ b/vendor/github.com/knative/pkg/kmeta/labels.go @@ -26,6 +26,13 @@ import ( // The methods in this file are used for managing subresources in cases where // a controller instantiates different resources for each version of itself. +// There are two sets of methods available here: +// * `*VersionLabel*`: these methods act on `metadata.resourceVersion` and +// create new labels for EVERY change to the resource (incl. `/status`). +// * `*GenerationLabel*`: these methods act on `metadata.generation` and +// create new labels for changes to the resource's "spec" (typically, but +// some K8s resources change `metadata.generation` for annotations as well +// e.g. Deployment). // // For example, if an A might instantiate N B's at version 1 and M B's at // version 2 then it can use MakeVersionLabels to decorate each subresource @@ -66,6 +73,37 @@ func MakeOldVersionLabelSelector(om metav1.ObjectMetaAccessor) labels.Selector { ) } +// MakeGenerationLabels constructs a set of labels to apply to subresources +// instantiated at this version of the parent resource, so that we can +// efficiently select them. +func MakeGenerationLabels(om metav1.ObjectMetaAccessor) labels.Set { + return map[string]string{ + "controller": string(om.GetObjectMeta().GetUID()), + "generation": genStr(om), + } +} + +// MakeGenerationLabelSelector constructs a selector for subresources +// instantiated at this version of the parent resource. This keys +// off of the labels populated by MakeGenerationLabels. +func MakeGenerationLabelSelector(om metav1.ObjectMetaAccessor) labels.Selector { + return labels.SelectorFromSet(MakeGenerationLabels(om)) +} + +// MakeOldGenerationLabelSelector constructs a selector for subresources +// instantiated at an older version of the parent resource. This keys +// off of the labels populated by MakeGenerationLabels. +func MakeOldGenerationLabelSelector(om metav1.ObjectMetaAccessor) labels.Selector { + return labels.NewSelector().Add( + mustNewRequirement("controller", selection.Equals, []string{string(om.GetObjectMeta().GetUID())}), + mustNewRequirement("generation", selection.NotEquals, []string{genStr(om)}), + ) +} + +func genStr(om metav1.ObjectMetaAccessor) string { + return fmt.Sprintf("%05d", om.GetObjectMeta().GetGeneration()) +} + // mustNewRequirement panics if there are any errors constructing our selectors. func mustNewRequirement(key string, op selection.Operator, vals []string) labels.Requirement { r, err := labels.NewRequirement(key, op, vals)