diff --git a/Gopkg.lock b/Gopkg.lock index f4fb07847e8..d125f7ca4a4 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -268,7 +268,7 @@ revision = "5c1d8c8469d1ed34b2aecf4c2305b3a57fff2ee3" [[projects]] - digest = "1:89864a5a6728be62bc3397a082ad80cb84c4e0049f77606833037a647e47c74f" + digest = "1:80b6398334023f7a0ca11aeb8b826eba2406f579a3707aeacbadeb466606c107" name = "github.com/knative/pkg" packages = [ "apis", @@ -298,7 +298,7 @@ "webhook", ] pruneopts = "NUT" - revision = "a133825579436877df315e7cb6d8aeba49b9f575" + revision = "51f6214feeea1485dc8691a661d953fea1409402" [[projects]] digest = "1:63f3974f3afe3dc5b6a115c0d53b0897cd01be6880c4bf5d014fc69a95db6ed1" @@ -813,12 +813,13 @@ version = "kubernetes-1.11.0" [[projects]] - digest = "1:b2b13a7c98cee7405ee7e1cf93296d66de435196fc9a19047e31132f90a72030" + digest = "1:52e713cd6ac68d1ab506149a488466cdf0ecb1e524e970fcaef67ba21aeae9ff" name = "k8s.io/client-go" packages = [ "discovery", "discovery/fake", "dynamic", + "dynamic/fake", "informers", "informers/admissionregistration", "informers/admissionregistration/v1alpha1", @@ -1068,6 +1069,7 @@ "github.com/knative/serving/pkg/apis/serving/v1alpha1", "github.com/knative/serving/pkg/client/clientset/versioned", "github.com/knative/test-infra", + "github.com/mattbaird/jsonpatch", "github.com/prometheus/client_golang/prometheus/promhttp", "go.uber.org/zap", "go.uber.org/zap/zapcore", @@ -1085,6 +1087,7 @@ "k8s.io/apimachinery/pkg/api/errors", "k8s.io/apimachinery/pkg/api/meta", "k8s.io/apimachinery/pkg/apis/meta/v1", + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured", "k8s.io/apimachinery/pkg/labels", "k8s.io/apimachinery/pkg/runtime", "k8s.io/apimachinery/pkg/runtime/schema", @@ -1100,6 +1103,7 @@ "k8s.io/client-go/discovery", "k8s.io/client-go/discovery/fake", "k8s.io/client-go/dynamic", + "k8s.io/client-go/dynamic/fake", "k8s.io/client-go/informers", "k8s.io/client-go/informers/core/v1", "k8s.io/client-go/kubernetes", diff --git a/Gopkg.toml b/Gopkg.toml index 8806bbad68d..1e0c87eee04 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -76,7 +76,7 @@ required = [ [[constraint]] name = "github.com/knative/pkg" # HEAD as of 2018-09-20 - revision = "a133825579436877df315e7cb6d8aeba49b9f575" + revision = "51f6214feeea1485dc8691a661d953fea1409402" [[constraint]] name = "github.com/knative/serving" diff --git a/cmd/controller/controller-runtime-main.go b/cmd/controller/controller-runtime-main.go index 60bfa94e668..45c0ba9bcef 100644 --- a/cmd/controller/controller-runtime-main.go +++ b/cmd/controller/controller-runtime-main.go @@ -17,8 +17,10 @@ package main import ( channelsv1alpha1 "github.com/knative/eventing/pkg/apis/channels/v1alpha1" + subscriptionsv1alpha1 "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" + "github.com/knative/eventing/pkg/controller/eventing/subscription" "github.com/knative/eventing/pkg/controller/feed" "github.com/knative/eventing/pkg/controller/flow" @@ -58,6 +60,7 @@ func controllerRuntimeStart() error { feedsv1alpha1.AddToScheme, flowsv1alpha1.AddToScheme, istiov1alpha3.AddToScheme, + subscriptionsv1alpha1.AddToScheme, } for _, schemeFunc := range schemeFuncs { schemeFunc(mrg.GetScheme()) @@ -69,6 +72,7 @@ func controllerRuntimeStart() error { eventtype.ProvideController, feed.ProvideController, flow.ProvideController, + subscription.ProvideController, } for _, provider := range providers { diff --git a/config/300-subscriptioneventing.yaml b/config/300-subscriptioneventing.yaml new file mode 100644 index 00000000000..cc974f51254 --- /dev/null +++ b/config/300-subscriptioneventing.yaml @@ -0,0 +1,31 @@ +# 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. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: subscriptions.eventing.knative.dev +spec: + group: eventing.knative.dev + version: v1alpha1 + names: + kind: Subscription + plural: subscriptions + singular: subscription + categories: + - all + - knative + - eventing + shortNames: + - sub + scope: Namespaced diff --git a/pkg/apis/eventing/v1alpha1/channel_types.go b/pkg/apis/eventing/v1alpha1/channel_types.go index 4073e6b277c..07c353123cd 100644 --- a/pkg/apis/eventing/v1alpha1/channel_types.go +++ b/pkg/apis/eventing/v1alpha1/channel_types.go @@ -60,6 +60,9 @@ var _ duckv1alpha1.ConditionsAccessor = (*ChannelStatus)(nil) // Check that Channel implements the Conditions duck type. var _ = duck.VerifyType(&Channel{}, &duckv1alpha1.Conditions{}) +var _ = duck.VerifyType(&Channel{}, &duckv1alpha1.Channelable{}) +var _ = duck.VerifyType(&Channel{}, &duckv1alpha1.Subscribable{}) +var _ = duck.VerifyType(&Channel{}, &duckv1alpha1.Sinkable{}) // ChannelSpec specifies the Provisioner backing a channel and the configuration // arguments for a Channel. @@ -80,24 +83,8 @@ type ChannelSpec struct { // +optional Arguments *runtime.RawExtension `json:"arguments,omitempty"` - // Subscribers is a list of the Subscribers to this channel. This is filled in - // by the Subscriptions controller. Users should not mutate this field. - Subscribers []ChannelSubscriberSpec `json:"subscribers,omitempty"` -} - -// ChannelSubscriberSpec defines a single subscriber to a Channel. At least one -// of Call or Result must be present. -type ChannelSubscriberSpec struct { - // Call is an optional reference to a function for processing events. - // Events from the From channel will be delivered here and replies - // are optionally handled by Result. - // +optional - Call *Callable `json:"call,omitempty"` - - // Result optionally specifies how to handle events received from the Call - // target. - // +optional - Result *ResultStrategy `json:"result,omitempty"` + // Channel conforms to Duck type Channelable. + Channelable *duckv1alpha1.Channelable `json:"channelable,omitempty"` } var chanCondSet = duckv1alpha1.NewLivingConditionSet(ChannelConditionProvisioned) @@ -112,13 +99,13 @@ type ChannelStatus struct { // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty"` - // DomainInternal holds the top-level domain that will distribute traffic - // over the provided targets from inside the cluster. It generally has the - // form {channel}.{namespace}.svc.cluster.local - // TODO: move this to a struct that can be embedded similar to ObjectMeta and - // TypeMeta. - // +optional - DomainInternal string `json:"domainInternal,omitempty"` + // Channel is Sinkable. It currently exposes the endpoint as top-level domain + // that will distribute traffic over the provided targets from inside the cluster. + // It generally has the form {channel}.{namespace}.svc.cluster.local + Sinkable duckv1alpha1.Sinkable `json:"sinkable,omitempty"` + + // Channel is Subscribable. It just points to itself + Subscribable duckv1alpha1.Subscribable `json:"subscribable,omitempty"` // Represents the latest available observations of a channel's current state. // +optional diff --git a/pkg/apis/eventing/v1alpha1/channel_validation.go b/pkg/apis/eventing/v1alpha1/channel_validation.go index 49be38677f9..1a41996e3ae 100644 --- a/pkg/apis/eventing/v1alpha1/channel_validation.go +++ b/pkg/apis/eventing/v1alpha1/channel_validation.go @@ -34,9 +34,13 @@ func (cs *ChannelSpec) Validate() *apis.FieldError { errs = errs.Also(apis.ErrMissingField("provisioner")) } - for i, subscriber := range cs.Subscribers { - if subscriber.Call == nil && subscriber.Result == nil { - errs = errs.Also(apis.ErrMissingField("call", "result").ViaField(fmt.Sprintf("subscriber[%d]", i))) + if cs.Channelable != nil { + for i, subscriber := range cs.Channelable.Subscribers { + if subscriber.SinkableDomain == "" && subscriber.CallableDomain == "" { + fe := apis.ErrMissingField("sinkableDomain", "callableDomain") + fe.Details = "expected at least one of, got none" + errs = errs.Also(fe.ViaField(fmt.Sprintf("subscriber[%d]", i)).ViaField("channelable")) + } } } @@ -51,7 +55,7 @@ func (current *Channel) CheckImmutableFields(og apis.Immutable) *apis.FieldError if !ok { return &apis.FieldError{Message: "The provided resource was not a Channel"} } - ignoreArguments := cmpopts.IgnoreFields(ChannelSpec{}, "Arguments") + ignoreArguments := cmpopts.IgnoreFields(ChannelSpec{}, "Arguments", "Channelable") if diff := cmp.Diff(original.Spec, current.Spec, ignoreArguments); diff != "" { return &apis.FieldError{ Message: "Immutable fields changed", diff --git a/pkg/apis/eventing/v1alpha1/channel_validation_test.go b/pkg/apis/eventing/v1alpha1/channel_validation_test.go index 3423363ce8d..cc684046ea8 100644 --- a/pkg/apis/eventing/v1alpha1/channel_validation_test.go +++ b/pkg/apis/eventing/v1alpha1/channel_validation_test.go @@ -21,6 +21,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/knative/pkg/apis" + duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -59,35 +60,16 @@ func TestChannelValidation(t *testing.T) { Name: "foo", }, }, - Subscribers: []ChannelSubscriberSpec{{ - Call: &Callable{ - TargetURI: &targetURI, - }, - }, { - Result: &ResultStrategy{ - Target: &corev1.ObjectReference{ - APIVersion: "eventing.knative.dev/v1alpha1", - Kind: "Channel", - Name: "to-chan", - }, - }, - }, { - Call: &Callable{ - TargetURI: &targetURI, - }, - Result: &ResultStrategy{ - Target: &corev1.ObjectReference{ - APIVersion: "eventing.knative.dev/v1alpha1", - Kind: "Channel", - Name: "to-chan", - }, - }, + Channelable: &duckv1alpha1.Channelable{ + Subscribers: []duckv1alpha1.ChannelSubscriberSpec{{ + CallableDomain: "callableendpoint", + SinkableDomain: "resultendpoint", + }}, }}, - }, }, want: nil, }, { - name: "empty subscriber", + name: "empty subscriber at index 1", c: &Channel{ Spec: ChannelSpec{ Provisioner: &ProvisionerReference{ @@ -95,14 +77,18 @@ func TestChannelValidation(t *testing.T) { Name: "foo", }, }, - Subscribers: []ChannelSubscriberSpec{{ - Call: &Callable{ - TargetURI: &targetURI, - }, - }, {}}, - }, + Channelable: &duckv1alpha1.Channelable{ + Subscribers: []duckv1alpha1.ChannelSubscriberSpec{{ + CallableDomain: "callableendpoint", + SinkableDomain: "callableendpoint", + }, {}}, + }}, }, - want: apis.ErrMissingField("spec.subscriber[1].call", "spec.subscriber[1].result"), + want: func() *apis.FieldError { + fe := apis.ErrMissingField("spec.channelable.subscriber[1].sinkableDomain", "spec.channelable.subscriber[1].callableDomain") + fe.Details = "expected at least one of, got none" + return fe + }(), }, { name: "2 empty subscribers", c: &Channel{ @@ -112,11 +98,21 @@ func TestChannelValidation(t *testing.T) { Name: "foo", }, }, - Subscribers: []ChannelSubscriberSpec{{}, {}}, + Channelable: &duckv1alpha1.Channelable{ + Subscribers: []duckv1alpha1.ChannelSubscriberSpec{{}, {}}, + }, }, }, - want: apis.ErrMissingField("spec.subscriber[0].call", "spec.subscriber[0].result"). - Also(apis.ErrMissingField("spec.subscriber[1].call", "spec.subscriber[1].result")), + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrMissingField("spec.channelable.subscriber[0].sinkableDomain", "spec.channelable.subscriber[0].callableDomain") + fe.Details = "expected at least one of, got none" + errs = errs.Also(fe) + fe = apis.ErrMissingField("spec.channelable.subscriber[1].sinkableDomain", "spec.channelable.subscriber[1].callableDomain") + fe.Details = "expected at least one of, got none" + errs = errs.Also(fe) + return errs + }(), }} for _, test := range tests { diff --git a/pkg/apis/eventing/v1alpha1/subscription_defaults_test.go b/pkg/apis/eventing/v1alpha1/subscription_defaults_test.go new file mode 100644 index 00000000000..3c24ff2c31a --- /dev/null +++ b/pkg/apis/eventing/v1alpha1/subscription_defaults_test.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 + +import "testing" + +// No-op test because method does nothing. +func TestSubscriptionDefaults(t *testing.T) { + s := Subscription{} + s.SetDefaults() +} diff --git a/pkg/apis/eventing/v1alpha1/subscription_types.go b/pkg/apis/eventing/v1alpha1/subscription_types.go index f4b690d7b89..9c8dd8ca68e 100644 --- a/pkg/apis/eventing/v1alpha1/subscription_types.go +++ b/pkg/apis/eventing/v1alpha1/subscription_types.go @@ -55,6 +55,9 @@ var _ duckv1alpha1.ConditionsAccessor = (*SubscriptionStatus)(nil) // Check that Subscription implements the Conditions duck type. var _ = duck.VerifyType(&Subscription{}, &duckv1alpha1.Conditions{}) +// And it's Subscribable +var _ = duck.VerifyType(&Subscription{}, &duckv1alpha1.Subscribable{}) + // 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 @@ -175,8 +178,25 @@ type SubscriptionStatus struct { // +patchMergeKey=type // +patchStrategy=merge Conditions duckv1alpha1.Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + + // Subscription might be Subscribable. This depends if there's a Result channel + // In that case, this points to that resource. + Subscribable duckv1alpha1.Subscribable `json:"subscribable,omitempty"` } +const ( + // SubscriptionConditionReady has status True when all subconditions below have been set to True. + SubscriptionConditionReady = duckv1alpha1.ConditionReady + + // SubscriptionReferencesResolved has status True when all the specified references have been successfully + // resolved. + SubscriptionConditionReferencesResolved duckv1alpha1.ConditionType = "Resolved" + + // SubscriptionConditionFromReady has status True when controller has successfully added a subscription to From + // resource. + SubscriptionConditionFromReady duckv1alpha1.ConditionType = "FromReady" +) + // GetSpecJSON returns spec as json func (s *Subscription) GetSpecJSON() ([]byte, error) { return json.Marshal(s.Spec) diff --git a/pkg/apis/eventing/v1alpha1/subscription_types_test.go b/pkg/apis/eventing/v1alpha1/subscription_types_test.go new file mode 100644 index 00000000000..f41df7c91f0 --- /dev/null +++ b/pkg/apis/eventing/v1alpha1/subscription_types_test.go @@ -0,0 +1,138 @@ +/* +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 ( + "testing" + + "github.com/google/go-cmp/cmp" + duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" + corev1 "k8s.io/api/core/v1" +) + +var subscriptionConditionReady = duckv1alpha1.Condition{ + Type: SubscriptionConditionReady, + Status: corev1.ConditionTrue, +} + +var subscriptionConditionReferencesResolved = duckv1alpha1.Condition{ + Type: SubscriptionConditionReferencesResolved, + Status: corev1.ConditionFalse, +} + +var subscriptionConditionFromReady = duckv1alpha1.Condition{ + Type: SubscriptionConditionFromReady, + Status: corev1.ConditionTrue, +} + +func TestSubscriptionGetCondition(t *testing.T) { + tests := []struct { + name string + cs *SubscriptionStatus + condQuery duckv1alpha1.ConditionType + want *duckv1alpha1.Condition + }{{ + name: "single condition", + cs: &SubscriptionStatus{ + Conditions: []duckv1alpha1.Condition{ + subscriptionConditionReady, + }, + }, + condQuery: duckv1alpha1.ConditionReady, + want: &subscriptionConditionReady, + }, { + name: "multiple conditions", + cs: &SubscriptionStatus{ + Conditions: []duckv1alpha1.Condition{ + subscriptionConditionReady, + subscriptionConditionReferencesResolved, + }, + }, + condQuery: SubscriptionConditionReferencesResolved, + want: &subscriptionConditionReferencesResolved, + }, { + name: "multiple conditions, condition true", + cs: &SubscriptionStatus{ + Conditions: []duckv1alpha1.Condition{ + subscriptionConditionReady, + subscriptionConditionFromReady, + }, + }, + condQuery: SubscriptionConditionFromReady, + want: &subscriptionConditionFromReady, + }, { + name: "unknown condition", + cs: &SubscriptionStatus{ + Conditions: []duckv1alpha1.Condition{ + subscriptionConditionReady, + subscriptionConditionReferencesResolved, + }, + }, + condQuery: duckv1alpha1.ConditionType("foo"), + want: nil, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.cs.GetCondition(test.condQuery) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("unexpected condition (-want, +got) = %v", diff) + } + }) + } +} + +func TestSubscriptionSetConditions(t *testing.T) { + c := &Subscription{ + Status: SubscriptionStatus{}, + } + want := duckv1alpha1.Conditions{subscriptionConditionReady} + c.Status.SetConditions(want) + got := c.Status.GetConditions() + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("unexpected conditions (-want, +got) = %v", diff) + } +} + +func TestSubscriptionGetSpecJSON(t *testing.T) { + targetURI := "http://example.com" + c := &Subscription{ + Spec: SubscriptionSpec{ + From: corev1.ObjectReference{ + Name: "foo", + }, + Call: &Callable{ + TargetURI: &targetURI, + }, + Result: &ResultStrategy{ + Target: &corev1.ObjectReference{ + Name: "result", + }, + }, + }, + } + + want := `{"from":{"name":"foo"},"call":{"targetURI":"http://example.com"},"result":{"target":{"name":"result"}}}` + got, err := c.GetSpecJSON() + if err != nil { + t.Fatalf("unexpected spec JSON error: %v", err) + } + + if diff := cmp.Diff(want, string(got)); diff != "" { + t.Errorf("unexpected spec JSON (-want, +got) = %v", diff) + } +} diff --git a/pkg/apis/eventing/v1alpha1/subscription_validation.go b/pkg/apis/eventing/v1alpha1/subscription_validation.go index cebdf24cdfd..b06068be966 100644 --- a/pkg/apis/eventing/v1alpha1/subscription_validation.go +++ b/pkg/apis/eventing/v1alpha1/subscription_validation.go @@ -18,7 +18,6 @@ package v1alpha1 import ( "reflect" - // "strings" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -58,7 +57,7 @@ func (ss *SubscriptionSpec) Validate() *apis.FieldError { } if !missingResultStrategy { - if fe := isValidResultStrategy(ss.Result); fe != nil { + if fe := isValidResultStrategy(*ss.Result); fe != nil { errs = errs.Also(fe.ViaField("result")) } } @@ -94,7 +93,7 @@ func isFromEmpty(f corev1.ObjectReference) bool { // Valid from only contains the following fields: // - Kind == 'Channel' -// - APIVersion == 'channels.knative.dev/v1alpha1' +// - APIVersion == 'eventing.knative.dev/v1alpha1' // - Name == not empty func isValidFrom(f corev1.ObjectReference) *apis.FieldError { errs := isValidObjectReference(f) @@ -105,9 +104,9 @@ func isValidFrom(f corev1.ObjectReference) *apis.FieldError { fe.Details = "only 'Channel' kind is allowed" errs = errs.Also(fe) } - if f.APIVersion != "channels.knative.dev/v1alpha1" { + if f.APIVersion != "eventing.knative.dev/v1alpha1" { fe := apis.ErrInvalidValue(f.APIVersion, "apiVersion") - fe.Details = "only channels.knative.dev/v1alpha1 is allowed for apiVersion" + fe.Details = "only eventing.knative.dev/v1alpha1 is allowed for apiVersion" errs = errs.Also(fe) } return errs @@ -117,8 +116,23 @@ 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 { - return isValidObjectReference(*r.Target).ViaField("target") +func isValidResultStrategy(r ResultStrategy) *apis.FieldError { + fe := isValidObjectReference(*r.Target) + if fe != nil { + return fe.ViaField("target") + } + if r.Target.Kind != "Channel" { + fe := apis.ErrInvalidValue(r.Target.Kind, "kind") + fe.Paths = []string{"kind"} + fe.Details = "only 'Channel' kind is allowed" + return fe + } + if r.Target.APIVersion != "eventing.knative.dev/v1alpha1" { + fe := apis.ErrInvalidValue(r.Target.APIVersion, "apiVersion") + fe.Details = "only eventing.knative.dev/v1alpha1 is allowed for apiVersion" + return fe + } + return nil } func isValidObjectReference(f corev1.ObjectReference) *apis.FieldError { diff --git a/pkg/apis/eventing/v1alpha1/subscription_validation_test.go b/pkg/apis/eventing/v1alpha1/subscription_validation_test.go index a5e749212e2..7d9b9dc1711 100644 --- a/pkg/apis/eventing/v1alpha1/subscription_validation_test.go +++ b/pkg/apis/eventing/v1alpha1/subscription_validation_test.go @@ -25,7 +25,7 @@ import ( const ( channelKind = "Channel" - channelAPIVersion = "channels.knative.dev/v1alpha1" + channelAPIVersion = "eventing.knative.dev/v1alpha1" routeKind = "Route" routeAPIVersion = "serving.knative.dev/v1alpha1" fromChannelName = "fromChannel" @@ -41,7 +41,7 @@ func getValidFromRef() corev1.ObjectReference { } } -func getValidResultRef() *ResultStrategy { +func getValidResultStrategy() *ResultStrategy { return &ResultStrategy{ Target: &corev1.ObjectReference{ Name: resultChannelName, @@ -165,7 +165,7 @@ func TestSubscriptionSpecValidation(t *testing.T) { name: "missing Call", c: &SubscriptionSpec{ From: getValidFromRef(), - Result: getValidResultRef(), + Result: getValidResultStrategy(), }, want: nil, }, { @@ -173,7 +173,7 @@ func TestSubscriptionSpecValidation(t *testing.T) { c: &SubscriptionSpec{ From: getValidFromRef(), Call: &Callable{}, - Result: getValidResultRef(), + Result: getValidResultStrategy(), }, want: nil, }, { @@ -246,7 +246,7 @@ func TestSubscriptionImmutable(t *testing.T) { newCall := getValidCall() newCall.Target.Name = "newCall" - newResult := getValidResultRef() + newResult := getValidResultStrategy() newResult.Target.Name = "newResultChannel" tests := []struct { @@ -297,7 +297,7 @@ func TestSubscriptionImmutable(t *testing.T) { c: &Subscription{ Spec: SubscriptionSpec{ From: getValidFromRef(), - Result: getValidResultRef(), + Result: getValidResultStrategy(), }, }, og: &Subscription{ @@ -312,7 +312,7 @@ func TestSubscriptionImmutable(t *testing.T) { c: &Subscription{ Spec: SubscriptionSpec{ From: getValidFromRef(), - Result: getValidResultRef(), + Result: getValidResultStrategy(), }, }, og: &Subscription{ @@ -333,7 +333,7 @@ func TestSubscriptionImmutable(t *testing.T) { og: &Subscription{ Spec: SubscriptionSpec{ From: getValidFromRef(), - Result: getValidResultRef(), + Result: getValidResultStrategy(), }, }, want: nil, @@ -420,7 +420,7 @@ func TestValidFrom(t *testing.T) { }, want: func() *apis.FieldError { fe := apis.ErrInvalidValue("", "apiVersion") - fe.Details = "only channels.knative.dev/v1alpha1 is allowed for apiVersion" + fe.Details = "only eventing.knative.dev/v1alpha1 is allowed for apiVersion" return apis.ErrMissingField("apiVersion").Also(fe) }(), }, { @@ -455,7 +455,7 @@ func TestValidFrom(t *testing.T) { }, want: func() *apis.FieldError { fe := apis.ErrInvalidValue("wrongapiversion", "apiVersion") - fe.Details = "only channels.knative.dev/v1alpha1 is allowed for apiVersion" + fe.Details = "only eventing.knative.dev/v1alpha1 is allowed for apiVersion" return fe }(), }, { @@ -613,38 +613,17 @@ func TestValidCallable(t *testing.T) { } func TestValidResultStrategy(t *testing.T) { - targetURI := "http://example.com" tests := []struct { name string - c Callable + c ResultStrategy want *apis.FieldError }{{ name: "valid target", - c: *getValidCall(), + c: *getValidResultStrategy(), want: nil, - }, { - name: "valid targetURI", - c: Callable{ - TargetURI: &targetURI, - }, - want: nil, - }, { - name: "both target and targetURI given", - c: Callable{ - Target: &corev1.ObjectReference{ - Name: fromChannelName, - APIVersion: channelAPIVersion, - Kind: channelKind, - }, - TargetURI: &targetURI, - }, - want: func() *apis.FieldError { - fe := apis.ErrMultipleOneOf("target", "targetURI") - return fe - }(), }, { name: "missing name in target", - c: Callable{ + c: ResultStrategy{ Target: &corev1.ObjectReference{ APIVersion: channelAPIVersion, Kind: channelKind, @@ -656,7 +635,7 @@ func TestValidResultStrategy(t *testing.T) { }(), }, { name: "missing apiVersion in target", - c: Callable{ + c: ResultStrategy{ Target: &corev1.ObjectReference{ Name: fromChannelName, Kind: channelKind, @@ -668,7 +647,7 @@ func TestValidResultStrategy(t *testing.T) { }(), }, { name: "missing kind in target", - c: Callable{ + c: ResultStrategy{ Target: &corev1.ObjectReference{ Name: fromChannelName, APIVersion: channelAPIVersion, @@ -678,9 +657,37 @@ func TestValidResultStrategy(t *testing.T) { fe := apis.ErrMissingField("target.kind") return fe }(), + }, { + name: "invalid kind", + c: ResultStrategy{ + Target: &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: ResultStrategy{ + Target: &corev1.ObjectReference{ + Name: fromChannelName, + APIVersion: "wrongapiversion", + Kind: channelKind, + }, + }, + want: func() *apis.FieldError { + fe := apis.ErrInvalidValue("wrongapiversion", "apiVersion") + fe.Details = "only eventing.knative.dev/v1alpha1 is allowed for apiVersion" + return fe + }(), }, { name: "extra field, namespace", - c: Callable{ + c: ResultStrategy{ Target: &corev1.ObjectReference{ Name: fromChannelName, APIVersion: channelAPIVersion, @@ -696,7 +703,7 @@ func TestValidResultStrategy(t *testing.T) { }, { // 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{ + c: ResultStrategy{ Target: &corev1.ObjectReference{ Name: fromChannelName, APIVersion: channelAPIVersion, @@ -709,7 +716,7 @@ func TestValidResultStrategy(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got := isValidCallable(test.c) + got := isValidResultStrategy(test.c) if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { t.Errorf("%s: isValidFrom (-want, +got) = %v", test.name, diff) } diff --git a/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go index ce044896091..04144e69030 100644 --- a/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/eventing/v1alpha1/zz_generated.deepcopy.go @@ -142,11 +142,13 @@ func (in *ChannelSpec) DeepCopyInto(out *ChannelSpec) { (*in).DeepCopyInto(*out) } } - if in.Subscribers != nil { - in, out := &in.Subscribers, &out.Subscribers - *out = make([]ChannelSubscriberSpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) + if in.Channelable != nil { + in, out := &in.Channelable, &out.Channelable + if *in == nil { + *out = nil + } else { + *out = new(duck_v1alpha1.Channelable) + (*in).DeepCopyInto(*out) } } return @@ -165,6 +167,8 @@ func (in *ChannelSpec) DeepCopy() *ChannelSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ChannelStatus) DeepCopyInto(out *ChannelStatus) { *out = *in + out.Sinkable = in.Sinkable + out.Subscribable = in.Subscribable if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make(duck_v1alpha1.Conditions, len(*in)) @@ -185,40 +189,6 @@ func (in *ChannelStatus) DeepCopy() *ChannelStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ChannelSubscriberSpec) DeepCopyInto(out *ChannelSubscriberSpec) { - *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 ChannelSubscriberSpec. -func (in *ChannelSubscriberSpec) DeepCopy() *ChannelSubscriberSpec { - if in == nil { - return nil - } - out := new(ChannelSubscriberSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterProvisioner) DeepCopyInto(out *ClusterProvisioner) { *out = *in @@ -476,6 +446,7 @@ func (in *SubscriptionStatus) DeepCopyInto(out *SubscriptionStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + out.Subscribable = in.Subscribable return } diff --git a/pkg/controller/eventing/subscription/provider.go b/pkg/controller/eventing/subscription/provider.go new file mode 100644 index 00000000000..1575fef4cb1 --- /dev/null +++ b/pkg/controller/eventing/subscription/provider.go @@ -0,0 +1,78 @@ +/* +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 subscription + +import ( + "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +const ( + // controllerAgentName is the string used by this controller to identify + // itself when creating events. + controllerAgentName = "subscription-controller" +) + +type reconciler struct { + client client.Client + restConfig *rest.Config + dynamicClient dynamic.Interface + recorder record.EventRecorder +} + +// Verify the struct implements reconcile.Reconciler +var _ reconcile.Reconciler = &reconciler{} + +// ProvideController returns a Subscription controller. +func ProvideController(mgr manager.Manager) (controller.Controller, error) { + // Setup a new controller to Reconcile Subscriptions. + c, err := controller.New(controllerAgentName, mgr, controller.Options{ + Reconciler: &reconciler{ + recorder: mgr.GetRecorder(controllerAgentName), + }, + }) + if err != nil { + return nil, err + } + + // Watch Subscription events and enqueue Subscription object key. + if err := c.Watch(&source.Kind{Type: &v1alpha1.Subscription{}}, &handler.EnqueueRequestForObject{}); err != nil { + return nil, err + } + + return c, nil +} + +func (r *reconciler) InjectClient(c client.Client) error { + r.client = c + return nil +} + +func (r *reconciler) InjectConfig(c *rest.Config) error { + r.restConfig = c + var err error + r.dynamicClient, err = dynamic.NewForConfig(c) + return err +} diff --git a/pkg/controller/eventing/subscription/reconcile.go b/pkg/controller/eventing/subscription/reconcile.go new file mode 100644 index 00000000000..93f61bed3c2 --- /dev/null +++ b/pkg/controller/eventing/subscription/reconcile.go @@ -0,0 +1,341 @@ +/* +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 subscription + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/golang/glog" + "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + "github.com/knative/pkg/apis/duck" + duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" + "github.com/mattbaird/jsonpatch" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// Reconcile compares the actual state with the desired, and attempts to +// converge the two. It then updates the Status block of the Subscription resource +// with the current status of the resource. +func (r *reconciler) Reconcile(request reconcile.Request) (reconcile.Result, error) { + glog.Infof("Reconciling subscription %v", request) + subscription := &v1alpha1.Subscription{} + err := r.client.Get(context.TODO(), request.NamespacedName, subscription) + + if errors.IsNotFound(err) { + glog.Errorf("could not find subscription %v\n", request) + return reconcile.Result{}, nil + } + + if err != nil { + glog.Errorf("could not fetch Subscription %v for %+v\n", err, request) + return reconcile.Result{}, err + } + + original := subscription.DeepCopy() + + // Reconcile this copy of the Subscription and then write back any status + // updates regardless of whether the reconcile error out. + err = r.reconcile(subscription) + if equality.Semantic.DeepEqual(original.Status, subscription.Status) { + // If we didn't change anything then don't call updateStatus. + // This is important because the copy we loaded from the informer's + // cache may be stale and we don't want to overwrite a prior update + // to status with this stale state. + } else if _, err := r.updateStatus(subscription); err != nil { + glog.Warningf("Failed to update subscription status: %v", err) + return reconcile.Result{}, err + } + + // Requeue if the resource is not ready: + return reconcile.Result{}, err +} + +func (r *reconciler) reconcile(subscription *v1alpha1.Subscription) error { + // TODO: Should this just also set up a defer call for subscription.SetConditions. + // No time right now as I'm turning into a pumpking but seems reasonable. + conditions := []duckv1alpha1.Condition{} + + // See if the subscription has been deleted + accessor, err := meta.Accessor(subscription) + if err != nil { + glog.Warningf("Failed to get metadata accessor: %s", err) + return err + } + deletionTimestamp := accessor.GetDeletionTimestamp() + glog.Infof("DeletionTimestamp: %v", deletionTimestamp) + + // Reconcile the subscription to the From channel that's consuming events that are either + // going to the call or if there's no call, directly to result. + from, err := r.resolveFromChannelable(subscription.Namespace, &subscription.Spec.From) + if err != nil { + glog.Warningf("Failed to resolve From %+v : %s", subscription.Spec.From, err) + return err + } + if from.Status.Subscribable == nil { + return fmt.Errorf("from is not subscribable %s %s/%s", subscription.Spec.From.Kind, subscription.Namespace, subscription.Spec.From.Name) + } + + callDomain := "" + if subscription.Spec.Call != nil { + callDomain, err = r.resolveCall(subscription.Namespace, *subscription.Spec.Call) + if err != nil { + glog.Warningf("Failed to resolve Call %+v : %s", *subscription.Spec.Call, err) + return err + } + if callDomain == "" { + return fmt.Errorf("could not get domain from call (is it not targetable?)") + } + glog.Infof("Resolved call to: %q", callDomain) + } + + resultDomain := "" + if subscription.Spec.Result != nil { + resultDomain, err = r.resolveResult(subscription.Namespace, *subscription.Spec.Result) + if err != nil { + glog.Warningf("Failed to resolve Result %v : %v", subscription.Spec.Result, err) + return err + } + if resultDomain == "" { + glog.Warningf("Failed to resolve result %v to actual domain", *subscription.Spec.Result) + return err + } + glog.Infof("Resolved result to: %q", resultDomain) + } + + // Everything that was supposed to be resolved was, so flip the status bit on that. + conditions = append(conditions, duckv1alpha1.Condition{ + Type: v1alpha1.SubscriptionConditionReferencesResolved, + Status: corev1.ConditionTrue, + }) + + // Ok, now that we have the From and at least one of the Call/Result, let's reconcile + // the From with this information. + err = r.reconcileFromChannel(subscription.Namespace, from.Status.Subscribable.Channelable, callDomain, resultDomain, deletionTimestamp != nil) + if err != nil { + glog.Warningf("Failed to resolve from Channel : %s", err) + return err + } + // Everything went well, set the fact that subscriptions have been modified + conditions = append(conditions, duckv1alpha1.Condition{ + Type: duckv1alpha1.ConditionReady, + Status: corev1.ConditionTrue, + }) + subscription.Status.SetConditions(conditions) + return nil +} + +func (r *reconciler) updateStatus(subscription *v1alpha1.Subscription) (*v1alpha1.Subscription, error) { + newSubscription := &v1alpha1.Subscription{} + err := r.client.Get(context.TODO(), client.ObjectKey{Namespace: subscription.Namespace, Name: subscription.Name}, newSubscription) + + if err != nil { + return nil, err + } + newSubscription.Status = subscription.Status + + // Until #38113 is merged, we must use Update instead of UpdateStatus to + // update the Status block of the Subscription resource. UpdateStatus will not + // allow changes to the Spec of the resource, which is ideal for ensuring + // nothing other than resource status has been updated. + if err = r.client.Update(context.TODO(), newSubscription); err != nil { + return nil, err + } + return newSubscription, nil +} + +// resolveCall resolves the Spec.Call object. If it's an ObjectReference will resolve the object +// and treat it as a Targetable.If it's TargetURI then it's used as is. +// TODO: Once Service Routes, etc. support Targetable, use that. +// +func (r *reconciler) resolveCall(namespace string, callable v1alpha1.Callable) (string, error) { + if callable.TargetURI != nil && *callable.TargetURI != "" { + return *callable.TargetURI, nil + } + + obj, err := r.fetchObjectReference(namespace, callable.Target) + if err != nil { + glog.Warningf("Failed to fetch Callable target %+v: %s", callable.Target, err) + return "", err + } + t := duckv1alpha1.LegacyTarget{} + // Once Knative services support Targetable, switch to using this. + //t := duckv1alpha1.Target{} + err = duck.FromUnstructured(*obj, &t) + if err != nil { + glog.Warningf("Failed to unserialize legacy target: %s", err) + return "", err + } + + return t.Status.DomainInternal, nil + // Once Knative services support Targetable, switch to using this + // if t.Status.Targetable != nil { + // return t.Status.Targetable.DomainInternal, nil + // } + //return "", fmt.Errorf("status does not contain targetable") +} + +// resolveResult resolves the Spec.Result object. +func (r *reconciler) resolveResult(namespace string, resultStrategy v1alpha1.ResultStrategy) (string, error) { + obj, err := r.fetchObjectReference(namespace, resultStrategy.Target) + if err != nil { + glog.Warningf("Failed to fetch ResultStrategy target %+v: %s", resultStrategy, err) + return "", err + } + s := duckv1alpha1.Sink{} + err = duck.FromUnstructured(*obj, &s) + if err != nil { + glog.Warningf("Failed to unserialize Sinkable target: %s", err) + return "", err + } + if s.Status.Sinkable != nil { + return s.Status.Sinkable.DomainInternal, nil + } + return "", fmt.Errorf("status does not contain sinkable") +} + +// resolveFromChannelable fetches an object based on ObjectReference. It assumes that the +// fetched object then implements Subscribable interface and returns the ObjectReference +// representing the Channelable interface. +func (r *reconciler) resolveFromChannelable(namespace string, ref *corev1.ObjectReference) (*duckv1alpha1.Subscription, error) { + obj, err := r.fetchObjectReference(namespace, ref) + if err != nil { + glog.Warningf("Failed to fetch From target %+v: %s", ref, err) + return nil, err + } + + c := duckv1alpha1.Subscription{} + err = duck.FromUnstructured(*obj, &c) + return &c, err +} + +// fetchObjectReference fetches an object based on ObjectReference. +func (r *reconciler) fetchObjectReference(namespace string, ref *corev1.ObjectReference) (*unstructured.Unstructured, error) { + // resourceClient, err := r.CreateResourceInterface2(r.restConfig, ref, namespace) + resourceClient, err := r.CreateResourceInterface(namespace, ref) + if err != nil { + glog.Warningf("failed to create dynamic client resource: %v", err) + return nil, err + } + + return resourceClient.Get(ref.Name, metav1.GetOptions{}) +} + +func (r *reconciler) reconcileFromChannel(namespace string, subscribable corev1.ObjectReference, callDomain string, resultDomain string, deleted bool) error { + glog.Infof("Reconciling From Channel: %+v call: %q result %q deleted: %v", subscribable, callDomain, resultDomain, deleted) + + // First get the original object and convert it to only the bits we care about + s, err := r.fetchObjectReference(namespace, &subscribable) + if err != nil { + return err + } + original := duckv1alpha1.Channel{} + err = duck.FromUnstructured(*s, &original) + if err != nil { + return err + } + + // TODO: Handle deletes. + + after := original.DeepCopy() + after.Spec.Channelable = &duckv1alpha1.Channelable{ + Subscribers: []duckv1alpha1.ChannelSubscriberSpec{{CallableDomain: callDomain, SinkableDomain: resultDomain}}, + } + + patch, err := createPatch(original, after) + if err != nil { + return err + } + + // Note that we have to just use normal JSON to marshal here even though + // jsonpatch provides a Marshal method because we need to pass an array + // to k8s and there's no way to serialize the whole jsonpatch array. + patchBytes, err := json.Marshal(patch) + if err != nil { + glog.Warningf("failed to marshal json patch: %s", err) + return err + } + + resourceClient, err := r.CreateResourceInterface(namespace, &subscribable) + if err != nil { + glog.Warningf("failed to create dynamic client resource: %v", err) + return err + } + patched, err := resourceClient.Patch(original.Name, types.JSONPatchType, patchBytes) + if err != nil { + glog.Warningf("Failed to patch the object: %s", err) + glog.Warningf("Patch was: %+v", patch) + return err + } + glog.Warningf("Patched resource: %+v", patched) + return nil +} + +func (r *reconciler) CreateResourceInterface(namespace string, ref *corev1.ObjectReference) (dynamic.ResourceInterface, error) { + gvk := ref.GroupVersionKind() + + rc := r.dynamicClient.Resource(schema.GroupVersionResource{ + Group: gvk.Group, + Version: gvk.Version, + Resource: pluralizeKind(gvk.Kind), + }) + if rc == nil { + return nil, fmt.Errorf("failed to create dynamic client resource") + } + return rc.Namespace(namespace), nil + +} + +// takes a kind and pluralizes it. This is super terrible, but I am +// not aware of a generic way to do this. +// I am not alone in thinking this and I haven't found a better solution: +// This seems relevant: +// https://github.com/kubernetes/kubernetes/issues/18622 +func pluralizeKind(kind string) string { + ret := strings.ToLower(kind) + if strings.HasSuffix(ret, "s") { + return fmt.Sprintf("%ses", ret) + } + return fmt.Sprintf("%ss", ret) +} + +func createPatch(before, after interface{}) ([]jsonpatch.JsonPatchOperation, error) { + // Marshal the before and after. + rawBefore, err := json.Marshal(before) + if err != nil { + return nil, err + } + + rawAfter, err := json.Marshal(after) + if err != nil { + return nil, err + } + + return jsonpatch.CreatePatch(rawBefore, rawAfter) +} diff --git a/pkg/controller/eventing/subscription/reconcile_test.go b/pkg/controller/eventing/subscription/reconcile_test.go new file mode 100644 index 00000000000..b883bf03d52 --- /dev/null +++ b/pkg/controller/eventing/subscription/reconcile_test.go @@ -0,0 +1,496 @@ +/* +Copyright 2018 The Knative Authors + +Licensed under the Apache License, Veroute.on 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 subscription + +import ( + "fmt" + "testing" + + eventingv1alpha1 "github.com/knative/eventing/pkg/apis/eventing/v1alpha1" + controllertesting "github.com/knative/eventing/pkg/controller/testing" + duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +var ( + trueVal = true + targetURI = "http://target.example.com" +) + +const ( + fromChannelName = "fromchannel" + resultChannelName = "resultchannel" + routeName = "callroute" + ChannelKind = "Channel" + routeKind = "Route" + targetDNS = "myfunction.mynamespace.svc.cluster.local" + sinkableDNS = "myresultchannel.mynamespace.svc.cluster.local" + eventType = "myeventtype" + subscriptionName = "testsubscription" + testNS = "testnamespace" +) + +func init() { + // Add types to scheme + eventingv1alpha1.AddToScheme(scheme.Scheme) + duckv1alpha1.AddToScheme(scheme.Scheme) +} + +var testCases = []controllertesting.TestCase{ + { + Name: "non existent key", + ReconcileKey: "non-existent-test-ns/non-existent-test-key", + WantErr: false, + }, { + Name: "subscription but From channel does not exist", + InitialState: []runtime.Object{ + getNewSubscription(), + }, + ReconcileKey: fmt.Sprintf("%s/%s", testNS, subscriptionName), + WantErrMsg: `channels.eventing.knative.dev "fromchannel" not found`, + }, { + Name: "subscription, but From is not subscribable", + InitialState: []runtime.Object{ + getNewSubscription(), + }, + ReconcileKey: fmt.Sprintf("%s/%s", testNS, subscriptionName), + WantErrMsg: "from is not subscribable Channel testnamespace/fromchannel", + Scheme: scheme.Scheme, + Objects: []runtime.Object{ + // Source channel + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + "kind": ChannelKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": fromChannelName, + }, + "spec": map[string]interface{}{ + "channelable": map[string]interface{}{}, + }, + "status": map[string]interface{}{ + "notsubscribable": map[string]interface{}{ + "channelable": map[string]interface{}{ + "kind": ChannelKind, + "name": fromChannelName, + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + }, + }, + }, + }}, + }, + }, { + Name: "Valid from, call does not exist", + InitialState: []runtime.Object{ + getNewSubscription(), + }, + ReconcileKey: fmt.Sprintf("%s/%s", testNS, subscriptionName), + WantErrMsg: `routes.serving.knative.dev "callroute" not found`, + Scheme: scheme.Scheme, + Objects: []runtime.Object{ + // Source channel + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + "kind": ChannelKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": fromChannelName, + }, + "spec": map[string]interface{}{ + "channelable": map[string]interface{}{}, + }, + "status": map[string]interface{}{ + "subscribable": map[string]interface{}{ + "channelable": map[string]interface{}{ + "kind": ChannelKind, + "name": fromChannelName, + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + }, + }, + }, + }}, + }, + }, { + Name: "Valid from, call is not targetable", + InitialState: []runtime.Object{ + getNewSubscription(), + }, + ReconcileKey: fmt.Sprintf("%s/%s", testNS, subscriptionName), + WantErrMsg: "could not get domain from call (is it not targetable?)", + Scheme: scheme.Scheme, + Objects: []runtime.Object{ + // Source channel + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + "kind": ChannelKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": fromChannelName, + }, + "spec": map[string]interface{}{ + "channelable": map[string]interface{}{}, + }, + "status": map[string]interface{}{ + "subscribable": map[string]interface{}{ + "channelable": map[string]interface{}{ + "kind": ChannelKind, + "name": fromChannelName, + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + }, + }, + }, + }}, + // Call (using knative route) + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "serving.knative.dev/v1alpha1", + "kind": routeKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": routeName, + }, + "status": map[string]interface{}{ + "someotherstuff": targetDNS, + }, + }}, + }, + }, { + Name: "Valid from and call, result does not exist", + InitialState: []runtime.Object{ + getNewSubscription(), + }, + ReconcileKey: fmt.Sprintf("%s/%s", testNS, subscriptionName), + WantErrMsg: `channels.eventing.knative.dev "resultchannel" not found`, + Scheme: scheme.Scheme, + Objects: []runtime.Object{ + // Source channel + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + "kind": ChannelKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": fromChannelName, + }, + "spec": map[string]interface{}{ + "channelable": map[string]interface{}{}, + }, + "status": map[string]interface{}{ + "subscribable": map[string]interface{}{ + "channelable": map[string]interface{}{ + "kind": ChannelKind, + "name": fromChannelName, + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + }, + }, + }, + }}, + // Call (using knative route) + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "serving.knative.dev/v1alpha1", + "kind": routeKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": routeName, + }, + "status": map[string]interface{}{ + "domainInternal": targetDNS, + }, + }}, + }, + }, { + Name: "valid from, call, result is not sinkable", + InitialState: []runtime.Object{ + getNewSubscription(), + }, + ReconcileKey: fmt.Sprintf("%s/%s", testNS, subscriptionName), + WantErrMsg: "status does not contain sinkable", + WantPresent: []runtime.Object{ + // TODO: Again this works on gke cluster, but I need to set + // something else up here. later... + // getNewSubscriptionWithReferencesResolvedStatus(), + getNewSubscription(), + }, + Scheme: scheme.Scheme, + Objects: []runtime.Object{ + // Source channel + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + "kind": ChannelKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": fromChannelName, + }, + "spec": map[string]interface{}{ + "channelable": map[string]interface{}{}, + }, + "status": map[string]interface{}{ + "subscribable": map[string]interface{}{ + "channelable": map[string]interface{}{ + "kind": ChannelKind, + "name": fromChannelName, + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + }, + }, + }, + }}, + // Call (using knative route) + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "serving.knative.dev/v1alpha1", + "kind": routeKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": routeName, + }, + "status": map[string]interface{}{ + "domainInternal": targetDNS, + }, + }}, + // Result channel + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + "kind": ChannelKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": resultChannelName, + }, + "spec": map[string]interface{}{ + "channelable": map[string]interface{}{}, + }, + "status": map[string]interface{}{ + "subscribable": map[string]interface{}{ + "channelable": map[string]interface{}{ + "kind": ChannelKind, + "name": fromChannelName, + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + }, + }, + }, + }}, + }, + }, { + Name: "new subscription: adds status, all targets resolved, subscribers modified", + InitialState: []runtime.Object{ + getNewSubscription(), + }, + ReconcileKey: fmt.Sprintf("%s/%s", testNS, subscriptionName), + // TODO: JSON patch is not working for some reason. Is this the array vs. non-array, or + // k8s accepting something the fake doesn't, or is there a real bug somewhere? + // it works correctly on the k8s cluster. so need to figure this out + // Marking this as expecting a failure. Needs to be fixed obviously. + WantResult: reconcile.Result{}, + WantErrMsg: "invalid JSON document", + Scheme: scheme.Scheme, + Objects: []runtime.Object{ + // Source channel + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + "kind": ChannelKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": fromChannelName, + }, + "spec": map[string]interface{}{ + "channelable": map[string]interface{}{}, + }, + "status": map[string]interface{}{ + "subscribable": map[string]interface{}{ + "channelable": map[string]interface{}{ + "kind": ChannelKind, + "name": fromChannelName, + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + }, + }, + }, + }}, + // Call (using knative route) + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "serving.knative.dev/v1alpha1", + "kind": routeKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": routeName, + }, + "status": map[string]interface{}{ + "domainInternal": targetDNS, + }, + }}, + // Result channel + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + "kind": ChannelKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": resultChannelName, + }, + "spec": map[string]interface{}{ + "channelable": map[string]interface{}{}, + }, + "status": map[string]interface{}{ + "subscribable": map[string]interface{}{ + "channelable": map[string]interface{}{ + "kind": ChannelKind, + "name": fromChannelName, + "apiVersion": eventingv1alpha1.SchemeGroupVersion.String(), + }, + }, + "sinkable": map[string]interface{}{ + "domainInternal": sinkableDNS, + }, + }, + }}, + }, + }, +} + +func TestAllCases(t *testing.T) { + recorder := record.NewBroadcaster().NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName}) + + for _, tc := range testCases { + c := tc.GetClient() + dc := tc.GetDynamicClient() + + r := &reconciler{ + client: c, + dynamicClient: dc, + restConfig: &rest.Config{}, + recorder: recorder, + } + t.Run(tc.Name, tc.Runner(t, r, c)) + } +} + +func getNewFromChannel() *eventingv1alpha1.Channel { + return getNewChannel(fromChannelName) +} + +func getNewResultChannel() *eventingv1alpha1.Channel { + return getNewChannel(resultChannelName) +} + +func getNewChannel(name string) *eventingv1alpha1.Channel { + channel := &eventingv1alpha1.Channel{ + TypeMeta: channelType(), + ObjectMeta: om("test", name), + Spec: eventingv1alpha1.ChannelSpec{}, + } + channel.ObjectMeta.OwnerReferences = append(channel.ObjectMeta.OwnerReferences, getOwnerReference(false)) + + // selflink is not filled in when we create the object, so clear it + channel.ObjectMeta.SelfLink = "" + return channel +} + +func getNewSubscriptionWithReferencesResolvedStatus() *eventingv1alpha1.Subscription { + s := getNewSubscription() + s.Status.SetConditions([]duckv1alpha1.Condition{{ + Type: eventingv1alpha1.SubscriptionConditionReferencesResolved, + Status: corev1.ConditionTrue, + }}) + return s +} + +func getNewSubscription() *eventingv1alpha1.Subscription { + subscription := &eventingv1alpha1.Subscription{ + TypeMeta: subscriptionType(), + ObjectMeta: om(testNS, subscriptionName), + Spec: eventingv1alpha1.SubscriptionSpec{ + From: corev1.ObjectReference{ + Name: fromChannelName, + Kind: ChannelKind, + APIVersion: eventingv1alpha1.SchemeGroupVersion.String(), + }, + Call: &eventingv1alpha1.Callable{ + Target: &corev1.ObjectReference{ + Name: routeName, + Kind: routeKind, + APIVersion: "serving.knative.dev/v1alpha1", + }, + }, + Result: &eventingv1alpha1.ResultStrategy{ + Target: &corev1.ObjectReference{ + Name: resultChannelName, + Kind: ChannelKind, + APIVersion: eventingv1alpha1.SchemeGroupVersion.String(), + }, + }, + }, + } + subscription.ObjectMeta.OwnerReferences = append(subscription.ObjectMeta.OwnerReferences, getOwnerReference(false)) + + // selflink is not filled in when we create the object, so clear it + subscription.ObjectMeta.SelfLink = "" + return subscription +} + +func channelType() metav1.TypeMeta { + return metav1.TypeMeta{ + APIVersion: eventingv1alpha1.SchemeGroupVersion.String(), + Kind: "Channel", + } +} + +func subscriptionType() metav1.TypeMeta { + return metav1.TypeMeta{ + APIVersion: eventingv1alpha1.SchemeGroupVersion.String(), + Kind: "Subscription", + } +} + +func om(namespace, name string) metav1.ObjectMeta { + return metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + SelfLink: fmt.Sprintf("/apis/eventing/v1alpha1/namespaces/%s/object/%s", namespace, name), + } +} +func feedObjectMeta(namespace, generateName string) metav1.ObjectMeta { + return metav1.ObjectMeta{ + Namespace: namespace, + GenerateName: generateName, + OwnerReferences: []metav1.OwnerReference{ + getOwnerReference(true), + }, + } +} + +func getOwnerReference(blockOwnerDeletion bool) metav1.OwnerReference { + return metav1.OwnerReference{ + APIVersion: eventingv1alpha1.SchemeGroupVersion.String(), + Kind: "Subscription", + Name: subscriptionName, + Controller: &trueVal, + BlockOwnerDeletion: &blockOwnerDeletion, + } +} diff --git a/pkg/controller/testing/table.go b/pkg/controller/testing/table.go index 382b83454e9..5a85c1a7e39 100644 --- a/pkg/controller/testing/table.go +++ b/pkg/controller/testing/table.go @@ -29,6 +29,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" + dynamicfake "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/cache" "sigs.k8s.io/controller-runtime/pkg/client" @@ -69,6 +71,12 @@ type TestCase struct { // Mocks that tamper with the client's responses. Mocks Mocks + + // Scheme for the dynamic client + Scheme *runtime.Scheme + + // Fake dynamic objects + Objects []runtime.Object } // Runner returns a testing func that can be passed to t.Run. @@ -97,6 +105,15 @@ func (tc *TestCase) Runner(t *testing.T, r reconcile.Reconciler, c *MockClient) } } +// GetDynamicClient returns the mockDynamicClient to use for this test case. +func (tc *TestCase) GetDynamicClient() dynamic.Interface { + if tc.Scheme == nil { + return dynamicfake.NewSimpleDynamicClient(runtime.NewScheme(), tc.Objects...) + } else { + return dynamicfake.NewSimpleDynamicClient(tc.Scheme, tc.Objects...) + } +} + // GetClient returns the mockClient to use for this test case. func (tc *TestCase) GetClient() *MockClient { innerClient := fake.NewFakeClient(tc.InitialState...) diff --git a/vendor/github.com/knative/pkg/apis/duck/unstructured.go b/vendor/github.com/knative/pkg/apis/duck/unstructured.go new file mode 100644 index 00000000000..e6fc733ad7b --- /dev/null +++ b/vendor/github.com/knative/pkg/apis/duck/unstructured.go @@ -0,0 +1,34 @@ +/* +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 duck + +import ( + "encoding/json" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +// FromUnstructured takes unstructured object from (say from client-go/dynamic) and +// converts it into our duck types. +func FromUnstructured(obj unstructured.Unstructured, target interface{}) error { + // Use the unstructured marshaller to ensure it's proper JSON + raw, err := obj.MarshalJSON() + if err != nil { + return err + } + return json.Unmarshal(raw, &target) +} diff --git a/vendor/github.com/knative/pkg/apis/duck/v1alpha1/channelable_types.go b/vendor/github.com/knative/pkg/apis/duck/v1alpha1/channelable_types.go new file mode 100644 index 00000000000..3e450b31604 --- /dev/null +++ b/vendor/github.com/knative/pkg/apis/duck/v1alpha1/channelable_types.go @@ -0,0 +1,96 @@ +/* +Copyright 2018 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/knative/pkg/apis/duck" +) + +// Channelable is the schema for the channelable portion of the spec +// section of the resource. +type Channelable struct { + // TODO: What is actually required here for Channel spec. + // This is the list of subscriptions for this channel. + Subscribers []ChannelSubscriberSpec `json:"subscribers,omitempty"` +} + +// ChannelSubscriberSpec defines a single subscriber to a Channel. +// CallableDomain is the endpoint for the call +// SinkableDomain is the endpoint for the result +// One of them must be present +type ChannelSubscriberSpec struct { + // +optional + CallableDomain string `json:"callableDomain,omitempty"` + // +optional + SinkableDomain string `json:"sinkableDomain,omitempty"` +} + +// Implementations can verify that they implement Channelable via: +var _ = duck.VerifyType(&Channel{}, &Channelable{}) + +// Channelable is an Implementable "duck type". +var _ duck.Implementable = (*Channelable)(nil) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Channel is a skeleton type wrapping Channelable in the manner we expect +// resource writers defining compatible resources to embed it. We will +// typically use this type to deserialize Channelable ObjectReferences and +// access the Channelable data. This is not a real resource. +type Channel struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // ChannelSpec is the part where Channelable object is + // configured as to be compatible with Channelable contract. + Spec ChannelSpec `json:"spec"` +} + +// ChannelSpec shows how we expect folks to embed Channelable in +// their Spec field. +type ChannelSpec struct { + Channelable *Channelable `json:"channelable,omitempty"` +} + +// In order for Channelable to be Implementable, Channel must be Populatable. +var _ duck.Populatable = (*Channel)(nil) + +// GetFullType implements duck.Implementable +func (_ *Channelable) GetFullType() duck.Populatable { + return &Channel{} +} + +// Populate implements duck.Populatable +func (t *Channel) Populate() { + t.Spec.Channelable = &Channelable{ + // Populate ALL fields + Subscribers: []ChannelSubscriberSpec{{"call1", "sink2"}, {"call2", "sink2"}}, + } +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ChannelList is a list of Channel resources +type ChannelList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []Channel `json:"items"` +} diff --git a/vendor/github.com/knative/pkg/apis/duck/v1alpha1/legacy_targetable_types.go b/vendor/github.com/knative/pkg/apis/duck/v1alpha1/legacy_targetable_types.go new file mode 100644 index 00000000000..f36b74e997b --- /dev/null +++ b/vendor/github.com/knative/pkg/apis/duck/v1alpha1/legacy_targetable_types.go @@ -0,0 +1,88 @@ +/* +Copyright 2018 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/knative/pkg/apis/duck" +) + +// LegacyTargetable left around until we migrate to Targetable in the +// dependent resources. Targetable has more structure in the way it +// defines the fields. LegacyTargetable only assumed a single string +// in the Status field and we're moving towards defining proper structs +// under Status rather than strings. +// This is to support existing resources until they migrate. +// +// Do not use this for anything new, use Targetable + +// LegacyTargetable is the old schema for the targetable portion +// of the payload +// +// For new resources use Targetable. +type LegacyTargetable struct { + DomainInternal string `json:"domainInternal,omitempty"` +} + +// Implementations can verify that they implement LegacyTargetable via: +var _ = duck.VerifyType(&LegacyTarget{}, &LegacyTargetable{}) + +// LegacyTargetable is an Implementable "duck type". +var _ duck.Implementable = (*LegacyTargetable)(nil) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// LegacyTarget is a skeleton type wrapping LegacyTargetable in the manner we +// want to support unless they get migrated into supporting Legacy. +// We will typically use this type to deserialize LegacyTargetable +// ObjectReferences and access the LegacyTargetable data. This is not a +// real resource. +// ** Do not use this for any new resources ** +type LegacyTarget struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Status LegacyTargetable `json:"status"` +} + +// In order for LegacyTargetable to be Implementable, LegacyTarget must be Populatable. +var _ duck.Populatable = (*LegacyTarget)(nil) + +// GetFullType implements duck.Implementable +func (_ *LegacyTargetable) GetFullType() duck.Populatable { + return &LegacyTarget{} +} + +// Populate implements duck.Populatable +func (t *LegacyTarget) Populate() { + t.Status = LegacyTargetable{ + // Populate ALL fields + DomainInternal: "this is not empty", + } +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// LegacyTargetList is a list of LegacyTarget resources +type LegacyTargetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []LegacyTarget `json:"items"` +} diff --git a/vendor/github.com/knative/pkg/apis/duck/v1alpha1/sinkable_types.go b/vendor/github.com/knative/pkg/apis/duck/v1alpha1/sinkable_types.go new file mode 100644 index 00000000000..d7027d8bcf8 --- /dev/null +++ b/vendor/github.com/knative/pkg/apis/duck/v1alpha1/sinkable_types.go @@ -0,0 +1,86 @@ +/* +Copyright 2018 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/knative/pkg/apis/duck" +) + +// Sinkable is very similar concept as Targetable. However, at the +// transport level they have different contracts and hence Sinkable +// and Targetable are two distinct resources. + +// Sinkable is the schema for the sinkable portion of the payload +type Sinkable struct { + DomainInternal string `json:"domainInternal,omitempty"` +} + +// Implementations can verify that they implement Sinkable via: +var _ = duck.VerifyType(&Sink{}, &Sinkable{}) + +// Sinkable is an Implementable "duck type". +var _ duck.Implementable = (*Sinkable)(nil) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Sink is a skeleton type wrapping Sinkable in the manner we expect +// resource writers defining compatible resources to embed it. We will +// typically use this type to deserialize Sinkable ObjectReferences and +// access the Sinkable data. This is not a real resource. +type Sink struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Status SinkStatus `json:"status"` +} + +// SinkStatus shows how we expect folks to embed Sinkable in +// their Status field. +type SinkStatus struct { + Sinkable *Sinkable `json:"sinkable,omitempty"` +} + +// In order for Sinkable to be Implementable, Sink must be Populatable. +var _ duck.Populatable = (*Sink)(nil) + +// GetFullType implements duck.Implementable +func (_ *Sinkable) GetFullType() duck.Populatable { + return &Sink{} +} + +// Populate implements duck.Populatable +func (t *Sink) Populate() { + t.Status = SinkStatus{ + &Sinkable{ + // Populate ALL fields + DomainInternal: "this is not empty", + }, + } +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// SinkList is a list of Sink resources +type SinkList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []Sink `json:"items"` +} diff --git a/vendor/github.com/knative/pkg/apis/duck/v1alpha1/subscribable_types.go b/vendor/github.com/knative/pkg/apis/duck/v1alpha1/subscribable_types.go index 3f295344c81..f037780f27b 100644 --- a/vendor/github.com/knative/pkg/apis/duck/v1alpha1/subscribable_types.go +++ b/vendor/github.com/knative/pkg/apis/duck/v1alpha1/subscribable_types.go @@ -17,19 +17,25 @@ limitations under the License. package v1alpha1 import ( + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/knative/pkg/apis/duck" ) -// Subscribable is the schema for the subscribable portion of the payload +// Subscribable is the schema for the subscribable portion of the payload. +// It is a reference to actual object that implements Channelable duck +// type. type Subscribable struct { - // TODO(vaikas): Give me a schema! - Field string `json:"field,omitempty"` + // Channelable is a reference to the actual resource + // that provides the ability to perform Subscription capabilities. + // This may point to object itself (for example Channel) or to another + // object providing the actual capabilities.. + Channelable corev1.ObjectReference `json:"channelable,omitempty"` } // Implementations can verify that they implement Subscribable via: -var _ = duck.VerifyType(&Topic{}, &Subscribable{}) +var _ = duck.VerifyType(&Subscription{}, &Subscribable{}) // Subscribable is an Implementable "duck type". var _ duck.Implementable = (*Subscribable)(nil) @@ -37,45 +43,55 @@ var _ duck.Implementable = (*Subscribable)(nil) // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// Topic is a skeleton type wrapping Subscribable in the manner we expect -// resource writers defining compatible resources to embed it. We will -// typically use this type to deserialize Subscribable ObjectReferences and -// access the Subscribable data. This is not a real resource. -type Topic struct { +// Subscription is a skeleton type wrapping the notion that this object +// can be subscribed to. SubscriptionStatus provides the reference +// (in a form of Subscribable) to the object that you can actually create +// a subscription to. +// We will typically use this type to deserialize Subscription objects +// to access the Subscripion data. This is not a real resource. +type Subscription struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Status TopicStatus `json:"status"` + // SubscriptionStatus is the part of the Status where a Subscribable + // object points to the underlying Channelable object that fullfills + // the SubscribableSpec contract. Note that this can be a self-link + // for example for concrete Channel implementations. + Status SubscriptionStatus `json:"status"` } -// TopicStatus shows how we expect folks to embed Subscribable in +// SubscriptionStatus shows how we expect folks to embed Subscribable in // their Status field. -type TopicStatus struct { +type SubscriptionStatus struct { Subscribable *Subscribable `json:"subscribable,omitempty"` } -// In order for Subscribable to be Implementable, Topic must be Populatable. -var _ duck.Populatable = (*Topic)(nil) +// In order for Subscribable to be Implementable, Subscribable must be Populatable. +var _ duck.Populatable = (*Subscription)(nil) // GetFullType implements duck.Implementable func (_ *Subscribable) GetFullType() duck.Populatable { - return &Topic{} + return &Subscription{} } // Populate implements duck.Populatable -func (t *Topic) Populate() { +func (t *Subscription) Populate() { t.Status.Subscribable = &Subscribable{ // Populate ALL fields - Field: "this is not empty", + Channelable: corev1.ObjectReference{ + Name: "placeholdername", + APIVersion: "apiversionhere", + Kind: "ChannelKindHere", + }, } } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// TopicList is a list of Topic resources -type TopicList struct { +// SubscribableList is a list of Subscribable resources +type SubscriptionList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata"` - Items []Topic `json:"items"` + Items []Subscription `json:"items"` } diff --git a/vendor/github.com/knative/pkg/apis/duck/v1alpha1/targettable_types.go b/vendor/github.com/knative/pkg/apis/duck/v1alpha1/targetable_types.go similarity index 86% rename from vendor/github.com/knative/pkg/apis/duck/v1alpha1/targettable_types.go rename to vendor/github.com/knative/pkg/apis/duck/v1alpha1/targetable_types.go index 99e97dcda3d..717107ceef5 100644 --- a/vendor/github.com/knative/pkg/apis/duck/v1alpha1/targettable_types.go +++ b/vendor/github.com/knative/pkg/apis/duck/v1alpha1/targetable_types.go @@ -22,10 +22,13 @@ import ( "github.com/knative/pkg/apis/duck" ) +// Targetable is very similar concept as Sinkable. However, at the +// transport level they have different contracts and hence Sinkable +// and Targetable are two distinct resources. + // Targetable is the schema for the targetable portion of the payload type Targetable struct { - // TODO(vaikas): Give me a schema! - Field string `json:"field,omitempty"` + DomainInternal string `json:"domainInternal,omitempty"` } // Implementations can verify that they implement Targetable via: @@ -64,9 +67,11 @@ func (_ *Targetable) GetFullType() duck.Populatable { // Populate implements duck.Populatable func (t *Target) Populate() { - t.Status.Targetable = &Targetable{ - // Populate ALL fields - Field: "this is not empty", + t.Status = TargetStatus{ + &Targetable{ + // Populate ALL fields + DomainInternal: "this is not empty", + }, } } diff --git a/vendor/github.com/knative/pkg/apis/duck/v1alpha1/zz_generated.deepcopy.go b/vendor/github.com/knative/pkg/apis/duck/v1alpha1/zz_generated.deepcopy.go index 9cb3f7268ce..203f1ec5b31 100644 --- a/vendor/github.com/knative/pkg/apis/duck/v1alpha1/zz_generated.deepcopy.go +++ b/vendor/github.com/knative/pkg/apis/duck/v1alpha1/zz_generated.deepcopy.go @@ -24,6 +24,124 @@ import ( 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 *Channel) DeepCopyInto(out *Channel) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Channel. +func (in *Channel) DeepCopy() *Channel { + if in == nil { + return nil + } + out := new(Channel) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Channel) 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 *ChannelList) DeepCopyInto(out *ChannelList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Channel, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelList. +func (in *ChannelList) DeepCopy() *ChannelList { + if in == nil { + return nil + } + out := new(ChannelList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ChannelList) 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 *ChannelSpec) DeepCopyInto(out *ChannelSpec) { + *out = *in + if in.Channelable != nil { + in, out := &in.Channelable, &out.Channelable + *out = new(Channelable) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelSpec. +func (in *ChannelSpec) DeepCopy() *ChannelSpec { + if in == nil { + return nil + } + out := new(ChannelSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ChannelSubscriberSpec) DeepCopyInto(out *ChannelSubscriberSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelSubscriberSpec. +func (in *ChannelSubscriberSpec) DeepCopy() *ChannelSubscriberSpec { + if in == nil { + return nil + } + out := new(ChannelSubscriberSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Channelable) DeepCopyInto(out *Channelable) { + *out = *in + if in.Subscribers != nil { + in, out := &in.Subscribers, &out.Subscribers + *out = make([]ChannelSubscriberSpec, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Channelable. +func (in *Channelable) DeepCopy() *Channelable { + if in == nil { + return nil + } + out := new(Channelable) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in @@ -223,23 +341,83 @@ func (in *KResourceStatus) DeepCopy() *KResourceStatus { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Subscribable) DeepCopyInto(out *Subscribable) { +func (in *LegacyTarget) DeepCopyInto(out *LegacyTarget) { *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Status = in.Status return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subscribable. -func (in *Subscribable) DeepCopy() *Subscribable { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LegacyTarget. +func (in *LegacyTarget) DeepCopy() *LegacyTarget { if in == nil { return nil } - out := new(Subscribable) + out := new(LegacyTarget) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LegacyTarget) 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 *Target) DeepCopyInto(out *Target) { +func (in *LegacyTargetList) DeepCopyInto(out *LegacyTargetList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LegacyTarget, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LegacyTargetList. +func (in *LegacyTargetList) DeepCopy() *LegacyTargetList { + if in == nil { + return nil + } + out := new(LegacyTargetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LegacyTargetList) 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 *LegacyTargetable) DeepCopyInto(out *LegacyTargetable) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LegacyTargetable. +func (in *LegacyTargetable) DeepCopy() *LegacyTargetable { + if in == nil { + return nil + } + out := new(LegacyTargetable) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Sink) DeepCopyInto(out *Sink) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -247,18 +425,18 @@ func (in *Target) DeepCopyInto(out *Target) { return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Target. -func (in *Target) DeepCopy() *Target { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Sink. +func (in *Sink) DeepCopy() *Sink { if in == nil { return nil } - out := new(Target) + out := new(Sink) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Target) DeepCopyObject() runtime.Object { +func (in *Sink) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -266,13 +444,13 @@ func (in *Target) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TargetList) DeepCopyInto(out *TargetList) { +func (in *SinkList) DeepCopyInto(out *SinkList) { *out = *in out.TypeMeta = in.TypeMeta out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]Target, len(*in)) + *out = make([]Sink, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -280,18 +458,18 @@ func (in *TargetList) DeepCopyInto(out *TargetList) { return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetList. -func (in *TargetList) DeepCopy() *TargetList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SinkList. +func (in *SinkList) DeepCopy() *SinkList { if in == nil { return nil } - out := new(TargetList) + out := new(SinkList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TargetList) DeepCopyObject() runtime.Object { +func (in *SinkList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -299,44 +477,61 @@ func (in *TargetList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TargetStatus) DeepCopyInto(out *TargetStatus) { +func (in *SinkStatus) DeepCopyInto(out *SinkStatus) { *out = *in - if in.Targetable != nil { - in, out := &in.Targetable, &out.Targetable - *out = new(Targetable) + if in.Sinkable != nil { + in, out := &in.Sinkable, &out.Sinkable + *out = new(Sinkable) **out = **in } return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetStatus. -func (in *TargetStatus) DeepCopy() *TargetStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SinkStatus. +func (in *SinkStatus) DeepCopy() *SinkStatus { if in == nil { return nil } - out := new(TargetStatus) + out := new(SinkStatus) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Targetable) DeepCopyInto(out *Targetable) { +func (in *Sinkable) DeepCopyInto(out *Sinkable) { *out = *in return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Targetable. -func (in *Targetable) DeepCopy() *Targetable { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Sinkable. +func (in *Sinkable) DeepCopy() *Sinkable { if in == nil { return nil } - out := new(Targetable) + out := new(Sinkable) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Subscribable) DeepCopyInto(out *Subscribable) { + *out = *in + out.Channelable = in.Channelable + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subscribable. +func (in *Subscribable) DeepCopy() *Subscribable { + if in == nil { + return nil + } + out := new(Subscribable) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Topic) DeepCopyInto(out *Topic) { +func (in *Subscription) DeepCopyInto(out *Subscription) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -344,18 +539,18 @@ func (in *Topic) DeepCopyInto(out *Topic) { return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Topic. -func (in *Topic) DeepCopy() *Topic { +// 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(Topic) + out := new(Subscription) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Topic) DeepCopyObject() runtime.Object { +func (in *Subscription) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -363,13 +558,13 @@ func (in *Topic) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TopicList) DeepCopyInto(out *TopicList) { +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([]Topic, len(*in)) + *out = make([]Subscription, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -377,18 +572,18 @@ func (in *TopicList) DeepCopyInto(out *TopicList) { return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TopicList. -func (in *TopicList) DeepCopy() *TopicList { +// 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(TopicList) + out := new(SubscriptionList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TopicList) DeepCopyObject() runtime.Object { +func (in *SubscriptionList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -396,7 +591,7 @@ func (in *TopicList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TopicStatus) DeepCopyInto(out *TopicStatus) { +func (in *SubscriptionStatus) DeepCopyInto(out *SubscriptionStatus) { *out = *in if in.Subscribable != nil { in, out := &in.Subscribable, &out.Subscribable @@ -406,12 +601,109 @@ func (in *TopicStatus) DeepCopyInto(out *TopicStatus) { return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TopicStatus. -func (in *TopicStatus) DeepCopy() *TopicStatus { +// 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 +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Target) DeepCopyInto(out *Target) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Target. +func (in *Target) DeepCopy() *Target { + if in == nil { + return nil + } + out := new(Target) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Target) 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 *TargetList) DeepCopyInto(out *TargetList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Target, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetList. +func (in *TargetList) DeepCopy() *TargetList { + if in == nil { + return nil + } + out := new(TargetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TargetList) 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 *TargetStatus) DeepCopyInto(out *TargetStatus) { + *out = *in + if in.Targetable != nil { + in, out := &in.Targetable, &out.Targetable + *out = new(Targetable) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetStatus. +func (in *TargetStatus) DeepCopy() *TargetStatus { + if in == nil { + return nil + } + out := new(TargetStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Targetable) DeepCopyInto(out *Targetable) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Targetable. +func (in *Targetable) DeepCopy() *Targetable { if in == nil { return nil } - out := new(TopicStatus) + out := new(Targetable) in.DeepCopyInto(out) return out } diff --git a/vendor/k8s.io/client-go/dynamic/fake/simple.go b/vendor/k8s.io/client-go/dynamic/fake/simple.go new file mode 100644 index 00000000000..a71cec50ea4 --- /dev/null +++ b/vendor/k8s.io/client-go/dynamic/fake/simple.go @@ -0,0 +1,363 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fake + +import ( + "strings" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/testing" +) + +func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient { + codecs := serializer.NewCodecFactory(scheme) + o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &FakeDynamicClient{} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type FakeDynamicClient struct { + testing.Fake + scheme *runtime.Scheme +} + +type dynamicResourceClient struct { + client *FakeDynamicClient + namespace string + resource schema.GroupVersionResource +} + +var _ dynamic.Interface = &FakeDynamicClient{} + +func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface { + return &dynamicResourceClient{client: c, resource: resource} +} + +func (c *dynamicResourceClient) Namespace(ns string) dynamic.ResourceInterface { + ret := *c + ret.namespace = ns + return &ret +} + +func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) { + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootCreateAction(c.resource, obj), obj) + + case len(c.namespace) == 0 && len(subresources) > 0: + accessor, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + name := accessor.GetName() + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), obj), obj) + + case len(c.namespace) > 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj) + + case len(c.namespace) > 0 && len(subresources) > 0: + accessor, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + name := accessor.GetName() + uncastRet, err = c.client.Fake. + Invokes(testing.NewCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), c.namespace, obj), obj) + + } + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + + ret := &unstructured.Unstructured{} + if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { + return nil, err + } + return ret, err +} + +func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) { + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootUpdateAction(c.resource, obj), obj) + + case len(c.namespace) == 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), obj), obj) + + case len(c.namespace) > 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj) + + case len(c.namespace) > 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, obj), obj) + + } + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + + ret := &unstructured.Unstructured{} + if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { + return nil, err + } + return ret, err +} + +func (c *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(c.resource, "status", obj), obj) + + case len(c.namespace) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj) + + } + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + + ret := &unstructured.Unstructured{} + if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { + return nil, err + } + return ret, err +} + +func (c *dynamicResourceClient) Delete(name string, opts *metav1.DeleteOptions, subresources ...string) error { + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + _, err = c.client.Fake. + Invokes(testing.NewRootDeleteAction(c.resource, name), &metav1.Status{Status: "dynamic delete fail"}) + + case len(c.namespace) == 0 && len(subresources) > 0: + _, err = c.client.Fake. + Invokes(testing.NewRootDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic delete fail"}) + + case len(c.namespace) > 0 && len(subresources) == 0: + _, err = c.client.Fake. + Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic delete fail"}) + + case len(c.namespace) > 0 && len(subresources) > 0: + _, err = c.client.Fake. + Invokes(testing.NewDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, name), &metav1.Status{Status: "dynamic delete fail"}) + } + + return err +} + +func (c *dynamicResourceClient) DeleteCollection(opts *metav1.DeleteOptions, listOptions metav1.ListOptions) error { + var err error + switch { + case len(c.namespace) == 0: + action := testing.NewRootDeleteCollectionAction(c.resource, listOptions) + _, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"}) + + case len(c.namespace) > 0: + action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions) + _, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"}) + + } + + return err +} + +func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) { + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootGetAction(c.resource, name), &metav1.Status{Status: "dynamic get fail"}) + + case len(c.namespace) == 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootGetSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic get fail"}) + + case len(c.namespace) > 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic get fail"}) + + case len(c.namespace) > 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewGetSubresourceAction(c.resource, c.namespace, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic get fail"}) + } + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + + ret := &unstructured.Unstructured{} + if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { + return nil, err + } + return ret, err +} + +func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { + var obj runtime.Object + var err error + switch { + case len(c.namespace) == 0: + obj, err = c.client.Fake. + Invokes(testing.NewRootListAction(c.resource, schema.GroupVersionKind{Version: "v1", Kind: "List"}, opts), &metav1.Status{Status: "dynamic list fail"}) + + case len(c.namespace) > 0: + obj, err = c.client.Fake. + Invokes(testing.NewListAction(c.resource, schema.GroupVersionKind{Version: "v1", Kind: "List"}, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"}) + + } + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + + retUnstructured := &unstructured.Unstructured{} + if err := c.client.scheme.Convert(obj, retUnstructured, nil); err != nil { + return nil, err + } + entireList, err := retUnstructured.ToList() + if err != nil { + return nil, err + } + + list := &unstructured.UnstructuredList{} + for _, item := range entireList.Items { + metadata, err := meta.Accessor(item) + if err != nil { + return nil, err + } + if label.Matches(labels.Set(metadata.GetLabels())) { + list.Items = append(list.Items, item) + } + } + return list, nil +} + +func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) { + switch { + case len(c.namespace) == 0: + return c.client.Fake. + InvokesWatch(testing.NewRootWatchAction(c.resource, opts)) + + case len(c.namespace) > 0: + return c.client.Fake. + InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts)) + + } + + panic("math broke") +} + +func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (*unstructured.Unstructured, error) { + var uncastRet runtime.Object + var err error + switch { + case len(c.namespace) == 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootPatchAction(c.resource, name, data), &metav1.Status{Status: "dynamic patch fail"}) + + case len(c.namespace) == 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, data, subresources...), &metav1.Status{Status: "dynamic patch fail"}) + + case len(c.namespace) > 0 && len(subresources) == 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewPatchAction(c.resource, c.namespace, name, data), &metav1.Status{Status: "dynamic patch fail"}) + + case len(c.namespace) > 0 && len(subresources) > 0: + uncastRet, err = c.client.Fake. + Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, data, subresources...), &metav1.Status{Status: "dynamic patch fail"}) + + } + + if err != nil { + return nil, err + } + if uncastRet == nil { + return nil, err + } + + ret := &unstructured.Unstructured{} + if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil { + return nil, err + } + return ret, err +}