From 313b88b43f6691a69c5c899b4e99766c6d956248 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Sat, 8 Sep 2018 00:03:17 +0000 Subject: [PATCH 01/15] Rename Subscription fields to: from,to,processor --- .../channels/v1alpha1/subscription_types.go | 25 +++++--- .../v1alpha1/subscription_validation.go | 14 ++-- .../v1alpha1/subscription_validation_test.go | 64 +++++++++++-------- 3 files changed, 63 insertions(+), 40 deletions(-) diff --git a/pkg/apis/channels/v1alpha1/subscription_types.go b/pkg/apis/channels/v1alpha1/subscription_types.go index 93747ae103f..fa4a6ad2af4 100644 --- a/pkg/apis/channels/v1alpha1/subscription_types.go +++ b/pkg/apis/channels/v1alpha1/subscription_types.go @@ -47,8 +47,14 @@ var _ apis.Immutable = (*Subscription)(nil) var _ runtime.Object = (*Subscription)(nil) var _ webhook.GenericCRD = (*ClusterBus)(nil) -// SubscriptionSpec specifies the Channel and Subscriber and the configuration -// arguments for the Subscription. +// SubscriptionSpec specifies the Channel for incoming events, a handler and the Channel +// for outgoing messages. +// from --[transform]--> to +// Note that the following are valid configurations also: +// Sink, no outgoing events: +// from -- transform +// no-op function (identity transformation): +// from --> to type SubscriptionSpec struct { // TODO: Generation does not work correctly with CRD. They are scrubbed // by the APIserver (https://github.com/kubernetes/kubernetes/issues/58778) @@ -57,14 +63,17 @@ type SubscriptionSpec struct { // +optional Generation int64 `json:"generation,omitempty"` - // Channel is the name of the channel to subscribe to. - Channel string `json:"channel"` + // From is the name of the channel to subscribe to for receiving events + // to be transformed. + From string `json:"from"` - // Subscriber is the name of the subscriber service DNS name. - Subscriber string `json:"subscriber"` + // Processor is the processor service DNS name. Events + // from the From channel will be delivered here and replies are sent + // to To channel. + Processor string `json:"processor,omitempty"` - // Target service DNS name for replies returned by the subscriber. - ReplyTo string `json:"replyTo,omitempty"` + // To is the name of the channel to send transformed events + To string `json:"to,omitempty"` // Arguments is a list of configuration arguments for the Subscription. The // Arguments for a channel must contain values for each of the Parameters diff --git a/pkg/apis/channels/v1alpha1/subscription_validation.go b/pkg/apis/channels/v1alpha1/subscription_validation.go index c2f7ae8ab91..073b1c6f4c3 100644 --- a/pkg/apis/channels/v1alpha1/subscription_validation.go +++ b/pkg/apis/channels/v1alpha1/subscription_validation.go @@ -27,14 +27,14 @@ func (s *Subscription) Validate() *apis.FieldError { } func (ss *SubscriptionSpec) Validate() *apis.FieldError { - if ss.Channel == "" { - fe := apis.ErrMissingField("channel") - fe.Details = "the Subscription must reference a Channel" + if ss.From == "" { + fe := apis.ErrMissingField("from") + fe.Details = "the Subscription must reference a from channel" return fe } - if ss.Subscriber == "" { - fe := apis.ErrMissingField("subscriber") - fe.Details = "the Subscription must reference a Subscriber" + if ss.To == "" && ss.Processor == "" { + fe := apis.ErrMissingField("to", "processor") + fe.Details = "the Subscription must reference a to channel or a processor" return fe } return nil @@ -49,7 +49,7 @@ func (current *Subscription) CheckImmutableFields(og apis.Immutable) *apis.Field return nil } - ignoreArguments := cmpopts.IgnoreFields(SubscriptionSpec{}, "Subscriber", "Arguments") + ignoreArguments := cmpopts.IgnoreFields(SubscriptionSpec{}, "Processor", "Arguments") if diff := cmp.Diff(original.Spec, current.Spec, ignoreArguments); diff != "" { return &apis.FieldError{ Message: "Immutable fields changed (-old +new)", diff --git a/pkg/apis/channels/v1alpha1/subscription_validation_test.go b/pkg/apis/channels/v1alpha1/subscription_validation_test.go index f585ab191b9..928ca12fed3 100644 --- a/pkg/apis/channels/v1alpha1/subscription_validation_test.go +++ b/pkg/apis/channels/v1alpha1/subscription_validation_test.go @@ -30,34 +30,48 @@ func TestSubscriptionSpecValidation(t *testing.T) { }{{ name: "valid", c: &SubscriptionSpec{ - Channel: "bar", - Subscriber: "foo", + From: "fromChannel", + Processor: "processor", }, want: nil, }, { name: "valid with arguments", c: &SubscriptionSpec{ - Channel: "bar", - Subscriber: "foo", - Arguments: &[]Argument{{Name: "foo", Value: "bar"}}, + From: "fromChannel", + Processor: "processor", + Arguments: &[]Argument{{Name: "foo", Value: "bar"}}, }, want: nil, }, { - name: "missing subscriber", + name: "missing processor and to", c: &SubscriptionSpec{ - Channel: "foo", + From: "fromChannel", }, want: func() *apis.FieldError { - fe := apis.ErrMissingField("subscriber") - fe.Details = "the Subscription must reference a Subscriber" + fe := apis.ErrMissingField("to", "processor") + fe.Details = "the Subscription must reference a to channel or a processor" return fe }(), + }, { + name: "missing to", + c: &SubscriptionSpec{ + From: "fromChannel", + To: "toChannel", + }, + want: nil, + }, { + name: "missing processor", + c: &SubscriptionSpec{ + From: "fromChannel", + To: "toChannel", + }, + want: nil, }, { name: "empty", c: &SubscriptionSpec{}, want: func() *apis.FieldError { - fe := apis.ErrMissingField("channel") - fe.Details = "the Subscription must reference a Channel" + fe := apis.ErrMissingField("from") + fe.Details = "the Subscription must reference a from channel" return fe }(), }} @@ -66,7 +80,7 @@ func TestSubscriptionSpecValidation(t *testing.T) { t.Run(test.name, func(t *testing.T) { got := test.c.Validate() if diff := cmp.Diff(test.want, got); diff != "" { - t.Errorf("validateChannel (-want, +got) = %v", diff) + t.Errorf("validateFrom (-want, +got) = %v", diff) } }) } @@ -82,48 +96,48 @@ func TestSubscriptionImmutable(t *testing.T) { name: "valid", c: &Subscription{ Spec: SubscriptionSpec{ - Channel: "foo", + From: "foo", }, }, og: &Subscription{ Spec: SubscriptionSpec{ - Channel: "foo", + From: "foo", }, }, want: nil, }, { - name: "valid, new subscriber", + name: "valid, new processor", c: &Subscription{ Spec: SubscriptionSpec{ - Channel: "foo", - Subscriber: "bar", + From: "foo", + Processor: "newProcessor", }, }, og: &Subscription{ Spec: SubscriptionSpec{ - Channel: "foo", - Subscriber: "baz", + From: "foo", + Processor: "processor", }, }, want: nil, }, { - name: "channel changed", + name: "from changed", c: &Subscription{ Spec: SubscriptionSpec{ - Channel: "foo", + From: "fromChannel", }, }, og: &Subscription{ Spec: SubscriptionSpec{ - Channel: "bar", + From: "newFromChannel", }, }, want: &apis.FieldError{ Message: "Immutable fields changed (-old +new)", Paths: []string{"spec"}, - Details: `{v1alpha1.SubscriptionSpec}.Channel: - -: "bar" - +: "foo" + Details: `{v1alpha1.SubscriptionSpec}.From: + -: "newFromChannel" + +: "fromChannel" `, }, }} From 758ae8c1d6c3914026fbccdcc9addee061358e03 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Tue, 11 Sep 2018 20:54:11 +0000 Subject: [PATCH 02/15] make things refs --- .../channels/v1alpha1/subscription_types.go | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/pkg/apis/channels/v1alpha1/subscription_types.go b/pkg/apis/channels/v1alpha1/subscription_types.go index fa4a6ad2af4..350497bf650 100644 --- a/pkg/apis/channels/v1alpha1/subscription_types.go +++ b/pkg/apis/channels/v1alpha1/subscription_types.go @@ -63,17 +63,48 @@ type SubscriptionSpec struct { // +optional Generation int64 `json:"generation,omitempty"` - // From is the name of the channel to subscribe to for receiving events - // to be transformed. - From string `json:"from"` + // Reference to an object that will be used to create the subscription + // for receiving events. The object must have spec.subscriptions + // list which will then be modified accordingly. + // + // You can specify only the following fields of the ObjectReference: + // - Kind + // - APIVersion + // - Name + From *corev1.ObjectReference `json:"from,omitempty"` // Processor is the processor service DNS name. Events // from the From channel will be delivered here and replies are sent // to To channel. - Processor string `json:"processor,omitempty"` - - // To is the name of the channel to send transformed events - To string `json:"to,omitempty"` + // Reference to an object that will be used to deliver events for + // (optional) processing before sending them to To for further + // if specified for additional Subscriptions to then subscribe + // to these events for further processing. + // + // For example, this could be a reference to a Route resource + // or a Configuration resource. + // TODO: Specify the required fields the target object must + // have in the status. + // You can specify only the following fields of the ObjectReference: + // - Kind + // - APIVersion + // - Name + // +optional + Processor *corev1.ObjectReference `json:"processor,omitempty"` + + // To is the (optional) resolved channel where (optionally) processed + // events get sent. + // + // This has to be a channel + // + // TODO: Specify the required fields the target object must + // have in the status. + // You can specify only the following fields of the ObjectReference: + // - Kind + // - APIVersion + // - Name + // +optional + To *corev1.ObjectReference `json:"to,omitempty"` // Arguments is a list of configuration arguments for the Subscription. The // Arguments for a channel must contain values for each of the Parameters From dc67d248d32c5456bc499ccd3190bebcfeb7eac1 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Tue, 11 Sep 2018 21:11:16 +0000 Subject: [PATCH 03/15] address pr feedback, remove left over cruft --- pkg/apis/channels/v1alpha1/subscription_types.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/apis/channels/v1alpha1/subscription_types.go b/pkg/apis/channels/v1alpha1/subscription_types.go index 350497bf650..1a0e3ad0c59 100644 --- a/pkg/apis/channels/v1alpha1/subscription_types.go +++ b/pkg/apis/channels/v1alpha1/subscription_types.go @@ -73,16 +73,16 @@ type SubscriptionSpec struct { // - Name From *corev1.ObjectReference `json:"from,omitempty"` - // Processor is the processor service DNS name. Events - // from the From channel will be delivered here and replies are sent - // to To channel. + // Processor is reference to (optional) function for processing events. + // Events from the From channel will be delivered here and replies + // are sent to To channel. // Reference to an object that will be used to deliver events for // (optional) processing before sending them to To for further // if specified for additional Subscriptions to then subscribe // to these events for further processing. // // For example, this could be a reference to a Route resource - // or a Configuration resource. + // or a Service resource. // TODO: Specify the required fields the target object must // have in the status. // You can specify only the following fields of the ObjectReference: From a20d789afb88fcc15767b83637857df13e9323d2 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Tue, 11 Sep 2018 22:44:31 +0000 Subject: [PATCH 04/15] add the contracts we expect each object reference to fulfill --- pkg/apis/channels/v1alpha1/subscription_types.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/apis/channels/v1alpha1/subscription_types.go b/pkg/apis/channels/v1alpha1/subscription_types.go index 1a0e3ad0c59..0eb51e5543b 100644 --- a/pkg/apis/channels/v1alpha1/subscription_types.go +++ b/pkg/apis/channels/v1alpha1/subscription_types.go @@ -67,6 +67,8 @@ type SubscriptionSpec struct { // for receiving events. The object must have spec.subscriptions // list which will then be modified accordingly. // + // This object must fulfill the Subscribable contract. + // // You can specify only the following fields of the ObjectReference: // - Kind // - APIVersion @@ -76,6 +78,9 @@ type SubscriptionSpec struct { // Processor is reference to (optional) function for processing events. // Events from the From channel will be delivered here and replies // are sent to To channel. + // + // This object must fulfill the Targetable contract. + // // Reference to an object that will be used to deliver events for // (optional) processing before sending them to To for further // if specified for additional Subscriptions to then subscribe @@ -95,7 +100,7 @@ type SubscriptionSpec struct { // To is the (optional) resolved channel where (optionally) processed // events get sent. // - // This has to be a channel + // This object must fulfill the Channel contract. // // TODO: Specify the required fields the target object must // have in the status. From 7e2d059c14e26b18849f58d1ab5511e37322ff17 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 12 Sep 2018 17:46:20 +0000 Subject: [PATCH 05/15] use Sinkable contract for the To ref --- pkg/apis/channels/v1alpha1/subscription_types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/apis/channels/v1alpha1/subscription_types.go b/pkg/apis/channels/v1alpha1/subscription_types.go index 0eb51e5543b..d3953e707c8 100644 --- a/pkg/apis/channels/v1alpha1/subscription_types.go +++ b/pkg/apis/channels/v1alpha1/subscription_types.go @@ -100,7 +100,7 @@ type SubscriptionSpec struct { // To is the (optional) resolved channel where (optionally) processed // events get sent. // - // This object must fulfill the Channel contract. + // This object must fulfill the Sinkable contract. // // TODO: Specify the required fields the target object must // have in the status. From 28be0a68545787ef04e66ade10da896647423dca Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 12 Sep 2018 18:43:57 +0000 Subject: [PATCH 06/15] update tests to refs --- .../channels/v1alpha1/subscription_types.go | 1 + .../v1alpha1/subscription_validation.go | 14 +++- .../v1alpha1/subscription_validation_test.go | 79 ++++++++++++++----- 3 files changed, 72 insertions(+), 22 deletions(-) diff --git a/pkg/apis/channels/v1alpha1/subscription_types.go b/pkg/apis/channels/v1alpha1/subscription_types.go index d3953e707c8..54acd2a3047 100644 --- a/pkg/apis/channels/v1alpha1/subscription_types.go +++ b/pkg/apis/channels/v1alpha1/subscription_types.go @@ -22,6 +22,7 @@ import ( "github.com/knative/pkg/apis" "github.com/knative/pkg/webhook" "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) diff --git a/pkg/apis/channels/v1alpha1/subscription_validation.go b/pkg/apis/channels/v1alpha1/subscription_validation.go index 073b1c6f4c3..0fd4b103e09 100644 --- a/pkg/apis/channels/v1alpha1/subscription_validation.go +++ b/pkg/apis/channels/v1alpha1/subscription_validation.go @@ -20,6 +20,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/knative/pkg/apis" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" ) func (s *Subscription) Validate() *apis.FieldError { @@ -27,14 +29,20 @@ func (s *Subscription) Validate() *apis.FieldError { } func (ss *SubscriptionSpec) Validate() *apis.FieldError { - if ss.From == "" { + if ss.From == nil || equality.Semantic.DeepEqual(ss.From, &corev1.ObjectReference{}) { fe := apis.ErrMissingField("from") fe.Details = "the Subscription must reference a from channel" return fe } - if ss.To == "" && ss.Processor == "" { + if ss.Processor == nil && ss.To == nil { fe := apis.ErrMissingField("to", "processor") - fe.Details = "the Subscription must reference a to channel or a processor" + fe.Details = "the Subscription must reference at least one of (to channel or a processor)" + return fe + } + + if equality.Semantic.DeepEqual(ss.Processor, &corev1.ObjectReference{}) && equality.Semantic.DeepEqual(ss.To, &corev1.ObjectReference{}) { + fe := apis.ErrMissingField("to", "processor") + fe.Details = "the Subscription must reference at least one of (to channel or a processor)" return fe } return nil diff --git a/pkg/apis/channels/v1alpha1/subscription_validation_test.go b/pkg/apis/channels/v1alpha1/subscription_validation_test.go index 928ca12fed3..e5b932cbdb6 100644 --- a/pkg/apis/channels/v1alpha1/subscription_validation_test.go +++ b/pkg/apis/channels/v1alpha1/subscription_validation_test.go @@ -20,8 +20,43 @@ import ( "github.com/google/go-cmp/cmp" "github.com/knative/pkg/apis" + corev1 "k8s.io/api/core/v1" ) +const ( + channelKind = "Channel" + channelAPIVersion = "eventing.knative.dev/v1alpha1" + routeKind = "Route" + routeAPIVersion = "serving.knative.dev/v1alpha1" + FromChannelName = "fromChannel" + ToChannelName = "toChannel" + ProcessorName = "processor" +) + +func getValidFromRef() *corev1.ObjectReference { + return &corev1.ObjectReference{ + Name: FromChannelName, + Kind: channelKind, + APIVersion: channelAPIVersion, + } +} + +func getValidToRef() *corev1.ObjectReference { + return &corev1.ObjectReference{ + Name: ToChannelName, + Kind: channelKind, + APIVersion: channelAPIVersion, + } +} + +func getValidProcessor() *corev1.ObjectReference { + return &corev1.ObjectReference{ + Name: ProcessorName, + Kind: routeKind, + APIVersion: routeAPIVersion, + } +} + func TestSubscriptionSpecValidation(t *testing.T) { tests := []struct { name string @@ -30,40 +65,40 @@ func TestSubscriptionSpecValidation(t *testing.T) { }{{ name: "valid", c: &SubscriptionSpec{ - From: "fromChannel", - Processor: "processor", + From: getValidFromRef(), + Processor: getValidProcessor(), }, want: nil, }, { name: "valid with arguments", c: &SubscriptionSpec{ - From: "fromChannel", - Processor: "processor", + From: getValidFromRef(), + Processor: getValidProcessor(), Arguments: &[]Argument{{Name: "foo", Value: "bar"}}, }, want: nil, }, { name: "missing processor and to", c: &SubscriptionSpec{ - From: "fromChannel", + From: getValidFromRef(), }, want: func() *apis.FieldError { fe := apis.ErrMissingField("to", "processor") - fe.Details = "the Subscription must reference a to channel or a processor" + fe.Details = "the Subscription must reference at least one of (to channel or a processor)" return fe }(), }, { name: "missing to", c: &SubscriptionSpec{ - From: "fromChannel", - To: "toChannel", + From: getValidFromRef(), + Processor: getValidProcessor(), }, want: nil, }, { name: "missing processor", c: &SubscriptionSpec{ - From: "fromChannel", - To: "toChannel", + From: getValidFromRef(), + To: getValidToRef(), }, want: nil, }, { @@ -87,6 +122,12 @@ func TestSubscriptionSpecValidation(t *testing.T) { } func TestSubscriptionImmutable(t *testing.T) { + newFrom := getValidFromRef() + newFrom.Name = "newFromChannel" + + newProcessor := getValidProcessor() + newProcessor.Name = "newProcessor" + tests := []struct { name string c *Subscription @@ -96,12 +137,12 @@ func TestSubscriptionImmutable(t *testing.T) { name: "valid", c: &Subscription{ Spec: SubscriptionSpec{ - From: "foo", + From: getValidFromRef(), }, }, og: &Subscription{ Spec: SubscriptionSpec{ - From: "foo", + From: getValidFromRef(), }, }, want: nil, @@ -109,14 +150,14 @@ func TestSubscriptionImmutable(t *testing.T) { name: "valid, new processor", c: &Subscription{ Spec: SubscriptionSpec{ - From: "foo", - Processor: "newProcessor", + From: getValidFromRef(), + Processor: getValidProcessor(), }, }, og: &Subscription{ Spec: SubscriptionSpec{ - From: "foo", - Processor: "processor", + From: getValidFromRef(), + Processor: newProcessor, }, }, want: nil, @@ -124,18 +165,18 @@ func TestSubscriptionImmutable(t *testing.T) { name: "from changed", c: &Subscription{ Spec: SubscriptionSpec{ - From: "fromChannel", + From: getValidFromRef(), }, }, og: &Subscription{ Spec: SubscriptionSpec{ - From: "newFromChannel", + From: newFrom, }, }, want: &apis.FieldError{ Message: "Immutable fields changed (-old +new)", Paths: []string{"spec"}, - Details: `{v1alpha1.SubscriptionSpec}.From: + Details: `{v1alpha1.SubscriptionSpec}.From.Name: -: "newFromChannel" +: "fromChannel" `, From 0c3fcd7dbc4a72201e7469668034a4e3b3b7da8e Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 12 Sep 2018 18:56:41 +0000 Subject: [PATCH 07/15] more tests --- .../v1alpha1/subscription_validation_test.go | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pkg/apis/channels/v1alpha1/subscription_validation_test.go b/pkg/apis/channels/v1alpha1/subscription_validation_test.go index e5b932cbdb6..aeae6a45e73 100644 --- a/pkg/apis/channels/v1alpha1/subscription_validation_test.go +++ b/pkg/apis/channels/v1alpha1/subscription_validation_test.go @@ -77,6 +77,16 @@ func TestSubscriptionSpecValidation(t *testing.T) { Arguments: &[]Argument{{Name: "foo", Value: "bar"}}, }, want: nil, + }, { + name: "empty from", + c: &SubscriptionSpec{ + From: &corev1.ObjectReference{}, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("from") + fe.Details = "the Subscription must reference a from channel" + return fe + }(), }, { name: "missing processor and to", c: &SubscriptionSpec{ @@ -87,6 +97,18 @@ func TestSubscriptionSpecValidation(t *testing.T) { fe.Details = "the Subscription must reference at least one of (to channel or a processor)" return fe }(), + }, { + name: "empty processor and to", + c: &SubscriptionSpec{ + From: getValidFromRef(), + Processor: &corev1.ObjectReference{}, + To: &corev1.ObjectReference{}, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("to", "processor") + fe.Details = "the Subscription must reference at least one of (to channel or a processor)" + return fe + }(), }, { name: "missing to", c: &SubscriptionSpec{ @@ -94,6 +116,14 @@ func TestSubscriptionSpecValidation(t *testing.T) { Processor: getValidProcessor(), }, want: nil, + }, { + name: "empty to", + c: &SubscriptionSpec{ + From: getValidFromRef(), + Processor: getValidProcessor(), + To: &corev1.ObjectReference{}, + }, + want: nil, }, { name: "missing processor", c: &SubscriptionSpec{ @@ -101,6 +131,14 @@ func TestSubscriptionSpecValidation(t *testing.T) { To: getValidToRef(), }, want: nil, + }, { + name: "empty processor", + c: &SubscriptionSpec{ + From: getValidFromRef(), + Processor: &corev1.ObjectReference{}, + To: getValidToRef(), + }, + want: nil, }, { name: "empty", c: &SubscriptionSpec{}, From 785ddbce97f547ad9a06863d4bbe9716bd3cac27 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 12 Sep 2018 19:10:29 +0000 Subject: [PATCH 08/15] moar tests --- .../v1alpha1/subscription_validation_test.go | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/pkg/apis/channels/v1alpha1/subscription_validation_test.go b/pkg/apis/channels/v1alpha1/subscription_validation_test.go index aeae6a45e73..8b298dee8e7 100644 --- a/pkg/apis/channels/v1alpha1/subscription_validation_test.go +++ b/pkg/apis/channels/v1alpha1/subscription_validation_test.go @@ -57,6 +57,12 @@ func getValidProcessor() *corev1.ObjectReference { } } +type DummyImmutableType struct{} + +func (d *DummyImmutableType) CheckImmutableFields(og apis.Immutable) *apis.FieldError { + return nil +} + func TestSubscriptionSpecValidation(t *testing.T) { tests := []struct { name string @@ -184,6 +190,16 @@ func TestSubscriptionImmutable(t *testing.T) { }, }, want: nil, + }, { + name: "new nil is ok", + c: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + Processor: getValidProcessor(), + }, + }, + og: nil, + want: nil, }, { name: "valid, new processor", c: &Subscription{ @@ -230,3 +246,23 @@ func TestSubscriptionImmutable(t *testing.T) { }) } } + +func TestInvalidImmutableType(t *testing.T) { + name := "invalid type" + c := &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + Processor: getValidProcessor(), + }, + } + og := &DummyImmutableType{} + want := &apis.FieldError{ + Message: "The provided original was not a Subscription", + } + t.Run(name, func(t *testing.T) { + got := c.CheckImmutableFields(og) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("CheckImmutableFields (-want, +got) = %v", diff) + } + }) +} From 973d77c222a10e8f9d08beebcddc88613835bd91 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 12 Sep 2018 20:13:58 +0000 Subject: [PATCH 09/15] add test for Subscription object --- .../v1alpha1/subscription_validation_test.go | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pkg/apis/channels/v1alpha1/subscription_validation_test.go b/pkg/apis/channels/v1alpha1/subscription_validation_test.go index 8b298dee8e7..2379a97b139 100644 --- a/pkg/apis/channels/v1alpha1/subscription_validation_test.go +++ b/pkg/apis/channels/v1alpha1/subscription_validation_test.go @@ -63,6 +63,29 @@ func (d *DummyImmutableType) CheckImmutableFields(og apis.Immutable) *apis.Field return nil } +func TestSubscriptionValidation(t *testing.T) { + name := "empty from" + c := &Subscription{ + + Spec: SubscriptionSpec{ + From: &corev1.ObjectReference{}, + }, + } + want := &apis.FieldError{ + Paths: []string{"spec.from"}, + Message: "missing field(s)", + Details: "the Subscription must reference a from channel", + } + + t.Run(name, func(t *testing.T) { + got := c.Validate() + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("CheckImmutableFields (-want, +got) = %v", diff) + } + }) + +} + func TestSubscriptionSpecValidation(t *testing.T) { tests := []struct { name string From b8a8e499199fa6a3bf7b23c910bc1933484a3977 Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 12 Sep 2018 21:14:49 +0000 Subject: [PATCH 10/15] processor -> call --- .../channels/v1alpha1/subscription_types.go | 4 +- .../v1alpha1/subscription_validation.go | 14 +-- .../v1alpha1/subscription_validation_test.go | 90 +++++++++++-------- 3 files changed, 63 insertions(+), 45 deletions(-) diff --git a/pkg/apis/channels/v1alpha1/subscription_types.go b/pkg/apis/channels/v1alpha1/subscription_types.go index 54acd2a3047..38f4e199d0e 100644 --- a/pkg/apis/channels/v1alpha1/subscription_types.go +++ b/pkg/apis/channels/v1alpha1/subscription_types.go @@ -76,7 +76,7 @@ type SubscriptionSpec struct { // - Name From *corev1.ObjectReference `json:"from,omitempty"` - // Processor is reference to (optional) function for processing events. + // Call is reference to (optional) function for processing events. // Events from the From channel will be delivered here and replies // are sent to To channel. // @@ -96,7 +96,7 @@ type SubscriptionSpec struct { // - APIVersion // - Name // +optional - Processor *corev1.ObjectReference `json:"processor,omitempty"` + Call *corev1.ObjectReference `json:"call,omitempty"` // To is the (optional) resolved channel where (optionally) processed // events get sent. diff --git a/pkg/apis/channels/v1alpha1/subscription_validation.go b/pkg/apis/channels/v1alpha1/subscription_validation.go index 0fd4b103e09..41fc92427f6 100644 --- a/pkg/apis/channels/v1alpha1/subscription_validation.go +++ b/pkg/apis/channels/v1alpha1/subscription_validation.go @@ -34,15 +34,15 @@ func (ss *SubscriptionSpec) Validate() *apis.FieldError { fe.Details = "the Subscription must reference a from channel" return fe } - if ss.Processor == nil && ss.To == nil { - fe := apis.ErrMissingField("to", "processor") - fe.Details = "the Subscription must reference at least one of (to channel or a processor)" + if ss.Call == nil && ss.To == nil { + fe := apis.ErrMissingField("to", "call") + fe.Details = "the Subscription must reference at least one of (to channel or a call)" return fe } - if equality.Semantic.DeepEqual(ss.Processor, &corev1.ObjectReference{}) && equality.Semantic.DeepEqual(ss.To, &corev1.ObjectReference{}) { - fe := apis.ErrMissingField("to", "processor") - fe.Details = "the Subscription must reference at least one of (to channel or a processor)" + if equality.Semantic.DeepEqual(ss.Call, &corev1.ObjectReference{}) && equality.Semantic.DeepEqual(ss.To, &corev1.ObjectReference{}) { + fe := apis.ErrMissingField("to", "call") + fe.Details = "the Subscription must reference at least one of (to channel or a call)" return fe } return nil @@ -57,7 +57,7 @@ func (current *Subscription) CheckImmutableFields(og apis.Immutable) *apis.Field return nil } - ignoreArguments := cmpopts.IgnoreFields(SubscriptionSpec{}, "Processor", "Arguments") + ignoreArguments := cmpopts.IgnoreFields(SubscriptionSpec{}, "Call", "To", "Arguments") if diff := cmp.Diff(original.Spec, current.Spec, ignoreArguments); diff != "" { return &apis.FieldError{ Message: "Immutable fields changed (-old +new)", diff --git a/pkg/apis/channels/v1alpha1/subscription_validation_test.go b/pkg/apis/channels/v1alpha1/subscription_validation_test.go index 2379a97b139..2a99a1d61e4 100644 --- a/pkg/apis/channels/v1alpha1/subscription_validation_test.go +++ b/pkg/apis/channels/v1alpha1/subscription_validation_test.go @@ -30,7 +30,7 @@ const ( routeAPIVersion = "serving.knative.dev/v1alpha1" FromChannelName = "fromChannel" ToChannelName = "toChannel" - ProcessorName = "processor" + CallName = "call" ) func getValidFromRef() *corev1.ObjectReference { @@ -49,9 +49,9 @@ func getValidToRef() *corev1.ObjectReference { } } -func getValidProcessor() *corev1.ObjectReference { +func getValidCall() *corev1.ObjectReference { return &corev1.ObjectReference{ - Name: ProcessorName, + Name: CallName, Kind: routeKind, APIVersion: routeAPIVersion, } @@ -94,15 +94,15 @@ func TestSubscriptionSpecValidation(t *testing.T) { }{{ name: "valid", c: &SubscriptionSpec{ - From: getValidFromRef(), - Processor: getValidProcessor(), + From: getValidFromRef(), + Call: getValidCall(), }, want: nil, }, { name: "valid with arguments", c: &SubscriptionSpec{ From: getValidFromRef(), - Processor: getValidProcessor(), + Call: getValidCall(), Arguments: &[]Argument{{Name: "foo", Value: "bar"}}, }, want: nil, @@ -117,55 +117,55 @@ func TestSubscriptionSpecValidation(t *testing.T) { return fe }(), }, { - name: "missing processor and to", + name: "missing call and to", c: &SubscriptionSpec{ From: getValidFromRef(), }, want: func() *apis.FieldError { - fe := apis.ErrMissingField("to", "processor") - fe.Details = "the Subscription must reference at least one of (to channel or a processor)" + fe := apis.ErrMissingField("to", "call") + fe.Details = "the Subscription must reference at least one of (to channel or a call)" return fe }(), }, { - name: "empty processor and to", + name: "empty call and to", c: &SubscriptionSpec{ - From: getValidFromRef(), - Processor: &corev1.ObjectReference{}, - To: &corev1.ObjectReference{}, + From: getValidFromRef(), + Call: &corev1.ObjectReference{}, + To: &corev1.ObjectReference{}, }, want: func() *apis.FieldError { - fe := apis.ErrMissingField("to", "processor") - fe.Details = "the Subscription must reference at least one of (to channel or a processor)" + fe := apis.ErrMissingField("to", "call") + fe.Details = "the Subscription must reference at least one of (to channel or a call)" return fe }(), }, { name: "missing to", c: &SubscriptionSpec{ - From: getValidFromRef(), - Processor: getValidProcessor(), + From: getValidFromRef(), + Call: getValidCall(), }, want: nil, }, { name: "empty to", c: &SubscriptionSpec{ - From: getValidFromRef(), - Processor: getValidProcessor(), - To: &corev1.ObjectReference{}, + From: getValidFromRef(), + Call: getValidCall(), + To: &corev1.ObjectReference{}, }, want: nil, }, { - name: "missing processor", + name: "missing call", c: &SubscriptionSpec{ From: getValidFromRef(), To: getValidToRef(), }, want: nil, }, { - name: "empty processor", + name: "empty call", c: &SubscriptionSpec{ - From: getValidFromRef(), - Processor: &corev1.ObjectReference{}, - To: getValidToRef(), + From: getValidFromRef(), + Call: &corev1.ObjectReference{}, + To: getValidToRef(), }, want: nil, }, { @@ -192,8 +192,11 @@ func TestSubscriptionImmutable(t *testing.T) { newFrom := getValidFromRef() newFrom.Name = "newFromChannel" - newProcessor := getValidProcessor() - newProcessor.Name = "newProcessor" + newCall := getValidCall() + newCall.Name = "newCall" + + newTo := getValidToRef() + newTo.Name = "newToChannel" tests := []struct { name string @@ -217,24 +220,39 @@ func TestSubscriptionImmutable(t *testing.T) { name: "new nil is ok", c: &Subscription{ Spec: SubscriptionSpec{ - From: getValidFromRef(), - Processor: getValidProcessor(), + From: getValidFromRef(), + Call: getValidCall(), }, }, og: nil, want: nil, }, { - name: "valid, new processor", + name: "valid, new call", c: &Subscription{ Spec: SubscriptionSpec{ - From: getValidFromRef(), - Processor: getValidProcessor(), + From: getValidFromRef(), + Call: getValidCall(), }, }, og: &Subscription{ Spec: SubscriptionSpec{ - From: getValidFromRef(), - Processor: newProcessor, + From: getValidFromRef(), + Call: newCall, + }, + }, + want: nil, + }, { + name: "valid, new to", + c: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + To: getValidToRef(), + }, + }, + og: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + Call: newTo, }, }, want: nil, @@ -274,8 +292,8 @@ func TestInvalidImmutableType(t *testing.T) { name := "invalid type" c := &Subscription{ Spec: SubscriptionSpec{ - From: getValidFromRef(), - Processor: getValidProcessor(), + From: getValidFromRef(), + Call: getValidCall(), }, } og := &DummyImmutableType{} From 38983f010d3fb8099e12bee32bc598d3d676d5ed Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Wed, 12 Sep 2018 22:26:11 +0000 Subject: [PATCH 11/15] Revert changes to pkg/channels, move everything over to pkg/eventing --- Gopkg.lock | 4 +- hack/update-codegen.sh | 2 +- .../channels/v1alpha1/subscription_types.go | 66 +--- .../v1alpha1/subscription_validation.go | 22 +- .../v1alpha1/subscription_validation_test.go | 220 ++----------- pkg/apis/eventing/register.go | 21 ++ pkg/apis/eventing/v1alpha1/doc.go | 16 + pkg/apis/eventing/v1alpha1/register.go | 53 +++ .../v1alpha1/subscription_defaults.go | 25 ++ .../eventing/v1alpha1/subscription_types.go | 200 ++++++++++++ .../v1alpha1/subscription_validation.go | 77 +++++ .../v1alpha1/subscription_validation_test.go | 301 ++++++++++++++++++ .../v1alpha1/zz_generated.deepcopy.go | 230 +++++++++++++ pkg/client/clientset/versioned/clientset.go | 22 ++ .../versioned/fake/clientset_generated.go | 12 + .../clientset/versioned/fake/register.go | 2 + .../clientset/versioned/scheme/register.go | 2 + .../versioned/typed/eventing/v1alpha1/doc.go | 20 ++ .../eventing/v1alpha1/eventing_client.go | 90 ++++++ .../typed/eventing/v1alpha1/fake/doc.go | 20 ++ .../v1alpha1/fake/fake_eventing_client.go | 40 +++ .../v1alpha1/fake/fake_subscription.go | 128 ++++++++ .../eventing/v1alpha1/generated_expansion.go | 21 ++ .../typed/eventing/v1alpha1/subscription.go | 157 +++++++++ .../externalversions/eventing/interface.go | 46 +++ .../eventing/v1alpha1/interface.go | 45 +++ .../eventing/v1alpha1/subscription.go | 89 ++++++ .../informers/externalversions/factory.go | 6 + .../informers/externalversions/generic.go | 5 + .../eventing/v1alpha1/expansion_generated.go | 27 ++ .../listers/eventing/v1alpha1/subscription.go | 94 ++++++ 31 files changed, 1795 insertions(+), 268 deletions(-) create mode 100644 pkg/apis/eventing/register.go create mode 100644 pkg/apis/eventing/v1alpha1/doc.go create mode 100644 pkg/apis/eventing/v1alpha1/register.go create mode 100644 pkg/apis/eventing/v1alpha1/subscription_defaults.go create mode 100644 pkg/apis/eventing/v1alpha1/subscription_types.go create mode 100644 pkg/apis/eventing/v1alpha1/subscription_validation.go create mode 100644 pkg/apis/eventing/v1alpha1/subscription_validation_test.go create mode 100644 pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go create mode 100644 pkg/client/clientset/versioned/typed/eventing/v1alpha1/doc.go create mode 100644 pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go create mode 100644 pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/doc.go create mode 100644 pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go create mode 100644 pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_subscription.go create mode 100644 pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go create mode 100644 pkg/client/clientset/versioned/typed/eventing/v1alpha1/subscription.go create mode 100644 pkg/client/informers/externalversions/eventing/interface.go create mode 100644 pkg/client/informers/externalversions/eventing/v1alpha1/interface.go create mode 100644 pkg/client/informers/externalversions/eventing/v1alpha1/subscription.go create mode 100644 pkg/client/listers/eventing/v1alpha1/expansion_generated.go create mode 100644 pkg/client/listers/eventing/v1alpha1/subscription.go diff --git a/Gopkg.lock b/Gopkg.lock index 958d97db59a..5b50b3a3b33 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -310,7 +310,7 @@ [[projects]] branch = "master" - digest = "1:36968d4eb1d52090841ae868d7125d8ff10afabdea0d6b622c1f4a662f94be58" + digest = "1:849fc53918b4658b896a43881305d60afc0d6a51d87c0d5b065afd631415f1d4" name = "github.com/knative/test-infra" packages = ["."] pruneopts = "T" @@ -1041,8 +1041,10 @@ "github.com/knative/pkg/webhook", "github.com/knative/serving/pkg/apis/serving/v1alpha1", "github.com/knative/serving/pkg/client/clientset/versioned", + "github.com/knative/test-infra", "github.com/prometheus/client_golang/prometheus/promhttp", "go.uber.org/zap", + "go.uber.org/zap/zapcore", "golang.org/x/net/context", "golang.org/x/oauth2", "google.golang.org/grpc/codes", diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index ea164dfe467..9a177fae61e 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -28,7 +28,7 @@ CODEGEN_PKG=${CODEGEN_PKG:-$(cd ${REPO_ROOT_DIR}; ls -d -1 ./vendor/k8s.io/code- # instead of the $GOPATH directly. For normal projects this can be dropped. ${CODEGEN_PKG}/generate-groups.sh "deepcopy,client,informer,lister" \ github.com/knative/eventing/pkg/client github.com/knative/eventing/pkg/apis \ - "channels:v1alpha1 feeds:v1alpha1 flows:v1alpha1" \ + "channels:v1alpha1 feeds:v1alpha1 flows:v1alpha1 eventing:v1alpha1" \ --go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt # Make sure our dependencies are up-to-date diff --git a/pkg/apis/channels/v1alpha1/subscription_types.go b/pkg/apis/channels/v1alpha1/subscription_types.go index 38f4e199d0e..93747ae103f 100644 --- a/pkg/apis/channels/v1alpha1/subscription_types.go +++ b/pkg/apis/channels/v1alpha1/subscription_types.go @@ -22,7 +22,6 @@ import ( "github.com/knative/pkg/apis" "github.com/knative/pkg/webhook" "k8s.io/api/core/v1" - corev1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -48,14 +47,8 @@ var _ apis.Immutable = (*Subscription)(nil) var _ runtime.Object = (*Subscription)(nil) var _ webhook.GenericCRD = (*ClusterBus)(nil) -// SubscriptionSpec specifies the Channel for incoming events, a handler and the Channel -// for outgoing messages. -// from --[transform]--> to -// Note that the following are valid configurations also: -// Sink, no outgoing events: -// from -- transform -// no-op function (identity transformation): -// from --> to +// SubscriptionSpec specifies the Channel and Subscriber and the configuration +// arguments for the Subscription. type SubscriptionSpec struct { // TODO: Generation does not work correctly with CRD. They are scrubbed // by the APIserver (https://github.com/kubernetes/kubernetes/issues/58778) @@ -64,53 +57,14 @@ type SubscriptionSpec struct { // +optional Generation int64 `json:"generation,omitempty"` - // Reference to an object that will be used to create the subscription - // for receiving events. The object must have spec.subscriptions - // list which will then be modified accordingly. - // - // This object must fulfill the Subscribable contract. - // - // You can specify only the following fields of the ObjectReference: - // - Kind - // - APIVersion - // - Name - From *corev1.ObjectReference `json:"from,omitempty"` - - // Call is reference to (optional) function for processing events. - // Events from the From channel will be delivered here and replies - // are sent to To channel. - // - // This object must fulfill the Targetable contract. - // - // Reference to an object that will be used to deliver events for - // (optional) processing before sending them to To for further - // if specified for additional Subscriptions to then subscribe - // to these events for further processing. - // - // For example, this could be a reference to a Route resource - // or a Service resource. - // TODO: Specify the required fields the target object must - // have in the status. - // You can specify only the following fields of the ObjectReference: - // - Kind - // - APIVersion - // - Name - // +optional - Call *corev1.ObjectReference `json:"call,omitempty"` - - // To is the (optional) resolved channel where (optionally) processed - // events get sent. - // - // This object must fulfill the Sinkable contract. - // - // TODO: Specify the required fields the target object must - // have in the status. - // You can specify only the following fields of the ObjectReference: - // - Kind - // - APIVersion - // - Name - // +optional - To *corev1.ObjectReference `json:"to,omitempty"` + // Channel is the name of the channel to subscribe to. + Channel string `json:"channel"` + + // Subscriber is the name of the subscriber service DNS name. + Subscriber string `json:"subscriber"` + + // Target service DNS name for replies returned by the subscriber. + ReplyTo string `json:"replyTo,omitempty"` // Arguments is a list of configuration arguments for the Subscription. The // Arguments for a channel must contain values for each of the Parameters diff --git a/pkg/apis/channels/v1alpha1/subscription_validation.go b/pkg/apis/channels/v1alpha1/subscription_validation.go index 41fc92427f6..c2f7ae8ab91 100644 --- a/pkg/apis/channels/v1alpha1/subscription_validation.go +++ b/pkg/apis/channels/v1alpha1/subscription_validation.go @@ -20,8 +20,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/knative/pkg/apis" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/equality" ) func (s *Subscription) Validate() *apis.FieldError { @@ -29,20 +27,14 @@ func (s *Subscription) Validate() *apis.FieldError { } func (ss *SubscriptionSpec) Validate() *apis.FieldError { - if ss.From == nil || equality.Semantic.DeepEqual(ss.From, &corev1.ObjectReference{}) { - fe := apis.ErrMissingField("from") - fe.Details = "the Subscription must reference a from channel" + if ss.Channel == "" { + fe := apis.ErrMissingField("channel") + fe.Details = "the Subscription must reference a Channel" return fe } - if ss.Call == nil && ss.To == nil { - fe := apis.ErrMissingField("to", "call") - fe.Details = "the Subscription must reference at least one of (to channel or a call)" - return fe - } - - if equality.Semantic.DeepEqual(ss.Call, &corev1.ObjectReference{}) && equality.Semantic.DeepEqual(ss.To, &corev1.ObjectReference{}) { - fe := apis.ErrMissingField("to", "call") - fe.Details = "the Subscription must reference at least one of (to channel or a call)" + if ss.Subscriber == "" { + fe := apis.ErrMissingField("subscriber") + fe.Details = "the Subscription must reference a Subscriber" return fe } return nil @@ -57,7 +49,7 @@ func (current *Subscription) CheckImmutableFields(og apis.Immutable) *apis.Field return nil } - ignoreArguments := cmpopts.IgnoreFields(SubscriptionSpec{}, "Call", "To", "Arguments") + ignoreArguments := cmpopts.IgnoreFields(SubscriptionSpec{}, "Subscriber", "Arguments") if diff := cmp.Diff(original.Spec, current.Spec, ignoreArguments); diff != "" { return &apis.FieldError{ Message: "Immutable fields changed (-old +new)", diff --git a/pkg/apis/channels/v1alpha1/subscription_validation_test.go b/pkg/apis/channels/v1alpha1/subscription_validation_test.go index 2a99a1d61e4..f585ab191b9 100644 --- a/pkg/apis/channels/v1alpha1/subscription_validation_test.go +++ b/pkg/apis/channels/v1alpha1/subscription_validation_test.go @@ -20,72 +20,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/knative/pkg/apis" - corev1 "k8s.io/api/core/v1" ) -const ( - channelKind = "Channel" - channelAPIVersion = "eventing.knative.dev/v1alpha1" - routeKind = "Route" - routeAPIVersion = "serving.knative.dev/v1alpha1" - FromChannelName = "fromChannel" - ToChannelName = "toChannel" - CallName = "call" -) - -func getValidFromRef() *corev1.ObjectReference { - return &corev1.ObjectReference{ - Name: FromChannelName, - Kind: channelKind, - APIVersion: channelAPIVersion, - } -} - -func getValidToRef() *corev1.ObjectReference { - return &corev1.ObjectReference{ - Name: ToChannelName, - Kind: channelKind, - APIVersion: channelAPIVersion, - } -} - -func getValidCall() *corev1.ObjectReference { - return &corev1.ObjectReference{ - Name: CallName, - Kind: routeKind, - APIVersion: routeAPIVersion, - } -} - -type DummyImmutableType struct{} - -func (d *DummyImmutableType) CheckImmutableFields(og apis.Immutable) *apis.FieldError { - return nil -} - -func TestSubscriptionValidation(t *testing.T) { - name := "empty from" - c := &Subscription{ - - Spec: SubscriptionSpec{ - From: &corev1.ObjectReference{}, - }, - } - want := &apis.FieldError{ - Paths: []string{"spec.from"}, - Message: "missing field(s)", - Details: "the Subscription must reference a from channel", - } - - t.Run(name, func(t *testing.T) { - got := c.Validate() - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("CheckImmutableFields (-want, +got) = %v", diff) - } - }) - -} - func TestSubscriptionSpecValidation(t *testing.T) { tests := []struct { name string @@ -94,86 +30,34 @@ func TestSubscriptionSpecValidation(t *testing.T) { }{{ name: "valid", c: &SubscriptionSpec{ - From: getValidFromRef(), - Call: getValidCall(), + Channel: "bar", + Subscriber: "foo", }, want: nil, }, { name: "valid with arguments", c: &SubscriptionSpec{ - From: getValidFromRef(), - Call: getValidCall(), - Arguments: &[]Argument{{Name: "foo", Value: "bar"}}, + Channel: "bar", + Subscriber: "foo", + Arguments: &[]Argument{{Name: "foo", Value: "bar"}}, }, want: nil, }, { - name: "empty from", - c: &SubscriptionSpec{ - From: &corev1.ObjectReference{}, - }, - want: func() *apis.FieldError { - fe := apis.ErrMissingField("from") - fe.Details = "the Subscription must reference a from channel" - return fe - }(), - }, { - name: "missing call and to", + name: "missing subscriber", c: &SubscriptionSpec{ - From: getValidFromRef(), + Channel: "foo", }, want: func() *apis.FieldError { - fe := apis.ErrMissingField("to", "call") - fe.Details = "the Subscription must reference at least one of (to channel or a call)" + fe := apis.ErrMissingField("subscriber") + fe.Details = "the Subscription must reference a Subscriber" return fe }(), - }, { - name: "empty call and to", - c: &SubscriptionSpec{ - From: getValidFromRef(), - Call: &corev1.ObjectReference{}, - To: &corev1.ObjectReference{}, - }, - want: func() *apis.FieldError { - fe := apis.ErrMissingField("to", "call") - fe.Details = "the Subscription must reference at least one of (to channel or a call)" - return fe - }(), - }, { - name: "missing to", - c: &SubscriptionSpec{ - From: getValidFromRef(), - Call: getValidCall(), - }, - want: nil, - }, { - name: "empty to", - c: &SubscriptionSpec{ - From: getValidFromRef(), - Call: getValidCall(), - To: &corev1.ObjectReference{}, - }, - want: nil, - }, { - name: "missing call", - c: &SubscriptionSpec{ - From: getValidFromRef(), - To: getValidToRef(), - }, - want: nil, - }, { - name: "empty call", - c: &SubscriptionSpec{ - From: getValidFromRef(), - Call: &corev1.ObjectReference{}, - To: getValidToRef(), - }, - want: nil, }, { name: "empty", c: &SubscriptionSpec{}, want: func() *apis.FieldError { - fe := apis.ErrMissingField("from") - fe.Details = "the Subscription must reference a from channel" + fe := apis.ErrMissingField("channel") + fe.Details = "the Subscription must reference a Channel" return fe }(), }} @@ -182,22 +66,13 @@ func TestSubscriptionSpecValidation(t *testing.T) { t.Run(test.name, func(t *testing.T) { got := test.c.Validate() if diff := cmp.Diff(test.want, got); diff != "" { - t.Errorf("validateFrom (-want, +got) = %v", diff) + t.Errorf("validateChannel (-want, +got) = %v", diff) } }) } } func TestSubscriptionImmutable(t *testing.T) { - newFrom := getValidFromRef() - newFrom.Name = "newFromChannel" - - newCall := getValidCall() - newCall.Name = "newCall" - - newTo := getValidToRef() - newTo.Name = "newToChannel" - tests := []struct { name string c *Subscription @@ -207,73 +82,48 @@ func TestSubscriptionImmutable(t *testing.T) { name: "valid", c: &Subscription{ Spec: SubscriptionSpec{ - From: getValidFromRef(), + Channel: "foo", }, }, og: &Subscription{ Spec: SubscriptionSpec{ - From: getValidFromRef(), + Channel: "foo", }, }, want: nil, }, { - name: "new nil is ok", + name: "valid, new subscriber", c: &Subscription{ Spec: SubscriptionSpec{ - From: getValidFromRef(), - Call: getValidCall(), - }, - }, - og: nil, - want: nil, - }, { - name: "valid, new call", - c: &Subscription{ - Spec: SubscriptionSpec{ - From: getValidFromRef(), - Call: getValidCall(), - }, - }, - og: &Subscription{ - Spec: SubscriptionSpec{ - From: getValidFromRef(), - Call: newCall, - }, - }, - want: nil, - }, { - name: "valid, new to", - c: &Subscription{ - Spec: SubscriptionSpec{ - From: getValidFromRef(), - To: getValidToRef(), + Channel: "foo", + Subscriber: "bar", }, }, og: &Subscription{ Spec: SubscriptionSpec{ - From: getValidFromRef(), - Call: newTo, + Channel: "foo", + Subscriber: "baz", }, }, want: nil, }, { - name: "from changed", + name: "channel changed", c: &Subscription{ Spec: SubscriptionSpec{ - From: getValidFromRef(), + Channel: "foo", }, }, og: &Subscription{ Spec: SubscriptionSpec{ - From: newFrom, + Channel: "bar", }, }, want: &apis.FieldError{ Message: "Immutable fields changed (-old +new)", Paths: []string{"spec"}, - Details: `{v1alpha1.SubscriptionSpec}.From.Name: - -: "newFromChannel" - +: "fromChannel" + Details: `{v1alpha1.SubscriptionSpec}.Channel: + -: "bar" + +: "foo" `, }, }} @@ -287,23 +137,3 @@ func TestSubscriptionImmutable(t *testing.T) { }) } } - -func TestInvalidImmutableType(t *testing.T) { - name := "invalid type" - c := &Subscription{ - Spec: SubscriptionSpec{ - From: getValidFromRef(), - Call: getValidCall(), - }, - } - og := &DummyImmutableType{} - want := &apis.FieldError{ - Message: "The provided original was not a Subscription", - } - t.Run(name, func(t *testing.T) { - got := c.CheckImmutableFields(og) - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("CheckImmutableFields (-want, +got) = %v", diff) - } - }) -} diff --git a/pkg/apis/eventing/register.go b/pkg/apis/eventing/register.go new file mode 100644 index 00000000000..49493ed5772 --- /dev/null +++ b/pkg/apis/eventing/register.go @@ -0,0 +1,21 @@ +/* +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 eventing + +const ( + GroupName = "eventing.knative.dev" +) diff --git a/pkg/apis/eventing/v1alpha1/doc.go b/pkg/apis/eventing/v1alpha1/doc.go new file mode 100644 index 00000000000..b2c114bfe8a --- /dev/null +++ b/pkg/apis/eventing/v1alpha1/doc.go @@ -0,0 +1,16 @@ +/* +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. +*/ +// +k8s:deepcopy-gen=package +// Package v1alpha1 is the v1alpha1 version of the API. +// +groupName=eventing.knative.dev +package v1alpha1 diff --git a/pkg/apis/eventing/v1alpha1/register.go b/pkg/apis/eventing/v1alpha1/register.go new file mode 100644 index 00000000000..3f24f758a88 --- /dev/null +++ b/pkg/apis/eventing/v1alpha1/register.go @@ -0,0 +1,53 @@ +/* +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 ( + "github.com/knative/eventing/pkg/apis/eventing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: eventing.GroupName, Version: "v1alpha1"} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Subscription{}, + &SubscriptionList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/apis/eventing/v1alpha1/subscription_defaults.go b/pkg/apis/eventing/v1alpha1/subscription_defaults.go new file mode 100644 index 00000000000..ed8fdf5b16f --- /dev/null +++ b/pkg/apis/eventing/v1alpha1/subscription_defaults.go @@ -0,0 +1,25 @@ +/* +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 + +func (s *Subscription) SetDefaults() { + s.Spec.SetDefaults() +} + +func (ss *SubscriptionSpec) SetDefaults() { + // TODO anything? +} diff --git a/pkg/apis/eventing/v1alpha1/subscription_types.go b/pkg/apis/eventing/v1alpha1/subscription_types.go new file mode 100644 index 00000000000..83e4650f208 --- /dev/null +++ b/pkg/apis/eventing/v1alpha1/subscription_types.go @@ -0,0 +1,200 @@ +/* + * 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 ( + "encoding/json" + + "github.com/knative/pkg/apis" + "github.com/knative/pkg/webhook" + "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// +genclient +// +genclient:noStatus +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:defaulter-gen=true + +// Subscription routes events received on a Channel to a DNS name and +// corresponds to the subscriptions.channels.knative.dev CRD. +type Subscription struct { + meta_v1.TypeMeta `json:",inline"` + meta_v1.ObjectMeta `json:"metadata"` + Spec SubscriptionSpec `json:"spec"` + Status SubscriptionStatus `json:"status,omitempty"` +} + +// Check that Subscription can be validated, can be defaulted, and has immutable fields. +var _ apis.Validatable = (*Subscription)(nil) +var _ apis.Defaultable = (*Subscription)(nil) +var _ apis.Immutable = (*Subscription)(nil) +var _ runtime.Object = (*Subscription)(nil) +var _ webhook.GenericCRD = (*Subscription)(nil) + +// SubscriptionSpec specifies the Channel for incoming events, a Call target for +// processing those events and where to put the result of the processing. Only +// From (where the events are coming from) is always required. You can optionally +// only Process the events (results in no output events) by leaving out the Result. +// You can also perform an identity transformation on the invoming events by leaving +// out the Call and only specifying Result. +// +// The following are all valid specifications: +// from --[call]--> result +// Sink, no outgoing events: +// from -- call +// no-op function (identity transformation): +// from --> result +type SubscriptionSpec struct { + // TODO: Generation used to not work correctly with CRD. They were scrubbed + // by the APIserver (https://github.com/kubernetes/kubernetes/issues/58778) + // So, we add Generation here. Once the above bug gets rolled out to production + // clusters, remove this and use ObjectMeta.Generation instead. + // +optional + Generation int64 `json:"generation,omitempty"` + + // Reference to an object that will be used to create the subscription + // for receiving events. The object must have spec.subscriptions + // list which will then be modified accordingly. + // + // This object must fulfill the Subscribable contract. + // + // You can specify only the following fields of the ObjectReference: + // - Kind + // - APIVersion + // - Name + // This field is immutable + From *corev1.ObjectReference `json:"from,omitempty"` + + // Call is reference to (optional) function for processing events. + // Events from the From channel will be delivered here and replies + // are sent to a channel as specified by the Result. + // +optional + Call *Callable `json:"call,omitempty"` + + // Result specifies (optionally) how to handle events returned from + // the Call target. + // +optional + Result *ResultStrategy `json:"to,omitempty"` +} + +// Callable specifies the reference to an object that's expected to +// provide the resolved target of the action. Currently we inspect +// the objects Status and see if there's a predefined Status field +// that we will then use to dispatch events to be processed by the target. +// Currently must resolve to a k8s service or Istio virtual service. Note that +// in the future we should try to utilize subresources (/resolve ?) to +// make this cleaner, but CRDs do not support subresources yet, so we need +// to rely on a specified Status field today. By relying on this behaviour +// we can utilize a dynamic client instead of having to understand all +// kinds of different types of objects. As long as they adhere to this +// particular contract, they can be used as a Target. +// This ensures that we can support external targets and for ease of use +// we also allow for an URI to be specified. +// There of course is also a requirement for the resolved Callable to +// behave properly at the data plane level. +type Callable struct { + // Only one of these can be specified + + // Reference to an object that will be used to find the target + // endpoint. + // For example, this could be a reference to a Route resource + // or a Knative Service resource. + // TODO: Specify the required fields the target object must + // have in the status. + // You can specify only the following fields of the ObjectReference: + // - Kind + // - APIVersion + // - Name + // +optional + Target *corev1.ObjectReference `json:"target,omitempty"` + + // Reference to a 'known' endpoint where no resolving is done. + // http://k8s-service for example + // http://myexternalhandler.example.com/foo/bar + // +optional + TargetURI *string `json:"targetURI,omitempty"` +} + +type ResultStrategy struct { + // This object must fulfill the Sinkable contract. + // + // TODO: Specify the required fields the target object must + // have in the status. + // You can specify only the following fields of the ObjectReference: + // - Kind + // - APIVersion + // - Name + // +optional + Target *corev1.ObjectReference `json:"target,omitempty"` +} + +type SubscriptionConditionType string + +const ( + // Dispatching means the subscription is actively listening for incoming events on its channel and dispatching them. + SubscriptionDispatching SubscriptionConditionType = "Dispatching" +) + +// SubscriptionCondition describes the state of a subscription at a point in time. +type SubscriptionCondition struct { + // Type of subscription condition. + Type SubscriptionConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status v1.ConditionStatus `json:"status"` + // The last time this condition was updated. + LastUpdateTime meta_v1.Time `json:"lastUpdateTime,omitempty"` + // Last time the condition transitioned from one status to another. + LastTransitionTime meta_v1.Time `json:"lastTransitionTime,omitempty"` + // The reason for the condition's last transition. + Reason string `json:"reason,omitempty"` + // A human readable message indicating details about the transition. + Message string `json:"message,omitempty"` +} + +// SubscriptionStatus (computed) for a subscription +type SubscriptionStatus struct { + + // Represents the latest available observations of a subscription's current state. + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []SubscriptionCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +} + +func (ss *SubscriptionStatus) GetCondition(t SubscriptionConditionType) *SubscriptionCondition { + for _, cond := range ss.Conditions { + if cond.Type == t { + return &cond + } + } + return nil +} + +func (s *Subscription) GetSpecJSON() ([]byte, error) { + return json.Marshal(s.Spec) +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// SubscriptionList returned in list operations +type SubscriptionList struct { + meta_v1.TypeMeta `json:",inline"` + meta_v1.ListMeta `json:"metadata"` + Items []Subscription `json:"items"` +} diff --git a/pkg/apis/eventing/v1alpha1/subscription_validation.go b/pkg/apis/eventing/v1alpha1/subscription_validation.go new file mode 100644 index 00000000000..09734585407 --- /dev/null +++ b/pkg/apis/eventing/v1alpha1/subscription_validation.go @@ -0,0 +1,77 @@ +/* +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 ( + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/knative/pkg/apis" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" +) + +func (s *Subscription) Validate() *apis.FieldError { + return s.Spec.Validate().ViaField("spec") +} + +// We require always From +// Also at least one of 'call' and 'result' must be defined (non-nill and non-empty) +func (ss *SubscriptionSpec) Validate() *apis.FieldError { + if ss.From == nil || equality.Semantic.DeepEqual(ss.From, &corev1.ObjectReference{}) { + fe := apis.ErrMissingField("from") + fe.Details = "the Subscription must reference a from channel" + return fe + } + + if ss.Call == nil && ss.Result == nil { + fe := apis.ErrMissingField("result", "call") + fe.Details = "the Subscription must reference at least one of (result channel or a call)" + return fe + } + + if equality.Semantic.DeepEqual(ss.Call, &Callable{}) && equality.Semantic.DeepEqual(ss.Result, &ResultStrategy{}) { + fe := apis.ErrMissingField("result", "call") + fe.Details = "the Subscription must reference at least one of (result channel or a call)" + return fe + } + + // TODO(Before checking in): validate the underlying Call/Result/From properly once we settle on the + // shapes of these things. + + return nil +} + +func (current *Subscription) CheckImmutableFields(og apis.Immutable) *apis.FieldError { + original, ok := og.(*Subscription) + if !ok { + return &apis.FieldError{Message: "The provided original was not a Subscription"} + } + if original == nil { + return nil + } + + // Only Call and Result are mutable. + ignoreArguments := cmpopts.IgnoreFields(SubscriptionSpec{}, "Call", "Result") + if diff := cmp.Diff(original.Spec, current.Spec, ignoreArguments); diff != "" { + return &apis.FieldError{ + Message: "Immutable fields changed (-old +new)", + Paths: []string{"spec"}, + Details: diff, + } + } + return nil +} diff --git a/pkg/apis/eventing/v1alpha1/subscription_validation_test.go b/pkg/apis/eventing/v1alpha1/subscription_validation_test.go new file mode 100644 index 00000000000..a4e2dd8e1b6 --- /dev/null +++ b/pkg/apis/eventing/v1alpha1/subscription_validation_test.go @@ -0,0 +1,301 @@ +/* +Copyright 2018 The Knative Authors. All Rights Reserved. +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 ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/knative/pkg/apis" + corev1 "k8s.io/api/core/v1" +) + +const ( + channelKind = "Channel" + channelAPIVersion = "eventing.knative.dev/v1alpha1" + routeKind = "Route" + routeAPIVersion = "serving.knative.dev/v1alpha1" + FromChannelName = "fromChannel" + ToChannelName = "toChannel" + CallName = "call" +) + +func getValidFromRef() *corev1.ObjectReference { + return &corev1.ObjectReference{ + Name: FromChannelName, + Kind: channelKind, + APIVersion: channelAPIVersion, + } +} + +func getValidToRef() *corev1.ObjectReference { + return &corev1.ObjectReference{ + Name: ToChannelName, + Kind: channelKind, + APIVersion: channelAPIVersion, + } +} + +func getValidCall() *corev1.ObjectReference { + return &corev1.ObjectReference{ + Name: CallName, + Kind: routeKind, + APIVersion: routeAPIVersion, + } +} + +type DummyImmutableType struct{} + +func (d *DummyImmutableType) CheckImmutableFields(og apis.Immutable) *apis.FieldError { + return nil +} + +func TestSubscriptionValidation(t *testing.T) { + name := "empty from" + c := &Subscription{ + + Spec: SubscriptionSpec{ + From: &corev1.ObjectReference{}, + }, + } + want := &apis.FieldError{ + Paths: []string{"spec.from"}, + Message: "missing field(s)", + Details: "the Subscription must reference a from channel", + } + + t.Run(name, func(t *testing.T) { + got := c.Validate() + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("CheckImmutableFields (-want, +got) = %v", diff) + } + }) + +} + +func TestSubscriptionSpecValidation(t *testing.T) { + tests := []struct { + name string + c *SubscriptionSpec + want *apis.FieldError + }{{ + name: "valid", + c: &SubscriptionSpec{ + From: getValidFromRef(), + Call: getValidCall(), + }, + want: nil, + }, { + name: "empty from", + c: &SubscriptionSpec{ + From: &corev1.ObjectReference{}, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("from") + fe.Details = "the Subscription must reference a from channel" + return fe + }(), + }, { + name: "missing call and to", + c: &SubscriptionSpec{ + From: getValidFromRef(), + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("to", "call") + fe.Details = "the Subscription must reference at least one of (to channel or a call)" + return fe + }(), + }, { + name: "empty call and to", + c: &SubscriptionSpec{ + From: getValidFromRef(), + Call: &corev1.ObjectReference{}, + To: &corev1.ObjectReference{}, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("to", "call") + fe.Details = "the Subscription must reference at least one of (to channel or a call)" + return fe + }(), + }, { + name: "missing to", + c: &SubscriptionSpec{ + From: getValidFromRef(), + Call: getValidCall(), + }, + want: nil, + }, { + name: "empty to", + c: &SubscriptionSpec{ + From: getValidFromRef(), + Call: getValidCall(), + To: &corev1.ObjectReference{}, + }, + want: nil, + }, { + name: "missing call", + c: &SubscriptionSpec{ + From: getValidFromRef(), + To: getValidToRef(), + }, + want: nil, + }, { + name: "empty call", + c: &SubscriptionSpec{ + From: getValidFromRef(), + Call: &corev1.ObjectReference{}, + To: getValidToRef(), + }, + want: nil, + }, { + name: "empty", + c: &SubscriptionSpec{}, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("from") + fe.Details = "the Subscription must reference a from channel" + return fe + }(), + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.c.Validate() + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("validateFrom (-want, +got) = %v", diff) + } + }) + } +} + +func TestSubscriptionImmutable(t *testing.T) { + newFrom := getValidFromRef() + newFrom.Name = "newFromChannel" + + newCall := getValidCall() + newCall.Name = "newCall" + + newTo := getValidToRef() + newTo.Name = "newToChannel" + + tests := []struct { + name string + c *Subscription + og *Subscription + want *apis.FieldError + }{{ + name: "valid", + c: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + }, + }, + og: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + }, + }, + want: nil, + }, { + name: "new nil is ok", + c: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + Call: getValidCall(), + }, + }, + og: nil, + want: nil, + }, { + name: "valid, new call", + c: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + Call: getValidCall(), + }, + }, + og: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + Call: newCall, + }, + }, + want: nil, + }, { + name: "valid, new to", + c: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + To: getValidToRef(), + }, + }, + og: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + Call: newTo, + }, + }, + want: nil, + }, { + name: "from changed", + c: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + }, + }, + og: &Subscription{ + Spec: SubscriptionSpec{ + From: newFrom, + }, + }, + want: &apis.FieldError{ + Message: "Immutable fields changed (-old +new)", + Paths: []string{"spec"}, + Details: `{v1alpha1.SubscriptionSpec}.From.Name: + -: "newFromChannel" + +: "fromChannel" +`, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.c.CheckImmutableFields(test.og) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("CheckImmutableFields (-want, +got) = %v", diff) + } + }) + } +} + +func TestInvalidImmutableType(t *testing.T) { + name := "invalid type" + c := &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + Call: getValidCall(), + }, + } + og := &DummyImmutableType{} + want := &apis.FieldError{ + Message: "The provided original was not a Subscription", + } + t.Run(name, func(t *testing.T) { + got := c.CheckImmutableFields(og) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("CheckImmutableFields (-want, +got) = %v", diff) + } + }) +} diff --git a/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..e9d67673dc5 --- /dev/null +++ b/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,230 @@ +// +build !ignore_autogenerated + +/* +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 deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/api/core/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Callable) DeepCopyInto(out *Callable) { + *out = *in + if in.Target != nil { + in, out := &in.Target, &out.Target + if *in == nil { + *out = nil + } else { + *out = new(v1.ObjectReference) + **out = **in + } + } + if in.TargetURI != nil { + in, out := &in.TargetURI, &out.TargetURI + if *in == nil { + *out = nil + } else { + *out = new(string) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Callable. +func (in *Callable) DeepCopy() *Callable { + if in == nil { + return nil + } + out := new(Callable) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResultStrategy) DeepCopyInto(out *ResultStrategy) { + *out = *in + if in.Target != nil { + in, out := &in.Target, &out.Target + if *in == nil { + *out = nil + } else { + *out = new(v1.ObjectReference) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResultStrategy. +func (in *ResultStrategy) DeepCopy() *ResultStrategy { + if in == nil { + return nil + } + out := new(ResultStrategy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Subscription) DeepCopyInto(out *Subscription) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subscription. +func (in *Subscription) DeepCopy() *Subscription { + if in == nil { + return nil + } + out := new(Subscription) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Subscription) 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 *SubscriptionCondition) DeepCopyInto(out *SubscriptionCondition) { + *out = *in + in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime) + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionCondition. +func (in *SubscriptionCondition) DeepCopy() *SubscriptionCondition { + if in == nil { + return nil + } + out := new(SubscriptionCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubscriptionList) DeepCopyInto(out *SubscriptionList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Subscription, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionList. +func (in *SubscriptionList) DeepCopy() *SubscriptionList { + if in == nil { + return nil + } + out := new(SubscriptionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SubscriptionList) 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 *SubscriptionSpec) DeepCopyInto(out *SubscriptionSpec) { + *out = *in + if in.From != nil { + in, out := &in.From, &out.From + if *in == nil { + *out = nil + } else { + *out = new(v1.ObjectReference) + **out = **in + } + } + if in.Call != nil { + in, out := &in.Call, &out.Call + if *in == nil { + *out = nil + } else { + *out = new(Callable) + (*in).DeepCopyInto(*out) + } + } + if in.Result != nil { + in, out := &in.Result, &out.Result + if *in == nil { + *out = nil + } else { + *out = new(ResultStrategy) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionSpec. +func (in *SubscriptionSpec) DeepCopy() *SubscriptionSpec { + if in == nil { + return nil + } + out := new(SubscriptionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubscriptionStatus) DeepCopyInto(out *SubscriptionStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]SubscriptionCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionStatus. +func (in *SubscriptionStatus) DeepCopy() *SubscriptionStatus { + if in == nil { + return nil + } + out := new(SubscriptionStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index 2c1872f405a..c9df07b37da 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -20,6 +20,7 @@ package versioned import ( channelsv1alpha1 "github.com/knative/eventing/pkg/client/clientset/versioned/typed/channels/v1alpha1" + eventingv1alpha1 "github.com/knative/eventing/pkg/client/clientset/versioned/typed/eventing/v1alpha1" feedsv1alpha1 "github.com/knative/eventing/pkg/client/clientset/versioned/typed/feeds/v1alpha1" flowsv1alpha1 "github.com/knative/eventing/pkg/client/clientset/versioned/typed/flows/v1alpha1" discovery "k8s.io/client-go/discovery" @@ -32,6 +33,9 @@ type Interface interface { ChannelsV1alpha1() channelsv1alpha1.ChannelsV1alpha1Interface // Deprecated: please explicitly pick a version if possible. Channels() channelsv1alpha1.ChannelsV1alpha1Interface + EventingV1alpha1() eventingv1alpha1.EventingV1alpha1Interface + // Deprecated: please explicitly pick a version if possible. + Eventing() eventingv1alpha1.EventingV1alpha1Interface FeedsV1alpha1() feedsv1alpha1.FeedsV1alpha1Interface // Deprecated: please explicitly pick a version if possible. Feeds() feedsv1alpha1.FeedsV1alpha1Interface @@ -45,6 +49,7 @@ type Interface interface { type Clientset struct { *discovery.DiscoveryClient channelsV1alpha1 *channelsv1alpha1.ChannelsV1alpha1Client + eventingV1alpha1 *eventingv1alpha1.EventingV1alpha1Client feedsV1alpha1 *feedsv1alpha1.FeedsV1alpha1Client flowsV1alpha1 *flowsv1alpha1.FlowsV1alpha1Client } @@ -60,6 +65,17 @@ func (c *Clientset) Channels() channelsv1alpha1.ChannelsV1alpha1Interface { return c.channelsV1alpha1 } +// EventingV1alpha1 retrieves the EventingV1alpha1Client +func (c *Clientset) EventingV1alpha1() eventingv1alpha1.EventingV1alpha1Interface { + return c.eventingV1alpha1 +} + +// Deprecated: Eventing retrieves the default version of EventingClient. +// Please explicitly pick a version. +func (c *Clientset) Eventing() eventingv1alpha1.EventingV1alpha1Interface { + return c.eventingV1alpha1 +} + // FeedsV1alpha1 retrieves the FeedsV1alpha1Client func (c *Clientset) FeedsV1alpha1() feedsv1alpha1.FeedsV1alpha1Interface { return c.feedsV1alpha1 @@ -102,6 +118,10 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { if err != nil { return nil, err } + cs.eventingV1alpha1, err = eventingv1alpha1.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } cs.feedsV1alpha1, err = feedsv1alpha1.NewForConfig(&configShallowCopy) if err != nil { return nil, err @@ -123,6 +143,7 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { func NewForConfigOrDie(c *rest.Config) *Clientset { var cs Clientset cs.channelsV1alpha1 = channelsv1alpha1.NewForConfigOrDie(c) + cs.eventingV1alpha1 = eventingv1alpha1.NewForConfigOrDie(c) cs.feedsV1alpha1 = feedsv1alpha1.NewForConfigOrDie(c) cs.flowsV1alpha1 = flowsv1alpha1.NewForConfigOrDie(c) @@ -134,6 +155,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { func New(c rest.Interface) *Clientset { var cs Clientset cs.channelsV1alpha1 = channelsv1alpha1.New(c) + cs.eventingV1alpha1 = eventingv1alpha1.New(c) cs.feedsV1alpha1 = feedsv1alpha1.New(c) cs.flowsV1alpha1 = flowsv1alpha1.New(c) diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go index 4ad403e5ed2..1a6301babe4 100644 --- a/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -22,6 +22,8 @@ import ( clientset "github.com/knative/eventing/pkg/client/clientset/versioned" channelsv1alpha1 "github.com/knative/eventing/pkg/client/clientset/versioned/typed/channels/v1alpha1" fakechannelsv1alpha1 "github.com/knative/eventing/pkg/client/clientset/versioned/typed/channels/v1alpha1/fake" + eventingv1alpha1 "github.com/knative/eventing/pkg/client/clientset/versioned/typed/eventing/v1alpha1" + fakeeventingv1alpha1 "github.com/knative/eventing/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake" feedsv1alpha1 "github.com/knative/eventing/pkg/client/clientset/versioned/typed/feeds/v1alpha1" fakefeedsv1alpha1 "github.com/knative/eventing/pkg/client/clientset/versioned/typed/feeds/v1alpha1/fake" flowsv1alpha1 "github.com/knative/eventing/pkg/client/clientset/versioned/typed/flows/v1alpha1" @@ -85,6 +87,16 @@ func (c *Clientset) Channels() channelsv1alpha1.ChannelsV1alpha1Interface { return &fakechannelsv1alpha1.FakeChannelsV1alpha1{Fake: &c.Fake} } +// EventingV1alpha1 retrieves the EventingV1alpha1Client +func (c *Clientset) EventingV1alpha1() eventingv1alpha1.EventingV1alpha1Interface { + return &fakeeventingv1alpha1.FakeEventingV1alpha1{Fake: &c.Fake} +} + +// Eventing retrieves the EventingV1alpha1Client +func (c *Clientset) Eventing() eventingv1alpha1.EventingV1alpha1Interface { + return &fakeeventingv1alpha1.FakeEventingV1alpha1{Fake: &c.Fake} +} + // FeedsV1alpha1 retrieves the FeedsV1alpha1Client func (c *Clientset) FeedsV1alpha1() feedsv1alpha1.FeedsV1alpha1Interface { return &fakefeedsv1alpha1.FakeFeedsV1alpha1{Fake: &c.Fake} diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go index d4f9991e3ca..9c7ef66dd10 100644 --- a/pkg/client/clientset/versioned/fake/register.go +++ b/pkg/client/clientset/versioned/fake/register.go @@ -20,6 +20,7 @@ package fake import ( channelsv1alpha1 "github.com/knative/eventing/pkg/apis/channels/v1alpha1" + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" feedsv1alpha1 "github.com/knative/eventing/pkg/apis/feeds/v1alpha1" flowsv1alpha1 "github.com/knative/eventing/pkg/apis/flows/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -53,6 +54,7 @@ func init() { // correctly. func AddToScheme(scheme *runtime.Scheme) { channelsv1alpha1.AddToScheme(scheme) + eventingv1alpha1.AddToScheme(scheme) feedsv1alpha1.AddToScheme(scheme) flowsv1alpha1.AddToScheme(scheme) } diff --git a/pkg/client/clientset/versioned/scheme/register.go b/pkg/client/clientset/versioned/scheme/register.go index 19d55694dd3..b3afb7f218d 100644 --- a/pkg/client/clientset/versioned/scheme/register.go +++ b/pkg/client/clientset/versioned/scheme/register.go @@ -20,6 +20,7 @@ package scheme import ( channelsv1alpha1 "github.com/knative/eventing/pkg/apis/channels/v1alpha1" + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" feedsv1alpha1 "github.com/knative/eventing/pkg/apis/feeds/v1alpha1" flowsv1alpha1 "github.com/knative/eventing/pkg/apis/flows/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -53,6 +54,7 @@ func init() { // correctly. func AddToScheme(scheme *runtime.Scheme) { channelsv1alpha1.AddToScheme(scheme) + eventingv1alpha1.AddToScheme(scheme) feedsv1alpha1.AddToScheme(scheme) flowsv1alpha1.AddToScheme(scheme) } diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/doc.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/doc.go new file mode 100644 index 00000000000..75445c17900 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +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. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go new file mode 100644 index 00000000000..362710dd220 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/eventing_client.go @@ -0,0 +1,90 @@ +/* +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/eventing/pkg/apis/eventing/v1alpha1" + "github.com/knative/eventing/pkg/client/clientset/versioned/scheme" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + rest "k8s.io/client-go/rest" +) + +type EventingV1alpha1Interface interface { + RESTClient() rest.Interface + SubscriptionsGetter +} + +// EventingV1alpha1Client is used to interact with features provided by the eventing.knative.dev group. +type EventingV1alpha1Client struct { + restClient rest.Interface +} + +func (c *EventingV1alpha1Client) Subscriptions(namespace string) SubscriptionInterface { + return newSubscriptions(c, namespace) +} + +// NewForConfig creates a new EventingV1alpha1Client for the given config. +func NewForConfig(c *rest.Config) (*EventingV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &EventingV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new EventingV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *EventingV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new EventingV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *EventingV1alpha1Client { + return &EventingV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *EventingV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/doc.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/doc.go new file mode 100644 index 00000000000..128aa183a91 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/doc.go @@ -0,0 +1,20 @@ +/* +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 has the automatically generated clients. +package fake diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go new file mode 100644 index 00000000000..005dbd11327 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_eventing_client.go @@ -0,0 +1,40 @@ +/* +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/eventing/pkg/client/clientset/versioned/typed/eventing/v1alpha1" + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" +) + +type FakeEventingV1alpha1 struct { + *testing.Fake +} + +func (c *FakeEventingV1alpha1) Subscriptions(namespace string) v1alpha1.SubscriptionInterface { + return &FakeSubscriptions{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeEventingV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_subscription.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_subscription.go new file mode 100644 index 00000000000..85a728524aa --- /dev/null +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/fake/fake_subscription.go @@ -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/eventing/pkg/apis/eventing/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" +) + +// FakeSubscriptions implements SubscriptionInterface +type FakeSubscriptions struct { + Fake *FakeEventingV1alpha1 + ns string +} + +var subscriptionsResource = schema.GroupVersionResource{Group: "eventing.knative.dev", Version: "v1alpha1", Resource: "subscriptions"} + +var subscriptionsKind = schema.GroupVersionKind{Group: "eventing.knative.dev", Version: "v1alpha1", Kind: "Subscription"} + +// Get takes name of the subscription, and returns the corresponding subscription object, and an error if there is any. +func (c *FakeSubscriptions) Get(name string, options v1.GetOptions) (result *v1alpha1.Subscription, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(subscriptionsResource, c.ns, name), &v1alpha1.Subscription{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Subscription), err +} + +// List takes label and field selectors, and returns the list of Subscriptions that match those selectors. +func (c *FakeSubscriptions) List(opts v1.ListOptions) (result *v1alpha1.SubscriptionList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(subscriptionsResource, subscriptionsKind, c.ns, opts), &v1alpha1.SubscriptionList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.SubscriptionList{ListMeta: obj.(*v1alpha1.SubscriptionList).ListMeta} + for _, item := range obj.(*v1alpha1.SubscriptionList).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 subscriptions. +func (c *FakeSubscriptions) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(subscriptionsResource, c.ns, opts)) + +} + +// Create takes the representation of a subscription and creates it. Returns the server's representation of the subscription, and an error, if there is any. +func (c *FakeSubscriptions) Create(subscription *v1alpha1.Subscription) (result *v1alpha1.Subscription, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(subscriptionsResource, c.ns, subscription), &v1alpha1.Subscription{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Subscription), err +} + +// Update takes the representation of a subscription and updates it. Returns the server's representation of the subscription, and an error, if there is any. +func (c *FakeSubscriptions) Update(subscription *v1alpha1.Subscription) (result *v1alpha1.Subscription, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(subscriptionsResource, c.ns, subscription), &v1alpha1.Subscription{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Subscription), err +} + +// Delete takes name of the subscription and deletes it. Returns an error if one occurs. +func (c *FakeSubscriptions) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(subscriptionsResource, c.ns, name), &v1alpha1.Subscription{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeSubscriptions) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(subscriptionsResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha1.SubscriptionList{}) + return err +} + +// Patch applies the patch and returns the patched subscription. +func (c *FakeSubscriptions) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Subscription, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(subscriptionsResource, c.ns, name, data, subresources...), &v1alpha1.Subscription{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Subscription), err +} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go new file mode 100644 index 00000000000..9911c7c289f --- /dev/null +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/generated_expansion.go @@ -0,0 +1,21 @@ +/* +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 + +type SubscriptionExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1alpha1/subscription.go b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/subscription.go new file mode 100644 index 00000000000..dde8b0ac06b --- /dev/null +++ b/pkg/client/clientset/versioned/typed/eventing/v1alpha1/subscription.go @@ -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/eventing/pkg/apis/eventing/v1alpha1" + scheme "github.com/knative/eventing/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" +) + +// SubscriptionsGetter has a method to return a SubscriptionInterface. +// A group's client should implement this interface. +type SubscriptionsGetter interface { + Subscriptions(namespace string) SubscriptionInterface +} + +// SubscriptionInterface has methods to work with Subscription resources. +type SubscriptionInterface interface { + Create(*v1alpha1.Subscription) (*v1alpha1.Subscription, error) + Update(*v1alpha1.Subscription) (*v1alpha1.Subscription, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha1.Subscription, error) + List(opts v1.ListOptions) (*v1alpha1.SubscriptionList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Subscription, err error) + SubscriptionExpansion +} + +// subscriptions implements SubscriptionInterface +type subscriptions struct { + client rest.Interface + ns string +} + +// newSubscriptions returns a Subscriptions +func newSubscriptions(c *EventingV1alpha1Client, namespace string) *subscriptions { + return &subscriptions{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the subscription, and returns the corresponding subscription object, and an error if there is any. +func (c *subscriptions) Get(name string, options v1.GetOptions) (result *v1alpha1.Subscription, err error) { + result = &v1alpha1.Subscription{} + err = c.client.Get(). + Namespace(c.ns). + Resource("subscriptions"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Subscriptions that match those selectors. +func (c *subscriptions) List(opts v1.ListOptions) (result *v1alpha1.SubscriptionList, err error) { + result = &v1alpha1.SubscriptionList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("subscriptions"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested subscriptions. +func (c *subscriptions) Watch(opts v1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("subscriptions"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch() +} + +// Create takes the representation of a subscription and creates it. Returns the server's representation of the subscription, and an error, if there is any. +func (c *subscriptions) Create(subscription *v1alpha1.Subscription) (result *v1alpha1.Subscription, err error) { + result = &v1alpha1.Subscription{} + err = c.client.Post(). + Namespace(c.ns). + Resource("subscriptions"). + Body(subscription). + Do(). + Into(result) + return +} + +// Update takes the representation of a subscription and updates it. Returns the server's representation of the subscription, and an error, if there is any. +func (c *subscriptions) Update(subscription *v1alpha1.Subscription) (result *v1alpha1.Subscription, err error) { + result = &v1alpha1.Subscription{} + err = c.client.Put(). + Namespace(c.ns). + Resource("subscriptions"). + Name(subscription.Name). + Body(subscription). + Do(). + Into(result) + return +} + +// Delete takes name of the subscription and deletes it. Returns an error if one occurs. +func (c *subscriptions) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("subscriptions"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *subscriptions) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("subscriptions"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched subscription. +func (c *subscriptions) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Subscription, err error) { + result = &v1alpha1.Subscription{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("subscriptions"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/client/informers/externalversions/eventing/interface.go b/pkg/client/informers/externalversions/eventing/interface.go new file mode 100644 index 00000000000..17435a38470 --- /dev/null +++ b/pkg/client/informers/externalversions/eventing/interface.go @@ -0,0 +1,46 @@ +/* +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 eventing + +import ( + v1alpha1 "github.com/knative/eventing/pkg/client/informers/externalversions/eventing/v1alpha1" + internalinterfaces "github.com/knative/eventing/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() v1alpha1.Interface +} + +type group struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// V1alpha1 returns a new v1alpha1.Interface. +func (g *group) V1alpha1() v1alpha1.Interface { + return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/pkg/client/informers/externalversions/eventing/v1alpha1/interface.go b/pkg/client/informers/externalversions/eventing/v1alpha1/interface.go new file mode 100644 index 00000000000..a6a55a02c9f --- /dev/null +++ b/pkg/client/informers/externalversions/eventing/v1alpha1/interface.go @@ -0,0 +1,45 @@ +/* +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 ( + internalinterfaces "github.com/knative/eventing/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // Subscriptions returns a SubscriptionInformer. + Subscriptions() SubscriptionInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// Subscriptions returns a SubscriptionInformer. +func (v *version) Subscriptions() SubscriptionInformer { + return &subscriptionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/eventing/v1alpha1/subscription.go b/pkg/client/informers/externalversions/eventing/v1alpha1/subscription.go new file mode 100644 index 00000000000..4b248675659 --- /dev/null +++ b/pkg/client/informers/externalversions/eventing/v1alpha1/subscription.go @@ -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" + + eventing_v1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + versioned "github.com/knative/eventing/pkg/client/clientset/versioned" + internalinterfaces "github.com/knative/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/knative/eventing/pkg/client/listers/eventing/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" +) + +// SubscriptionInformer provides access to a shared informer and lister for +// Subscriptions. +type SubscriptionInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.SubscriptionLister +} + +type subscriptionInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewSubscriptionInformer constructs a new informer for Subscription 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 NewSubscriptionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredSubscriptionInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredSubscriptionInformer constructs a new informer for Subscription 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 NewFilteredSubscriptionInformer(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.EventingV1alpha1().Subscriptions(namespace).List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.EventingV1alpha1().Subscriptions(namespace).Watch(options) + }, + }, + &eventing_v1alpha1.Subscription{}, + resyncPeriod, + indexers, + ) +} + +func (f *subscriptionInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredSubscriptionInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *subscriptionInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&eventing_v1alpha1.Subscription{}, f.defaultInformer) +} + +func (f *subscriptionInformer) Lister() v1alpha1.SubscriptionLister { + return v1alpha1.NewSubscriptionLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go index df9d1890b9d..adee6a64b79 100644 --- a/pkg/client/informers/externalversions/factory.go +++ b/pkg/client/informers/externalversions/factory.go @@ -25,6 +25,7 @@ import ( versioned "github.com/knative/eventing/pkg/client/clientset/versioned" channels "github.com/knative/eventing/pkg/client/informers/externalversions/channels" + eventing "github.com/knative/eventing/pkg/client/informers/externalversions/eventing" feeds "github.com/knative/eventing/pkg/client/informers/externalversions/feeds" flows "github.com/knative/eventing/pkg/client/informers/externalversions/flows" internalinterfaces "github.com/knative/eventing/pkg/client/informers/externalversions/internalinterfaces" @@ -175,6 +176,7 @@ type SharedInformerFactory interface { WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool Channels() channels.Interface + Eventing() eventing.Interface Feeds() feeds.Interface Flows() flows.Interface } @@ -183,6 +185,10 @@ func (f *sharedInformerFactory) Channels() channels.Interface { return channels.New(f, f.namespace, f.tweakListOptions) } +func (f *sharedInformerFactory) Eventing() eventing.Interface { + return eventing.New(f, f.namespace, f.tweakListOptions) +} + func (f *sharedInformerFactory) Feeds() feeds.Interface { return feeds.New(f, f.namespace, f.tweakListOptions) } diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index e1e625fd027..ebec1b27cf0 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -22,6 +22,7 @@ import ( "fmt" v1alpha1 "github.com/knative/eventing/pkg/apis/channels/v1alpha1" + eventing_v1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" feeds_v1alpha1 "github.com/knative/eventing/pkg/apis/feeds/v1alpha1" flows_v1alpha1 "github.com/knative/eventing/pkg/apis/flows/v1alpha1" schema "k8s.io/apimachinery/pkg/runtime/schema" @@ -64,6 +65,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case v1alpha1.SchemeGroupVersion.WithResource("subscriptions"): return &genericInformer{resource: resource.GroupResource(), informer: f.Channels().V1alpha1().Subscriptions().Informer()}, nil + // Group=eventing.knative.dev, Version=v1alpha1 + case eventing_v1alpha1.SchemeGroupVersion.WithResource("subscriptions"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Eventing().V1alpha1().Subscriptions().Informer()}, nil + // Group=feeds.knative.dev, Version=v1alpha1 case feeds_v1alpha1.SchemeGroupVersion.WithResource("clustereventsources"): return &genericInformer{resource: resource.GroupResource(), informer: f.Feeds().V1alpha1().ClusterEventSources().Informer()}, nil diff --git a/pkg/client/listers/eventing/v1alpha1/expansion_generated.go b/pkg/client/listers/eventing/v1alpha1/expansion_generated.go new file mode 100644 index 00000000000..68d69ebd2a4 --- /dev/null +++ b/pkg/client/listers/eventing/v1alpha1/expansion_generated.go @@ -0,0 +1,27 @@ +/* +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 + +// SubscriptionListerExpansion allows custom methods to be added to +// SubscriptionLister. +type SubscriptionListerExpansion interface{} + +// SubscriptionNamespaceListerExpansion allows custom methods to be added to +// SubscriptionNamespaceLister. +type SubscriptionNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/eventing/v1alpha1/subscription.go b/pkg/client/listers/eventing/v1alpha1/subscription.go new file mode 100644 index 00000000000..0a834e5ebce --- /dev/null +++ b/pkg/client/listers/eventing/v1alpha1/subscription.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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// SubscriptionLister helps list Subscriptions. +type SubscriptionLister interface { + // List lists all Subscriptions in the indexer. + List(selector labels.Selector) (ret []*v1alpha1.Subscription, err error) + // Subscriptions returns an object that can list and get Subscriptions. + Subscriptions(namespace string) SubscriptionNamespaceLister + SubscriptionListerExpansion +} + +// subscriptionLister implements the SubscriptionLister interface. +type subscriptionLister struct { + indexer cache.Indexer +} + +// NewSubscriptionLister returns a new SubscriptionLister. +func NewSubscriptionLister(indexer cache.Indexer) SubscriptionLister { + return &subscriptionLister{indexer: indexer} +} + +// List lists all Subscriptions in the indexer. +func (s *subscriptionLister) List(selector labels.Selector) (ret []*v1alpha1.Subscription, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Subscription)) + }) + return ret, err +} + +// Subscriptions returns an object that can list and get Subscriptions. +func (s *subscriptionLister) Subscriptions(namespace string) SubscriptionNamespaceLister { + return subscriptionNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// SubscriptionNamespaceLister helps list and get Subscriptions. +type SubscriptionNamespaceLister interface { + // List lists all Subscriptions in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha1.Subscription, err error) + // Get retrieves the Subscription from the indexer for a given namespace and name. + Get(name string) (*v1alpha1.Subscription, error) + SubscriptionNamespaceListerExpansion +} + +// subscriptionNamespaceLister implements the SubscriptionNamespaceLister +// interface. +type subscriptionNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Subscriptions in the indexer for a given namespace. +func (s subscriptionNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Subscription, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Subscription)) + }) + return ret, err +} + +// Get retrieves the Subscription from the indexer for a given namespace and name. +func (s subscriptionNamespaceLister) Get(name string) (*v1alpha1.Subscription, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("subscription"), name) + } + return obj.(*v1alpha1.Subscription), nil +} From c9fc3b70dcde582740126e799ebd2e420bcddd5b Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Thu, 13 Sep 2018 22:20:49 +0000 Subject: [PATCH 12/15] add tests --- .../eventing/v1alpha1/subscription_types.go | 14 +- .../v1alpha1/subscription_validation.go | 139 ++++- .../v1alpha1/subscription_validation_test.go | 503 ++++++++++++++++-- .../v1alpha1/zz_generated.deepcopy.go | 10 +- 4 files changed, 595 insertions(+), 71 deletions(-) diff --git a/pkg/apis/eventing/v1alpha1/subscription_types.go b/pkg/apis/eventing/v1alpha1/subscription_types.go index 83e4650f208..763443c6445 100644 --- a/pkg/apis/eventing/v1alpha1/subscription_types.go +++ b/pkg/apis/eventing/v1alpha1/subscription_types.go @@ -79,8 +79,17 @@ type SubscriptionSpec struct { // - Kind // - APIVersion // - Name - // This field is immutable - From *corev1.ObjectReference `json:"from,omitempty"` + // Currently Kind must be "Channel" and + // APIVersion must be "channels.knative.dev/v1alpha1" + // + // This field is immutable. We have no good answer on what happens to + // the events that are currently in the channel being consumed from + // and what the semantics there should be. For now, you can always + // delete the Subscription and recreate it to point to a different + // channel, giving the user more control over what semantics should + // be used (drain the channel first, possibly have events dropped, + // etc.) + From corev1.ObjectReference `json:"from"` // Call is reference to (optional) function for processing events. // Events from the From channel will be delivered here and replies @@ -170,7 +179,6 @@ type SubscriptionCondition struct { // SubscriptionStatus (computed) for a subscription type SubscriptionStatus struct { - // Represents the latest available observations of a subscription's current state. // +patchMergeKey=type // +patchStrategy=merge diff --git a/pkg/apis/eventing/v1alpha1/subscription_validation.go b/pkg/apis/eventing/v1alpha1/subscription_validation.go index 09734585407..15e9fbe6ebd 100644 --- a/pkg/apis/eventing/v1alpha1/subscription_validation.go +++ b/pkg/apis/eventing/v1alpha1/subscription_validation.go @@ -17,6 +17,9 @@ limitations under the License. package v1alpha1 import ( + "reflect" + // "strings" + "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/knative/pkg/apis" @@ -31,28 +34,150 @@ func (s *Subscription) Validate() *apis.FieldError { // We require always From // Also at least one of 'call' and 'result' must be defined (non-nill and non-empty) func (ss *SubscriptionSpec) Validate() *apis.FieldError { - if ss.From == nil || equality.Semantic.DeepEqual(ss.From, &corev1.ObjectReference{}) { + if isFromEmpty(ss.From) { fe := apis.ErrMissingField("from") fe.Details = "the Subscription must reference a from channel" return fe } - if ss.Call == nil && ss.Result == nil { + if fe := isValidFrom(ss.From); fe != nil { + return fe.ViaField("from") + } + + missingCallable := isCallableNilOrEmpty(ss.Call) + missingResultStrategy := isResultStrategyNilOrEmpty(ss.Result) + if missingCallable && missingResultStrategy { fe := apis.ErrMissingField("result", "call") fe.Details = "the Subscription must reference at least one of (result channel or a call)" return fe } - if equality.Semantic.DeepEqual(ss.Call, &Callable{}) && equality.Semantic.DeepEqual(ss.Result, &ResultStrategy{}) { - fe := apis.ErrMissingField("result", "call") - fe.Details = "the Subscription must reference at least one of (result channel or a call)" + if !missingCallable { + if fe := isValidCallable(*ss.Call); fe != nil { + return fe.ViaField("call") + } + } + + if !missingResultStrategy { + if fe := isValidResultStrategy(ss.Result); fe != nil { + return fe.ViaField("result") + } + } + + return nil +} + +func isCallableNilOrEmpty(c *Callable) bool { + return c == nil || equality.Semantic.DeepEqual(c, &Callable{}) || + (equality.Semantic.DeepEqual(c.Target, &corev1.ObjectReference{}) && c.TargetURI == nil) + +} + +func isValidCallable(c Callable) *apis.FieldError { + if c.TargetURI != nil && c.Target != nil && !equality.Semantic.DeepEqual(c.Target, &corev1.ObjectReference{}) { + return apis.ErrMultipleOneOf("target", "targetURI") + } + + // If Target given, check the fields. + if c.Target != nil && !equality.Semantic.DeepEqual(c.Target, &corev1.ObjectReference{}) { + fe := isValidObjectReference(*c.Target) + if fe != nil { + return fe.ViaField("target") + } + } + return nil +} + +func isFromEmpty(f corev1.ObjectReference) bool { + return equality.Semantic.DeepEqual(f, corev1.ObjectReference{}) +} + +// Valid from only contains the following fields: +// - Kind == 'Channel' +// - APIVersion == 'channels.knative.dev/v1alpha1' +// - Name == not empty +func isValidFrom(f corev1.ObjectReference) *apis.FieldError { + fe := isValidObjectReference(f) + if fe != nil { + return fe + } + if f.Kind != "Channel" { + fe := apis.ErrInvalidValue(f.Kind, "kind") + fe.Paths = []string{"kind"} + fe.Details = "only 'Channel' kind is allowed" + return fe + } + if f.APIVersion != "channels.knative.dev/v1alpha1" { + fe := apis.ErrInvalidValue(f.APIVersion, "apiVersion") + fe.Details = "only channels.knative.dev/v1alpha1 is allowed for apiVersion" return fe } + return nil +} - // TODO(Before checking in): validate the underlying Call/Result/From properly once we settle on the - // shapes of these things. +func isResultStrategyNilOrEmpty(r *ResultStrategy) bool { + return r == nil || equality.Semantic.DeepEqual(r, &ResultStrategy{}) || equality.Semantic.DeepEqual(r.Target, &corev1.ObjectReference{}) +} +func isValidResultStrategy(r *ResultStrategy) *apis.FieldError { + fe := isValidObjectReference(*r.Target) + if fe != nil { + return fe.ViaField("target") + } return nil + +} + +func isValidObjectReference(f corev1.ObjectReference) *apis.FieldError { + fe := checkRequiredFields(f) + if fe != nil { + return fe + } + return checkDisallowedFields(f) +} + +// Check the corev1.ObjectReference to make sure it has the required fields. They +// are not checked for anything more except that they are set. +func checkRequiredFields(f corev1.ObjectReference) *apis.FieldError { + if f.Name == "" { + return apis.ErrMissingField("name") + } + if f.APIVersion == "" { + return apis.ErrMissingField("apiVersion") + } + if f.Kind == "" { + return apis.ErrMissingField("kind") + } + return nil +} + +// Check the corev1.ObjectReference to make sure it only has the following fields set: +// Name, Kind, APIVersion +// If any other fields are set and is not the Zero value, returns an apis.FieldError +// with the fieldpaths for all those fields. +func checkDisallowedFields(f corev1.ObjectReference) *apis.FieldError { + disallowedFields := []string{} + // See if there are any fields that have been set that should not be. + // TODO: Hoist this kind of stuff into pkg repository. + s := reflect.ValueOf(f) + typeOf := s.Type() + for i := 0; i < s.NumField(); i++ { + field := s.Field(i) + fieldName := typeOf.Field(i).Name + if fieldName == "Name" || fieldName == "Kind" || fieldName == "APIVersion" { + continue + } + if !cmp.Equal(field.Interface(), reflect.Zero(field.Type()).Interface()) { + disallowedFields = append(disallowedFields, fieldName) + } + } + if len(disallowedFields) > 0 { + fe := apis.ErrDisallowedFields(disallowedFields...) + fe.Details = "only name, apiVersion and kind are supported fields" + return fe + } + return nil + } func (current *Subscription) CheckImmutableFields(og apis.Immutable) *apis.FieldError { diff --git a/pkg/apis/eventing/v1alpha1/subscription_validation_test.go b/pkg/apis/eventing/v1alpha1/subscription_validation_test.go index a4e2dd8e1b6..e83e9c27f47 100644 --- a/pkg/apis/eventing/v1alpha1/subscription_validation_test.go +++ b/pkg/apis/eventing/v1alpha1/subscription_validation_test.go @@ -25,35 +25,39 @@ import ( const ( channelKind = "Channel" - channelAPIVersion = "eventing.knative.dev/v1alpha1" + channelAPIVersion = "channels.knative.dev/v1alpha1" routeKind = "Route" routeAPIVersion = "serving.knative.dev/v1alpha1" - FromChannelName = "fromChannel" - ToChannelName = "toChannel" - CallName = "call" + fromChannelName = "fromChannel" + resultChannelName = "toChannel" + callName = "call" ) -func getValidFromRef() *corev1.ObjectReference { - return &corev1.ObjectReference{ - Name: FromChannelName, +func getValidFromRef() corev1.ObjectReference { + return corev1.ObjectReference{ + Name: fromChannelName, Kind: channelKind, APIVersion: channelAPIVersion, } } -func getValidToRef() *corev1.ObjectReference { - return &corev1.ObjectReference{ - Name: ToChannelName, - Kind: channelKind, - APIVersion: channelAPIVersion, +func getValidResultRef() *ResultStrategy { + return &ResultStrategy{ + Target: &corev1.ObjectReference{ + Name: resultChannelName, + Kind: channelKind, + APIVersion: channelAPIVersion, + }, } } -func getValidCall() *corev1.ObjectReference { - return &corev1.ObjectReference{ - Name: CallName, - Kind: routeKind, - APIVersion: routeAPIVersion, +func getValidCall() *Callable { + return &Callable{ + Target: &corev1.ObjectReference{ + Name: callName, + Kind: routeKind, + APIVersion: routeAPIVersion, + }, } } @@ -66,9 +70,8 @@ func (d *DummyImmutableType) CheckImmutableFields(og apis.Immutable) *apis.Field func TestSubscriptionValidation(t *testing.T) { name := "empty from" c := &Subscription{ - Spec: SubscriptionSpec{ - From: &corev1.ObjectReference{}, + From: corev1.ObjectReference{}, }, } want := &apis.FieldError{ @@ -80,7 +83,7 @@ func TestSubscriptionValidation(t *testing.T) { t.Run(name, func(t *testing.T) { got := c.Validate() if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("CheckImmutableFields (-want, +got) = %v", diff) + t.Errorf("Subscription.Validate (-want, +got) = %v", diff) } }) @@ -99,9 +102,9 @@ func TestSubscriptionSpecValidation(t *testing.T) { }, want: nil, }, { - name: "empty from", + name: "empty From", c: &SubscriptionSpec{ - From: &corev1.ObjectReference{}, + From: corev1.ObjectReference{}, }, want: func() *apis.FieldError { fe := apis.ErrMissingField("from") @@ -109,55 +112,67 @@ func TestSubscriptionSpecValidation(t *testing.T) { return fe }(), }, { - name: "missing call and to", + name: "missing name in From", c: &SubscriptionSpec{ - From: getValidFromRef(), + From: corev1.ObjectReference{ + Kind: channelKind, + APIVersion: channelAPIVersion, + }, }, want: func() *apis.FieldError { - fe := apis.ErrMissingField("to", "call") - fe.Details = "the Subscription must reference at least one of (to channel or a call)" + fe := apis.ErrMissingField("from.name") return fe }(), }, { - name: "empty call and to", + name: "missing Call and Result", c: &SubscriptionSpec{ From: getValidFromRef(), - Call: &corev1.ObjectReference{}, - To: &corev1.ObjectReference{}, }, want: func() *apis.FieldError { - fe := apis.ErrMissingField("to", "call") - fe.Details = "the Subscription must reference at least one of (to channel or a call)" + fe := apis.ErrMissingField("result", "call") + fe.Details = "the Subscription must reference at least one of (result channel or a call)" + return fe + }(), + }, { + name: "empty Call and Result", + c: &SubscriptionSpec{ + From: getValidFromRef(), + Call: &Callable{}, + Result: &ResultStrategy{}, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("result", "call") + fe.Details = "the Subscription must reference at least one of (result channel or a call)" return fe }(), }, { - name: "missing to", + name: "missing Result", c: &SubscriptionSpec{ From: getValidFromRef(), Call: getValidCall(), }, want: nil, }, { - name: "empty to", + name: "empty Result", c: &SubscriptionSpec{ - From: getValidFromRef(), - Call: getValidCall(), - To: &corev1.ObjectReference{}, + From: getValidFromRef(), + Call: getValidCall(), + Result: &ResultStrategy{}, }, want: nil, }, { - name: "missing call", + name: "missing Call", c: &SubscriptionSpec{ - From: getValidFromRef(), - To: getValidToRef(), + From: getValidFromRef(), + Result: getValidResultRef(), }, want: nil, }, { - name: "empty call", + name: "empty Call", c: &SubscriptionSpec{ - From: getValidFromRef(), - Call: &corev1.ObjectReference{}, - To: getValidToRef(), + From: getValidFromRef(), + Call: &Callable{}, + Result: getValidResultRef(), }, want: nil, }, { @@ -168,6 +183,36 @@ func TestSubscriptionSpecValidation(t *testing.T) { fe.Details = "the Subscription must reference a from channel" return fe }(), + }, { + name: "missing name in Call.Target", + c: &SubscriptionSpec{ + From: getValidFromRef(), + Call: &Callable{ + Target: &corev1.ObjectReference{ + Kind: channelKind, + APIVersion: channelAPIVersion, + }, + }, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("call.target.name") + return fe + }(), + }, { + name: "missing name in Result.Target", + c: &SubscriptionSpec{ + From: getValidFromRef(), + Result: &ResultStrategy{ + Target: &corev1.ObjectReference{ + Kind: channelKind, + APIVersion: channelAPIVersion, + }, + }, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("result.target.name") + return fe + }(), }} for _, test := range tests { @@ -185,10 +230,10 @@ func TestSubscriptionImmutable(t *testing.T) { newFrom.Name = "newFromChannel" newCall := getValidCall() - newCall.Name = "newCall" + newCall.Target.Name = "newCall" - newTo := getValidToRef() - newTo.Name = "newToChannel" + newResult := getValidResultRef() + newResult.Target.Name = "newResultChannel" tests := []struct { name string @@ -219,7 +264,7 @@ func TestSubscriptionImmutable(t *testing.T) { og: nil, want: nil, }, { - name: "valid, new call", + name: "valid, new Call", c: &Subscription{ Spec: SubscriptionSpec{ From: getValidFromRef(), @@ -234,22 +279,52 @@ func TestSubscriptionImmutable(t *testing.T) { }, want: nil, }, { - name: "valid, new to", + name: "valid, new Result", c: &Subscription{ Spec: SubscriptionSpec{ - From: getValidFromRef(), - To: getValidToRef(), + From: getValidFromRef(), + Result: getValidResultRef(), + }, + }, + og: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + Result: newResult, + }, + }, + want: nil, + }, { + name: "valid, have Result, remove and replace with Call", + c: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + Result: getValidResultRef(), }, }, og: &Subscription{ Spec: SubscriptionSpec{ From: getValidFromRef(), - Call: newTo, + Call: getValidCall(), }, }, want: nil, }, { - name: "from changed", + name: "valid, have Call, remove and replace with Result", + c: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + Call: getValidCall(), + }, + }, + og: &Subscription{ + Spec: SubscriptionSpec{ + From: getValidFromRef(), + Result: getValidResultRef(), + }, + }, + want: nil, + }, { + name: "From changed", c: &Subscription{ Spec: SubscriptionSpec{ From: getValidFromRef(), @@ -299,3 +374,327 @@ func TestInvalidImmutableType(t *testing.T) { } }) } + +func TestValidFrom(t *testing.T) { + tests := []struct { + name string + c corev1.ObjectReference + want *apis.FieldError + }{{ + name: "valid", + c: corev1.ObjectReference{ + Name: fromChannelName, + APIVersion: channelAPIVersion, + Kind: channelKind, + }, + want: nil, + }, { + name: "missing name", + c: corev1.ObjectReference{ + APIVersion: channelAPIVersion, + Kind: channelKind, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("name") + return fe + }(), + }, { + name: "missing apiVersion", + c: corev1.ObjectReference{ + Name: fromChannelName, + Kind: channelKind, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("apiVersion") + return fe + }(), + }, { + name: "missing kind", + c: corev1.ObjectReference{ + Name: fromChannelName, + APIVersion: channelAPIVersion, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("kind") + return fe + }(), + }, { + name: "invalid kind", + c: corev1.ObjectReference{ + Name: fromChannelName, + APIVersion: channelAPIVersion, + Kind: "subscription", + }, + want: func() *apis.FieldError { + fe := apis.ErrInvalidValue("subscription", "kind") + fe.Details = "only 'Channel' kind is allowed" + return fe + }(), + }, { + name: "invalid apiVersion", + c: corev1.ObjectReference{ + Name: fromChannelName, + APIVersion: "wrongapiversion", + Kind: channelKind, + }, + want: func() *apis.FieldError { + fe := apis.ErrInvalidValue("wrongapiversion", "apiVersion") + fe.Details = "only channels.knative.dev/v1alpha1 is allowed for apiVersion" + return fe + }(), + }, { + name: "extra field, namespace", + c: corev1.ObjectReference{ + Name: fromChannelName, + APIVersion: channelAPIVersion, + Kind: channelKind, + Namespace: "secretnamespace", + }, + want: func() *apis.FieldError { + fe := apis.ErrDisallowedFields("Namespace") + fe.Details = "only name, apiVersion and kind are supported fields" + return fe + }(), + }, { + name: "extra field, namespace and resourceVersion", + c: corev1.ObjectReference{ + Name: fromChannelName, + APIVersion: channelAPIVersion, + Kind: channelKind, + Namespace: "secretnamespace", + ResourceVersion: "myresourceversion", + }, + want: func() *apis.FieldError { + fe := apis.ErrDisallowedFields("Namespace", "ResourceVersion") + fe.Details = "only name, apiVersion and kind are supported fields" + return fe + }(), + }, { + // Make sure that if an empty field for namespace is given, it's treated as not there. + name: "valid extra field, namespace empty", + c: corev1.ObjectReference{ + Name: fromChannelName, + APIVersion: channelAPIVersion, + Kind: channelKind, + Namespace: "", + }, + want: nil, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := isValidFrom(test.c) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("isValidFrom (-want, +got) = %v", diff) + } + }) + } +} + +func TestValidCallable(t *testing.T) { + targetURI := "http://example.com" + tests := []struct { + name string + c Callable + want *apis.FieldError + }{{ + name: "valid target", + c: *getValidCall(), + want: nil, + }, { + name: "valid targetURI", + c: Callable{ + TargetURI: &targetURI, + }, + want: nil, + }, { + name: "both target and targetURI given", + c: Callable{ + Target: &corev1.ObjectReference{ + APIVersion: channelAPIVersion, + Kind: channelKind, + }, + TargetURI: &targetURI, + }, + want: func() *apis.FieldError { + fe := apis.ErrMultipleOneOf("target", "targetURI") + return fe + }(), + }, { + name: "missing name in target", + c: Callable{ + Target: &corev1.ObjectReference{ + APIVersion: channelAPIVersion, + Kind: channelKind, + }, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("target.name") + return fe + }(), + }, { + name: "missing apiVersion in target", + c: Callable{ + Target: &corev1.ObjectReference{ + Name: fromChannelName, + Kind: channelKind, + }, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("target.apiVersion") + return fe + }(), + }, { + name: "missing kind in target", + c: Callable{ + Target: &corev1.ObjectReference{ + Name: fromChannelName, + APIVersion: channelAPIVersion, + }, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("target.kind") + return fe + }(), + }, { + name: "extra field, namespace", + c: Callable{ + Target: &corev1.ObjectReference{ + Name: fromChannelName, + APIVersion: channelAPIVersion, + Kind: channelKind, + Namespace: "secretnamespace", + }, + }, + want: func() *apis.FieldError { + fe := apis.ErrDisallowedFields("target.Namespace") + fe.Details = "only name, apiVersion and kind are supported fields" + return fe + }(), + }, { + // Make sure that if an empty field for namespace is given, it's treated as not there. + name: "valid extra field, namespace empty", + c: Callable{ + Target: &corev1.ObjectReference{ + Name: fromChannelName, + APIVersion: channelAPIVersion, + Kind: channelKind, + Namespace: "", + }, + }, + want: nil, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := isValidCallable(test.c) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("isValidFrom (-want, +got) = %v", diff) + } + }) + } +} + +func TestValidResultStrategy(t *testing.T) { + targetURI := "http://example.com" + tests := []struct { + name string + c Callable + want *apis.FieldError + }{{ + name: "valid target", + c: *getValidCall(), + want: nil, + }, { + name: "valid targetURI", + c: Callable{ + TargetURI: &targetURI, + }, + want: nil, + }, { + name: "both target and targetURI given", + c: Callable{ + Target: &corev1.ObjectReference{ + APIVersion: channelAPIVersion, + Kind: channelKind, + }, + TargetURI: &targetURI, + }, + want: func() *apis.FieldError { + fe := apis.ErrMultipleOneOf("target", "targetURI") + return fe + }(), + }, { + name: "missing name in target", + c: Callable{ + Target: &corev1.ObjectReference{ + APIVersion: channelAPIVersion, + Kind: channelKind, + }, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("target.name") + return fe + }(), + }, { + name: "missing apiVersion in target", + c: Callable{ + Target: &corev1.ObjectReference{ + Name: fromChannelName, + Kind: channelKind, + }, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("target.apiVersion") + return fe + }(), + }, { + name: "missing kind in target", + c: Callable{ + Target: &corev1.ObjectReference{ + Name: fromChannelName, + APIVersion: channelAPIVersion, + }, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("target.kind") + return fe + }(), + }, { + name: "extra field, namespace", + c: Callable{ + Target: &corev1.ObjectReference{ + Name: fromChannelName, + APIVersion: channelAPIVersion, + Kind: channelKind, + Namespace: "secretnamespace", + }, + }, + want: func() *apis.FieldError { + fe := apis.ErrDisallowedFields("target.Namespace") + fe.Details = "only name, apiVersion and kind are supported fields" + return fe + }(), + }, { + // Make sure that if an empty field for namespace is given, it's treated as not there. + name: "valid extra field, namespace empty", + c: Callable{ + Target: &corev1.ObjectReference{ + Name: fromChannelName, + APIVersion: channelAPIVersion, + Kind: channelKind, + Namespace: "", + }, + }, + want: nil, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := isValidCallable(test.c) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("isValidFrom (-want, +got) = %v", diff) + } + }) + } +} diff --git a/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go index e9d67673dc5..26072f59b90 100644 --- a/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go @@ -166,15 +166,7 @@ func (in *SubscriptionList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SubscriptionSpec) DeepCopyInto(out *SubscriptionSpec) { *out = *in - if in.From != nil { - in, out := &in.From, &out.From - if *in == nil { - *out = nil - } else { - *out = new(v1.ObjectReference) - **out = **in - } - } + out.From = in.From if in.Call != nil { in, out := &in.Call, &out.Call if *in == nil { From add1e888b546e7e12cd1b3576c9abc728ec590aa Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Thu, 13 Sep 2018 22:25:19 +0000 Subject: [PATCH 13/15] address pr feedback --- pkg/apis/eventing/v1alpha1/subscription_types.go | 8 ++------ pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go | 6 +----- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/pkg/apis/eventing/v1alpha1/subscription_types.go b/pkg/apis/eventing/v1alpha1/subscription_types.go index 763443c6445..2cee0e461ad 100644 --- a/pkg/apis/eventing/v1alpha1/subscription_types.go +++ b/pkg/apis/eventing/v1alpha1/subscription_types.go @@ -157,8 +157,8 @@ type ResultStrategy struct { type SubscriptionConditionType string const ( - // Dispatching means the subscription is actively listening for incoming events on its channel and dispatching them. - SubscriptionDispatching SubscriptionConditionType = "Dispatching" + // SubscriptionReady is when the From,Channel and Result have been reconciled successfully. + SubscriptionReady SubscriptionConditionType = "Ready" ) // SubscriptionCondition describes the state of a subscription at a point in time. @@ -167,10 +167,6 @@ type SubscriptionCondition struct { Type SubscriptionConditionType `json:"type"` // Status of the condition, one of True, False, Unknown. Status v1.ConditionStatus `json:"status"` - // The last time this condition was updated. - LastUpdateTime meta_v1.Time `json:"lastUpdateTime,omitempty"` - // Last time the condition transitioned from one status to another. - LastTransitionTime meta_v1.Time `json:"lastTransitionTime,omitempty"` // The reason for the condition's last transition. Reason string `json:"reason,omitempty"` // A human readable message indicating details about the transition. diff --git a/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go index 26072f59b90..a3678bad294 100644 --- a/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go @@ -115,8 +115,6 @@ func (in *Subscription) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SubscriptionCondition) DeepCopyInto(out *SubscriptionCondition) { *out = *in - in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime) - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) return } @@ -204,9 +202,7 @@ func (in *SubscriptionStatus) DeepCopyInto(out *SubscriptionStatus) { if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]SubscriptionCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } + copy(*out, *in) } return } From 6a6778ac808dbda0798420d7fe1cde0ac80de9ed Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Thu, 13 Sep 2018 22:29:31 +0000 Subject: [PATCH 14/15] rename meta_v1 to metav1 --- pkg/apis/eventing/v1alpha1/subscription_types.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/apis/eventing/v1alpha1/subscription_types.go b/pkg/apis/eventing/v1alpha1/subscription_types.go index 2cee0e461ad..16d35296457 100644 --- a/pkg/apis/eventing/v1alpha1/subscription_types.go +++ b/pkg/apis/eventing/v1alpha1/subscription_types.go @@ -23,7 +23,7 @@ import ( "github.com/knative/pkg/webhook" "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1" - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -35,10 +35,10 @@ import ( // Subscription routes events received on a Channel to a DNS name and // corresponds to the subscriptions.channels.knative.dev CRD. type Subscription struct { - meta_v1.TypeMeta `json:",inline"` - meta_v1.ObjectMeta `json:"metadata"` - Spec SubscriptionSpec `json:"spec"` - Status SubscriptionStatus `json:"status,omitempty"` + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + Spec SubscriptionSpec `json:"spec"` + Status SubscriptionStatus `json:"status,omitempty"` } // Check that Subscription can be validated, can be defaulted, and has immutable fields. @@ -198,7 +198,7 @@ func (s *Subscription) GetSpecJSON() ([]byte, error) { // SubscriptionList returned in list operations type SubscriptionList struct { - meta_v1.TypeMeta `json:",inline"` - meta_v1.ListMeta `json:"metadata"` - Items []Subscription `json:"items"` + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + Items []Subscription `json:"items"` } From 92379006c42e8dd9c7bb46a86eb03f24f1e73f5d Mon Sep 17 00:00:00 2001 From: Ville Aikas Date: Fri, 14 Sep 2018 04:41:56 +0000 Subject: [PATCH 15/15] address PR comments --- .../eventing/v1alpha1/subscription_types.go | 20 ++++++++++++------- .../v1alpha1/subscription_validation.go | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/pkg/apis/eventing/v1alpha1/subscription_types.go b/pkg/apis/eventing/v1alpha1/subscription_types.go index 16d35296457..28067323807 100644 --- a/pkg/apis/eventing/v1alpha1/subscription_types.go +++ b/pkg/apis/eventing/v1alpha1/subscription_types.go @@ -100,24 +100,30 @@ type SubscriptionSpec struct { // Result specifies (optionally) how to handle events returned from // the Call target. // +optional - Result *ResultStrategy `json:"to,omitempty"` + Result *ResultStrategy `json:"result,omitempty"` } // Callable specifies the reference to an object that's expected to -// provide the resolved target of the action. Currently we inspect -// the objects Status and see if there's a predefined Status field -// that we will then use to dispatch events to be processed by the target. -// Currently must resolve to a k8s service or Istio virtual service. Note that -// in the future we should try to utilize subresources (/resolve ?) to +// provide the resolved target of the action. +// Currently we inspect the objects Status and see if there's a predefined +// Status field that we will then use to dispatch events to be processed by +// the target. Currently must resolve to a k8s service or Istio virtual +// service. +// Note that in the future we should try to utilize subresources (/resolve ?) to // make this cleaner, but CRDs do not support subresources yet, so we need // to rely on a specified Status field today. By relying on this behaviour // we can utilize a dynamic client instead of having to understand all // kinds of different types of objects. As long as they adhere to this // particular contract, they can be used as a Target. +// // This ensures that we can support external targets and for ease of use // we also allow for an URI to be specified. // There of course is also a requirement for the resolved Callable to // behave properly at the data plane level. +// TODO: Add a pointer to a real spec for this. +// For now, this means: Receive an event payload, and respond with one of: +// success and an optional response event, or failure. +// Delivery failures may be retried by the from Channel type Callable struct { // Only one of these can be specified @@ -157,7 +163,7 @@ type ResultStrategy struct { type SubscriptionConditionType string const ( - // SubscriptionReady is when the From,Channel and Result have been reconciled successfully. + // SubscriptionReady is when the From,Call and Result have been reconciled successfully. SubscriptionReady SubscriptionConditionType = "Ready" ) diff --git a/pkg/apis/eventing/v1alpha1/subscription_validation.go b/pkg/apis/eventing/v1alpha1/subscription_validation.go index 15e9fbe6ebd..6327f25ea9d 100644 --- a/pkg/apis/eventing/v1alpha1/subscription_validation.go +++ b/pkg/apis/eventing/v1alpha1/subscription_validation.go @@ -74,7 +74,7 @@ func isCallableNilOrEmpty(c *Callable) bool { } func isValidCallable(c Callable) *apis.FieldError { - if c.TargetURI != nil && c.Target != nil && !equality.Semantic.DeepEqual(c.Target, &corev1.ObjectReference{}) { + if c.TargetURI != nil && *c.TargetURI != "" && c.Target != nil && !equality.Semantic.DeepEqual(c.Target, &corev1.ObjectReference{}) { return apis.ErrMultipleOneOf("target", "targetURI") }