From 0ba84799911c22eb4e0f7da4b2b56ac859e7f401 Mon Sep 17 00:00:00 2001 From: Lionel Villard Date: Wed, 15 Jul 2020 17:06:14 -0400 Subject: [PATCH 1/4] pingsource v1beta1 api --- pkg/apis/sources/v1beta1/ping_conversion.go | 34 + .../sources/v1beta1/ping_conversion_test.go | 34 + pkg/apis/sources/v1beta1/ping_defaults.go | 35 + .../sources/v1beta1/ping_defaults_test.go | 67 ++ pkg/apis/sources/v1beta1/ping_lifecycle.go | 117 +++ .../sources/v1beta1/ping_lifecycle_test.go | 687 ++++++++++++++++++ pkg/apis/sources/v1beta1/ping_types.go | 101 +++ pkg/apis/sources/v1beta1/ping_types_test.go | 28 + pkg/apis/sources/v1beta1/ping_validation.go | 58 ++ .../sources/v1beta1/ping_validation_test.go | 124 ++++ 10 files changed, 1285 insertions(+) create mode 100644 pkg/apis/sources/v1beta1/ping_conversion.go create mode 100644 pkg/apis/sources/v1beta1/ping_conversion_test.go create mode 100644 pkg/apis/sources/v1beta1/ping_defaults.go create mode 100644 pkg/apis/sources/v1beta1/ping_defaults_test.go create mode 100644 pkg/apis/sources/v1beta1/ping_lifecycle.go create mode 100644 pkg/apis/sources/v1beta1/ping_lifecycle_test.go create mode 100644 pkg/apis/sources/v1beta1/ping_types.go create mode 100644 pkg/apis/sources/v1beta1/ping_types_test.go create mode 100644 pkg/apis/sources/v1beta1/ping_validation.go create mode 100644 pkg/apis/sources/v1beta1/ping_validation_test.go diff --git a/pkg/apis/sources/v1beta1/ping_conversion.go b/pkg/apis/sources/v1beta1/ping_conversion.go new file mode 100644 index 00000000000..d33d937798c --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_conversion.go @@ -0,0 +1,34 @@ +/* +Copyright 2020 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 v1beta1 + +import ( + "context" + "fmt" + + "knative.dev/pkg/apis" +) + +// ConvertTo implements apis.Convertible +func (source *PingSource) ConvertTo(ctx context.Context, sink apis.Convertible) error { + return fmt.Errorf("v1beta1 is the highest known version, got: %T", sink) +} + +// ConvertFrom implements apis.Convertible +func (sink *PingSource) ConvertFrom(ctx context.Context, source apis.Convertible) error { + return fmt.Errorf("v1beta1 is the highest known version, got: %T", source) +} diff --git a/pkg/apis/sources/v1beta1/ping_conversion_test.go b/pkg/apis/sources/v1beta1/ping_conversion_test.go new file mode 100644 index 00000000000..189ab86afdc --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_conversion_test.go @@ -0,0 +1,34 @@ +/* +Copyright 2020 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 v1beta1 + +import ( + "context" + "testing" +) + +func TestPingSourceConversionBadType(t *testing.T) { + good, bad := &PingSource{}, &PingSource{} + + if err := good.ConvertTo(context.Background(), bad); err == nil { + t.Errorf("ConvertTo() = %#v, wanted error", bad) + } + + if err := good.ConvertFrom(context.Background(), bad); err == nil { + t.Errorf("ConvertFrom() = %#v, wanted error", good) + } +} diff --git a/pkg/apis/sources/v1beta1/ping_defaults.go b/pkg/apis/sources/v1beta1/ping_defaults.go new file mode 100644 index 00000000000..9fef43260f6 --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_defaults.go @@ -0,0 +1,35 @@ +/* +Copyright 2020 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 v1beta1 + +import ( + "context" +) + +const ( + defaultSchedule = "* * * * *" +) + +func (s *PingSource) SetDefaults(ctx context.Context) { + s.Spec.SetDefaults(ctx) +} + +func (ss *PingSourceSpec) SetDefaults(ctx context.Context) { + if ss.Schedule == "" { + ss.Schedule = defaultSchedule + } +} diff --git a/pkg/apis/sources/v1beta1/ping_defaults_test.go b/pkg/apis/sources/v1beta1/ping_defaults_test.go new file mode 100644 index 00000000000..e97160fc2d7 --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_defaults_test.go @@ -0,0 +1,67 @@ +/* +Copyright 2020 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 v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestPingSourceSetDefaults(t *testing.T) { + testCases := map[string]struct { + initial PingSource + expected PingSource + }{ + "nil": { + expected: PingSource{ + Spec: PingSourceSpec{ + Schedule: defaultSchedule, + }, + }, + }, + "empty": { + initial: PingSource{}, + expected: PingSource{ + Spec: PingSourceSpec{ + Schedule: defaultSchedule, + }, + }, + }, + "with schedule": { + initial: PingSource{ + Spec: PingSourceSpec{ + Schedule: "1 2 3 4 5", + }, + }, + expected: PingSource{ + Spec: PingSourceSpec{ + Schedule: "1 2 3 4 5", + }, + }, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + tc.initial.SetDefaults(context.TODO()) + if diff := cmp.Diff(tc.expected, tc.initial); diff != "" { + t.Fatalf("Unexpected defaults (-want, +got): %s", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta1/ping_lifecycle.go b/pkg/apis/sources/v1beta1/ping_lifecycle.go new file mode 100644 index 00000000000..ad934327892 --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_lifecycle.go @@ -0,0 +1,117 @@ +/* +Copyright 2020 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 v1beta1 + +import ( + "fmt" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/pkg/apis" +) + +const ( + // PingSourceConditionReady has status True when the PingSource is ready to send events. + PingSourceConditionReady = apis.ConditionReady + + // PingSourceConditionSinkProvided has status True when the PingSource has been configured with a sink target. + PingSourceConditionSinkProvided apis.ConditionType = "SinkProvided" + + // PingSourceConditionDeployed has status True when the PingSource has had it's receive adapter deployment created. + PingSourceConditionDeployed apis.ConditionType = "Deployed" +) + +var PingSourceCondSet = apis.NewLivingConditionSet( + PingSourceConditionSinkProvided, + PingSourceConditionDeployed) + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (*PingSource) GetConditionSet() apis.ConditionSet { + return PingSourceCondSet +} + +// PingSourceSource returns the PingSource CloudEvent source. +func PingSourceSource(namespace, name string) string { + return fmt.Sprintf("/apis/v1/namespaces/%s/pingsources/%s", namespace, name) +} + +// GetUntypedSpec returns the spec of the PingSource. +func (s *PingSource) GetUntypedSpec() interface{} { + return s.Spec +} + +// GetGroupVersionKind returns the GroupVersionKind. +func (s *PingSource) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("PingSource") +} + +// GetCondition returns the condition currently associated with the given type, or nil. +func (s *PingSourceStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return PingSourceCondSet.Manage(s).GetCondition(t) +} + +// GetTopLevelCondition returns the top level Condition. +func (ps *PingSourceStatus) GetTopLevelCondition() *apis.Condition { + return PingSourceCondSet.Manage(ps).GetTopLevelCondition() +} + +// IsReady returns true if the resource is ready overall. +func (s *PingSourceStatus) IsReady() bool { + return PingSourceCondSet.Manage(s).IsHappy() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (s *PingSourceStatus) InitializeConditions() { + PingSourceCondSet.Manage(s).InitializeConditions() +} + +// MarkSink sets the condition that the source has a sink configured. +func (s *PingSourceStatus) MarkSink(uri *apis.URL) { + s.SinkURI = uri + if uri != nil { + PingSourceCondSet.Manage(s).MarkTrue(PingSourceConditionSinkProvided) + } else { + PingSourceCondSet.Manage(s).MarkFalse(PingSourceConditionSinkProvided, "SinkEmpty", "Sink has resolved to empty.") + } +} + +// MarkNoSink sets the condition that the source does not have a sink configured. +func (s *PingSourceStatus) MarkNoSink(reason, messageFormat string, messageA ...interface{}) { + PingSourceCondSet.Manage(s).MarkFalse(PingSourceConditionSinkProvided, reason, messageFormat, messageA...) +} + +// PropagateDeploymentAvailability uses the availability of the provided Deployment to determine if +// PingSourceConditionDeployed should be marked as true or false. +func (s *PingSourceStatus) PropagateDeploymentAvailability(d *appsv1.Deployment) { + deploymentAvailableFound := false + for _, cond := range d.Status.Conditions { + if cond.Type == appsv1.DeploymentAvailable { + deploymentAvailableFound = true + if cond.Status == corev1.ConditionTrue { + PingSourceCondSet.Manage(s).MarkTrue(PingSourceConditionDeployed) + } else if cond.Status == corev1.ConditionFalse { + PingSourceCondSet.Manage(s).MarkFalse(PingSourceConditionDeployed, cond.Reason, cond.Message) + } else if cond.Status == corev1.ConditionUnknown { + PingSourceCondSet.Manage(s).MarkUnknown(PingSourceConditionDeployed, cond.Reason, cond.Message) + } + } + } + if !deploymentAvailableFound { + PingSourceCondSet.Manage(s).MarkUnknown(PingSourceConditionDeployed, "DeploymentUnavailable", "The Deployment '%s' is unavailable.", d.Name) + } +} diff --git a/pkg/apis/sources/v1beta1/ping_lifecycle_test.go b/pkg/apis/sources/v1beta1/ping_lifecycle_test.go new file mode 100644 index 00000000000..b1aac55567b --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_lifecycle_test.go @@ -0,0 +1,687 @@ +/* +Copyright 2020 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 v1beta1 + +import ( + "testing" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "knative.dev/pkg/apis" +) + +func TestPingSourceGetConditionSet(t *testing.T) { + r := &PingSource{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestPingSource_GetGroupVersionKind(t *testing.T) { + src := PingSource{} + gvk := src.GetGroupVersionKind() + + if gvk.Kind != "PingSource" { + t.Errorf("Should be PingSource.") + } +} + +func TestPingSource_PingSourceSource(t *testing.T) { + cePingSource := PingSourceSource("ns1", "job1") + + if cePingSource != "/apis/v1/namespaces/ns1/pingsources/job1" { + t.Errorf("Should be '/apis/v1/namespaces/ns1/pingsources/job1'") + } +} + +func TestPingSourceStatusIsReady(t *testing.T) { + exampleUri, _ := apis.ParseURL("uri://example") + + tests := []struct { + name string + s *PingSourceStatus + wantConditionStatus corev1.ConditionStatus + want bool + }{{ + name: "uninitialized", + s: &PingSourceStatus{}, + want: false, + }, { + name: "initialized", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + + s.MarkSink(exampleUri) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark schedule", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkEventType() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark schedule and sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark schedule, sink and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }, { + name: "mark schedule, sink and unavailable deployment", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.UnavailableDeployment()) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark schedule, sink and unknown deployment", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.UnknownDeployment()) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark schedule, sink, deployed, and event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.MarkEventType() + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }, { + name: "mark schedule, sink and deployed then not deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.PropagateDeploymentAvailability(&appsv1.Deployment{}) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark schedule, sink, deployed and event types then no event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.MarkNoEventType("Testing", "") + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }, { + name: "mark schedule validated, sink empty and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(nil) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark schedule validated, sink empty and deployed then sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(nil) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.MarkSink(exampleUri) + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.wantConditionStatus != "" { + gotConditionStatus := test.s.GetTopLevelCondition().Status + if gotConditionStatus != test.wantConditionStatus { + t.Errorf("unexpected condition status: want %v, got %v", test.wantConditionStatus, gotConditionStatus) + } + } + got := test.s.IsReady() + if got != test.want { + t.Errorf("unexpected readiness: want %v, got %v", test.want, got) + } + }) + } +} + +func TestPingSourceStatusGetTopLevelCondition(t *testing.T) { + exampleUri, _ := apis.ParseURL("uri://example") + + tests := []struct { + name string + s *PingSourceStatus + want *apis.Condition + }{{ + name: "uninitialized", + s: &PingSourceStatus{}, + want: nil, + }, { + name: "initialized", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSink(exampleUri) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark schedule", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkEventType() + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark sink and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark schedule and sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark schedule, sink and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark schedule, sink, deployed, and event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.MarkEventType() + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark schedule, sink and deployed then not deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.PropagateDeploymentAvailability(&appsv1.Deployment{}) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Reason: "DeploymentUnavailable", + Status: corev1.ConditionUnknown, + Message: "The Deployment '' is unavailable.", + }, + }, { + name: "mark schedule, sink, deployed and event types then no event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.MarkNoEventType("Testing", "") + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark schedule validated, sink empty and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(nil) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Reason: "SinkEmpty", + Status: corev1.ConditionFalse, + Message: "Sink has resolved to empty.", + }, + }, { + name: "mark schedule validated, sink empty and deployed then sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(nil) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.MarkSink(exampleUri) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.s.GetTopLevelCondition() + ignoreTime := cmpopts.IgnoreFields(apis.Condition{}, + "LastTransitionTime", "Severity") + if diff := cmp.Diff(test.want, got, ignoreTime); diff != "" { + t.Errorf("unexpected condition (-want, +got) = %v", diff) + } + }) + } +} + +func TestPingSourceStatusGetCondition(t *testing.T) { + exampleUri, _ := apis.ParseURL("uri://example") + tests := []struct { + name string + s *PingSourceStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "uninitialized", + s: &PingSourceStatus{}, + condQuery: PingSourceConditionReady, + want: nil, + }, { + name: "initialized", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSink(exampleUri) + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark schedule", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark schedule, sink and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark schedule, sink, deployed, and event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.MarkEventType() + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark schedule, sink and deployed then no sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.MarkNoSink("Testing", "hi%s", "") + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionFalse, + Reason: "Testing", + Message: "hi", + }, + }, { + name: "mark schedule, sink and deployed then invalid schedule", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.MarkInvalidSchedule("Testing", "hi%s", "") + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionFalse, + Reason: "Testing", + Message: "hi", + }, + }, { + name: "mark schedule, sink and deployed then deploying", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.PropagateDeploymentAvailability(&appsv1.Deployment{}) + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + Reason: "DeploymentUnavailable", + Message: "The Deployment '' is unavailable.", + }, + }, { + name: "mark schedule, sink and deployed then not deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.PropagateDeploymentAvailability(&appsv1.Deployment{}) + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + Reason: "DeploymentUnavailable", + Message: "The Deployment '' is unavailable.", + }, + }, { + name: "mark schedule, sink, deployed and event types, then no event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.MarkEventType() + s.MarkNoEventType("Testing", "hi") + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark schedule, sink empty and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(nil) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionFalse, + Reason: "SinkEmpty", + Message: "Sink has resolved to empty.", + }, + }, { + name: "mark schedule, sink empty and deployed then sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(nil) + s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) + s.MarkSink(exampleUri) + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.s.GetCondition(test.condQuery) + ignoreTime := cmpopts.IgnoreFields(apis.Condition{}, + "LastTransitionTime", "Severity") + if diff := cmp.Diff(test.want, got, ignoreTime); diff != "" { + t.Errorf("unexpected condition (-want, +got) = %v", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta1/ping_types.go b/pkg/apis/sources/v1beta1/ping_types.go new file mode 100644 index 00000000000..4582568196b --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_types.go @@ -0,0 +1,101 @@ +/* +Copyright 2020 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 v1beta1 + +import ( + "knative.dev/pkg/apis" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +genreconciler +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:defaulter-gen=true + +// PingSource is the Schema for the PingSources API. +type PingSource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec PingSourceSpec `json:"spec,omitempty"` + Status PingSourceStatus `json:"status,omitempty"` +} + +// Check the interfaces that PingSource should be implementing. +var ( + _ runtime.Object = (*PingSource)(nil) + _ kmeta.OwnerRefable = (*PingSource)(nil) + _ apis.Validatable = (*PingSource)(nil) + _ apis.Defaultable = (*PingSource)(nil) + _ apis.HasSpec = (*PingSource)(nil) + _ duckv1.KRShaped = (*PingSource)(nil) +) + +// PingSourceSpec defines the desired state of the PingSource. +type PingSourceSpec struct { + // inherits duck/v1 SourceSpec, which currently provides: + // * Sink - a reference to an object that will resolve to a domain name or + // a URI directly to use as the sink. + // * CloudEventOverrides - defines overrides to control the output format + // and modifications of the event sent to the sink. + duckv1.SourceSpec `json:",inline"` + + // Schedule is the cronjob schedule. Defaults to `* * * * *`. + // +optional + Schedule string `json:"schedule,omitempty"` + + // Timezone modifies the actual time relative to the specified timezone. + // Defaults to the system time zone. + // More information about time zones: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + Timezone string `json:"timezone,omitempty` + + // JsonData is json encoded data used as the body of the event posted to + // the sink. Default is empty. If set, datacontenttype will also be set + // to "application/json". + // +optional + JsonData string `json:"jsonData,omitempty"` +} + +// PingSourceStatus defines the observed state of PingSource. +type PingSourceStatus struct { + // inherits duck/v1 SourceStatus, which currently provides: + // * ObservedGeneration - the 'Generation' of the Service that was last + // processed by the controller. + // * Conditions - the latest available observations of a resource's current + // state. + // * SinkURI - the current active sink URI that has been configured for the + // Source. + duckv1.SourceStatus `json:",inline"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PingSourceList contains a list of PingSources. +type PingSourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []PingSource `json:"items"` +} + +// GetStatus retrieves the status of the PingSource. Implements the KRShaped interface. +func (p *PingSource) GetStatus() *duckv1.Status { + return &p.Status.Status +} diff --git a/pkg/apis/sources/v1beta1/ping_types_test.go b/pkg/apis/sources/v1beta1/ping_types_test.go new file mode 100644 index 00000000000..bdcadebc0f5 --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_types_test.go @@ -0,0 +1,28 @@ +/* +Copyright 2020 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 v1beta1 + +import "testing" + +func TestPingSource_GetStatus(t *testing.T) { + p := &PingSource{ + Status: PingSourceStatus{}, + } + if got, want := p.GetStatus(), &p.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} diff --git a/pkg/apis/sources/v1beta1/ping_validation.go b/pkg/apis/sources/v1beta1/ping_validation.go new file mode 100644 index 00000000000..45e2f3d0235 --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_validation.go @@ -0,0 +1,58 @@ +/* +Copyright 2020 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 v1beta1 + +import ( + "context" + + "github.com/robfig/cron/v3" + "knative.dev/pkg/apis" + + "knative.dev/eventing/pkg/apis/eventing" +) + +func (c *PingSource) Validate(ctx context.Context) *apis.FieldError { + errs := c.Spec.Validate(ctx).ViaField("spec") + return ValidateAnnotations(errs, c.Annotations) +} + +func (cs *PingSourceSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + + if _, err := cron.ParseStandard(cs.Schedule); err != nil { + fe := apis.ErrInvalidValue(cs.Schedule, "schedule") + errs = errs.Also(fe) + } + + if fe := cs.Sink.Validate(ctx); fe != nil { + errs = errs.Also(fe.ViaField("sink")) + } + return errs +} + +func ValidateAnnotations(errs *apis.FieldError, annotations map[string]string) *apis.FieldError { + if annotations != nil { + if scope, ok := annotations[eventing.ScopeAnnotationKey]; ok { + if scope != eventing.ScopeResource && scope != eventing.ScopeCluster { + iv := apis.ErrInvalidValue(scope, "") + iv.Details = "expected either 'cluster' or 'resource'" + errs = errs.Also(iv.ViaFieldKey("annotations", eventing.ScopeAnnotationKey).ViaField("metadata")) + } + } + } + return errs +} diff --git a/pkg/apis/sources/v1beta1/ping_validation_test.go b/pkg/apis/sources/v1beta1/ping_validation_test.go new file mode 100644 index 00000000000..769b1f1af0a --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_validation_test.go @@ -0,0 +1,124 @@ +/* +Copyright 2020 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 v1beta1 + +import ( + "context" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/eventing/pkg/apis/eventing" + + duckv1 "knative.dev/pkg/apis/duck/v1" + + "github.com/google/go-cmp/cmp" + "knative.dev/pkg/apis" +) + +func TestPingSourceValidation(t *testing.T) { + tests := []struct { + name string + source PingSource + want *apis.FieldError + }{{ + name: "valid spec", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: nil, + }, { + name: "empty sink", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + }, + }, + want: func() *apis.FieldError { + return apis.ErrGeneric("expected at least one, got none", "ref", "uri").ViaField("spec.sink") + }(), + }, { + name: "invalid schedule", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "2", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrInvalidValue("2", "spec.schedule") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "invalid annotation", + source: PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + eventing.ScopeAnnotationKey: "notvalid", + }, + }, + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + Namespace: "namespace", + }, + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrInvalidValue("notvalid", "metadata.annotations.[eventing.knative.dev/scope]\nexpected either 'cluster' or 'resource'") + errs = errs.Also(fe) + return errs + }(), + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.source.Validate(context.TODO()) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Errorf("ContainerSourceSpec.Validate (-want, +got) = %v", diff) + } + }) + } +} From c0127609f248be3fad873e503ef0b11ee2063e80 Mon Sep 17 00:00:00 2001 From: Lionel Villard Date: Thu, 16 Jul 2020 11:04:01 -0400 Subject: [PATCH 2/4] WIP: pingsource v1beta1 types --- .../sources/v1beta1/ping_lifecycle_test.go | 427 ----------------- pkg/apis/sources/v1beta1/ping_types.go | 3 +- pkg/apis/sources/v1beta1/ping_validation.go | 23 +- .../sources/v1beta1/ping_validation_test.go | 49 +- .../sources/v1beta1/zz_generated.deepcopy.go | 95 ++++ .../sources/v1beta1/fake/fake_pingsource.go | 140 ++++++ .../v1beta1/fake/fake_sources_client.go | 4 + .../sources/v1beta1/generated_expansion.go | 2 + .../typed/sources/v1beta1/pingsource.go | 191 ++++++++ .../typed/sources/v1beta1/sources_client.go | 5 + .../informers/externalversions/generic.go | 2 + .../sources/v1beta1/interface.go | 7 + .../sources/v1beta1/pingsource.go | 89 ++++ .../sources/v1beta1/pingsource/fake/fake.go | 40 ++ .../sources/v1beta1/pingsource/pingsource.go | 52 ++ .../sources/v1beta1/pingsource/controller.go | 142 ++++++ .../sources/v1beta1/pingsource/reconciler.go | 448 ++++++++++++++++++ .../v1beta1/pingsource/stub/controller.go | 54 +++ .../v1beta1/pingsource/stub/reconciler.go | 87 ++++ .../sources/v1beta1/expansion_generated.go | 8 + .../listers/sources/v1beta1/pingsource.go | 94 ++++ 21 files changed, 1495 insertions(+), 467 deletions(-) create mode 100644 pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_pingsource.go create mode 100644 pkg/client/clientset/versioned/typed/sources/v1beta1/pingsource.go create mode 100644 pkg/client/informers/externalversions/sources/v1beta1/pingsource.go create mode 100644 pkg/client/injection/informers/sources/v1beta1/pingsource/fake/fake.go create mode 100644 pkg/client/injection/informers/sources/v1beta1/pingsource/pingsource.go create mode 100644 pkg/client/injection/reconciler/sources/v1beta1/pingsource/controller.go create mode 100644 pkg/client/injection/reconciler/sources/v1beta1/pingsource/reconciler.go create mode 100644 pkg/client/injection/reconciler/sources/v1beta1/pingsource/stub/controller.go create mode 100644 pkg/client/injection/reconciler/sources/v1beta1/pingsource/stub/reconciler.go create mode 100644 pkg/client/listers/sources/v1beta1/pingsource.go diff --git a/pkg/apis/sources/v1beta1/ping_lifecycle_test.go b/pkg/apis/sources/v1beta1/ping_lifecycle_test.go index b1aac55567b..bbc83d67067 100644 --- a/pkg/apis/sources/v1beta1/ping_lifecycle_test.go +++ b/pkg/apis/sources/v1beta1/ping_lifecycle_test.go @@ -19,7 +19,6 @@ package v1beta1 import ( "testing" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "github.com/google/go-cmp/cmp" @@ -94,26 +93,6 @@ func TestPingSourceStatusIsReady(t *testing.T) { }(), wantConditionStatus: corev1.ConditionUnknown, want: false, - }, { - name: "mark schedule", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - return s - }(), - wantConditionStatus: corev1.ConditionUnknown, - want: false, - }, { - name: "mark event types", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkEventType() - return s - }(), - wantConditionStatus: corev1.ConditionUnknown, - want: false, }, { name: "mark sink and deployed", s: func() *PingSourceStatus { @@ -123,117 +102,6 @@ func TestPingSourceStatusIsReady(t *testing.T) { s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) return s }(), - wantConditionStatus: corev1.ConditionUnknown, - want: false, - }, { - name: "mark schedule and sink", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - return s - }(), - wantConditionStatus: corev1.ConditionUnknown, - want: false, - }, { - name: "mark schedule, sink and deployed", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - return s - }(), - wantConditionStatus: corev1.ConditionTrue, - want: true, - }, { - name: "mark schedule, sink and unavailable deployment", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.UnavailableDeployment()) - return s - }(), - wantConditionStatus: corev1.ConditionFalse, - want: false, - }, { - name: "mark schedule, sink and unknown deployment", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.UnknownDeployment()) - return s - }(), - wantConditionStatus: corev1.ConditionUnknown, - want: false, - }, { - name: "mark schedule, sink, deployed, and event types", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.MarkEventType() - return s - }(), - wantConditionStatus: corev1.ConditionTrue, - want: true, - }, { - name: "mark schedule, sink and deployed then not deployed", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.PropagateDeploymentAvailability(&appsv1.Deployment{}) - return s - }(), - wantConditionStatus: corev1.ConditionUnknown, - want: false, - }, { - name: "mark schedule, sink, deployed and event types then no event types", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.MarkNoEventType("Testing", "") - return s - }(), - wantConditionStatus: corev1.ConditionTrue, - want: true, - }, { - name: "mark schedule validated, sink empty and deployed", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(nil) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - return s - }(), - wantConditionStatus: corev1.ConditionFalse, - want: false, - }, { - name: "mark schedule validated, sink empty and deployed then sink", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(nil) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.MarkSink(exampleUri) - return s - }(), wantConditionStatus: corev1.ConditionTrue, want: true, }} @@ -300,30 +168,6 @@ func TestPingSourceStatusGetTopLevelCondition(t *testing.T) { Type: PingSourceConditionReady, Status: corev1.ConditionUnknown, }, - }, { - name: "mark schedule", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - return s - }(), - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionUnknown, - }, - }, { - name: "mark event types", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkEventType() - return s - }(), - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionUnknown, - }, }, { name: "mark sink and deployed", s: func() *PingSourceStatus { @@ -333,111 +177,6 @@ func TestPingSourceStatusGetTopLevelCondition(t *testing.T) { s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) return s }(), - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionUnknown, - }, - }, { - name: "mark schedule and sink", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - return s - }(), - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionUnknown, - }, - }, { - name: "mark schedule, sink and deployed", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - return s - }(), - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionTrue, - }, - }, { - name: "mark schedule, sink, deployed, and event types", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.MarkEventType() - return s - }(), - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionTrue, - }, - }, { - name: "mark schedule, sink and deployed then not deployed", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.PropagateDeploymentAvailability(&appsv1.Deployment{}) - return s - }(), - want: &apis.Condition{ - Type: PingSourceConditionReady, - Reason: "DeploymentUnavailable", - Status: corev1.ConditionUnknown, - Message: "The Deployment '' is unavailable.", - }, - }, { - name: "mark schedule, sink, deployed and event types then no event types", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.MarkNoEventType("Testing", "") - return s - }(), - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionTrue, - }, - }, { - name: "mark schedule validated, sink empty and deployed", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(nil) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - return s - }(), - want: &apis.Condition{ - Type: PingSourceConditionReady, - Reason: "SinkEmpty", - Status: corev1.ConditionFalse, - Message: "Sink has resolved to empty.", - }, - }, { - name: "mark schedule validated, sink empty and deployed then sink", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(nil) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.MarkSink(exampleUri) - return s - }(), want: &apis.Condition{ Type: PingSourceConditionReady, Status: corev1.ConditionTrue, @@ -506,172 +245,6 @@ func TestPingSourceStatusGetCondition(t *testing.T) { Type: PingSourceConditionReady, Status: corev1.ConditionUnknown, }, - }, { - name: "mark schedule", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - return s - }(), - condQuery: PingSourceConditionReady, - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionUnknown, - }, - }, { - name: "mark schedule, sink and deployed", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - return s - }(), - condQuery: PingSourceConditionReady, - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionTrue, - }, - }, { - name: "mark schedule, sink, deployed, and event types", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.MarkEventType() - return s - }(), - condQuery: PingSourceConditionReady, - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionTrue, - }, - }, { - name: "mark schedule, sink and deployed then no sink", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.MarkNoSink("Testing", "hi%s", "") - return s - }(), - condQuery: PingSourceConditionReady, - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionFalse, - Reason: "Testing", - Message: "hi", - }, - }, { - name: "mark schedule, sink and deployed then invalid schedule", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.MarkInvalidSchedule("Testing", "hi%s", "") - return s - }(), - condQuery: PingSourceConditionReady, - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionFalse, - Reason: "Testing", - Message: "hi", - }, - }, { - name: "mark schedule, sink and deployed then deploying", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.PropagateDeploymentAvailability(&appsv1.Deployment{}) - return s - }(), - condQuery: PingSourceConditionReady, - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionUnknown, - Reason: "DeploymentUnavailable", - Message: "The Deployment '' is unavailable.", - }, - }, { - name: "mark schedule, sink and deployed then not deployed", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.PropagateDeploymentAvailability(&appsv1.Deployment{}) - return s - }(), - condQuery: PingSourceConditionReady, - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionUnknown, - Reason: "DeploymentUnavailable", - Message: "The Deployment '' is unavailable.", - }, - }, { - name: "mark schedule, sink, deployed and event types, then no event types", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(exampleUri) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.MarkEventType() - s.MarkNoEventType("Testing", "hi") - return s - }(), - condQuery: PingSourceConditionReady, - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionTrue, - }, - }, { - name: "mark schedule, sink empty and deployed", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(nil) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - return s - }(), - condQuery: PingSourceConditionReady, - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionFalse, - Reason: "SinkEmpty", - Message: "Sink has resolved to empty.", - }, - }, { - name: "mark schedule, sink empty and deployed then sink", - s: func() *PingSourceStatus { - s := &PingSourceStatus{} - s.InitializeConditions() - s.MarkSchedule() - s.MarkSink(nil) - s.PropagateDeploymentAvailability(TestHelper.AvailableDeployment()) - s.MarkSink(exampleUri) - return s - }(), - condQuery: PingSourceConditionReady, - want: &apis.Condition{ - Type: PingSourceConditionReady, - Status: corev1.ConditionTrue, - }, }} for _, test := range tests { diff --git a/pkg/apis/sources/v1beta1/ping_types.go b/pkg/apis/sources/v1beta1/ping_types.go index 4582568196b..94781424bfe 100644 --- a/pkg/apis/sources/v1beta1/ping_types.go +++ b/pkg/apis/sources/v1beta1/ping_types.go @@ -64,7 +64,8 @@ type PingSourceSpec struct { // Timezone modifies the actual time relative to the specified timezone. // Defaults to the system time zone. - // More information about time zones: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + // More general information about time zones: https://www.iana.org/time-zones + // List of valid timezone values: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones Timezone string `json:"timezone,omitempty` // JsonData is json encoded data used as the body of the event posted to diff --git a/pkg/apis/sources/v1beta1/ping_validation.go b/pkg/apis/sources/v1beta1/ping_validation.go index 45e2f3d0235..7c8142a93f6 100644 --- a/pkg/apis/sources/v1beta1/ping_validation.go +++ b/pkg/apis/sources/v1beta1/ping_validation.go @@ -21,18 +21,20 @@ import ( "github.com/robfig/cron/v3" "knative.dev/pkg/apis" - - "knative.dev/eventing/pkg/apis/eventing" ) func (c *PingSource) Validate(ctx context.Context) *apis.FieldError { - errs := c.Spec.Validate(ctx).ViaField("spec") - return ValidateAnnotations(errs, c.Annotations) + return c.Spec.Validate(ctx).ViaField("spec") } func (cs *PingSourceSpec) Validate(ctx context.Context) *apis.FieldError { var errs *apis.FieldError + schedule := cs.Schedule + if cs.Timezone != "" { + schedule = "CRON_TZ=" + cs.Timezone + " " + schedule + } + if _, err := cron.ParseStandard(cs.Schedule); err != nil { fe := apis.ErrInvalidValue(cs.Schedule, "schedule") errs = errs.Also(fe) @@ -43,16 +45,3 @@ func (cs *PingSourceSpec) Validate(ctx context.Context) *apis.FieldError { } return errs } - -func ValidateAnnotations(errs *apis.FieldError, annotations map[string]string) *apis.FieldError { - if annotations != nil { - if scope, ok := annotations[eventing.ScopeAnnotationKey]; ok { - if scope != eventing.ScopeResource && scope != eventing.ScopeCluster { - iv := apis.ErrInvalidValue(scope, "") - iv.Details = "expected either 'cluster' or 'resource'" - errs = errs.Also(iv.ViaFieldKey("annotations", eventing.ScopeAnnotationKey).ViaField("metadata")) - } - } - } - return errs -} diff --git a/pkg/apis/sources/v1beta1/ping_validation_test.go b/pkg/apis/sources/v1beta1/ping_validation_test.go index 769b1f1af0a..901f56d9392 100644 --- a/pkg/apis/sources/v1beta1/ping_validation_test.go +++ b/pkg/apis/sources/v1beta1/ping_validation_test.go @@ -20,9 +20,6 @@ import ( "context" "testing" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "knative.dev/eventing/pkg/apis/eventing" - duckv1 "knative.dev/pkg/apis/duck/v1" "github.com/google/go-cmp/cmp" @@ -52,20 +49,29 @@ func TestPingSourceValidation(t *testing.T) { }, want: nil, }, { - name: "empty sink", + name: "valid spec with timezone", source: PingSource{ Spec: PingSourceSpec{ Schedule: "*/2 * * * *", + Timezone: "Europe/Paris", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, }, }, - want: func() *apis.FieldError { - return apis.ErrGeneric("expected at least one, got none", "ref", "uri").ViaField("spec.sink") - }(), + want: nil, }, { - name: "invalid schedule", + name: "valid spec with invalid timezone", source: PingSource{ Spec: PingSourceSpec{ - Schedule: "2", + Schedule: "*/2 * * * *", + Timezone: "Knative/Land", SourceSpec: duckv1.SourceSpec{ Sink: duckv1.Destination{ Ref: &duckv1.KReference{ @@ -77,29 +83,28 @@ func TestPingSourceValidation(t *testing.T) { }, }, }, + want: nil, + }, { + name: "empty sink", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + }, + }, want: func() *apis.FieldError { - var errs *apis.FieldError - fe := apis.ErrInvalidValue("2", "spec.schedule") - errs = errs.Also(fe) - return errs + return apis.ErrGeneric("expected at least one, got none", "ref", "uri").ViaField("spec.sink") }(), }, { - name: "invalid annotation", + name: "invalid schedule", source: PingSource{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - eventing.ScopeAnnotationKey: "notvalid", - }, - }, Spec: PingSourceSpec{ - Schedule: "*/2 * * * *", + Schedule: "2", SourceSpec: duckv1.SourceSpec{ Sink: duckv1.Destination{ Ref: &duckv1.KReference{ APIVersion: "v1alpha1", Kind: "broker", Name: "default", - Namespace: "namespace", }, }, }, @@ -107,7 +112,7 @@ func TestPingSourceValidation(t *testing.T) { }, want: func() *apis.FieldError { var errs *apis.FieldError - fe := apis.ErrInvalidValue("notvalid", "metadata.annotations.[eventing.knative.dev/scope]\nexpected either 'cluster' or 'resource'") + fe := apis.ErrInvalidValue("2", "spec.schedule") errs = errs.Also(fe) return errs }(), diff --git a/pkg/apis/sources/v1beta1/zz_generated.deepcopy.go b/pkg/apis/sources/v1beta1/zz_generated.deepcopy.go index 74fd3d497f3..c749fddbb1b 100644 --- a/pkg/apis/sources/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/sources/v1beta1/zz_generated.deepcopy.go @@ -265,6 +265,101 @@ func (in *ContainerSourceStatus) DeepCopy() *ContainerSourceStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PingSource) DeepCopyInto(out *PingSource) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PingSource. +func (in *PingSource) DeepCopy() *PingSource { + if in == nil { + return nil + } + out := new(PingSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PingSource) 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 *PingSourceList) DeepCopyInto(out *PingSourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PingSource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PingSourceList. +func (in *PingSourceList) DeepCopy() *PingSourceList { + if in == nil { + return nil + } + out := new(PingSourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PingSourceList) 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 *PingSourceSpec) DeepCopyInto(out *PingSourceSpec) { + *out = *in + in.SourceSpec.DeepCopyInto(&out.SourceSpec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PingSourceSpec. +func (in *PingSourceSpec) DeepCopy() *PingSourceSpec { + if in == nil { + return nil + } + out := new(PingSourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PingSourceStatus) DeepCopyInto(out *PingSourceStatus) { + *out = *in + in.SourceStatus.DeepCopyInto(&out.SourceStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PingSourceStatus. +func (in *PingSourceStatus) DeepCopy() *PingSourceStatus { + if in == nil { + return nil + } + out := new(PingSourceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SinkBinding) DeepCopyInto(out *SinkBinding) { *out = *in diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_pingsource.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_pingsource.go new file mode 100644 index 00000000000..15e3728c0c1 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_pingsource.go @@ -0,0 +1,140 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" +) + +// FakePingSources implements PingSourceInterface +type FakePingSources struct { + Fake *FakeSourcesV1beta1 + ns string +} + +var pingsourcesResource = schema.GroupVersionResource{Group: "sources.knative.dev", Version: "v1beta1", Resource: "pingsources"} + +var pingsourcesKind = schema.GroupVersionKind{Group: "sources.knative.dev", Version: "v1beta1", Kind: "PingSource"} + +// Get takes name of the pingSource, and returns the corresponding pingSource object, and an error if there is any. +func (c *FakePingSources) Get(name string, options v1.GetOptions) (result *v1beta1.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(pingsourcesResource, c.ns, name), &v1beta1.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PingSource), err +} + +// List takes label and field selectors, and returns the list of PingSources that match those selectors. +func (c *FakePingSources) List(opts v1.ListOptions) (result *v1beta1.PingSourceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(pingsourcesResource, pingsourcesKind, c.ns, opts), &v1beta1.PingSourceList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.PingSourceList{ListMeta: obj.(*v1beta1.PingSourceList).ListMeta} + for _, item := range obj.(*v1beta1.PingSourceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested pingSources. +func (c *FakePingSources) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(pingsourcesResource, c.ns, opts)) + +} + +// Create takes the representation of a pingSource and creates it. Returns the server's representation of the pingSource, and an error, if there is any. +func (c *FakePingSources) Create(pingSource *v1beta1.PingSource) (result *v1beta1.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(pingsourcesResource, c.ns, pingSource), &v1beta1.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PingSource), err +} + +// Update takes the representation of a pingSource and updates it. Returns the server's representation of the pingSource, and an error, if there is any. +func (c *FakePingSources) Update(pingSource *v1beta1.PingSource) (result *v1beta1.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(pingsourcesResource, c.ns, pingSource), &v1beta1.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PingSource), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakePingSources) UpdateStatus(pingSource *v1beta1.PingSource) (*v1beta1.PingSource, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(pingsourcesResource, "status", c.ns, pingSource), &v1beta1.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PingSource), err +} + +// Delete takes name of the pingSource and deletes it. Returns an error if one occurs. +func (c *FakePingSources) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(pingsourcesResource, c.ns, name), &v1beta1.PingSource{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakePingSources) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(pingsourcesResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.PingSourceList{}) + return err +} + +// Patch applies the patch and returns the patched pingSource. +func (c *FakePingSources) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(pingsourcesResource, c.ns, name, pt, data, subresources...), &v1beta1.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PingSource), err +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_sources_client.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_sources_client.go index 9ea481b5db5..9dad257eabb 100644 --- a/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_sources_client.go +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_sources_client.go @@ -36,6 +36,10 @@ func (c *FakeSourcesV1beta1) ContainerSources(namespace string) v1beta1.Containe return &FakeContainerSources{c, namespace} } +func (c *FakeSourcesV1beta1) PingSources(namespace string) v1beta1.PingSourceInterface { + return &FakePingSources{c, namespace} +} + func (c *FakeSourcesV1beta1) SinkBindings(namespace string) v1beta1.SinkBindingInterface { return &FakeSinkBindings{c, namespace} } diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/generated_expansion.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/generated_expansion.go index e42f4bcb3df..1538ebdcd85 100644 --- a/pkg/client/clientset/versioned/typed/sources/v1beta1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/generated_expansion.go @@ -22,4 +22,6 @@ type ApiServerSourceExpansion interface{} type ContainerSourceExpansion interface{} +type PingSourceExpansion interface{} + type SinkBindingExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/pingsource.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/pingsource.go new file mode 100644 index 00000000000..cb9fb2a71c4 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/pingsource.go @@ -0,0 +1,191 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// PingSourcesGetter has a method to return a PingSourceInterface. +// A group's client should implement this interface. +type PingSourcesGetter interface { + PingSources(namespace string) PingSourceInterface +} + +// PingSourceInterface has methods to work with PingSource resources. +type PingSourceInterface interface { + Create(*v1beta1.PingSource) (*v1beta1.PingSource, error) + Update(*v1beta1.PingSource) (*v1beta1.PingSource, error) + UpdateStatus(*v1beta1.PingSource) (*v1beta1.PingSource, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1beta1.PingSource, error) + List(opts v1.ListOptions) (*v1beta1.PingSourceList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.PingSource, err error) + PingSourceExpansion +} + +// pingSources implements PingSourceInterface +type pingSources struct { + client rest.Interface + ns string +} + +// newPingSources returns a PingSources +func newPingSources(c *SourcesV1beta1Client, namespace string) *pingSources { + return &pingSources{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the pingSource, and returns the corresponding pingSource object, and an error if there is any. +func (c *pingSources) Get(name string, options v1.GetOptions) (result *v1beta1.PingSource, err error) { + result = &v1beta1.PingSource{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pingsources"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of PingSources that match those selectors. +func (c *pingSources) List(opts v1.ListOptions) (result *v1beta1.PingSourceList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta1.PingSourceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pingsources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested pingSources. +func (c *pingSources) Watch(opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("pingsources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch() +} + +// Create takes the representation of a pingSource and creates it. Returns the server's representation of the pingSource, and an error, if there is any. +func (c *pingSources) Create(pingSource *v1beta1.PingSource) (result *v1beta1.PingSource, err error) { + result = &v1beta1.PingSource{} + err = c.client.Post(). + Namespace(c.ns). + Resource("pingsources"). + Body(pingSource). + Do(). + Into(result) + return +} + +// Update takes the representation of a pingSource and updates it. Returns the server's representation of the pingSource, and an error, if there is any. +func (c *pingSources) Update(pingSource *v1beta1.PingSource) (result *v1beta1.PingSource, err error) { + result = &v1beta1.PingSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("pingsources"). + Name(pingSource.Name). + Body(pingSource). + Do(). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + +func (c *pingSources) UpdateStatus(pingSource *v1beta1.PingSource) (result *v1beta1.PingSource, err error) { + result = &v1beta1.PingSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("pingsources"). + Name(pingSource.Name). + SubResource("status"). + Body(pingSource). + Do(). + Into(result) + return +} + +// Delete takes name of the pingSource and deletes it. Returns an error if one occurs. +func (c *pingSources) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("pingsources"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *pingSources) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + var timeout time.Duration + if listOptions.TimeoutSeconds != nil { + timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("pingsources"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Timeout(timeout). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched pingSource. +func (c *pingSources) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.PingSource, err error) { + result = &v1beta1.PingSource{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("pingsources"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/sources_client.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/sources_client.go index 576f4d5b6cd..4f69f47b769 100644 --- a/pkg/client/clientset/versioned/typed/sources/v1beta1/sources_client.go +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/sources_client.go @@ -28,6 +28,7 @@ type SourcesV1beta1Interface interface { RESTClient() rest.Interface ApiServerSourcesGetter ContainerSourcesGetter + PingSourcesGetter SinkBindingsGetter } @@ -44,6 +45,10 @@ func (c *SourcesV1beta1Client) ContainerSources(namespace string) ContainerSourc return newContainerSources(c, namespace) } +func (c *SourcesV1beta1Client) PingSources(namespace string) PingSourceInterface { + return newPingSources(c, namespace) +} + func (c *SourcesV1beta1Client) SinkBindings(namespace string) SinkBindingInterface { return newSinkBindings(c, namespace) } diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index e969775f8bc..5302d0d0086 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -130,6 +130,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1beta1().ApiServerSources().Informer()}, nil case sourcesv1beta1.SchemeGroupVersion.WithResource("containersources"): return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1beta1().ContainerSources().Informer()}, nil + case sourcesv1beta1.SchemeGroupVersion.WithResource("pingsources"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1beta1().PingSources().Informer()}, nil case sourcesv1beta1.SchemeGroupVersion.WithResource("sinkbindings"): return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1beta1().SinkBindings().Informer()}, nil diff --git a/pkg/client/informers/externalversions/sources/v1beta1/interface.go b/pkg/client/informers/externalversions/sources/v1beta1/interface.go index 90247203d83..ac8abddf5d7 100644 --- a/pkg/client/informers/externalversions/sources/v1beta1/interface.go +++ b/pkg/client/informers/externalversions/sources/v1beta1/interface.go @@ -28,6 +28,8 @@ type Interface interface { ApiServerSources() ApiServerSourceInformer // ContainerSources returns a ContainerSourceInformer. ContainerSources() ContainerSourceInformer + // PingSources returns a PingSourceInformer. + PingSources() PingSourceInformer // SinkBindings returns a SinkBindingInformer. SinkBindings() SinkBindingInformer } @@ -53,6 +55,11 @@ func (v *version) ContainerSources() ContainerSourceInformer { return &containerSourceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// PingSources returns a PingSourceInformer. +func (v *version) PingSources() PingSourceInformer { + return &pingSourceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // SinkBindings returns a SinkBindingInformer. func (v *version) SinkBindings() SinkBindingInformer { return &sinkBindingInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/informers/externalversions/sources/v1beta1/pingsource.go b/pkg/client/informers/externalversions/sources/v1beta1/pingsource.go new file mode 100644 index 00000000000..3092d4fd744 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1beta1/pingsource.go @@ -0,0 +1,89 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1beta1 "knative.dev/eventing/pkg/client/listers/sources/v1beta1" +) + +// PingSourceInformer provides access to a shared informer and lister for +// PingSources. +type PingSourceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta1.PingSourceLister +} + +type pingSourceInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewPingSourceInformer constructs a new informer for PingSource type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPingSourceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredPingSourceInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredPingSourceInformer constructs a new informer for PingSource type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredPingSourceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1beta1().PingSources(namespace).List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1beta1().PingSources(namespace).Watch(options) + }, + }, + &sourcesv1beta1.PingSource{}, + resyncPeriod, + indexers, + ) +} + +func (f *pingSourceInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredPingSourceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *pingSourceInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&sourcesv1beta1.PingSource{}, f.defaultInformer) +} + +func (f *pingSourceInformer) Lister() v1beta1.PingSourceLister { + return v1beta1.NewPingSourceLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/injection/informers/sources/v1beta1/pingsource/fake/fake.go b/pkg/client/injection/informers/sources/v1beta1/pingsource/fake/fake.go new file mode 100644 index 00000000000..9dbf6308117 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/pingsource/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package fake + +import ( + context "context" + + fake "knative.dev/eventing/pkg/client/injection/informers/factory/fake" + pingsource "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/pingsource" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = pingsource.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Sources().V1beta1().PingSources() + return context.WithValue(ctx, pingsource.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/sources/v1beta1/pingsource/pingsource.go b/pkg/client/injection/informers/sources/v1beta1/pingsource/pingsource.go new file mode 100644 index 00000000000..3449402819e --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/pingsource/pingsource.go @@ -0,0 +1,52 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package pingsource + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1" + factory "knative.dev/eventing/pkg/client/injection/informers/factory" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Sources().V1beta1().PingSources() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1beta1.PingSourceInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1.PingSourceInformer from context.") + } + return untyped.(v1beta1.PingSourceInformer) +} diff --git a/pkg/client/injection/reconciler/sources/v1beta1/pingsource/controller.go b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/controller.go new file mode 100644 index 00000000000..bb2eb85686e --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/controller.go @@ -0,0 +1,142 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package pingsource + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + corev1 "k8s.io/api/core/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + scheme "k8s.io/client-go/kubernetes/scheme" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + record "k8s.io/client-go/tools/record" + versionedscheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" + client "knative.dev/eventing/pkg/client/injection/client" + pingsource "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/pingsource" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "pingsource-controller" + defaultFinalizerName = "pingsources.sources.knative.dev" +) + +// NewImpl returns a controller.Impl that handles queuing and feeding work from +// the queue through an implementation of controller.Reconciler, delegating to +// the provided Interface and optional Finalizer methods. OptionsFn is used to return +// controller.Options to be used but the internal reconciler. +func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl { + logger := logging.FromContext(ctx) + + // Check the options function input. It should be 0 or 1. + if len(optionsFns) > 1 { + logger.Fatalf("up to one options function is supported, found %d", len(optionsFns)) + } + + pingsourceInformer := pingsource.Get(ctx) + + lister := pingsourceInformer.Lister() + + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, + Client: client.Get(ctx), + Lister: lister, + reconciler: r, + finalizerName: defaultFinalizerName, + } + + t := reflect.TypeOf(r).Elem() + queueName := fmt.Sprintf("%s.%s", strings.ReplaceAll(t.PkgPath(), "/", "-"), t.Name()) + + impl := controller.NewImpl(rec, logger, queueName) + agentName := defaultControllerAgentName + + // Pass impl to the options. Save any optional results. + for _, fn := range optionsFns { + opts := fn(impl) + if opts.ConfigStore != nil { + rec.configStore = opts.ConfigStore + } + if opts.FinalizerName != "" { + rec.finalizerName = opts.FinalizerName + } + if opts.AgentName != "" { + agentName = opts.AgentName + } + if opts.SkipStatusUpdates { + rec.skipStatusUpdates = true + } + } + + rec.Recorder = createRecorder(ctx, agentName) + + return impl +} + +func createRecorder(ctx context.Context, agentName string) record.EventRecorder { + logger := logging.FromContext(ctx) + + recorder := controller.GetEventRecorder(ctx) + if recorder == nil { + // Create event broadcaster + logger.Debug("Creating event broadcaster") + eventBroadcaster := record.NewBroadcaster() + watches := []watch.Interface{ + eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof), + eventBroadcaster.StartRecordingToSink( + &v1.EventSinkImpl{Interface: kubeclient.Get(ctx).CoreV1().Events("")}), + } + recorder = eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: agentName}) + go func() { + <-ctx.Done() + for _, w := range watches { + w.Stop() + } + }() + } + + return recorder +} + +func init() { + versionedscheme.AddToScheme(scheme.Scheme) +} diff --git a/pkg/client/injection/reconciler/sources/v1beta1/pingsource/reconciler.go b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/reconciler.go new file mode 100644 index 00000000000..3e73967d307 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/reconciler.go @@ -0,0 +1,448 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package pingsource + +import ( + context "context" + json "encoding/json" + fmt "fmt" + reflect "reflect" + + zap "go.uber.org/zap" + v1 "k8s.io/api/core/v1" + equality "k8s.io/apimachinery/pkg/api/equality" + errors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + sets "k8s.io/apimachinery/pkg/util/sets" + cache "k8s.io/client-go/tools/cache" + record "k8s.io/client-go/tools/record" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + sourcesv1beta1 "knative.dev/eventing/pkg/client/listers/sources/v1beta1" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" +) + +// Interface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta1.PingSource. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1beta1.PingSource. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1beta1.PingSource) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.PingSource. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1beta1.PingSource. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1beta1.PingSource) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta1.PingSource if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1beta1.PingSource. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1beta1.PingSource) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.PingSource if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1beta1.PingSource. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1beta1.PingSource) reconciler.Event +} + +// reconcilerImpl implements controller.Reconciler for v1beta1.PingSource resources. +type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware + reconciler.LeaderAwareFuncs + + // Client is used to write back status updates. + Client versioned.Interface + + // Listers index properties about resources + Lister sourcesv1beta1.PingSourceLister + + // Recorder is an event recorder for recording Event resources to the + // Kubernetes API. + Recorder record.EventRecorder + + // configStore allows for decorating a context with config maps. + // +optional + configStore reconciler.ConfigStore + + // reconciler is the implementation of the business logic of the resource. + reconciler Interface + + // finalizerName is the name of the finalizer to reconcile. + finalizerName string + + // skipStatusUpdates configures whether or not this reconciler automatically updates + // the status of the reconciled resource. + skipStatusUpdates bool +} + +// Check that our Reconciler implements controller.Reconciler +var _ controller.Reconciler = (*reconcilerImpl)(nil) + +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + +func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister sourcesv1beta1.PingSourceLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler { + // Check the options function input. It should be 0 or 1. + if len(options) > 1 { + logger.Fatalf("up to one options struct is supported, found %d", len(options)) + } + + // Fail fast when users inadvertently implement the other LeaderAware interface. + // For the typed reconcilers, Promote shouldn't take any arguments. + if _, ok := r.(reconciler.LeaderAware); ok { + logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r) + } + // TODO: Consider validating when folks implement ReadOnlyFinalizer, but not Finalizer. + + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, + Client: client, + Lister: lister, + Recorder: recorder, + reconciler: r, + finalizerName: defaultFinalizerName, + } + + for _, opts := range options { + if opts.ConfigStore != nil { + rec.configStore = opts.ConfigStore + } + if opts.FinalizerName != "" { + rec.finalizerName = opts.FinalizerName + } + if opts.SkipStatusUpdates { + rec.skipStatusUpdates = true + } + } + + return rec +} + +// Reconcile implements controller.Reconciler +func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { + logger := logging.FromContext(ctx) + + // Convert the namespace/name string into a distinct namespace and name + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + logger.Errorf("invalid resource key: %s", key) + return nil + } + // Establish whether we are the leader for use below. + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + if !isLeader && !isROI && !isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return nil + } + + // If configStore is set, attach the frozen configuration to the context. + if r.configStore != nil { + ctx = r.configStore.ToContext(ctx) + } + + // Add the recorder to context. + ctx = controller.WithEventRecorder(ctx, r.Recorder) + + // Get the resource with this namespace/name. + + getter := r.Lister.PingSources(namespace) + + original, err := getter.Get(name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing. + logger.Debugf("resource %q no longer exists", key) + return nil + } else if err != nil { + return err + } + + // Don't modify the informers copy. + resource := original.DeepCopy() + + var reconcileEvent reconciler.Event + if resource.GetDeletionTimestamp().IsZero() { + if isLeader { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + return fmt.Errorf("failed to set finalizers: %w", err) + } + + reconciler.PreProcessReconcile(ctx, resource) + + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) + + reconciler.PostProcessReconcile(ctx, resource, original) + + } else if isROI { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveKind")) + + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = roi.ObserveKind(ctx, resource) + } + } else if fin, ok := r.reconciler.(Finalizer); isLeader && ok { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "FinalizeKind")) + + // For finalizing reconcilers, if this resource being marked for deletion + // and reconciled cleanly (nil or normal event), remove the finalizer. + reconcileEvent = fin.FinalizeKind(ctx, resource) + if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { + return fmt.Errorf("failed to clear finalizers: %w", err) + } + } else if !isLeader && isROF { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ObserveFinalizeKind")) + + // For finalizing reconcilers, just observe when we aren't the leader. + reconcileEvent = rof.ObserveFinalizeKind(ctx, resource) + } + + // Synchronize the status. + switch { + case r.skipStatusUpdates: + // This reconciler implementation is configured to skip resource updates. + // This may mean this reconciler does not observe spec, but reconciles external changes. + case equality.Semantic.DeepEqual(original.Status, resource.Status): + // If we didn't change anything then don't call updateStatus. + // This is important because the copy we loaded from the injectionInformer's + // cache may be stale and we don't want to overwrite a prior update + // to status with this stale state. + case !isLeader: + // High-availability reconcilers may have many replicas watching the resource, but only + // the elected leader is expected to write modifications. + logger.Warn("Saw status changes when we aren't the leader!") + default: + if err = r.updateStatus(original, resource); err != nil { + logger.Warnw("Failed to update resource status", zap.Error(err)) + r.Recorder.Eventf(resource, v1.EventTypeWarning, "UpdateFailed", + "Failed to update status for %q: %v", resource.Name, err) + return err + } + } + + // Report the reconciler event, if any. + if reconcileEvent != nil { + var event *reconciler.ReconcilerEvent + if reconciler.EventAs(reconcileEvent, &event) { + logger.Infow("Returned an event", zap.Any("event", reconcileEvent)) + r.Recorder.Eventf(resource, event.EventType, event.Reason, event.Format, event.Args...) + + // the event was wrapped inside an error, consider the reconciliation as failed + if _, isEvent := reconcileEvent.(*reconciler.ReconcilerEvent); !isEvent { + return reconcileEvent + } + return nil + } + + logger.Errorw("Returned an error", zap.Error(reconcileEvent)) + r.Recorder.Event(resource, v1.EventTypeWarning, "InternalError", reconcileEvent.Error()) + return reconcileEvent + } + + return nil +} + +func (r *reconcilerImpl) updateStatus(existing *v1beta1.PingSource, desired *v1beta1.PingSource) error { + existing = existing.DeepCopy() + return reconciler.RetryUpdateConflicts(func(attempts int) (err error) { + // The first iteration tries to use the injectionInformer's state, subsequent attempts fetch the latest state via API. + if attempts > 0 { + + getter := r.Client.SourcesV1beta1().PingSources(desired.Namespace) + + existing, err = getter.Get(desired.Name, metav1.GetOptions{}) + if err != nil { + return err + } + } + + // If there's nothing to update, just return. + if reflect.DeepEqual(existing.Status, desired.Status) { + return nil + } + + existing.Status = desired.Status + + updater := r.Client.SourcesV1beta1().PingSources(existing.Namespace) + + _, err = updater.UpdateStatus(existing) + return err + }) +} + +// updateFinalizersFiltered will update the Finalizers of the resource. +// TODO: this method could be generic and sync all finalizers. For now it only +// updates defaultFinalizerName or its override. +func (r *reconcilerImpl) updateFinalizersFiltered(ctx context.Context, resource *v1beta1.PingSource) (*v1beta1.PingSource, error) { + + getter := r.Lister.PingSources(resource.Namespace) + + actual, err := getter.Get(resource.Name) + if err != nil { + return resource, err + } + + // Don't modify the informers copy. + existing := actual.DeepCopy() + + var finalizers []string + + // If there's nothing to update, just return. + existingFinalizers := sets.NewString(existing.Finalizers...) + desiredFinalizers := sets.NewString(resource.Finalizers...) + + if desiredFinalizers.Has(r.finalizerName) { + if existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Add the finalizer. + finalizers = append(existing.Finalizers, r.finalizerName) + } else { + if !existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Remove the finalizer. + existingFinalizers.Delete(r.finalizerName) + finalizers = existingFinalizers.List() + } + + mergePatch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "finalizers": finalizers, + "resourceVersion": existing.ResourceVersion, + }, + } + + patch, err := json.Marshal(mergePatch) + if err != nil { + return resource, err + } + + patcher := r.Client.SourcesV1beta1().PingSources(resource.Namespace) + + resourceName := resource.Name + resource, err = patcher.Patch(resourceName, types.MergePatchType, patch) + if err != nil { + r.Recorder.Eventf(resource, v1.EventTypeWarning, "FinalizerUpdateFailed", + "Failed to update finalizers for %q: %v", resourceName, err) + } else { + r.Recorder.Eventf(resource, v1.EventTypeNormal, "FinalizerUpdate", + "Updated %q finalizers", resource.GetName()) + } + return resource, err +} + +func (r *reconcilerImpl) setFinalizerIfFinalizer(ctx context.Context, resource *v1beta1.PingSource) (*v1beta1.PingSource, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1beta1.PingSource, reconcileEvent reconciler.Event) (*v1beta1.PingSource, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + if resource.GetDeletionTimestamp().IsZero() { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + if reconcileEvent != nil { + var event *reconciler.ReconcilerEvent + if reconciler.EventAs(reconcileEvent, &event) { + if event.EventType == v1.EventTypeNormal { + finalizers.Delete(r.finalizerName) + } + } + } else { + finalizers.Delete(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} diff --git a/pkg/client/injection/reconciler/sources/v1beta1/pingsource/stub/controller.go b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/stub/controller.go new file mode 100644 index 00000000000..1742f05c71b --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/stub/controller.go @@ -0,0 +1,54 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package pingsource + +import ( + context "context" + + pingsource "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/pingsource" + v1beta1pingsource "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1beta1/pingsource" + configmap "knative.dev/pkg/configmap" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" +) + +// TODO: PLEASE COPY AND MODIFY THIS FILE AS A STARTING POINT + +// NewController creates a Reconciler for PingSource and returns the result of NewImpl. +func NewController( + ctx context.Context, + cmw configmap.Watcher, +) *controller.Impl { + logger := logging.FromContext(ctx) + + pingsourceInformer := pingsource.Get(ctx) + + // TODO: setup additional informers here. + + r := &Reconciler{} + impl := v1beta1pingsource.NewImpl(ctx, r) + + logger.Info("Setting up event handlers.") + + pingsourceInformer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue)) + + // TODO: add additional informer event handlers here. + + return impl +} diff --git a/pkg/client/injection/reconciler/sources/v1beta1/pingsource/stub/reconciler.go b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/stub/reconciler.go new file mode 100644 index 00000000000..b300bfa997e --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/stub/reconciler.go @@ -0,0 +1,87 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package pingsource + +import ( + context "context" + + v1 "k8s.io/api/core/v1" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + pingsource "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1beta1/pingsource" + reconciler "knative.dev/pkg/reconciler" +) + +// TODO: PLEASE COPY AND MODIFY THIS FILE AS A STARTING POINT + +// newReconciledNormal makes a new reconciler event with event type Normal, and +// reason PingSourceReconciled. +func newReconciledNormal(namespace, name string) reconciler.Event { + return reconciler.NewEvent(v1.EventTypeNormal, "PingSourceReconciled", "PingSource reconciled: \"%s/%s\"", namespace, name) +} + +// Reconciler implements controller.Reconciler for PingSource resources. +type Reconciler struct { + // TODO: add additional requirements here. +} + +// Check that our Reconciler implements Interface +var _ pingsource.Interface = (*Reconciler)(nil) + +// Optionally check that our Reconciler implements Finalizer +//var _ pingsource.Finalizer = (*Reconciler)(nil) + +// Optionally check that our Reconciler implements ReadOnlyInterface +// Implement this to observe resources even when we are not the leader. +//var _ pingsource.ReadOnlyInterface = (*Reconciler)(nil) + +// Optionally check that our Reconciler implements ReadOnlyFinalizer +// Implement this to observe tombstoned resources even when we are not +// the leader (best effort). +//var _ pingsource.ReadOnlyFinalizer = (*Reconciler)(nil) + +// ReconcileKind implements Interface.ReconcileKind. +func (r *Reconciler) ReconcileKind(ctx context.Context, o *v1beta1.PingSource) reconciler.Event { + // TODO: use this if the resource implements InitializeConditions. + // o.Status.InitializeConditions() + + // TODO: add custom reconciliation logic here. + + // TODO: use this if the object has .status.ObservedGeneration. + // o.Status.ObservedGeneration = o.Generation + return newReconciledNormal(o.Namespace, o.Name) +} + +// Optionally, use FinalizeKind to add finalizers. FinalizeKind will be called +// when the resource is deleted. +//func (r *Reconciler) FinalizeKind(ctx context.Context, o *v1beta1.PingSource) reconciler.Event { +// // TODO: add custom finalization logic here. +// return nil +//} + +// Optionally, use ObserveKind to observe the resource when we are not the leader. +// func (r *Reconciler) ObserveKind(ctx context.Context, o *v1beta1.PingSource) reconciler.Event { +// // TODO: add custom observation logic here. +// return nil +// } + +// Optionally, use ObserveFinalizeKind to observe resources being finalized when we are no the leader. +//func (r *Reconciler) ObserveFinalizeKind(ctx context.Context, o *v1beta1.PingSource) reconciler.Event { +// // TODO: add custom observation logic here. +// return nil +//} diff --git a/pkg/client/listers/sources/v1beta1/expansion_generated.go b/pkg/client/listers/sources/v1beta1/expansion_generated.go index f36739de82b..16d57166a32 100644 --- a/pkg/client/listers/sources/v1beta1/expansion_generated.go +++ b/pkg/client/listers/sources/v1beta1/expansion_generated.go @@ -34,6 +34,14 @@ type ContainerSourceListerExpansion interface{} // ContainerSourceNamespaceLister. type ContainerSourceNamespaceListerExpansion interface{} +// PingSourceListerExpansion allows custom methods to be added to +// PingSourceLister. +type PingSourceListerExpansion interface{} + +// PingSourceNamespaceListerExpansion allows custom methods to be added to +// PingSourceNamespaceLister. +type PingSourceNamespaceListerExpansion interface{} + // SinkBindingListerExpansion allows custom methods to be added to // SinkBindingLister. type SinkBindingListerExpansion interface{} diff --git a/pkg/client/listers/sources/v1beta1/pingsource.go b/pkg/client/listers/sources/v1beta1/pingsource.go new file mode 100644 index 00000000000..0c55dc2b169 --- /dev/null +++ b/pkg/client/listers/sources/v1beta1/pingsource.go @@ -0,0 +1,94 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" +) + +// PingSourceLister helps list PingSources. +type PingSourceLister interface { + // List lists all PingSources in the indexer. + List(selector labels.Selector) (ret []*v1beta1.PingSource, err error) + // PingSources returns an object that can list and get PingSources. + PingSources(namespace string) PingSourceNamespaceLister + PingSourceListerExpansion +} + +// pingSourceLister implements the PingSourceLister interface. +type pingSourceLister struct { + indexer cache.Indexer +} + +// NewPingSourceLister returns a new PingSourceLister. +func NewPingSourceLister(indexer cache.Indexer) PingSourceLister { + return &pingSourceLister{indexer: indexer} +} + +// List lists all PingSources in the indexer. +func (s *pingSourceLister) List(selector labels.Selector) (ret []*v1beta1.PingSource, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.PingSource)) + }) + return ret, err +} + +// PingSources returns an object that can list and get PingSources. +func (s *pingSourceLister) PingSources(namespace string) PingSourceNamespaceLister { + return pingSourceNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// PingSourceNamespaceLister helps list and get PingSources. +type PingSourceNamespaceLister interface { + // List lists all PingSources in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1beta1.PingSource, err error) + // Get retrieves the PingSource from the indexer for a given namespace and name. + Get(name string) (*v1beta1.PingSource, error) + PingSourceNamespaceListerExpansion +} + +// pingSourceNamespaceLister implements the PingSourceNamespaceLister +// interface. +type pingSourceNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all PingSources in the indexer for a given namespace. +func (s pingSourceNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.PingSource, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.PingSource)) + }) + return ret, err +} + +// Get retrieves the PingSource from the indexer for a given namespace and name. +func (s pingSourceNamespaceLister) Get(name string) (*v1beta1.PingSource, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta1.Resource("pingsource"), name) + } + return obj.(*v1beta1.PingSource), nil +} From 3febb2170af211a9ad41d91553b10b9309224ccd Mon Sep 17 00:00:00 2001 From: Lionel Villard Date: Mon, 27 Jul 2020 10:50:20 -0400 Subject: [PATCH 3/4] fix validation logic --- pkg/apis/sources/v1beta1/ping_validation.go | 12 +++++++++--- pkg/apis/sources/v1beta1/ping_validation_test.go | 11 ++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pkg/apis/sources/v1beta1/ping_validation.go b/pkg/apis/sources/v1beta1/ping_validation.go index 7c8142a93f6..e65ceacbac6 100644 --- a/pkg/apis/sources/v1beta1/ping_validation.go +++ b/pkg/apis/sources/v1beta1/ping_validation.go @@ -18,6 +18,7 @@ package v1beta1 import ( "context" + "strings" "github.com/robfig/cron/v3" "knative.dev/pkg/apis" @@ -35,9 +36,14 @@ func (cs *PingSourceSpec) Validate(ctx context.Context) *apis.FieldError { schedule = "CRON_TZ=" + cs.Timezone + " " + schedule } - if _, err := cron.ParseStandard(cs.Schedule); err != nil { - fe := apis.ErrInvalidValue(cs.Schedule, "schedule") - errs = errs.Also(fe) + if _, err := cron.ParseStandard(schedule); err != nil { + if strings.HasPrefix(err.Error(), "provided bad location") { + fe := apis.ErrInvalidValue(err, "timezone") + errs = errs.Also(fe) + } else { + fe := apis.ErrInvalidValue(err, "schedule") + errs = errs.Also(fe) + } } if fe := cs.Sink.Validate(ctx); fe != nil { diff --git a/pkg/apis/sources/v1beta1/ping_validation_test.go b/pkg/apis/sources/v1beta1/ping_validation_test.go index 901f56d9392..77c316736b1 100644 --- a/pkg/apis/sources/v1beta1/ping_validation_test.go +++ b/pkg/apis/sources/v1beta1/ping_validation_test.go @@ -83,7 +83,12 @@ func TestPingSourceValidation(t *testing.T) { }, }, }, - want: nil, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrInvalidValue("provided bad location Knative/Land: unknown time zone Knative/Land", "spec.timezone") + errs = errs.Also(fe) + return errs + }(), }, { name: "empty sink", source: PingSource{ @@ -112,7 +117,7 @@ func TestPingSourceValidation(t *testing.T) { }, want: func() *apis.FieldError { var errs *apis.FieldError - fe := apis.ErrInvalidValue("2", "spec.schedule") + fe := apis.ErrInvalidValue("expected exactly 5 fields, found 1: [2]", "spec.schedule") errs = errs.Also(fe) return errs }(), @@ -122,7 +127,7 @@ func TestPingSourceValidation(t *testing.T) { t.Run(test.name, func(t *testing.T) { got := test.source.Validate(context.TODO()) if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { - t.Errorf("ContainerSourceSpec.Validate (-want, +got) = %v", diff) + t.Errorf("PingSourceSpec.Validate (-want, +got) = %v", diff) } }) } From a1e0a072f1cdb481892f7439e2250d4ac784dd4f Mon Sep 17 00:00:00 2001 From: Lionel Villard Date: Mon, 27 Jul 2020 11:23:49 -0400 Subject: [PATCH 4/4] run update-codegen --- .../sources/v1beta1/pingsource/reconciler.go | 91 ++++++++------- .../sources/v1beta1/pingsource/state.go | 105 ++++++++++++++++++ 2 files changed, 150 insertions(+), 46 deletions(-) create mode 100644 pkg/client/injection/reconciler/sources/v1beta1/pingsource/state.go diff --git a/pkg/client/injection/reconciler/sources/v1beta1/pingsource/reconciler.go b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/reconciler.go index 3e73967d307..501daf5ca03 100644 --- a/pkg/client/injection/reconciler/sources/v1beta1/pingsource/reconciler.go +++ b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/reconciler.go @@ -32,7 +32,6 @@ import ( labels "k8s.io/apimachinery/pkg/labels" types "k8s.io/apimachinery/pkg/types" sets "k8s.io/apimachinery/pkg/util/sets" - cache "k8s.io/client-go/tools/cache" record "k8s.io/client-go/tools/record" v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" versioned "knative.dev/eventing/pkg/client/clientset/versioned" @@ -84,6 +83,15 @@ type ReadOnlyFinalizer interface { ObserveFinalizeKind(ctx context.Context, o *v1beta1.PingSource) reconciler.Event } +type doReconcile func(ctx context.Context, o *v1beta1.PingSource) reconciler.Event + +const ( + doReconcileKind = "ReconcileKind" + doFinalizeKind = "FinalizeKind" + doObserveKind = "ObserveKind" + doObserveFinalizeKind = "ObserveFinalizeKind" +) + // reconcilerImpl implements controller.Reconciler for v1beta1.PingSource resources. type reconcilerImpl struct { // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware @@ -176,22 +184,19 @@ func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versio func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { logger := logging.FromContext(ctx) - // Convert the namespace/name string into a distinct namespace and name - namespace, name, err := cache.SplitMetaNamespaceKey(key) + // Initialize the reconciler state. This will convert the namespace/name + // string into a distinct namespace and name, determin if this instance of + // the reconciler is the leader, and any additional interfaces implemented + // by the reconciler. Returns an error is the resource key is invalid. + s, err := newState(key, r) if err != nil { logger.Errorf("invalid resource key: %s", key) return nil } - // Establish whether we are the leader for use below. - isLeader := r.IsLeaderFor(types.NamespacedName{ - Namespace: namespace, - Name: name, - }) - roi, isROI := r.reconciler.(ReadOnlyInterface) - rof, isROF := r.reconciler.(ReadOnlyFinalizer) - if !isLeader && !isROI && !isROF { - // If we are not the leader, and we don't implement either ReadOnly - // interface, then take a fast-path out. + + // If we are not the leader, and we don't implement either ReadOnly + // observer interfaces, then take a fast-path out. + if s.isNotLeaderNorObserver() { return nil } @@ -205,9 +210,9 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // Get the resource with this namespace/name. - getter := r.Lister.PingSources(namespace) + getter := r.Lister.PingSources(s.namespace) - original, err := getter.Get(name) + original, err := getter.Get(s.name) if errors.IsNotFound(err) { // The resource may no longer exist, in which case we stop processing. @@ -221,48 +226,42 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { resource := original.DeepCopy() var reconcileEvent reconciler.Event - if resource.GetDeletionTimestamp().IsZero() { - if isLeader { - // Append the target method to the logger. - logger = logger.With(zap.String("targetMethod", "ReconcileKind")) - - // Set and update the finalizer on resource if r.reconciler - // implements Finalizer. - if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { - return fmt.Errorf("failed to set finalizers: %w", err) - } - reconciler.PreProcessReconcile(ctx, resource) + name, do := s.reconcileMethodFor(resource) + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", name)) + switch name { + case doReconcileKind: + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ReconcileKind")) - // Reconcile this copy of the resource and then write back any status - // updates regardless of whether the reconciliation errored out. - reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + return fmt.Errorf("failed to set finalizers: %w", err) + } - reconciler.PostProcessReconcile(ctx, resource, original) + reconciler.PreProcessReconcile(ctx, resource) - } else if isROI { - // Append the target method to the logger. - logger = logger.With(zap.String("targetMethod", "ObserveKind")) + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = do(ctx, resource) - // Observe any changes to this resource, since we are not the leader. - reconcileEvent = roi.ObserveKind(ctx, resource) - } - } else if fin, ok := r.reconciler.(Finalizer); isLeader && ok { - // Append the target method to the logger. - logger = logger.With(zap.String("targetMethod", "FinalizeKind")) + reconciler.PostProcessReconcile(ctx, resource, original) + case "FinalizeKind": // For finalizing reconcilers, if this resource being marked for deletion // and reconciled cleanly (nil or normal event), remove the finalizer. - reconcileEvent = fin.FinalizeKind(ctx, resource) + reconcileEvent = do(ctx, resource) + if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { return fmt.Errorf("failed to clear finalizers: %w", err) } - } else if !isLeader && isROF { - // Append the target method to the logger. - logger = logger.With(zap.String("targetMethod", "ObserveFinalizeKind")) - // For finalizing reconcilers, just observe when we aren't the leader. - reconcileEvent = rof.ObserveFinalizeKind(ctx, resource) + case "ObserveKind", "ObserveFinalizeKind": + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = do(ctx, resource) + } // Synchronize the status. @@ -275,7 +274,7 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { // This is important because the copy we loaded from the injectionInformer's // cache may be stale and we don't want to overwrite a prior update // to status with this stale state. - case !isLeader: + case !s.isLeader: // High-availability reconcilers may have many replicas watching the resource, but only // the elected leader is expected to write modifications. logger.Warn("Saw status changes when we aren't the leader!") diff --git a/pkg/client/injection/reconciler/sources/v1beta1/pingsource/state.go b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/state.go new file mode 100644 index 00000000000..986f22f6388 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/state.go @@ -0,0 +1,105 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package pingsource + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" +) + +// state is used to track the state of a reconciler in a single run. +type state struct { + // Key is the original reconciliation key from the queue. + key string + // Namespace is the namespace split from the reconciliation key. + namespace string + // Namespace is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // rof is the read only interface cast of the reconciler. + roi ReadOnlyInterface + // IsROI (Read Only Interface) the reconciler only observes reconciliation. + isROI bool + // rof is the read only finalizer cast of the reconciler. + rof ReadOnlyFinalizer + // IsROF (Read Only Finalizer) the reconciler only observes finalize. + isROF bool + // IsLeader the instance of the reconciler is the elected leader. + isLeader bool +} + +func newState(key string, r *reconcilerImpl) (*state, error) { + // Convert the namespace/name string into a distinct namespace and name + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, fmt.Errorf("invalid resource key: %s", key) + } + + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + rof: rof, + isROF: isROF, + isLeader: isLeader, + }, nil +} + +// isNotLeaderNorObserver checks to see if this reconciler with the current +// state is enabled to do any work or not. +// isNotLeaderNorObserver returns true when there is no work possible for the +// reconciler. +func (s *state) isNotLeaderNorObserver() bool { + if !s.isLeader && !s.isROI && !s.isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1beta1.PingSource) (string, doReconcile) { + if o.GetDeletionTimestamp().IsZero() { + if s.isLeader { + return doReconcileKind, s.reconciler.ReconcileKind + } else if s.isROI { + return doObserveKind, s.roi.ObserveKind + } + } else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok { + return doFinalizeKind, fin.FinalizeKind + } else if !s.isLeader && s.isROF { + return doObserveFinalizeKind, s.rof.ObserveFinalizeKind + } + return "unknown", nil +}