diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index 97f753d20d3..58b0bb8b73b 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -91,6 +91,7 @@ var ourTypes = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{ sourcesv1alpha2.SchemeGroupVersion.WithKind("SinkBinding"): &sourcesv1alpha2.SinkBinding{}, sourcesv1alpha2.SchemeGroupVersion.WithKind("ContainerSource"): &sourcesv1alpha2.ContainerSource{}, // v1beta1 + sourcesv1beta1.SchemeGroupVersion.WithKind("ApiServerSource"): &sourcesv1beta1.ApiServerSource{}, sourcesv1beta1.SchemeGroupVersion.WithKind("SinkBinding"): &sourcesv1beta1.SinkBinding{}, sourcesv1beta1.SchemeGroupVersion.WithKind("ContainerSource"): &sourcesv1beta1.ContainerSource{}, @@ -310,12 +311,13 @@ func NewConversionController(ctx context.Context, cmw configmap.Watcher) *contro }, // Sources - sourcesv1alpha2.Kind("ApiServerSource"): { + sourcesv1beta1.Kind("ApiServerSource"): { DefinitionName: sources.ApiServerSourceResource.String(), HubVersion: sourcesv1alpha1_, Zygotes: map[string]conversion.ConvertibleObject{ sourcesv1alpha1_: &sourcesv1alpha1.ApiServerSource{}, sourcesv1alpha2_: &sourcesv1alpha2.ApiServerSource{}, + sourcesv1beta1_: &sourcesv1beta1.ApiServerSource{}, }, }, sourcesv1alpha2.Kind("PingSource"): { diff --git a/config/core/resources/apiserversource.yaml b/config/core/resources/apiserversource.yaml index 29661640eb5..f9ad1d94eab 100644 --- a/config/core/resources/apiserversource.yaml +++ b/config/core/resources/apiserversource.yaml @@ -67,12 +67,16 @@ spec: name: v1alpha2 served: true storage: false + - <<: *version + name: v1beta1 + served: true + storage: false names: categories: - - all - - knative - - eventing - - sources + - all + - knative + - eventing + - sources kind: ApiServerSource plural: apiserversources scope: Namespaced diff --git a/pkg/apis/sources/v1alpha1/apiserver_conversion.go b/pkg/apis/sources/v1alpha1/apiserver_conversion.go index 970d25f3124..619ff14fce9 100644 --- a/pkg/apis/sources/v1alpha1/apiserver_conversion.go +++ b/pkg/apis/sources/v1alpha1/apiserver_conversion.go @@ -18,7 +18,6 @@ package v1alpha1 import ( "context" - "fmt" "reflect" "github.com/google/go-cmp/cmp" @@ -32,7 +31,7 @@ import ( ) // ConvertTo implements apis.Convertible. -// Converts source (from v1alpha1.ApiServerSource) into v1alpha2.ApiServerSource +// Converts source (from v1alpha1.ApiServerSource) into into a higher version. func (source *ApiServerSource) ConvertTo(ctx context.Context, obj apis.Convertible) error { switch sink := obj.(type) { case *v1alpha2.ApiServerSource: @@ -95,12 +94,12 @@ func (source *ApiServerSource) ConvertTo(ctx context.Context, obj apis.Convertib source.Status.SourceStatus.DeepCopyInto(&sink.Status.SourceStatus) return nil default: - return fmt.Errorf("Unknown conversion, got: %T", sink) + return apis.ConvertToViaProxy(ctx, source, &v1alpha2.ApiServerSource{}, sink) } } // ConvertFrom implements apis.Convertible. -// Converts obj from v1alpha2.ApiServerSource into v1alpha1.ApiServerSource +// Converts obj from a higher version into v1alpha1.ApiServerSource. func (sink *ApiServerSource) ConvertFrom(ctx context.Context, obj apis.Convertible) error { switch source := obj.(type) { case *v1alpha2.ApiServerSource: @@ -158,6 +157,6 @@ func (sink *ApiServerSource) ConvertFrom(ctx context.Context, obj apis.Convertib return nil default: - return fmt.Errorf("Unknown conversion, got: %T", source) + return apis.ConvertFromViaProxy(ctx, source, &v1alpha2.ApiServerSource{}, sink) } } diff --git a/pkg/apis/sources/v1alpha1/apiserver_conversion_test.go b/pkg/apis/sources/v1alpha1/apiserver_conversion_test.go index a0c536a5aa3..98ba9858697 100644 --- a/pkg/apis/sources/v1alpha1/apiserver_conversion_test.go +++ b/pkg/apis/sources/v1alpha1/apiserver_conversion_test.go @@ -25,6 +25,7 @@ import ( duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" "knative.dev/eventing/pkg/apis/sources/v1alpha2" + "knative.dev/eventing/pkg/apis/sources/v1beta1" "github.com/google/go-cmp/cmp" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -46,7 +47,7 @@ func TestApiServerSourceConversionBadType(t *testing.T) { func TestApiServerSourceConversionRoundTripUp(t *testing.T) { // Just one for now, just adding the for loop for ease of future changes. - versions := []apis.Convertible{&v1alpha2.ApiServerSource{}} + versions := []apis.Convertible{&v1alpha2.ApiServerSource{}, &v1beta1.ApiServerSource{}} path, _ := apis.ParseURL("/path") sink := duckv1beta1.Destination{ @@ -196,8 +197,6 @@ func TestApiServerSourceConversionRoundTripUp(t *testing.T) { // This tests round tripping from a higher version -> v1alpha1 and back to the higher version. func TestApiServerSourceConversionRoundTripDown(t *testing.T) { - // Just one for now, just adding the for loop for ease of future changes. - path, _ := apis.ParseURL("/path") sink := duckv1.Destination{ Ref: &duckv1.KReference{ @@ -221,28 +220,28 @@ func TestApiServerSourceConversionRoundTripDown(t *testing.T) { name string in apis.Convertible }{{name: "empty", - in: &v1alpha2.ApiServerSource{ + in: &v1beta1.ApiServerSource{ ObjectMeta: metav1.ObjectMeta{ Name: "apiserver-name", Namespace: "apiserver-ns", Generation: 17, }, - Spec: v1alpha2.ApiServerSourceSpec{}, - Status: v1alpha2.ApiServerSourceStatus{}, + Spec: v1beta1.ApiServerSourceSpec{}, + Status: v1beta1.ApiServerSourceStatus{}, }, }, {name: "simple configuration", - in: &v1alpha2.ApiServerSource{ + in: &v1beta1.ApiServerSource{ ObjectMeta: metav1.ObjectMeta{ Name: "apiserver-name", Namespace: "apiserver-ns", Generation: 17, }, - Spec: v1alpha2.ApiServerSourceSpec{ + Spec: v1beta1.ApiServerSourceSpec{ SourceSpec: duckv1.SourceSpec{ Sink: sink, }, }, - Status: v1alpha2.ApiServerSourceStatus{ + Status: v1beta1.ApiServerSourceStatus{ SourceStatus: duckv1.SourceStatus{ Status: duckv1.Status{ ObservedGeneration: 1, @@ -256,19 +255,19 @@ func TestApiServerSourceConversionRoundTripDown(t *testing.T) { }, }, }, {name: "full", - in: &v1alpha2.ApiServerSource{ + in: &v1beta1.ApiServerSource{ ObjectMeta: metav1.ObjectMeta{ Name: "apiserver-name", Namespace: "apiserver-ns", Generation: 17, }, - Spec: v1alpha2.ApiServerSourceSpec{ + Spec: v1beta1.ApiServerSourceSpec{ SourceSpec: duckv1.SourceSpec{ Sink: sink, CloudEventOverrides: &ceOverrides, }, }, - Status: v1alpha2.ApiServerSourceStatus{ + Status: v1beta1.ApiServerSourceStatus{ SourceStatus: duckv1.SourceStatus{ Status: duckv1.Status{ ObservedGeneration: 1, diff --git a/pkg/apis/sources/v1alpha2/apiserver_conversion.go b/pkg/apis/sources/v1alpha2/apiserver_conversion.go index 40709ae2c28..89660e600c0 100644 --- a/pkg/apis/sources/v1alpha2/apiserver_conversion.go +++ b/pkg/apis/sources/v1alpha2/apiserver_conversion.go @@ -18,17 +18,120 @@ package v1alpha2 import ( "context" - "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/eventing/pkg/apis/sources/v1beta1" "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" ) // ConvertTo implements apis.Convertible -func (source *ApiServerSource) ConvertTo(ctx context.Context, sink apis.Convertible) error { - return fmt.Errorf("v1alpha2 is the highest known version, got: %T", sink) +// Converts source (from v1alpha2.ApiServerSource) into into a higher version. +func (source *ApiServerSource) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1beta1.ApiServerSource: + // Meta + sink.ObjectMeta = source.ObjectMeta + + // Spec + + if len(source.Spec.Resources) > 0 { + sink.Spec.Resources = make([]v1beta1.APIVersionKindSelector, len(source.Spec.Resources)) + } + for i, v := range source.Spec.Resources { + sink.Spec.Resources[i] = v1beta1.APIVersionKindSelector{ + APIVersion: v.APIVersion, + Kind: v.Kind, + } + + if v.LabelSelector != nil { + sink.Spec.Resources[i].LabelSelector = &metav1.LabelSelector{} + v.LabelSelector.DeepCopyInto(sink.Spec.Resources[i].LabelSelector) + } + } + + sink.Spec.EventMode = source.Spec.EventMode + + // Optional Spec + + if source.Spec.ResourceOwner != nil { + sink.Spec.ResourceOwner = &v1beta1.APIVersionKind{ + Kind: source.Spec.ResourceOwner.Kind, + APIVersion: source.Spec.ResourceOwner.APIVersion, + } + } + + var ref *duckv1.KReference + if source.Spec.Sink.Ref != nil { + ref = &duckv1.KReference{ + Kind: source.Spec.Sink.Ref.Kind, + Namespace: source.Spec.Sink.Ref.Namespace, + Name: source.Spec.Sink.Ref.Name, + APIVersion: source.Spec.Sink.Ref.APIVersion, + } + } + sink.Spec.Sink = duckv1.Destination{ + Ref: ref, + URI: source.Spec.Sink.URI, + } + + if source.Spec.CloudEventOverrides != nil { + sink.Spec.CloudEventOverrides = source.Spec.CloudEventOverrides.DeepCopy() + } + + sink.Spec.ServiceAccountName = source.Spec.ServiceAccountName + + // Status + source.Status.SourceStatus.DeepCopyInto(&sink.Status.SourceStatus) + return nil + default: + return apis.ConvertToViaProxy(ctx, source, &v1beta1.ApiServerSource{}, sink) + } } // ConvertFrom implements apis.Convertible -func (sink *ApiServerSource) ConvertFrom(ctx context.Context, source apis.Convertible) error { - return fmt.Errorf("v1alpha2 is the highest known version, got: %T", source) +// Converts obj from a higher version into v1alpha2.ApiServerSource. +func (sink *ApiServerSource) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1beta1.ApiServerSource: + // Meta + sink.ObjectMeta = source.ObjectMeta + + // Spec + sink.Spec.EventMode = source.Spec.EventMode + + sink.Spec.CloudEventOverrides = source.Spec.CloudEventOverrides + + sink.Spec.Sink = source.Spec.Sink + + if len(source.Spec.Resources) > 0 { + sink.Spec.Resources = make([]APIVersionKindSelector, len(source.Spec.Resources)) + } + for i, v := range source.Spec.Resources { + sink.Spec.Resources[i] = APIVersionKindSelector{} + sink.Spec.Resources[i].APIVersion = v.APIVersion + sink.Spec.Resources[i].Kind = v.Kind + if v.LabelSelector != nil { + sink.Spec.Resources[i].LabelSelector = v.LabelSelector.DeepCopy() + } + } + + // Spec Optionals + + if source.Spec.ResourceOwner != nil { + sink.Spec.ResourceOwner = &APIVersionKind{ + Kind: source.Spec.ResourceOwner.Kind, + APIVersion: source.Spec.ResourceOwner.APIVersion, + } + } + + sink.Spec.ServiceAccountName = source.Spec.ServiceAccountName + + // Status + source.Status.SourceStatus.DeepCopyInto(&sink.Status.SourceStatus) + + return nil + default: + return apis.ConvertFromViaProxy(ctx, source, &v1beta1.ApiServerSource{}, sink) + } } diff --git a/pkg/apis/sources/v1alpha2/apiserver_conversion_test.go b/pkg/apis/sources/v1alpha2/apiserver_conversion_test.go index 62bd3b96b85..c8137e0cd91 100644 --- a/pkg/apis/sources/v1alpha2/apiserver_conversion_test.go +++ b/pkg/apis/sources/v1alpha2/apiserver_conversion_test.go @@ -18,11 +18,19 @@ package v1alpha2 import ( "context" + "reflect" "testing" + + "knative.dev/eventing/pkg/apis/sources/v1beta1" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" ) -func TestApiServerConversionBadType(t *testing.T) { - good, bad := &ApiServerSource{}, &ApiServerSource{} +func TestApiServerSourceConversionBadType(t *testing.T) { + good, bad := &ApiServerSource{}, &dummy{} if err := good.ConvertTo(context.Background(), bad); err == nil { t.Errorf("ConvertTo() = %#v, wanted error", bad) @@ -32,3 +40,294 @@ func TestApiServerConversionBadType(t *testing.T) { t.Errorf("ConvertFrom() = %#v, wanted error", good) } } + +func TestApiServerSourceConversionRoundTripUp(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&v1beta1.ApiServerSource{}} + + path, _ := apis.ParseURL("/path") + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri, _ := apis.ParseURL("http://example.com/path") + + tests := []struct { + name string + in *ApiServerSource + }{{name: "empty", + in: &ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: ApiServerSourceSpec{}, + Status: ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, + }, {name: "simple configuration", + in: &ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: ApiServerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Resources: []APIVersionKindSelector{{ + APIVersion: "A1", + Kind: "K1", + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"A1": "K1"}, + }, + }, { + APIVersion: "A2", + Kind: "K2", + }}, + EventMode: "Ref", + }, + Status: ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "Unknown", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: ApiServerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + }, + }, + Resources: []APIVersionKindSelector{{ + APIVersion: "A1", + Kind: "K1", + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "aKey", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"the", "house"}, + }}, + }, + }, { + APIVersion: "A2", + Kind: "K2", + }}, + ResourceOwner: &APIVersionKind{ + APIVersion: "custom/v1", + Kind: "Parent", + }, + EventMode: "Resource", + ServiceAccountName: "adult", + }, + Status: ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }} + for _, test := range tests { + for _, version := range versions { + t.Run(test.name, func(t *testing.T) { + ver := version + if err := test.in.ConvertTo(context.Background(), ver); err != nil { + t.Errorf("ConvertTo() = %v", err) + } + + got := &ApiServerSource{} + + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Errorf("ConvertFrom() = %v", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Errorf("roundtrip (-want, +got) = %v", diff) + } + }) + } + } +} + +// This tests round tripping from a higher version -> v1alpha1 and back to the higher version. +func TestApiServerSourceConversionRoundTripDown(t *testing.T) { + path, _ := apis.ParseURL("/path") + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkURI, _ := apis.ParseURL("http://example.com/path") + + ceOverrides := duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + } + + tests := []struct { + name string + in apis.Convertible + }{{name: "empty", + in: &v1beta1.ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: v1beta1.ApiServerSourceSpec{}, + Status: v1beta1.ApiServerSourceStatus{}, + }, + }, {name: "simple configuration", + in: &v1beta1.ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: v1beta1.ApiServerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Resources: []v1beta1.APIVersionKindSelector{{ + APIVersion: "A1", + Kind: "K1", + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"A1": "K1"}, + }, + }, { + APIVersion: "A2", + Kind: "K2", + }}, + EventMode: "Ref", + }, + Status: v1beta1.ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkURI, + }, + }, + }, + }, {name: "full", + in: &v1beta1.ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: v1beta1.ApiServerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Resources: []v1beta1.APIVersionKindSelector{{ + APIVersion: "A1", + Kind: "K1", + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "aKey", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"the", "house"}, + }}, + }, + }, { + APIVersion: "A2", + Kind: "K2", + }}, + ResourceOwner: &v1beta1.APIVersionKind{ + APIVersion: "custom/v1", + Kind: "Parent", + }, + EventMode: "Resource", + ServiceAccountName: "adult", + }, + Status: v1beta1.ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkURI, + }, + }, + }, + }} + for _, test := range tests { + + t.Run(test.name, func(t *testing.T) { + down := &ApiServerSource{} + if err := down.ConvertFrom(context.Background(), test.in); err != nil { + t.Errorf("ConvertTo() = %v", err) + } + + got := (reflect.New(reflect.TypeOf(test.in).Elem()).Interface()).(apis.Convertible) + + if err := down.ConvertTo(context.Background(), got); err != nil { + t.Errorf("ConvertFrom() = %v", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Errorf("roundtrip (-want, +got) = %v", diff) + } + }) + } +}