diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index f1bb226d32f..dcc137fb5b3 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -57,6 +57,7 @@ import ( sourcesv1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" sourcesv1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" "knative.dev/eventing/pkg/logconfig" "knative.dev/eventing/pkg/reconciler/sinkbinding" ) @@ -95,6 +96,8 @@ var ourTypes = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{ sourcesv1beta1.SchemeGroupVersion.WithKind("PingSource"): &sourcesv1beta1.PingSource{}, sourcesv1beta1.SchemeGroupVersion.WithKind("SinkBinding"): &sourcesv1beta1.SinkBinding{}, sourcesv1beta1.SchemeGroupVersion.WithKind("ContainerSource"): &sourcesv1beta1.ContainerSource{}, + // v1beta2 + sourcesv1beta2.SchemeGroupVersion.WithKind("PingSource"): &sourcesv1beta2.PingSource{}, // v1 sourcesv1.SchemeGroupVersion.WithKind("ApiServerSource"): &sourcesv1.ApiServerSource{}, sourcesv1.SchemeGroupVersion.WithKind("SinkBinding"): &sourcesv1.SinkBinding{}, @@ -245,6 +248,7 @@ func NewConversionController(ctx context.Context, cmw configmap.Watcher) *contro sourcesv1alpha1_ = sourcesv1alpha1.SchemeGroupVersion.Version sourcesv1alpha2_ = sourcesv1alpha2.SchemeGroupVersion.Version sourcesv1beta1_ = sourcesv1beta1.SchemeGroupVersion.Version + sourcesv1beta2_ = sourcesv1beta2.SchemeGroupVersion.Version sourcesv1_ = sourcesv1.SchemeGroupVersion.Version ) @@ -333,6 +337,7 @@ func NewConversionController(ctx context.Context, cmw configmap.Watcher) *contro Zygotes: map[string]conversion.ConvertibleObject{ sourcesv1alpha2_: &sourcesv1alpha2.PingSource{}, sourcesv1beta1_: &sourcesv1beta1.PingSource{}, + sourcesv1beta2_: &sourcesv1beta2.PingSource{}, }, }, sourcesv1.Kind("SinkBinding"): { diff --git a/config/core/resources/pingsource.yaml b/config/core/resources/pingsource.yaml index 85adcd4ab51..958cbf94c32 100644 --- a/config/core/resources/pingsource.yaml +++ b/config/core/resources/pingsource.yaml @@ -322,6 +322,152 @@ spec: description: 'SinkURI is the current active sink URI that has been configured for the Source.' type: string + - <<: *version + name: v1beta2 + served: true + storage: false + schema: + openAPIV3Schema: + type: object + description: 'PingSource describes an event source with a fixed payload produced on a specified cron schedule.' + properties: + spec: + type: object + description: 'PingSourceSpec defines the desired state of the PingSource (from the client).' + properties: + ceOverrides: + description: 'CloudEventOverrides defines overrides to control the + output format and modifications of the event sent to the sink.' + type: object + properties: + extensions: + description: 'Extensions specify what attribute are added or + overridden on the outbound event. Each `Extensions` key-value + pair are set on the event as an attribute extension independently.' + type: object + additionalProperties: + type: string + x-kubernetes-preserve-unknown-fields: true + contentType: + description: 'ContentType is the media type of `data` or `dataBase64`. Default is empty' + type: string + data: + description: 'Data is data used as the body of the event posted to the sink. Default is empty. + Mutually exclusive with `dataBase64`.' + type: string + dataBase64: + description: 'DataBase64 is base64 encoded binary data used as the body of the event posted to the sink. + Mutually exclusive with `data`.' + type: string + schedule: + description: 'Schedule is the cron schedule. Defaults to `* * * + * *`.' + type: string + sink: + description: 'Sink is a reference to an object that will resolve to + a uri to use as the sink.' + type: object + properties: + ref: + description: 'Ref points to an Addressable.' + type: object + properties: + apiVersion: + description: 'API version of the referent.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + This is optional field, it gets defaulted to the + object holding it if left out.' + type: string + uri: + description: 'URI can be an absolute URL(non-empty scheme and + non-empty host) pointing to the target or a relative URI. + Relative URIs will be resolved using the base URI retrieved + from Ref.' + type: string + timezone: + description: 'Timezone modifies the actual time relative to the specified + timezone. Defaults to the system time zone. 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' + type: string + status: + type: object + description: 'PingSourceStatus defines the observed state of PingSource (from the controller).' + properties: + annotations: + description: 'Annotations is additional Status fields for the Resource + to save some additional State as well as convey more information + to the user. This is roughly akin to Annotations on any k8s resource, + just the reconciler conveying richer information outwards.' + type: object + x-kubernetes-preserve-unknown-fields: true + ceAttributes: + description: 'CloudEventAttributes are the specific attributes that + the Source uses as part of its CloudEvents.' + type: array + items: + type: object + properties: + source: + description: 'Source is the CloudEvents source attribute.' + type: string + type: + description: 'Type refers to the CloudEvent type attribute.' + type: string + conditions: + description: 'Conditions the latest available observations of a resource''s + current state.' + type: array + items: + type: object + required: + - type + - status + properties: + lastTransitionTime: + description: 'LastTransitionTime is the last time the condition + transitioned from one status to another. We use VolatileTime + in place of metav1.Time to exclude this from creating + equality.Semantic differences (all other things held + constant).' + type: string + message: + description: 'A human readable message indicating details + about the transition.' + type: string + reason: + description: 'The reason for the condition''s last transition.' + type: string + severity: + description: 'Severity with which to treat failures of + this type of condition. When this is not specified, + it defaults to Error.' + type: string + status: + description: 'Status of the condition, one of True, False, + Unknown.' + type: string + type: + description: 'Type of condition.' + type: string + observedGeneration: + description: 'ObservedGeneration is the "Generation" of the Service + that was last processed by the controller.' + type: integer + format: int64 + sinkUri: + description: 'SinkURI is the current active sink URI that has been + configured for the Source.' + type: string names: categories: - all diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 57b752ab29f..05866751e55 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -44,7 +44,7 @@ chmod +x ${CODEGEN_PKG}/generate-groups.sh # instead of the $GOPATH directly. For normal projects this can be dropped. ${CODEGEN_PKG}/generate-groups.sh "deepcopy,client,informer,lister" \ knative.dev/eventing/pkg/client knative.dev/eventing/pkg/apis \ - "eventing:v1beta1 eventing:v1 messaging:v1beta1 messaging:v1 flows:v1beta1 flows:v1 sources:v1alpha1 sources:v1alpha2 sources:v1beta1 sources:v1 configs:v1alpha1" \ + "eventing:v1beta1 eventing:v1 messaging:v1beta1 messaging:v1 flows:v1beta1 flows:v1 sources:v1alpha1 sources:v1alpha2 sources:v1beta1 sources:v1beta2 sources:v1 configs:v1alpha1" \ --go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt # Deep copy config @@ -64,7 +64,7 @@ ${CODEGEN_PKG}/generate-groups.sh "deepcopy" \ chmod +x ${KNATIVE_CODEGEN_PKG}/hack/generate-knative.sh ${KNATIVE_CODEGEN_PKG}/hack/generate-knative.sh "injection" \ knative.dev/eventing/pkg/client knative.dev/eventing/pkg/apis \ - "eventing:v1beta1 eventing:v1 messaging:v1beta1 messaging:v1 flows:v1beta1 flows:v1 sources:v1alpha1 sources:v1alpha2 sources:v1beta1 sources:v1 duck:v1alpha1 duck:v1beta1 duck:v1 configs:v1alpha1" \ + "eventing:v1beta1 eventing:v1 messaging:v1beta1 messaging:v1 flows:v1beta1 flows:v1 sources:v1alpha1 sources:v1alpha2 sources:v1beta1 sources:v1beta2 sources:v1 duck:v1alpha1 duck:v1beta1 duck:v1 configs:v1alpha1" \ --go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt # Make sure our dependencies are up-to-date diff --git a/pkg/adapter/mtping/adapter.go b/pkg/adapter/mtping/adapter.go index 268af638efc..8e8b04bcefa 100644 --- a/pkg/adapter/mtping/adapter.go +++ b/pkg/adapter/mtping/adapter.go @@ -32,7 +32,7 @@ import ( "knative.dev/pkg/logging" "knative.dev/eventing/pkg/adapter/v2" - "knative.dev/eventing/pkg/apis/sources/v1beta1" + v1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" ) const ( @@ -93,7 +93,7 @@ func GetNoShutDownAfterValue() int { // Implements MTAdapter -func (a *mtpingAdapter) Update(ctx context.Context, source *v1beta1.PingSource) { +func (a *mtpingAdapter) Update(ctx context.Context, source *v1beta2.PingSource) { logging.FromContext(ctx).Info("Synchronizing schedule") key := fmt.Sprintf("%s/%s", source.Namespace, source.Name) @@ -113,7 +113,7 @@ func (a *mtpingAdapter) Update(ctx context.Context, source *v1beta1.PingSource) a.entryidMu.Unlock() } -func (a *mtpingAdapter) Remove(ctx context.Context, source *v1beta1.PingSource) { +func (a *mtpingAdapter) Remove(ctx context.Context, source *v1beta2.PingSource) { key := fmt.Sprintf("%s/%s", source.Namespace, source.Name) a.entryidMu.RLock() diff --git a/pkg/adapter/mtping/adapter_test.go b/pkg/adapter/mtping/adapter_test.go index e76bdb79d26..f03648ada89 100644 --- a/pkg/adapter/mtping/adapter_test.go +++ b/pkg/adapter/mtping/adapter_test.go @@ -24,7 +24,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/eventing/pkg/apis/sources/v1beta2" "github.com/robfig/cron/v3" @@ -70,7 +70,7 @@ func TestUpdateRemoveAdapter(t *testing.T) { entryids: make(map[string]cron.EntryID), } - adapter.Update(ctx, &sourcesv1beta1.PingSource{ + adapter.Update(ctx, &v1beta2.PingSource{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "test-ns", @@ -81,7 +81,7 @@ func TestUpdateRemoveAdapter(t *testing.T) { t.Error(`Expected cron entries to contain "test-ns/test-name"`) } - adapter.Remove(ctx, &sourcesv1beta1.PingSource{ + adapter.Remove(ctx, &v1beta2.PingSource{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "test-ns", @@ -96,7 +96,7 @@ type testRunner struct { CronJobRunner } -func (*testRunner) AddSchedule(*sourcesv1beta1.PingSource) cron.EntryID { +func (*testRunner) AddSchedule(*v1beta2.PingSource) cron.EntryID { return cron.EntryID(1) } func (*testRunner) RemoveSchedule(cron.EntryID) {} diff --git a/pkg/adapter/mtping/controller.go b/pkg/adapter/mtping/controller.go index c039b46b2e0..12ccc584d51 100644 --- a/pkg/adapter/mtping/controller.go +++ b/pkg/adapter/mtping/controller.go @@ -23,9 +23,9 @@ import ( "knative.dev/pkg/logging" "knative.dev/eventing/pkg/adapter/v2" - "knative.dev/eventing/pkg/apis/sources/v1beta1" - pingsourceinformer "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/pingsource" - pingsourcereconciler "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1beta1/pingsource" + "knative.dev/eventing/pkg/apis/sources/v1beta2" + pingsourceinformer "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta2/pingsource" + pingsourcereconciler "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1beta2/pingsource" ) // TODO: code generation @@ -33,10 +33,10 @@ import ( // MTAdapter is the interface the multi-tenant PingSource adapter must implement type MTAdapter interface { // Update is called when the source is ready and when the specification and/or status has changed. - Update(ctx context.Context, source *v1beta1.PingSource) + Update(ctx context.Context, source *v1beta2.PingSource) // Remove is called when the source has been deleted. - Remove(ctx context.Context, source *v1beta1.PingSource) + Remove(ctx context.Context, source *v1beta2.PingSource) } // NewController initializes the controller. This is called by the shared adapter Main diff --git a/pkg/adapter/mtping/controller_test.go b/pkg/adapter/mtping/controller_test.go index dd3454496f7..5ecf9ef3b34 100644 --- a/pkg/adapter/mtping/controller_test.go +++ b/pkg/adapter/mtping/controller_test.go @@ -22,21 +22,21 @@ import ( . "knative.dev/pkg/reconciler/testing" + "knative.dev/eventing/pkg/adapter/v2" // Fake injection informers + _ "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta2/pingsource/fake" - "knative.dev/eventing/pkg/adapter/v2" - "knative.dev/eventing/pkg/apis/sources/v1beta1" - _ "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/pingsource/fake" + "knative.dev/eventing/pkg/apis/sources/v1beta2" ) type testAdapter struct { adapter.Adapter } -func (testAdapter) Update(ctx context.Context, source *v1beta1.PingSource) { +func (testAdapter) Update(context.Context, *v1beta2.PingSource) { } -func (testAdapter) Remove(ctx context.Context, source *v1beta1.PingSource) { +func (testAdapter) Remove(context.Context, *v1beta2.PingSource) { } func TestNew(t *testing.T) { diff --git a/pkg/adapter/mtping/pingsource.go b/pkg/adapter/mtping/pingsource.go index 6bae4b1fecd..4b590e6da69 100644 --- a/pkg/adapter/mtping/pingsource.go +++ b/pkg/adapter/mtping/pingsource.go @@ -22,8 +22,8 @@ import ( "knative.dev/pkg/reconciler" - "knative.dev/eventing/pkg/apis/sources/v1beta1" - pingsourcereconciler "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1beta1/pingsource" + "knative.dev/eventing/pkg/apis/sources/v1beta2" + pingsourcereconciler "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1beta2/pingsource" ) // TODO: code generation @@ -39,7 +39,7 @@ var _ pingsourcereconciler.Interface = (*Reconciler)(nil) // Check that our Reconciler implements FinalizeKind. var _ pingsourcereconciler.Finalizer = (*Reconciler)(nil) -func (r *Reconciler) ReconcileKind(ctx context.Context, source *v1beta1.PingSource) reconciler.Event { +func (r *Reconciler) ReconcileKind(ctx context.Context, source *v1beta2.PingSource) reconciler.Event { if !source.Status.IsReady() { return fmt.Errorf("warning: PingSource is not ready") } @@ -50,7 +50,7 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, source *v1beta1.PingSour return nil } -func (r *Reconciler) FinalizeKind(ctx context.Context, source *v1beta1.PingSource) reconciler.Event { +func (r *Reconciler) FinalizeKind(ctx context.Context, source *v1beta2.PingSource) reconciler.Event { // Update the adapter state r.mtadapter.Remove(ctx, source) diff --git a/pkg/adapter/mtping/pingsource_test.go b/pkg/adapter/mtping/pingsource_test.go index 73ec69c30b5..81e52ab2574 100644 --- a/pkg/adapter/mtping/pingsource_test.go +++ b/pkg/adapter/mtping/pingsource_test.go @@ -20,14 +20,17 @@ import ( "context" "testing" + cloudevents "github.com/cloudevents/sdk-go/v2" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" clientgotesting "k8s.io/client-go/testing" - sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/eventing/pkg/apis/sources/v1beta2" fakeeventingclient "knative.dev/eventing/pkg/client/injection/client/fake" - "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1beta1/pingsource" + "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1beta2/pingsource" . "knative.dev/eventing/pkg/reconciler/testing" + rttestingv1beta2 "knative.dev/eventing/pkg/reconciler/testing/v1beta2" "knative.dev/pkg/apis" duckv1 "knative.dev/pkg/apis/duck/v1" "knative.dev/pkg/configmap" @@ -41,7 +44,9 @@ const ( testNS = "test-namespace" pingSourceName = "test-pingsource" testSchedule = "*/2 * * * *" + testContentType = cloudevents.TextPlain testData = "data" + testDataBase64 = "ZGF0YQ==" sinkName = "mysink" defaultFinalizerName = "pingsources.sources.knative.dev" ) @@ -70,19 +75,72 @@ func TestAllCases(t *testing.T) { Name: "valid schedule", Key: pingsourceKey, Objects: []runtime.Object{ - NewPingSourceV1Beta1(pingSourceName, testNS, - WithPingSourceV1B1Spec(sourcesv1beta1.PingSourceSpec{ + rttestingv1beta2.NewPingSource(pingSourceName, testNS, + rttestingv1beta2.WithPingSourceSpec(v1beta2.PingSourceSpec{ + Schedule: testSchedule, + ContentType: testContentType, + Data: testData, + SourceSpec: duckv1.SourceSpec{ + Sink: sinkDest, + CloudEventOverrides: nil, + }, + }), + rttestingv1beta2.WithInitPingSourceConditions, + rttestingv1beta2.WithPingSourceDeployed, + rttestingv1beta2.WithPingSourceSink(sinkURI), + rttestingv1beta2.WithPingSourceCloudEventAttributes, + ), + }, + WantEvents: []string{ + Eventf(corev1.EventTypeNormal, "FinalizerUpdate", `Updated "%s" finalizers`, pingSourceName), + }, + WantPatches: []clientgotesting.PatchActionImpl{ + patchFinalizers(testNS, pingSourceName, defaultFinalizerName), + }, + WantErr: false, + }, { + Name: "valid schedule without contentType, data and dataBase64", + Key: pingsourceKey, + Objects: []runtime.Object{ + rttestingv1beta2.NewPingSource(pingSourceName, testNS, + rttestingv1beta2.WithPingSourceSpec(v1beta2.PingSourceSpec{ Schedule: testSchedule, - JsonData: testData, SourceSpec: duckv1.SourceSpec{ Sink: sinkDest, CloudEventOverrides: nil, }, }), - WithInitPingSourceV1B1Conditions, - WithPingSourceV1B1Deployed, - WithPingSourceV1B1Sink(sinkURI), - WithPingSourceV1B1CloudEventAttributes, + rttestingv1beta2.WithInitPingSourceConditions, + rttestingv1beta2.WithPingSourceDeployed, + rttestingv1beta2.WithPingSourceSink(sinkURI), + rttestingv1beta2.WithPingSourceCloudEventAttributes, + ), + }, + WantEvents: []string{ + Eventf(corev1.EventTypeNormal, "FinalizerUpdate", `Updated "%s" finalizers`, pingSourceName), + }, + WantPatches: []clientgotesting.PatchActionImpl{ + patchFinalizers(testNS, pingSourceName, defaultFinalizerName), + }, + WantErr: false, + }, { + Name: "valid schedule with dataBase64", + Key: pingsourceKey, + Objects: []runtime.Object{ + rttestingv1beta2.NewPingSource(pingSourceName, testNS, + rttestingv1beta2.WithPingSourceSpec(v1beta2.PingSourceSpec{ + Schedule: testSchedule, + ContentType: testContentType, + DataBase64: testDataBase64, + SourceSpec: duckv1.SourceSpec{ + Sink: sinkDest, + CloudEventOverrides: nil, + }, + }), + rttestingv1beta2.WithInitPingSourceConditions, + rttestingv1beta2.WithPingSourceDeployed, + rttestingv1beta2.WithPingSourceSink(sinkURI), + rttestingv1beta2.WithPingSourceCloudEventAttributes, ), }, WantEvents: []string{ @@ -96,20 +154,21 @@ func TestAllCases(t *testing.T) { Name: "valid schedule, with finalizer", Key: pingsourceKey, Objects: []runtime.Object{ - NewPingSourceV1Beta1(pingSourceName, testNS, - WithPingSourceV1B1Spec(sourcesv1beta1.PingSourceSpec{ - Schedule: testSchedule, - JsonData: testData, + rttestingv1beta2.NewPingSource(pingSourceName, testNS, + rttestingv1beta2.WithPingSourceSpec(v1beta2.PingSourceSpec{ + Schedule: testSchedule, + ContentType: testContentType, + Data: testData, SourceSpec: duckv1.SourceSpec{ Sink: sinkDest, CloudEventOverrides: nil, }, }), - WithInitPingSourceV1B1Conditions, - WithPingSourceV1B1Deployed, - WithPingSourceV1B1Sink(sinkURI), - WithPingSourceV1B1CloudEventAttributes, - WithPingSourceV1B1Finalizers(defaultFinalizerName), + rttestingv1beta2.WithInitPingSourceConditions, + rttestingv1beta2.WithPingSourceDeployed, + rttestingv1beta2.WithPingSourceSink(sinkURI), + rttestingv1beta2.WithPingSourceCloudEventAttributes, + rttestingv1beta2.WithPingSourceFinalizers(defaultFinalizerName), ), }, WantErr: false, @@ -117,21 +176,22 @@ func TestAllCases(t *testing.T) { Name: "valid schedule, deleted with finalizer", Key: pingsourceKey, Objects: []runtime.Object{ - NewPingSourceV1Beta1(pingSourceName, testNS, - WithPingSourceV1B1Spec(sourcesv1beta1.PingSourceSpec{ - Schedule: testSchedule, - JsonData: testData, + rttestingv1beta2.NewPingSource(pingSourceName, testNS, + rttestingv1beta2.WithPingSourceSpec(v1beta2.PingSourceSpec{ + Schedule: testSchedule, + ContentType: testContentType, + Data: testData, SourceSpec: duckv1.SourceSpec{ Sink: sinkDest, CloudEventOverrides: nil, }, }), - WithInitPingSourceV1B1Conditions, - WithPingSourceV1B1Deployed, - WithPingSourceV1B1Sink(sinkURI), - WithPingSourceV1B1CloudEventAttributes, - WithPingSourceV1B1Finalizers(defaultFinalizerName), - WithPingSourceV1B1Deleted, + rttestingv1beta2.WithInitPingSourceConditions, + rttestingv1beta2.WithPingSourceDeployed, + rttestingv1beta2.WithPingSourceSink(sinkURI), + rttestingv1beta2.WithPingSourceCloudEventAttributes, + rttestingv1beta2.WithPingSourceFinalizers(defaultFinalizerName), + rttestingv1beta2.WithPingSourceDeleted, ), }, WantEvents: []string{ @@ -145,20 +205,21 @@ func TestAllCases(t *testing.T) { Name: "valid schedule, deleted without finalizer", Key: pingsourceKey, Objects: []runtime.Object{ - NewPingSourceV1Beta1(pingSourceName, testNS, - WithPingSourceV1B1Spec(sourcesv1beta1.PingSourceSpec{ - Schedule: testSchedule, - JsonData: testData, + rttestingv1beta2.NewPingSource(pingSourceName, testNS, + rttestingv1beta2.WithPingSourceSpec(v1beta2.PingSourceSpec{ + Schedule: testSchedule, + ContentType: testContentType, + Data: testData, SourceSpec: duckv1.SourceSpec{ Sink: sinkDest, CloudEventOverrides: nil, }, }), - WithInitPingSourceV1B1Conditions, - WithPingSourceV1B1Deployed, - WithPingSourceV1B1Sink(sinkURI), - WithPingSourceV1B1CloudEventAttributes, - WithPingSourceV1B1Deleted, + rttestingv1beta2.WithInitPingSourceConditions, + rttestingv1beta2.WithPingSourceDeployed, + rttestingv1beta2.WithPingSourceSink(sinkURI), + rttestingv1beta2.WithPingSourceCloudEventAttributes, + rttestingv1beta2.WithPingSourceDeleted, ), }, WantErr: false, @@ -170,7 +231,7 @@ func TestAllCases(t *testing.T) { table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler { r := &Reconciler{mtadapter: testAdapter{}} return pingsource.NewReconciler(ctx, logging.FromContext(ctx), - fakeeventingclient.Get(ctx), listers.GetPingSourceV1beta1Lister(), + fakeeventingclient.Get(ctx), listers.GetPingSourceV1beta2Lister(), controller.GetEventRecorder(ctx), r) }, false, logger)) diff --git a/pkg/adapter/mtping/runner.go b/pkg/adapter/mtping/runner.go index b9fc65f9b85..4ddd5cd01e6 100644 --- a/pkg/adapter/mtping/runner.go +++ b/pkg/adapter/mtping/runner.go @@ -33,13 +33,13 @@ import ( kncloudevents "knative.dev/eventing/pkg/adapter/v2" "knative.dev/eventing/pkg/adapter/v2/util/crstatusevent" - sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/eventing/pkg/apis/sources/v1beta2" ) type CronJobRunner interface { Start(stopCh <-chan struct{}) Stop() - AddSchedule(source *sourcesv1beta1.PingSource) cron.EntryID + AddSchedule(source *v1beta2.PingSource) cron.EntryID RemoveSchedule(id cron.EntryID) } @@ -70,16 +70,8 @@ func NewCronJobsRunner(ceClient cloudevents.Client, kubeClient kubernetes.Interf } } -func (a *cronJobsRunner) AddSchedule(source *sourcesv1beta1.PingSource) cron.EntryID { - event := cloudevents.NewEvent() - event.SetType(sourcesv1beta1.PingSourceEventType) - event.SetSource(sourcesv1beta1.PingSourceSource(source.Namespace, source.Name)) - event.SetData(cloudevents.ApplicationJSON, makeMessage(source.Spec.JsonData)) - if source.Spec.CloudEventOverrides != nil && source.Spec.CloudEventOverrides.Extensions != nil { - for key, override := range source.Spec.CloudEventOverrides.Extensions { - event.SetExtension(key, override) - } - } +func (a *cronJobsRunner) AddSchedule(source *v1beta2.PingSource) cron.EntryID { + event := makeEvent(source) ctx := context.Background() ctx = cloudevents.ContextWithTarget(ctx, source.Status.SinkURI.String()) @@ -140,16 +132,43 @@ func (a *cronJobsRunner) cronTick(ctx context.Context, event cloudevents.Event) } } -type message struct { - Body string `json:"body"` -} +func makeEvent(source *v1beta2.PingSource) cloudevents.Event { + event := cloudevents.NewEvent() + event.SetType(v1beta2.PingSourceEventType) + event.SetSource(v1beta2.PingSourceSource(source.Namespace, source.Name)) + if source.Spec.CloudEventOverrides != nil && source.Spec.CloudEventOverrides.Extensions != nil { + for key, override := range source.Spec.CloudEventOverrides.Extensions { + event.SetExtension(key, override) + } + } -func makeMessage(body string) interface{} { - // try to marshal the body into an interface. - var objmap map[string]*json.RawMessage - if err := json.Unmarshal([]byte(body), &objmap); err != nil { - // Default to a wrapped message. - return message{Body: body} + // Set event data, at most one of data and dataBase64 exists. + // 1. If dataBase64 exists, then it's binary data, set event.DataEncoded to []byte(dataBase64) + // 2. If data exists, then it's not binary data + // a. If contentType is not `application/json`, set event.DataEncoded to []byte(data) + // b. If contentType is `application/json`, unmarshal it into an interface, event.DataEncoded will be json.Marshal(interface), + // this is to be compatible with the existing v1beta1 PingSource -> CloudEvent conversion logic, to make sure + // that `data` is populated in the cloudevent json format instead of `data_base64`, and not breaking subscribers + // that does not leverage cloudevents sdk. + var data interface{} + if source.Spec.DataBase64 != "" { + data = []byte(source.Spec.DataBase64) + } else if source.Spec.Data != "" { + switch source.Spec.ContentType { + case cloudevents.ApplicationJSON: + // unmarshal the body into an interface, JSON validation is done in pingsource_validation + // ignoring the error returned by json.Unmarshal here. + var objmap map[string]*json.RawMessage + _ = json.Unmarshal([]byte(source.Spec.Data), &objmap) + data = objmap + default: + data = []byte(source.Spec.Data) + } + } + + if data != nil { + _ = event.SetData(source.Spec.ContentType, data) } - return objmap + + return event } diff --git a/pkg/adapter/mtping/runner_test.go b/pkg/adapter/mtping/runner_test.go index 83fea5a201b..648fc124254 100644 --- a/pkg/adapter/mtping/runner_test.go +++ b/pkg/adapter/mtping/runner_test.go @@ -22,6 +22,8 @@ import ( "testing" "time" + cloudevents "github.com/cloudevents/sdk-go/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "knative.dev/pkg/apis" @@ -32,56 +34,158 @@ import ( rectesting "knative.dev/pkg/reconciler/testing" adaptertesting "knative.dev/eventing/pkg/adapter/v2/test" - sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/eventing/pkg/apis/sources/v1beta2" ) -const threeSecondsTillNextMinCronJob = 60 - 3 +const ( + threeSecondsTillNextMinCronJob = 60 - 3 + sampleData = "some data" + sampleJSONData = `{"msg":"some data"}` + sampleXmlData = "
Value" + sampleDataBase64 = "c29tZSBkYXRh" // "some data" + sampleJSONDataBase64 = "eyJtc2ciOiJzb21lIGRhdGEifQ==" // {"msg":"some data"} +) func TestAddRunRemoveSchedules(t *testing.T) { testCases := map[string]struct { - src *sourcesv1beta1.PingSource - delay time.Duration + src *v1beta2.PingSource + wantContentType string + wantData string }{ "TestAddRunRemoveSchedule": { - src: &sourcesv1beta1.PingSource{ + src: &v1beta2.PingSource{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "test-ns", }, - Spec: sourcesv1beta1.PingSourceSpec{ + Spec: v1beta2.PingSourceSpec{ SourceSpec: duckv1.SourceSpec{ CloudEventOverrides: &duckv1.CloudEventOverrides{}, }, - Schedule: "* * * * ?", - JsonData: "some data", + Schedule: "* * * * ?", + ContentType: cloudevents.TextPlain, + Data: sampleData, }, - Status: sourcesv1beta1.PingSourceStatus{ + Status: v1beta2.PingSourceStatus{ SourceStatus: duckv1.SourceStatus{ SinkURI: &apis.URL{Path: "a sink"}, }, }, }, + wantData: sampleData, + wantContentType: cloudevents.TextPlain, }, "TestAddRunRemoveScheduleWithExtensionOverride": { - src: &sourcesv1beta1.PingSource{ + src: &v1beta2.PingSource{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "test-ns", }, - Spec: sourcesv1beta1.PingSourceSpec{ + Spec: v1beta2.PingSourceSpec{ SourceSpec: duckv1.SourceSpec{ CloudEventOverrides: &duckv1.CloudEventOverrides{ Extensions: map[string]string{"1": "one", "2": "two"}, }, }, - Schedule: "* * * * ?", - JsonData: "some data", + Schedule: "* * * * ?", + ContentType: cloudevents.TextPlain, + Data: sampleData, + }, + Status: v1beta2.PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + SinkURI: &apis.URL{Path: "a sink"}, + }, + }, + }, + wantData: sampleData, + wantContentType: cloudevents.TextPlain, + }, "TestAddRunRemoveScheduleWithDataBase64": { + src: &v1beta2.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-ns", + }, + Spec: v1beta2.PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + CloudEventOverrides: &duckv1.CloudEventOverrides{}, + }, + Schedule: "* * * * ?", + ContentType: cloudevents.TextPlain, + DataBase64: sampleDataBase64, + }, + Status: v1beta2.PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + SinkURI: &apis.URL{Path: "a sink"}, + }, + }, + }, + wantData: sampleDataBase64, + wantContentType: cloudevents.TextPlain, + }, "TestAddRunRemoveScheduleWithJsonData": { + src: &v1beta2.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-ns", + }, + Spec: v1beta2.PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + CloudEventOverrides: &duckv1.CloudEventOverrides{}, + }, + Schedule: "* * * * ?", + Data: sampleJSONData, + ContentType: cloudevents.ApplicationJSON, + }, + Status: v1beta2.PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + SinkURI: &apis.URL{Path: "a sink"}, + }, + }, + }, + wantData: sampleJSONData, + wantContentType: cloudevents.ApplicationJSON, + }, "TestAddRunRemoveScheduleWithJsonDataBase64": { + src: &v1beta2.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-ns", }, - Status: sourcesv1beta1.PingSourceStatus{ + Spec: v1beta2.PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + CloudEventOverrides: &duckv1.CloudEventOverrides{}, + }, + Schedule: "* * * * ?", + DataBase64: sampleJSONDataBase64, + ContentType: cloudevents.ApplicationJSON, + }, + Status: v1beta2.PingSourceStatus{ SourceStatus: duckv1.SourceStatus{ SinkURI: &apis.URL{Path: "a sink"}, }, }, }, + wantData: sampleJSONDataBase64, + wantContentType: cloudevents.ApplicationJSON, + }, "TestAddRunRemoveScheduleWithXmlData": { + src: &v1beta2.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-ns", + }, + Spec: v1beta2.PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + CloudEventOverrides: &duckv1.CloudEventOverrides{}, + }, + Schedule: "* * * * ?", + Data: sampleXmlData, + ContentType: cloudevents.ApplicationXML, + }, + Status: v1beta2.PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + SinkURI: &apis.URL{Path: "a sink"}, + }, + }, + }, + wantData: sampleXmlData, + wantContentType: cloudevents.ApplicationXML, }, } for n, tc := range testCases { @@ -100,7 +204,7 @@ func TestAddRunRemoveSchedules(t *testing.T) { entry.Job.Run() - validateSent(t, ce, `{"body":"some data"}`, tc.src.Spec.CloudEventOverrides.Extensions) + validateSent(t, ce, tc.wantData, tc.wantContentType, tc.src.Spec.CloudEventOverrides.Extensions) runner.RemoveSchedule(entryId) @@ -153,19 +257,20 @@ func TestStartStopCronDelayWait(t *testing.T) { go func() { runner.AddSchedule( - &sourcesv1beta1.PingSource{ + &v1beta2.PingSource{ ObjectMeta: metav1.ObjectMeta{ Name: "test-name", Namespace: "test-ns", }, - Spec: sourcesv1beta1.PingSourceSpec{ + Spec: v1beta2.PingSourceSpec{ SourceSpec: duckv1.SourceSpec{ CloudEventOverrides: &duckv1.CloudEventOverrides{}, }, - Schedule: "* * * * *", - JsonData: "some delayed data", + Schedule: "* * * * *", + ContentType: cloudevents.TextPlain, + Data: "some delayed data", }, - Status: sourcesv1beta1.PingSourceStatus{ + Status: v1beta2.PingSourceStatus{ SourceStatus: duckv1.SourceStatus{ SinkURI: &apis.URL{Path: "a delayed sink"}, }, @@ -181,21 +286,25 @@ func TestStartStopCronDelayWait(t *testing.T) { runner.Stop() // cron job because of delay is still running. - validateSent(t, ce, `{"body":"some delayed data"}`, nil) - + validateSent(t, ce, "some delayed data", cloudevents.TextPlain, nil) } -func validateSent(t *testing.T, ce *adaptertesting.TestCloudEventsClient, wantData string, - extensions map[string]string) { +func validateSent(t *testing.T, ce *adaptertesting.TestCloudEventsClient, wantData string, wantContentType string, extensions map[string]string) { if got := len(ce.Sent()); got != 1 { t.Error("Expected 1 event to be sent, got", got) } - if got := ce.Sent()[0].Data(); string(got) != wantData { + event := ce.Sent()[0] + + if gotContentType := event.DataContentType(); gotContentType != wantContentType { + t.Errorf("Expected event with contentType=%q to be sent, got %q", wantContentType, gotContentType) + } + + if got := event.Data(); string(got) != wantData { t.Errorf("Expected %q event to be sent, got %q", wantData, got) } - gotExtensions := ce.Sent()[0].Context.GetExtensions() + gotExtensions := event.Context.GetExtensions() if extensions == nil && gotExtensions != nil { t.Error("Expected event with no extension overrides, got:", gotExtensions) diff --git a/pkg/apis/sources/v1beta1/ping_conversion.go b/pkg/apis/sources/v1beta1/ping_conversion.go index d33d937798c..bfb9d0485e2 100644 --- a/pkg/apis/sources/v1beta1/ping_conversion.go +++ b/pkg/apis/sources/v1beta1/ping_conversion.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Knative Authors. +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. @@ -18,17 +18,130 @@ package v1beta1 import ( "context" + "encoding/json" "fmt" + cloudevents "github.com/cloudevents/sdk-go/v2" + + "knative.dev/eventing/pkg/apis/sources/v1beta2" "knative.dev/pkg/apis" ) +const ( + // V1B1SpecAnnotationKey is used to indicate that a v1beta2 object is converted from v1beta1 + // also it can be used to downgrade such object to v1beta1 + V1B1SpecAnnotationKey = "pingsources.sources.knative.dev/v1beta1-spec" + + // V1B2SpecAnnotationKey is used to indicate that a v1beta1 object is converted from v1beta2 + // also it can be used to convert the v1beta1 object back to v1beta2, considering that v1beta2 introduces more features. + V1B2SpecAnnotationKey = "pingsources.sources.knative.dev/v1beta2-spec" +) + +type message struct { + Body string `json:"body"` +} + +func makeMessage(body string) ([]byte, error) { + // try to marshal the body into an interface. + var objmap map[string]*json.RawMessage + if err := json.Unmarshal([]byte(body), &objmap); err != nil { + // Default to a wrapped message. + return json.Marshal(message{Body: body}) + } + return json.Marshal(objmap) +} + // 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) +// Converts source from v1beta1.PingSource into a higher version. +func (source *PingSource) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1beta2.PingSource: + sink.ObjectMeta = source.ObjectMeta + sink.Status = v1beta2.PingSourceStatus{ + SourceStatus: source.Status.SourceStatus, + } + + // deep copy annotations to avoid mutation on source.ObjectMeta.Annotations + annotations := make(map[string]string) + for key, value := range source.GetAnnotations() { + annotations[key] = value + } + + // try to unmarshal v1beta2.PingSource.Spec from V1B2SpecAnnotationKey + v1beta2Spec, ok := annotations[V1B2SpecAnnotationKey] + if ok { + if err := json.Unmarshal([]byte(v1beta2Spec), &sink.Spec); err != nil { + ok = false + } + // we don't need this annotation in a v1beta2.PingSource object + delete(annotations, V1B2SpecAnnotationKey) + } + + // cannot unmarshal, do a normal conversion + if !ok { + sink.Spec = v1beta2.PingSourceSpec{ + SourceSpec: source.Spec.SourceSpec, + Schedule: source.Spec.Schedule, + Timezone: source.Spec.Timezone, + } + + if source.Spec.JsonData != "" { + msg, err := makeMessage(source.Spec.JsonData) + if err != nil { + return fmt.Errorf("error converting jsonData to a higher version: %v", err) + } + sink.Spec.ContentType = cloudevents.ApplicationJSON + sink.Spec.Data = string(msg) + } + } + + // marshal and store v1beta1.PingSource.Spec into V1B1SpecAnnotationKey + // this is to help if we need to convert back to v1beta1.PingSource + v1beta1Spec, _ := json.Marshal(source.Spec) + annotations[V1B1SpecAnnotationKey] = string(v1beta1Spec) + sink.SetAnnotations(annotations) + + return nil + default: + return apis.ConvertToViaProxy(ctx, source, &v1beta2.PingSource{}, 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) +// Converts obj from a higher version into v1beta1.PingSource. +func (sink *PingSource) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1beta2.PingSource: + sink.ObjectMeta = source.ObjectMeta + sink.Status = PingSourceStatus{ + SourceStatus: source.Status.SourceStatus, + } + + // deep copy annotations to avoid mutation on source.ObjectMeta.Annotations + annotations := make(map[string]string) + for key, value := range source.GetAnnotations() { + annotations[key] = value + } + + // v1beta2 objects originally converted from v1beta1 is expected to have this annotation + v1beta1Spec, ok := annotations[V1B1SpecAnnotationKey] + + if ok { + if err := json.Unmarshal([]byte(v1beta1Spec), &sink.Spec); err != nil { + return fmt.Errorf("error unmarshalling annotation %v=%v into %T: %v", V1B1SpecAnnotationKey, v1beta1Spec, sink.Spec, err) + } + // we don't need this annotation in a v1beta1.PingSource object + delete(annotations, V1B1SpecAnnotationKey) + } + + // marshal and store v1beta2.PingSource.Spec into V1B2SpecAnnotationKey + // this is to help if we need to convert back to v1beta2.PingSource + v1beta2Configuration, _ := json.Marshal(source.Spec) + annotations[V1B2SpecAnnotationKey] = string(v1beta2Configuration) + sink.SetAnnotations(annotations) + + return nil + default: + return apis.ConvertFromViaProxy(ctx, source, &v1beta2.PingSource{}, sink) + } } diff --git a/pkg/apis/sources/v1beta1/ping_conversion_test.go b/pkg/apis/sources/v1beta1/ping_conversion_test.go index 7cef1b2dca1..bd3cd8f57ca 100644 --- a/pkg/apis/sources/v1beta1/ping_conversion_test.go +++ b/pkg/apis/sources/v1beta1/ping_conversion_test.go @@ -19,6 +19,14 @@ package v1beta1 import ( "context" "testing" + + cloudevents "github.com/cloudevents/sdk-go/v2" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/eventing/pkg/apis/sources/v1beta2" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" ) func TestPingSourceConversionBadType(t *testing.T) { @@ -32,3 +40,349 @@ func TestPingSourceConversionBadType(t *testing.T) { t.Error("ConvertFrom() = nil, wanted error") } } + +// This tests round tripping from v1beta1 -> a higher version and back to v1beta1. +func TestPingSourceConversionRoundTripUp(t *testing.T) { + versions := []apis.Convertible{&v1beta2.PingSource{}} + + path := apis.HTTP("") + path.Path = "/path" + + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + + meta := metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + } + + tests := []struct { + name string + in *PingSource + }{{ + "empty", + &PingSource{ + ObjectMeta: meta, + Spec: PingSourceSpec{}, + Status: PingSourceStatus{}, + }, + }, { + "simple configuration", + &PingSource{ + ObjectMeta: meta, + Spec: PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + }, + Status: PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, { + "full with valid jsonData", + &PingSource{ + ObjectMeta: meta, + Spec: PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Schedule: "* * * * *", + JsonData: `{"msg":"hey"}`, + }, + Status: PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, { + "full with jsonData that cannot be unmarshalled", + &PingSource{ + ObjectMeta: meta, + Spec: PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Schedule: "* * * * *", + JsonData: "hello", + }, + Status: PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, { + "full with invalid v1beta2 spec annotation", + &PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + Annotations: map[string]string{ + V1B2SpecAnnotationKey: "$$ invalid json $$", + }, + }, + Spec: PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Schedule: "* * * * *", + JsonData: "hello", + }, + Status: PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }} + + for _, test := range tests { + for _, version := range versions { + t.Run(test.name, func(t *testing.T) { + ver := version + if err := test.in.ConvertTo(context.Background(), ver); err != nil { + t.Error("ConvertTo() =", err) + } + + got := &PingSource{} + + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Error("ConvertFrom() =", err) + } + + if diff := diffIgnoringAnnotations(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} + +// This tests round tripping from a higher version -> v1beta1 and back to the higher version. +func TestPingSourceConversionRoundTripDown(t *testing.T) { + path := apis.HTTP("") + path.Path = "/path" + + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + + ceOverrides := duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + } + + ceAttributes := []duckv1.CloudEventAttributes{{ + Type: PingSourceEventType, + Source: PingSourceSource("ping-ns", "ping-name"), + }} + + tests := []struct { + name string + in *v1beta2.PingSource + }{{name: "empty", + in: &v1beta2.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta2.PingSourceSpec{}, + Status: v1beta2.PingSourceStatus{}, + }, + }, {name: "simple configuration", + in: &v1beta2.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta2.PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + }, + Status: v1beta2.PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full: v1beta1 annotation contains valid json data", + in: &v1beta2.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta2.PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Schedule: "1 2 3 4 5", + Timezone: "Knative/Land", + ContentType: cloudevents.ApplicationJSON, + Data: `{"foo":"bar"}`, + }, + Status: v1beta2.PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + CloudEventAttributes: ceAttributes, + }, + }, + }, + }, {name: "full: v1beta1 annotation contains invalid json data", + in: &v1beta2.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta2.PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Schedule: "1 2 3 4 5", + Timezone: "Knative/Land", + ContentType: cloudevents.ApplicationJSON, + Data: `{"body":"hello"}`, + }, + Status: v1beta2.PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + CloudEventAttributes: ceAttributes, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + down := &PingSource{} + if err := down.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertTo() =", err) + } + + got := &v1beta2.PingSource{} + + if err := down.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := diffIgnoringAnnotations(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } +} + +// returns the diff of want and got, but ignoring the difference of annotations. +func diffIgnoringAnnotations(want metav1.Object, got metav1.Object) string { + want.SetAnnotations(nil) + got.SetAnnotations(nil) + return cmp.Diff(want, got) +} + +func TestPingSourceConversionFromHigherVersionNotDowngradable(t *testing.T) { + tests := []struct { + name string + in apis.Convertible + }{{name: "v1beta1 spec annotation is invalid json", + in: &v1beta2.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + Annotations: map[string]string{ + V1B1SpecAnnotationKey: "$$ invalid json $$", + }, + }, + Spec: v1beta2.PingSourceSpec{}, + Status: v1beta2.PingSourceStatus{}, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + out := &PingSource{} + if err := out.ConvertFrom(context.Background(), test.in); err == nil { + t.Errorf("ConvertFrom() = %#v, wanted error", out) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta2/doc.go b/pkg/apis/sources/v1beta2/doc.go new file mode 100644 index 00000000000..1454b38ba2d --- /dev/null +++ b/pkg/apis/sources/v1beta2/doc.go @@ -0,0 +1,20 @@ +/* +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 v1beta2 contains API Schema definitions for the sources v1beta2 API group. +// +k8s:deepcopy-gen=package +// +groupName=sources.knative.dev +package v1beta2 diff --git a/pkg/apis/sources/v1beta2/implements_test.go b/pkg/apis/sources/v1beta2/implements_test.go new file mode 100644 index 00000000000..2cb7a8c102f --- /dev/null +++ b/pkg/apis/sources/v1beta2/implements_test.go @@ -0,0 +1,39 @@ +/* +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 v1beta2 + +import ( + "testing" + + "knative.dev/pkg/apis/duck" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestTypesImplements(t *testing.T) { + testCases := []struct { + instance interface{} + iface duck.Implementable + }{ + {instance: &PingSource{}, iface: &duckv1.Conditions{}}, + {instance: &PingSource{}, iface: &duckv1.Source{}}, + } + for _, tc := range testCases { + if err := duck.VerifyType(tc.instance, tc.iface); err != nil { + t.Error(err) + } + } +} diff --git a/pkg/apis/sources/v1beta2/ping_conversion.go b/pkg/apis/sources/v1beta2/ping_conversion.go new file mode 100644 index 00000000000..17fb38b6208 --- /dev/null +++ b/pkg/apis/sources/v1beta2/ping_conversion.go @@ -0,0 +1,36 @@ +/* +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 v1beta2 + +import ( + "context" + "fmt" + + "knative.dev/pkg/apis" +) + +// ConvertTo implements apis.Convertible +// Converts source from v1beta2.PingSource into a higher version. +func (source *PingSource) ConvertTo(ctx context.Context, sink apis.Convertible) error { + return fmt.Errorf("v1beta2 is the highest known version, got: %T", sink) +} + +// ConvertFrom implements apis.Convertible +// Converts source from a higher version into v1beta2.PingSource +func (sink *PingSource) ConvertFrom(ctx context.Context, source apis.Convertible) error { + return fmt.Errorf("v1beta2 is the highest known version, got: %T", source) +} diff --git a/pkg/apis/sources/v1beta2/ping_conversion_test.go b/pkg/apis/sources/v1beta2/ping_conversion_test.go new file mode 100644 index 00000000000..cfa191feb57 --- /dev/null +++ b/pkg/apis/sources/v1beta2/ping_conversion_test.go @@ -0,0 +1,48 @@ +/* +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 v1beta2 + +import ( + "context" + "errors" + "testing" + + "knative.dev/pkg/apis" +) + +// implement apis.Convertible +type dummyObject struct{} + +func (*dummyObject) ConvertTo(ctx context.Context, obj apis.Convertible) error { + return errors.New("Won't go") +} + +func (*dummyObject) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + return errors.New("Won't go") +} + +func TestPingSourceConversionBadType(t *testing.T) { + good, bad := &PingSource{}, &dummyObject{} + + 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/v1beta2/ping_defaults.go b/pkg/apis/sources/v1beta2/ping_defaults.go new file mode 100644 index 00000000000..8078c36d11f --- /dev/null +++ b/pkg/apis/sources/v1beta2/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 v1beta2 + +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/v1beta2/ping_defaults_test.go b/pkg/apis/sources/v1beta2/ping_defaults_test.go new file mode 100644 index 00000000000..276b9701159 --- /dev/null +++ b/pkg/apis/sources/v1beta2/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 v1beta2 + +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.Fatal("Unexpected defaults (-want, +got):", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta2/ping_lifecycle.go b/pkg/apis/sources/v1beta2/ping_lifecycle.go new file mode 100644 index 00000000000..76da4136867 --- /dev/null +++ b/pkg/apis/sources/v1beta2/ping_lifecycle.go @@ -0,0 +1,122 @@ +/* +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 v1beta2 + +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) + +const ( + // PingSourceEventType is the default PingSource CloudEvent type. + PingSourceEventType = "dev.knative.sources.ping" +) + +// 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/v1beta2/ping_lifecycle_test.go b/pkg/apis/sources/v1beta2/ping_lifecycle_test.go new file mode 100644 index 00000000000..170806cd8dc --- /dev/null +++ b/pkg/apis/sources/v1beta2/ping_lifecycle_test.go @@ -0,0 +1,279 @@ +/* +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 v1beta2 + +import ( + "testing" + + appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + corev1 "k8s.io/api/core/v1" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "knative.dev/pkg/apis" +) + +var ( + availableDeployment = &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "available", + }, + Status: appsv1.DeploymentStatus{ + Conditions: []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentAvailable, + Status: corev1.ConditionTrue, + }, + }, + }, + } +) + +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.Error("Should be PingSource.") + } +} + +func TestPingSource_PingSourceSource(t *testing.T) { + cePingSource := PingSourceSource("ns1", "job1") + + if cePingSource != "/apis/v1/namespaces/ns1/pingsources/job1" { + t.Error("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(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 sink and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + 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(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 sink and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + 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.Error("unexpected condition (-want, +got) =", 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(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, + }, + }} + + 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.Error("unexpected condition (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta2/ping_types.go b/pkg/apis/sources/v1beta2/ping_types.go new file mode 100644 index 00000000000..414a27cedcc --- /dev/null +++ b/pkg/apis/sources/v1beta2/ping_types.go @@ -0,0 +1,110 @@ +/* +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 v1beta2 + +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 cron 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 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"` + + // ContentType is the media type of Data or DataBase64. Default is empty + // +optional + ContentType string `json:"contentType,omitempty"` + + // Data is data used as the body of the event posted to the sink. Default is empty. + // Mutually exclusive with DataBase64. + // +optional + Data string `json:"data,omitempty"` + + // DataBase64 is base64 encoded binary data used as the body of the event posted to the sink. Default is empty. + // Mutually exclusive with Data. + // +optional + DataBase64 string `json:"dataBase64,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/v1beta2/ping_types_test.go b/pkg/apis/sources/v1beta2/ping_types_test.go new file mode 100644 index 00000000000..91df1640c32 --- /dev/null +++ b/pkg/apis/sources/v1beta2/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 v1beta2 + +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/v1beta2/ping_validation.go b/pkg/apis/sources/v1beta2/ping_validation.go new file mode 100644 index 00000000000..dfab0499cbc --- /dev/null +++ b/pkg/apis/sources/v1beta2/ping_validation.go @@ -0,0 +1,86 @@ +/* +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 v1beta2 + +import ( + "context" + "encoding/base64" + "encoding/json" + "strings" + + cloudevents "github.com/cloudevents/sdk-go/v2" + + "github.com/robfig/cron/v3" + "knative.dev/pkg/apis" +) + +func (c *PingSource) Validate(ctx context.Context) *apis.FieldError { + 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(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 { + errs = errs.Also(fe.ViaField("sink")) + } + + if cs.Data != "" && cs.DataBase64 != "" { + errs = errs.Also(apis.ErrMultipleOneOf("data", "dataBase64")) + } else { + if cs.DataBase64 != "" { + decoded, err := base64.StdEncoding.DecodeString(cs.DataBase64) + // invalid base64 string + if err != nil { + errs = errs.Also(apis.ErrInvalidValue(err, "dataBase64")) + } else { + // validate if the decoded base64 string is valid JSON + if cs.ContentType == cloudevents.ApplicationJSON { + if err := validateJSON(string(decoded)); err != nil { + errs = errs.Also(apis.ErrInvalidValue(err, "dataBase64")) + } + } + } + } else if cs.Data != "" && cs.ContentType == cloudevents.ApplicationJSON { + // validate if data is valid JSON + if err := validateJSON(cs.Data); err != nil { + errs = errs.Also(apis.ErrInvalidValue(err, "data")) + } + } + } + return errs +} + +func validateJSON(str string) error { + var objmap map[string]interface{} + return json.Unmarshal([]byte(str), &objmap) +} diff --git a/pkg/apis/sources/v1beta2/ping_validation_test.go b/pkg/apis/sources/v1beta2/ping_validation_test.go new file mode 100644 index 00000000000..96b2c8cac3f --- /dev/null +++ b/pkg/apis/sources/v1beta2/ping_validation_test.go @@ -0,0 +1,274 @@ +/* +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 v1beta2 + +import ( + "context" + "encoding/base64" + "testing" + + cloudevents "github.com/cloudevents/sdk-go/v2" + + 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: "v1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: nil, + }, { + name: "valid spec with timezone", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + Timezone: "Europe/Paris", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: nil, + }, { + name: "valid spec with invalid timezone", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + Timezone: "Knative/Land", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + 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{ + 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: "v1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrInvalidValue("expected exactly 5 fields, found 1: [2]", "spec.schedule") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "valid spec with data", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + ContentType: "application/json", + Data: `{"msg": "Hello World"}`, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: nil, + }, { + name: "valid spec with dataBase64", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + ContentType: cloudevents.TextPlain, + DataBase64: "SGVsbG8gV29ybGQu", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: nil, + }, { + name: "invalid spec with data and dataBase64 both set", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + ContentType: cloudevents.TextPlain, + Data: "Hello world", + DataBase64: "SGVsbG8gV29ybGQu", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrMultipleOneOf("spec.data", "spec.dataBase64") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "invalid spec: dataBase64 is invalid base64 string", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + ContentType: cloudevents.TextPlain, + DataBase64: "$$$ invalid base64 string $$$", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrInvalidValue("illegal base64 data at input byte 0", "spec.dataBase64") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "invalid spec: contentType=application/json, data is invalid JSON format", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + ContentType: cloudevents.ApplicationJSON, + Data: "Invalid JSON", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrInvalidValue("invalid character 'I' looking for beginning of value", "spec.data") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "invalid spec: contentType=application/json, decoded dataBase64 is invalid JSON format", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + ContentType: cloudevents.ApplicationJSON, + DataBase64: base64.StdEncoding.EncodeToString([]byte("Invalid JSON")), + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrInvalidValue("invalid character 'I' looking for beginning of value", "spec.dataBase64") + 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.Error("PingSourceSpec.Validate (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta2/register.go b/pkg/apis/sources/v1beta2/register.go new file mode 100644 index 00000000000..aa10f1f697c --- /dev/null +++ b/pkg/apis/sources/v1beta2/register.go @@ -0,0 +1,53 @@ +/* +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 v1beta2 + +import ( + "knative.dev/eventing/pkg/apis/sources" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: sources.GroupName, Version: "v1beta2"} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &PingSource{}, + &PingSourceList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/apis/sources/v1beta2/register_test.go b/pkg/apis/sources/v1beta2/register_test.go new file mode 100644 index 00000000000..12f56dc50ec --- /dev/null +++ b/pkg/apis/sources/v1beta2/register_test.go @@ -0,0 +1,71 @@ +/* +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 v1beta2 + +import ( + "testing" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/google/go-cmp/cmp" +) + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func TestResource(t *testing.T) { + want := schema.GroupResource{ + Group: "sources.knative.dev", + Resource: "foo", + } + + got := Resource("foo") + + if diff := cmp.Diff(want, got); diff != "" { + t.Error("unexpected resource (-want, +got) =", diff) + } +} + +// Kind takes an unqualified resource and returns a Group qualified GroupKind +func TestKind(t *testing.T) { + want := schema.GroupKind{ + Group: "sources.knative.dev", + Kind: "kind", + } + + got := Kind("kind") + + if diff := cmp.Diff(want, got); diff != "" { + t.Error("unexpected resource (-want, +got) =", diff) + } +} + +// TestKnownTypes makes sure that expected types get added. +func TestKnownTypes(t *testing.T) { + scheme := runtime.NewScheme() + addKnownTypes(scheme) + types := scheme.KnownTypes(SchemeGroupVersion) + + for _, name := range []string{ + "PingSource", + "PingSourceList", + } { + if _, ok := types[name]; !ok { + t.Errorf("Did not find %q as registered type", name) + } + } + +} diff --git a/pkg/apis/sources/v1beta2/zz_generated.deepcopy.go b/pkg/apis/sources/v1beta2/zz_generated.deepcopy.go new file mode 100644 index 00000000000..b04af992dc4 --- /dev/null +++ b/pkg/apis/sources/v1beta2/zz_generated.deepcopy.go @@ -0,0 +1,120 @@ +// +build !ignore_autogenerated + +/* +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 deepcopy-gen. DO NOT EDIT. + +package v1beta2 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *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 +} diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index dde42d32304..dfce533a248 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -35,6 +35,7 @@ import ( sourcesv1alpha1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha1" sourcesv1alpha2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha2" sourcesv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1beta1" + sourcesv1beta2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1beta2" ) type Interface interface { @@ -49,6 +50,7 @@ type Interface interface { SourcesV1alpha1() sourcesv1alpha1.SourcesV1alpha1Interface SourcesV1alpha2() sourcesv1alpha2.SourcesV1alpha2Interface SourcesV1beta1() sourcesv1beta1.SourcesV1beta1Interface + SourcesV1beta2() sourcesv1beta2.SourcesV1beta2Interface SourcesV1() sourcesv1.SourcesV1Interface } @@ -66,6 +68,7 @@ type Clientset struct { sourcesV1alpha1 *sourcesv1alpha1.SourcesV1alpha1Client sourcesV1alpha2 *sourcesv1alpha2.SourcesV1alpha2Client sourcesV1beta1 *sourcesv1beta1.SourcesV1beta1Client + sourcesV1beta2 *sourcesv1beta2.SourcesV1beta2Client sourcesV1 *sourcesv1.SourcesV1Client } @@ -119,6 +122,11 @@ func (c *Clientset) SourcesV1beta1() sourcesv1beta1.SourcesV1beta1Interface { return c.sourcesV1beta1 } +// SourcesV1beta2 retrieves the SourcesV1beta2Client +func (c *Clientset) SourcesV1beta2() sourcesv1beta2.SourcesV1beta2Interface { + return c.sourcesV1beta2 +} + // SourcesV1 retrieves the SourcesV1Client func (c *Clientset) SourcesV1() sourcesv1.SourcesV1Interface { return c.sourcesV1 @@ -185,6 +193,10 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { if err != nil { return nil, err } + cs.sourcesV1beta2, err = sourcesv1beta2.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } cs.sourcesV1, err = sourcesv1.NewForConfig(&configShallowCopy) if err != nil { return nil, err @@ -211,6 +223,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { cs.sourcesV1alpha1 = sourcesv1alpha1.NewForConfigOrDie(c) cs.sourcesV1alpha2 = sourcesv1alpha2.NewForConfigOrDie(c) cs.sourcesV1beta1 = sourcesv1beta1.NewForConfigOrDie(c) + cs.sourcesV1beta2 = sourcesv1beta2.NewForConfigOrDie(c) cs.sourcesV1 = sourcesv1.NewForConfigOrDie(c) cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) @@ -230,6 +243,7 @@ func New(c rest.Interface) *Clientset { cs.sourcesV1alpha1 = sourcesv1alpha1.New(c) cs.sourcesV1alpha2 = sourcesv1alpha2.New(c) cs.sourcesV1beta1 = sourcesv1beta1.New(c) + cs.sourcesV1beta2 = sourcesv1beta2.New(c) cs.sourcesV1 = sourcesv1.New(c) cs.DiscoveryClient = discovery.NewDiscoveryClient(c) diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go index 6df1029f433..953d3ed9d9e 100644 --- a/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -47,6 +47,8 @@ import ( fakesourcesv1alpha2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake" sourcesv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1beta1" fakesourcesv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1beta1/fake" + sourcesv1beta2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1beta2" + fakesourcesv1beta2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1beta2/fake" ) // NewSimpleClientset returns a clientset that will respond with the provided objects. @@ -146,6 +148,11 @@ func (c *Clientset) SourcesV1beta1() sourcesv1beta1.SourcesV1beta1Interface { return &fakesourcesv1beta1.FakeSourcesV1beta1{Fake: &c.Fake} } +// SourcesV1beta2 retrieves the SourcesV1beta2Client +func (c *Clientset) SourcesV1beta2() sourcesv1beta2.SourcesV1beta2Interface { + return &fakesourcesv1beta2.FakeSourcesV1beta2{Fake: &c.Fake} +} + // SourcesV1 retrieves the SourcesV1Client func (c *Clientset) SourcesV1() sourcesv1.SourcesV1Interface { return &fakesourcesv1.FakeSourcesV1{Fake: &c.Fake} diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go index 5ecd3477cc2..9b657a73224 100644 --- a/pkg/client/clientset/versioned/fake/register.go +++ b/pkg/client/clientset/versioned/fake/register.go @@ -35,6 +35,7 @@ import ( sourcesv1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" sourcesv1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" ) var scheme = runtime.NewScheme() @@ -51,6 +52,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{ sourcesv1alpha1.AddToScheme, sourcesv1alpha2.AddToScheme, sourcesv1beta1.AddToScheme, + sourcesv1beta2.AddToScheme, sourcesv1.AddToScheme, } diff --git a/pkg/client/clientset/versioned/scheme/register.go b/pkg/client/clientset/versioned/scheme/register.go index 5f61a7a1a72..0f004eade7c 100644 --- a/pkg/client/clientset/versioned/scheme/register.go +++ b/pkg/client/clientset/versioned/scheme/register.go @@ -35,6 +35,7 @@ import ( sourcesv1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" sourcesv1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" ) var Scheme = runtime.NewScheme() @@ -51,6 +52,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{ sourcesv1alpha1.AddToScheme, sourcesv1alpha2.AddToScheme, sourcesv1beta1.AddToScheme, + sourcesv1beta2.AddToScheme, sourcesv1.AddToScheme, } diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta2/doc.go b/pkg/client/clientset/versioned/typed/sources/v1beta2/doc.go new file mode 100644 index 00000000000..6e62cd8f186 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta2/doc.go @@ -0,0 +1,20 @@ +/* +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. + +// This package has the automatically generated typed clients. +package v1beta2 diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta2/fake/doc.go b/pkg/client/clientset/versioned/typed/sources/v1beta2/fake/doc.go new file mode 100644 index 00000000000..c7f6e65cab8 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta2/fake/doc.go @@ -0,0 +1,20 @@ +/* +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 has the automatically generated clients. +package fake diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta2/fake/fake_pingsource.go b/pkg/client/clientset/versioned/typed/sources/v1beta2/fake/fake_pingsource.go new file mode 100644 index 00000000000..38a5311b6d5 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta2/fake/fake_pingsource.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 client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + 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" + v1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" +) + +// FakePingSources implements PingSourceInterface +type FakePingSources struct { + Fake *FakeSourcesV1beta2 + ns string +} + +var pingsourcesResource = schema.GroupVersionResource{Group: "sources.knative.dev", Version: "v1beta2", Resource: "pingsources"} + +var pingsourcesKind = schema.GroupVersionKind{Group: "sources.knative.dev", Version: "v1beta2", 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(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(pingsourcesResource, c.ns, name), &v1beta2.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta2.PingSource), err +} + +// List takes label and field selectors, and returns the list of PingSources that match those selectors. +func (c *FakePingSources) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.PingSourceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(pingsourcesResource, pingsourcesKind, c.ns, opts), &v1beta2.PingSourceList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta2.PingSourceList{ListMeta: obj.(*v1beta2.PingSourceList).ListMeta} + for _, item := range obj.(*v1beta2.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(ctx context.Context, 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(ctx context.Context, pingSource *v1beta2.PingSource, opts v1.CreateOptions) (result *v1beta2.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(pingsourcesResource, c.ns, pingSource), &v1beta2.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta2.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(ctx context.Context, pingSource *v1beta2.PingSource, opts v1.UpdateOptions) (result *v1beta2.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(pingsourcesResource, c.ns, pingSource), &v1beta2.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta2.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(ctx context.Context, pingSource *v1beta2.PingSource, opts v1.UpdateOptions) (*v1beta2.PingSource, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(pingsourcesResource, "status", c.ns, pingSource), &v1beta2.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta2.PingSource), err +} + +// Delete takes name of the pingSource and deletes it. Returns an error if one occurs. +func (c *FakePingSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(pingsourcesResource, c.ns, name), &v1beta2.PingSource{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakePingSources) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(pingsourcesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta2.PingSourceList{}) + return err +} + +// Patch applies the patch and returns the patched pingSource. +func (c *FakePingSources) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(pingsourcesResource, c.ns, name, pt, data, subresources...), &v1beta2.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta2.PingSource), err +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta2/fake/fake_sources_client.go b/pkg/client/clientset/versioned/typed/sources/v1beta2/fake/fake_sources_client.go new file mode 100644 index 00000000000..53382bb2bc2 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta2/fake/fake_sources_client.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 client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + v1beta2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1beta2" +) + +type FakeSourcesV1beta2 struct { + *testing.Fake +} + +func (c *FakeSourcesV1beta2) PingSources(namespace string) v1beta2.PingSourceInterface { + return &FakePingSources{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeSourcesV1beta2) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta2/generated_expansion.go b/pkg/client/clientset/versioned/typed/sources/v1beta2/generated_expansion.go new file mode 100644 index 00000000000..6b530e5b366 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta2/generated_expansion.go @@ -0,0 +1,21 @@ +/* +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 v1beta2 + +type PingSourceExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta2/pingsource.go b/pkg/client/clientset/versioned/typed/sources/v1beta2/pingsource.go new file mode 100644 index 00000000000..76d9b4fce8a --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta2/pingsource.go @@ -0,0 +1,195 @@ +/* +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 v1beta2 + +import ( + "context" + "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" + v1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" + 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(ctx context.Context, pingSource *v1beta2.PingSource, opts v1.CreateOptions) (*v1beta2.PingSource, error) + Update(ctx context.Context, pingSource *v1beta2.PingSource, opts v1.UpdateOptions) (*v1beta2.PingSource, error) + UpdateStatus(ctx context.Context, pingSource *v1beta2.PingSource, opts v1.UpdateOptions) (*v1beta2.PingSource, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta2.PingSource, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta2.PingSourceList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.PingSource, err error) + PingSourceExpansion +} + +// pingSources implements PingSourceInterface +type pingSources struct { + client rest.Interface + ns string +} + +// newPingSources returns a PingSources +func newPingSources(c *SourcesV1beta2Client, 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(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.PingSource, err error) { + result = &v1beta2.PingSource{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pingsources"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of PingSources that match those selectors. +func (c *pingSources) List(ctx context.Context, opts v1.ListOptions) (result *v1beta2.PingSourceList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta2.PingSourceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pingsources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested pingSources. +func (c *pingSources) Watch(ctx context.Context, 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(ctx) +} + +// 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(ctx context.Context, pingSource *v1beta2.PingSource, opts v1.CreateOptions) (result *v1beta2.PingSource, err error) { + result = &v1beta2.PingSource{} + err = c.client.Post(). + Namespace(c.ns). + Resource("pingsources"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(pingSource). + Do(ctx). + 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(ctx context.Context, pingSource *v1beta2.PingSource, opts v1.UpdateOptions) (result *v1beta2.PingSource, err error) { + result = &v1beta2.PingSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("pingsources"). + Name(pingSource.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(pingSource). + Do(ctx). + 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(ctx context.Context, pingSource *v1beta2.PingSource, opts v1.UpdateOptions) (result *v1beta2.PingSource, err error) { + result = &v1beta2.PingSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("pingsources"). + Name(pingSource.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(pingSource). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the pingSource and deletes it. Returns an error if one occurs. +func (c *pingSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("pingsources"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *pingSources) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("pingsources"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched pingSource. +func (c *pingSources) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta2.PingSource, err error) { + result = &v1beta2.PingSource{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("pingsources"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta2/sources_client.go b/pkg/client/clientset/versioned/typed/sources/v1beta2/sources_client.go new file mode 100644 index 00000000000..4a94b730550 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta2/sources_client.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 client-gen. DO NOT EDIT. + +package v1beta2 + +import ( + rest "k8s.io/client-go/rest" + v1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" + "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +type SourcesV1beta2Interface interface { + RESTClient() rest.Interface + PingSourcesGetter +} + +// SourcesV1beta2Client is used to interact with features provided by the sources.knative.dev group. +type SourcesV1beta2Client struct { + restClient rest.Interface +} + +func (c *SourcesV1beta2Client) PingSources(namespace string) PingSourceInterface { + return newPingSources(c, namespace) +} + +// NewForConfig creates a new SourcesV1beta2Client for the given config. +func NewForConfig(c *rest.Config) (*SourcesV1beta2Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &SourcesV1beta2Client{client}, nil +} + +// NewForConfigOrDie creates a new SourcesV1beta2Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *SourcesV1beta2Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new SourcesV1beta2Client for the given RESTClient. +func New(c rest.Interface) *SourcesV1beta2Client { + return &SourcesV1beta2Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1beta2.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *SourcesV1beta2Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index c0d8778324d..411984acc4b 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -34,6 +34,7 @@ import ( sourcesv1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + v1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" ) // GenericInformer is type of SharedIndexInformer which will locate and delegate to other @@ -142,6 +143,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case sourcesv1beta1.SchemeGroupVersion.WithResource("sinkbindings"): return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1beta1().SinkBindings().Informer()}, nil + // Group=sources.knative.dev, Version=v1beta2 + case v1beta2.SchemeGroupVersion.WithResource("pingsources"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1beta2().PingSources().Informer()}, nil + } return nil, fmt.Errorf("no informer found for %v", resource) diff --git a/pkg/client/informers/externalversions/sources/interface.go b/pkg/client/informers/externalversions/sources/interface.go index e51d7361297..c6432a53865 100644 --- a/pkg/client/informers/externalversions/sources/interface.go +++ b/pkg/client/informers/externalversions/sources/interface.go @@ -24,6 +24,7 @@ import ( v1alpha1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha1" v1alpha2 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2" v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1" + v1beta2 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta2" ) // Interface provides access to each of this group's versions. @@ -34,6 +35,8 @@ type Interface interface { V1alpha2() v1alpha2.Interface // V1beta1 provides access to shared informers for resources in V1beta1. V1beta1() v1beta1.Interface + // V1beta2 provides access to shared informers for resources in V1beta2. + V1beta2() v1beta2.Interface // V1 provides access to shared informers for resources in V1. V1() v1.Interface } @@ -64,6 +67,11 @@ func (g *group) V1beta1() v1beta1.Interface { return v1beta1.New(g.factory, g.namespace, g.tweakListOptions) } +// V1beta2 returns a new v1beta2.Interface. +func (g *group) V1beta2() v1beta2.Interface { + return v1beta2.New(g.factory, g.namespace, g.tweakListOptions) +} + // V1 returns a new v1.Interface. func (g *group) V1() v1.Interface { return v1.New(g.factory, g.namespace, g.tweakListOptions) diff --git a/pkg/client/informers/externalversions/sources/v1beta2/interface.go b/pkg/client/informers/externalversions/sources/v1beta2/interface.go new file mode 100644 index 00000000000..0bd4b25c787 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1beta2/interface.go @@ -0,0 +1,45 @@ +/* +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 v1beta2 + +import ( + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // PingSources returns a PingSourceInformer. + PingSources() PingSourceInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// PingSources returns a PingSourceInformer. +func (v *version) PingSources() PingSourceInformer { + return &pingSourceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/sources/v1beta2/pingsource.go b/pkg/client/informers/externalversions/sources/v1beta2/pingsource.go new file mode 100644 index 00000000000..9fa7264e8c9 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1beta2/pingsource.go @@ -0,0 +1,90 @@ +/* +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 v1beta2 + +import ( + "context" + 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" + sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1beta2 "knative.dev/eventing/pkg/client/listers/sources/v1beta2" +) + +// PingSourceInformer provides access to a shared informer and lister for +// PingSources. +type PingSourceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta2.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.SourcesV1beta2().PingSources(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1beta2().PingSources(namespace).Watch(context.TODO(), options) + }, + }, + &sourcesv1beta2.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(&sourcesv1beta2.PingSource{}, f.defaultInformer) +} + +func (f *pingSourceInformer) Lister() v1beta2.PingSourceLister { + return v1beta2.NewPingSourceLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/injection/informers/sources/v1beta2/pingsource/fake/fake.go b/pkg/client/injection/informers/sources/v1beta2/pingsource/fake/fake.go new file mode 100644 index 00000000000..d5befc6534e --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta2/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/v1beta2/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().V1beta2().PingSources() + return context.WithValue(ctx, pingsource.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/sources/v1beta2/pingsource/pingsource.go b/pkg/client/injection/informers/sources/v1beta2/pingsource/pingsource.go new file mode 100644 index 00000000000..bc5256f6ca4 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta2/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" + + v1beta2 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta2" + 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().V1beta2().PingSources() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1beta2.PingSourceInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta2.PingSourceInformer from context.") + } + return untyped.(v1beta2.PingSourceInformer) +} diff --git a/pkg/client/injection/reconciler/sources/v1beta2/pingsource/controller.go b/pkg/client/injection/reconciler/sources/v1beta2/pingsource/controller.go new file mode 100644 index 00000000000..3ea5ee805c2 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta2/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/v1beta2/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.Fatal("Up to one options function is supported, found: ", 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/v1beta2/pingsource/reconciler.go b/pkg/client/injection/reconciler/sources/v1beta2/pingsource/reconciler.go new file mode 100644 index 00000000000..afc9409d4b6 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta2/pingsource/reconciler.go @@ -0,0 +1,449 @@ +/* +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" + record "k8s.io/client-go/tools/record" + v1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + sourcesv1beta2 "knative.dev/eventing/pkg/client/listers/sources/v1beta2" + controller "knative.dev/pkg/controller" + kmp "knative.dev/pkg/kmp" + logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" +) + +// Interface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta2.PingSource. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1beta2.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 *v1beta2.PingSource) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta2.PingSource. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1beta2.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 *v1beta2.PingSource) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta2.PingSource if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1beta2.PingSource. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1beta2.PingSource) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta2.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 v1beta2.PingSource. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1beta2.PingSource) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1beta2.PingSource) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1beta2.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 sourcesv1beta2.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 sourcesv1beta2.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.Fatal("Up to one options struct is supported, found: ", 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) + + // Initialize the reconciler state. This will convert the namespace/name + // string into a distinct namespace and name, determine 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.Error("Invalid resource key: ", key) + return nil + } + + // 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 + } + + // 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(s.namespace) + + original, err := getter.Get(s.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 + + name, do := s.reconcileMethodFor(resource) + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", name)) + switch name { + case reconciler.DoReconcileKind: + // 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) + } + + if !r.skipStatusUpdates { + 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 = do(ctx, resource) + + if !r.skipStatusUpdates { + reconciler.PostProcessReconcile(ctx, resource, original) + } + + case reconciler.DoFinalizeKind: + // For finalizing reconcilers, if this resource being marked for deletion + // and reconciled cleanly (nil or normal event), remove the finalizer. + reconcileEvent = do(ctx, resource) + + if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { + return fmt.Errorf("failed to clear finalizers: %w", err) + } + + case reconciler.DoObserveKind, reconciler.DoObserveFinalizeKind: + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = do(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 !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!") + default: + if err = r.updateStatus(ctx, 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(ctx context.Context, existing *v1beta2.PingSource, desired *v1beta2.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.SourcesV1beta2().PingSources(desired.Namespace) + + existing, err = getter.Get(ctx, 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 + } + + if diff, err := kmp.SafeDiff(existing.Status, desired.Status); err == nil && diff != "" { + logging.FromContext(ctx).Debug("Updating status with: ", diff) + } + + existing.Status = desired.Status + + updater := r.Client.SourcesV1beta2().PingSources(existing.Namespace) + + _, err = updater.UpdateStatus(ctx, existing, metav1.UpdateOptions{}) + 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 *v1beta2.PingSource) (*v1beta2.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.SourcesV1beta2().PingSources(resource.Namespace) + + resourceName := resource.Name + updated, err := patcher.Patch(ctx, resourceName, types.MergePatchType, patch, metav1.PatchOptions{}) + if err != nil { + r.Recorder.Eventf(existing, v1.EventTypeWarning, "FinalizerUpdateFailed", + "Failed to update finalizers for %q: %v", resourceName, err) + } else { + r.Recorder.Eventf(updated, v1.EventTypeNormal, "FinalizerUpdate", + "Updated %q finalizers", resource.GetName()) + } + return updated, err +} + +func (r *reconcilerImpl) setFinalizerIfFinalizer(ctx context.Context, resource *v1beta2.PingSource) (*v1beta2.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 *v1beta2.PingSource, reconcileEvent reconciler.Event) (*v1beta2.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/v1beta2/pingsource/state.go b/pkg/client/injection/reconciler/sources/v1beta2/pingsource/state.go new file mode 100644 index 00000000000..7dcc4845aa0 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta2/pingsource/state.go @@ -0,0 +1,106 @@ +/* +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" + v1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" + reconciler "knative.dev/pkg/reconciler" +) + +// 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 *v1beta2.PingSource) (string, doReconcile) { + if o.GetDeletionTimestamp().IsZero() { + if s.isLeader { + return reconciler.DoReconcileKind, s.reconciler.ReconcileKind + } else if s.isROI { + return reconciler.DoObserveKind, s.roi.ObserveKind + } + } else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok { + return reconciler.DoFinalizeKind, fin.FinalizeKind + } else if !s.isLeader && s.isROF { + return reconciler.DoObserveFinalizeKind, s.rof.ObserveFinalizeKind + } + return "unknown", nil +} diff --git a/pkg/client/listers/sources/v1beta2/expansion_generated.go b/pkg/client/listers/sources/v1beta2/expansion_generated.go new file mode 100644 index 00000000000..aa9fdf32c55 --- /dev/null +++ b/pkg/client/listers/sources/v1beta2/expansion_generated.go @@ -0,0 +1,27 @@ +/* +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 v1beta2 + +// PingSourceListerExpansion allows custom methods to be added to +// PingSourceLister. +type PingSourceListerExpansion interface{} + +// PingSourceNamespaceListerExpansion allows custom methods to be added to +// PingSourceNamespaceLister. +type PingSourceNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/sources/v1beta2/pingsource.go b/pkg/client/listers/sources/v1beta2/pingsource.go new file mode 100644 index 00000000000..62bf468358e --- /dev/null +++ b/pkg/client/listers/sources/v1beta2/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 v1beta2 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" +) + +// PingSourceLister helps list PingSources. +type PingSourceLister interface { + // List lists all PingSources in the indexer. + List(selector labels.Selector) (ret []*v1beta2.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 []*v1beta2.PingSource, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta2.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 []*v1beta2.PingSource, err error) + // Get retrieves the PingSource from the indexer for a given namespace and name. + Get(name string) (*v1beta2.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 []*v1beta2.PingSource, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta2.PingSource)) + }) + return ret, err +} + +// Get retrieves the PingSource from the indexer for a given namespace and name. +func (s pingSourceNamespaceLister) Get(name string) (*v1beta2.PingSource, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta2.Resource("pingsource"), name) + } + return obj.(*v1beta2.PingSource), nil +} diff --git a/pkg/reconciler/mtbroker/trigger/trigger_test.go b/pkg/reconciler/mtbroker/trigger/trigger_test.go index 4bcfe39fdc3..edc49e3bc1c 100644 --- a/pkg/reconciler/mtbroker/trigger/trigger_test.go +++ b/pkg/reconciler/mtbroker/trigger/trigger_test.go @@ -21,6 +21,8 @@ import ( "fmt" "testing" + cloudevents "github.com/cloudevents/sdk-go/v2" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -34,7 +36,7 @@ import ( "knative.dev/eventing/pkg/apis/eventing" eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" - sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/eventing/pkg/apis/sources/v1beta2" fakeeventingclient "knative.dev/eventing/pkg/client/injection/client/fake" "knative.dev/eventing/pkg/client/injection/ducks/duck/v1/channelable" "knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1/trigger" @@ -54,8 +56,8 @@ import ( "knative.dev/pkg/resolver" _ "knative.dev/eventing/pkg/client/injection/informers/eventing/v1/trigger/fake" - rtv1alpha1 "knative.dev/eventing/pkg/reconciler/testing" . "knative.dev/eventing/pkg/reconciler/testing/v1" + rtv1beta2 "knative.dev/eventing/pkg/reconciler/testing/v1beta2" _ "knative.dev/pkg/client/injection/ducks/duck/v1/addressable/fake" . "knative.dev/pkg/reconciler/testing" ) @@ -82,9 +84,10 @@ const ( pingSourceName = "test-ping-source" testSchedule = "*/2 * * * *" + testContentType = cloudevents.TextPlain testData = "data" sinkName = "testsink" - dependencyAnnotation = `{"kind":"PingSource","name":"test-ping-source","apiVersion":"sources.knative.dev/v1beta1"}` + dependencyAnnotation = `{"kind":"PingSource","name":"test-ping-source","apiVersion":"sources.knative.dev/v1beta2"}` subscriberURIReference = "foo" subscriberResolvedTargetURI = "http://example.com/subscriber/foo" @@ -889,37 +892,38 @@ func makeFalseStatusSubscription() *messagingv1.Subscription { return s } -func makeFalseStatusPingSource() *sourcesv1beta1.PingSource { - return rtv1alpha1.NewPingSourceV1Beta1(pingSourceName, testNS, rtv1alpha1.WithPingSourceV1B1SinkNotFound) +func makeFalseStatusPingSource() *v1beta2.PingSource { + return rtv1beta2.NewPingSource(pingSourceName, testNS, rtv1beta2.WithPingSourceSinkNotFound) } -func makeUnknownStatusCronJobSource() *sourcesv1beta1.PingSource { - cjs := rtv1alpha1.NewPingSourceV1Beta1(pingSourceName, testNS) +func makeUnknownStatusCronJobSource() *v1beta2.PingSource { + cjs := rtv1beta2.NewPingSource(pingSourceName, testNS) cjs.Status.InitializeConditions() return cjs } -func makeGenerationNotEqualPingSource() *sourcesv1beta1.PingSource { +func makeGenerationNotEqualPingSource() *v1beta2.PingSource { c := makeFalseStatusPingSource() c.Generation = currentGeneration c.Status.ObservedGeneration = outdatedGeneration return c } -func makeReadyPingSource() *sourcesv1beta1.PingSource { +func makeReadyPingSource() *v1beta2.PingSource { u, _ := apis.ParseURL(sinkURI) - return rtv1alpha1.NewPingSourceV1Beta1(pingSourceName, testNS, - rtv1alpha1.WithPingSourceV1B1Spec(sourcesv1beta1.PingSourceSpec{ - Schedule: testSchedule, - JsonData: testData, + return rtv1beta2.NewPingSource(pingSourceName, testNS, + rtv1beta2.WithPingSourceSpec(v1beta2.PingSourceSpec{ + Schedule: testSchedule, + ContentType: testContentType, + Data: testData, SourceSpec: duckv1.SourceSpec{ Sink: brokerDestv1, }, }), - rtv1alpha1.WithInitPingSourceV1B1Conditions, - rtv1alpha1.WithPingSourceV1B1Deployed, - rtv1alpha1.WithPingSourceV1B1CloudEventAttributes, - rtv1alpha1.WithPingSourceV1B1Sink(u), + rtv1beta2.WithInitPingSourceConditions, + rtv1beta2.WithPingSourceDeployed, + rtv1beta2.WithPingSourceCloudEventAttributes, + rtv1beta2.WithPingSourceSink(u), ) } func makeSubscriberKubernetesServiceAsUnstructured() *unstructured.Unstructured { diff --git a/pkg/reconciler/pingsource/controller.go b/pkg/reconciler/pingsource/controller.go index 6131342cf4b..7001c883084 100644 --- a/pkg/reconciler/pingsource/controller.go +++ b/pkg/reconciler/pingsource/controller.go @@ -34,8 +34,8 @@ import ( "knative.dev/pkg/tracker" "knative.dev/eventing/pkg/adapter/v2" - pingsourceinformer "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/pingsource" - pingsourcereconciler "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1beta1/pingsource" + pingsourceinformer "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta2/pingsource" + pingsourcereconciler "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1beta2/pingsource" reconcilersource "knative.dev/eventing/pkg/reconciler/source" ) diff --git a/pkg/reconciler/pingsource/controller_test.go b/pkg/reconciler/pingsource/controller_test.go index fee9ac5de64..d678016a4e3 100644 --- a/pkg/reconciler/pingsource/controller_test.go +++ b/pkg/reconciler/pingsource/controller_test.go @@ -29,7 +29,7 @@ import ( // Fake injection informers _ "knative.dev/eventing/pkg/client/injection/informers/eventing/v1beta1/eventtype/fake" - _ "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/pingsource/fake" + _ "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta2/pingsource/fake" _ "knative.dev/pkg/client/injection/ducks/duck/v1/addressable/fake" _ "knative.dev/pkg/client/injection/kube/informers/apps/v1/deployment/fake" _ "knative.dev/pkg/client/injection/kube/informers/core/v1/serviceaccount/fake" diff --git a/pkg/reconciler/pingsource/pingsource.go b/pkg/reconciler/pingsource/pingsource.go index 39f8396e07c..3d4efd2f31a 100644 --- a/pkg/reconciler/pingsource/pingsource.go +++ b/pkg/reconciler/pingsource/pingsource.go @@ -42,9 +42,9 @@ import ( "knative.dev/pkg/tracker" "knative.dev/eventing/pkg/adapter/mtping" - "knative.dev/eventing/pkg/apis/sources/v1beta1" - pingsourcereconciler "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1beta1/pingsource" - listers "knative.dev/eventing/pkg/client/listers/sources/v1beta1" + "knative.dev/eventing/pkg/apis/sources/v1beta2" + pingsourcereconciler "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1beta2/pingsource" + listers "knative.dev/eventing/pkg/client/listers/sources/v1beta2" "knative.dev/eventing/pkg/reconciler/pingsource/resources" reconcilersource "knative.dev/eventing/pkg/reconciler/source" ) @@ -85,7 +85,7 @@ type Reconciler struct { // Check that our Reconciler implements ReconcileKind var _ pingsourcereconciler.Interface = (*Reconciler)(nil) -func (r *Reconciler) ReconcileKind(ctx context.Context, source *v1beta1.PingSource) pkgreconciler.Event { +func (r *Reconciler) ReconcileKind(ctx context.Context, source *v1beta2.PingSource) pkgreconciler.Event { // This Source attempts to reconcile three things. // 1. Determine the sink's URI. // - Nothing to delete. @@ -134,14 +134,14 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, source *v1beta1.PingSour } source.Status.CloudEventAttributes = []duckv1.CloudEventAttributes{{ - Type: v1beta1.PingSourceEventType, - Source: v1beta1.PingSourceSource(source.Namespace, source.Name), + Type: v1beta2.PingSourceEventType, + Source: v1beta2.PingSourceSource(source.Namespace, source.Name), }} return nil } -func (r *Reconciler) reconcileReceiveAdapter(ctx context.Context, source *v1beta1.PingSource) (*appsv1.Deployment, error) { +func (r *Reconciler) reconcileReceiveAdapter(ctx context.Context, source *v1beta2.PingSource) (*appsv1.Deployment, error) { loggingConfig, err := logging.ConfigToJSON(r.configs.LoggingConfig()) if err != nil { logging.FromContext(ctx).Errorw("error while converting logging config to JSON", zap.Any("receiveAdapter", err)) diff --git a/pkg/reconciler/pingsource/pingsource_test.go b/pkg/reconciler/pingsource/pingsource_test.go index ab50c863dc4..fd38fa7a1bb 100644 --- a/pkg/reconciler/pingsource/pingsource_test.go +++ b/pkg/reconciler/pingsource/pingsource_test.go @@ -20,6 +20,8 @@ import ( "context" "testing" + cloudevents "github.com/cloudevents/sdk-go/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "knative.dev/eventing/pkg/adapter/v2" "knative.dev/pkg/network" @@ -33,9 +35,9 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" clientgotesting "k8s.io/client-go/testing" - sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/eventing/pkg/apis/sources/v1beta2" fakeeventingclient "knative.dev/eventing/pkg/client/injection/client/fake" - "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1beta1/pingsource" + "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1beta2/pingsource" "knative.dev/eventing/pkg/reconciler/pingsource/resources" "knative.dev/pkg/apis" duckv1 "knative.dev/pkg/apis/duck/v1" @@ -50,6 +52,7 @@ import ( . "knative.dev/eventing/pkg/reconciler/testing" rtv1beta1 "knative.dev/eventing/pkg/reconciler/testing/v1beta1" + rtv1beta2 "knative.dev/eventing/pkg/reconciler/testing/v1beta2" _ "knative.dev/pkg/client/injection/ducks/duck/v1beta1/addressable/fake" . "knative.dev/pkg/reconciler/testing" ) @@ -68,11 +71,13 @@ var ( ) const ( - sourceName = "test-ping-source" - sourceUID = "1234" - testNS = "testnamespace" - testSchedule = "*/2 * * * *" - testData = "data" + sourceName = "test-ping-source" + sourceUID = "1234" + testNS = "testnamespace" + testSchedule = "*/2 * * * *" + testContentType = cloudevents.TextPlain + testData = "data" + testDataBase64 = "ZGF0YQ==" // "data" sinkName = "testsink" generation = 1 @@ -98,34 +103,36 @@ func TestAllCases(t *testing.T) { }, { Name: "missing sink", Objects: []runtime.Object{ - NewPingSourceV1Beta1(sourceName, testNS, - WithPingSourceV1B1Spec(sourcesv1beta1.PingSourceSpec{ - Schedule: testSchedule, - JsonData: testData, + rtv1beta2.NewPingSource(sourceName, testNS, + rtv1beta2.WithPingSourceSpec(v1beta2.PingSourceSpec{ + Schedule: testSchedule, + ContentType: testContentType, + Data: testData, SourceSpec: duckv1.SourceSpec{ Sink: sinkDest, }, }), - WithPingSourceV1B1UID(sourceUID), - WithPingSourceV1B1ObjectMetaGeneration(generation), + rtv1beta2.WithPingSource(sourceUID), + rtv1beta2.WithPingSourceObjectMetaGeneration(generation), ), }, Key: testNS + "/" + sourceName, WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ - Object: NewPingSourceV1Beta1(sourceName, testNS, - WithPingSourceV1B1Spec(sourcesv1beta1.PingSourceSpec{ - Schedule: testSchedule, - JsonData: testData, + Object: rtv1beta2.NewPingSource(sourceName, testNS, + rtv1beta2.WithPingSourceSpec(v1beta2.PingSourceSpec{ + Schedule: testSchedule, + ContentType: testContentType, + Data: testData, SourceSpec: duckv1.SourceSpec{ Sink: sinkDest, }, }), - WithPingSourceV1B1UID(sourceUID), - WithPingSourceV1B1ObjectMetaGeneration(generation), + rtv1beta2.WithPingSource(sourceUID), + rtv1beta2.WithPingSourceObjectMetaGeneration(generation), // Status Update: - WithInitPingSourceV1B1Conditions, - WithPingSourceV1B1StatusObservedGeneration(generation), - WithPingSourceV1B1SinkNotFound, + rtv1beta2.WithInitPingSourceConditions, + rtv1beta2.WithPingSourceStatusObservedGeneration(generation), + rtv1beta2.WithPingSourceSinkNotFound, ), }}, WantEvents: []string{ @@ -135,16 +142,59 @@ func TestAllCases(t *testing.T) { }, { Name: "valid", Objects: []runtime.Object{ - NewPingSourceV1Beta1(sourceName, testNS, - WithPingSourceV1B1Spec(sourcesv1beta1.PingSourceSpec{ - Schedule: testSchedule, - JsonData: testData, + rtv1beta2.NewPingSource(sourceName, testNS, + rtv1beta2.WithPingSourceSpec(v1beta2.PingSourceSpec{ + Schedule: testSchedule, + ContentType: testContentType, + Data: testData, + SourceSpec: duckv1.SourceSpec{ + Sink: sinkDest, + }, + }), + rtv1beta2.WithPingSource(sourceUID), + rtv1beta2.WithPingSourceObjectMetaGeneration(generation), + ), + rtv1beta1.NewChannel(sinkName, testNS, + rtv1beta1.WithInitChannelConditions, + rtv1beta1.WithChannelAddress(sinkDNS), + ), + makeAvailableMTAdapter(), + }, + Key: testNS + "/" + sourceName, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ + Object: rtv1beta2.NewPingSource(sourceName, testNS, + rtv1beta2.WithPingSourceSpec(v1beta2.PingSourceSpec{ + Schedule: testSchedule, + ContentType: testContentType, + Data: testData, + SourceSpec: duckv1.SourceSpec{ + Sink: sinkDest, + }, + }), + rtv1beta2.WithPingSource(sourceUID), + rtv1beta2.WithPingSourceObjectMetaGeneration(generation), + // Status Update: + rtv1beta2.WithInitPingSourceConditions, + rtv1beta2.WithPingSourceDeployed, + rtv1beta2.WithPingSourceSink(sinkURI), + rtv1beta2.WithPingSourceCloudEventAttributes, + rtv1beta2.WithPingSourceStatusObservedGeneration(generation), + ), + }}, + }, { + Name: "valid with dataBase64", + Objects: []runtime.Object{ + rtv1beta2.NewPingSource(sourceName, testNS, + rtv1beta2.WithPingSourceSpec(v1beta2.PingSourceSpec{ + Schedule: testSchedule, + ContentType: testContentType, + DataBase64: testDataBase64, SourceSpec: duckv1.SourceSpec{ Sink: sinkDest, }, }), - WithPingSourceV1B1UID(sourceUID), - WithPingSourceV1B1ObjectMetaGeneration(generation), + rtv1beta2.WithPingSource(sourceUID), + rtv1beta2.WithPingSourceObjectMetaGeneration(generation), ), rtv1beta1.NewChannel(sinkName, testNS, rtv1beta1.WithInitChannelConditions, @@ -154,22 +204,23 @@ func TestAllCases(t *testing.T) { }, Key: testNS + "/" + sourceName, WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ - Object: NewPingSourceV1Beta1(sourceName, testNS, - WithPingSourceV1B1Spec(sourcesv1beta1.PingSourceSpec{ - Schedule: testSchedule, - JsonData: testData, + Object: rtv1beta2.NewPingSource(sourceName, testNS, + rtv1beta2.WithPingSourceSpec(v1beta2.PingSourceSpec{ + Schedule: testSchedule, + ContentType: testContentType, + DataBase64: testDataBase64, SourceSpec: duckv1.SourceSpec{ Sink: sinkDest, }, }), - WithPingSourceV1B1UID(sourceUID), - WithPingSourceV1B1ObjectMetaGeneration(generation), + rtv1beta2.WithPingSource(sourceUID), + rtv1beta2.WithPingSourceObjectMetaGeneration(generation), // Status Update: - WithInitPingSourceV1B1Conditions, - WithPingSourceV1B1Deployed, - WithPingSourceV1B1Sink(sinkURI), - WithPingSourceV1B1CloudEventAttributes, - WithPingSourceV1B1StatusObservedGeneration(generation), + rtv1beta2.WithInitPingSourceConditions, + rtv1beta2.WithPingSourceDeployed, + rtv1beta2.WithPingSourceSink(sinkURI), + rtv1beta2.WithPingSourceCloudEventAttributes, + rtv1beta2.WithPingSourceStatusObservedGeneration(generation), ), }}, }, @@ -180,14 +231,14 @@ func TestAllCases(t *testing.T) { ctx = addressable.WithDuck(ctx) r := &Reconciler{ kubeClientSet: fakekubeclient.Get(ctx), - pingLister: listers.GetPingSourceV1beta1Lister(), + pingLister: listers.GetPingSourceV1beta2Lister(), deploymentLister: listers.GetDeploymentLister(), tracker: tracker.New(func(types.NamespacedName) {}, 0), } r.sinkResolver = resolver.NewURIResolver(ctx, func(types.NamespacedName) {}) return pingsource.NewReconciler(ctx, logging.FromContext(ctx), - fakeeventingclient.Get(ctx), listers.GetPingSourceV1beta1Lister(), + fakeeventingclient.Get(ctx), listers.GetPingSourceV1beta2Lister(), controller.GetEventRecorder(ctx), r) }, true, diff --git a/pkg/reconciler/testing/listers.go b/pkg/reconciler/testing/listers.go index 8e2692e0333..2101f974a2d 100644 --- a/pkg/reconciler/testing/listers.go +++ b/pkg/reconciler/testing/listers.go @@ -38,6 +38,7 @@ import ( sourcesv1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" sourcesv1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" fakeeventingclientset "knative.dev/eventing/pkg/client/clientset/versioned/fake" configslisters "knative.dev/eventing/pkg/client/listers/configs/v1alpha1" eventingv1beta1listers "knative.dev/eventing/pkg/client/listers/eventing/v1beta1" @@ -46,6 +47,7 @@ import ( sourcelisters "knative.dev/eventing/pkg/client/listers/sources/v1alpha1" sourcev1alpha2listers "knative.dev/eventing/pkg/client/listers/sources/v1alpha2" sourcev1beta1listers "knative.dev/eventing/pkg/client/listers/sources/v1beta1" + sourcev1beta2listers "knative.dev/eventing/pkg/client/listers/sources/v1beta2" duckv1 "knative.dev/pkg/apis/duck/v1" "knative.dev/pkg/reconciler/testing" ) @@ -168,6 +170,10 @@ func (l *Listers) GetPingSourceV1beta1Lister() sourcev1beta1listers.PingSourceLi return sourcev1beta1listers.NewPingSourceLister(l.indexerFor(&sourcesv1beta1.PingSource{})) } +func (l *Listers) GetPingSourceV1beta2Lister() sourcev1beta2listers.PingSourceLister { + return sourcev1beta2listers.NewPingSourceLister(l.indexerFor(&sourcesv1beta2.PingSource{})) +} + func (l *Listers) GetSinkBindingV1alpha2Lister() sourcev1alpha2listers.SinkBindingLister { return sourcev1alpha2listers.NewSinkBindingLister(l.indexerFor(&sourcesv1alpha2.SinkBinding{})) } diff --git a/pkg/reconciler/testing/pingsource.go b/pkg/reconciler/testing/pingsource.go deleted file mode 100644 index dec22bf5c10..00000000000 --- a/pkg/reconciler/testing/pingsource.go +++ /dev/null @@ -1,206 +0,0 @@ -/* -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 testing - -import ( - "context" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "knative.dev/pkg/apis" - duckv1 "knative.dev/pkg/apis/duck/v1" - - "knative.dev/eventing/pkg/apis/sources/v1alpha2" - "knative.dev/eventing/pkg/apis/sources/v1beta1" -) - -// PingSourceV1A2Option enables further configuration of a CronJob. -type PingSourceV1A2Option func(*v1alpha2.PingSource) - -// PingSourceV1B1Option enables further configuration of a CronJob. -type PingSourceV1B1Option func(*v1beta1.PingSource) - -// NewPingSourceV1Alpha2 creates a PingSource with PingSourceOption. -func NewPingSourceV1Alpha2(name, namespace string, o ...PingSourceV1A2Option) *v1alpha2.PingSource { - c := &v1alpha2.PingSource{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - } - for _, opt := range o { - opt(c) - } - c.SetDefaults(context.Background()) // TODO: We should add defaults and validation. - return c -} - -// NewPingSourceV1Beta1 creates a PingSource with PingSourceOption. -func NewPingSourceV1Beta1(name, namespace string, o ...PingSourceV1B1Option) *v1beta1.PingSource { - c := &v1beta1.PingSource{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - } - for _, opt := range o { - opt(c) - } - c.SetDefaults(context.Background()) // TODO: We should add defaults and validation. - return c -} - -func WithPingSourceV1A2UID(uid string) PingSourceV1A2Option { - return func(c *v1alpha2.PingSource) { - c.UID = types.UID(uid) - } -} - -func WithInitPingSourceV1A2Conditions(s *v1alpha2.PingSource) { - s.Status.InitializeConditions() -} - -func WithValidPingSourceV1A2Schedule(s *v1alpha2.PingSource) { - s.Status.MarkSchedule() -} - -func WithPingSourceV1A2SinkNotFound(s *v1alpha2.PingSource) { - s.Status.MarkNoSink("NotFound", "") -} - -func WithPingSourceV1A2Sink(uri *apis.URL) PingSourceV1A2Option { - return func(s *v1alpha2.PingSource) { - s.Status.MarkSink(uri) - } -} - -func WithPingSourceV1A2NotDeployed(name string) PingSourceV1A2Option { - return func(s *v1alpha2.PingSource) { - s.Status.PropagateDeploymentAvailability(NewDeployment(name, "any")) - } -} - -func WithPingSourceV1A2Deployed(s *v1alpha2.PingSource) { - s.Status.PropagateDeploymentAvailability(NewDeployment("any", "any", WithDeploymentAvailable())) -} - -func WithPingSourceV1A2CloudEventAttributes(s *v1alpha2.PingSource) { - s.Status.CloudEventAttributes = []duckv1.CloudEventAttributes{{ - Type: v1alpha2.PingSourceEventType, - Source: v1alpha2.PingSourceSource(s.Namespace, s.Name), - }} -} - -func WithValidPingSourceV1A2Resources(s *v1alpha2.PingSource) { - s.Status.MarkResourcesCorrect() -} - -func WithPingSourceV1A2Spec(spec v1alpha2.PingSourceSpec) PingSourceV1A2Option { - return func(c *v1alpha2.PingSource) { - c.Spec = spec - } -} - -func WithPingSourceV1A2StatusObservedGeneration(generation int64) PingSourceV1A2Option { - return func(c *v1alpha2.PingSource) { - c.Status.ObservedGeneration = generation - } -} - -func WithPingSourceV1A2ObjectMetaGeneration(generation int64) PingSourceV1A2Option { - return func(c *v1alpha2.PingSource) { - c.ObjectMeta.Generation = generation - } -} - -func WithPingSourceV1A2Finalizers(finalizers ...string) PingSourceV1A2Option { - return func(c *v1alpha2.PingSource) { - c.Finalizers = finalizers - } -} - -func WithPingSourceV1A2Deleted(c *v1alpha2.PingSource) { - t := metav1.NewTime(time.Unix(1e9, 0)) - c.SetDeletionTimestamp(&t) -} - -func WithPingSourceV1B1UID(uid string) PingSourceV1B1Option { - return func(c *v1beta1.PingSource) { - c.UID = types.UID(uid) - } -} - -func WithInitPingSourceV1B1Conditions(s *v1beta1.PingSource) { - s.Status.InitializeConditions() -} - -func WithPingSourceV1B1SinkNotFound(s *v1beta1.PingSource) { - s.Status.MarkNoSink("NotFound", "") -} - -func WithPingSourceV1B1Sink(uri *apis.URL) PingSourceV1B1Option { - return func(s *v1beta1.PingSource) { - s.Status.MarkSink(uri) - } -} - -func WithPingSourceV1B1NotDeployed(name string) PingSourceV1B1Option { - return func(s *v1beta1.PingSource) { - s.Status.PropagateDeploymentAvailability(NewDeployment(name, "any")) - } -} - -func WithPingSourceV1B1Deployed(s *v1beta1.PingSource) { - s.Status.PropagateDeploymentAvailability(NewDeployment("any", "any", WithDeploymentAvailable())) -} - -func WithPingSourceV1B1CloudEventAttributes(s *v1beta1.PingSource) { - s.Status.CloudEventAttributes = []duckv1.CloudEventAttributes{{ - Type: v1beta1.PingSourceEventType, - Source: v1beta1.PingSourceSource(s.Namespace, s.Name), - }} -} - -func WithPingSourceV1B1Spec(spec v1beta1.PingSourceSpec) PingSourceV1B1Option { - return func(c *v1beta1.PingSource) { - c.Spec = spec - } -} - -func WithPingSourceV1B1StatusObservedGeneration(generation int64) PingSourceV1B1Option { - return func(c *v1beta1.PingSource) { - c.Status.ObservedGeneration = generation - } -} - -func WithPingSourceV1B1ObjectMetaGeneration(generation int64) PingSourceV1B1Option { - return func(c *v1beta1.PingSource) { - c.ObjectMeta.Generation = generation - } -} - -func WithPingSourceV1B1Finalizers(finalizers ...string) PingSourceV1B1Option { - return func(c *v1beta1.PingSource) { - c.Finalizers = finalizers - } -} - -func WithPingSourceV1B1Deleted(c *v1beta1.PingSource) { - t := metav1.NewTime(time.Unix(1e9, 0)) - c.SetDeletionTimestamp(&t) -} diff --git a/pkg/reconciler/testing/v1alpha2/pingsource.go b/pkg/reconciler/testing/v1alpha2/pingsource.go new file mode 100644 index 00000000000..26e6cd8c180 --- /dev/null +++ b/pkg/reconciler/testing/v1alpha2/pingsource.go @@ -0,0 +1,48 @@ +/* +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 testing + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/eventing/pkg/apis/sources/v1alpha2" +) + +// PingSourceOption enables further configuration of a CronJob. +type PingSourceOption func(*v1alpha2.PingSource) + +// NewPingSource creates a PingSource with PingSourceOption. +func NewPingSource(name, namespace string, o ...PingSourceOption) *v1alpha2.PingSource { + c := &v1alpha2.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } + for _, opt := range o { + opt(c) + } + c.SetDefaults(context.Background()) // TODO: We should add defaults and validation. + return c +} + +func WithPingSourceSpec(spec v1alpha2.PingSourceSpec) PingSourceOption { + return func(c *v1alpha2.PingSource) { + c.Spec = spec + } +} diff --git a/pkg/reconciler/testing/v1beta1/pingsource.go b/pkg/reconciler/testing/v1beta1/pingsource.go new file mode 100644 index 00000000000..2c5c7dda11b --- /dev/null +++ b/pkg/reconciler/testing/v1beta1/pingsource.go @@ -0,0 +1,90 @@ +/* +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 testing + +import ( + "context" + "time" + + "knative.dev/eventing/pkg/reconciler/testing" + + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/eventing/pkg/apis/sources/v1beta1" +) + +// PingSourceOption enables further configuration of a CronJob. +type PingSourceOption func(*v1beta1.PingSource) + +// NewPingSource creates a PingSource with PingSourceOption. +func NewPingSource(name, namespace string, o ...PingSourceOption) *v1beta1.PingSource { + c := &v1beta1.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } + for _, opt := range o { + opt(c) + } + c.SetDefaults(context.Background()) // TODO: We should add defaults and validation. + return c +} + +func WithInitPingSourceConditions(s *v1beta1.PingSource) { + s.Status.InitializeConditions() +} + +func WithPingSourceSink(uri *apis.URL) PingSourceOption { + return func(s *v1beta1.PingSource) { + s.Status.MarkSink(uri) + } +} + +func WithPingSourceDeployed(s *v1beta1.PingSource) { + s.Status.PropagateDeploymentAvailability(testing.NewDeployment("any", "any", testing.WithDeploymentAvailable())) +} + +func WithPingSourceCloudEventAttributes(s *v1beta1.PingSource) { + s.Status.CloudEventAttributes = []duckv1.CloudEventAttributes{{ + Type: v1beta1.PingSourceEventType, + Source: v1beta1.PingSourceSource(s.Namespace, s.Name), + }} +} + +func WithPingSourceSpec(spec v1beta1.PingSourceSpec) PingSourceOption { + return func(c *v1beta1.PingSource) { + c.Spec = spec + } +} + +func WithPingSourceSinkNotFound(s *v1beta1.PingSource) { + s.Status.MarkNoSink("NotFound", "") +} + +func WithPingSourceFinalizers(finalizers ...string) PingSourceOption { + return func(c *v1beta1.PingSource) { + c.Finalizers = finalizers + } +} + +func WithPingSourceDeleted(c *v1beta1.PingSource) { + t := metav1.NewTime(time.Unix(1e9, 0)) + c.SetDeletionTimestamp(&t) +} diff --git a/pkg/reconciler/testing/v1beta2/pingsource.go b/pkg/reconciler/testing/v1beta2/pingsource.go new file mode 100644 index 00000000000..4a743796a22 --- /dev/null +++ b/pkg/reconciler/testing/v1beta2/pingsource.go @@ -0,0 +1,109 @@ +/* +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 testing + +import ( + "context" + "time" + + "knative.dev/eventing/pkg/reconciler/testing" + + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "knative.dev/eventing/pkg/apis/sources/v1beta2" +) + +// PingSourceOption enables further configuration of a CronJob. +type PingSourceOption func(*v1beta2.PingSource) + +// NewPingSource creates a PingSource with PingSourceOption. +func NewPingSource(name, namespace string, o ...PingSourceOption) *v1beta2.PingSource { + c := &v1beta2.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } + for _, opt := range o { + opt(c) + } + c.SetDefaults(context.Background()) // TODO: We should add defaults and validation. + return c +} + +func WithPingSource(uid string) PingSourceOption { + return func(c *v1beta2.PingSource) { + c.UID = types.UID(uid) + } +} + +func WithInitPingSourceConditions(s *v1beta2.PingSource) { + s.Status.InitializeConditions() +} + +func WithPingSourceSinkNotFound(s *v1beta2.PingSource) { + s.Status.MarkNoSink("NotFound", "") +} + +func WithPingSourceSink(uri *apis.URL) PingSourceOption { + return func(s *v1beta2.PingSource) { + s.Status.MarkSink(uri) + } +} + +func WithPingSourceDeployed(s *v1beta2.PingSource) { + s.Status.PropagateDeploymentAvailability(testing.NewDeployment("any", "any", testing.WithDeploymentAvailable())) +} + +func WithPingSourceCloudEventAttributes(s *v1beta2.PingSource) { + s.Status.CloudEventAttributes = []duckv1.CloudEventAttributes{{ + Type: v1beta2.PingSourceEventType, + Source: v1beta2.PingSourceSource(s.Namespace, s.Name), + }} +} + +func WithPingSourceSpec(spec v1beta2.PingSourceSpec) PingSourceOption { + return func(c *v1beta2.PingSource) { + c.Spec = spec + } +} + +func WithPingSourceStatusObservedGeneration(generation int64) PingSourceOption { + return func(c *v1beta2.PingSource) { + c.Status.ObservedGeneration = generation + } +} + +func WithPingSourceObjectMetaGeneration(generation int64) PingSourceOption { + return func(c *v1beta2.PingSource) { + c.ObjectMeta.Generation = generation + } +} + +func WithPingSourceFinalizers(finalizers ...string) PingSourceOption { + return func(c *v1beta2.PingSource) { + c.Finalizers = finalizers + } +} + +func WithPingSourceDeleted(c *v1beta2.PingSource) { + t := metav1.NewTime(time.Unix(1e9, 0)) + c.SetDeletionTimestamp(&t) +} diff --git a/test/conformance/main_test.go b/test/conformance/main_test.go index 15729040af0..130401ab968 100644 --- a/test/conformance/main_test.go +++ b/test/conformance/main_test.go @@ -88,7 +88,7 @@ func addSourcesInitializers() { ) sourcesTestRunner.AddComponentSetupClientOption( testlib.PingSourceTypeMeta, - setupclientoptions.PingSourceV1B1ClientSetupOption( + setupclientoptions.PingSourceV1B2ClientSetupOption( ctx, pingSrcName, recordEventsPingPodName), ) } diff --git a/test/e2e-conformance-tests.sh b/test/e2e-conformance-tests.sh index a6d1da4611a..ab914f2c8b2 100755 --- a/test/e2e-conformance-tests.sh +++ b/test/e2e-conformance-tests.sh @@ -32,7 +32,11 @@ source "$(dirname "$0")/e2e-common.sh" initialize $@ --skip-istio-addon -echo "Running Conformance tests for: Multi Tenant Channel Based Broker (v1beta1), Channel (v1beta1, v1), InMemoryChannel (v1beta1, v1) , ApiServerSource (v1beta1, v1), ContainerSource (v1alpha2, v1) and PingSource (v1beta1)" -go_test_e2e -timeout=30m -parallel=12 ./test/conformance -brokers=eventing.knative.dev/v1beta1:MTChannelBasedBroker -channels=messaging.knative.dev/v1beta1:Channel,messaging.knative.dev/v1beta1:InMemoryChannel,messaging.knative.dev/v1:Channel,messaging.knative.dev/v1:InMemoryChannel -sources=sources.knative.dev/v1beta1:ApiServerSource,sources.knative.dev/v1alpha2:ContainerSource,sources.knative.dev/v1beta1:PingSource,sources.knative.dev/v1:ApiServerSource,sources.knative.dev/v1:ContainerSource || fail_test +echo "Running Conformance tests for: Multi Tenant Channel Based Broker (v1beta1), Channel (v1beta1, v1), InMemoryChannel (v1beta1, v1) , ApiServerSource (v1beta1, v1), ContainerSource (v1alpha2, v1) and PingSource (v1beta1, v1beta2)" +go_test_e2e -timeout=30m -parallel=12 ./test/conformance \ + -brokers=eventing.knative.dev/v1beta1:MTChannelBasedBroker \ + -channels=messaging.knative.dev/v1beta1:Channel,messaging.knative.dev/v1beta1:InMemoryChannel,messaging.knative.dev/v1:Channel,messaging.knative.dev/v1:InMemoryChannel \ + -sources=sources.knative.dev/v1beta1:ApiServerSource,sources.knative.dev/v1alpha2:ContainerSource,sources.knative.dev/v1beta1:PingSource,sources.knative.dev/v1beta2:PingSource,sources.knative.dev/v1:ApiServerSource,sources.knative.dev/v1:ContainerSource \ + || fail_test success diff --git a/test/e2e-tests.sh b/test/e2e-tests.sh index 7693467088b..b5a47cebece 100755 --- a/test/e2e-tests.sh +++ b/test/e2e-tests.sh @@ -32,7 +32,11 @@ source "$(dirname "$0")/e2e-common.sh" initialize $@ --skip-istio-addon -echo "Running E2E tests for: Multi Tenant Channel Based Broker, Channel (v1beta1, v1), InMemoryChannel (v1beta1, v1) , ApiServerSource (v1beta1, v1), ContainerSource (v1alpha2, v1) and PingSource (v1beta1)" -go_test_e2e -timeout=30m -parallel=12 ./test/e2e -brokerclass=MTChannelBasedBroker -channels=messaging.knative.dev/v1beta1:Channel,messaging.knative.dev/v1beta1:InMemoryChannel,messaging.knative.dev/v1:Channel,messaging.knative.dev/v1:InMemoryChannel -sources=sources.knative.dev/v1beta1:ApiServerSource,sources.knative.dev/v1alpha2:ContainerSource,sources.knative.dev/v1beta1:PingSource,sources.knative.dev/v1:ApiServerSource,sources.knative.dev/v1:ContainerSource || fail_test +echo "Running E2E tests for: Multi Tenant Channel Based Broker, Channel (v1beta1, v1), InMemoryChannel (v1beta1, v1) , ApiServerSource (v1beta1, v1), ContainerSource (v1alpha2, v1) and PingSource (v1beta1, v1beta2)" +go_test_e2e -timeout=30m -parallel=12 ./test/e2e \ + -brokerclass=MTChannelBasedBroker \ + -channels=messaging.knative.dev/v1beta1:Channel,messaging.knative.dev/v1beta1:InMemoryChannel,messaging.knative.dev/v1:Channel,messaging.knative.dev/v1:InMemoryChannel \ + -sources=sources.knative.dev/v1beta1:ApiServerSource,sources.knative.dev/v1alpha2:ContainerSource,sources.knative.dev/v1beta1:PingSource,sources.knative.dev/v1beta2:PingSource,sources.knative.dev/v1:ApiServerSource,sources.knative.dev/v1:ContainerSource \ + || fail_test success diff --git a/test/e2e/source_ping_test.go b/test/e2e/source_ping_v1alpha2_test.go similarity index 92% rename from test/e2e/source_ping_test.go rename to test/e2e/source_ping_v1alpha2_test.go index 5c3c0d90b2a..e264998d248 100644 --- a/test/e2e/source_ping_test.go +++ b/test/e2e/source_ping_v1alpha2_test.go @@ -22,6 +22,8 @@ import ( "fmt" "testing" + rttestingv1alpha2 "knative.dev/eventing/pkg/reconciler/testing/v1alpha2" + "knative.dev/eventing/pkg/reconciler/sugar" sourcesv1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" @@ -35,8 +37,6 @@ import ( testlib "knative.dev/eventing/test/lib" "knative.dev/eventing/test/lib/resources" - - eventingtesting "knative.dev/eventing/pkg/reconciler/testing" ) func TestPingSourceV1Alpha2(t *testing.T) { @@ -56,10 +56,10 @@ func TestPingSourceV1Alpha2(t *testing.T) { eventTracker, _ := recordevents.StartEventRecordOrFail(ctx, client, recordEventPodName) // create cron job source data := fmt.Sprintf(`{"msg":"TestPingSource %s"}`, uuid.NewUUID()) - source := eventingtesting.NewPingSourceV1Alpha2( + source := rttestingv1alpha2.NewPingSource( sourceName, client.Namespace, - eventingtesting.WithPingSourceV1A2Spec(sourcesv1alpha2.PingSourceSpec{ + rttestingv1alpha2.WithPingSourceSpec(sourcesv1alpha2.PingSourceSpec{ JsonData: data, SourceSpec: duckv1.SourceSpec{ Sink: duckv1.Destination{ @@ -99,10 +99,10 @@ func TestPingSourceV1Alpha2EventTypes(t *testing.T) { client.WaitForResourceReadyOrFail(sugarresources.DefaultBrokerName, testlib.BrokerTypeMeta) // Create ping source - source := eventingtesting.NewPingSourceV1Alpha2( + source := rttestingv1alpha2.NewPingSource( sourceName, client.Namespace, - eventingtesting.WithPingSourceV1A2Spec(sourcesv1alpha2.PingSourceSpec{ + rttestingv1alpha2.WithPingSourceSpec(sourcesv1alpha2.PingSourceSpec{ JsonData: fmt.Sprintf(`{"msg":"TestPingSource %s"}`, uuid.NewUUID()), SourceSpec: duckv1.SourceSpec{ Sink: duckv1.Destination{ diff --git a/test/e2e/source_ping_v1beta1_test.go b/test/e2e/source_ping_v1beta1_test.go index 6ff2cfa382e..0886356852a 100644 --- a/test/e2e/source_ping_v1beta1_test.go +++ b/test/e2e/source_ping_v1beta1_test.go @@ -36,7 +36,7 @@ import ( testlib "knative.dev/eventing/test/lib" "knative.dev/eventing/test/lib/resources" - eventingtesting "knative.dev/eventing/pkg/reconciler/testing" + rttestingv1beta1 "knative.dev/eventing/pkg/reconciler/testing/v1beta1" ) func TestPingSourceV1Beta1(t *testing.T) { @@ -56,10 +56,10 @@ func TestPingSourceV1Beta1(t *testing.T) { eventTracker, _ := recordevents.StartEventRecordOrFail(ctx, client, recordEventPodName) // create cron job source data := fmt.Sprintf(`{"msg":"TestPingSource %s"}`, uuid.NewUUID()) - source := eventingtesting.NewPingSourceV1Beta1( + source := rttestingv1beta1.NewPingSource( sourceName, client.Namespace, - eventingtesting.WithPingSourceV1B1Spec(sourcesv1beta1.PingSourceSpec{ + rttestingv1beta1.WithPingSourceSpec(sourcesv1beta1.PingSourceSpec{ JsonData: data, SourceSpec: duckv1.SourceSpec{ Sink: duckv1.Destination{ @@ -99,10 +99,10 @@ func TestPingSourceV1Beta1EventTypes(t *testing.T) { client.WaitForResourceReadyOrFail(sugarresources.DefaultBrokerName, testlib.BrokerTypeMeta) // Create ping source - source := eventingtesting.NewPingSourceV1Beta1( + source := rttestingv1beta1.NewPingSource( sourceName, client.Namespace, - eventingtesting.WithPingSourceV1B1Spec(sourcesv1beta1.PingSourceSpec{ + rttestingv1beta1.WithPingSourceSpec(sourcesv1beta1.PingSourceSpec{ JsonData: fmt.Sprintf(`{"msg":"TestPingSource %s"}`, uuid.NewUUID()), SourceSpec: duckv1.SourceSpec{ Sink: duckv1.Destination{ diff --git a/test/e2e/source_ping_v1beta2_test.go b/test/e2e/source_ping_v1beta2_test.go new file mode 100644 index 00000000000..e92a0c24dbe --- /dev/null +++ b/test/e2e/source_ping_v1beta2_test.go @@ -0,0 +1,135 @@ +// +build e2e + +/* +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 e2e + +import ( + "context" + "fmt" + "testing" + + cloudevents "github.com/cloudevents/sdk-go/v2" + + "knative.dev/eventing/pkg/reconciler/sugar" + + sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" + sugarresources "knative.dev/eventing/pkg/reconciler/sugar/resources" + "knative.dev/eventing/test/lib/recordevents" + + . "github.com/cloudevents/sdk-go/v2/test" + "k8s.io/apimachinery/pkg/util/uuid" + + duckv1 "knative.dev/pkg/apis/duck/v1" + + testlib "knative.dev/eventing/test/lib" + "knative.dev/eventing/test/lib/resources" + + rttestingv1beta2 "knative.dev/eventing/pkg/reconciler/testing/v1beta2" +) + +func TestPingSourceV1Beta2(t *testing.T) { + const ( + sourceName = "e2e-ping-source" + // Every 1 minute starting from now + + recordEventPodName = "e2e-ping-source-logger-pod-v1beta2" + ) + + client := setup(t, true) + defer tearDown(client) + + ctx := context.Background() + + // create event logger pod and service + eventTracker, _ := recordevents.StartEventRecordOrFail(ctx, client, recordEventPodName) + // create cron job source + data := fmt.Sprintf(`{"msg":"TestPingSource %s"}`, uuid.NewUUID()) + source := rttestingv1beta2.NewPingSource( + sourceName, + client.Namespace, + rttestingv1beta2.WithPingSourceSpec(sourcesv1beta2.PingSourceSpec{ + ContentType: cloudevents.ApplicationJSON, + Data: data, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: resources.KnativeRefForService(recordEventPodName, client.Namespace), + }, + }, + }), + ) + client.CreatePingSourceV1Beta2OrFail(source) + + // wait for all test resources to be ready + client.WaitForAllTestResourcesReadyOrFail(ctx) + + // verify the logger service receives the event and only once + eventTracker.AssertExact(1, recordevents.MatchEvent( + HasSource(sourcesv1beta2.PingSourceSource(client.Namespace, sourceName)), + HasData([]byte(data)), + )) +} + +func TestPingSourceV1Beta2EventTypes(t *testing.T) { + const ( + sourceName = "e2e-ping-source-eventtype" + ) + + client := setup(t, true) + defer tearDown(client) + + ctx := context.Background() + + // Label namespace so that it creates the default broker. + if err := client.LabelNamespace(map[string]string{sugar.InjectionLabelKey: sugar.InjectionEnabledLabelValue}); err != nil { + t.Fatal("Error annotating namespace:", err) + } + + // Wait for default broker ready. + client.WaitForResourceReadyOrFail(sugarresources.DefaultBrokerName, testlib.BrokerTypeMeta) + + // Create ping source + source := rttestingv1beta2.NewPingSource( + sourceName, + client.Namespace, + rttestingv1beta2.WithPingSourceSpec(sourcesv1beta2.PingSourceSpec{ + ContentType: cloudevents.ApplicationJSON, + Data: fmt.Sprintf(`{"msg":"TestPingSource %s"}`, uuid.NewUUID()), + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + // TODO change sink to be a non-Broker one once we revisit EventType https://github.com/knative/eventing/issues/2750 + Ref: resources.KnativeRefForBroker(sugarresources.DefaultBrokerName, client.Namespace), + }, + }, + }), + ) + client.CreatePingSourceV1Beta2OrFail(source) + + // wait for all test resources to be ready + client.WaitForAllTestResourcesReadyOrFail(ctx) + + // Verify that an EventType was created. + eventTypes, err := waitForEventTypes(ctx, client, 1) + if err != nil { + t.Fatal("Waiting for EventTypes:", err) + } + et := eventTypes[0] + if et.Spec.Type != sourcesv1beta2.PingSourceEventType && et.Spec.Source.String() != sourcesv1beta2.PingSourceSource(client.Namespace, sourceName) { + t.Fatalf("Invalid spec.type and/or spec.source for PingSource EventType, expected: type=%s source=%s, got: type=%s source=%s", + sourcesv1beta2.PingSourceEventType, sourcesv1beta2.PingSourceSource(client.Namespace, sourceName), et.Spec.Type, et.Spec.Source) + } +} diff --git a/test/e2e/trigger_dependency_annotation_test.go b/test/e2e/trigger_dependency_annotation_test.go index d68b5e55b1a..5ed4a046196 100644 --- a/test/e2e/trigger_dependency_annotation_test.go +++ b/test/e2e/trigger_dependency_annotation_test.go @@ -22,15 +22,17 @@ import ( "fmt" "testing" + cloudevents "github.com/cloudevents/sdk-go/v2" + "knative.dev/eventing/pkg/reconciler/sugar" . "github.com/cloudevents/sdk-go/v2/test" "k8s.io/apimachinery/pkg/util/uuid" duckv1 "knative.dev/pkg/apis/duck/v1" - sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" sugarresources "knative.dev/eventing/pkg/reconciler/sugar/resources" - eventingtesting "knative.dev/eventing/pkg/reconciler/testing" + eventingtestingv1beta2 "knative.dev/eventing/pkg/reconciler/testing/v1beta2" testlib "knative.dev/eventing/test/lib" "knative.dev/eventing/test/lib/recordevents" "knative.dev/eventing/test/lib/resources" @@ -45,7 +47,7 @@ func TestTriggerDependencyAnnotation(t *testing.T) { defaultBrokerName = sugarresources.DefaultBrokerName triggerName = "trigger-annotation" subscriberName = "subscriber-annotation" - dependencyAnnotation = `{"kind":"PingSource","name":"test-ping-source-annotation","apiVersion":"sources.knative.dev/v1beta1"}` + dependencyAnnotation = `{"kind":"PingSource","name":"test-ping-source-annotation","apiVersion":"sources.knative.dev/v1beta2"}` pingSourceName = "test-ping-source-annotation" // Every 1 minute starting from now schedule = "*/1 * * * *" @@ -74,12 +76,13 @@ func TestTriggerDependencyAnnotation(t *testing.T) { ) jsonData := fmt.Sprintf(`{"msg":"Test trigger-annotation %s"}`, uuid.NewUUID()) - pingSource := eventingtesting.NewPingSourceV1Beta1( + pingSource := eventingtestingv1beta2.NewPingSource( pingSourceName, client.Namespace, - eventingtesting.WithPingSourceV1B1Spec(sourcesv1beta1.PingSourceSpec{ - Schedule: schedule, - JsonData: jsonData, + eventingtestingv1beta2.WithPingSourceSpec(sourcesv1beta2.PingSourceSpec{ + Schedule: schedule, + ContentType: cloudevents.ApplicationJSON, + Data: jsonData, SourceSpec: duckv1.SourceSpec{ Sink: duckv1.Destination{ Ref: &duckv1.KReference{ @@ -93,13 +96,13 @@ func TestTriggerDependencyAnnotation(t *testing.T) { }), ) - client.CreatePingSourceV1Beta1OrFail(pingSource) + client.CreatePingSourceV1Beta2OrFail(pingSource) // Trigger should become ready after pingSource was created client.WaitForResourceReadyOrFail(triggerName, testlib.TriggerTypeMeta) eventTracker.AssertAtLeast(1, recordevents.MatchEvent( - HasSource(sourcesv1beta1.PingSourceSource(client.Namespace, pingSourceName)), + HasSource(sourcesv1beta2.PingSourceSource(client.Namespace, pingSourceName)), HasData([]byte(jsonData)), )) } diff --git a/test/lib/creation.go b/test/lib/creation.go index 8058cdb6c1f..2c5f95e70c9 100644 --- a/test/lib/creation.go +++ b/test/lib/creation.go @@ -43,6 +43,7 @@ import ( sourcesv1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" sourcesv1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" "knative.dev/eventing/pkg/utils" "knative.dev/eventing/test/lib/duck" "knative.dev/eventing/test/lib/resources" @@ -678,7 +679,7 @@ func (c *Client) CreateContainerSourceV1OrFail(containerSource *sourcesv1.Contai c.Tracker.AddObj(containerSource) } -// CreatePingSourceV1Alpha2OrFail will create an PingSource +// CreatePingSourceV1Alpha2OrFail will create a PingSource func (c *Client) CreatePingSourceV1Alpha2OrFail(pingSource *sourcesv1alpha2.PingSource) { c.T.Logf("Creating pingsource %+v", pingSource) pingInterface := c.Eventing.SourcesV1alpha2().PingSources(c.Namespace) @@ -695,7 +696,7 @@ func (c *Client) CreatePingSourceV1Alpha2OrFail(pingSource *sourcesv1alpha2.Ping c.Tracker.AddObj(pingSource) } -// CreatePingSourceV1Beta1OrFail will create an PingSource +// CreatePingSourceV1Beta1OrFail will create a PingSource func (c *Client) CreatePingSourceV1Beta1OrFail(pingSource *sourcesv1beta1.PingSource) { c.T.Logf("Creating pingsource %+v", pingSource) pingInterface := c.Eventing.SourcesV1beta1().PingSources(c.Namespace) @@ -712,6 +713,23 @@ func (c *Client) CreatePingSourceV1Beta1OrFail(pingSource *sourcesv1beta1.PingSo c.Tracker.AddObj(pingSource) } +// CreatePingSourceV1Beta2OrFail will create a PingSource +func (c *Client) CreatePingSourceV1Beta2OrFail(pingSource *sourcesv1beta2.PingSource) { + c.T.Logf("Creating pingsource %+v", pingSource) + pingInterface := c.Eventing.SourcesV1beta2().PingSources(c.Namespace) + err := c.RetryWebhookErrors(func(attempts int) (err error) { + _, e := pingInterface.Create(context.Background(), pingSource, metav1.CreateOptions{}) + if e != nil { + c.T.Logf("Failed to create pingsource %q: %v", pingSource.Name, e) + } + return e + }) + if err != nil && !errors.IsAlreadyExists(err) { + c.T.Fatalf("Failed to create pingsource %q: %v", pingSource.Name, err) + } + c.Tracker.AddObj(pingSource) +} + func (c *Client) CreateServiceOrFail(svc *corev1.Service) *corev1.Service { c.T.Logf("Creating service %+v", svc) namespace := c.Namespace diff --git a/test/lib/setupclientoptions/sources.go b/test/lib/setupclientoptions/sources.go index 73947f99247..ef9157ca5a0 100644 --- a/test/lib/setupclientoptions/sources.go +++ b/test/lib/setupclientoptions/sources.go @@ -20,13 +20,15 @@ import ( "context" "fmt" + cloudevents "github.com/cloudevents/sdk-go/v2" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/util/uuid" sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" - sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" - eventingtesting "knative.dev/eventing/pkg/reconciler/testing" + sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" eventingtestingv1 "knative.dev/eventing/pkg/reconciler/testing/v1" + eventingtestingv1beta2 "knative.dev/eventing/pkg/reconciler/testing/v1beta2" testlib "knative.dev/eventing/test/lib" "knative.dev/eventing/test/lib/recordevents" "knative.dev/eventing/test/lib/resources" @@ -69,10 +71,10 @@ func ApiServerSourceV1ClientSetupOption(ctx context.Context, name string, mode s } } -// PingSourceV1B1ClientSetupOption returns a ClientSetupOption that can be used +// PingSourceV1B2ClientSetupOption returns a ClientSetupOption that can be used // to create a new PingSource. It creates a RecordEvents pod and a // PingSource object with the RecordEvent pod as its sink. -func PingSourceV1B1ClientSetupOption(ctx context.Context, name string, recordEventsPodName string) testlib.SetupClientOption { +func PingSourceV1B2ClientSetupOption(ctx context.Context, name string, recordEventsPodName string) testlib.SetupClientOption { return func(client *testlib.Client) { // create event logger pod and service @@ -80,11 +82,12 @@ func PingSourceV1B1ClientSetupOption(ctx context.Context, name string, recordEve // create cron job source data := fmt.Sprintf(`{"msg":"TestPingSource %s"}`, uuid.NewUUID()) - source := eventingtesting.NewPingSourceV1Beta1( + source := eventingtestingv1beta2.NewPingSource( name, client.Namespace, - eventingtesting.WithPingSourceV1B1Spec(sourcesv1beta1.PingSourceSpec{ - JsonData: data, + eventingtestingv1beta2.WithPingSourceSpec(sourcesv1beta2.PingSourceSpec{ + ContentType: cloudevents.ApplicationJSON, + Data: data, SourceSpec: duckv1.SourceSpec{ Sink: duckv1.Destination{ Ref: resources.KnativeRefForService(recordEventsPodName, client.Namespace), @@ -92,7 +95,7 @@ func PingSourceV1B1ClientSetupOption(ctx context.Context, name string, recordEve }, }), ) - client.CreatePingSourceV1Beta1OrFail(source) + client.CreatePingSourceV1Beta2OrFail(source) // wait for all test resources to be ready client.WaitForAllTestResourcesReadyOrFail(ctx)