Eliminate custom methods from GenericCRD (#78)

* Prune the GenericCRD spec to what is used.

Encapsulate our change detection slightly.

* Support common spec mutations via duck typing.

This adds support for performing common mutations to objects via duck types and JSON patching.

Fixes: https://github.com/knative/pkg/issues/76

* Eliminate getSpecJSON thru schemaless duck typing.

This leverages a one-off trick to get the JSON of the spec field from arbitrary types.
This commit is contained in:
Matt Moore 2018-09-18 14:25:19 -07:00 committed by Knative Prow Robot
parent 6f0348fb69
commit c15d7c8f22
16 changed files with 763 additions and 93 deletions

View File

@ -0,0 +1,76 @@
/*
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 v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/knative/pkg/apis/duck"
)
// Generation is the schema for the generational portion of the payload
type Generation int64
// Implementations can verify that they implement Generation via:
var emptyGen Generation
var _ = duck.VerifyType(&Generational{}, &emptyGen)
// Generation is an Implementable "duck type".
var _ duck.Implementable = (*Generation)(nil)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Generational is a skeleton type wrapping Generation in the manner we expect
// resource writers defining compatible resources to embed it. We will
// typically use this type to deserialize Generation ObjectReferences and
// access the Generation data. This is not a real resource.
type Generational struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec GenerationalSpec `json:"spec"`
}
// GenerationalSpec shows how we expect folks to embed Generation in
// their Spec field.
type GenerationalSpec struct {
Generation Generation `json:"generation,omitempty"`
}
// In order for Generation to be Implementable, Generational must be Populatable.
var _ duck.Populatable = (*Generational)(nil)
// GetFullType implements duck.Implementable
func (_ *Generation) GetFullType() duck.Populatable {
return &Generational{}
}
// Populate implements duck.Populatable
func (t *Generational) Populate() {
t.Spec.Generation = 1234
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// GenerationalList is a list of Generational resources
type GenerationalList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []Generational `json:"items"`
}

View File

@ -63,6 +63,82 @@ func (in Conditions) DeepCopy() Conditions {
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Generational) DeepCopyInto(out *Generational) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Generational.
func (in *Generational) DeepCopy() *Generational {
if in == nil {
return nil
}
out := new(Generational)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Generational) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GenerationalList) DeepCopyInto(out *GenerationalList) {
*out = *in
out.TypeMeta = in.TypeMeta
out.ListMeta = in.ListMeta
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Generational, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenerationalList.
func (in *GenerationalList) DeepCopy() *GenerationalList {
if in == nil {
return nil
}
out := new(GenerationalList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *GenerationalList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GenerationalSpec) DeepCopyInto(out *GenerationalSpec) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenerationalSpec.
func (in *GenerationalSpec) DeepCopy() *GenerationalSpec {
if in == nil {
return nil
}
out := new(GenerationalSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KResource) DeepCopyInto(out *KResource) {
*out = *in

View File

@ -27,6 +27,7 @@ import (
type DuckV1alpha1Interface interface {
RESTClient() rest.Interface
GenerationalsGetter
KResourcesGetter
TargetsGetter
TopicsGetter
@ -37,6 +38,10 @@ type DuckV1alpha1Client struct {
restClient rest.Interface
}
func (c *DuckV1alpha1Client) Generationals(namespace string) GenerationalInterface {
return newGenerationals(c, namespace)
}
func (c *DuckV1alpha1Client) KResources(namespace string) KResourceInterface {
return newKResources(c, namespace)
}

View File

@ -28,6 +28,10 @@ type FakeDuckV1alpha1 struct {
*testing.Fake
}
func (c *FakeDuckV1alpha1) Generationals(namespace string) v1alpha1.GenerationalInterface {
return &FakeGenerationals{c, namespace}
}
func (c *FakeDuckV1alpha1) KResources(namespace string) v1alpha1.KResourceInterface {
return &FakeKResources{c, namespace}
}

View File

@ -0,0 +1,128 @@
/*
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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeGenerationals implements GenerationalInterface
type FakeGenerationals struct {
Fake *FakeDuckV1alpha1
ns string
}
var generationalsResource = schema.GroupVersionResource{Group: "duck.knative.dev", Version: "v1alpha1", Resource: "generationals"}
var generationalsKind = schema.GroupVersionKind{Group: "duck.knative.dev", Version: "v1alpha1", Kind: "Generational"}
// Get takes name of the generational, and returns the corresponding generational object, and an error if there is any.
func (c *FakeGenerationals) Get(name string, options v1.GetOptions) (result *v1alpha1.Generational, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(generationalsResource, c.ns, name), &v1alpha1.Generational{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.Generational), err
}
// List takes label and field selectors, and returns the list of Generationals that match those selectors.
func (c *FakeGenerationals) List(opts v1.ListOptions) (result *v1alpha1.GenerationalList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(generationalsResource, generationalsKind, c.ns, opts), &v1alpha1.GenerationalList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.GenerationalList{ListMeta: obj.(*v1alpha1.GenerationalList).ListMeta}
for _, item := range obj.(*v1alpha1.GenerationalList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested generationals.
func (c *FakeGenerationals) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(generationalsResource, c.ns, opts))
}
// Create takes the representation of a generational and creates it. Returns the server's representation of the generational, and an error, if there is any.
func (c *FakeGenerationals) Create(generational *v1alpha1.Generational) (result *v1alpha1.Generational, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(generationalsResource, c.ns, generational), &v1alpha1.Generational{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.Generational), err
}
// Update takes the representation of a generational and updates it. Returns the server's representation of the generational, and an error, if there is any.
func (c *FakeGenerationals) Update(generational *v1alpha1.Generational) (result *v1alpha1.Generational, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(generationalsResource, c.ns, generational), &v1alpha1.Generational{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.Generational), err
}
// Delete takes name of the generational and deletes it. Returns an error if one occurs.
func (c *FakeGenerationals) Delete(name string, options *v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(generationalsResource, c.ns, name), &v1alpha1.Generational{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeGenerationals) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(generationalsResource, c.ns, listOptions)
_, err := c.Fake.Invokes(action, &v1alpha1.GenerationalList{})
return err
}
// Patch applies the patch and returns the patched generational.
func (c *FakeGenerationals) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Generational, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(generationalsResource, c.ns, name, data, subresources...), &v1alpha1.Generational{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.Generational), err
}

View File

@ -18,6 +18,8 @@ limitations under the License.
package v1alpha1
type GenerationalExpansion interface{}
type KResourceExpansion interface{}
type TargetExpansion interface{}

View File

@ -0,0 +1,157 @@
/*
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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
import (
v1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
scheme "github.com/knative/pkg/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// GenerationalsGetter has a method to return a GenerationalInterface.
// A group's client should implement this interface.
type GenerationalsGetter interface {
Generationals(namespace string) GenerationalInterface
}
// GenerationalInterface has methods to work with Generational resources.
type GenerationalInterface interface {
Create(*v1alpha1.Generational) (*v1alpha1.Generational, error)
Update(*v1alpha1.Generational) (*v1alpha1.Generational, error)
Delete(name string, options *v1.DeleteOptions) error
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
Get(name string, options v1.GetOptions) (*v1alpha1.Generational, error)
List(opts v1.ListOptions) (*v1alpha1.GenerationalList, error)
Watch(opts v1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Generational, err error)
GenerationalExpansion
}
// generationals implements GenerationalInterface
type generationals struct {
client rest.Interface
ns string
}
// newGenerationals returns a Generationals
func newGenerationals(c *DuckV1alpha1Client, namespace string) *generationals {
return &generationals{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the generational, and returns the corresponding generational object, and an error if there is any.
func (c *generationals) Get(name string, options v1.GetOptions) (result *v1alpha1.Generational, err error) {
result = &v1alpha1.Generational{}
err = c.client.Get().
Namespace(c.ns).
Resource("generationals").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of Generationals that match those selectors.
func (c *generationals) List(opts v1.ListOptions) (result *v1alpha1.GenerationalList, err error) {
result = &v1alpha1.GenerationalList{}
err = c.client.Get().
Namespace(c.ns).
Resource("generationals").
VersionedParams(&opts, scheme.ParameterCodec).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested generationals.
func (c *generationals) Watch(opts v1.ListOptions) (watch.Interface, error) {
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("generationals").
VersionedParams(&opts, scheme.ParameterCodec).
Watch()
}
// Create takes the representation of a generational and creates it. Returns the server's representation of the generational, and an error, if there is any.
func (c *generationals) Create(generational *v1alpha1.Generational) (result *v1alpha1.Generational, err error) {
result = &v1alpha1.Generational{}
err = c.client.Post().
Namespace(c.ns).
Resource("generationals").
Body(generational).
Do().
Into(result)
return
}
// Update takes the representation of a generational and updates it. Returns the server's representation of the generational, and an error, if there is any.
func (c *generationals) Update(generational *v1alpha1.Generational) (result *v1alpha1.Generational, err error) {
result = &v1alpha1.Generational{}
err = c.client.Put().
Namespace(c.ns).
Resource("generationals").
Name(generational.Name).
Body(generational).
Do().
Into(result)
return
}
// Delete takes name of the generational and deletes it. Returns an error if one occurs.
func (c *generationals) Delete(name string, options *v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("generationals").
Name(name).
Body(options).
Do().
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *generationals) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("generationals").
VersionedParams(&listOptions, scheme.ParameterCodec).
Body(options).
Do().
Error()
}
// Patch applies the patch and returns the patched generational.
func (c *generationals) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Generational, err error) {
result = &v1alpha1.Generational{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("generationals").
SubResource(subresources...).
Name(name).
Body(data).
Do().
Into(result)
return
}

View File

@ -0,0 +1,89 @@
/*
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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
import (
time "time"
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
versioned "github.com/knative/pkg/client/clientset/versioned"
internalinterfaces "github.com/knative/pkg/client/informers/externalversions/internalinterfaces"
v1alpha1 "github.com/knative/pkg/client/listers/duck/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// GenerationalInformer provides access to a shared informer and lister for
// Generationals.
type GenerationalInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.GenerationalLister
}
type generationalInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewGenerationalInformer constructs a new informer for Generational type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewGenerationalInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredGenerationalInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredGenerationalInformer constructs a new informer for Generational type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredGenerationalInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.DuckV1alpha1().Generationals(namespace).List(options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.DuckV1alpha1().Generationals(namespace).Watch(options)
},
},
&duckv1alpha1.Generational{},
resyncPeriod,
indexers,
)
}
func (f *generationalInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredGenerationalInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *generationalInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&duckv1alpha1.Generational{}, f.defaultInformer)
}
func (f *generationalInformer) Lister() v1alpha1.GenerationalLister {
return v1alpha1.NewGenerationalLister(f.Informer().GetIndexer())
}

View File

@ -24,6 +24,8 @@ import (
// Interface provides access to all the informers in this group version.
type Interface interface {
// Generationals returns a GenerationalInformer.
Generationals() GenerationalInformer
// KResources returns a KResourceInformer.
KResources() KResourceInformer
// Targets returns a TargetInformer.
@ -43,6 +45,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// Generationals returns a GenerationalInformer.
func (v *version) Generationals() GenerationalInformer {
return &generationalInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// KResources returns a KResourceInformer.
func (v *version) KResources() KResourceInformer {
return &kResourceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}

View File

@ -59,6 +59,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Authentication().V1alpha1().Policies().Informer()}, nil
// Group=duck.knative.dev, Version=v1alpha1
case duckv1alpha1.SchemeGroupVersion.WithResource("generationals"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Duck().V1alpha1().Generationals().Informer()}, nil
case duckv1alpha1.SchemeGroupVersion.WithResource("kresources"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Duck().V1alpha1().KResources().Informer()}, nil
case duckv1alpha1.SchemeGroupVersion.WithResource("targets"):

View File

@ -18,6 +18,14 @@ limitations under the License.
package v1alpha1
// GenerationalListerExpansion allows custom methods to be added to
// GenerationalLister.
type GenerationalListerExpansion interface{}
// GenerationalNamespaceListerExpansion allows custom methods to be added to
// GenerationalNamespaceLister.
type GenerationalNamespaceListerExpansion interface{}
// KResourceListerExpansion allows custom methods to be added to
// KResourceLister.
type KResourceListerExpansion interface{}

View File

@ -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.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
import (
v1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// GenerationalLister helps list Generationals.
type GenerationalLister interface {
// List lists all Generationals in the indexer.
List(selector labels.Selector) (ret []*v1alpha1.Generational, err error)
// Generationals returns an object that can list and get Generationals.
Generationals(namespace string) GenerationalNamespaceLister
GenerationalListerExpansion
}
// generationalLister implements the GenerationalLister interface.
type generationalLister struct {
indexer cache.Indexer
}
// NewGenerationalLister returns a new GenerationalLister.
func NewGenerationalLister(indexer cache.Indexer) GenerationalLister {
return &generationalLister{indexer: indexer}
}
// List lists all Generationals in the indexer.
func (s *generationalLister) List(selector labels.Selector) (ret []*v1alpha1.Generational, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.Generational))
})
return ret, err
}
// Generationals returns an object that can list and get Generationals.
func (s *generationalLister) Generationals(namespace string) GenerationalNamespaceLister {
return generationalNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// GenerationalNamespaceLister helps list and get Generationals.
type GenerationalNamespaceLister interface {
// List lists all Generationals in the indexer for a given namespace.
List(selector labels.Selector) (ret []*v1alpha1.Generational, err error)
// Get retrieves the Generational from the indexer for a given namespace and name.
Get(name string) (*v1alpha1.Generational, error)
GenerationalNamespaceListerExpansion
}
// generationalNamespaceLister implements the GenerationalNamespaceLister
// interface.
type generationalNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all Generationals in the indexer for a given namespace.
func (s generationalNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Generational, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.Generational))
})
return ret, err
}
// Get retrieves the Generational from the indexer for a given namespace and name.
func (s generationalNamespaceLister) Get(name string) (*v1alpha1.Generational, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("generational"), name)
}
return obj.(*v1alpha1.Generational), nil
}

View File

@ -17,12 +17,13 @@ limitations under the License.
package testing
import (
"encoding/json"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/knative/pkg/apis"
"github.com/knative/pkg/apis/duck"
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@ -40,6 +41,10 @@ var _ apis.Validatable = (*Resource)(nil)
var _ apis.Defaultable = (*Resource)(nil)
var _ apis.Immutable = (*Resource)(nil)
// Check that we implement the Generation duck type.
var emptyGen duckv1alpha1.Generation
var _ = duck.VerifyType(&Resource{}, &emptyGen)
type ResourceSpec struct {
Generation int64 `json:"generation,omitempty"`
@ -48,18 +53,6 @@ type ResourceSpec struct {
FieldThatsImmutable string `json:"fieldThatsImmutable,omitempty"`
}
func (r *Resource) GetGeneration() int64 {
return r.Spec.Generation
}
func (r *Resource) SetGeneration(generation int64) {
r.Spec.Generation = generation
}
func (r *Resource) GetSpecJSON() ([]byte, error) {
return json.Marshal(r.Spec)
}
func (c *Resource) SetDefaults() {
c.Spec.SetDefaults()
}

View File

@ -33,6 +33,8 @@ import (
"go.uber.org/zap"
"github.com/knative/pkg/apis"
"github.com/knative/pkg/apis/duck"
duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1"
"github.com/knative/pkg/logging"
"github.com/knative/pkg/logging/logkey"
@ -109,7 +111,7 @@ type ResourceDefaulter func(patches *[]jsonpatch.JsonPatchOperation, crd Generic
type AdmissionController struct {
Client kubernetes.Interface
Options ControllerOptions
Handlers map[schema.GroupVersionKind]runtime.Object
Handlers map[schema.GroupVersionKind]GenericCRD
Logger *zap.SugaredLogger
}
@ -118,15 +120,7 @@ type AdmissionController struct {
type GenericCRD interface {
apis.Defaultable
apis.Validatable
// GetObjectMeta return the object metadata
GetObjectMeta() metav1.Object
// GetGeneration returns the current Generation of the object
GetGeneration() int64
// SetGeneration sets the Generation of the object
SetGeneration(int64)
// GetSpecJSON returns the Spec part of the resource marshalled into JSON
GetSpecJSON() ([]byte, error)
runtime.Object
}
// GetAPIServerExtensionCACert gets the Kubernetes aggregate apiserver
@ -227,19 +221,10 @@ func Validate(ctx context.Context) ResourceCallback {
// SetDefaults simply leverages apis.Defaultable to set defaults.
func SetDefaults(ctx context.Context) ResourceDefaulter {
return func(patches *[]jsonpatch.JsonPatchOperation, crd GenericCRD) error {
rawOriginal, err := json.Marshal(crd)
if err != nil {
return err
}
crd.SetDefaults()
before, after := crd.DeepCopyObject(), crd
after.SetDefaults()
// Marshal the before and after.
rawAfter, err := json.Marshal(crd)
if err != nil {
return err
}
patch, err := jsonpatch.CreatePatch(rawOriginal, rawAfter)
patch, err := createPatch(before, after)
if err != nil {
return err
}
@ -248,6 +233,21 @@ func SetDefaults(ctx context.Context) ResourceDefaulter {
}
}
func createPatch(before, after interface{}) ([]jsonpatch.JsonPatchOperation, error) {
// Marshal the before and after.
rawBefore, err := json.Marshal(before)
if err != nil {
return nil, err
}
rawAfter, err := json.Marshal(after)
if err != nil {
return nil, err
}
return jsonpatch.CreatePatch(rawBefore, rawAfter)
}
func configureCerts(ctx context.Context, client kubernetes.Interface, options *ControllerOptions) (*tls.Config, []byte, error) {
apiServerCACert, err := getAPIServerExtensionCACert(client)
if err != nil {
@ -286,6 +286,13 @@ func (ac *AdmissionController) Run(stop <-chan struct{}) error {
logger.Infof("Delaying admission webhook registration for %v", ac.Options.RegistrationDelay)
}
// Verify that each of the types we are given implements the Generation duck type.
for _, crd := range ac.Handlers {
cp := crd.DeepCopyObject()
var emptyGen duckv1alpha1.Generation
duck.VerifyType(cp, &emptyGen)
}
select {
case <-time.After(ac.Options.RegistrationDelay):
cl := ac.Client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations()
@ -571,62 +578,91 @@ func (ac *AdmissionController) mutate(ctx context.Context, kind metav1.GroupVers
// ObjectMeta.Generation instead.
func updateGeneration(ctx context.Context, patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error {
logger := logging.FromContext(ctx)
var oldGeneration int64
if old == nil {
logger.Info("Old is nil")
} else {
oldGeneration = old.GetGeneration()
}
if oldGeneration == 0 {
logger.Info("Creating an object, setting generation to 1")
*patches = append(*patches, jsonpatch.JsonPatchOperation{
Operation: "add",
Path: "/spec/generation",
Value: 1,
})
if chg, err := hasChanged(ctx, old, new); err != nil {
return err
} else if !chg {
logger.Info("No changes in the spec, not bumping generation")
return nil
}
oldSpecJSON, err := old.GetSpecJSON()
// Leverage Spec duck typing to bump the Generation of the resource.
before, err := asGenerational(ctx, new)
if err != nil {
return err
}
after := before.DeepCopyObject().(*duckv1alpha1.Generational)
after.Spec.Generation = after.Spec.Generation + 1
genBump, err := createPatch(before, after)
if err != nil {
return err
}
*patches = append(*patches, genBump...)
return nil
}
// Not worth fully duck typing since there's no shared schema.
type hasSpec struct {
Spec json.RawMessage `json:"spec"`
}
func getSpecJSON(crd GenericCRD) ([]byte, error) {
b, err := json.Marshal(crd)
if err != nil {
return nil, err
}
hs := hasSpec{}
if err := json.Unmarshal(b, &hs); err != nil {
return nil, err
}
return []byte(hs.Spec), nil
}
func hasChanged(ctx context.Context, old, new GenericCRD) (bool, error) {
if old == nil {
return true, nil
}
logger := logging.FromContext(ctx)
oldSpecJSON, err := getSpecJSON(old)
if err != nil {
logger.Error("Failed to get Spec JSON for old", zap.Error(err))
return false, err
}
newSpecJSON, err := new.GetSpecJSON()
newSpecJSON, err := getSpecJSON(new)
if err != nil {
logger.Error("Failed to get Spec JSON for new", zap.Error(err))
return false, err
}
specPatches, err := jsonpatch.CreatePatch(oldSpecJSON, newSpecJSON)
if err != nil {
fmt.Printf("Error creating JSON patch:%v", err)
return err
return false, err
}
if len(specPatches) > 0 {
specPatchesJSON, err := json.Marshal(specPatches)
if err != nil {
logger.Error("Failed to marshal spec patches", zap.Error(err))
return err
}
logger.Infof("Specs differ:\n%+v\n", string(specPatchesJSON))
if len(specPatches) == 0 {
return false, nil
}
specPatchesJSON, err := json.Marshal(specPatches)
if err != nil {
logger.Error("Failed to marshal spec patches", zap.Error(err))
return false, err
}
logger.Infof("Specs differ:\n%+v\n", string(specPatchesJSON))
return true, nil
}
operation := "replace"
if newGeneration := new.GetGeneration(); newGeneration == 0 {
// If new is missing Generation, we need to "add" instead of "replace".
// We see this for Service resources because the initial generation is
// added to the managed Configuration and Route, but not the Service
// that manages them.
// TODO(#642): Remove this.
operation = "add"
}
*patches = append(*patches, jsonpatch.JsonPatchOperation{
Operation: operation,
Path: "/spec/generation",
Value: oldGeneration + 1,
})
return nil
func asGenerational(ctx context.Context, crd GenericCRD) (*duckv1alpha1.Generational, error) {
raw, err := json.Marshal(crd)
if err != nil {
return nil, err
}
logger.Info("No changes in the spec, not bumping generation")
return nil
kr := &duckv1alpha1.Generational{}
if err := json.Unmarshal(raw, kr); err != nil {
return nil, err
}
return kr, nil
}
func generateSecret(ctx context.Context, options *ControllerOptions) (*corev1.Secret, error) {

View File

@ -215,11 +215,7 @@ func TestValidResponseForResource(t *testing.T) {
t.Fatalf("Failed to decode response: %v", err)
}
expectJsonPatch := jsonpatch.JsonPatchOperation{
Operation: "add",
Path: "/spec/generation",
Value: float64(1),
}
expectJsonPatch := incrementGenerationPatch(testRev.Spec.Generation)
var respPatch []jsonpatch.JsonPatchOperation
err = json.Unmarshal(reviewResponse.Response.Patch, &respPatch)

View File

@ -33,7 +33,6 @@ import (
"go.uber.org/zap"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
// corev1 "k8s.io/api/core/v1"
@ -142,7 +141,7 @@ func TestValidCreateResourceSucceeds(t *testing.T) {
resp := ac.admit(TestContextWithLogger(t), createCreateResource(&r))
expectAllowed(t, resp)
expectPatches(t, resp.Patch, []jsonpatch.JsonPatchOperation{
incrementGenerationPatch(0),
incrementGenerationPatch(r.Spec.Generation),
})
}
@ -152,7 +151,7 @@ func TestValidCreateResourceSucceedsWithDefaultPatch(t *testing.T) {
resp := ac.admit(TestContextWithLogger(t), createCreateResource(&r))
expectAllowed(t, resp)
expectPatches(t, resp.Patch, []jsonpatch.JsonPatchOperation{
incrementGenerationPatch(0),
incrementGenerationPatch(r.Spec.Generation),
{
Operation: "add",
Path: "/spec/fieldWithDefault",
@ -476,11 +475,11 @@ func expectPatches(t *testing.T, a []byte, e []jsonpatch.JsonPatchOperation) {
}
}
func incrementGenerationPatch(old float64) jsonpatch.JsonPatchOperation {
func incrementGenerationPatch(old int64) jsonpatch.JsonPatchOperation {
return jsonpatch.JsonPatchOperation{
Operation: "add",
Operation: "replace",
Path: "/spec/generation",
Value: old + 1.0,
Value: float64(old) + 1.0,
}
}
@ -489,13 +488,11 @@ func NewAdmissionController(client kubernetes.Interface, options ControllerOptio
return &AdmissionController{
Client: client,
Options: options,
Handlers: map[schema.GroupVersionKind]runtime.Object{
{
Group: "pkg.knative.dev",
Version: "v1alpha1",
Kind: "Resource",
}: &Resource{},
},
Handlers: map[schema.GroupVersionKind]GenericCRD{{
Group: "pkg.knative.dev",
Version: "v1alpha1",
Kind: "Resource",
}: &Resource{}},
Logger: logger,
}, nil
}