diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index 5bb6e370b75..21de1d87d93 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -41,14 +41,22 @@ import ( "knative.dev/pkg/webhook/resourcesemantics/validation" defaultconfig "knative.dev/eventing/pkg/apis/config" + "knative.dev/eventing/pkg/apis/eventing" eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + "knative.dev/eventing/pkg/apis/flows" flowsv1 "knative.dev/eventing/pkg/apis/flows/v1" + flowsv1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" + "knative.dev/eventing/pkg/apis/messaging" channeldefaultconfig "knative.dev/eventing/pkg/apis/messaging/config" messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" "knative.dev/eventing/pkg/apis/sources" pingdefaultconfig "knative.dev/eventing/pkg/apis/sources/config" sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + 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/reconciler/sinkbinding" ) @@ -56,17 +64,36 @@ import ( var ourTypes = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{ // For group eventing.knative.dev. // v1beta1 + eventingv1beta1.SchemeGroupVersion.WithKind("Broker"): &eventingv1beta1.Broker{}, + eventingv1beta1.SchemeGroupVersion.WithKind("Trigger"): &eventingv1beta1.Trigger{}, eventingv1beta1.SchemeGroupVersion.WithKind("EventType"): &eventingv1beta1.EventType{}, // v1 eventingv1.SchemeGroupVersion.WithKind("Broker"): &eventingv1.Broker{}, eventingv1.SchemeGroupVersion.WithKind("Trigger"): &eventingv1.Trigger{}, // For group messaging.knative.dev. + // v1beta1 + messagingv1beta1.SchemeGroupVersion.WithKind("InMemoryChannel"): &messagingv1beta1.InMemoryChannel{}, + messagingv1beta1.SchemeGroupVersion.WithKind("Channel"): &messagingv1beta1.Channel{}, + messagingv1beta1.SchemeGroupVersion.WithKind("Subscription"): &messagingv1beta1.Subscription{}, // v1 messagingv1.SchemeGroupVersion.WithKind("Channel"): &messagingv1.Channel{}, messagingv1.SchemeGroupVersion.WithKind("Subscription"): &messagingv1.Subscription{}, // For group sources.knative.dev. + // v1alpha1 + sourcesv1alpha1.SchemeGroupVersion.WithKind("ApiServerSource"): &sourcesv1alpha1.ApiServerSource{}, + sourcesv1alpha1.SchemeGroupVersion.WithKind("SinkBinding"): &sourcesv1alpha1.SinkBinding{}, + // v1alpha2 + sourcesv1alpha2.SchemeGroupVersion.WithKind("ApiServerSource"): &sourcesv1alpha2.ApiServerSource{}, + sourcesv1alpha2.SchemeGroupVersion.WithKind("PingSource"): &sourcesv1alpha2.PingSource{}, + sourcesv1alpha2.SchemeGroupVersion.WithKind("SinkBinding"): &sourcesv1alpha2.SinkBinding{}, + sourcesv1alpha2.SchemeGroupVersion.WithKind("ContainerSource"): &sourcesv1alpha2.ContainerSource{}, + // v1beta1 + sourcesv1beta1.SchemeGroupVersion.WithKind("ApiServerSource"): &sourcesv1beta1.ApiServerSource{}, + 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 @@ -76,6 +103,9 @@ var ourTypes = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{ sourcesv1.SchemeGroupVersion.WithKind("ContainerSource"): &sourcesv1.ContainerSource{}, // For group flows.knative.dev + // v1beta1 + flowsv1beta1.SchemeGroupVersion.WithKind("Parallel"): &flowsv1beta1.Parallel{}, + flowsv1beta1.SchemeGroupVersion.WithKind("Sequence"): &flowsv1beta1.Sequence{}, // v1 flowsv1.SchemeGroupVersion.WithKind("Parallel"): &flowsv1.Parallel{}, flowsv1.SchemeGroupVersion.WithKind("Sequence"): &flowsv1.Sequence{}, @@ -207,8 +237,17 @@ func NewConversionController(ctx context.Context, cmw configmap.Watcher) *contro } var ( - sourcesv1beta2_ = sourcesv1beta2.SchemeGroupVersion.Version - sourcesv1_ = sourcesv1.SchemeGroupVersion.Version + eventingv1beta1_ = eventingv1beta1.SchemeGroupVersion.Version + eventingv1_ = eventingv1.SchemeGroupVersion.Version + messagingv1beta1_ = messagingv1beta1.SchemeGroupVersion.Version + messagingv1_ = messagingv1.SchemeGroupVersion.Version + flowsv1beta1_ = flowsv1beta1.SchemeGroupVersion.Version + flowsv1_ = flowsv1.SchemeGroupVersion.Version + sourcesv1alpha1_ = sourcesv1alpha1.SchemeGroupVersion.Version + sourcesv1alpha2_ = sourcesv1alpha2.SchemeGroupVersion.Version + sourcesv1beta1_ = sourcesv1beta1.SchemeGroupVersion.Version + sourcesv1beta2_ = sourcesv1beta2.SchemeGroupVersion.Version + sourcesv1_ = sourcesv1.SchemeGroupVersion.Version ) return conversion.NewConversionController(ctx, @@ -217,13 +256,106 @@ func NewConversionController(ctx context.Context, cmw configmap.Watcher) *contro // Specify the types of custom resource definitions that should be converted map[schema.GroupKind]conversion.GroupKindConversion{ + // Eventing + eventingv1.Kind("Trigger"): { + DefinitionName: eventing.TriggersResource.String(), + HubVersion: eventingv1beta1_, + Zygotes: map[string]conversion.ConvertibleObject{ + eventingv1beta1_: &eventingv1beta1.Trigger{}, + eventingv1_: &eventingv1.Trigger{}, + }, + }, + eventingv1.Kind("Broker"): { + DefinitionName: eventing.BrokersResource.String(), + HubVersion: eventingv1beta1_, + Zygotes: map[string]conversion.ConvertibleObject{ + eventingv1beta1_: &eventingv1beta1.Broker{}, + eventingv1_: &eventingv1.Broker{}, + }, + }, + + // Messaging + messagingv1.Kind("Channel"): { + DefinitionName: messaging.ChannelsResource.String(), + HubVersion: messagingv1beta1_, + Zygotes: map[string]conversion.ConvertibleObject{ + messagingv1beta1_: &messagingv1beta1.Channel{}, + messagingv1_: &messagingv1.Channel{}, + }, + }, + messagingv1.Kind("InMemoryChannel"): { + DefinitionName: messaging.InMemoryChannelsResource.String(), + HubVersion: messagingv1beta1_, + Zygotes: map[string]conversion.ConvertibleObject{ + messagingv1beta1_: &messagingv1beta1.InMemoryChannel{}, + messagingv1_: &messagingv1.InMemoryChannel{}, + }, + }, + messagingv1.Kind("Subscription"): { + DefinitionName: messaging.SubscriptionsResource.String(), + HubVersion: messagingv1beta1_, + Zygotes: map[string]conversion.ConvertibleObject{ + messagingv1beta1_: &messagingv1beta1.Subscription{}, + messagingv1_: &messagingv1.Subscription{}, + }, + }, + + // flows + flowsv1.Kind("Sequence"): { + DefinitionName: flows.SequenceResource.String(), + HubVersion: flowsv1beta1_, + Zygotes: map[string]conversion.ConvertibleObject{ + flowsv1beta1_: &flowsv1beta1.Sequence{}, + flowsv1_: &flowsv1.Sequence{}, + }, + }, + flowsv1.Kind("Parallel"): { + DefinitionName: flows.ParallelResource.String(), + HubVersion: flowsv1beta1_, + Zygotes: map[string]conversion.ConvertibleObject{ + flowsv1beta1_: &flowsv1beta1.Parallel{}, + flowsv1_: &flowsv1.Parallel{}, + }, + }, + // Sources - sourcesv1.Kind("PingSource"): { + sourcesv1.Kind("ApiServerSource"): { + DefinitionName: sources.ApiServerSourceResource.String(), + HubVersion: sourcesv1alpha1_, + Zygotes: map[string]conversion.ConvertibleObject{ + sourcesv1alpha1_: &sourcesv1alpha1.ApiServerSource{}, + sourcesv1alpha2_: &sourcesv1alpha2.ApiServerSource{}, + sourcesv1beta1_: &sourcesv1beta1.ApiServerSource{}, + sourcesv1_: &sourcesv1.ApiServerSource{}, + }, + }, + sourcesv1beta1.Kind("PingSource"): { DefinitionName: sources.PingSourceResource.String(), - HubVersion: sourcesv1beta2_, + HubVersion: sourcesv1alpha2_, + Zygotes: map[string]conversion.ConvertibleObject{ + sourcesv1alpha2_: &sourcesv1alpha2.PingSource{}, + sourcesv1beta1_: &sourcesv1beta1.PingSource{}, + sourcesv1beta2_: &sourcesv1beta2.PingSource{}, + sourcesv1_: &sourcesv1.PingSource{}, + }, + }, + sourcesv1.Kind("SinkBinding"): { + DefinitionName: sources.SinkBindingResource.String(), + HubVersion: sourcesv1alpha1_, + Zygotes: map[string]conversion.ConvertibleObject{ + sourcesv1alpha1_: &sourcesv1alpha1.SinkBinding{}, + sourcesv1alpha2_: &sourcesv1alpha2.SinkBinding{}, + sourcesv1beta1_: &sourcesv1beta1.SinkBinding{}, + sourcesv1_: &sourcesv1.SinkBinding{}, + }, + }, + sourcesv1.Kind("ContainerSource"): { + DefinitionName: sources.ContainerSourceResource.String(), + HubVersion: sourcesv1alpha2_, Zygotes: map[string]conversion.ConvertibleObject{ - sourcesv1beta2_: &sourcesv1beta2.PingSource{}, - sourcesv1_: &sourcesv1.PingSource{}, + sourcesv1alpha2_: &sourcesv1alpha2.ContainerSource{}, + sourcesv1beta1_: &sourcesv1beta1.ContainerSource{}, + sourcesv1_: &sourcesv1.ContainerSource{}, }, }, }, diff --git a/config/channels/in-memory-channel/resources/in-memory-channel.yaml b/config/channels/in-memory-channel/resources/in-memory-channel.yaml index 8c6854eebe9..a8dda8f8258 100644 --- a/config/channels/in-memory-channel/resources/in-memory-channel.yaml +++ b/config/channels/in-memory-channel/resources/in-memory-channel.yaml @@ -23,13 +23,14 @@ metadata: spec: group: messaging.knative.dev versions: - - name: v1 + - &version + name: v1beta1 served: true - storage: true + storage: false subresources: status: {} schema: - openAPIV3Schema: + openAPIV3Schema: &openAPIV3Schema description: 'InMemoryChannel is a resource representing an in memory channel' type: object properties: @@ -222,6 +223,13 @@ spec: - name: Reason type: string jsonPath: ".status.conditions[?(@.type==\"Ready\")].reason" + - << : *version + name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + <<: *openAPIV3Schema names: kind: InMemoryChannel plural: inmemorychannels diff --git a/config/core/resources/apiserversource.yaml b/config/core/resources/apiserversource.yaml index ee491fb47d6..98c4b7ac7fd 100644 --- a/config/core/resources/apiserversource.yaml +++ b/config/core/resources/apiserversource.yaml @@ -36,13 +36,302 @@ metadata: spec: group: sources.knative.dev versions: - - name: v1 + - &version + name: v1alpha1 served: true - storage: true + storage: false subresources: status: {} schema: openAPIV3Schema: + type: object + description: 'ApiServerSource is an event source that brings Kubernetes API server events into Knative.' + properties: + spec: + type: object + description: 'ApiServerSourceSpec defines the desired state of ApiServerSource (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 + mode: + description: 'Mode is the mode the receive adapter controller runs + under: Ref or Resource. `Ref` sends only the reference to the + resource. `Resource` send the full resource.' + type: string + owner: + description: 'ResourceOwner is an additional filter to only track resources + that are owned by a specific resource type. If ResourceOwner matches + Resources[n] then Resources[n] is allowed to pass the ResourceOwner + filter.' + type: object + properties: + apiVersion: + description: 'APIVersion - the API version of the resource + to watch.' + type: string + kind: + description: 'Kind of the resource to watch. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + resources: + description: 'Resources is the list of resources to watch' + type: array + items: + type: object + properties: + apiVersion: + description: 'API version of the resource to watch.' + type: string + controller: + description: 'If true, send an event referencing the object + controlling the resource Deprecated: Per-resource + controller flag will no longer be supported in v1alpha2, + please use Spec.Owner as a GKV.' + type: boolean + controllerSelector: + description: 'ControllerSelector restricts this source + to objects with a controlling owner reference of the + specified kind. Only apiVersion and kind are used. + Both are optional. Deprecated: Per-resource owner + refs will no longer be supported in v1alpha2, please + use Spec.Owner as a GKV.' + type: object + properties: + apiVersion: + description: 'API version of the referent.' + type: string + blockOwnerDeletion: + description: 'If true, AND if the owner has the + "foregroundDeletion" finalizer, then the owner + cannot be deleted from the key-value store + until this reference is removed. Defaults + to false. To set this field, a user needs + "delete" permission of the owner, otherwise + 422 (Unprocessable Entity) will be returned.' + type: boolean + controller: + description: 'If true, this reference points to + the managing controller.' + type: boolean + 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: + http://kubernetes.io/docs/user-guide/identifiers#names' + type: string + uid: + description: 'UID of the referent. More info: + http://kubernetes.io/docs/user-guide/identifiers#uids' + type: string + kind: + description: 'Kind of the resource to watch. More info: + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + labelSelector: + description: 'LabelSelector restricts this source to objects + with the selected labels More info: http://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors' + type: object + properties: + matchExpressions: + description: 'matchExpressions is a list of label + selector requirements. The requirements are + ANDed.' + type: array + items: + type: object + properties: + key: + description: 'key is the label key + that the selector applies to.' + type: string + operator: + description: 'operator represents a + key''s relationship to a set of + values. Valid operators are In, + NotIn, Exists and DoesNotExist.' + type: string + values: + description: 'values is an array of + string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. This + array is replaced during a strategic + merge patch.' + type: array + items: + type: string + matchLabels: + description: 'matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed.' + type: object + x-kubernetes-preserve-unknown-fields: true + serviceAccountName: + description: 'ServiceAccountName is the name of the ServiceAccount + to use to run this source.' + type: string + sink: + description: 'Sink is a reference to an object that will resolve to + a domain name to use as the sink.' + type: object + properties: + apiVersion: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + ref: + description: 'Ref points to an Addressable.' + type: object + properties: + apiVersion: + description: 'API version of the referent.' + type: string + fieldPath: + description: 'If referring to a piece of an object + instead of an entire object, this string should + contain a valid JSON/Go field access statement, + such as desiredState.manifest.containers[2]. For + example, if the object reference is to a container + within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to + the name of the container that triggered the event) + or if no container name is specified "spec.containers[2]" + (container with index 2 in this pod). This syntax + is chosen only to have some well-defined way of + referencing a part of an object.' + 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/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this + reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + 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 + status: + type: object + description: 'ApiServerSourceStatus defines the observed state of ApiServerSource (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 + additionalPrinterColumns: + - name: Sink + type: string + jsonPath: ".status.sinkUri" + - name: Age + type: date + jsonPath: .metadata.creationTimestamp + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type==\"Ready\")].reason" + - << : *version + name: v1alpha2 + served: true + storage: false + schema: + openAPIV3Schema: &openAPIV3Schema + type: object description: 'ApiServerSource is an event source that brings Kubernetes API server events into Knative.' type: object properties: @@ -188,19 +477,22 @@ spec: sinkUri: description: SinkURI is the current active sink URI that has been configured for the Source. type: string - additionalPrinterColumns: - - name: Sink - type: string - jsonPath: ".status.sinkUri" - - name: Age - type: date - jsonPath: .metadata.creationTimestamp - - name: Ready - type: string - jsonPath: ".status.conditions[?(@.type==\"Ready\")].status" - - name: Reason - type: string - jsonPath: ".status.conditions[?(@.type==\"Ready\")].reason" + - <<: *version + name: v1beta1 + served: true + storage: false + # the schema of v1beta1 is exactly the same as v1alpha2 schema + schema: + openAPIV3Schema: + <<: *openAPIV3Schema + - <<: *version + name: v1 + served: true + storage: true + # the schema of v1 is exactly the same as v1beta1 schema + schema: + openAPIV3Schema: + <<: *openAPIV3Schema names: categories: - all diff --git a/config/core/resources/broker.yaml b/config/core/resources/broker.yaml index a4014de5700..c83af9e3433 100644 --- a/config/core/resources/broker.yaml +++ b/config/core/resources/broker.yaml @@ -23,13 +23,14 @@ metadata: spec: group: eventing.knative.dev versions: - - name: v1 + - &version + name: v1beta1 served: true - storage: true + storage: false subresources: status: {} schema: - openAPIV3Schema: + openAPIV3Schema: &openAPIV3Schema description: 'Broker collects a pool of events that are consumable using Triggers. Brokers provide a well-known endpoint for event delivery that senders can use with minimal knowledge of the event routing strategy. Subscribers use Triggers to request delivery of events from a Broker''s pool to a specific URL or Addressable endpoint.' type: object properties: @@ -148,6 +149,14 @@ spec: - name: Reason type: string jsonPath: ".status.conditions[?(@.type==\"Ready\")].reason" + - << : *version + name: v1 + served: true + storage: true + # the schema of v1 is exactly the same as v1beta1 schema + schema: + openAPIV3Schema: + << : *openAPIV3Schema names: kind: Broker plural: brokers diff --git a/config/core/resources/channel.yaml b/config/core/resources/channel.yaml index 33f5b8b1ff7..1d5d084ace6 100644 --- a/config/core/resources/channel.yaml +++ b/config/core/resources/channel.yaml @@ -24,9 +24,10 @@ metadata: spec: group: messaging.knative.dev versions: - - name: v1 + - &version + name: v1beta1 served: true - storage: true + storage: false subresources: status: {} additionalPrinterColumns: @@ -43,7 +44,7 @@ spec: type: string jsonPath: ".status.conditions[?(@.type==\"Ready\")].reason" schema: - openAPIV3Schema: + openAPIV3Schema: &openAPIV3Schema description: 'Channel represents a generic Channel. It is normally used when we want a Channel, but do not need a specific Channel implementation.' type: object properties: @@ -253,6 +254,14 @@ spec: uid: description: UID is used to understand the origin of the subscriber. type: string + - << : *version + name: v1 + served: true + storage: true + # the schema of v1 is exactly the same as v1beta1 schema + schema: + openAPIV3Schema: + << : *openAPIV3Schema names: kind: Channel plural: channels diff --git a/config/core/resources/containersource.yaml b/config/core/resources/containersource.yaml index 48fb0299a0d..66062ee0c83 100644 --- a/config/core/resources/containersource.yaml +++ b/config/core/resources/containersource.yaml @@ -24,13 +24,14 @@ metadata: spec: group: sources.knative.dev versions: - - name: v1 + - &version + name: v1alpha2 served: true - storage: true + storage: false subresources: status: {} schema: - openAPIV3Schema: + openAPIV3Schema: &openAPIV3Schema description: 'ContainerSource is an event source that starts a container image which generates events under certain situations and sends messages to a sink URI' type: object properties: @@ -139,6 +140,22 @@ spec: - name: Reason type: string jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + - <<: *version + name: v1beta1 + served: true + storage: false + # the schema of v1beta1 is exactly the same as v1alpha2 schema + schema: + openAPIV3Schema: + <<: *openAPIV3Schema + - <<: *version + name: v1 + served: true + storage: true + # the schema of v1 is exactly the same as v1beta1 schema + schema: + openAPIV3Schema: + <<: *openAPIV3Schema names: categories: - all diff --git a/config/core/resources/parallel.yaml b/config/core/resources/parallel.yaml index 93f64c864e8..4fbce9811a7 100644 --- a/config/core/resources/parallel.yaml +++ b/config/core/resources/parallel.yaml @@ -22,13 +22,14 @@ metadata: spec: group: flows.knative.dev versions: - - name: v1 + - &version + name: v1beta1 served: true - storage: true + storage: false subresources: status: {} schema: - openAPIV3Schema: + openAPIV3Schema: &openAPIV3Schema description: 'Parallel defines conditional branches that will be wired in series through Channels and Subscriptions.' type: object properties: @@ -320,6 +321,14 @@ spec: - name: Reason type: string jsonPath: ".status.conditions[?(@.type==\"Ready\")].reason" + - <<: *version + name: v1 + served: true + storage: true + # the schema of v1 is exactly the same as v1beta1 schema + schema: + openAPIV3Schema: + << : *openAPIV3Schema names: kind: Parallel plural: parallels diff --git a/config/core/resources/pingsource.yaml b/config/core/resources/pingsource.yaml index d10af857d6f..5def70c5b29 100644 --- a/config/core/resources/pingsource.yaml +++ b/config/core/resources/pingsource.yaml @@ -31,11 +31,299 @@ spec: group: sources.knative.dev versions: - &version - name: v1beta2 + name: v1alpha2 served: true - storage: true + storage: false subresources: status: {} + 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 + jsonData: + description: 'JsonData is json encoded data used as the body of the + event posted to the sink. Default is empty. If set, datacontenttype + will also be set to "application/json".' + type: string + schedule: + description: 'Schedule is the cronjob 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 + 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 + additionalPrinterColumns: + - name: Sink + type: string + jsonPath: .status.sinkUri + - name: Schedule + type: string + jsonPath: .spec.schedule + - name: Age + type: date + jsonPath: .metadata.creationTimestamp + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + - <<: *version + name: v1beta1 + 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 + jsonData: + description: 'JsonData is json encoded data used as the body of the + event posted to the sink. Default is empty. If set, datacontenttype + will also be set to "application/json".' + type: string + schedule: + description: 'Schedule is the cronjob 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 + - <<: *version + name: v1beta2 + served: true + storage: true schema: openAPIV3Schema: type: object @@ -177,22 +465,6 @@ spec: description: 'SinkURI is the current active sink URI that has been configured for the Source.' type: string - additionalPrinterColumns: - - name: Sink - type: string - jsonPath: .status.sinkUri - - name: Schedule - type: string - jsonPath: .spec.schedule - - name: Age - type: date - jsonPath: .metadata.creationTimestamp - - name: Ready - type: string - jsonPath: ".status.conditions[?(@.type=='Ready')].status" - - name: Reason - type: string - jsonPath: ".status.conditions[?(@.type=='Ready')].reason" - <<: *version name: v1 served: true diff --git a/config/core/resources/sequence.yaml b/config/core/resources/sequence.yaml index b8953d6bfcd..2e34781ed90 100644 --- a/config/core/resources/sequence.yaml +++ b/config/core/resources/sequence.yaml @@ -22,13 +22,14 @@ metadata: spec: group: flows.knative.dev versions: - - name: v1 + - &version + name: v1beta1 served: true - storage: true + storage: false subresources: status: {} schema: - openAPIV3Schema: + openAPIV3Schema: &openAPIV3Schema description: 'Sequence defines a sequence of Subscribers that will be wired in series through Channels and Subscriptions.' type: object properties: @@ -305,6 +306,14 @@ spec: - name: Reason type: string jsonPath: ".status.conditions[?(@.type==\"Ready\")].reason" + - <<: *version + name: v1 + served: true + storage: true + # the schema of v1 is exactly the same as v1beta1 schema + schema: + openAPIV3Schema: + << : *openAPIV3Schema names: kind: Sequence plural: sequences diff --git a/config/core/resources/sinkbindings.yaml b/config/core/resources/sinkbindings.yaml index fae13c4857a..e6d89f84912 100644 --- a/config/core/resources/sinkbindings.yaml +++ b/config/core/resources/sinkbindings.yaml @@ -25,13 +25,48 @@ metadata: spec: group: sources.knative.dev versions: - - name: v1 + - &version + name: v1alpha1 served: true - storage: true + storage: false subresources: status: {} schema: openAPIV3Schema: + type: object + # this is a work around so we don't need to flush out the + # schema for each version at this time + # + # see issue: https://github.com/knative/serving/issues/912 + x-kubernetes-preserve-unknown-fields: true + additionalPrinterColumns: + - name: Sink + type: string + jsonPath: ".status.sinkUri" + - name: Age + type: date + jsonPath: .metadata.creationTimestamp + - name: Ready + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].status" + - name: Reason + type: string + jsonPath: ".status.conditions[?(@.type=='Ready')].reason" + - <<: *version + name: v1alpha2 + served: true + storage: false + - <<: *version + name: v1beta1 + served: true + storage: false + - <<: *version + name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object description: 'SinkBinding describes a Binding that is also a Source. The `sink` (from the Source duck) is resolved to a URL and then projected into the `subject` by augmenting the runtime @@ -168,19 +203,6 @@ spec: sinkUri: description: SinkURI is the current active sink URI that has been configured for the Source. type: string - additionalPrinterColumns: - - name: Sink - type: string - jsonPath: ".status.sinkUri" - - name: Age - type: date - jsonPath: .metadata.creationTimestamp - - name: Ready - type: string - jsonPath: ".status.conditions[?(@.type=='Ready')].status" - - name: Reason - type: string - jsonPath: ".status.conditions[?(@.type=='Ready')].reason" names: categories: - all diff --git a/config/core/resources/subscription.yaml b/config/core/resources/subscription.yaml index 15777dcb4c1..27ee4fc945f 100644 --- a/config/core/resources/subscription.yaml +++ b/config/core/resources/subscription.yaml @@ -21,13 +21,14 @@ metadata: spec: group: messaging.knative.dev versions: - - name: v1 + - &version + name: v1beta1 served: true - storage: true + storage: false subresources: status: {} schema: - openAPIV3Schema: + openAPIV3Schema: &openAPIV3Schema description: 'Subscription routes events received on a Channel to a DNS name and corresponds to the subscriptions.channels.knative.dev CRD.' type: object properties: @@ -203,6 +204,14 @@ spec: - name: Reason type: string jsonPath: ".status.conditions[?(@.type==\"Ready\")].reason" + - <<: *version + name: v1 + served: true + storage: true + # the schema of v1 is exactly the same as v1beta1 schema + schema: + openAPIV3Schema: + << : *openAPIV3Schema names: kind: Subscription plural: subscriptions diff --git a/config/core/resources/trigger.yaml b/config/core/resources/trigger.yaml index b0a74b5d01a..c8fc304f56d 100644 --- a/config/core/resources/trigger.yaml +++ b/config/core/resources/trigger.yaml @@ -22,9 +22,10 @@ metadata: spec: group: eventing.knative.dev versions: - - name: v1 + - &version + name: v1beta1 served: true - storage: true + storage: false subresources: status: {} additionalPrinterColumns: @@ -43,6 +44,131 @@ spec: - name: Reason type: string jsonPath: ".status.conditions[?(@.type==\"Ready\")].reason" + schema: + openAPIV3Schema: + type: object + properties: + spec: + description: Spec defines the desired state of the Trigger. + type: object + properties: + broker: + description: Broker is the broker that this trigger receives events from. If not specified, will default to ''default''.' + type: string + delivery: + description: Delivery contains the delivery spec for this specific trigger. + type: object + properties: + backoffDelay: + description: 'BackoffDelay is the delay before retrying. More information on Duration format: - https://www.iso.org/iso-8601-date-and-time-format.html - https://en.wikipedia.org/wiki/ISO_8601 For linear policy, backoff delay is backoffDelay*. For exponential policy, backoff delay is backoffDelay*2^.' + type: string + backoffPolicy: + description: BackoffPolicy is the retry backoff policy (linear, exponential). + type: string + deadLetterSink: + description: DeadLetterSink is the sink receiving event that could not be sent to a destination. + 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 + retry: + description: Retry is the minimum number of retries the sender should attempt when sending an event before moving it to the dead letter sink. + type: integer + format: int32 + filter: + description: 'Filter is the filter to apply against all events from the Broker. Only events that pass this filter will be sent to the Subscriber. If not specified, will default to allowing all events. ' + type: object + properties: + attributes: + description: 'Attributes filters events by exact match on event context attributes. Each key in the map is compared with the equivalent key in the event context. An event passes the filter if all values are equal to the specified values. Nested context attributes are not supported as keys. Only string values are supported. ' + type: object + x-kubernetes-preserve-unknown-fields: true + subscriber: + description: Subscriber is the addressable that receives events from the Broker that pass the Filter. It is required. + 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 + status: + description: Status represents the current state of the Trigger. This data may be out of date. + type: object + 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 + 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 + subscriberUri: + description: SubscriberURI is the resolved URI of the receiver for this Trigger. + type: string + - << : *version + name: v1 + served: true + storage: true schema: openAPIV3Schema: description: 'Trigger represents a request to have events delivered to a subscriber from a Broker''s event pool.' diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 0fb378398ca..f96bf365295 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -38,7 +38,7 @@ group "Kubernetes Codegen" # 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:v1 flows:v1 sources:v1beta2 sources:v1" \ + "eventing:v1beta1 eventing:v1 messaging:v1beta1 messaging:v1 flows:v1beta1 flows:v1 sources:v1alpha1 sources:v1alpha2 sources:v1beta1 sources:v1beta2 sources:v1" \ --go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt # Deep copy config @@ -59,7 +59,7 @@ group "Knative Codegen" # Knative Injection ${KNATIVE_CODEGEN_PKG}/hack/generate-knative.sh "injection" \ knative.dev/eventing/pkg/client knative.dev/eventing/pkg/apis \ - "eventing:v1beta1 eventing:v1 messaging:v1 flows:v1 sources:v1beta2 sources:v1 duck:v1beta1 duck:v1" \ + "eventing:v1beta1 eventing:v1 messaging:v1beta1 messaging:v1 flows:v1beta1 flows:v1 sources:v1alpha1 sources:v1alpha2 sources:v1beta1 sources:v1beta2 sources:v1 duck:v1beta1 duck:v1" \ --go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt group "Update deps post-codegen" diff --git a/openshift/e2e-rekt-tests.sh b/openshift/e2e-rekt-tests.sh index 3438ec43952..b6521243427 100644 --- a/openshift/e2e-rekt-tests.sh +++ b/openshift/e2e-rekt-tests.sh @@ -19,11 +19,17 @@ failed=0 (( !failed )) && install_knative_eventing || failed=1 (( !failed )) && install_tracing || failed=1 - +echo "**************************************" +echo "*** OS RUN REKT TESTS ***" +echo "**************************************" (( !failed )) && run_e2e_rekt_tests || failed=1 - +echo "**************************************" +echo "*** OS UNINSTALL KNATIVE EVENTING ***" +echo "**************************************" (( !failed )) && uninstall_knative_eventing || failed=1 - +echo "**************************************" +echo "*** DUMP CLUSTER? FAILED=$failed ***" +echo "**************************************" (( failed )) && dump_cluster_state (( failed )) && exit 1 diff --git a/pkg/adapter/mtping/pingsource_test.go b/pkg/adapter/mtping/pingsource_test.go index d3607f7c625..64a3ba59778 100644 --- a/pkg/adapter/mtping/pingsource_test.go +++ b/pkg/adapter/mtping/pingsource_test.go @@ -54,7 +54,7 @@ var ( Name: sinkName, Namespace: testNS, Kind: "Channel", - APIVersion: "messaging.knative.dev/v1", + APIVersion: "messaging.knative.dev/v1beta1", }, } diff --git a/pkg/adapter/v2/util/crstatusevent/eventsstatus_test.go b/pkg/adapter/v2/util/crstatusevent/eventsstatus_test.go index f973ec9c9d7..f17f9d5a233 100644 --- a/pkg/adapter/v2/util/crstatusevent/eventsstatus_test.go +++ b/pkg/adapter/v2/util/crstatusevent/eventsstatus_test.go @@ -24,19 +24,19 @@ import ( "github.com/cloudevents/sdk-go/v2/protocol" "github.com/cloudevents/sdk-go/v2/protocol/http" - corev1 "k8s.io/api/core/v1" + v1beta1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/record" - v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/eventing/pkg/apis/sources/v1alpha2" ) func logF(format string, a ...interface{}) { } -var src = &v1.ApiServerSource{ +var src = &v1alpha2.ApiServerSource{ TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", + APIVersion: "v1alpha2", Kind: "ApiServerSource", }, ObjectMeta: metav1.ObjectMeta{ @@ -44,8 +44,8 @@ var src = &v1.ApiServerSource{ Namespace: "source-namespace", UID: "1234", }, - Spec: v1.ApiServerSourceSpec{ - Resources: []v1.APIVersionKindSelector{{ + Spec: v1alpha2.ApiServerSourceSpec{ + Resources: []v1alpha2.APIVersionKindSelector{{ APIVersion: "", Kind: "Namespace", }, { @@ -58,7 +58,7 @@ var src = &v1.ApiServerSource{ MatchLabels: map[string]string{"test-key1": "test-value1"}, }, }}, - ResourceOwner: &v1.APIVersionKind{ + ResourceOwner: &v1alpha2.APIVersionKind{ APIVersion: "custom/v1", Kind: "Parent", }, @@ -67,14 +67,14 @@ var src = &v1.ApiServerSource{ }, } var mutex = &sync.Mutex{} -var recordTestSinkResults map[string]*corev1.Event = make(map[string]*corev1.Event) +var recordTestSinkResults map[string]*v1beta1.Event = make(map[string]*v1beta1.Event) type fakeSink struct { record.EventSink Name string } -func (f fakeSink) Create(event *corev1.Event) (*corev1.Event, error) { +func (f fakeSink) Create(event *v1beta1.Event) (*v1beta1.Event, error) { mutex.Lock() defer mutex.Unlock() recordTestSinkResults[f.Name] = event diff --git a/pkg/apis/eventing/v1beta1/broker_conversion.go b/pkg/apis/eventing/v1beta1/broker_conversion.go new file mode 100644 index 00000000000..973b517dd96 --- /dev/null +++ b/pkg/apis/eventing/v1beta1/broker_conversion.go @@ -0,0 +1,67 @@ +/* +Copyright 2020 The Knative Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "fmt" + + duckv1 "knative.dev/eventing/pkg/apis/duck/v1" + duckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + v1 "knative.dev/eventing/pkg/apis/eventing/v1" + "knative.dev/pkg/apis" +) + +// ConvertTo implements apis.Convertible +func (source *Broker) ConvertTo(ctx context.Context, to apis.Convertible) error { + switch sink := to.(type) { + case *v1.Broker: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.Config = source.Spec.Config + if source.Spec.Delivery != nil { + sink.Spec.Delivery = &duckv1.DeliverySpec{} + if err := source.Spec.Delivery.ConvertTo(ctx, sink.Spec.Delivery); err != nil { + return err + } + } + sink.Status.Status = source.Status.Status + sink.Status.Address = source.Status.Address + return nil + default: + return fmt.Errorf("unknown version, got: %T", sink) + } +} + +// ConvertFrom implements apis.Convertible +func (sink *Broker) ConvertFrom(ctx context.Context, from apis.Convertible) error { + switch source := from.(type) { + case *v1.Broker: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.Config = source.Spec.Config + if source.Spec.Delivery != nil { + sink.Spec.Delivery = &duckv1beta1.DeliverySpec{} + if err := sink.Spec.Delivery.ConvertFrom(ctx, source.Spec.Delivery); err != nil { + return err + } + } + sink.Status.Status = source.Status.Status + sink.Status.Address = source.Status.Address + return nil + default: + return fmt.Errorf("unknown version, got: %T", source) + } +} diff --git a/pkg/apis/eventing/v1beta1/broker_conversion_test.go b/pkg/apis/eventing/v1beta1/broker_conversion_test.go new file mode 100644 index 00000000000..3bc4ef83409 --- /dev/null +++ b/pkg/apis/eventing/v1beta1/broker_conversion_test.go @@ -0,0 +1,232 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/eventing/pkg/apis/eventing" + v1 "knative.dev/eventing/pkg/apis/eventing/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestBrokerConversionBadType(t *testing.T) { + good, bad := &Broker{}, &Trigger{} + + 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) + } +} + +func TestBrokerConversionBadVersion(t *testing.T) { + good, bad := &Broker{}, &Broker{} + + 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) + } +} + +// Test v1beta1 -> v1 -> v1beta1 +func TestBrokerConversionRoundTripV1beta1(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&v1.Broker{}} + + linear := eventingduckv1beta1.BackoffPolicyLinear + + tests := []struct { + name string + in *Broker + }{{ + name: "min configuration", + in: &Broker{ + ObjectMeta: metav1.ObjectMeta{ + Name: "broker-name", + Namespace: "broker-ns", + Generation: 17, + }, + Spec: BrokerSpec{}, + }, + }, { + name: "full configuration", + in: &Broker{ + ObjectMeta: metav1.ObjectMeta{ + Name: "broker-name", + Namespace: "broker-ns", + Generation: 17, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Kind: "config-kind", + Namespace: "config-ns", + Name: "config-name", + APIVersion: "config-version", + }, + Delivery: &eventingduckv1beta1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dl-sink-kind", + Namespace: "dl-sink-ns", + Name: "dl-sink-name", + APIVersion: "dl-sink-version", + }, + URI: apis.HTTP("dl-sink.example.com"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + Status: BrokerStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + Annotations: map[string]string{ + eventing.BrokerChannelAddressStatusAnnotationKey: "http://foo.bar.svc.cluster.local/", + }, + }, + Address: duckv1.Addressable{ + URL: apis.HTTP("address"), + }, + }, + }, + }} + + 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 := &Broker{} + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Error("ConvertFrom() =", err) + } + + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} + +// Test v1 -> v1beta1 -> v1 +func TestBrokerConversionRoundTripV1(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&Broker{}} + + linear := eventingduckv1.BackoffPolicyLinear + + tests := []struct { + name string + in *v1.Broker + }{{ + name: "min configuration", + in: &v1.Broker{ + ObjectMeta: metav1.ObjectMeta{ + Name: "par-name", + Namespace: "par-ns", + Generation: 17, + }, + Spec: v1.BrokerSpec{}, + }, + }, { + name: "full configuration", + in: &v1.Broker{ + ObjectMeta: metav1.ObjectMeta{ + Name: "broker-name", + Namespace: "broker-ns", + Generation: 17, + }, + Spec: v1.BrokerSpec{ + Config: &duckv1.KReference{ + Kind: "config-kind", + Namespace: "config-ns", + Name: "config-name", + APIVersion: "config-version", + }, + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dl-sink-kind", + Namespace: "dl-sink-ns", + Name: "dl-sink-name", + APIVersion: "dl-sink-version", + }, + URI: apis.HTTP("dl-sink.example.com"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + Status: v1.BrokerStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + Annotations: map[string]string{ + eventing.BrokerChannelAddressStatusAnnotationKey: "http://foo.bar.svc.cluster.local/", + }, + }, + Address: duckv1.Addressable{ + URL: apis.HTTP("address"), + }, + }, + }, + }} + + for _, test := range tests { + for _, version := range versions { + t.Run(test.name, func(t *testing.T) { + ver := version + if err := ver.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertDown() =", err) + } + got := &v1.Broker{} + if err := ver.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertUp() =", err) + } + + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} diff --git a/pkg/apis/eventing/v1beta1/broker_defaults.go b/pkg/apis/eventing/v1beta1/broker_defaults.go new file mode 100644 index 00000000000..8ec18d5c93a --- /dev/null +++ b/pkg/apis/eventing/v1beta1/broker_defaults.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. +*/ + +package v1beta1 + +import ( + "context" + + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + + "knative.dev/eventing/pkg/apis/config" + "knative.dev/eventing/pkg/apis/eventing" + "knative.dev/pkg/apis" +) + +func (b *Broker) SetDefaults(ctx context.Context) { + // Default Spec fields. + withNS := apis.WithinParent(ctx, b.ObjectMeta) + b.Spec.SetDefaults(withNS) + eventing.DefaultBrokerClassIfUnset(withNS, &b.ObjectMeta) +} + +func (bs *BrokerSpec) SetDefaults(ctx context.Context) { + cfg := config.FromContextOrDefaults(ctx) + c, err := cfg.Defaults.GetBrokerConfig(apis.ParentMeta(ctx).Namespace) + if err == nil { + if bs.Config == nil { + bs.Config = c.KReference + } + if bs.Delivery == nil && c.Delivery != nil { + bs.Delivery = &eventingduckv1beta1.DeliverySpec{} + bs.Delivery.ConvertFrom(ctx, c.Delivery) + } + } + // Default the namespace if not given + if bs.Config != nil { + bs.Config.SetDefaults(ctx) + } +} diff --git a/pkg/apis/eventing/v1beta1/broker_defaults_test.go b/pkg/apis/eventing/v1beta1/broker_defaults_test.go new file mode 100644 index 00000000000..06b70d5f899 --- /dev/null +++ b/pkg/apis/eventing/v1beta1/broker_defaults_test.go @@ -0,0 +1,418 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + + "k8s.io/utils/pointer" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "knative.dev/eventing/pkg/apis/config" + "knative.dev/eventing/pkg/apis/eventing" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +var ( + defaultConfig = &config.Config{ + Defaults: &config.Defaults{ + // NamespaceDefaultsConfig are the default Broker Configs for each namespace. + // Namespace is the key, the value is the KReference to the config. + NamespaceDefaultsConfig: map[string]*config.ClassAndBrokerConfig{ + "mynamespace": { + BrokerConfig: &config.BrokerConfig{ + KReference: &duckv1.KReference{ + APIVersion: "v1", + Kind: "ConfigMap", + Namespace: "knative-eventing", + Name: "kafka-channel", + }, + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + Namespace: "default", + Name: "handle-error", + APIVersion: "serving.knative.dev/v1", + }, + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: (*eventingduckv1.BackoffPolicyType)(pointer.StringPtr("exponential")), + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + "mynamespace2": { + BrokerClass: "mynamespace2class", + BrokerConfig: &config.BrokerConfig{ + KReference: &duckv1.KReference{ + APIVersion: "v1", + Kind: "ConfigMap", + Namespace: "knative-eventing", + Name: "natss-channel", + }, + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + Namespace: "knative-eventing", + Name: "handle-error", + APIVersion: "serving.knative.dev/v1", + }, + }, + Retry: pointer.Int32Ptr(3), + BackoffPolicy: (*eventingduckv1.BackoffPolicyType)(pointer.StringPtr("exponential")), + BackoffDelay: pointer.StringPtr("3s"), + }, + }, + }, + "mynamespace3": { + BrokerClass: "mynamespace3class", + BrokerConfig: &config.BrokerConfig{ + KReference: &duckv1.KReference{ + APIVersion: "v1", + Kind: "ConfigMap", + Name: "natss-channel", + }, + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + Namespace: "mynamespace3", + Name: "handle-error", + APIVersion: "serving.knative.dev/v1", + }, + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: (*eventingduckv1.BackoffPolicyType)(pointer.StringPtr("linear")), + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + }, + ClusterDefault: &config.ClassAndBrokerConfig{ + BrokerClass: eventing.MTChannelBrokerClassValue, + BrokerConfig: &config.BrokerConfig{ + KReference: &duckv1.KReference{ + APIVersion: "v1", + Kind: "ConfigMap", + Namespace: "knative-eventing", + Name: "imc-channel", + }, + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + Namespace: "knative-eventing", + Name: "handle-error", + APIVersion: "serving.knative.dev/v1", + }, + }, + Retry: pointer.Int32Ptr(3), + BackoffPolicy: (*eventingduckv1.BackoffPolicyType)(pointer.StringPtr("exponential")), + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + }, + } +) + +func TestBrokerSetDefaults(t *testing.T) { + testCases := map[string]struct { + initial Broker + expected Broker + }{ + "default everything from cluster": { + expected: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + eventing.BrokerClassKey: eventing.MTChannelBrokerClassValue, + }, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + APIVersion: "v1", + Kind: "ConfigMap", + Namespace: "knative-eventing", + Name: "imc-channel", + }, + Delivery: &eventingduckv1beta1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + Namespace: "knative-eventing", + Name: "handle-error", + APIVersion: "serving.knative.dev/v1", + }, + }, + Retry: pointer.Int32Ptr(3), + BackoffPolicy: (*eventingduckv1beta1.BackoffPolicyType)(pointer.StringPtr("exponential")), + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + }, + "default annotation from cluster": { + expected: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + eventing.BrokerClassKey: eventing.MTChannelBrokerClassValue, + }, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + APIVersion: "v1", + Kind: "ConfigMap", + Namespace: "knative-eventing", + Name: "imc-channel", + }, + Delivery: &eventingduckv1beta1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + Namespace: "knative-eventing", + Name: "handle-error", + APIVersion: "serving.knative.dev/v1", + }, + }, + Retry: pointer.Int32Ptr(3), + BackoffPolicy: (*eventingduckv1beta1.BackoffPolicyType)(pointer.StringPtr("exponential")), + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + initial: Broker{ + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + APIVersion: "v1", + Kind: "ConfigMap", + Namespace: "knative-eventing", + Name: "imc-channel", + }, + }, + }, + }, + "config already specified, adds annotation": { + initial: Broker{ + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + APIVersion: SchemeGroupVersion.String(), + Kind: "OtherChannel", + }, + }, + }, + expected: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + eventing.BrokerClassKey: eventing.MTChannelBrokerClassValue, + }, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + APIVersion: SchemeGroupVersion.String(), + Kind: "OtherChannel", + }, + Delivery: &eventingduckv1beta1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + Namespace: "knative-eventing", + Name: "handle-error", + APIVersion: "serving.knative.dev/v1", + }, + }, + Retry: pointer.Int32Ptr(3), + BackoffPolicy: (*eventingduckv1beta1.BackoffPolicyType)(pointer.StringPtr("exponential")), + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + }, + "no config, uses namespace broker config, cluster class": { + initial: Broker{ + ObjectMeta: metav1.ObjectMeta{Namespace: "mynamespace"}, + }, + expected: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "mynamespace", + Annotations: map[string]string{ + eventing.BrokerClassKey: eventing.MTChannelBrokerClassValue, + }, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Kind: "ConfigMap", + Namespace: "knative-eventing", + Name: "kafka-channel", + APIVersion: "v1", + }, + Delivery: &eventingduckv1beta1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + Namespace: "default", + Name: "handle-error", + APIVersion: "serving.knative.dev/v1", + }, + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: (*eventingduckv1beta1.BackoffPolicyType)(pointer.StringPtr("exponential")), + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + }, + "no config, uses namespace broker config, defaults namespace, cluster class": { + initial: Broker{ + ObjectMeta: metav1.ObjectMeta{Namespace: "mynamespace3"}, + }, + expected: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "mynamespace3", + Annotations: map[string]string{ + eventing.BrokerClassKey: "mynamespace3class", + }, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Kind: "ConfigMap", + Namespace: "mynamespace3", + Name: "natss-channel", + APIVersion: "v1", + }, + Delivery: &eventingduckv1beta1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + Namespace: "mynamespace3", + Name: "handle-error", + APIVersion: "serving.knative.dev/v1", + }, + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: (*eventingduckv1beta1.BackoffPolicyType)(pointer.StringPtr("linear")), + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + }, + "no config, uses namespace broker config and class": { + initial: Broker{ + ObjectMeta: metav1.ObjectMeta{Namespace: "mynamespace2"}, + }, + expected: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "mynamespace2", + Annotations: map[string]string{ + eventing.BrokerClassKey: "mynamespace2class", + }, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Kind: "ConfigMap", + Namespace: "knative-eventing", + Name: "natss-channel", + APIVersion: "v1", + }, + Delivery: &eventingduckv1beta1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + Namespace: "knative-eventing", + Name: "handle-error", + APIVersion: "serving.knative.dev/v1", + }, + }, + Retry: pointer.Int32Ptr(3), + BackoffPolicy: (*eventingduckv1beta1.BackoffPolicyType)(pointer.StringPtr("exponential")), + BackoffDelay: pointer.StringPtr("3s"), + }, + }, + }, + }, + "config, missing namespace, defaulted": { + initial: Broker{ + ObjectMeta: metav1.ObjectMeta{Name: "rando", Namespace: "randons"}, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Kind: "ConfigMap", + Name: "natss-channel", + APIVersion: "v1", + }, + Delivery: &eventingduckv1beta1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + Namespace: "rando", + Name: "handle-error", + APIVersion: "serving.knative.dev/v1", + }, + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: (*eventingduckv1beta1.BackoffPolicyType)(pointer.StringPtr("linear")), + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + expected: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rando", + Namespace: "randons", + Annotations: map[string]string{ + eventing.BrokerClassKey: "MTChannelBasedBroker", + }, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Kind: "ConfigMap", + Namespace: "randons", + Name: "natss-channel", + APIVersion: "v1", + }, + Delivery: &eventingduckv1beta1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Service", + Namespace: "rando", + Name: "handle-error", + APIVersion: "serving.knative.dev/v1", + }, + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: (*eventingduckv1beta1.BackoffPolicyType)(pointer.StringPtr("linear")), + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + tc.initial.SetDefaults(config.ToContext(context.Background(), defaultConfig)) + if diff := cmp.Diff(tc.expected, tc.initial); diff != "" { + t.Fatal("Unexpected defaults (-want, +got):", diff) + } + }) + } +} diff --git a/pkg/apis/eventing/v1beta1/broker_lifecycle.go b/pkg/apis/eventing/v1beta1/broker_lifecycle.go new file mode 100644 index 00000000000..b4169bccab9 --- /dev/null +++ b/pkg/apis/eventing/v1beta1/broker_lifecycle.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. +*/ + +package v1beta1 + +import ( + "sync" + + "knative.dev/pkg/apis" +) + +const ( + BrokerConditionReady = apis.ConditionReady + BrokerConditionIngress apis.ConditionType = "IngressReady" + BrokerConditionTriggerChannel apis.ConditionType = "TriggerChannelReady" + BrokerConditionFilter apis.ConditionType = "FilterReady" + BrokerConditionAddressable apis.ConditionType = "Addressable" +) + +var brokerCondSet = apis.NewLivingConditionSet( + BrokerConditionIngress, + BrokerConditionTriggerChannel, + BrokerConditionFilter, + BrokerConditionAddressable, +) +var brokerCondSetLock = sync.RWMutex{} + +// RegisterAlternateBrokerConditionSet register a apis.ConditionSet for the given broker class. +func RegisterAlternateBrokerConditionSet(conditionSet apis.ConditionSet) { + brokerCondSetLock.Lock() + defer brokerCondSetLock.Unlock() + + brokerCondSet = conditionSet +} + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (b *Broker) GetConditionSet() apis.ConditionSet { + brokerCondSetLock.RLock() + defer brokerCondSetLock.RUnlock() + + return brokerCondSet +} + +// GetConditionSet retrieves the condition set for this resource. +func (bs *BrokerStatus) GetConditionSet() apis.ConditionSet { + brokerCondSetLock.RLock() + defer brokerCondSetLock.RUnlock() + + return brokerCondSet +} + +// GetTopLevelCondition returns the top level Condition. +func (bs *BrokerStatus) GetTopLevelCondition() *apis.Condition { + return bs.GetConditionSet().Manage(bs).GetTopLevelCondition() +} + +// SetAddress makes this Broker addressable by setting the URI. It also +// sets the BrokerConditionAddressable to true. +func (bs *BrokerStatus) SetAddress(url *apis.URL) { + bs.Address.URL = url + if url != nil { + bs.GetConditionSet().Manage(bs).MarkTrue(BrokerConditionAddressable) + } else { + bs.GetConditionSet().Manage(bs).MarkFalse(BrokerConditionAddressable, "nil URL", "URL is nil") + } +} + +// GetCondition returns the condition currently associated with the given type, or nil. +func (bs *BrokerStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return bs.GetConditionSet().Manage(bs).GetCondition(t) +} + +// IsReady returns true if the resource is ready overall. +func (bs *BrokerStatus) IsReady() bool { + return bs.GetConditionSet().Manage(bs).IsHappy() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (bs *BrokerStatus) InitializeConditions() { + bs.GetConditionSet().Manage(bs).InitializeConditions() +} diff --git a/pkg/apis/eventing/v1beta1/broker_lifecycle_mt.go b/pkg/apis/eventing/v1beta1/broker_lifecycle_mt.go new file mode 100644 index 00000000000..1722576b49d --- /dev/null +++ b/pkg/apis/eventing/v1beta1/broker_lifecycle_mt.go @@ -0,0 +1,62 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + + "knative.dev/eventing/pkg/apis/duck" + duckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" +) + +func (bs *BrokerStatus) MarkIngressFailed(reason, format string, args ...interface{}) { + bs.GetConditionSet().Manage(bs).MarkFalse(BrokerConditionIngress, reason, format, args...) +} + +func (bs *BrokerStatus) PropagateIngressAvailability(ep *corev1.Endpoints) { + if duck.EndpointsAreAvailable(ep) { + bs.GetConditionSet().Manage(bs).MarkTrue(BrokerConditionIngress) + } else { + bs.MarkIngressFailed("EndpointsUnavailable", "Endpoints %q are unavailable.", ep.Name) + } +} + +func (bs *BrokerStatus) MarkTriggerChannelFailed(reason, format string, args ...interface{}) { + bs.GetConditionSet().Manage(bs).MarkFalse(BrokerConditionTriggerChannel, reason, format, args...) +} + +func (bs *BrokerStatus) PropagateTriggerChannelReadiness(cs *duckv1beta1.ChannelableStatus) { + // TODO: Once you can get a Ready status from Channelable in a generic way, use it here... + address := cs.AddressStatus.Address + if address != nil { + bs.GetConditionSet().Manage(bs).MarkTrue(BrokerConditionTriggerChannel) + } else { + bs.MarkTriggerChannelFailed("ChannelNotReady", "trigger Channel is not ready: not addressable") + } +} + +func (bs *BrokerStatus) MarkFilterFailed(reason, format string, args ...interface{}) { + bs.GetConditionSet().Manage(bs).MarkFalse(BrokerConditionFilter, reason, format, args...) +} + +func (bs *BrokerStatus) PropagateFilterAvailability(ep *corev1.Endpoints) { + if duck.EndpointsAreAvailable(ep) { + bs.GetConditionSet().Manage(bs).MarkTrue(BrokerConditionFilter) + } else { + bs.MarkFilterFailed("EndpointsUnavailable", "Endpoints %q are unavailable.", ep.Name) + } +} diff --git a/pkg/apis/eventing/v1beta1/broker_lifecycle_test.go b/pkg/apis/eventing/v1beta1/broker_lifecycle_test.go new file mode 100644 index 00000000000..793db025fe2 --- /dev/null +++ b/pkg/apis/eventing/v1beta1/broker_lifecycle_test.go @@ -0,0 +1,391 @@ +/* +Copyright 2020 The Knative Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + duckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/eventing/pkg/apis/eventing" + + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +var ( + trueVal = true + falseVal = false +) + +var ( + brokerConditionReady = apis.Condition{ + Type: BrokerConditionReady, + Status: corev1.ConditionTrue, + } + + brokerConditionIngress = apis.Condition{ + Type: BrokerConditionIngress, + Status: corev1.ConditionTrue, + } + + brokerConditionTriggerChannel = apis.Condition{ + Type: BrokerConditionTriggerChannel, + Status: corev1.ConditionTrue, + } + + brokerConditionFilter = apis.Condition{ + Type: BrokerConditionFilter, + Status: corev1.ConditionTrue, + } + + brokerConditionAddressable = apis.Condition{ + Type: BrokerConditionAddressable, + Status: corev1.ConditionFalse, + } +) + +func TestBrokerGetConditionSet(t *testing.T) { + + customCondition := apis.NewLivingConditionSet( + apis.ConditionReady, + "ConditionGolangReady", + ) + brokerClass := "Golang" + + tt := []struct { + name string + broker Broker + expectedConditionSet apis.ConditionSet + }{ + { + name: "default condition set", + broker: Broker{}, + expectedConditionSet: brokerCondSet, + }, + { + name: "custom condition set", + broker: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + eventing.BrokerClassKey: brokerClass, + }, + }, + }, + expectedConditionSet: customCondition, + }, + } + + for _, tc := range tt { + tc := tc + t.Run(tc.name, func(t *testing.T) { + defer RegisterAlternateBrokerConditionSet(brokerCondSet) // reset to default condition set + + RegisterAlternateBrokerConditionSet(tc.expectedConditionSet) + + if diff := cmp.Diff(tc.expectedConditionSet, tc.broker.GetConditionSet(), cmp.AllowUnexported(apis.ConditionSet{})); diff != "" { + t.Error("unexpected conditions (-want, +got)", diff) + } + if diff := cmp.Diff(tc.expectedConditionSet, tc.broker.Status.GetConditionSet(), cmp.AllowUnexported(apis.ConditionSet{})); diff != "" { + t.Error("unexpected conditions (-want, +got)", diff) + } + }) + } +} + +func TestBrokerGetCondition(t *testing.T) { + tests := []struct { + name string + bs *BrokerStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "single condition", + bs: &BrokerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + brokerConditionReady, + }, + }, + }, + condQuery: apis.ConditionReady, + want: &brokerConditionReady, + }, { + name: "multiple conditions", + bs: &BrokerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + brokerConditionIngress, + brokerConditionTriggerChannel, + brokerConditionFilter, + }, + }, + }, + condQuery: BrokerConditionFilter, + want: &brokerConditionFilter, + }, { + name: "multiple conditions, condition false", + bs: &BrokerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + brokerConditionTriggerChannel, + brokerConditionFilter, + brokerConditionAddressable, + }, + }, + }, + condQuery: BrokerConditionAddressable, + want: &brokerConditionAddressable, + }, { + name: "unknown condition", + bs: &BrokerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + brokerConditionAddressable, + brokerConditionReady, + }, + }, + }, + condQuery: apis.ConditionType("foo"), + want: nil, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.bs.GetCondition(test.condQuery) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Error("unexpected condition (-want, +got) =", diff) + } + }) + } +} + +func TestBrokerInitializeConditions(t *testing.T) { + tests := []struct { + name string + bs *BrokerStatus + want *BrokerStatus + }{{ + name: "empty", + bs: &BrokerStatus{}, + want: &BrokerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: BrokerConditionAddressable, + Status: corev1.ConditionUnknown, + }, { + Type: BrokerConditionFilter, + Status: corev1.ConditionUnknown, + }, { + Type: BrokerConditionIngress, + Status: corev1.ConditionUnknown, + }, { + Type: BrokerConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: BrokerConditionTriggerChannel, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, { + name: "one false", + bs: &BrokerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: BrokerConditionTriggerChannel, + Status: corev1.ConditionFalse, + }}, + }, + }, + want: &BrokerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: BrokerConditionAddressable, + Status: corev1.ConditionUnknown, + }, { + Type: BrokerConditionFilter, + Status: corev1.ConditionUnknown, + }, { + Type: BrokerConditionIngress, + Status: corev1.ConditionUnknown, + }, { + Type: BrokerConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: BrokerConditionTriggerChannel, + Status: corev1.ConditionFalse, + }}, + }, + }, + }, { + name: "one true", + bs: &BrokerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: BrokerConditionFilter, + Status: corev1.ConditionTrue, + }}, + }, + }, + want: &BrokerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: BrokerConditionAddressable, + Status: corev1.ConditionUnknown, + }, { + Type: BrokerConditionFilter, + Status: corev1.ConditionTrue, + }, { + Type: BrokerConditionIngress, + Status: corev1.ConditionUnknown, + }, { + Type: BrokerConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: BrokerConditionTriggerChannel, + Status: corev1.ConditionUnknown, + }}, + }, + }}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.bs.InitializeConditions() + if diff := cmp.Diff(test.want, test.bs, ignoreAllButTypeAndStatus); diff != "" { + t.Error("unexpected conditions (-want, +got) =", diff) + } + }) + } +} + +func TestBrokerIsReady(t *testing.T) { + tests := []struct { + name string + markIngressReady *bool + markTriggerChannelReady *bool + markFilterReady *bool + address *apis.URL + markIngressSubscriptionOwned bool + markIngressSubscriptionReady *bool + wantReady bool + }{{ + name: "all happy", + markIngressReady: &trueVal, + markTriggerChannelReady: &trueVal, + markFilterReady: &trueVal, + address: &apis.URL{Scheme: "http", Host: "hostname"}, + markIngressSubscriptionOwned: true, + markIngressSubscriptionReady: &trueVal, + wantReady: true, + }, { + name: "all happy - deprecated", + markIngressReady: &trueVal, + markTriggerChannelReady: &trueVal, + markFilterReady: &trueVal, + address: &apis.URL{Scheme: "http", Host: "hostname"}, + markIngressSubscriptionOwned: true, + markIngressSubscriptionReady: &trueVal, + wantReady: true, + }, { + name: "ingress sad", + markIngressReady: &falseVal, + markTriggerChannelReady: &trueVal, + markFilterReady: &trueVal, + address: &apis.URL{Scheme: "http", Host: "hostname"}, + markIngressSubscriptionOwned: true, + markIngressSubscriptionReady: &trueVal, + wantReady: false, + }, { + name: "trigger channel sad", + markIngressReady: &trueVal, + markTriggerChannelReady: &falseVal, + markFilterReady: &trueVal, + address: &apis.URL{Scheme: "http", Host: "hostname"}, + markIngressSubscriptionOwned: true, + markIngressSubscriptionReady: &trueVal, + wantReady: false, + }, { + name: "filter sad", + markIngressReady: &trueVal, + markTriggerChannelReady: &trueVal, + markFilterReady: &falseVal, + address: &apis.URL{Scheme: "http", Host: "hostname"}, + markIngressSubscriptionOwned: true, + markIngressSubscriptionReady: &trueVal, + wantReady: false, + }, { + name: "addressable sad", + markIngressReady: &trueVal, + markTriggerChannelReady: &trueVal, + markFilterReady: &trueVal, + address: nil, + markIngressSubscriptionOwned: true, + markIngressSubscriptionReady: &trueVal, + wantReady: false, + }, { + name: "all sad", + markIngressReady: &falseVal, + markTriggerChannelReady: &falseVal, + markFilterReady: &falseVal, + address: nil, + markIngressSubscriptionOwned: true, + markIngressSubscriptionReady: &falseVal, + wantReady: false, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + bs := &BrokerStatus{} + if test.markIngressReady != nil { + var ep *corev1.Endpoints + if *test.markIngressReady { + ep = TestHelper.AvailableEndpoints() + } else { + ep = TestHelper.UnavailableEndpoints() + } + bs.PropagateIngressAvailability(ep) + } + if test.markTriggerChannelReady != nil { + var c *duckv1beta1.ChannelableStatus + if *test.markTriggerChannelReady { + c = TestHelper.ReadyChannelStatus() + } else { + c = TestHelper.NotReadyChannelStatus() + } + bs.PropagateTriggerChannelReadiness(c) + } + if test.markFilterReady != nil { + var ep *corev1.Endpoints + if *test.markFilterReady { + ep = TestHelper.AvailableEndpoints() + } else { + ep = TestHelper.UnavailableEndpoints() + } + bs.PropagateFilterAvailability(ep) + } + bs.SetAddress(test.address) + + got := bs.IsReady() + if test.wantReady != got { + t.Errorf("unexpected readiness: want %v, got %v", test.wantReady, got) + } + + }) + } +} diff --git a/pkg/apis/eventing/v1beta1/broker_types.go b/pkg/apis/eventing/v1beta1/broker_types.go new file mode 100644 index 00000000000..66cf13d3a08 --- /dev/null +++ b/pkg/apis/eventing/v1beta1/broker_types.go @@ -0,0 +1,119 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +genreconciler:class=eventing.knative.dev/broker.class,krshapedlogic=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Broker collects a pool of events that are consumable using Triggers. Brokers +// provide a well-known endpoint for event delivery that senders can use with +// minimal knowledge of the event routing strategy. Receivers use Triggers to +// request delivery of events from a Broker's pool to a specific URL or +// Addressable endpoint. +type Broker struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of the Broker. + Spec BrokerSpec `json:"spec,omitempty"` + + // Status represents the current state of the Broker. This data may be out of + // date. + // +optional + Status BrokerStatus `json:"status,omitempty"` +} + +var ( + // Check that Broker can be validated, can be defaulted, and has immutable fields. + _ apis.Validatable = (*Broker)(nil) + _ apis.Defaultable = (*Broker)(nil) + + // Check that Broker can return its spec untyped. + _ apis.HasSpec = (*Broker)(nil) + + _ runtime.Object = (*Broker)(nil) + + // Check that we can create OwnerReferences to a Broker. + _ kmeta.OwnerRefable = (*Broker)(nil) + + // Check that the type conforms to the duck Knative Resource shape. + _ duckv1.KRShaped = (*Broker)(nil) +) + +type BrokerSpec struct { + // Config is a KReference to the configuration that specifies + // configuration options for this Broker. For example, this could be + // a pointer to a ConfigMap. + // +optional + Config *duckv1.KReference `json:"config,omitempty"` + + // Delivery is the delivery specification for Events within the Broker mesh. + // This includes things like retries, DLQ, etc. + // +optional + Delivery *eventingduckv1beta1.DeliverySpec `json:"delivery,omitempty"` +} + +// BrokerStatus represents the current state of a Broker. +type BrokerStatus struct { + // inherits duck/v1 Status, 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. + duckv1.Status `json:",inline"` + + // Broker is Addressable. It exposes the endpoint as an URI to get events + // delivered into the Broker mesh. + Address duckv1.Addressable `json:"address,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// BrokerList is a collection of Brokers. +type BrokerList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + Items []Broker `json:"items"` +} + +// GetGroupVersionKind returns GroupVersionKind for Brokers +func (t *Broker) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("Broker") +} + +// GetUntypedSpec returns the spec of the Broker. +func (b *Broker) GetUntypedSpec() interface{} { + return b.Spec +} + +// GetStatus retrieves the status of the Broker. Implements the KRShaped interface. +func (t *Broker) GetStatus() *duckv1.Status { + return &t.Status.Status +} diff --git a/pkg/apis/eventing/v1beta1/broker_types_test.go b/pkg/apis/eventing/v1beta1/broker_types_test.go new file mode 100644 index 00000000000..62bfec6495b --- /dev/null +++ b/pkg/apis/eventing/v1beta1/broker_types_test.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 v1beta1 + +import "testing" + +func TestBrokerGetStatus(t *testing.T) { + r := &Broker{ + Status: BrokerStatus{}, + } + if got, want := r.GetStatus(), &r.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} + +func TestBroker_GetGroupVersionKind(t *testing.T) { + b := Broker{} + gvk := b.GetGroupVersionKind() + if gvk.Kind != "Broker" { + t.Errorf("Should be Broker.") + } +} diff --git a/pkg/apis/eventing/v1beta1/broker_validation.go b/pkg/apis/eventing/v1beta1/broker_validation.go new file mode 100644 index 00000000000..e77d209481d --- /dev/null +++ b/pkg/apis/eventing/v1beta1/broker_validation.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. +*/ + +package v1beta1 + +import ( + "context" + + "knative.dev/pkg/apis" + "knative.dev/pkg/kmp" +) + +const ( + BrokerClassAnnotationKey = "eventing.knative.dev/broker.class" +) + +func (b *Broker) Validate(ctx context.Context) *apis.FieldError { + withNS := apis.AllowDifferentNamespace(apis.WithinParent(ctx, b.ObjectMeta)) + + // Make sure a BrokerClassAnnotation exists + var errs *apis.FieldError + if bc, ok := b.GetAnnotations()[BrokerClassAnnotationKey]; !ok || bc == "" { + errs = errs.Also(apis.ErrMissingField(BrokerClassAnnotationKey)) + } + + errs = errs.Also(b.Spec.Validate(withNS).ViaField("spec")) + + if apis.IsInUpdate(ctx) { + original := apis.GetBaseline(ctx).(*Broker) + errs = errs.Also(b.CheckImmutableFields(ctx, original)) + } + return errs +} + +func (bs *BrokerSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + + // Validate the Config + if bs.Config != nil { + if ce := bs.Config.Validate(ctx); ce != nil { + errs = errs.Also(ce.ViaField("config")) + } + } + + if bs.Delivery != nil { + if de := bs.Delivery.Validate(ctx); de != nil { + errs = errs.Also(de.ViaField("delivery")) + } + } + return errs +} + +func (b *Broker) CheckImmutableFields(ctx context.Context, original *Broker) *apis.FieldError { + if original == nil { + return nil + } + + // Make sure you can't change the class annotation. + diff, err := kmp.ShortDiff(original.GetAnnotations()[BrokerClassAnnotationKey], b.GetAnnotations()[BrokerClassAnnotationKey]) + + if err != nil { + return &apis.FieldError{ + Message: "couldn't diff the Broker objects", + Details: err.Error(), + } + } + + if diff != "" { + return &apis.FieldError{ + Message: "Immutable fields changed (-old +new)", + Paths: []string{"annotations"}, + Details: diff, + } + } + return nil +} diff --git a/pkg/apis/eventing/v1beta1/broker_validation_test.go b/pkg/apis/eventing/v1beta1/broker_validation_test.go new file mode 100644 index 00000000000..8948a9f3440 --- /dev/null +++ b/pkg/apis/eventing/v1beta1/broker_validation_test.go @@ -0,0 +1,321 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestBrokerImmutableFields(t *testing.T) { + original := &Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"eventing.knative.dev/broker.class": "original"}, + }, + } + current := &Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"eventing.knative.dev/broker.class": "current"}, + }, + } + + tests := map[string]struct { + og *Broker + wantErr *apis.FieldError + }{ + "nil original": { + wantErr: nil, + }, + "no BrokerClassAnnotation mutation": { + og: current, + wantErr: nil, + }, + "BrokerClassAnnotation mutated": { + og: original, + wantErr: &apis.FieldError{ + Message: "Immutable fields changed (-old +new)", + Paths: []string{"annotations"}, + Details: `{string}: + -: "original" + +: "current" +`, + }, + }, + } + + for n, test := range tests { + t.Run(n, func(t *testing.T) { + gotErr := current.CheckImmutableFields(context.Background(), test.og) + if diff := cmp.Diff(test.wantErr.Error(), gotErr.Error()); diff != "" { + t.Error("Broker.CheckImmutableFields (-want, +got) =", diff) + } + }) + } +} + +func TestValidate(t *testing.T) { + invalidString := "invalid time" + tests := []struct { + name string + b Broker + want *apis.FieldError + }{{ + name: "missing annotation", + b: Broker{}, + want: apis.ErrMissingField("eventing.knative.dev/broker.class"), + }, { + name: "empty annotation", + b: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"eventing.knative.dev/broker.class": ""}, + }, + }, + want: apis.ErrMissingField("eventing.knative.dev/broker.class"), + }, { + name: "valid empty", + b: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"eventing.knative.dev/broker.class": "MTChannelBasedBroker"}, + }, + }, + }, { + name: "valid config", + b: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"eventing.knative.dev/broker.class": "MTChannelBasedBroker"}, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Namespace: "namespace", + Name: "name", + Kind: "kind", + APIVersion: "apiversion", + }, + }, + }, + }, { + name: "valid config, no namespace", + b: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"eventing.knative.dev/broker.class": "MTChannelBasedBroker"}, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Name: "name", + Kind: "kind", + APIVersion: "apiversion", + }, + }, + }, + }, { + name: "invalid config, missing name", + b: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"eventing.knative.dev/broker.class": "MTChannelBasedBroker"}, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Namespace: "namespace", + Kind: "kind", + APIVersion: "apiversion", + }, + }, + }, + want: apis.ErrMissingField("spec.config.name"), + }, { + name: "invalid config, missing apiVersion", + b: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"eventing.knative.dev/broker.class": "MTChannelBasedBroker"}, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Namespace: "namespace", + Name: "name", + Kind: "kind", + }, + }, + }, + want: apis.ErrMissingField("spec.config.apiVersion"), + }, { + name: "invalid config, missing kind", + b: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"eventing.knative.dev/broker.class": "MTChannelBasedBroker"}, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Namespace: "namespace", + Name: "name", + APIVersion: "apiversion", + }, + }, + }, + want: apis.ErrMissingField("spec.config.kind"), + }, { + name: "invalid delivery, invalid delay string", + b: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"eventing.knative.dev/broker.class": "MTChannelBasedBroker"}, + }, + Spec: BrokerSpec{ + Delivery: &eventingduckv1beta1.DeliverySpec{ + BackoffDelay: &invalidString, + }, + }, + }, + want: apis.ErrInvalidValue(invalidString, "spec.delivery.backoffDelay"), + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.b.Validate(context.Background()) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("Broker.Validate (-want, +got) =", diff) + } + }) + } +} + +func TestValidateUpdate(t *testing.T) { + tests := []struct { + name string + b Broker + bNew Broker + want *apis.FieldError + }{{ + name: "valid config change", + b: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"eventing.knative.dev/broker.class": "MTChannelBasedBroker"}, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Namespace: "namespace", + Name: "name", + Kind: "kind", + APIVersion: "apiversion", + }, + }, + }, + bNew: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"eventing.knative.dev/broker.class": "MTChannelBasedBroker"}, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Namespace: "namespace", + Name: "name2", + Kind: "kind", + APIVersion: "apiversion", + }, + }, + }, + }, { + name: "invalid config change, broker.class", + b: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"eventing.knative.dev/broker.class": "MTChannelBasedBroker"}, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Namespace: "namespace", + Name: "name", + Kind: "kind", + APIVersion: "apiversion", + }, + }, + }, + bNew: Broker{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{"eventing.knative.dev/broker.class": "SomeOtherBrokerClass"}, + }, + Spec: BrokerSpec{ + Config: &duckv1.KReference{ + Namespace: "namespace", + Name: "name", + Kind: "kind", + APIVersion: "apiversion", + }, + }, + }, + want: &apis.FieldError{ + Message: "Immutable fields changed (-old +new)", + Paths: []string{"annotations"}, + Details: `{string}: + -: "MTChannelBasedBroker" + +: "SomeOtherBrokerClass" +`, + }, + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := apis.WithinUpdate(context.Background(), &test.b) + got := test.bNew.Validate(ctx) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("Broker.Validate (-want, +got) =", diff) + } + }) + } +} + +func TestValidSpec(t *testing.T) { + bop := eventingduckv1beta1.BackoffPolicyExponential + tests := []struct { + name string + spec BrokerSpec + want *apis.FieldError + }{{ + name: "valid empty", + spec: BrokerSpec{}, + }, { + name: "valid config", + spec: BrokerSpec{ + Config: &duckv1.KReference{ + Namespace: "namespace", + Name: "name", + Kind: "kind", + APIVersion: "apiversion", + }, + }, + }, { + name: "valid delivery", + spec: BrokerSpec{ + Config: &duckv1.KReference{ + Namespace: "namespace", + Name: "name", + Kind: "kind", + APIVersion: "apiversion", + }, + Delivery: &eventingduckv1beta1.DeliverySpec{BackoffPolicy: &bop}, + }, + }, {}} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.spec.Validate(context.Background()) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("BrokerSpec.Validate (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/eventing/v1beta1/eventtype_lifecycle.go b/pkg/apis/eventing/v1beta1/eventtype_lifecycle.go index bc87d472014..a713b0b6d78 100644 --- a/pkg/apis/eventing/v1beta1/eventtype_lifecycle.go +++ b/pkg/apis/eventing/v1beta1/eventtype_lifecycle.go @@ -18,8 +18,8 @@ package v1beta1 import ( corev1 "k8s.io/api/core/v1" - eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + "knative.dev/pkg/apis" ) diff --git a/pkg/apis/eventing/v1beta1/eventtype_lifecycle_test.go b/pkg/apis/eventing/v1beta1/eventtype_lifecycle_test.go index 81074476be3..c2be74cb1f9 100644 --- a/pkg/apis/eventing/v1beta1/eventtype_lifecycle_test.go +++ b/pkg/apis/eventing/v1beta1/eventtype_lifecycle_test.go @@ -19,11 +19,10 @@ package v1beta1 import ( "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + "github.com/google/go-cmp/cmp" corev1 "k8s.io/api/core/v1" - eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" "knative.dev/pkg/apis" duckv1 "knative.dev/pkg/apis/duck/v1" ) @@ -31,7 +30,9 @@ import ( var ( trueValue = true falseValue = false +) +var ( eventTypeConditionReady = apis.Condition{ Type: EventTypeConditionReady, Status: corev1.ConditionTrue, @@ -46,10 +47,6 @@ var ( Type: EventTypeConditionBrokerReady, Status: corev1.ConditionTrue, } - - ignoreAllButTypeAndStatus = cmpopts.IgnoreFields( - apis.Condition{}, - "LastTransitionTime", "Message", "Reason", "Severity") ) func TestEventTypeGetConditionSet(t *testing.T) { diff --git a/pkg/apis/eventing/v1beta1/register.go b/pkg/apis/eventing/v1beta1/register.go index 29ade310aec..9367cb007e8 100644 --- a/pkg/apis/eventing/v1beta1/register.go +++ b/pkg/apis/eventing/v1beta1/register.go @@ -45,8 +45,12 @@ var ( // Adds the list of known types to Scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, + &Broker{}, + &BrokerList{}, &EventType{}, &EventTypeList{}, + &Trigger{}, + &TriggerList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/pkg/apis/eventing/v1beta1/roundtrip_test.go b/pkg/apis/eventing/v1beta1/roundtrip_test.go new file mode 100644 index 00000000000..5d71bcb0077 --- /dev/null +++ b/pkg/apis/eventing/v1beta1/roundtrip_test.go @@ -0,0 +1,114 @@ +/* +Copyright 2020 The Knative Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "math/rand" + "testing" + + fuzz "github.com/google/gofuzz" + "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + duckv1 "knative.dev/eventing/pkg/apis/duck/v1" + v1 "knative.dev/eventing/pkg/apis/eventing/v1" + pkgfuzzer "knative.dev/pkg/apis/testing/fuzzer" + "knative.dev/pkg/apis/testing/roundtrip" +) + +var ( + linear = duckv1.BackoffPolicyLinear + exponential = duckv1.BackoffPolicyExponential + bops = []*duckv1.BackoffPolicyType{nil, &linear, &exponential} +) + +// FuzzerFuncs includes fuzzing funcs for knative.dev/eventing v1 types +// +// For other examples see +// https://github.com/kubernetes/apimachinery/blob/master/pkg/apis/meta/fuzzer/fuzzer.go +var FuzzerFuncs = fuzzer.MergeFuzzerFuncs( + func(codecs serializer.CodecFactory) []interface{} { + return []interface{}{ + func(s *v1.TriggerStatus, c fuzz.Continue) { + c.FuzzNoCustom(s) // fuzz the status object + + // Clear the random fuzzed condition + s.Status.SetConditions(nil) + + // Fuzz the known conditions except their type value + s.InitializeConditions() + pkgfuzzer.FuzzConditions(&s.Status, c) + }, + func(s *v1.BrokerStatus, c fuzz.Continue) { + c.FuzzNoCustom(s) // fuzz the status object + + // Clear the random fuzzed condition + s.Status.SetConditions(nil) + + // Fuzz the known conditions except their type value + s.InitializeConditions() + pkgfuzzer.FuzzConditions(&s.Status, c) + }, + func(ds *duckv1.DeliverySpec, c fuzz.Continue) { + c.FuzzNoCustom(ds) // fuzz the DeliverySpec + if ds.BackoffPolicy != nil && *ds.BackoffPolicy == "" { + ds.BackoffPolicy = nil + } else { + //nolint:gosec // Cryptographic randomness is not necessary. + ds.BackoffPolicy = bops[rand.Intn(3)] + } + }, + } + }, +) + +func TestEventingRoundTripTypesToJSON(t *testing.T) { + scheme := runtime.NewScheme() + utilruntime.Must(AddToScheme(scheme)) + + fuzzerFuncs := fuzzer.MergeFuzzerFuncs( + pkgfuzzer.Funcs, + FuzzerFuncs, + ) + roundtrip.ExternalTypesViaJSON(t, scheme, fuzzerFuncs) +} + +func TestEventingRoundTripTypesToBetaHub(t *testing.T) { + scheme := runtime.NewScheme() + + sb := runtime.SchemeBuilder{ + AddToScheme, + v1.AddToScheme, + } + + utilruntime.Must(sb.AddToScheme(scheme)) + + hubs := runtime.NewScheme() + hubs.AddKnownTypes(SchemeGroupVersion, + &Broker{}, + &EventType{}, + &Trigger{}, + ) + + fuzzerFuncs := fuzzer.MergeFuzzerFuncs( + pkgfuzzer.Funcs, + FuzzerFuncs, + ) + + roundtrip.ExternalTypesViaHub(t, scheme, hubs, fuzzerFuncs) +} diff --git a/pkg/apis/eventing/v1beta1/test_helper.go b/pkg/apis/eventing/v1beta1/test_helper.go new file mode 100644 index 00000000000..7b7883f6808 --- /dev/null +++ b/pkg/apis/eventing/v1beta1/test_helper.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 v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + + duckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +type testHelper struct{} + +// TestHelper contains helpers for unit tests. +var TestHelper = testHelper{} + +func (testHelper) ReadySubscriptionCondition() *apis.Condition { + return &apis.Condition{ + Type: apis.ConditionReady, + Status: corev1.ConditionTrue, + Severity: apis.ConditionSeverityError, + } +} + +func (testHelper) FalseSubscriptionCondition() *apis.Condition { + return &apis.Condition{ + Type: apis.ConditionReady, + Status: corev1.ConditionFalse, + Severity: apis.ConditionSeverityError, + Message: "test induced failure condition", + } +} + +func (testHelper) ReadySubscriptionStatus() *messagingv1beta1.SubscriptionStatus { + ss := &messagingv1beta1.SubscriptionStatus{} + ss.MarkChannelReady() + ss.MarkReferencesResolved() + ss.MarkAddedToChannel() + return ss +} + +func (t testHelper) ReadyBrokerStatus() *BrokerStatus { + bs := &BrokerStatus{} + bs.PropagateIngressAvailability(t.AvailableEndpoints()) + bs.PropagateTriggerChannelReadiness(t.ReadyChannelStatus()) + bs.PropagateFilterAvailability(t.AvailableEndpoints()) + bs.SetAddress(apis.HTTP("example.com")) + return bs +} + +func (testHelper) ReadyBrokerCondition() *apis.Condition { + return &apis.Condition{ + Type: apis.ConditionReady, + Status: corev1.ConditionTrue, + Severity: apis.ConditionSeverityError, + } +} + +func (testHelper) UnknownBrokerStatus() *BrokerStatus { + bs := &BrokerStatus{} + return bs +} + +func (testHelper) FalseBrokerStatus() *BrokerStatus { + bs := &BrokerStatus{} + bs.SetAddress(nil) + return bs +} + +func (testHelper) UnavailableEndpoints() *corev1.Endpoints { + ep := &corev1.Endpoints{} + ep.Name = "unavailable" + ep.Subsets = []corev1.EndpointSubset{{ + NotReadyAddresses: []corev1.EndpointAddress{{ + IP: "127.0.0.1", + }}, + }} + return ep +} + +func (testHelper) AvailableEndpoints() *corev1.Endpoints { + ep := &corev1.Endpoints{} + ep.Name = "available" + ep.Subsets = []corev1.EndpointSubset{{ + Addresses: []corev1.EndpointAddress{{ + IP: "127.0.0.1", + }}, + }} + return ep +} + +func (testHelper) ReadyChannelStatus() *duckv1beta1.ChannelableStatus { + cs := &duckv1beta1.ChannelableStatus{ + Status: duckv1.Status{}, + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: &apis.URL{Scheme: "http", Host: "foo"}, + }, + }, + SubscribableStatus: duckv1beta1.SubscribableStatus{}} + return cs +} + +func (t testHelper) NotReadyChannelStatus() *duckv1beta1.ChannelableStatus { + return &duckv1beta1.ChannelableStatus{} +} diff --git a/pkg/apis/eventing/v1beta1/trigger_conversion.go b/pkg/apis/eventing/v1beta1/trigger_conversion.go new file mode 100644 index 00000000000..52c02d39de4 --- /dev/null +++ b/pkg/apis/eventing/v1beta1/trigger_conversion.go @@ -0,0 +1,81 @@ +/* +Copyright 2020 The Knative Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "fmt" + + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + v1 "knative.dev/eventing/pkg/apis/eventing/v1" + "knative.dev/pkg/apis" +) + +// ConvertTo implements apis.Convertible +func (source *Trigger) ConvertTo(ctx context.Context, to apis.Convertible) error { + switch sink := to.(type) { + case *v1.Trigger: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.Broker = source.Spec.Broker + sink.Spec.Subscriber = source.Spec.Subscriber + if source.Spec.Filter != nil { + sink.Spec.Filter = &v1.TriggerFilter{ + Attributes: make(v1.TriggerFilterAttributes), + } + for k, v := range source.Spec.Filter.Attributes { + sink.Spec.Filter.Attributes[k] = v + } + } + if source.Spec.Delivery != nil { + sink.Spec.Delivery = &eventingduckv1.DeliverySpec{} + source.Spec.Delivery.DeepCopyInto(sink.Spec.Delivery) + } + sink.Status.Status = source.Status.Status + sink.Status.SubscriberURI = source.Status.SubscriberURI + return nil + default: + return fmt.Errorf("unknown version, got: %T", sink) + } +} + +// ConvertFrom implements apis.Convertible +func (sink *Trigger) ConvertFrom(ctx context.Context, from apis.Convertible) error { + switch source := from.(type) { + case *v1.Trigger: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.Broker = source.Spec.Broker + sink.Spec.Subscriber = source.Spec.Subscriber + if source.Spec.Filter != nil { + attributes := TriggerFilterAttributes{} + for k, v := range source.Spec.Filter.Attributes { + attributes[k] = v + } + sink.Spec.Filter = &TriggerFilter{ + Attributes: attributes, + } + } + if source.Spec.Delivery != nil { + sink.Spec.Delivery = &eventingduckv1.DeliverySpec{} + source.Spec.Delivery.DeepCopyInto(sink.Spec.Delivery) + } + sink.Status.Status = source.Status.Status + sink.Status.SubscriberURI = source.Status.SubscriberURI + return nil + default: + return fmt.Errorf("unknown version, got: %T", source) + } +} diff --git a/pkg/apis/eventing/v1beta1/trigger_conversion_test.go b/pkg/apis/eventing/v1beta1/trigger_conversion_test.go new file mode 100644 index 00000000000..614e06188d7 --- /dev/null +++ b/pkg/apis/eventing/v1beta1/trigger_conversion_test.go @@ -0,0 +1,311 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + v1 "knative.dev/eventing/pkg/apis/eventing/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestTriggerConversionBadType(t *testing.T) { + good, bad := &Trigger{}, &Trigger{} + + 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) + } +} + +func TestTriggerConversionBadVersion(t *testing.T) { + good, bad := &Trigger{}, &Trigger{} + + 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) + } +} + +// Test v1beta1 -> v1 -> v1beta1 +func TestTriggerConversionRoundTripV1beta1(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&v1.Trigger{}} + + tests := []struct { + name string + in *Trigger + }{{name: "simple configuration", + in: &Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "trigger-name", + Namespace: "trigger-ns", + Generation: 17, + }, + Spec: TriggerSpec{ + Broker: "default", + }, + Status: TriggerStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, {name: "filter rules", + in: &Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "trigger-name", + Namespace: "trigger-ns", + Generation: 17, + }, + Spec: TriggerSpec{ + Broker: "default", + Filter: &TriggerFilter{ + Attributes: TriggerFilterAttributes{"source": "mysource", "type": "mytype"}, + }, + }, + Status: TriggerStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, {name: "filter rules, many", + in: &Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "trigger-name", + Namespace: "trigger-ns", + Generation: 17, + }, + Spec: TriggerSpec{ + Broker: "default", + Filter: &TriggerFilter{ + Attributes: TriggerFilterAttributes{"source": "mysource", "type": "mytype", "customkey": "customvalue"}, + }, + }, + Status: TriggerStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, {name: "full configuration", + in: &Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "trigger-name", + Namespace: "trigger-ns", + Generation: 17, + }, + Spec: TriggerSpec{ + Broker: "default", + Filter: &TriggerFilter{ + Attributes: TriggerFilterAttributes{"source": "mysource", "type": "mytype"}, + }, + Subscriber: duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "subscriberKind", + Namespace: "subscriberNamespace", + Name: "subscriberName", + APIVersion: "subscriberAPIVersion", + }, + URI: apis.HTTP("subscriberURI"), + }, + }, + Status: TriggerStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SubscriberURI: apis.HTTP("subscriberURI"), + }, + }, + }} + + 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 := &Trigger{} + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Error("ConvertFrom() =", err) + } + + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} + +// Test v1 -> v1beta1 -> v1 +func TestTriggerConversionRoundTripV1(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&Trigger{}} + + tests := []struct { + name string + in *v1.Trigger + }{{name: "simple configuration", + in: &v1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "trigger-name", + Namespace: "trigger-ns", + Generation: 17, + }, + Spec: v1.TriggerSpec{ + Broker: "default", + }, + Status: v1.TriggerStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, {name: "filter rules", + in: &v1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "trigger-name", + Namespace: "trigger-ns", + Generation: 17, + }, + Spec: v1.TriggerSpec{ + Broker: "default", + Filter: &v1.TriggerFilter{ + Attributes: v1.TriggerFilterAttributes{"source": "mysource", "type": "mytype"}, + }, + }, + Status: v1.TriggerStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, {name: "filter rules, many", + in: &v1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "trigger-name", + Namespace: "trigger-ns", + Generation: 17, + }, + Spec: v1.TriggerSpec{ + Broker: "default", + Filter: &v1.TriggerFilter{ + Attributes: v1.TriggerFilterAttributes{"source": "mysource", "type": "mytype", "customkey": "customvalue"}, + }, + }, + Status: v1.TriggerStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, {name: "full configuration", + in: &v1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: "trigger-name", + Namespace: "trigger-ns", + Generation: 17, + }, + Spec: v1.TriggerSpec{ + Broker: "default", + Filter: &v1.TriggerFilter{ + Attributes: v1.TriggerFilterAttributes{"source": "mysource", "type": "mytype"}, + }, + Subscriber: duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "subscriberKind", + Namespace: "subscriberNamespace", + Name: "subscriberName", + APIVersion: "subscriberAPIVersion", + }, + URI: apis.HTTP("subscriberURI"), + }, + }, + Status: v1.TriggerStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SubscriberURI: apis.HTTP("subscriberURI"), + }, + }, + }} + + for _, test := range tests { + for _, version := range versions { + t.Run(test.name, func(t *testing.T) { + ver := version + if err := ver.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertDown() =", err) + } + got := &v1.Trigger{} + if err := ver.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertUp() =", err) + } + + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} diff --git a/pkg/apis/eventing/v1beta1/trigger_defaults.go b/pkg/apis/eventing/v1beta1/trigger_defaults.go new file mode 100644 index 00000000000..42eba28c58b --- /dev/null +++ b/pkg/apis/eventing/v1beta1/trigger_defaults.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. +*/ + +package v1beta1 + +import ( + "context" + + "knative.dev/pkg/apis" +) + +const ( + brokerLabel = "eventing.knative.dev/broker" +) + +func (t *Trigger) SetDefaults(ctx context.Context) { + withNS := apis.WithinParent(ctx, t.ObjectMeta) + t.Spec.SetDefaults(withNS) + setLabels(t) +} + +func (ts *TriggerSpec) SetDefaults(ctx context.Context) { + if ts.Broker == "" { + ts.Broker = "default" + } + // Make a default filter that allows anything. + if ts.Filter == nil { + ts.Filter = &TriggerFilter{} + } + // Default the Subscriber namespace + ts.Subscriber.SetDefaults(ctx) +} + +func setLabels(t *Trigger) { + if len(t.Labels) == 0 { + t.Labels = map[string]string{} + } + t.Labels[brokerLabel] = t.Spec.Broker +} diff --git a/pkg/apis/eventing/v1beta1/trigger_defaults_test.go b/pkg/apis/eventing/v1beta1/trigger_defaults_test.go new file mode 100644 index 00000000000..eb57060b2ca --- /dev/null +++ b/pkg/apis/eventing/v1beta1/trigger_defaults_test.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 v1beta1 + +import ( + "context" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + duckv1 "knative.dev/pkg/apis/duck/v1" + + "github.com/google/go-cmp/cmp" +) + +var ( + defaultBroker = "default" + otherBroker = "other_broker" + namespace = "testnamespace" + emptyTriggerFilter = &TriggerFilter{} + defaultTrigger = Trigger{ + ObjectMeta: v1.ObjectMeta{ + Labels: map[string]string{brokerLabel: defaultBroker}, + }, + Spec: TriggerSpec{ + Broker: defaultBroker, + Filter: emptyTriggerFilter, + }, + } +) + +func TestTriggerDefaults(t *testing.T) { + testCases := map[string]struct { + initial Trigger + expected Trigger + }{ + "nil broker": { + initial: Trigger{Spec: TriggerSpec{Filter: emptyTriggerFilter}}, + expected: Trigger{ + ObjectMeta: v1.ObjectMeta{ + Labels: map[string]string{brokerLabel: defaultBroker}, + }, + Spec: TriggerSpec{Broker: defaultBroker, Filter: emptyTriggerFilter}}, + }, + "nil filter": { + initial: Trigger{Spec: TriggerSpec{Broker: otherBroker}}, + expected: Trigger{ + ObjectMeta: v1.ObjectMeta{ + Labels: map[string]string{brokerLabel: otherBroker}, + }, + Spec: TriggerSpec{Broker: otherBroker, Filter: emptyTriggerFilter}}, + }, + "subscriber, ns defaulted": { + initial: Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + }, + Spec: TriggerSpec{ + Broker: otherBroker, + Subscriber: duckv1.Destination{ + Ref: &duckv1.KReference{ + Name: "foo", + }, + }}}, + expected: Trigger{ + ObjectMeta: v1.ObjectMeta{ + Namespace: namespace, + Labels: map[string]string{brokerLabel: otherBroker}, + }, + Spec: TriggerSpec{ + Broker: otherBroker, + Filter: emptyTriggerFilter, + Subscriber: duckv1.Destination{ + Ref: &duckv1.KReference{ + Name: "foo", + Namespace: namespace, + }, + }, + }}, + }, + "nil broker and nil filter": { + initial: Trigger{}, + expected: defaultTrigger, + }, + "with broker and label": { + initial: Trigger{ + ObjectMeta: v1.ObjectMeta{ + Labels: map[string]string{"otherLabel": "my-other-label"}, + }, + Spec: TriggerSpec{Broker: defaultBroker}}, + expected: Trigger{ + ObjectMeta: v1.ObjectMeta{ + Labels: map[string]string{ + "otherLabel": "my-other-label", + brokerLabel: defaultBroker}, + }, + Spec: TriggerSpec{Broker: defaultBroker, Filter: emptyTriggerFilter}}, + }, + } + 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/eventing/v1beta1/trigger_lifecycle.go b/pkg/apis/eventing/v1beta1/trigger_lifecycle.go new file mode 100644 index 00000000000..f47030c0644 --- /dev/null +++ b/pkg/apis/eventing/v1beta1/trigger_lifecycle.go @@ -0,0 +1,187 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +var triggerCondSet = apis.NewLivingConditionSet(TriggerConditionBroker, TriggerConditionSubscribed, TriggerConditionDependency, TriggerConditionSubscriberResolved) + +const ( + // TriggerConditionReady has status True when all subconditions below have been set to True. + TriggerConditionReady = apis.ConditionReady + + TriggerConditionBroker apis.ConditionType = "BrokerReady" + + TriggerConditionSubscribed apis.ConditionType = "SubscriptionReady" + + TriggerConditionDependency apis.ConditionType = "DependencyReady" + + TriggerConditionSubscriberResolved apis.ConditionType = "SubscriberResolved" + + // TriggerAnyFilter Constant to represent that we should allow anything. + TriggerAnyFilter = "" +) + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (*Trigger) GetConditionSet() apis.ConditionSet { + return triggerCondSet +} + +// GetGroupVersionKind returns GroupVersionKind for Triggers +func (t *Trigger) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("Trigger") +} + +// GetUntypedSpec returns the spec of the Trigger. +func (t *Trigger) GetUntypedSpec() interface{} { + return t.Spec +} + +// GetCondition returns the condition currently associated with the given type, or nil. +func (ts *TriggerStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return triggerCondSet.Manage(ts).GetCondition(t) +} + +// GetTopLevelCondition returns the top level Condition. +func (ts *TriggerStatus) GetTopLevelCondition() *apis.Condition { + return triggerCondSet.Manage(ts).GetTopLevelCondition() +} + +// IsReady returns true if the resource is ready overall. +func (ts *TriggerStatus) IsReady() bool { + return triggerCondSet.Manage(ts).IsHappy() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (ts *TriggerStatus) InitializeConditions() { + triggerCondSet.Manage(ts).InitializeConditions() +} + +func (ts *TriggerStatus) PropagateBrokerCondition(bc *apis.Condition) { + if bc == nil { + ts.MarkBrokerNotConfigured() + return + } + + switch { + case bc.Status == corev1.ConditionUnknown: + ts.MarkBrokerUnknown(bc.Reason, bc.Message) + case bc.Status == corev1.ConditionTrue: + triggerCondSet.Manage(ts).MarkTrue(TriggerConditionBroker) + case bc.Status == corev1.ConditionFalse: + ts.MarkBrokerFailed(bc.Reason, bc.Message) + default: + ts.MarkBrokerUnknown("BrokerUnknown", "The status of Broker is invalid: %v", bc.Status) + } +} + +func (ts *TriggerStatus) MarkBrokerFailed(reason, messageFormat string, messageA ...interface{}) { + triggerCondSet.Manage(ts).MarkFalse(TriggerConditionBroker, reason, messageFormat, messageA...) +} + +func (ts *TriggerStatus) MarkBrokerUnknown(reason, messageFormat string, messageA ...interface{}) { + triggerCondSet.Manage(ts).MarkUnknown(TriggerConditionBroker, reason, messageFormat, messageA...) +} + +func (ts *TriggerStatus) MarkBrokerNotConfigured() { + triggerCondSet.Manage(ts).MarkUnknown(TriggerConditionBroker, + "BrokerNotConfigured", "Broker has not yet been reconciled.") +} + +func (ts *TriggerStatus) PropagateSubscriptionCondition(sc *apis.Condition) { + if sc == nil { + ts.MarkSubscriptionNotConfigured() + return + } + + switch { + case sc.Status == corev1.ConditionUnknown: + ts.MarkSubscribedUnknown(sc.Reason, sc.Message) + case sc.Status == corev1.ConditionTrue: + triggerCondSet.Manage(ts).MarkTrue(TriggerConditionSubscribed) + case sc.Status == corev1.ConditionFalse: + ts.MarkNotSubscribed(sc.Reason, sc.Message) + default: + ts.MarkSubscribedUnknown("SubscriptionUnknown", "The status of Subscription is invalid: %v", sc.Status) + } +} + +func (ts *TriggerStatus) MarkNotSubscribed(reason, messageFormat string, messageA ...interface{}) { + triggerCondSet.Manage(ts).MarkFalse(TriggerConditionSubscribed, reason, messageFormat, messageA...) +} + +func (ts *TriggerStatus) MarkSubscribedUnknown(reason, messageFormat string, messageA ...interface{}) { + triggerCondSet.Manage(ts).MarkUnknown(TriggerConditionSubscribed, reason, messageFormat, messageA...) +} + +func (ts *TriggerStatus) MarkSubscriptionNotConfigured() { + triggerCondSet.Manage(ts).MarkUnknown(TriggerConditionSubscribed, + "SubscriptionNotConfigured", "Subscription has not yet been reconciled.") +} + +func (ts *TriggerStatus) MarkSubscriberResolvedSucceeded() { + triggerCondSet.Manage(ts).MarkTrue(TriggerConditionSubscriberResolved) +} + +func (ts *TriggerStatus) MarkSubscriberResolvedFailed(reason, messageFormat string, messageA ...interface{}) { + triggerCondSet.Manage(ts).MarkFalse(TriggerConditionSubscriberResolved, reason, messageFormat, messageA...) +} + +func (ts *TriggerStatus) MarkSubscriberResolvedUnknown(reason, messageFormat string, messageA ...interface{}) { + triggerCondSet.Manage(ts).MarkUnknown(TriggerConditionSubscriberResolved, reason, messageFormat, messageA...) +} + +func (ts *TriggerStatus) MarkDependencySucceeded() { + triggerCondSet.Manage(ts).MarkTrue(TriggerConditionDependency) +} + +func (ts *TriggerStatus) MarkDependencyFailed(reason, messageFormat string, messageA ...interface{}) { + triggerCondSet.Manage(ts).MarkFalse(TriggerConditionDependency, reason, messageFormat, messageA...) +} + +func (ts *TriggerStatus) MarkDependencyUnknown(reason, messageFormat string, messageA ...interface{}) { + triggerCondSet.Manage(ts).MarkUnknown(TriggerConditionDependency, reason, messageFormat, messageA...) +} + +func (ts *TriggerStatus) MarkDependencyNotConfigured() { + triggerCondSet.Manage(ts).MarkUnknown(TriggerConditionDependency, + "DependencyNotConfigured", "Dependency has not yet been reconciled.") +} + +func (ts *TriggerStatus) PropagateDependencyStatus(ks *duckv1.KResource) { + kc := ks.Status.GetCondition(apis.ConditionReady) + if kc == nil { + ts.MarkDependencyNotConfigured() + return + } + + switch { + case kc.Status == corev1.ConditionUnknown: + ts.MarkDependencyUnknown(kc.Reason, kc.Message) + case kc.Status == corev1.ConditionTrue: + ts.MarkDependencySucceeded() + case kc.Status == corev1.ConditionFalse: + ts.MarkDependencyFailed(kc.Reason, kc.Message) + default: + ts.MarkDependencyUnknown("DependencyUnknown", "The status of Dependency is invalid: %v", kc.Status) + } +} diff --git a/pkg/apis/eventing/v1beta1/trigger_lifecycle_test.go b/pkg/apis/eventing/v1beta1/trigger_lifecycle_test.go new file mode 100644 index 00000000000..d9ee527ea33 --- /dev/null +++ b/pkg/apis/eventing/v1beta1/trigger_lifecycle_test.go @@ -0,0 +1,360 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "testing" + + "knative.dev/pkg/apis" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + corev1 "k8s.io/api/core/v1" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +var ( + ignoreAllButTypeAndStatus = cmpopts.IgnoreFields( + apis.Condition{}, + "LastTransitionTime", "Message", "Reason", "Severity") + + triggerConditionReady = apis.Condition{ + Type: TriggerConditionReady, + Status: corev1.ConditionTrue, + } + + triggerConditionBroker = apis.Condition{ + Type: TriggerConditionBroker, + Status: corev1.ConditionTrue, + } + + triggerConditionDependency = apis.Condition{ + Type: TriggerConditionDependency, + Status: corev1.ConditionTrue, + } + + triggerConditionSubscriberResolved = apis.Condition{ + Type: TriggerConditionSubscriberResolved, + Status: corev1.ConditionTrue, + } + + triggerConditionSubscribed = apis.Condition{ + Type: TriggerConditionSubscribed, + Status: corev1.ConditionFalse, + } +) + +func TestTriggerGetConditionSet(t *testing.T) { + r := &Trigger{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestTriggerGetCondition(t *testing.T) { + tests := []struct { + name string + ts *TriggerStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "single condition", + ts: &TriggerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + triggerConditionReady, + }, + }, + }, + condQuery: apis.ConditionReady, + want: &triggerConditionReady, + }, { + name: "multiple conditions", + ts: &TriggerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + triggerConditionBroker, + triggerConditionSubscribed, + triggerConditionDependency, + triggerConditionSubscriberResolved, + }, + }, + }, + condQuery: TriggerConditionSubscribed, + want: &triggerConditionSubscribed, + }, { + name: "multiple conditions, condition false", + ts: &TriggerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + triggerConditionBroker, + triggerConditionSubscribed, + triggerConditionDependency, + triggerConditionSubscriberResolved, + }, + }, + }, + condQuery: TriggerConditionSubscribed, + want: &triggerConditionSubscribed, + }, { + name: "unknown condition", + ts: &TriggerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + triggerConditionSubscribed, + }, + }, + }, + condQuery: apis.ConditionType("foo"), + want: nil, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.ts.GetCondition(test.condQuery) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Error("unexpected condition (-want, +got) =", diff) + } + }) + } +} + +func TestTriggerInitializeConditions(t *testing.T) { + tests := []struct { + name string + ts *TriggerStatus + want *TriggerStatus + }{{ + name: "empty", + ts: &TriggerStatus{}, + want: &TriggerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: TriggerConditionBroker, + Status: corev1.ConditionUnknown, + }, { + Type: TriggerConditionDependency, + Status: corev1.ConditionUnknown, + }, { + Type: TriggerConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: TriggerConditionSubscriberResolved, + Status: corev1.ConditionUnknown, + }, { + Type: TriggerConditionSubscribed, + Status: corev1.ConditionUnknown, + }, + }, + }, + }, + }, { + name: "one false", + ts: &TriggerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: TriggerConditionBroker, + Status: corev1.ConditionFalse, + }}, + }, + }, + want: &TriggerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: TriggerConditionBroker, + Status: corev1.ConditionFalse, + }, { + Type: TriggerConditionDependency, + Status: corev1.ConditionUnknown, + }, + { + Type: TriggerConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: TriggerConditionSubscriberResolved, + Status: corev1.ConditionUnknown, + }, { + Type: TriggerConditionSubscribed, + Status: corev1.ConditionUnknown, + }, + }, + }, + }, + }, { + name: "one true", + ts: &TriggerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: TriggerConditionSubscribed, + Status: corev1.ConditionTrue, + }}, + }, + }, + want: &TriggerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: TriggerConditionBroker, + Status: corev1.ConditionUnknown, + }, { + Type: TriggerConditionDependency, + Status: corev1.ConditionUnknown, + }, { + Type: TriggerConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: TriggerConditionSubscriberResolved, + Status: corev1.ConditionUnknown, + }, { + Type: TriggerConditionSubscribed, + Status: corev1.ConditionTrue, + }, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.ts.InitializeConditions() + if diff := cmp.Diff(test.want, test.ts, ignoreAllButTypeAndStatus); diff != "" { + t.Error("unexpected conditions (-want, +got) =", diff) + } + }) + } +} + +func TestTriggerConditionStatus(t *testing.T) { + tests := []struct { + name string + brokerStatus *BrokerStatus + markKubernetesServiceExists bool + markVirtualServiceExists bool + subscriptionCondition *apis.Condition + subscriberResolvedStatus bool + dependencyAnnotationExists bool + dependencyStatus corev1.ConditionStatus + wantConditionStatus corev1.ConditionStatus + }{{ + name: "all happy", + brokerStatus: TestHelper.ReadyBrokerStatus(), + markKubernetesServiceExists: true, + markVirtualServiceExists: true, + subscriptionCondition: TestHelper.ReadySubscriptionCondition(), + subscriberResolvedStatus: true, + dependencyAnnotationExists: false, + wantConditionStatus: corev1.ConditionTrue, + }, { + name: "broker status unknown", + brokerStatus: TestHelper.UnknownBrokerStatus(), + markKubernetesServiceExists: true, + markVirtualServiceExists: true, + subscriptionCondition: TestHelper.ReadySubscriptionCondition(), + subscriberResolvedStatus: true, + dependencyAnnotationExists: false, + wantConditionStatus: corev1.ConditionUnknown, + }, { + name: "broker status false", + brokerStatus: TestHelper.FalseBrokerStatus(), + markKubernetesServiceExists: true, + markVirtualServiceExists: true, + subscriptionCondition: TestHelper.ReadySubscriptionCondition(), + subscriberResolvedStatus: true, + dependencyAnnotationExists: false, + wantConditionStatus: corev1.ConditionFalse, + }, { + name: "subscribed sad", + brokerStatus: TestHelper.ReadyBrokerStatus(), + markKubernetesServiceExists: true, + markVirtualServiceExists: true, + subscriptionCondition: TestHelper.FalseSubscriptionCondition(), + subscriberResolvedStatus: true, + dependencyAnnotationExists: false, + wantConditionStatus: corev1.ConditionFalse, + }, { + name: "failed to resolve subscriber", + brokerStatus: TestHelper.ReadyBrokerStatus(), + markKubernetesServiceExists: true, + markVirtualServiceExists: true, + subscriptionCondition: TestHelper.ReadySubscriptionCondition(), + subscriberResolvedStatus: false, + dependencyAnnotationExists: true, + dependencyStatus: corev1.ConditionTrue, + wantConditionStatus: corev1.ConditionFalse, + }, { + name: "dependency unknown", + brokerStatus: TestHelper.ReadyBrokerStatus(), + markKubernetesServiceExists: true, + markVirtualServiceExists: true, + subscriptionCondition: TestHelper.ReadySubscriptionCondition(), + subscriberResolvedStatus: true, + dependencyAnnotationExists: true, + dependencyStatus: corev1.ConditionUnknown, + wantConditionStatus: corev1.ConditionUnknown, + }, { + name: "dependency false", + brokerStatus: TestHelper.ReadyBrokerStatus(), + markKubernetesServiceExists: true, + markVirtualServiceExists: true, + subscriptionCondition: TestHelper.ReadySubscriptionCondition(), + subscriberResolvedStatus: true, + dependencyAnnotationExists: true, + dependencyStatus: corev1.ConditionFalse, + wantConditionStatus: corev1.ConditionFalse, + }, { + name: "all sad", + brokerStatus: TestHelper.FalseBrokerStatus(), + markKubernetesServiceExists: false, + markVirtualServiceExists: false, + subscriptionCondition: TestHelper.FalseSubscriptionCondition(), + subscriberResolvedStatus: false, + dependencyAnnotationExists: true, + dependencyStatus: corev1.ConditionFalse, + wantConditionStatus: corev1.ConditionFalse, + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ts := &TriggerStatus{} + if test.brokerStatus != nil { + ts.PropagateBrokerCondition(test.brokerStatus.GetTopLevelCondition()) + } + if test.subscriptionCondition != nil { + ts.PropagateSubscriptionCondition(test.subscriptionCondition) + } + if test.subscriberResolvedStatus { + ts.MarkSubscriberResolvedSucceeded() + } else { + ts.MarkSubscriberResolvedFailed("Unable to get the Subscriber's URI", "subscriber not found") + } + if !test.dependencyAnnotationExists { + ts.MarkDependencySucceeded() + } else { + if test.dependencyStatus == corev1.ConditionTrue { + ts.MarkDependencySucceeded() + } else if test.dependencyStatus == corev1.ConditionUnknown { + ts.MarkDependencyUnknown("The status of dependency is unknown", "The status of dependency is unknown: nil") + } else { + ts.MarkDependencyFailed("The status of dependency is false", "The status of dependency is unknown: nil") + } + } + got := ts.GetTopLevelCondition().Status + if test.wantConditionStatus != got { + t.Errorf("unexpected readiness: want %v, got %v", test.wantConditionStatus, got) + } + }) + } +} diff --git a/pkg/apis/eventing/v1beta1/trigger_types.go b/pkg/apis/eventing/v1beta1/trigger_types.go new file mode 100644 index 00000000000..3a5c9ccfc76 --- /dev/null +++ b/pkg/apis/eventing/v1beta1/trigger_types.go @@ -0,0 +1,140 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmeta" + + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" +) + +const ( + // DependencyAnnotation is the annotation key used to mark the sources that the Trigger depends on. + // This will be used when the kn client creates a source and trigger pair for the user such that the trigger only receives events produced by the paired source. + DependencyAnnotation = "knative.dev/dependency" + + // These are copied from ./pkg/reconcilers/sugar + + // InjectionAnnotation is the annotation key used to enable knative eventing + // injection for a namespace to automatically create a broker. + InjectionAnnotation = "eventing.knative.dev/injection" +) + +// +genclient +// +genreconciler +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Trigger represents a request to have events delivered to a consumer from a +// Broker's event pool. +type Trigger struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of the Trigger. + Spec TriggerSpec `json:"spec,omitempty"` + + // Status represents the current state of the Trigger. This data may be out of + // date. + // +optional + Status TriggerStatus `json:"status,omitempty"` +} + +var ( + // Check that Trigger can be validated, can be defaulted, and has immutable fields. + _ apis.Validatable = (*Trigger)(nil) + _ apis.Defaultable = (*Trigger)(nil) + + // Check that Trigger can return its spec untyped. + _ apis.HasSpec = (*Trigger)(nil) + + _ runtime.Object = (*Trigger)(nil) + + // Check that we can create OwnerReferences to a Trigger. + _ kmeta.OwnerRefable = (*Trigger)(nil) + + // Check that the type conforms to the duck Knative Resource shape. + _ duckv1.KRShaped = (*Trigger)(nil) +) + +type TriggerSpec struct { + // Broker is the broker that this trigger receives events from. If not specified, will default + // to 'default'. + Broker string `json:"broker,omitempty"` + + // Filter is the filter to apply against all events from the Broker. Only events that pass this + // filter will be sent to the Subscriber. If not specified, will default to allowing all events. + // + // +optional + Filter *TriggerFilter `json:"filter,omitempty"` + + // Subscriber is the addressable that receives events from the Broker that pass the Filter. It + // is required. + Subscriber duckv1.Destination `json:"subscriber"` + + // Delivery contains the delivery spec for this specific trigger. + // +optional + Delivery *eventingduckv1.DeliverySpec `json:"delivery,omitempty"` +} + +type TriggerFilter struct { + // Attributes filters events by exact match on event context attributes. + // Each key in the map is compared with the equivalent key in the event + // context. An event passes the filter if all values are equal to the + // specified values. + // + // Nested context attributes are not supported as keys. Only string values are supported. + // + // +optional + Attributes TriggerFilterAttributes `json:"attributes,omitempty"` +} + +// TriggerFilterAttributes is a map of context attribute names to values for +// filtering by equality. Only exact matches will pass the filter. You can use the value '' +// to indicate all strings match. +type TriggerFilterAttributes map[string]string + +// TriggerStatus represents the current state of a Trigger. +type TriggerStatus struct { + // inherits duck/v1 Status, 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. + duckv1.Status `json:",inline"` + + // SubscriberURI is the resolved URI of the receiver for this Trigger. + // +optional + SubscriberURI *apis.URL `json:"subscriberUri,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// TriggerList is a collection of Triggers. +type TriggerList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + Items []Trigger `json:"items"` +} + +// GetStatus retrieves the status of the Trigger. Implements the KRShaped interface. +func (t *Trigger) GetStatus() *duckv1.Status { + return &t.Status.Status +} diff --git a/pkg/apis/eventing/v1beta1/trigger_types_test.go b/pkg/apis/eventing/v1beta1/trigger_types_test.go new file mode 100644 index 00000000000..a7f2676fc0b --- /dev/null +++ b/pkg/apis/eventing/v1beta1/trigger_types_test.go @@ -0,0 +1,37 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import "testing" + +func TestTriggerGetStatus(t *testing.T) { + r := &Trigger{ + Status: TriggerStatus{}, + } + if got, want := r.GetStatus(), &r.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} + +func TestTrigger_GetGroupVersionKind(t *testing.T) { + + tr := Trigger{} + gvk := tr.GetGroupVersionKind() + if gvk.Kind != "Trigger" { + t.Errorf("Should be Trigger.") + } +} diff --git a/pkg/apis/eventing/v1beta1/trigger_validation.go b/pkg/apis/eventing/v1beta1/trigger_validation.go new file mode 100644 index 00000000000..c2a9f4a381e --- /dev/null +++ b/pkg/apis/eventing/v1beta1/trigger_validation.go @@ -0,0 +1,159 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "encoding/json" + "fmt" + "regexp" + + "knative.dev/pkg/apis" + "knative.dev/pkg/kmp" + + corev1 "k8s.io/api/core/v1" +) + +var ( + // Only allow lowercase alphanumeric, starting with letters. + validAttributeName = regexp.MustCompile(`^[a-z][a-z0-9]*$`) +) + +// Validate the Trigger. +func (t *Trigger) Validate(ctx context.Context) *apis.FieldError { + errs := t.Spec.Validate(ctx).ViaField("spec") + errs = t.validateAnnotation(errs, DependencyAnnotation, t.validateDependencyAnnotation) + errs = t.validateAnnotation(errs, InjectionAnnotation, t.validateInjectionAnnotation) + if apis.IsInUpdate(ctx) { + original := apis.GetBaseline(ctx).(*Trigger) + errs = errs.Also(t.CheckImmutableFields(ctx, original)) + } + return errs +} + +// Validate the TriggerSpec. +func (ts *TriggerSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + if ts.Broker == "" { + fe := apis.ErrMissingField("broker") + errs = errs.Also(fe) + } + + if ts.Filter != nil { + for attr := range map[string]string(ts.Filter.Attributes) { + if !validAttributeName.MatchString(attr) { + fe := &apis.FieldError{ + Message: fmt.Sprintf("Invalid attribute name: %q", attr), + Paths: []string{"filter.attributes"}, + } + errs = errs.Also(fe) + } + } + } + + if fe := ts.Subscriber.Validate(ctx); fe != nil { + errs = errs.Also(fe.ViaField("subscriber")) + } + + if ts.Delivery != nil { + if de := ts.Delivery.Validate(ctx); de != nil { + errs = errs.Also(de.ViaField("delivery")) + } + } + + return errs +} + +// CheckImmutableFields checks that any immutable fields were not changed. +func (t *Trigger) CheckImmutableFields(ctx context.Context, original *Trigger) *apis.FieldError { + if original == nil { + return nil + } + + if diff, err := kmp.ShortDiff(original.Spec.Broker, t.Spec.Broker); err != nil { + return &apis.FieldError{ + Message: "Failed to diff Trigger", + Paths: []string{"spec"}, + Details: err.Error(), + } + } else if diff != "" { + return &apis.FieldError{ + Message: "Immutable fields changed (-old +new)", + Paths: []string{"spec", "broker"}, + Details: diff, + } + } + return nil +} + +func GetObjRefFromDependencyAnnotation(dependencyAnnotation string) (corev1.ObjectReference, error) { + var objectRef corev1.ObjectReference + if err := json.Unmarshal([]byte(dependencyAnnotation), &objectRef); err != nil { + return objectRef, err + } + return objectRef, nil +} + +func (t *Trigger) validateAnnotation(errs *apis.FieldError, annotation string, function func(string) *apis.FieldError) *apis.FieldError { + if annotationValue, ok := t.GetAnnotations()[annotation]; ok { + annotationPrefix := fmt.Sprintf("metadata.annotations[%s]", annotation) + errs = errs.Also(function(annotationValue).ViaField(annotationPrefix)) + } + return errs +} + +func (t *Trigger) validateDependencyAnnotation(dependencyAnnotation string) *apis.FieldError { + depObjRef, err := GetObjRefFromDependencyAnnotation(dependencyAnnotation) + if err != nil { + return &apis.FieldError{ + Message: fmt.Sprintf("The provided annotation was not a corev1.ObjectReference: %q", dependencyAnnotation), + Details: err.Error(), + Paths: []string{""}, + } + } + var errs *apis.FieldError + if depObjRef.Namespace != "" && depObjRef.Namespace != t.GetNamespace() { + fe := &apis.FieldError{ + Message: fmt.Sprintf("Namespace must be empty or equal to the trigger namespace %q", t.GetNamespace()), + Paths: []string{"namespace"}, + } + errs = errs.Also(fe) + } + if depObjRef.Kind == "" { + fe := apis.ErrMissingField("kind") + errs = errs.Also(fe) + } + if depObjRef.Name == "" { + fe := apis.ErrMissingField("name") + errs = errs.Also(fe) + } + if depObjRef.APIVersion == "" { + fe := apis.ErrMissingField("apiVersion") + errs = errs.Also(fe) + } + return errs +} + +func (t *Trigger) validateInjectionAnnotation(injectionAnnotation string) *apis.FieldError { + if injectionAnnotation != "enabled" && injectionAnnotation != "disabled" { + return &apis.FieldError{ + Message: fmt.Sprintf(`The provided injection annotation value can only be "enabled" or "disabled", not %q`, injectionAnnotation), + Paths: []string{""}, + } + } + return nil +} diff --git a/pkg/apis/eventing/v1beta1/trigger_validation_test.go b/pkg/apis/eventing/v1beta1/trigger_validation_test.go new file mode 100644 index 00000000000..94a85123d2b --- /dev/null +++ b/pkg/apis/eventing/v1beta1/trigger_validation_test.go @@ -0,0 +1,506 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +var ( + validEmptyFilter = &TriggerFilter{} + validAttributesFilter = &TriggerFilter{ + Attributes: TriggerFilterAttributes{ + "type": "other_type", + "source": "other_source", + }, + } + validSubscriber = duckv1.Destination{ + Ref: &duckv1.KReference{ + Namespace: "namespace", + Name: "subscriber_test", + Kind: "Service", + APIVersion: "serving.knative.dev/v1alpha1", + }, + } + invalidSubscriber = duckv1.Destination{ + Ref: &duckv1.KReference{ + Namespace: "namespace", + Kind: "Service", + APIVersion: "serving.knative.dev/v1alpha1", + }, + } + // Dependency annotation + invalidDependencyAnnotation = "invalid dependency annotation" + dependencyAnnotationPath = fmt.Sprintf("metadata.annotations[%s]", DependencyAnnotation) + // Create default broker annotation + invalidInjectionAnnotation = "wut" + injectionAnnotationPath = fmt.Sprintf("metadata.annotations[%s]", InjectionAnnotation) +) + +func TestTriggerValidation(t *testing.T) { + invalidString := "invalid time" + tests := []struct { + name string + t *Trigger + want *apis.FieldError + }{{ + name: "invalid trigger spec", + t: &Trigger{Spec: TriggerSpec{}}, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrMissingField("spec.broker") + errs = errs.Also(fe) + fe = apis.ErrGeneric("expected at least one, got none", "spec.subscriber.ref", "spec.subscriber.uri") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "invalid dependency annotation, not a corev1.ObjectReference", + t: &Trigger{ + ObjectMeta: v1.ObjectMeta{ + Annotations: map[string]string{ + DependencyAnnotation: invalidDependencyAnnotation, + }}, + Spec: TriggerSpec{ + Broker: "test_broker", + Filter: validEmptyFilter, + Subscriber: validSubscriber, + }}, + want: &apis.FieldError{ + Paths: []string{dependencyAnnotationPath}, + Message: `The provided annotation was not a corev1.ObjectReference: "invalid dependency annotation"`, + Details: "invalid character 'i' looking for beginning of value", + }, + }, { + name: "invalid dependency annotation, trigger namespace is not equal to dependency namespace)", + t: &Trigger{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns-1", + Annotations: map[string]string{ + DependencyAnnotation: `{"kind":"PingSource","namespace":"test-ns-2", "name":"test-ping-source","apiVersion":"sources.knative.dev/v1alpha1"}`, + }}, + Spec: TriggerSpec{ + Broker: "test_broker", + Filter: validEmptyFilter, + Subscriber: validSubscriber, + }}, + want: &apis.FieldError{ + Paths: []string{dependencyAnnotationPath + "." + "namespace"}, + Message: `Namespace must be empty or equal to the trigger namespace "test-ns-1"`, + }, + }, + { + name: "invalid dependency annotation, missing kind)", + t: &Trigger{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + Annotations: map[string]string{ + DependencyAnnotation: `{"name":"test-ping-source","apiVersion":"sources.knative.dev/v1alpha1"}`, + }}, + Spec: TriggerSpec{ + Broker: "test_broker", + Filter: validEmptyFilter, + Subscriber: validSubscriber, + }}, + want: &apis.FieldError{ + Paths: []string{dependencyAnnotationPath + "." + "kind"}, + Message: "missing field(s)", + }, + }, { + name: "invalid dependency annotation, missing name", + t: &Trigger{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + Annotations: map[string]string{ + DependencyAnnotation: `{"kind":"PingSource","apiVersion":"sources.knative.dev/v1alpha1"}`, + }}, + Spec: TriggerSpec{ + Broker: "test_broker", + Filter: validEmptyFilter, + Subscriber: validSubscriber, + }}, + want: &apis.FieldError{ + Paths: []string{dependencyAnnotationPath + "." + "name"}, + Message: "missing field(s)", + }, + }, { + name: "invalid dependency annotation, missing apiVersion", + t: &Trigger{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + Annotations: map[string]string{ + DependencyAnnotation: `{"kind":"CronJobSource","name":"test-cronjob-source"}`, + }}, + Spec: TriggerSpec{ + Broker: "test_broker", + Filter: validEmptyFilter, + Subscriber: validSubscriber, + }}, + want: &apis.FieldError{ + Paths: []string{dependencyAnnotationPath + "." + "apiVersion"}, + Message: "missing field(s)", + }, + }, { + name: "invalid dependency annotation, missing kind, name, apiVersion", + t: &Trigger{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + Annotations: map[string]string{ + DependencyAnnotation: "{}", + }}, + Spec: TriggerSpec{ + Broker: "test_broker", + Filter: validEmptyFilter, + Subscriber: validSubscriber, + }}, + want: &apis.FieldError{ + Paths: []string{ + dependencyAnnotationPath + "." + "kind", + dependencyAnnotationPath + "." + "name", + dependencyAnnotationPath + "." + "apiVersion"}, + Message: "missing field(s)", + }, + }, { + name: "invalid trigger spec, invalid dependency annotation(missing kind, name, apiVersion)", + t: &Trigger{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + Annotations: map[string]string{ + DependencyAnnotation: "{}", + }}, + Spec: TriggerSpec{Subscriber: validSubscriber}}, + want: &apis.FieldError{ + Paths: []string{ + "spec.broker", + dependencyAnnotationPath + "." + "kind", + dependencyAnnotationPath + "." + "name", + dependencyAnnotationPath + "." + "apiVersion"}, + Message: "missing field(s)", + }, + }, { + name: "invalid injection annotation value", + t: &Trigger{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + Annotations: map[string]string{ + InjectionAnnotation: invalidInjectionAnnotation, + }}, + Spec: TriggerSpec{ + Broker: "default", + Filter: validEmptyFilter, + Subscriber: validSubscriber, + }}, + want: &apis.FieldError{ + Paths: []string{injectionAnnotationPath}, + Message: `The provided injection annotation value can only be "enabled" or "disabled", not "wut"`, + }, + }, { + name: "invalid delivery, invalid delay string", + t: &Trigger{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + }, + Spec: TriggerSpec{ + Broker: "test_broker", + Filter: validEmptyFilter, + Subscriber: validSubscriber, + Delivery: &eventingduckv1.DeliverySpec{ + BackoffDelay: &invalidString, + }, + }}, + want: apis.ErrInvalidValue(invalidString, "spec.delivery.backoffDelay"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.t.Validate(context.TODO()) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("Trigger.Validate (-want, +got) =", diff) + } + }) + } +} + +func TestTriggerUpdateValidation(t *testing.T) { + tests := []struct { + name string + t *Trigger + tNew *Trigger + want *apis.FieldError + }{{ + name: "invalid update, broker changed", + t: &Trigger{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + }, + Spec: TriggerSpec{ + Broker: "test_broker", + Filter: validEmptyFilter, + Subscriber: validSubscriber, + }}, + tNew: &Trigger{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "test-ns", + }, + Spec: TriggerSpec{ + Broker: "anotherBroker", + Filter: validEmptyFilter, + Subscriber: validSubscriber, + }}, + want: &apis.FieldError{ + Message: "Immutable fields changed (-old +new)", + Paths: []string{"spec", "broker"}, + Details: `{string}: + -: "test_broker" + +: "anotherBroker" +`, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := apis.WithinUpdate(context.Background(), test.t) + got := test.tNew.Validate(ctx) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("Trigger.Validate (-want, +got) =", diff) + } + }) + } +} + +func TestTriggerSpecValidation(t *testing.T) { + invalidString := "invalid time" + + tests := []struct { + name string + ts *TriggerSpec + want *apis.FieldError + }{{ + name: "invalid trigger spec", + ts: &TriggerSpec{}, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrMissingField("broker") + errs = errs.Also(fe) + fe = apis.ErrGeneric("expected at least one, got none", "subscriber.ref", "subscriber.uri") + errs = errs.Also(fe) + return errs + + }(), + }, { + name: "missing broker", + ts: &TriggerSpec{ + Broker: "", + Filter: validAttributesFilter, + Subscriber: validSubscriber, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("broker") + return fe + }(), + }, { + name: "missing attributes keys, match all", + ts: &TriggerSpec{ + Broker: "test_broker", + Filter: &TriggerFilter{ + Attributes: TriggerFilterAttributes{}, + }, + Subscriber: validSubscriber, + }, + want: &apis.FieldError{}, + }, { + name: "invalid attribute name, start with number", + ts: &TriggerSpec{ + Broker: "test_broker", + Filter: &TriggerFilter{ + Attributes: TriggerFilterAttributes{ + "0invalid": "my-value", + }, + }, + Subscriber: validSubscriber, + }, + want: &apis.FieldError{ + Message: `Invalid attribute name: "0invalid"`, + Paths: []string{"filter.attributes"}, + }, + }, { + name: "invalid attribute name, capital letters", + ts: &TriggerSpec{ + Broker: "test_broker", + Filter: &TriggerFilter{ + Attributes: TriggerFilterAttributes{ + "invALID": "my-value", + }, + }, + Subscriber: validSubscriber, + }, + want: &apis.FieldError{ + Message: `Invalid attribute name: "invALID"`, + Paths: []string{"filter.attributes"}, + }, + }, { + name: "missing subscriber", + ts: &TriggerSpec{ + Broker: "test_broker", + Filter: validAttributesFilter, + }, + want: apis.ErrGeneric("expected at least one, got none", "subscriber.ref", "subscriber.uri"), + }, { + name: "missing subscriber.ref.name", + ts: &TriggerSpec{ + Broker: "test_broker", + Filter: validAttributesFilter, + Subscriber: invalidSubscriber, + }, + want: apis.ErrMissingField("subscriber.ref.name"), + }, { + name: "missing broker", + ts: &TriggerSpec{ + Broker: "", + Filter: validAttributesFilter, + Subscriber: validSubscriber, + }, + want: apis.ErrMissingField("broker"), + }, { + name: "valid empty filter", + ts: &TriggerSpec{ + Broker: "test_broker", + Filter: validEmptyFilter, + Subscriber: validSubscriber, + }, + want: &apis.FieldError{}, + }, { + name: "valid SourceAndType filter", + ts: &TriggerSpec{ + Broker: "test_broker", + Filter: validAttributesFilter, + Subscriber: validSubscriber, + }, + want: &apis.FieldError{}, + }, { + name: "valid Attributes filter", + ts: &TriggerSpec{ + Broker: "test_broker", + Filter: validAttributesFilter, + Subscriber: validSubscriber, + }, + want: &apis.FieldError{}, + }, { + name: "invalid delivery, invalid delay string", + ts: &TriggerSpec{ + Broker: "test_broker", + Filter: validEmptyFilter, + Subscriber: validSubscriber, + Delivery: &eventingduckv1.DeliverySpec{ + BackoffDelay: &invalidString, + }, + }, + want: apis.ErrInvalidValue(invalidString, "delivery.backoffDelay"), + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.ts.Validate(context.TODO()) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Errorf("Validate TriggerSpec (-want, +got) =\n%s", diff) + } + }) + } +} + +func TestTriggerImmutableFields(t *testing.T) { + tests := []struct { + name string + current *Trigger + original *Trigger + want *apis.FieldError + }{{ + name: "good (no change)", + current: &Trigger{ + Spec: TriggerSpec{ + Broker: "broker", + }, + }, + original: &Trigger{ + Spec: TriggerSpec{ + Broker: "broker", + }, + }, + want: nil, + }, { + name: "new nil is ok", + current: &Trigger{ + Spec: TriggerSpec{ + Broker: "broker", + }, + }, + original: nil, + want: nil, + }, { + name: "good (filter change)", + current: &Trigger{ + Spec: TriggerSpec{ + Broker: "broker", + }, + }, + original: &Trigger{ + Spec: TriggerSpec{ + Broker: "broker", + Filter: validAttributesFilter, + }, + }, + want: nil, + }, { + name: "bad (broker change)", + current: &Trigger{ + Spec: TriggerSpec{ + Broker: "broker", + }, + }, + original: &Trigger{ + Spec: TriggerSpec{ + Broker: "original_broker", + }, + }, + want: &apis.FieldError{ + Message: "Immutable fields changed (-old +new)", + Paths: []string{"spec", "broker"}, + Details: `{string}: + -: "original_broker" + +: "broker" +`, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.current.CheckImmutableFields(context.TODO(), test.original) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("CheckImmutableFields (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/eventing/v1beta1/zz_generated.deepcopy.go b/pkg/apis/eventing/v1beta1/zz_generated.deepcopy.go index f50a3b144f0..893233786c9 100644 --- a/pkg/apis/eventing/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/eventing/v1beta1/zz_generated.deepcopy.go @@ -22,9 +22,117 @@ package v1beta1 import ( runtime "k8s.io/apimachinery/pkg/runtime" + duckv1 "knative.dev/eventing/pkg/apis/duck/v1" + duckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" apis "knative.dev/pkg/apis" + v1 "knative.dev/pkg/apis/duck/v1" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Broker) DeepCopyInto(out *Broker) { + *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 Broker. +func (in *Broker) DeepCopy() *Broker { + if in == nil { + return nil + } + out := new(Broker) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Broker) 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 *BrokerList) DeepCopyInto(out *BrokerList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Broker, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BrokerList. +func (in *BrokerList) DeepCopy() *BrokerList { + if in == nil { + return nil + } + out := new(BrokerList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BrokerList) 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 *BrokerSpec) DeepCopyInto(out *BrokerSpec) { + *out = *in + if in.Config != nil { + in, out := &in.Config, &out.Config + *out = new(v1.KReference) + **out = **in + } + if in.Delivery != nil { + in, out := &in.Delivery, &out.Delivery + *out = new(duckv1beta1.DeliverySpec) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BrokerSpec. +func (in *BrokerSpec) DeepCopy() *BrokerSpec { + if in == nil { + return nil + } + out := new(BrokerSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BrokerStatus) DeepCopyInto(out *BrokerStatus) { + *out = *in + in.Status.DeepCopyInto(&out.Status) + in.Address.DeepCopyInto(&out.Address) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BrokerStatus. +func (in *BrokerStatus) DeepCopy() *BrokerStatus { + if in == nil { + return nil + } + out := new(BrokerStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EventType) DeepCopyInto(out *EventType) { *out = *in @@ -128,3 +236,158 @@ func (in *EventTypeStatus) DeepCopy() *EventTypeStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Trigger) DeepCopyInto(out *Trigger) { + *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 Trigger. +func (in *Trigger) DeepCopy() *Trigger { + if in == nil { + return nil + } + out := new(Trigger) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Trigger) 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 *TriggerFilter) DeepCopyInto(out *TriggerFilter) { + *out = *in + if in.Attributes != nil { + in, out := &in.Attributes, &out.Attributes + *out = make(TriggerFilterAttributes, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TriggerFilter. +func (in *TriggerFilter) DeepCopy() *TriggerFilter { + if in == nil { + return nil + } + out := new(TriggerFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in TriggerFilterAttributes) DeepCopyInto(out *TriggerFilterAttributes) { + { + in := &in + *out = make(TriggerFilterAttributes, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TriggerFilterAttributes. +func (in TriggerFilterAttributes) DeepCopy() TriggerFilterAttributes { + if in == nil { + return nil + } + out := new(TriggerFilterAttributes) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TriggerList) DeepCopyInto(out *TriggerList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Trigger, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TriggerList. +func (in *TriggerList) DeepCopy() *TriggerList { + if in == nil { + return nil + } + out := new(TriggerList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TriggerList) 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 *TriggerSpec) DeepCopyInto(out *TriggerSpec) { + *out = *in + if in.Filter != nil { + in, out := &in.Filter, &out.Filter + *out = new(TriggerFilter) + (*in).DeepCopyInto(*out) + } + in.Subscriber.DeepCopyInto(&out.Subscriber) + if in.Delivery != nil { + in, out := &in.Delivery, &out.Delivery + *out = new(duckv1.DeliverySpec) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TriggerSpec. +func (in *TriggerSpec) DeepCopy() *TriggerSpec { + if in == nil { + return nil + } + out := new(TriggerSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TriggerStatus) DeepCopyInto(out *TriggerStatus) { + *out = *in + in.Status.DeepCopyInto(&out.Status) + if in.SubscriberURI != nil { + in, out := &in.SubscriberURI, &out.SubscriberURI + *out = new(apis.URL) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TriggerStatus. +func (in *TriggerStatus) DeepCopy() *TriggerStatus { + if in == nil { + return nil + } + out := new(TriggerStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/flows/v1beta1/doc.go b/pkg/apis/flows/v1beta1/doc.go new file mode 100644 index 00000000000..4b77f180564 --- /dev/null +++ b/pkg/apis/flows/v1beta1/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 v1beta1 is the v1beta1 version of the API. +// +k8s:deepcopy-gen=package +// +groupName=flows.knative.dev +package v1beta1 diff --git a/pkg/apis/flows/v1beta1/implements_test.go b/pkg/apis/flows/v1beta1/implements_test.go new file mode 100644 index 00000000000..955df282226 --- /dev/null +++ b/pkg/apis/flows/v1beta1/implements_test.go @@ -0,0 +1,41 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "testing" + + "knative.dev/pkg/apis/duck" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestTypesImplements(t *testing.T) { + testCases := []struct { + instance interface{} + iface duck.Implementable + }{ + // Sequence + {instance: &Sequence{}, iface: &duckv1.Conditions{}}, + // Parallel + {instance: &Parallel{}, iface: &duckv1.Conditions{}}, + } + for _, tc := range testCases { + if err := duck.VerifyType(tc.instance, tc.iface); err != nil { + t.Error(err) + } + } +} diff --git a/pkg/apis/flows/v1beta1/parallel_conversion.go b/pkg/apis/flows/v1beta1/parallel_conversion.go new file mode 100644 index 00000000000..3c8c511bfeb --- /dev/null +++ b/pkg/apis/flows/v1beta1/parallel_conversion.go @@ -0,0 +1,158 @@ +/* +Copyright 2020 The Knative Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "fmt" + + "knative.dev/pkg/apis" + + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + v1 "knative.dev/eventing/pkg/apis/flows/v1" + messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" +) + +// ConvertTo implements apis.Convertible +// Converts obj from v1beta1.Parallel into v1.Parallel +func (source *Parallel) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1.Parallel: + sink.ObjectMeta = source.ObjectMeta + + sink.Spec.Branches = make([]v1.ParallelBranch, len(source.Spec.Branches)) + for i, b := range source.Spec.Branches { + sink.Spec.Branches[i] = v1.ParallelBranch{ + Filter: b.Filter, + Subscriber: b.Subscriber, + Reply: b.Reply, + } + + if b.Delivery != nil { + sink.Spec.Branches[i].Delivery = &eventingduckv1.DeliverySpec{} + if err := b.Delivery.ConvertTo(ctx, sink.Spec.Branches[i].Delivery); err != nil { + return err + } + } + } + + if source.Spec.ChannelTemplate != nil { + sink.Spec.ChannelTemplate = &messagingv1.ChannelTemplateSpec{ + TypeMeta: source.Spec.ChannelTemplate.TypeMeta, + Spec: source.Spec.ChannelTemplate.Spec, + } + } + sink.Spec.Reply = source.Spec.Reply + + sink.Status.Status = source.Status.Status + sink.Status.AddressStatus = source.Status.AddressStatus + + sink.Status.IngressChannelStatus = v1.ParallelChannelStatus{ + Channel: source.Status.IngressChannelStatus.Channel, + ReadyCondition: source.Status.IngressChannelStatus.ReadyCondition, + } + + if source.Status.BranchStatuses != nil { + sink.Status.BranchStatuses = make([]v1.ParallelBranchStatus, len(source.Status.BranchStatuses)) + for i, b := range source.Status.BranchStatuses { + sink.Status.BranchStatuses[i] = v1.ParallelBranchStatus{ + FilterSubscriptionStatus: v1.ParallelSubscriptionStatus{ + Subscription: b.FilterSubscriptionStatus.Subscription, + ReadyCondition: b.FilterSubscriptionStatus.ReadyCondition, + }, + FilterChannelStatus: v1.ParallelChannelStatus{ + Channel: b.FilterChannelStatus.Channel, + ReadyCondition: b.FilterChannelStatus.ReadyCondition, + }, + SubscriptionStatus: v1.ParallelSubscriptionStatus{ + Subscription: b.SubscriptionStatus.Subscription, + ReadyCondition: b.SubscriptionStatus.ReadyCondition, + }, + } + } + } + + return nil + default: + return fmt.Errorf("Unknown conversion, got: %T", sink) + } +} + +// ConvertFrom implements apis.Convertible +// Converts obj from v1.Parallel into v1beta1.Parallel +func (sink *Parallel) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1.Parallel: + sink.ObjectMeta = source.ObjectMeta + + sink.Spec.Branches = make([]ParallelBranch, len(source.Spec.Branches)) + for i, b := range source.Spec.Branches { + sink.Spec.Branches[i] = ParallelBranch{ + Filter: b.Filter, + Subscriber: b.Subscriber, + Reply: b.Reply, + } + if b.Delivery != nil { + sink.Spec.Branches[i].Delivery = &eventingduckv1beta1.DeliverySpec{} + if err := sink.Spec.Branches[i].Delivery.ConvertFrom(ctx, b.Delivery); err != nil { + return err + } + } + } + if source.Spec.ChannelTemplate != nil { + sink.Spec.ChannelTemplate = &messagingv1beta1.ChannelTemplateSpec{ + TypeMeta: source.Spec.ChannelTemplate.TypeMeta, + Spec: source.Spec.ChannelTemplate.Spec, + } + } + sink.Spec.Reply = source.Spec.Reply + + sink.Status.Status = source.Status.Status + sink.Status.AddressStatus = source.Status.AddressStatus + + sink.Status.IngressChannelStatus = ParallelChannelStatus{ + Channel: source.Status.IngressChannelStatus.Channel, + ReadyCondition: source.Status.IngressChannelStatus.ReadyCondition, + } + + if source.Status.BranchStatuses != nil { + sink.Status.BranchStatuses = make([]ParallelBranchStatus, len(source.Status.BranchStatuses)) + for i, b := range source.Status.BranchStatuses { + sink.Status.BranchStatuses[i] = ParallelBranchStatus{ + FilterSubscriptionStatus: ParallelSubscriptionStatus{ + Subscription: b.FilterSubscriptionStatus.Subscription, + ReadyCondition: b.FilterSubscriptionStatus.ReadyCondition, + }, + FilterChannelStatus: ParallelChannelStatus{ + Channel: b.FilterChannelStatus.Channel, + ReadyCondition: b.FilterChannelStatus.ReadyCondition, + }, + SubscriptionStatus: ParallelSubscriptionStatus{ + Subscription: b.SubscriptionStatus.Subscription, + ReadyCondition: b.SubscriptionStatus.ReadyCondition, + }, + } + } + } + + return nil + default: + return fmt.Errorf("unknown version, got: %T", source) + } +} diff --git a/pkg/apis/flows/v1beta1/parallel_conversion_test.go b/pkg/apis/flows/v1beta1/parallel_conversion_test.go new file mode 100644 index 00000000000..0271bf4e00e --- /dev/null +++ b/pkg/apis/flows/v1beta1/parallel_conversion_test.go @@ -0,0 +1,466 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + "knative.dev/pkg/apis" + + duckv1 "knative.dev/pkg/apis/duck/v1" + + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + v1 "knative.dev/eventing/pkg/apis/flows/v1" + messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" +) + +func TestParallelConversionBadType(t *testing.T) { + good, bad := &Parallel{}, &Sequence{} + + 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) + } +} + +// Test v1beta1 -> v1 -> v1beta1 +func TestParallelRoundTripV1beta1(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&v1.Parallel{}} + linear := eventingduckv1beta1.BackoffPolicyLinear + + tests := []struct { + name string + in *Parallel + }{{ + name: "min configuration", + in: &Parallel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "par-name", + Namespace: "par-ns", + Generation: 17, + }, + Spec: ParallelSpec{ + Branches: []ParallelBranch{}, + }, + }, + }, { + name: "full configuration", + in: &Parallel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "par-name", + Namespace: "par-ns", + Generation: 17, + }, + Spec: ParallelSpec{ + Branches: []ParallelBranch{ + { + Filter: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "f1Kind", + Namespace: "f1Namespace", + Name: "f1Name", + APIVersion: "f1APIVersion", + }, + URI: apis.HTTP("f1.example.com")}, + + Subscriber: duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "s1Kind", + Namespace: "s1Namespace", + Name: "s1Name", + APIVersion: "s1APIVersion", + }, + URI: apis.HTTP("s1.example.com")}, + + Reply: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "reply1Kind", + Namespace: "reply1Namespace", + Name: "reply1Name", + APIVersion: "reply1APIVersion", + }, + URI: apis.HTTP("reply1.example.com"), + }, + Delivery: &eventingduckv1beta1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "d1Kind", + Namespace: "d1Namespace", + Name: "d1Name", + APIVersion: "d1APIVersion", + }, + URI: apis.HTTP("d1.example.com")}, + Retry: pointer.Int32Ptr(1), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("1m"), + }, + }, + { + Filter: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "f2Kind", + Namespace: "f2Namespace", + Name: "f2Name", + APIVersion: "f2APIVersion", + }, + URI: apis.HTTP("f2.example.com")}, + + Subscriber: duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "s2Kind", + Namespace: "s2Namespace", + Name: "s2Name", + APIVersion: "s2APIVersion", + }, + URI: apis.HTTP("s2.example.com")}, + + Reply: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "reply2Kind", + Namespace: "reply2Namespace", + Name: "reply2Name", + APIVersion: "reply2APIVersion", + }, + URI: apis.HTTP("reply2.example.com"), + }, + Delivery: &eventingduckv1beta1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "d2Kind", + Namespace: "d2Namespace", + Name: "d2Name", + APIVersion: "d2APIVersion", + }, + URI: apis.HTTP("d2.example.com")}, + Retry: pointer.Int32Ptr(1), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("1m"), + }, + }, + }, + ChannelTemplate: &messagingv1beta1.ChannelTemplateSpec{ + TypeMeta: metav1.TypeMeta{ + Kind: "channelKind", + APIVersion: "channelAPIVersion", + }, + }, + Reply: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "replyKind", + Namespace: "replyNamespace", + Name: "replyName", + APIVersion: "replyAPIVersion", + }, + URI: apis.HTTP("reply.example.com"), + }, + }, + Status: ParallelStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: apis.HTTP("addressstatus.example.com"), + }, + }, + IngressChannelStatus: ParallelChannelStatus{ + Channel: corev1.ObjectReference{ + Kind: "i-channel-kind", + APIVersion: "i-channel-apiversion", + Name: "i-channel-name", + Namespace: "i-channel-namespace", + }, + ReadyCondition: apis.Condition{Message: "i1-msg"}, + }, + BranchStatuses: []ParallelBranchStatus{ + { + FilterSubscriptionStatus: ParallelSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + Kind: "f1-sub-kind", + APIVersion: "f1-sub-apiversion", + Name: "f1-sub-name", + Namespace: "f1-sub-namespace", + }, + ReadyCondition: apis.Condition{Message: "f1-msg"}, + }, + SubscriptionStatus: ParallelSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + Kind: "s1-sub-kind", + APIVersion: "s1-sub-apiversion", + Name: "s1-sub-name", + Namespace: "s1-sub-namespace", + }, + ReadyCondition: apis.Condition{Message: "s1-msg"}, + }, + FilterChannelStatus: ParallelChannelStatus{ + Channel: corev1.ObjectReference{ + Kind: "s1-channel-kind", + APIVersion: "s1-channel-apiversion", + Name: "s1-channel-name", + Namespace: "s1-channel-namespace", + }, + ReadyCondition: apis.Condition{Message: "c1-msg"}, + }, + }, + }, + }, + }, + }} + + 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 := &Parallel{} + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Error("ConvertFrom() =", err) + } + + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} + +// Test v1 -> v1beta1 -> v1 +func TestParallelRoundTripV1(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&Parallel{}} + linear := eventingduckv1.BackoffPolicyLinear + + tests := []struct { + name string + in *v1.Parallel + }{{ + name: "min configuration", + in: &v1.Parallel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "par-name", + Namespace: "par-ns", + Generation: 17, + }, + Spec: v1.ParallelSpec{ + Branches: []v1.ParallelBranch{}, + }, + }, + }, { + name: "full configuration", + in: &v1.Parallel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "par-name", + Namespace: "par-ns", + Generation: 17, + }, + Spec: v1.ParallelSpec{ + Branches: []v1.ParallelBranch{ + { + Filter: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "f1Kind", + Namespace: "f1Namespace", + Name: "f1Name", + APIVersion: "f1APIVersion", + }, + URI: apis.HTTP("f1.example.com")}, + + Subscriber: duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "s1Kind", + Namespace: "s1Namespace", + Name: "s1Name", + APIVersion: "s1APIVersion", + }, + URI: apis.HTTP("s1.example.com")}, + + Reply: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "reply1Kind", + Namespace: "reply1Namespace", + Name: "reply1Name", + APIVersion: "reply1APIVersion", + }, + URI: apis.HTTP("reply1.example.com"), + }, + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "d1Kind", + Namespace: "d1Namespace", + Name: "d1Name", + APIVersion: "d1APIVersion", + }, + URI: apis.HTTP("d1.example.com")}, + Retry: pointer.Int32Ptr(1), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("1m"), + }, + }, + { + Filter: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "f2Kind", + Namespace: "f2Namespace", + Name: "f2Name", + APIVersion: "f2APIVersion", + }, + URI: apis.HTTP("f2.example.com")}, + + Subscriber: duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "s2Kind", + Namespace: "s2Namespace", + Name: "s2Name", + APIVersion: "s2APIVersion", + }, + URI: apis.HTTP("s2.example.com")}, + + Reply: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "reply2Kind", + Namespace: "reply2Namespace", + Name: "reply2Name", + APIVersion: "reply2APIVersion", + }, + URI: apis.HTTP("reply2.example.com"), + }, + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "d2Kind", + Namespace: "d2Namespace", + Name: "d2Name", + APIVersion: "d2APIVersion", + }, + URI: apis.HTTP("d2.example.com")}, + Retry: pointer.Int32Ptr(1), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("1m"), + }, + }, + }, + ChannelTemplate: &messagingv1.ChannelTemplateSpec{ + TypeMeta: metav1.TypeMeta{ + Kind: "channelKind", + APIVersion: "channelAPIVersion", + }, + }, + Reply: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "replyKind", + Namespace: "replyNamespace", + Name: "replyName", + APIVersion: "replyAPIVersion", + }, + URI: apis.HTTP("reply.example.com"), + }, + }, + Status: v1.ParallelStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: apis.HTTP("addressstatus.example.com"), + }, + }, + IngressChannelStatus: v1.ParallelChannelStatus{ + Channel: corev1.ObjectReference{ + Kind: "i-channel-kind", + APIVersion: "i-channel-apiversion", + Name: "i-channel-name", + Namespace: "i-channel-namespace", + }, + ReadyCondition: apis.Condition{Message: "i1-msg"}, + }, + BranchStatuses: []v1.ParallelBranchStatus{ + { + FilterSubscriptionStatus: v1.ParallelSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + Kind: "f1-sub-kind", + APIVersion: "f1-sub-apiversion", + Name: "f1-sub-name", + Namespace: "f1-sub-namespace", + }, + ReadyCondition: apis.Condition{Message: "f1-msg"}, + }, + SubscriptionStatus: v1.ParallelSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + Kind: "s1-sub-kind", + APIVersion: "s1-sub-apiversion", + Name: "s1-sub-name", + Namespace: "s1-sub-namespace", + }, + ReadyCondition: apis.Condition{Message: "s1-msg"}, + }, + FilterChannelStatus: v1.ParallelChannelStatus{ + Channel: corev1.ObjectReference{ + Kind: "s1-channel-kind", + APIVersion: "s1-channel-apiversion", + Name: "s1-channel-name", + Namespace: "s1-channel-namespace", + }, + ReadyCondition: apis.Condition{Message: "c1-msg"}, + }, + }, + }, + }, + }, + }} + + for _, test := range tests { + for _, version := range versions { + t.Run(test.name, func(t *testing.T) { + ver := version + if err := ver.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertFrom() =", err) + } + got := &v1.Parallel{} + if err := ver.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertTo() =", err) + } + + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} diff --git a/pkg/apis/flows/v1beta1/parallel_defaults.go b/pkg/apis/flows/v1beta1/parallel_defaults.go new file mode 100644 index 00000000000..712d342547b --- /dev/null +++ b/pkg/apis/flows/v1beta1/parallel_defaults.go @@ -0,0 +1,60 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + "knative.dev/eventing/pkg/apis/messaging/config" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + "knative.dev/pkg/apis" +) + +func (p *Parallel) SetDefaults(ctx context.Context) { + if p == nil { + return + } + + withNS := apis.WithinParent(ctx, p.ObjectMeta) + if p.Spec.ChannelTemplate == nil { + cfg := config.FromContextOrDefaults(ctx) + c, err := cfg.ChannelDefaults.GetChannelConfig(apis.ParentMeta(ctx).Namespace) + + if err == nil { + p.Spec.ChannelTemplate = &messagingv1beta1.ChannelTemplateSpec{ + TypeMeta: c.TypeMeta, + Spec: c.Spec, + } + } + } + p.Spec.SetDefaults(withNS) +} + +func (ps *ParallelSpec) SetDefaults(ctx context.Context) { + for _, branch := range ps.Branches { + if branch.Filter != nil { + branch.Filter.SetDefaults(ctx) + } + branch.Subscriber.SetDefaults(ctx) + if branch.Reply != nil { + branch.Reply.SetDefaults(ctx) + } + } + if ps.Reply != nil { + ps.Reply.SetDefaults(ctx) + } +} diff --git a/pkg/apis/flows/v1beta1/parallel_defaults_test.go b/pkg/apis/flows/v1beta1/parallel_defaults_test.go new file mode 100644 index 00000000000..db60af9be84 --- /dev/null +++ b/pkg/apis/flows/v1beta1/parallel_defaults_test.go @@ -0,0 +1,153 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/google/go-cmp/cmp" + "knative.dev/eventing/pkg/apis/messaging/config" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestParallelSetDefaults(t *testing.T) { + testCases := map[string]struct { + nilChannelDefaulter bool + channelTemplate *config.ChannelTemplateSpec + initial Parallel + expected Parallel + }{ + "nil ChannelDefaulter": { + nilChannelDefaulter: true, + expected: Parallel{}, + }, + "unset ChannelDefaulter": { + expected: Parallel{}, + }, + "set ChannelDefaulter": { + channelTemplate: configDefaultChannelTemplate, + expected: Parallel{ + Spec: ParallelSpec{ + ChannelTemplate: defaultChannelTemplate, + }, + }, + }, + "branches namespace defaulted": { + channelTemplate: configDefaultChannelTemplate, + initial: Parallel{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNS}, + Spec: ParallelSpec{ + Branches: []ParallelBranch{ + { + Filter: &duckv1.Destination{ + Ref: &duckv1.KReference{Name: "firstfilter"}, + }, + Subscriber: duckv1.Destination{ + Ref: &duckv1.KReference{Name: "firstsub"}, + }, + Reply: &duckv1.Destination{ + Ref: &duckv1.KReference{Name: "firstreply"}, + }, + }, { + Filter: &duckv1.Destination{ + Ref: &duckv1.KReference{Name: "secondfilter"}, + }, + Subscriber: duckv1.Destination{ + Ref: &duckv1.KReference{Name: "secondsub"}, + }, + Reply: &duckv1.Destination{ + Ref: &duckv1.KReference{Name: "secondreply"}, + }, + }}, + Reply: &duckv1.Destination{Ref: &duckv1.KReference{Name: "reply"}}}, + }, + expected: Parallel{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNS}, + Spec: ParallelSpec{ + ChannelTemplate: defaultChannelTemplate, + Branches: []ParallelBranch{ + { + Filter: &duckv1.Destination{ + Ref: &duckv1.KReference{Name: "firstfilter", Namespace: testNS}, + }, + Subscriber: duckv1.Destination{ + Ref: &duckv1.KReference{Name: "firstsub", Namespace: testNS}, + }, + Reply: &duckv1.Destination{ + Ref: &duckv1.KReference{Name: "firstreply", Namespace: testNS}, + }, + }, { + Filter: &duckv1.Destination{ + Ref: &duckv1.KReference{Name: "secondfilter", Namespace: testNS}, + }, + Subscriber: duckv1.Destination{ + Ref: &duckv1.KReference{Name: "secondsub", Namespace: testNS}, + }, + Reply: &duckv1.Destination{ + Ref: &duckv1.KReference{Name: "secondreply", Namespace: testNS}, + }, + }}, + Reply: &duckv1.Destination{Ref: &duckv1.KReference{Name: "reply", Namespace: testNS}}, + }, + }, + }, + "template already specified": { + channelTemplate: configDefaultChannelTemplate, + initial: Parallel{ + Spec: ParallelSpec{ + ChannelTemplate: &messagingv1beta1.ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + APIVersion: SchemeGroupVersion.String(), + Kind: "OtherChannel", + }, + }, + }, + }, + expected: Parallel{ + Spec: ParallelSpec{ + ChannelTemplate: &messagingv1beta1.ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + APIVersion: SchemeGroupVersion.String(), + Kind: "OtherChannel", + }, + }, + }, + }, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + ctx := context.Background() + if !tc.nilChannelDefaulter { + ctx = config.ToContext(ctx, &config.Config{ + ChannelDefaults: &config.ChannelDefaults{ + ClusterDefault: tc.channelTemplate, + }, + }) + } + tc.initial.SetDefaults(ctx) + if diff := cmp.Diff(tc.expected, tc.initial); diff != "" { + t.Fatal("Unexpected defaults (-want, +got):", diff) + } + }) + } +} diff --git a/pkg/apis/flows/v1beta1/parallel_lifecycle.go b/pkg/apis/flows/v1beta1/parallel_lifecycle.go new file mode 100644 index 00000000000..2ce72eb547d --- /dev/null +++ b/pkg/apis/flows/v1beta1/parallel_lifecycle.go @@ -0,0 +1,205 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + duckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + "knative.dev/pkg/apis" + pkgduckv1 "knative.dev/pkg/apis/duck/v1" +) + +var pCondSet = apis.NewLivingConditionSet(ParallelConditionReady, ParallelConditionChannelsReady, ParallelConditionSubscriptionsReady, ParallelConditionAddressable) + +const ( + // ParallelConditionReady has status True when all subconditions below have been set to True. + ParallelConditionReady = apis.ConditionReady + + // ParallelConditionChannelsReady has status True when all the channels created as part of + // this parallel are ready. + ParallelConditionChannelsReady apis.ConditionType = "ChannelsReady" + + // ParallelConditionSubscriptionsReady has status True when all the subscriptions created as part of + // this parallel are ready. + ParallelConditionSubscriptionsReady apis.ConditionType = "SubscriptionsReady" + + // ParallelConditionAddressable has status true when this Parallel meets + // the Addressable contract and has a non-empty hostname. + ParallelConditionAddressable apis.ConditionType = "Addressable" +) + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (*Parallel) GetConditionSet() apis.ConditionSet { + return pCondSet +} + +// GetGroupVersionKind returns GroupVersionKind for Parallel +func (*Parallel) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("Parallel") +} + +// GetUntypedSpec returns the spec of the Parallel. +func (p *Parallel) GetUntypedSpec() interface{} { + return p.Spec +} + +// GetCondition returns the condition currently associated with the given type, or nil. +func (ps *ParallelStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return pCondSet.Manage(ps).GetCondition(t) +} + +// IsReady returns true if the resource is ready overall. +func (ps *ParallelStatus) IsReady() bool { + return pCondSet.Manage(ps).IsHappy() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (ps *ParallelStatus) InitializeConditions() { + pCondSet.Manage(ps).InitializeConditions() +} + +// PropagateSubscriptionStatuses sets the ParallelConditionSubscriptionsReady based on +// the status of the incoming subscriptions. +func (ps *ParallelStatus) PropagateSubscriptionStatuses(filterSubscriptions []*messagingv1beta1.Subscription, subscriptions []*messagingv1beta1.Subscription) { + if ps.BranchStatuses == nil { + ps.BranchStatuses = make([]ParallelBranchStatus, len(subscriptions)) + } + allReady := true + // If there are no subscriptions, treat that as a False branch. Could go either way, but this seems right. + if len(subscriptions) == 0 { + allReady = false + } + + for i, s := range subscriptions { + ps.BranchStatuses[i].SubscriptionStatus = ParallelSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + APIVersion: s.APIVersion, + Kind: s.Kind, + Name: s.Name, + Namespace: s.Namespace, + }, + } + + readyCondition := s.Status.GetTopLevelCondition() + if readyCondition != nil { + ps.BranchStatuses[i].SubscriptionStatus.ReadyCondition = *readyCondition + if readyCondition.Status != corev1.ConditionTrue { + allReady = false + } + } else { + allReady = false + } + + fs := filterSubscriptions[i] + ps.BranchStatuses[i].FilterSubscriptionStatus = ParallelSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + APIVersion: fs.APIVersion, + Kind: fs.Kind, + Name: fs.Name, + Namespace: fs.Namespace, + }, + } + readyCondition = fs.Status.GetCondition(messagingv1beta1.SubscriptionConditionReady) + if readyCondition != nil { + ps.BranchStatuses[i].FilterSubscriptionStatus.ReadyCondition = *readyCondition + if readyCondition.Status != corev1.ConditionTrue { + allReady = false + } + } else { + allReady = false + } + + } + if allReady { + pCondSet.Manage(ps).MarkTrue(ParallelConditionSubscriptionsReady) + } else { + ps.MarkSubscriptionsNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none") + } +} + +// PropagateChannelStatuses sets the ChannelStatuses and ParallelConditionChannelsReady based on the +// status of the incoming channels. +func (ps *ParallelStatus) PropagateChannelStatuses(ingressChannel *duckv1beta1.Channelable, channels []*duckv1beta1.Channelable) { + if ps.BranchStatuses == nil { + ps.BranchStatuses = make([]ParallelBranchStatus, len(channels)) + } + allReady := true + + ps.IngressChannelStatus.Channel = corev1.ObjectReference{ + APIVersion: ingressChannel.APIVersion, + Kind: ingressChannel.Kind, + Name: ingressChannel.Name, + Namespace: ingressChannel.Namespace, + } + + address := ingressChannel.Status.AddressStatus.Address + if address != nil { + ps.IngressChannelStatus.ReadyCondition = apis.Condition{Type: apis.ConditionReady, Status: corev1.ConditionTrue} + } else { + ps.IngressChannelStatus.ReadyCondition = apis.Condition{Type: apis.ConditionReady, Status: corev1.ConditionFalse, Reason: "NotAddressable", Message: "Channel is not addressable"} + allReady = false + } + ps.setAddress(address) + + for i, c := range channels { + ps.BranchStatuses[i].FilterChannelStatus = ParallelChannelStatus{ + Channel: corev1.ObjectReference{ + APIVersion: c.APIVersion, + Kind: c.Kind, + Name: c.Name, + Namespace: c.Namespace, + }, + } + // TODO: Once the addressable has a real status to dig through, use that here instead of + // addressable, because it might be addressable but not ready. + address := c.Status.AddressStatus.Address + if address != nil { + ps.BranchStatuses[i].FilterChannelStatus.ReadyCondition = apis.Condition{Type: apis.ConditionReady, Status: corev1.ConditionTrue} + } else { + ps.BranchStatuses[i].FilterChannelStatus.ReadyCondition = apis.Condition{Type: apis.ConditionReady, Status: corev1.ConditionFalse, Reason: "NotAddressable", Message: "Channel is not addressable"} + allReady = false + } + } + if allReady { + pCondSet.Manage(ps).MarkTrue(ParallelConditionChannelsReady) + } else { + ps.MarkChannelsNotReady("ChannelsNotReady", "Channels are not ready yet, or there are none") + } +} + +func (ps *ParallelStatus) MarkChannelsNotReady(reason, messageFormat string, messageA ...interface{}) { + pCondSet.Manage(ps).MarkFalse(ParallelConditionChannelsReady, reason, messageFormat, messageA...) +} + +func (ps *ParallelStatus) MarkSubscriptionsNotReady(reason, messageFormat string, messageA ...interface{}) { + pCondSet.Manage(ps).MarkFalse(ParallelConditionSubscriptionsReady, reason, messageFormat, messageA...) +} + +func (ps *ParallelStatus) MarkAddressableNotReady(reason, messageFormat string, messageA ...interface{}) { + pCondSet.Manage(ps).MarkFalse(ParallelConditionAddressable, reason, messageFormat, messageA...) +} + +func (ps *ParallelStatus) setAddress(address *pkgduckv1.Addressable) { + ps.Address = address + if address == nil { + pCondSet.Manage(ps).MarkFalse(ParallelConditionAddressable, "emptyAddress", "addressable is nil") + } else { + pCondSet.Manage(ps).MarkTrue(ParallelConditionAddressable) + } +} diff --git a/pkg/apis/flows/v1beta1/parallel_lifecycle_test.go b/pkg/apis/flows/v1beta1/parallel_lifecycle_test.go new file mode 100644 index 00000000000..c56850195f4 --- /dev/null +++ b/pkg/apis/flows/v1beta1/parallel_lifecycle_test.go @@ -0,0 +1,433 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "testing" + + "knative.dev/pkg/apis" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + duckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +var parallelConditionReady = apis.Condition{ + Type: ParallelConditionReady, + Status: corev1.ConditionTrue, +} + +func TestParallelGetConditionSet(t *testing.T) { + r := &Parallel{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestParallelGetCondition(t *testing.T) { + tests := []struct { + name string + ss *ParallelStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "single condition", + ss: &ParallelStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + parallelConditionReady, + }, + }, + }, + condQuery: apis.ConditionReady, + want: ¶llelConditionReady, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.ss.GetCondition(test.condQuery) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Error("unexpected condition (-want, +got) =", diff) + } + }) + } +} + +func TestParallelInitializeConditions(t *testing.T) { + tests := []struct { + name string + ts *ParallelStatus + want *ParallelStatus + }{{ + name: "empty", + ts: &ParallelStatus{}, + want: &ParallelStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: ParallelConditionAddressable, + Status: corev1.ConditionUnknown, + }, { + Type: ParallelConditionChannelsReady, + Status: corev1.ConditionUnknown, + }, { + Type: ParallelConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: ParallelConditionSubscriptionsReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, { + name: "one false", + ts: &ParallelStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: ParallelConditionChannelsReady, + Status: corev1.ConditionFalse, + }}, + }, + }, + want: &ParallelStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: ParallelConditionAddressable, + Status: corev1.ConditionUnknown, + }, { + Type: ParallelConditionChannelsReady, + Status: corev1.ConditionFalse, + }, { + Type: ParallelConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: ParallelConditionSubscriptionsReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, { + name: "one true", + ts: &ParallelStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: ParallelConditionSubscriptionsReady, + Status: corev1.ConditionTrue, + }}, + }, + }, + want: &ParallelStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: ParallelConditionAddressable, + Status: corev1.ConditionUnknown, + }, { + Type: ParallelConditionChannelsReady, + Status: corev1.ConditionUnknown, + }, { + Type: ParallelConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: ParallelConditionSubscriptionsReady, + Status: corev1.ConditionTrue, + }}, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.ts.InitializeConditions() + if diff := cmp.Diff(test.want, test.ts, ignoreAllButTypeAndStatus); diff != "" { + t.Error("unexpected conditions (-want, +got) =", diff) + } + }) + } +} + +func TestParallelPropagateSubscriptionStatuses(t *testing.T) { + tests := []struct { + name string + fsubs []*messagingv1beta1.Subscription + subs []*messagingv1beta1.Subscription + want corev1.ConditionStatus + }{{ + name: "empty", + fsubs: []*messagingv1beta1.Subscription{}, + subs: []*messagingv1beta1.Subscription{}, + want: corev1.ConditionFalse, + }, { + name: "empty status", + fsubs: []*messagingv1beta1.Subscription{{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "messaging.knative.dev/v1beta1", + Kind: "Subscription", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "sub", + Namespace: "testns", + }, + Status: messagingv1beta1.SubscriptionStatus{}, + }}, subs: []*messagingv1beta1.Subscription{{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "messaging.knative.dev/v1beta1", + Kind: "Subscription", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "sub", + Namespace: "testns", + }, + Status: messagingv1beta1.SubscriptionStatus{}, + }}, + want: corev1.ConditionFalse, + }, { + name: "one filter and subscriber subscription not ready", + fsubs: []*messagingv1beta1.Subscription{getSubscription("fsub0", false)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", false)}, + want: corev1.ConditionFalse, + }, { + name: "one filter and one subscription ready", + fsubs: []*messagingv1beta1.Subscription{getSubscription("fsub0", true)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true)}, + want: corev1.ConditionTrue, + }, { + name: "one filter subscription not ready and one subscription ready", + fsubs: []*messagingv1beta1.Subscription{getSubscription("fsub0", false)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true)}, + want: corev1.ConditionFalse, + }, { + name: "one subscription ready, one not", + fsubs: []*messagingv1beta1.Subscription{getSubscription("fsub0", true), getSubscription("fsub1", false)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true), getSubscription("sub1", false)}, + want: corev1.ConditionFalse, + }, { + name: "two subscriptions ready", + fsubs: []*messagingv1beta1.Subscription{getSubscription("fsub0", true), getSubscription("fsub1", true)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true), getSubscription("sub1", true)}, + want: corev1.ConditionTrue, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ps := ParallelStatus{} + ps.PropagateSubscriptionStatuses(test.fsubs, test.subs) + got := ps.GetCondition(ParallelConditionSubscriptionsReady).Status + want := test.want + if want != got { + t.Errorf("unexpected conditions (-want, +got) = %v %v", want, got) + } + }) + } +} + +func TestParallelPropagateChannelStatuses(t *testing.T) { + tests := []struct { + name string + ichannel *duckv1beta1.Channelable + channels []*duckv1beta1.Channelable + want corev1.ConditionStatus + }{{ + name: "ingress false, empty", + ichannel: getChannelable(false), + channels: []*duckv1beta1.Channelable{}, + want: corev1.ConditionFalse, + }, { + name: "ingress false, one channelable not ready", + ichannel: getChannelable(false), + channels: []*duckv1beta1.Channelable{getChannelable(false)}, + want: corev1.ConditionFalse, + }, { + name: "ingress true, one channelable not ready", + ichannel: getChannelable(true), + channels: []*duckv1beta1.Channelable{getChannelable(false)}, + want: corev1.ConditionFalse, + }, { + name: "ingress false, one channelable ready", + ichannel: getChannelable(false), + channels: []*duckv1beta1.Channelable{getChannelable(true)}, + want: corev1.ConditionFalse, + }, { + name: "ingress true, one channelable ready", + ichannel: getChannelable(true), + channels: []*duckv1beta1.Channelable{getChannelable(true)}, + want: corev1.ConditionTrue, + }, { + name: "ingress true, one channelable ready, one not", + ichannel: getChannelable(true), + channels: []*duckv1beta1.Channelable{getChannelable(true), getChannelable(false)}, + want: corev1.ConditionFalse, + }, { + name: "ingress true, two channelables ready", + ichannel: getChannelable(true), + channels: []*duckv1beta1.Channelable{getChannelable(true), getChannelable(true)}, + want: corev1.ConditionTrue, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ps := ParallelStatus{} + ps.PropagateChannelStatuses(test.ichannel, test.channels) + got := ps.GetCondition(ParallelConditionChannelsReady).Status + want := test.want + if want != got { + t.Errorf("unexpected conditions (-want, +got) = %v %v", want, got) + } + }) + } +} + +func TestParallelReady(t *testing.T) { + tests := []struct { + name string + fsubs []*messagingv1beta1.Subscription + subs []*messagingv1beta1.Subscription + ichannel *duckv1beta1.Channelable + channels []*duckv1beta1.Channelable + want bool + }{{ + name: "ingress false, empty", + fsubs: []*messagingv1beta1.Subscription{}, + subs: []*messagingv1beta1.Subscription{}, + ichannel: getChannelable(false), + channels: []*duckv1beta1.Channelable{}, + want: false, + }, { + name: "ingress true, empty", + fsubs: []*messagingv1beta1.Subscription{}, + subs: []*messagingv1beta1.Subscription{}, + ichannel: getChannelable(true), + channels: []*duckv1beta1.Channelable{}, + want: false, + }, { + name: "ingress true, one channelable not ready, one subscription ready", + ichannel: getChannelable(true), + channels: []*duckv1beta1.Channelable{getChannelable(false)}, + fsubs: []*messagingv1beta1.Subscription{getSubscription("fsub0", true)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true)}, + want: false, + }, { + name: "ingress true, one channelable ready, one subscription not ready", + ichannel: getChannelable(true), + channels: []*duckv1beta1.Channelable{getChannelable(true)}, + fsubs: []*messagingv1beta1.Subscription{getSubscription("fsub0", false)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", false)}, + want: false, + }, { + name: "ingress false, one channelable ready, one subscription ready", + ichannel: getChannelable(false), + channels: []*duckv1beta1.Channelable{getChannelable(true)}, + fsubs: []*messagingv1beta1.Subscription{getSubscription("fsub0", true)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true)}, + want: false, + }, { + name: "ingress true, one channelable ready, one subscription ready", + ichannel: getChannelable(true), + channels: []*duckv1beta1.Channelable{getChannelable(true)}, + fsubs: []*messagingv1beta1.Subscription{getSubscription("fsub0", true)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true)}, + want: true, + }, { + name: "ingress true, one channelable ready, one not, two subscriptions ready", + ichannel: getChannelable(true), + channels: []*duckv1beta1.Channelable{getChannelable(true), getChannelable(false)}, + fsubs: []*messagingv1beta1.Subscription{getSubscription("fsub0", true), getSubscription("fsub1", true)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true), getSubscription("sub1", true)}, + want: false, + }, { + name: "ingress true, two channelables ready, one subscription ready, one not", + ichannel: getChannelable(true), + channels: []*duckv1beta1.Channelable{getChannelable(true), getChannelable(true)}, + fsubs: []*messagingv1beta1.Subscription{getSubscription("fsub0", true), getSubscription("fsub1", false)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true), getSubscription("sub1", false)}, + want: false, + }, { + name: "ingress false, two channelables ready, two subscriptions ready", + ichannel: getChannelable(false), + channels: []*duckv1beta1.Channelable{getChannelable(true), getChannelable(true)}, + fsubs: []*messagingv1beta1.Subscription{getSubscription("fsub0", true), getSubscription("fsub1", true)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true), getSubscription("sub1", true)}, + want: false, + }, { + name: "ingress true, two channelables ready, two subscriptions ready", + ichannel: getChannelable(true), + channels: []*duckv1beta1.Channelable{getChannelable(true), getChannelable(true)}, + fsubs: []*messagingv1beta1.Subscription{getSubscription("fsub0", true), getSubscription("fsub1", true)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true), getSubscription("sub1", true)}, + want: true, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ps := ParallelStatus{} + ps.PropagateChannelStatuses(test.ichannel, test.channels) + ps.PropagateSubscriptionStatuses(test.fsubs, test.subs) + got := ps.IsReady() + want := test.want + if want != got { + t.Errorf("unexpected conditions (-want, +got) = %v %v", want, got) + } + }) + } +} + +func TestParallelPropagateSetAddress(t *testing.T) { + URL := apis.HTTP("example.com") + tests := []struct { + name string + address *duckv1.Addressable + want *duckv1.Addressable + wantStatus corev1.ConditionStatus + }{{ + name: "nil", + address: nil, + want: nil, + wantStatus: corev1.ConditionFalse, + }, { + name: "empty", + address: &duckv1.Addressable{}, + want: &duckv1.Addressable{}, + wantStatus: corev1.ConditionTrue, + }, { + name: "URL", + address: &duckv1.Addressable{URL: URL}, + want: &duckv1.Addressable{URL: URL}, + wantStatus: corev1.ConditionTrue, + }, { + name: "nil", + address: &duckv1.Addressable{URL: nil}, + want: &duckv1.Addressable{}, + wantStatus: corev1.ConditionTrue, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ps := ParallelStatus{} + ps.setAddress(test.address) + got := ps.Address + if diff := cmp.Diff(test.want, got, ignoreAllButTypeAndStatus); diff != "" { + t.Error("unexpected address (-want, +got) =", diff) + } + gotStatus := ps.GetCondition(ParallelConditionAddressable).Status + if test.wantStatus != gotStatus { + t.Errorf("unexpected conditions (-want, +got) = %v %v", test.wantStatus, gotStatus) + } + }) + } +} diff --git a/pkg/apis/flows/v1beta1/parallel_types.go b/pkg/apis/flows/v1beta1/parallel_types.go new file mode 100644 index 00000000000..9cccfee7f46 --- /dev/null +++ b/pkg/apis/flows/v1beta1/parallel_types.go @@ -0,0 +1,166 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +genreconciler +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// Parallel defines conditional branches that will be wired in +// series through Channels and Subscriptions. +type Parallel struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of the Parallel. + Spec ParallelSpec `json:"spec,omitempty"` + + // Status represents the current state of the Parallel. This data may be out of + // date. + // +optional + Status ParallelStatus `json:"status,omitempty"` +} + +var ( + // Check that Parallel can be validated and defaulted. + _ apis.Validatable = (*Parallel)(nil) + _ apis.Defaultable = (*Parallel)(nil) + + // Check that Parallel can return its spec untyped. + _ apis.HasSpec = (*Parallel)(nil) + + // TODO: make appropriate fields immutable. + //_ apis.Immutable = (*Parallel)(nil) + + _ runtime.Object = (*Parallel)(nil) + + // Check that we can create OwnerReferences to a Parallel. + _ kmeta.OwnerRefable = (*Parallel)(nil) + + // Check that the type conforms to the duck Knative Resource shape. + _ duckv1.KRShaped = (*Parallel)(nil) +) + +type ParallelSpec struct { + // Branches is the list of Filter/Subscribers pairs. + Branches []ParallelBranch `json:"branches"` + + // ChannelTemplate specifies which Channel CRD to use. If left unspecified, it is set to the default Channel CRD + // for the namespace (or cluster, in case there are no defaults for the namespace). + // +optional + ChannelTemplate *messagingv1beta1.ChannelTemplateSpec `json:"channelTemplate"` + + // Reply is a Reference to where the result of a case Subscriber gets sent to + // when the case does not have a Reply + // +optional + Reply *duckv1.Destination `json:"reply,omitempty"` +} + +type ParallelBranch struct { + // Filter is the expression guarding the branch + // +optional + Filter *duckv1.Destination `json:"filter,omitempty"` + + // Subscriber receiving the event when the filter passes + Subscriber duckv1.Destination `json:"subscriber"` + + // Reply is a Reference to where the result of Subscriber of this case gets sent to. + // If not specified, sent the result to the Parallel Reply + // +optional + Reply *duckv1.Destination `json:"reply,omitempty"` + + // Delivery is the delivery specification for events to the subscriber + // This includes things like retries, DLQ, etc. + // Needed for Roundtripping v1alpha1 <-> v1beta1. + // +optional + Delivery *eventingduckv1beta1.DeliverySpec `json:"delivery,omitempty"` +} + +// ParallelStatus represents the current state of a Parallel. +type ParallelStatus struct { + // inherits duck/v1 Status, 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. + duckv1.Status `json:",inline"` + + // IngressChannelStatus corresponds to the ingress channel status. + IngressChannelStatus ParallelChannelStatus `json:"ingressChannelStatus"` + + // BranchStatuses is an array of corresponding to branch statuses. + // Matches the Spec.Branches array in the order. + BranchStatuses []ParallelBranchStatus `json:"branchStatuses"` + + // AddressStatus is the starting point to this Parallel. Sending to this + // will target the first subscriber. + // It generally has the form {channel}.{namespace}.svc.{cluster domain name} + duckv1.AddressStatus `json:",inline"` +} + +// ParallelBranchStatus represents the current state of a Parallel branch +type ParallelBranchStatus struct { + // FilterSubscriptionStatus corresponds to the filter subscription status. + FilterSubscriptionStatus ParallelSubscriptionStatus `json:"filterSubscriptionStatus"` + + // FilterChannelStatus corresponds to the filter channel status. + FilterChannelStatus ParallelChannelStatus `json:"filterChannelStatus"` + + // SubscriptionStatus corresponds to the subscriber subscription status. + SubscriptionStatus ParallelSubscriptionStatus `json:"subscriberSubscriptionStatus"` +} + +type ParallelChannelStatus struct { + // Channel is the reference to the underlying channel. + Channel corev1.ObjectReference `json:"channel"` + + // ReadyCondition indicates whether the Channel is ready or not. + ReadyCondition apis.Condition `json:"ready"` +} + +type ParallelSubscriptionStatus struct { + // Subscription is the reference to the underlying Subscription. + Subscription corev1.ObjectReference `json:"subscription"` + + // ReadyCondition indicates whether the Subscription is ready or not. + ReadyCondition apis.Condition `json:"ready"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ParallelList is a collection of Parallels. +type ParallelList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + Items []Parallel `json:"items"` +} + +// GetStatus retrieves the status of the Parallel. Implements the KRShaped interface. +func (p *Parallel) GetStatus() *duckv1.Status { + return &p.Status.Status +} diff --git a/pkg/apis/flows/v1beta1/parallel_types_test.go b/pkg/apis/flows/v1beta1/parallel_types_test.go new file mode 100644 index 00000000000..cef8b4b699f --- /dev/null +++ b/pkg/apis/flows/v1beta1/parallel_types_test.go @@ -0,0 +1,35 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import "testing" + +func TestParallelGetStatus(t *testing.T) { + r := &Parallel{ + Status: ParallelStatus{}, + } + if got, want := r.GetStatus(), &r.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} + +func TestParallelKind(t *testing.T) { + parallel := Parallel{} + if parallel.GetGroupVersionKind().String() != "flows.knative.dev/v1beta1, Kind=Parallel" { + t.Error("unexpected gvk:", parallel.GetGroupVersionKind()) + } +} diff --git a/pkg/apis/flows/v1beta1/parallel_validation.go b/pkg/apis/flows/v1beta1/parallel_validation.go new file mode 100644 index 00000000000..980e7f6bea5 --- /dev/null +++ b/pkg/apis/flows/v1beta1/parallel_validation.go @@ -0,0 +1,68 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + "knative.dev/pkg/apis" +) + +func (p *Parallel) Validate(ctx context.Context) *apis.FieldError { + return p.Spec.Validate(ctx).ViaField("spec") +} + +func (ps *ParallelSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + + if len(ps.Branches) == 0 { + errs = errs.Also(apis.ErrMissingField("branches")) + } + + for i, s := range ps.Branches { + if err := s.Filter.Validate(ctx); err != nil { + errs = errs.Also(apis.ErrInvalidArrayValue(s, "branches.filter", i)) + } + + if e := s.Subscriber.Validate(ctx); e != nil { + errs = errs.Also(apis.ErrInvalidArrayValue(s, "branches.subscriber", i)) + } + + if e := s.Reply.Validate(ctx); e != nil { + errs = errs.Also(apis.ErrInvalidArrayValue(s, "branches.reply", i)) + } + } + + if ps.ChannelTemplate == nil { + errs = errs.Also(apis.ErrMissingField("channelTemplate")) + return errs + } + + if len(ps.ChannelTemplate.APIVersion) == 0 { + errs = errs.Also(apis.ErrMissingField("channelTemplate.apiVersion")) + } + + if len(ps.ChannelTemplate.Kind) == 0 { + errs = errs.Also(apis.ErrMissingField("channelTemplate.kind")) + } + + if err := ps.Reply.Validate(ctx); err != nil { + errs = errs.Also(err.ViaField("reply")) + } + + return errs +} diff --git a/pkg/apis/flows/v1beta1/parallel_validation_test.go b/pkg/apis/flows/v1beta1/parallel_validation_test.go new file mode 100644 index 00000000000..0a063b232f3 --- /dev/null +++ b/pkg/apis/flows/v1beta1/parallel_validation_test.go @@ -0,0 +1,206 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + "knative.dev/pkg/apis" +) + +func getValidBranches() []ParallelBranch { + return []ParallelBranch{ + { + Filter: getValidDestinationRef(), + Subscriber: getValidDestination(), + Reply: getValidDestinationRef(), + Delivery: getValidDelivery(), + }, + } +} + +func TestParallelValidate(t *testing.T) { + tests := []struct { + name string + p *Parallel + want *apis.FieldError + }{ + { + name: "valid", + p: &Parallel{ + Spec: ParallelSpec{ + Branches: getValidBranches(), + ChannelTemplate: getValidChannelTemplate(), + Reply: getValidDestinationRef(), + }, + }, + want: nil, + }, + { + name: "invalid", + p: &Parallel{ + Spec: ParallelSpec{ + ChannelTemplate: getValidChannelTemplate(), + Reply: getValidDestinationRef(), + }, + }, + want: apis.ErrMissingField("spec.branches"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.p.Validate(context.TODO()) + if diff := cmp.Diff(tt.want.Error(), got.Error()); diff != "" { + t.Errorf("%s: Parallel.Validate (-want, +got) = %v", tt.name, diff) + } + }) + } +} + +func TestParallelSpecValidate(t *testing.T) { + invalidBranches := [][]ParallelBranch{ + {{ + Filter: getInvalidDestinationRef(), + Subscriber: getValidDestination(), + Reply: getValidDestinationRef(), + Delivery: getValidDelivery(), + }}, + {{ + Filter: getValidDestinationRef(), + Subscriber: getInvalidDestination(), + Reply: getValidDestinationRef(), + Delivery: getValidDelivery(), + }}, + {{ + Filter: getValidDestinationRef(), + Subscriber: getValidDestination(), + Reply: getInvalidDestinationRef(), + Delivery: getValidDelivery(), + }}, + } + + invalidChannelTemplates := []*messagingv1beta1.ChannelTemplateSpec{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "testChannel", + }, + }, + { + TypeMeta: metav1.TypeMeta{ + APIVersion: "testAPIVersion", + }, + }, + } + + tests := []struct { + name string + ps *ParallelSpec + want *apis.FieldError + }{ + { + name: "valid", + ps: &ParallelSpec{ + Branches: getValidBranches(), + ChannelTemplate: getValidChannelTemplate(), + Reply: getValidDestinationRef(), + }, + want: nil, + }, + { + name: "without branches", + ps: &ParallelSpec{ + ChannelTemplate: getValidChannelTemplate(), + Reply: getValidDestinationRef(), + }, + want: apis.ErrMissingField("branches"), + }, + { + name: "branches with invalid filter", + ps: &ParallelSpec{ + Branches: invalidBranches[0], + ChannelTemplate: getValidChannelTemplate(), + Reply: getValidDestinationRef(), + }, + want: apis.ErrInvalidArrayValue(invalidBranches[0][0], "branches.filter", 0), + }, + { + name: "branches with invalid subscriber", + ps: &ParallelSpec{ + Branches: invalidBranches[1], + ChannelTemplate: getValidChannelTemplate(), + Reply: getValidDestinationRef(), + }, + want: apis.ErrInvalidArrayValue(invalidBranches[1][0], "branches.subscriber", 0), + }, + { + name: "branches with invalid reply", + ps: &ParallelSpec{ + Branches: invalidBranches[2], + ChannelTemplate: getValidChannelTemplate(), + Reply: getValidDestinationRef(), + }, + want: apis.ErrInvalidArrayValue(invalidBranches[2][0], "branches.reply", 0), + }, + { + name: "without channelTemplate", + ps: &ParallelSpec{ + Branches: getValidBranches(), + Reply: getValidDestinationRef(), + }, + want: apis.ErrMissingField("channelTemplate"), + }, + { + name: "channelTemplate without apiVersion", + ps: &ParallelSpec{ + Branches: getValidBranches(), + ChannelTemplate: invalidChannelTemplates[0], + Reply: getValidDestinationRef(), + }, + want: apis.ErrMissingField("channelTemplate.apiVersion"), + }, + { + name: "channelTemplate without kind", + ps: &ParallelSpec{ + Branches: getValidBranches(), + ChannelTemplate: invalidChannelTemplates[1], + Reply: getValidDestinationRef(), + }, + want: apis.ErrMissingField("channelTemplate.kind"), + }, + { + name: "invalid reply", + ps: &ParallelSpec{ + Branches: getValidBranches(), + ChannelTemplate: getValidChannelTemplate(), + Reply: getInvalidDestinationRef(), + }, + want: apis.ErrMissingField("reply.ref.apiVersion"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.ps.Validate(context.TODO()) + if diff := cmp.Diff(tt.want.Error(), got.Error()); diff != "" { + t.Errorf("%s: ParallelSpec.Validate (-want, +got) = %v", tt.name, diff) + } + }) + } +} diff --git a/pkg/apis/flows/v1beta1/register.go b/pkg/apis/flows/v1beta1/register.go new file mode 100644 index 00000000000..8d46f4fb470 --- /dev/null +++ b/pkg/apis/flows/v1beta1/register.go @@ -0,0 +1,55 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "knative.dev/eventing/pkg/apis/flows" + + 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: flows.GroupName, Version: "v1beta1"} + +// 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, + &Sequence{}, + &SequenceList{}, + &Parallel{}, + &ParallelList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/apis/flows/v1beta1/sequence_conversion.go b/pkg/apis/flows/v1beta1/sequence_conversion.go new file mode 100644 index 00000000000..b0009978f0e --- /dev/null +++ b/pkg/apis/flows/v1beta1/sequence_conversion.go @@ -0,0 +1,143 @@ +/* +Copyright 2020 The Knative Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "fmt" + + "knative.dev/pkg/apis" + + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + v1 "knative.dev/eventing/pkg/apis/flows/v1" + messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" +) + +// ConvertTo implements apis.Convertible +// Converts obj from v1beta1.Sequence into v1.Sequence +func (source *Sequence) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1.Sequence: + sink.ObjectMeta = source.ObjectMeta + + sink.Spec.Steps = make([]v1.SequenceStep, len(source.Spec.Steps)) + for i, s := range source.Spec.Steps { + sink.Spec.Steps[i] = v1.SequenceStep{ + Destination: s.Destination, + } + + if s.Delivery != nil { + sink.Spec.Steps[i].Delivery = &eventingduckv1.DeliverySpec{} + if err := s.Delivery.ConvertTo(ctx, sink.Spec.Steps[i].Delivery); err != nil { + return err + } + } + } + + if source.Spec.ChannelTemplate != nil { + sink.Spec.ChannelTemplate = &messagingv1.ChannelTemplateSpec{ + TypeMeta: source.Spec.ChannelTemplate.TypeMeta, + Spec: source.Spec.ChannelTemplate.Spec, + } + } + sink.Spec.Reply = source.Spec.Reply + + sink.Status.Status = source.Status.Status + + if source.Status.SubscriptionStatuses != nil { + sink.Status.SubscriptionStatuses = make([]v1.SequenceSubscriptionStatus, len(source.Status.SubscriptionStatuses)) + for i, s := range source.Status.SubscriptionStatuses { + sink.Status.SubscriptionStatuses[i] = v1.SequenceSubscriptionStatus{ + Subscription: s.Subscription, + ReadyCondition: s.ReadyCondition, + } + } + } + + if source.Status.ChannelStatuses != nil { + sink.Status.ChannelStatuses = make([]v1.SequenceChannelStatus, len(source.Status.ChannelStatuses)) + for i, s := range source.Status.ChannelStatuses { + sink.Status.ChannelStatuses[i] = v1.SequenceChannelStatus{ + Channel: s.Channel, + ReadyCondition: s.ReadyCondition, + } + } + } + + return nil + default: + return fmt.Errorf("Unknown conversion, got: %T", sink) + } +} + +// ConvertFrom implements apis.Convertible +func (sink *Sequence) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1.Sequence: + sink.ObjectMeta = source.ObjectMeta + + sink.Spec.Steps = make([]SequenceStep, len(source.Spec.Steps)) + for i, s := range source.Spec.Steps { + sink.Spec.Steps[i] = SequenceStep{ + Destination: s.Destination, + } + if s.Delivery != nil { + sink.Spec.Steps[i].Delivery = &eventingduckv1beta1.DeliverySpec{} + if err := sink.Spec.Steps[i].Delivery.ConvertFrom(ctx, s.Delivery); err != nil { + return err + } + } + } + + if source.Spec.ChannelTemplate != nil { + sink.Spec.ChannelTemplate = &messagingv1beta1.ChannelTemplateSpec{ + TypeMeta: source.Spec.ChannelTemplate.TypeMeta, + Spec: source.Spec.ChannelTemplate.Spec, + } + } + + sink.Spec.Reply = source.Spec.Reply + + sink.Status.Status = source.Status.Status + + if source.Status.SubscriptionStatuses != nil { + sink.Status.SubscriptionStatuses = make([]SequenceSubscriptionStatus, len(source.Status.SubscriptionStatuses)) + for i, s := range source.Status.SubscriptionStatuses { + sink.Status.SubscriptionStatuses[i] = SequenceSubscriptionStatus{ + Subscription: s.Subscription, + ReadyCondition: s.ReadyCondition, + } + } + } + + if source.Status.ChannelStatuses != nil { + sink.Status.ChannelStatuses = make([]SequenceChannelStatus, len(source.Status.ChannelStatuses)) + for i, s := range source.Status.ChannelStatuses { + sink.Status.ChannelStatuses[i] = SequenceChannelStatus{ + Channel: s.Channel, + ReadyCondition: s.ReadyCondition, + } + } + } + + return nil + default: + return fmt.Errorf("unknown version, got: %T", source) + } +} diff --git a/pkg/apis/flows/v1beta1/sequence_defaults.go b/pkg/apis/flows/v1beta1/sequence_defaults.go new file mode 100644 index 00000000000..58c6c3e3301 --- /dev/null +++ b/pkg/apis/flows/v1beta1/sequence_defaults.go @@ -0,0 +1,62 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + "knative.dev/eventing/pkg/apis/messaging/config" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + "knative.dev/pkg/apis" +) + +func (s *Sequence) SetDefaults(ctx context.Context) { + if s == nil { + return + } + + withNS := apis.WithinParent(ctx, s.ObjectMeta) + if s.Spec.ChannelTemplate == nil { + cfg := config.FromContextOrDefaults(ctx) + c, err := cfg.ChannelDefaults.GetChannelConfig(apis.ParentMeta(ctx).Namespace) + + if err == nil { + s.Spec.ChannelTemplate = &messagingv1beta1.ChannelTemplateSpec{ + TypeMeta: c.TypeMeta, + Spec: c.Spec, + } + } + } + s.Spec.SetDefaults(withNS) +} + +func (ss *SequenceSpec) SetDefaults(ctx context.Context) { + // Default the namespace for all the steps. + for _, s := range ss.Steps { + s.SetDefaults(ctx) + } + // Default the reply + if ss.Reply != nil { + ss.Reply.SetDefaults(ctx) + } +} + +func (ss *SequenceStep) SetDefaults(ctx context.Context) { + ss.Destination.SetDefaults(ctx) + + // No delivery defaults. +} diff --git a/pkg/apis/flows/v1beta1/sequence_defaults_test.go b/pkg/apis/flows/v1beta1/sequence_defaults_test.go new file mode 100644 index 00000000000..23e2303734a --- /dev/null +++ b/pkg/apis/flows/v1beta1/sequence_defaults_test.go @@ -0,0 +1,139 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/google/go-cmp/cmp" + "knative.dev/eventing/pkg/apis/messaging/config" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +const ( + testNS = "testnamespace" +) + +var ( + configDefaultChannelTemplate = &config.ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + APIVersion: SchemeGroupVersion.String(), + Kind: "InMemoryChannel", + }, + } +) + +func TestSequenceSetDefaults(t *testing.T) { + testCases := map[string]struct { + nilChannelDefaulter bool + channelTemplate *config.ChannelTemplateSpec + initial Sequence + expected Sequence + }{ + "nil ChannelDefaulter": { + nilChannelDefaulter: true, + expected: Sequence{}, + }, + "unset ChannelDefaulter": { + expected: Sequence{}, + }, + "set ChannelDefaulter": { + channelTemplate: configDefaultChannelTemplate, + expected: Sequence{ + Spec: SequenceSpec{ + ChannelTemplate: defaultChannelTemplate, + }, + }, + }, + "steps and reply namespace defaulted": { + channelTemplate: configDefaultChannelTemplate, + initial: Sequence{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNS}, + Spec: SequenceSpec{ + Steps: []SequenceStep{ + {Destination: duckv1.Destination{ + Ref: &duckv1.KReference{Name: "first"}}}, + {Destination: duckv1.Destination{ + Ref: &duckv1.KReference{Name: "second"}}}, + }, + Reply: &duckv1.Destination{ + Ref: &duckv1.KReference{Name: "reply"}, + }, + }, + }, + expected: Sequence{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNS}, + Spec: SequenceSpec{ + ChannelTemplate: defaultChannelTemplate, + Steps: []SequenceStep{ + {Destination: duckv1.Destination{ + Ref: &duckv1.KReference{Namespace: testNS, Name: "first"}}}, + {Destination: duckv1.Destination{ + Ref: &duckv1.KReference{Namespace: testNS, Name: "second"}}}, + }, + Reply: &duckv1.Destination{ + Ref: &duckv1.KReference{Namespace: testNS, Name: "reply"}, + }, + }, + }, + }, + "template already specified": { + channelTemplate: configDefaultChannelTemplate, + initial: Sequence{ + Spec: SequenceSpec{ + ChannelTemplate: &messagingv1beta1.ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + APIVersion: SchemeGroupVersion.String(), + Kind: "OtherChannel", + }, + }, + }, + }, + expected: Sequence{ + Spec: SequenceSpec{ + ChannelTemplate: &messagingv1beta1.ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + APIVersion: SchemeGroupVersion.String(), + Kind: "OtherChannel", + }, + }, + }, + }, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + ctx := context.Background() + if !tc.nilChannelDefaulter { + ctx = config.ToContext(ctx, &config.Config{ + ChannelDefaults: &config.ChannelDefaults{ + ClusterDefault: tc.channelTemplate, + }, + }) + } + tc.initial.SetDefaults(ctx) + if diff := cmp.Diff(tc.expected, tc.initial); diff != "" { + t.Fatal("Unexpected defaults (-want, +got):", diff) + } + }) + } +} diff --git a/pkg/apis/flows/v1beta1/sequence_lifecycle.go b/pkg/apis/flows/v1beta1/sequence_lifecycle.go new file mode 100644 index 00000000000..93576dfd283 --- /dev/null +++ b/pkg/apis/flows/v1beta1/sequence_lifecycle.go @@ -0,0 +1,174 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +var sCondSet = apis.NewLivingConditionSet(SequenceConditionReady, SequenceConditionChannelsReady, SequenceConditionSubscriptionsReady, SequenceConditionAddressable) + +const ( + // SequenceConditionReady has status True when all subconditions below have been set to True. + SequenceConditionReady = apis.ConditionReady + + // SequenceConditionChannelsReady has status True when all the channels created as part of + // this sequence are ready. + SequenceConditionChannelsReady apis.ConditionType = "ChannelsReady" + + // SequenceConditionSubscriptionsReady has status True when all the subscriptions created as part of + // this sequence are ready. + SequenceConditionSubscriptionsReady apis.ConditionType = "SubscriptionsReady" + + // SequenceConditionAddressable has status true when this Sequence meets + // the Addressable contract and has a non-empty hostname. + SequenceConditionAddressable apis.ConditionType = "Addressable" +) + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (*Sequence) GetConditionSet() apis.ConditionSet { + return sCondSet +} + +// GetGroupVersionKind returns GroupVersionKind for InMemoryChannels +func (*Sequence) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("Sequence") +} + +// GetUntypedSpec returns the spec of the Sequence. +func (s *Sequence) GetUntypedSpec() interface{} { + return s.Spec +} + +// GetCondition returns the condition currently associated with the given type, or nil. +func (ss *SequenceStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return sCondSet.Manage(ss).GetCondition(t) +} + +// IsReady returns true if the resource is ready overall. +func (ss *SequenceStatus) IsReady() bool { + return sCondSet.Manage(ss).IsHappy() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (ss *SequenceStatus) InitializeConditions() { + sCondSet.Manage(ss).InitializeConditions() +} + +// PropagateSubscriptionStatuses sets the SubscriptionStatuses and SequenceConditionSubscriptionsReady based on +// the status of the incoming subscriptions. +func (ss *SequenceStatus) PropagateSubscriptionStatuses(subscriptions []*messagingv1beta1.Subscription) { + ss.SubscriptionStatuses = make([]SequenceSubscriptionStatus, len(subscriptions)) + allReady := true + // If there are no subscriptions, treat that as a False case. Could go either way, but this seems right. + if len(subscriptions) == 0 { + allReady = false + + } + for i, s := range subscriptions { + ss.SubscriptionStatuses[i] = SequenceSubscriptionStatus{ + Subscription: corev1.ObjectReference{ + APIVersion: s.APIVersion, + Kind: s.Kind, + Name: s.Name, + Namespace: s.Namespace, + }, + } + readyCondition := s.Status.GetCondition(messagingv1beta1.SubscriptionConditionReady) + if readyCondition != nil { + ss.SubscriptionStatuses[i].ReadyCondition = *readyCondition + if readyCondition.Status != corev1.ConditionTrue { + allReady = false + } + } else { + allReady = false + } + + } + if allReady { + sCondSet.Manage(ss).MarkTrue(SequenceConditionSubscriptionsReady) + } else { + ss.MarkSubscriptionsNotReady("SubscriptionsNotReady", "Subscriptions are not ready yet, or there are none") + } +} + +// PropagateChannelStatuses sets the ChannelStatuses and SequenceConditionChannelsReady based on the +// status of the incoming channels. +func (ss *SequenceStatus) PropagateChannelStatuses(channels []*eventingduckv1beta1.Channelable) { + ss.ChannelStatuses = make([]SequenceChannelStatus, len(channels)) + allReady := true + // If there are no channels, treat that as a False case. Could go either way, but this seems right. + if len(channels) == 0 { + allReady = false + + } + for i, c := range channels { + ss.ChannelStatuses[i] = SequenceChannelStatus{ + Channel: corev1.ObjectReference{ + APIVersion: c.APIVersion, + Kind: c.Kind, + Name: c.Name, + Namespace: c.Namespace, + }, + } + // TODO: Once the addressable has a real status to dig through, use that here instead of + // addressable, because it might be addressable but not ready. + address := c.Status.AddressStatus.Address + if address != nil { + ss.ChannelStatuses[i].ReadyCondition = apis.Condition{Type: apis.ConditionReady, Status: corev1.ConditionTrue} + } else { + ss.ChannelStatuses[i].ReadyCondition = apis.Condition{Type: apis.ConditionReady, Status: corev1.ConditionFalse, Reason: "NotAddressable", Message: "Channel is not addressable"} + allReady = false + } + + // Mark the Sequence address as the Address of the first channel. + if i == 0 { + ss.setAddress(address) + } + } + if allReady { + sCondSet.Manage(ss).MarkTrue(SequenceConditionChannelsReady) + } else { + ss.MarkChannelsNotReady("ChannelsNotReady", "Channels are not ready yet, or there are none") + } +} + +func (ss *SequenceStatus) MarkChannelsNotReady(reason, messageFormat string, messageA ...interface{}) { + sCondSet.Manage(ss).MarkFalse(SequenceConditionChannelsReady, reason, messageFormat, messageA...) +} + +func (ss *SequenceStatus) MarkSubscriptionsNotReady(reason, messageFormat string, messageA ...interface{}) { + sCondSet.Manage(ss).MarkFalse(SequenceConditionSubscriptionsReady, reason, messageFormat, messageA...) +} + +func (ss *SequenceStatus) MarkAddressableNotReady(reason, messageFormat string, messageA ...interface{}) { + sCondSet.Manage(ss).MarkFalse(SequenceConditionAddressable, reason, messageFormat, messageA...) +} + +func (ss *SequenceStatus) setAddress(address *duckv1.Addressable) { + if address == nil || address.URL == nil { + sCondSet.Manage(ss).MarkFalse(SequenceConditionAddressable, "emptyAddress", "addressable is nil") + } else { + ss.AddressStatus.Address = &duckv1.Addressable{URL: address.URL} + sCondSet.Manage(ss).MarkTrue(SequenceConditionAddressable) + } +} diff --git a/pkg/apis/flows/v1beta1/sequence_lifecycle_test.go b/pkg/apis/flows/v1beta1/sequence_lifecycle_test.go new file mode 100644 index 00000000000..93a89113385 --- /dev/null +++ b/pkg/apis/flows/v1beta1/sequence_lifecycle_test.go @@ -0,0 +1,404 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "testing" + + "knative.dev/pkg/apis" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + duckv1 "knative.dev/pkg/apis/duck/v1" + pkgduckv1 "knative.dev/pkg/apis/duck/v1" + + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" +) + +var sequenceConditionReady = apis.Condition{ + Type: SequenceConditionReady, + Status: corev1.ConditionTrue, +} + +func TestSequenceGetConditionSet(t *testing.T) { + r := &Sequence{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func getSubscription(name string, ready bool) *messagingv1beta1.Subscription { + s := messagingv1beta1.Subscription{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "eventing.knative.dev/v1alpha1", + Kind: "Subscription", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "testns", + }, + Status: messagingv1beta1.SubscriptionStatus{}, + } + if ready { + s.Status.MarkChannelReady() + s.Status.MarkReferencesResolved() + s.Status.MarkAddedToChannel() + } else { + s.Status.MarkChannelFailed("testInducedFailure", "Test Induced failure") + s.Status.MarkReferencesNotResolved("testInducedFailure", "Test Induced failure") + s.Status.MarkNotAddedToChannel("testInducedfailure", "Test Induced failure") + } + return &s +} + +func getChannelable(ready bool) *eventingduckv1beta1.Channelable { + URL := apis.HTTP("example.com") + c := eventingduckv1beta1.Channelable{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "messaging.knative.dev/v1beta1", + Kind: "InMemoryChannel", + }, + ObjectMeta: metav1.ObjectMeta{}, + Status: eventingduckv1beta1.ChannelableStatus{}, + } + + if ready { + c.Status.Address = &duckv1.Addressable{URL: URL} + } + + return &c +} + +func TestSequenceGetCondition(t *testing.T) { + tests := []struct { + name string + ss *SequenceStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "single condition", + ss: &SequenceStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + sequenceConditionReady, + }, + }, + }, + condQuery: apis.ConditionReady, + want: &sequenceConditionReady, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.ss.GetCondition(test.condQuery) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Error("unexpected condition (-want, +got) =", diff) + } + }) + } +} + +func TestSequenceInitializeConditions(t *testing.T) { + tests := []struct { + name string + ts *SequenceStatus + want *SequenceStatus + }{{ + name: "empty", + ts: &SequenceStatus{}, + want: &SequenceStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: SequenceConditionAddressable, + Status: corev1.ConditionUnknown, + }, { + Type: SequenceConditionChannelsReady, + Status: corev1.ConditionUnknown, + }, { + Type: SequenceConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: SequenceConditionSubscriptionsReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, { + name: "one false", + ts: &SequenceStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: SequenceConditionChannelsReady, + Status: corev1.ConditionFalse, + }}, + }, + }, + want: &SequenceStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: SequenceConditionAddressable, + Status: corev1.ConditionUnknown, + }, { + Type: SequenceConditionChannelsReady, + Status: corev1.ConditionFalse, + }, { + Type: SequenceConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: SequenceConditionSubscriptionsReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, { + name: "one true", + ts: &SequenceStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: SequenceConditionSubscriptionsReady, + Status: corev1.ConditionTrue, + }}, + }, + }, + want: &SequenceStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: SequenceConditionAddressable, + Status: corev1.ConditionUnknown, + }, { + Type: SequenceConditionChannelsReady, + Status: corev1.ConditionUnknown, + }, { + Type: SequenceConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: SequenceConditionSubscriptionsReady, + Status: corev1.ConditionTrue, + }}, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.ts.InitializeConditions() + if diff := cmp.Diff(test.want, test.ts, ignoreAllButTypeAndStatus); diff != "" { + t.Error("unexpected conditions (-want, +got) =", diff) + } + }) + } +} + +func TestSequencePropagateSubscriptionStatuses(t *testing.T) { + tests := []struct { + name string + subs []*messagingv1beta1.Subscription + want corev1.ConditionStatus + }{{ + name: "empty", + subs: []*messagingv1beta1.Subscription{}, + want: corev1.ConditionFalse, + }, { + name: "empty status", + subs: []*messagingv1beta1.Subscription{{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "eventing.knative.dev/v1alpha1", + Kind: "Subscription", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "sub", + Namespace: "testns", + }, + Status: messagingv1beta1.SubscriptionStatus{}, + }, + }, + want: corev1.ConditionFalse, + }, { + name: "one subscription not ready", + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", false)}, + want: corev1.ConditionFalse, + }, { + name: "one subscription ready", + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true)}, + want: corev1.ConditionTrue, + }, { + name: "one subscription ready, one not", + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true), getSubscription("sub1", false)}, + want: corev1.ConditionFalse, + }, { + name: "two subscriptions ready", + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true), getSubscription("sub1", true)}, + want: corev1.ConditionTrue, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ps := SequenceStatus{} + ps.PropagateSubscriptionStatuses(test.subs) + got := ps.GetCondition(SequenceConditionSubscriptionsReady).Status + want := test.want + if want != got { + t.Errorf("unexpected conditions (-want, +got) = %v %v", want, got) + } + }) + } +} + +func TestSequencePropagateChannelStatuses(t *testing.T) { + tests := []struct { + name string + channels []*eventingduckv1beta1.Channelable + want corev1.ConditionStatus + }{{ + name: "empty", + channels: []*eventingduckv1beta1.Channelable{}, + want: corev1.ConditionFalse, + }, { + name: "one channelable not ready", + channels: []*eventingduckv1beta1.Channelable{getChannelable(false)}, + want: corev1.ConditionFalse, + }, { + name: "one channelable ready", + channels: []*eventingduckv1beta1.Channelable{getChannelable(true)}, + want: corev1.ConditionTrue, + }, { + name: "one channelable ready, one not", + channels: []*eventingduckv1beta1.Channelable{getChannelable(true), getChannelable(false)}, + want: corev1.ConditionFalse, + }, { + name: "two channelables ready", + channels: []*eventingduckv1beta1.Channelable{getChannelable(true), getChannelable(true)}, + want: corev1.ConditionTrue, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ps := SequenceStatus{} + ps.PropagateChannelStatuses(test.channels) + got := ps.GetCondition(SequenceConditionChannelsReady).Status + want := test.want + if want != got { + t.Errorf("unexpected conditions (-want, +got) = %v %v", want, got) + } + }) + } +} + +func TestSequenceReady(t *testing.T) { + tests := []struct { + name string + subs []*messagingv1beta1.Subscription + channels []*eventingduckv1beta1.Channelable + want bool + }{{ + name: "empty", + subs: []*messagingv1beta1.Subscription{}, + channels: []*eventingduckv1beta1.Channelable{}, + want: false, + }, { + name: "one channelable not ready, one subscription ready", + channels: []*eventingduckv1beta1.Channelable{getChannelable(false)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true)}, + want: false, + }, { + name: "one channelable ready, one subscription not ready", + channels: []*eventingduckv1beta1.Channelable{getChannelable(true)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", false)}, + want: false, + }, { + name: "one channelable ready, one subscription ready", + channels: []*eventingduckv1beta1.Channelable{getChannelable(true)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true)}, + want: true, + }, { + name: "one channelable ready, one not, two subscriptions ready", + channels: []*eventingduckv1beta1.Channelable{getChannelable(true), getChannelable(false)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true), getSubscription("sub1", true)}, + want: false, + }, { + name: "two channelables ready, one subscription ready, one not", + channels: []*eventingduckv1beta1.Channelable{getChannelable(true), getChannelable(true)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true), getSubscription("sub1", false)}, + want: false, + }, { + name: "two channelables ready, two subscriptions ready", + channels: []*eventingduckv1beta1.Channelable{getChannelable(true), getChannelable(true)}, + subs: []*messagingv1beta1.Subscription{getSubscription("sub0", true), getSubscription("sub1", true)}, + want: true, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ps := SequenceStatus{} + ps.PropagateChannelStatuses(test.channels) + ps.PropagateSubscriptionStatuses(test.subs) + got := ps.IsReady() + want := test.want + if want != got { + t.Errorf("unexpected conditions (-want, +got) = %v %v", want, got) + } + }) + } +} + +func TestSequencePropagateSetAddress(t *testing.T) { + URL := apis.HTTP("example.com") + tests := []struct { + name string + address *duckv1.Addressable + want *pkgduckv1.Addressable + wantStatus corev1.ConditionStatus + }{{ + name: "nil", + address: nil, + want: nil, + wantStatus: corev1.ConditionFalse, + }, { + name: "empty", + address: &duckv1.Addressable{}, + want: nil, + wantStatus: corev1.ConditionFalse, + }, { + name: "URL", + address: &duckv1.Addressable{URL: URL}, + want: &pkgduckv1.Addressable{URL: URL}, + wantStatus: corev1.ConditionTrue, + }, { + name: "nil", + address: &duckv1.Addressable{URL: nil}, + want: nil, + wantStatus: corev1.ConditionFalse, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ps := SequenceStatus{} + ps.setAddress(test.address) + got := ps.Address + if diff := cmp.Diff(test.want, got, ignoreAllButTypeAndStatus); diff != "" { + t.Error("unexpected address (-want, +got) =", diff) + } + gotStatus := ps.GetCondition(SequenceConditionAddressable).Status + if test.wantStatus != gotStatus { + t.Errorf("unexpected conditions (-want, +got) = %v %v", test.wantStatus, gotStatus) + } + }) + } +} diff --git a/pkg/apis/flows/v1beta1/sequence_types.go b/pkg/apis/flows/v1beta1/sequence_types.go new file mode 100644 index 00000000000..ddf7baa2f47 --- /dev/null +++ b/pkg/apis/flows/v1beta1/sequence_types.go @@ -0,0 +1,145 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +genreconciler +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// Sequence defines a sequence of Subscribers that will be wired in +// series through Channels and Subscriptions. +type Sequence struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of the Sequence. + Spec SequenceSpec `json:"spec,omitempty"` + + // Status represents the current state of the Sequence. This data may be out of + // date. + // +optional + Status SequenceStatus `json:"status,omitempty"` +} + +var ( + // Check that Sequence can be validated and defaulted. + _ apis.Validatable = (*Sequence)(nil) + _ apis.Defaultable = (*Sequence)(nil) + + // Check that Sequence can return its spec untyped. + _ apis.HasSpec = (*Sequence)(nil) + + // TODO: make appropriate fields immutable. + //_ apis.Immutable = (*Sequence)(nil) + + _ runtime.Object = (*Sequence)(nil) + + // Check that we can create OwnerReferences to a Sequence. + _ kmeta.OwnerRefable = (*Sequence)(nil) + + // Check that the type conforms to the duck Knative Resource shape. + _ duckv1.KRShaped = (*Sequence)(nil) +) + +type SequenceSpec struct { + // Steps is the list of Destinations (processors / functions) that will be called in the order + // provided. Each step has its own delivery options + Steps []SequenceStep `json:"steps"` + + // ChannelTemplate specifies which Channel CRD to use. If left unspecified, it is set to the default Channel CRD + // for the namespace (or cluster, in case there are no defaults for the namespace). + // +optional + ChannelTemplate *messagingv1beta1.ChannelTemplateSpec `json:"channelTemplate,omitempty"` + + // Reply is a Reference to where the result of the last Subscriber gets sent to. + // +optional + Reply *duckv1.Destination `json:"reply,omitempty"` +} + +type SequenceStep struct { + // Subscriber receiving the step event + duckv1.Destination `json:",inline"` + + // Delivery is the delivery specification for events to the subscriber + // This includes things like retries, DLQ, etc. + // +optional + Delivery *eventingduckv1beta1.DeliverySpec `json:"delivery,omitempty"` +} + +type SequenceChannelStatus struct { + // Channel is the reference to the underlying channel. + Channel corev1.ObjectReference `json:"channel"` + + // ReadyCondition indicates whether the Channel is ready or not. + ReadyCondition apis.Condition `json:"ready"` +} + +type SequenceSubscriptionStatus struct { + // Subscription is the reference to the underlying Subscription. + Subscription corev1.ObjectReference `json:"subscription"` + + // ReadyCondition indicates whether the Subscription is ready or not. + ReadyCondition apis.Condition `json:"ready"` +} + +// SequenceStatus represents the current state of a Sequence. +type SequenceStatus struct { + // inherits duck/v1 Status, 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. + duckv1.Status `json:",inline"` + + // SubscriptionStatuses is an array of corresponding Subscription statuses. + // Matches the Spec.Steps array in the order. + SubscriptionStatuses []SequenceSubscriptionStatus `json:"subscriptionStatuses"` + + // ChannelStatuses is an array of corresponding Channel statuses. + // Matches the Spec.Steps array in the order. + ChannelStatuses []SequenceChannelStatus `json:"channelStatuses"` + + // AddressStatus is the starting point to this Sequence. Sending to this + // will target the first subscriber. + // It generally has the form {channel}.{namespace}.svc.{cluster domain name} + duckv1.AddressStatus `json:",inline"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// SequenceList is a collection of Sequences. +type SequenceList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + Items []Sequence `json:"items"` +} + +// GetStatus retrieves the status of the Sequence. Implements the KRShaped interface. +func (p *Sequence) GetStatus() *duckv1.Status { + return &p.Status.Status +} diff --git a/pkg/apis/flows/v1beta1/sequence_types_test.go b/pkg/apis/flows/v1beta1/sequence_types_test.go new file mode 100644 index 00000000000..f6e5e59eb37 --- /dev/null +++ b/pkg/apis/flows/v1beta1/sequence_types_test.go @@ -0,0 +1,43 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import "testing" + +func TestSequenceGetStatus(t *testing.T) { + r := &Sequence{ + Status: SequenceStatus{}, + } + if got, want := r.GetStatus(), &r.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} + +func TestSequenceKind(t *testing.T) { + sequence := Sequence{} + if sequence.GetGroupVersionKind().String() != "flows.knative.dev/v1beta1, Kind=Sequence" { + t.Error("unexpected gvk:", sequence.GetGroupVersionKind()) + } +} + +func TestSequence_GetGroupVersionKind(t *testing.T) { + s := Sequence{} + gvk := s.GetGroupVersionKind() + if gvk.Kind != "Sequence" { + t.Errorf("Should be Sequence.") + } +} diff --git a/pkg/apis/flows/v1beta1/sequence_validation.go b/pkg/apis/flows/v1beta1/sequence_validation.go new file mode 100644 index 00000000000..6b49d0dd35b --- /dev/null +++ b/pkg/apis/flows/v1beta1/sequence_validation.go @@ -0,0 +1,68 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + "knative.dev/pkg/apis" +) + +func (p *Sequence) Validate(ctx context.Context) *apis.FieldError { + return p.Spec.Validate(ctx).ViaField("spec") +} + +func (ps *SequenceSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + + if len(ps.Steps) == 0 { + errs = errs.Also(apis.ErrMissingField("steps")) + } + + for i, s := range ps.Steps { + if e := s.Validate(ctx); e != nil { + errs = errs.Also(apis.ErrInvalidArrayValue(s, "steps", i)) + } + } + + if ps.ChannelTemplate == nil { + errs = errs.Also(apis.ErrMissingField("channelTemplate")) + } else { + if ce := messagingv1beta1.IsValidChannelTemplate(ps.ChannelTemplate); ce != nil { + errs = errs.Also(ce.ViaField("channelTemplate")) + } + } + + if err := ps.Reply.Validate(ctx); err != nil { + errs = errs.Also(err.ViaField("reply")) + } + + return errs +} + +func (ss *SequenceStep) Validate(ctx context.Context) *apis.FieldError { + errs := ss.Destination.Validate(ctx) + + if ss.Delivery != nil { + if de := ss.Delivery.Validate(ctx); de != nil { + errs = errs.Also(de.ViaField("delivery")) + } + } + + return errs +} diff --git a/pkg/apis/flows/v1beta1/sequence_validation_test.go b/pkg/apis/flows/v1beta1/sequence_validation_test.go new file mode 100644 index 00000000000..e3bcacf9104 --- /dev/null +++ b/pkg/apis/flows/v1beta1/sequence_validation_test.go @@ -0,0 +1,303 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func getValidSteps() []SequenceStep { + return []SequenceStep{ + { + Destination: getValidDestination(), + Delivery: getValidDelivery(), + }, + } +} + +func getInvalidSteps() []SequenceStep { + return []SequenceStep{ + { + Destination: getValidDestination(), + Delivery: getInvalidDelivery(), + }, + } +} + +func getValidChannelTemplate() *messagingv1beta1.ChannelTemplateSpec { + return &messagingv1beta1.ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + APIVersion: "testAPIVersion", + Kind: "testChannel", + }, + } +} + +func getValidDestination() duckv1.Destination { + return duckv1.Destination{ + Ref: &duckv1.KReference{ + Name: "testName", + Kind: "testKind", + Namespace: "testNamespace", + APIVersion: "testAPIVersion", + }, + } +} + +func getValidDestinationRef() *duckv1.Destination { + d := getValidDestination() + return &d +} + +func getInvalidDestination() duckv1.Destination { + return duckv1.Destination{ + Ref: &duckv1.KReference{ + Name: "testName", + Kind: "testKind", + Namespace: "testNamespace", + }, + } +} + +func getInvalidDestinationRef() *duckv1.Destination { + d := getInvalidDestination() + return &d +} + +func getValidDelivery() *eventingduckv1beta1.DeliverySpec { + bop := eventingduckv1beta1.BackoffPolicyExponential + return &eventingduckv1beta1.DeliverySpec{ + BackoffPolicy: &bop, + } +} + +func getInvalidDelivery() *eventingduckv1beta1.DeliverySpec { + invalidBod := "invalid delay" + return &eventingduckv1beta1.DeliverySpec{ + BackoffDelay: &invalidBod, + } +} + +func TestSequenceValidate(t *testing.T) { + tests := []struct { + name string + s *Sequence + want *apis.FieldError + }{ + { + name: "valid", + s: &Sequence{ + Spec: SequenceSpec{ + Steps: getValidSteps(), + ChannelTemplate: getValidChannelTemplate(), + Reply: getValidDestinationRef(), + }, + }, + want: nil, + }, + { + name: "invalid", + s: &Sequence{ + Spec: SequenceSpec{ + ChannelTemplate: getValidChannelTemplate(), + Reply: getValidDestinationRef(), + }, + }, + want: apis.ErrMissingField("spec.steps"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.s.Validate(context.TODO()) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Errorf("%s: Sequence.Validate (-want, +got) = %v", test.name, diff) + } + }) + } +} + +func TestSequenceSpecValidate(t *testing.T) { + invalidSteps := getInvalidSteps() + + tests := []struct { + name string + ss *SequenceSpec + want *apis.FieldError + }{ + { + name: "valid", + ss: &SequenceSpec{ + Steps: getValidSteps(), + ChannelTemplate: getValidChannelTemplate(), + Reply: getValidDestinationRef(), + }, + want: nil, + }, + { + name: "no steps", + ss: &SequenceSpec{ + ChannelTemplate: getValidChannelTemplate(), + Reply: getValidDestinationRef(), + }, + want: apis.ErrMissingField("steps"), + }, + { + name: "invalid steps", + ss: &SequenceSpec{ + Steps: invalidSteps, + ChannelTemplate: getValidChannelTemplate(), + Reply: getValidDestinationRef(), + }, + want: apis.ErrInvalidArrayValue(invalidSteps[0], "steps", 0), + }, + { + name: "no channelTemplate", + ss: &SequenceSpec{ + Steps: getValidSteps(), + Reply: getValidDestinationRef(), + }, + want: apis.ErrMissingField("channelTemplate"), + }, + { + name: "no channelTemplate apiVersion", + ss: &SequenceSpec{ + Steps: getValidSteps(), + ChannelTemplate: &messagingv1beta1.ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + Kind: "testChannel", + }, + }, + Reply: getValidDestinationRef(), + }, + want: apis.ErrMissingField("channelTemplate.apiVersion"), + }, + { + name: "no channelTemplate kind", + ss: &SequenceSpec{ + Steps: getValidSteps(), + ChannelTemplate: &messagingv1beta1.ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + APIVersion: "testAPIVersion", + }, + }, + Reply: getValidDestinationRef(), + }, + want: apis.ErrMissingField("channelTemplate.kind"), + }, + { + name: "invalid channelTemplate & invalid reply", + ss: &SequenceSpec{ + Steps: getValidSteps(), + ChannelTemplate: &messagingv1beta1.ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + Kind: "testChannel", + }, + }, + Reply: getInvalidDestinationRef(), + }, + want: apis.ErrMissingField("channelTemplate.apiVersion", "reply.ref.apiVersion"), + }, + { + name: "invalid reply", + ss: &SequenceSpec{ + Steps: getValidSteps(), + ChannelTemplate: getValidChannelTemplate(), + Reply: getInvalidDestinationRef(), + }, + want: apis.ErrMissingField("reply.ref.apiVersion"), + }, + { + name: "no channel template & invalid reply", + ss: &SequenceSpec{ + Steps: getValidSteps(), + Reply: getInvalidDestinationRef(), + }, + want: apis.ErrMissingField("channelTemplate", "reply.ref.apiVersion"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.ss.Validate(context.TODO()) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Errorf("%s: SequenceSpec.Validate (-want, +got) = %v", test.name, diff) + } + }) + } +} + +func TestSequenceStepValidate(t *testing.T) { + tests := []struct { + name string + ss *SequenceStep + want *apis.FieldError + }{ + { + name: "valid", + ss: &SequenceStep{ + Destination: getValidDestination(), + Delivery: getValidDelivery(), + }, + want: nil, + }, + { + name: "invalid destination", + ss: &SequenceStep{ + Destination: getInvalidDestination(), + Delivery: getValidDelivery(), + }, + want: apis.ErrMissingField("ref.apiVersion"), + }, + { + name: "invalid delivery", + ss: &SequenceStep{ + Destination: getValidDestination(), + Delivery: getInvalidDelivery(), + }, + want: apis.ErrInvalidValue("invalid delay", "delivery.backoffDelay"), + }, + { + name: "invalid destination & invalid delivery", + ss: &SequenceStep{ + Destination: getInvalidDestination(), + Delivery: getInvalidDelivery(), + }, + want: func() *apis.FieldError { + errs := apis.ErrMissingField("ref.apiVersion") + return errs.Also(apis.ErrInvalidValue("invalid delay", "delivery.backoffDelay")) + }(), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.ss.Validate(context.TODO()) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Errorf("%s: SequenceStep.Validate (-want, +got) = %v", test.name, diff) + } + }) + } +} diff --git a/pkg/apis/flows/v1beta1/test_helpers.go b/pkg/apis/flows/v1beta1/test_helpers.go new file mode 100644 index 00000000000..7a476f14504 --- /dev/null +++ b/pkg/apis/flows/v1beta1/test_helpers.go @@ -0,0 +1,37 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "github.com/google/go-cmp/cmp/cmpopts" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + "knative.dev/pkg/apis" +) + +var ( + defaultChannelTemplate = &messagingv1beta1.ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + APIVersion: SchemeGroupVersion.String(), + Kind: "InMemoryChannel", + }, + } + ignoreAllButTypeAndStatus = cmpopts.IgnoreFields( + apis.Condition{}, + "LastTransitionTime", "Message", "Reason", "Severity") +) diff --git a/pkg/apis/flows/v1beta1/zz_generated.deepcopy.go b/pkg/apis/flows/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..35693be21c8 --- /dev/null +++ b/pkg/apis/flows/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,419 @@ +// +build !ignore_autogenerated + +/* +Copyright 2021 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 v1beta1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" + duckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + v1 "knative.dev/pkg/apis/duck/v1" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Parallel) DeepCopyInto(out *Parallel) { + *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 Parallel. +func (in *Parallel) DeepCopy() *Parallel { + if in == nil { + return nil + } + out := new(Parallel) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Parallel) 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 *ParallelBranch) DeepCopyInto(out *ParallelBranch) { + *out = *in + if in.Filter != nil { + in, out := &in.Filter, &out.Filter + *out = new(v1.Destination) + (*in).DeepCopyInto(*out) + } + in.Subscriber.DeepCopyInto(&out.Subscriber) + if in.Reply != nil { + in, out := &in.Reply, &out.Reply + *out = new(v1.Destination) + (*in).DeepCopyInto(*out) + } + if in.Delivery != nil { + in, out := &in.Delivery, &out.Delivery + *out = new(duckv1beta1.DeliverySpec) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParallelBranch. +func (in *ParallelBranch) DeepCopy() *ParallelBranch { + if in == nil { + return nil + } + out := new(ParallelBranch) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ParallelBranchStatus) DeepCopyInto(out *ParallelBranchStatus) { + *out = *in + in.FilterSubscriptionStatus.DeepCopyInto(&out.FilterSubscriptionStatus) + in.FilterChannelStatus.DeepCopyInto(&out.FilterChannelStatus) + in.SubscriptionStatus.DeepCopyInto(&out.SubscriptionStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParallelBranchStatus. +func (in *ParallelBranchStatus) DeepCopy() *ParallelBranchStatus { + if in == nil { + return nil + } + out := new(ParallelBranchStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ParallelChannelStatus) DeepCopyInto(out *ParallelChannelStatus) { + *out = *in + out.Channel = in.Channel + in.ReadyCondition.DeepCopyInto(&out.ReadyCondition) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParallelChannelStatus. +func (in *ParallelChannelStatus) DeepCopy() *ParallelChannelStatus { + if in == nil { + return nil + } + out := new(ParallelChannelStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ParallelList) DeepCopyInto(out *ParallelList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Parallel, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParallelList. +func (in *ParallelList) DeepCopy() *ParallelList { + if in == nil { + return nil + } + out := new(ParallelList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ParallelList) 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 *ParallelSpec) DeepCopyInto(out *ParallelSpec) { + *out = *in + if in.Branches != nil { + in, out := &in.Branches, &out.Branches + *out = make([]ParallelBranch, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ChannelTemplate != nil { + in, out := &in.ChannelTemplate, &out.ChannelTemplate + *out = new(messagingv1beta1.ChannelTemplateSpec) + (*in).DeepCopyInto(*out) + } + if in.Reply != nil { + in, out := &in.Reply, &out.Reply + *out = new(v1.Destination) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParallelSpec. +func (in *ParallelSpec) DeepCopy() *ParallelSpec { + if in == nil { + return nil + } + out := new(ParallelSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ParallelStatus) DeepCopyInto(out *ParallelStatus) { + *out = *in + in.Status.DeepCopyInto(&out.Status) + in.IngressChannelStatus.DeepCopyInto(&out.IngressChannelStatus) + if in.BranchStatuses != nil { + in, out := &in.BranchStatuses, &out.BranchStatuses + *out = make([]ParallelBranchStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.AddressStatus.DeepCopyInto(&out.AddressStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParallelStatus. +func (in *ParallelStatus) DeepCopy() *ParallelStatus { + if in == nil { + return nil + } + out := new(ParallelStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ParallelSubscriptionStatus) DeepCopyInto(out *ParallelSubscriptionStatus) { + *out = *in + out.Subscription = in.Subscription + in.ReadyCondition.DeepCopyInto(&out.ReadyCondition) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParallelSubscriptionStatus. +func (in *ParallelSubscriptionStatus) DeepCopy() *ParallelSubscriptionStatus { + if in == nil { + return nil + } + out := new(ParallelSubscriptionStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Sequence) DeepCopyInto(out *Sequence) { + *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 Sequence. +func (in *Sequence) DeepCopy() *Sequence { + if in == nil { + return nil + } + out := new(Sequence) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Sequence) 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 *SequenceChannelStatus) DeepCopyInto(out *SequenceChannelStatus) { + *out = *in + out.Channel = in.Channel + in.ReadyCondition.DeepCopyInto(&out.ReadyCondition) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SequenceChannelStatus. +func (in *SequenceChannelStatus) DeepCopy() *SequenceChannelStatus { + if in == nil { + return nil + } + out := new(SequenceChannelStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SequenceList) DeepCopyInto(out *SequenceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Sequence, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SequenceList. +func (in *SequenceList) DeepCopy() *SequenceList { + if in == nil { + return nil + } + out := new(SequenceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SequenceList) 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 *SequenceSpec) DeepCopyInto(out *SequenceSpec) { + *out = *in + if in.Steps != nil { + in, out := &in.Steps, &out.Steps + *out = make([]SequenceStep, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ChannelTemplate != nil { + in, out := &in.ChannelTemplate, &out.ChannelTemplate + *out = new(messagingv1beta1.ChannelTemplateSpec) + (*in).DeepCopyInto(*out) + } + if in.Reply != nil { + in, out := &in.Reply, &out.Reply + *out = new(v1.Destination) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SequenceSpec. +func (in *SequenceSpec) DeepCopy() *SequenceSpec { + if in == nil { + return nil + } + out := new(SequenceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SequenceStatus) DeepCopyInto(out *SequenceStatus) { + *out = *in + in.Status.DeepCopyInto(&out.Status) + if in.SubscriptionStatuses != nil { + in, out := &in.SubscriptionStatuses, &out.SubscriptionStatuses + *out = make([]SequenceSubscriptionStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ChannelStatuses != nil { + in, out := &in.ChannelStatuses, &out.ChannelStatuses + *out = make([]SequenceChannelStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.AddressStatus.DeepCopyInto(&out.AddressStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SequenceStatus. +func (in *SequenceStatus) DeepCopy() *SequenceStatus { + if in == nil { + return nil + } + out := new(SequenceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SequenceStep) DeepCopyInto(out *SequenceStep) { + *out = *in + in.Destination.DeepCopyInto(&out.Destination) + if in.Delivery != nil { + in, out := &in.Delivery, &out.Delivery + *out = new(duckv1beta1.DeliverySpec) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SequenceStep. +func (in *SequenceStep) DeepCopy() *SequenceStep { + if in == nil { + return nil + } + out := new(SequenceStep) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SequenceSubscriptionStatus) DeepCopyInto(out *SequenceSubscriptionStatus) { + *out = *in + out.Subscription = in.Subscription + in.ReadyCondition.DeepCopyInto(&out.ReadyCondition) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SequenceSubscriptionStatus. +func (in *SequenceSubscriptionStatus) DeepCopy() *SequenceSubscriptionStatus { + if in == nil { + return nil + } + out := new(SequenceSubscriptionStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/messaging/v1beta1/channel_conversion.go b/pkg/apis/messaging/v1beta1/channel_conversion.go new file mode 100644 index 00000000000..cfa050fdd91 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/channel_conversion.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 v1beta1 + +import ( + "context" + "fmt" + + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/eventing/pkg/apis/messaging" + v1 "knative.dev/eventing/pkg/apis/messaging/v1" + "knative.dev/pkg/apis" + "knative.dev/pkg/kmeta" +) + +// ConvertTo implements apis.Convertible +// Converts source (from v1beta1.Channel) into v1.Channel +func (source *Channel) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1.Channel: + sink.ObjectMeta = source.ObjectMeta + + // Does a deep copy, adds our duck version. + sink.Annotations = kmeta.UnionMaps(source.Annotations, map[string]string{messaging.SubscribableDuckVersionAnnotation: "v1"}) + source.Status.ConvertTo(ctx, &sink.Status) + return source.Spec.ConvertTo(ctx, &sink.Spec) + default: + return fmt.Errorf("unknown version, got: %T", sink) + } +} + +// ConvertTo helps implement apis.Convertible +func (source *ChannelSpec) ConvertTo(ctx context.Context, sink *v1.ChannelSpec) error { + if source.ChannelTemplate != nil { + sink.ChannelTemplate = &v1.ChannelTemplateSpec{ + TypeMeta: source.ChannelTemplate.TypeMeta, + Spec: source.ChannelTemplate.Spec, + } + } + sink.ChannelableSpec = eventingduckv1.ChannelableSpec{} + source.SubscribableSpec.ConvertTo(ctx, &sink.SubscribableSpec) + if source.Delivery != nil { + sink.Delivery = &eventingduckv1.DeliverySpec{} + return source.Delivery.ConvertTo(ctx, sink.Delivery) + } + return nil +} + +// ConvertTo helps implement apis.Convertible +func (source *ChannelStatus) ConvertTo(ctx context.Context, sink *v1.ChannelStatus) { + sink.Status = source.Status + sink.AddressStatus.Address = source.AddressStatus.Address + source.SubscribableStatus.ConvertTo(ctx, &sink.SubscribableStatus) + sink.Channel = source.Channel + sink.DeadLetterChannel = source.DeadLetterChannel +} + +// ConvertFrom implements apis.Convertible. +// Converts obj v1.Channel into v1beta1.Channel +func (sink *Channel) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1.Channel: + sink.ObjectMeta = source.ObjectMeta + sink.Status.ConvertFrom(ctx, source.Status) + sink.Spec.ConvertFrom(ctx, source.Spec) + // Does a deep copy, adds our duck version. + sink.Annotations = kmeta.UnionMaps(source.Annotations, map[string]string{messaging.SubscribableDuckVersionAnnotation: "v1beta1"}) + + return nil + default: + return fmt.Errorf("unknown version, got: %T", source) + } +} + +// ConvertFrom helps implement apis.Convertible +func (sink *ChannelSpec) ConvertFrom(ctx context.Context, source v1.ChannelSpec) { + if source.ChannelTemplate != nil { + sink.ChannelTemplate = &ChannelTemplateSpec{ + TypeMeta: source.ChannelTemplate.TypeMeta, + Spec: source.ChannelTemplate.Spec, + } + } + if source.Delivery != nil { + sink.Delivery = &v1beta1.DeliverySpec{} + sink.Delivery.ConvertFrom(ctx, source.Delivery) + } + sink.ChannelableSpec.SubscribableSpec.ConvertFrom(ctx, &source.ChannelableSpec.SubscribableSpec) +} + +// ConvertFrom helps implement apis.Convertible +func (sink *ChannelStatus) ConvertFrom(ctx context.Context, source v1.ChannelStatus) { + sink.Status = source.Status + sink.Channel = source.Channel + sink.SubscribableStatus.ConvertFrom(ctx, &source.SubscribableStatus) + sink.AddressStatus.Address = source.AddressStatus.Address + sink.DeadLetterChannel = source.DeadLetterChannel +} diff --git a/pkg/apis/messaging/v1beta1/channel_conversion_test.go b/pkg/apis/messaging/v1beta1/channel_conversion_test.go new file mode 100644 index 00000000000..6c2038c9d45 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/channel_conversion_test.go @@ -0,0 +1,315 @@ +/* +Copyright 2020 The Knative Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + "knative.dev/eventing/pkg/apis/duck/v1beta1" + eventingduck "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/eventing/pkg/apis/messaging" + v1 "knative.dev/eventing/pkg/apis/messaging/v1" + + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestChannelConversionBadType(t *testing.T) { + good, bad := &Channel{}, &Subscription{} + + 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) + } +} + +// Test v1beta1 -> v1 -> v1beta1 +func TestChannelConversion(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&v1.Channel{}} + + linear := eventingduck.BackoffPolicyLinear + + tests := []struct { + name string + in *Channel + }{{ + name: "min configuration", + in: &Channel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: ChannelSpec{}, + Status: ChannelStatus{ + ChannelableStatus: v1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{}, + }, + }, + }, + }, + }, { + name: "full configuration", + in: &Channel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: ChannelSpec{ + ChannelTemplate: &ChannelTemplateSpec{ + TypeMeta: metav1.TypeMeta{ + Kind: "channelKind", + APIVersion: "channelAPIVersion", + }, + // TODO: Add Spec... + }, + ChannelableSpec: v1beta1.ChannelableSpec{ + SubscribableSpec: v1beta1.SubscribableSpec{ + Subscribers: []v1beta1.SubscriberSpec{ + { + UID: "uid-1", + Generation: 7, + SubscriberURI: apis.HTTP("subscriber.example.com"), + ReplyURI: apis.HTTP("reply.example.com"), + Delivery: &v1beta1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("subscriber.dls.example.com"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + }, + }, + }, + Status: ChannelStatus{ + ChannelableStatus: eventingduck.ChannelableStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: apis.HTTP("addressstatus.example.com"), + }, + }, + SubscribableStatus: eventingduck.SubscribableStatus{ + Subscribers: []eventingduck.SubscriberStatus{ + { + UID: "status-uid-1", + ObservedGeneration: 99, + Ready: corev1.ConditionTrue, + Message: "msg", + }, + }, + }, + }, + Channel: &duckv1.KReference{ + Kind: "u-channel-kind", + APIVersion: "u-channel-apiversion", + Name: "u-channel-name", + Namespace: "u-channel-namespace", + }, + }, + }, + }} + 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 := &Channel{} + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Error("ConvertFrom() =", err) + } + // Make sure the annotation specifies the correct duck. + if test.in.Annotations == nil { + test.in.Annotations = make(map[string]string) + } + test.in.Annotations[messaging.SubscribableDuckVersionAnnotation] = "v1beta1" + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} + +// Test v1 -> v1beta1 -> v1 +func TestChannelConversionWithV1(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&Channel{}} + + linear := eventingduckv1.BackoffPolicyLinear + + tests := []struct { + name string + in *v1.Channel + }{{ + name: "min", + in: &v1.Channel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: v1.ChannelSpec{}, + Status: v1.ChannelStatus{ + ChannelableStatus: eventingduckv1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{}, + }, + }, + }, + }, + }, { + name: "full configuration", + in: &v1.Channel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: v1.ChannelSpec{ + ChannelTemplate: &v1.ChannelTemplateSpec{ + TypeMeta: metav1.TypeMeta{ + Kind: "channelKind", + APIVersion: "channelAPIVersion", + }, + // TODO: Add Spec... + }, + ChannelableSpec: eventingduckv1.ChannelableSpec{ + SubscribableSpec: eventingduckv1.SubscribableSpec{ + Subscribers: []eventingduckv1.SubscriberSpec{ + { + UID: "uid-1", + Generation: 7, + SubscriberURI: apis.HTTP("subscriber.example.com"), + ReplyURI: apis.HTTP("reply.example.com"), + // DeadLetterSinkURI: apis.HTTP("dlc.reply.example.com"), + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("subscriber.dls.example.com"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + }, + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("dls"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + Status: v1.ChannelStatus{ + ChannelableStatus: eventingduckv1.ChannelableStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: apis.HTTP("addressstatus.example.com"), + }, + }, + SubscribableStatus: eventingduckv1.SubscribableStatus{ + Subscribers: []eventingduckv1.SubscriberStatus{ + { + UID: "status-uid-1", + ObservedGeneration: 99, + Ready: corev1.ConditionTrue, + Message: "msg", + }, + }, + }, + }, + Channel: &duckv1.KReference{ + Kind: "u-channel-kind", + APIVersion: "u-channel-apiversion", + Name: "u-channel-name", + Namespace: "u-channel-namespace", + }, + }, + }, + }} + for _, test := range tests { + for _, version := range versions { + t.Run(test.name, func(t *testing.T) { + ver := version + if err := version.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertTo() =", err) + } + got := &v1.Channel{} + if err := ver.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertFrom() =", err) + } + // Make sure the annotation specifies the correct duck. + if test.in.Annotations == nil { + test.in.Annotations = make(map[string]string) + } + test.in.Annotations[messaging.SubscribableDuckVersionAnnotation] = "v1" + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} diff --git a/pkg/apis/messaging/v1beta1/channel_defaults.go b/pkg/apis/messaging/v1beta1/channel_defaults.go new file mode 100644 index 00000000000..83ac2336661 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/channel_defaults.go @@ -0,0 +1,58 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + "knative.dev/eventing/pkg/apis/messaging" + "knative.dev/eventing/pkg/apis/messaging/config" + "knative.dev/pkg/apis" +) + +func (c *Channel) SetDefaults(ctx context.Context) { + if c.Annotations == nil { + c.Annotations = make(map[string]string) + } + if _, ok := c.Annotations[messaging.SubscribableDuckVersionAnnotation]; !ok { + c.Annotations[messaging.SubscribableDuckVersionAnnotation] = "v1beta1" + } + + c.Spec.SetDefaults(apis.WithinParent(ctx, c.ObjectMeta)) +} + +func (cs *ChannelSpec) SetDefaults(ctx context.Context) { + if cs.ChannelTemplate != nil { + return + } + + cfg := config.FromContextOrDefaults(ctx) + c, err := cfg.ChannelDefaults.GetChannelConfig(apis.ParentMeta(ctx).Namespace) + if err == nil { + cs.ChannelTemplate = &ChannelTemplateSpec{ + c.TypeMeta, + c.Spec, + } + } +} + +// ChannelDefaulter sets the default Channel CRD and Arguments on Channels that do not +// specify any implementation. +type ChannelDefaulter interface { + // GetDefault determines the default Channel CRD for the given namespace. + GetDefault(namespace string) *ChannelTemplateSpec +} diff --git a/pkg/apis/messaging/v1beta1/channel_defaults_test.go b/pkg/apis/messaging/v1beta1/channel_defaults_test.go new file mode 100644 index 00000000000..f4854151b14 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/channel_defaults_test.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 v1beta1 + +import ( + "context" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/eventing/pkg/apis/messaging/config" + + "github.com/google/go-cmp/cmp" +) + +var ( + defaultChannelTemplate = &config.ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + APIVersion: SchemeGroupVersion.String(), + Kind: "InMemoryChannel", + }, + } + ourDefaultChannelTemplate = &ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + APIVersion: SchemeGroupVersion.String(), + Kind: "InMemoryChannel", + }, + } +) + +func TestChannelSetDefaults(t *testing.T) { + testCases := map[string]struct { + nilChannelDefaulter bool + channelTemplate *config.ChannelTemplateSpec + initial Channel + expected Channel + }{ + "nil ChannelDefaulter": { + nilChannelDefaulter: true, + expected: Channel{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"messaging.knative.dev/subscribable": "v1beta1"}}}, + }, + "unset ChannelDefaulter": { + expected: Channel{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"messaging.knative.dev/subscribable": "v1beta1"}}}, + }, + "set ChannelDefaulter": { + channelTemplate: defaultChannelTemplate, + expected: Channel{ + ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"messaging.knative.dev/subscribable": "v1beta1"}}, + Spec: ChannelSpec{ + ChannelTemplate: ourDefaultChannelTemplate, + }, + }, + }, + "template already specified": { + channelTemplate: defaultChannelTemplate, + initial: Channel{ + Spec: ChannelSpec{ + ChannelTemplate: &ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + APIVersion: SchemeGroupVersion.String(), + Kind: "OtherChannel", + }, + }, + }, + }, + expected: Channel{ + ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"messaging.knative.dev/subscribable": "v1beta1"}}, + Spec: ChannelSpec{ + ChannelTemplate: &ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + APIVersion: SchemeGroupVersion.String(), + Kind: "OtherChannel", + }, + }, + }, + }, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + ctx := context.Background() + if !tc.nilChannelDefaulter { + ctx = config.ToContext(ctx, &config.Config{ + ChannelDefaults: &config.ChannelDefaults{ + ClusterDefault: tc.channelTemplate, + }, + }) + } + tc.initial.SetDefaults(ctx) + if diff := cmp.Diff(tc.expected, tc.initial); diff != "" { + t.Fatal("Unexpected defaults (-want, +got):", diff) + } + }) + } +} diff --git a/pkg/apis/messaging/v1beta1/channel_lifecycle.go b/pkg/apis/messaging/v1beta1/channel_lifecycle.go new file mode 100644 index 00000000000..371b4bad6bd --- /dev/null +++ b/pkg/apis/messaging/v1beta1/channel_lifecycle.go @@ -0,0 +1,124 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + eventingduck "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +var chCondSet = apis.NewLivingConditionSet(ChannelConditionBackingChannelReady, ChannelConditionAddressable) + +const ( + // ChannelConditionReady has status True when all subconditions below have been set to True. + ChannelConditionReady = apis.ConditionReady + + // ChannelConditionBackingChannelReady has status True when the backing Channel CRD is ready. + ChannelConditionBackingChannelReady apis.ConditionType = "BackingChannelReady" + + // ChannelConditionAddressable has status true when this Channel meets + // the Addressable contract and has a non-empty hostname. + ChannelConditionAddressable apis.ConditionType = "Addressable" +) + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (*Channel) GetConditionSet() apis.ConditionSet { + return chCondSet +} + +// GetGroupVersionKind returns GroupVersionKind for Channels. +func (*Channel) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("Channel") +} + +// GetUntypedSpec returns the spec of the Channel. +func (c *Channel) GetUntypedSpec() interface{} { + return c.Spec +} + +// GetCondition returns the condition currently associated with the given type, or nil. +func (cs *ChannelStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return chCondSet.Manage(cs).GetCondition(t) +} + +// GetTopLevelCondition returns the top level Condition. +func (cs *ChannelStatus) GetTopLevelCondition() *apis.Condition { + return chCondSet.Manage(cs).GetTopLevelCondition() +} + +// IsReady returns true if the resource is ready overall. +func (cs *ChannelStatus) IsReady() bool { + return chCondSet.Manage(cs).IsHappy() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (cs *ChannelStatus) InitializeConditions() { + chCondSet.Manage(cs).InitializeConditions() +} + +func (cs *ChannelStatus) SetAddress(address *duckv1.Addressable) { + cs.Address = address + if address == nil || address.URL.IsEmpty() { + chCondSet.Manage(cs).MarkFalse(ChannelConditionAddressable, "EmptyHostname", "hostname is the empty string") + } else { + chCondSet.Manage(cs).MarkTrue(ChannelConditionAddressable) + + } +} + +func (cs *ChannelStatus) MarkBackingChannelFailed(reason, messageFormat string, messageA ...interface{}) { + chCondSet.Manage(cs).MarkFalse(ChannelConditionBackingChannelReady, reason, messageFormat, messageA...) +} + +func (cs *ChannelStatus) MarkBackingChannelUnknown(reason, messageFormat string, messageA ...interface{}) { + chCondSet.Manage(cs).MarkUnknown(ChannelConditionBackingChannelReady, reason, messageFormat, messageA...) +} + +func (cs *ChannelStatus) MarkBackingChannelNotConfigured() { + chCondSet.Manage(cs).MarkUnknown(ChannelConditionBackingChannelReady, + "BackingChannelNotConfigured", "BackingChannel has not yet been reconciled.") +} + +func (cs *ChannelStatus) MarkBackingChannelReady() { + chCondSet.Manage(cs).MarkTrue(ChannelConditionBackingChannelReady) +} + +func (cs *ChannelStatus) PropagateStatuses(chs *eventingduck.ChannelableStatus) { + // TODO: Once you can get a Ready status from Channelable in a generic way, use it here. + readyCondition := chs.Status.GetCondition(apis.ConditionReady) + if readyCondition == nil { + cs.MarkBackingChannelNotConfigured() + } else { + switch { + case readyCondition.Status == corev1.ConditionUnknown: + cs.MarkBackingChannelUnknown(readyCondition.Reason, readyCondition.Message) + case readyCondition.Status == corev1.ConditionTrue: + cs.MarkBackingChannelReady() + case readyCondition.Status == corev1.ConditionFalse: + cs.MarkBackingChannelFailed(readyCondition.Reason, readyCondition.Message) + default: + cs.MarkBackingChannelUnknown("BackingChannelUnknown", "The status of BackingChannel is invalid: %v", readyCondition.Status) + } + } + // Set the address and update the Addressable conditions. + cs.SetAddress(chs.AddressStatus.Address) + // Set the subscribable status. + cs.SubscribableStatus = chs.SubscribableStatus +} diff --git a/pkg/apis/messaging/v1beta1/channel_lifecycle_test.go b/pkg/apis/messaging/v1beta1/channel_lifecycle_test.go new file mode 100644 index 00000000000..d752f615365 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/channel_lifecycle_test.go @@ -0,0 +1,413 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + corev1 "k8s.io/api/core/v1" + "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +var ( + validAddress = &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: "test-domain", + }, + } +) + +func TestChannelGetConditionSet(t *testing.T) { + r := &Channel{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestChannelGetCondition(t *testing.T) { + tests := []struct { + name string + cs *ChannelStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "single condition", + cs: &ChannelStatus{ + ChannelableStatus: v1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + condReady, + }, + }, + }, + }, + condQuery: apis.ConditionReady, + want: &condReady, + }, { + name: "unknown condition", + cs: &ChannelStatus{ + ChannelableStatus: v1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + condReady, + }, + }, + }, + }, + condQuery: apis.ConditionType("foo"), + want: nil, + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.cs.GetCondition(test.condQuery) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Error("unexpected condition (-want, +got) =", diff) + } + }) + } +} + +func TestChannelInitializeConditions(t *testing.T) { + tests := []struct { + name string + cs *ChannelStatus + want *ChannelStatus + }{{ + name: "empty", + cs: &ChannelStatus{}, + want: &ChannelStatus{ + ChannelableStatus: v1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: ChannelConditionAddressable, + Status: corev1.ConditionUnknown, + }, { + Type: ChannelConditionBackingChannelReady, + Status: corev1.ConditionUnknown, + }, { + Type: ChannelConditionReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, + }, { + name: "one false", + cs: &ChannelStatus{ + ChannelableStatus: v1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: ChannelConditionAddressable, + Status: corev1.ConditionFalse, + }}, + }, + }, + }, + want: &ChannelStatus{ + ChannelableStatus: v1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: ChannelConditionAddressable, + Status: corev1.ConditionFalse, + }, { + Type: ChannelConditionBackingChannelReady, + Status: corev1.ConditionUnknown, + }, { + Type: ChannelConditionReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, + }, { + name: "one true", + cs: &ChannelStatus{ + ChannelableStatus: v1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: ChannelConditionBackingChannelReady, + Status: corev1.ConditionTrue, + }}, + }, + }, + }, + want: &ChannelStatus{ + ChannelableStatus: v1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: ChannelConditionAddressable, + Status: corev1.ConditionUnknown, + }, { + Type: ChannelConditionBackingChannelReady, + Status: corev1.ConditionTrue, + }, { + Type: ChannelConditionReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.cs.InitializeConditions() + ignore := cmpopts.IgnoreFields( + apis.Condition{}, + "LastTransitionTime", "Message", "Reason", "Severity") + if diff := cmp.Diff(test.want, test.cs, ignore); diff != "" { + t.Error("unexpected conditions (-want, +got) =", diff) + } + }) + } +} + +func TestChannelConditionStatus(t *testing.T) { + tests := []struct { + name string + address *duckv1.Addressable + backingChannelStatus corev1.ConditionStatus + wantConditionStatus corev1.ConditionStatus + }{{ + name: "all happy", + address: validAddress, + backingChannelStatus: corev1.ConditionTrue, + wantConditionStatus: corev1.ConditionTrue, + }, { + name: "address not set", + address: &duckv1.Addressable{}, + backingChannelStatus: corev1.ConditionTrue, + wantConditionStatus: corev1.ConditionFalse, + }, + { + name: "nil address", + address: nil, + backingChannelStatus: corev1.ConditionTrue, + wantConditionStatus: corev1.ConditionFalse, + }, { + name: "backing channel with unknown status", + address: validAddress, + backingChannelStatus: corev1.ConditionUnknown, + wantConditionStatus: corev1.ConditionUnknown, + }, { + name: "backing channel with false status", + address: validAddress, + backingChannelStatus: corev1.ConditionFalse, + wantConditionStatus: corev1.ConditionFalse, + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cs := &ChannelStatus{} + cs.InitializeConditions() + cs.SetAddress(test.address) + if test.backingChannelStatus == corev1.ConditionTrue { + cs.MarkBackingChannelReady() + } else if test.backingChannelStatus == corev1.ConditionFalse { + cs.MarkBackingChannelFailed("ChannelFailure", "testing") + } else { + cs.MarkBackingChannelUnknown("ChannelUnknown", "testing") + } + got := cs.GetTopLevelCondition().Status + if test.wantConditionStatus != got { + t.Errorf("unexpected readiness: want %v, got %v", test.wantConditionStatus, got) + } + }) + } +} + +func TestChannelSetAddressable(t *testing.T) { + testCases := map[string]struct { + address *duckv1.Addressable + want *ChannelStatus + }{ + "nil url": { + want: &ChannelStatus{ + ChannelableStatus: v1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + { + Type: ChannelConditionAddressable, + Status: corev1.ConditionFalse, + }, + // Note that Ready is here because when the condition is marked False, duck + // automatically sets Ready to false. + { + Type: ChannelConditionReady, + Status: corev1.ConditionFalse, + }, + }, + }, + AddressStatus: duckv1.AddressStatus{}, + }, + }, + }, + "has domain": { + address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: "test-domain", + }, + }, + want: &ChannelStatus{ + ChannelableStatus: v1beta1.ChannelableStatus{ + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: "test-domain", + }, + }, + }, + Status: duckv1.Status{ + Conditions: []apis.Condition{ + { + Type: ChannelConditionAddressable, + Status: corev1.ConditionTrue, + }, { + // Note: Ready is here because when the condition + // is marked True, duck automatically sets Ready to + // Unknown because of missing ChannelConditionBackingChannelReady. + Type: ChannelConditionReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + cs := &ChannelStatus{} + cs.SetAddress(tc.address) + ignore := cmpopts.IgnoreFields( + apis.Condition{}, + "LastTransitionTime", "Message", "Reason", "Severity") + if diff := cmp.Diff(tc.want, cs, ignore); diff != "" { + t.Error("unexpected conditions (-want, +got) =", diff) + } + }) + } +} + +func TestChannelPropagateStatuses(t *testing.T) { + testCases := map[string]struct { + channelableStatus *v1beta1.ChannelableStatus + wantConditionStatus corev1.ConditionStatus + }{ + "address set": { + channelableStatus: &v1beta1.ChannelableStatus{ + AddressStatus: duckv1.AddressStatus{ + Address: validAddress, + }, + }, + wantConditionStatus: corev1.ConditionUnknown, + }, + "address not set": { + channelableStatus: &v1beta1.ChannelableStatus{ + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{}, + }, + }, + wantConditionStatus: corev1.ConditionFalse, + }, + "url nil": { + channelableStatus: &v1beta1.ChannelableStatus{ + AddressStatus: duckv1.AddressStatus{ + Address: nil, + }, + }, + wantConditionStatus: corev1.ConditionFalse, + }, + "all set": { + channelableStatus: &v1beta1.ChannelableStatus{ + AddressStatus: duckv1.AddressStatus{ + Address: validAddress, + }, + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: apis.ConditionReady, + Status: corev1.ConditionTrue, + }}, + }, + }, + wantConditionStatus: corev1.ConditionTrue, + }, + "backing channel with unknown status": { + channelableStatus: &v1beta1.ChannelableStatus{ + AddressStatus: duckv1.AddressStatus{ + Address: validAddress, + }, + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: apis.ConditionReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + wantConditionStatus: corev1.ConditionUnknown, + }, + "no condition ready in backing channel": { + channelableStatus: &v1beta1.ChannelableStatus{ + AddressStatus: duckv1.AddressStatus{ + Address: validAddress, + }, + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionTrue, + }}, + }, + }, + wantConditionStatus: corev1.ConditionUnknown, + }, + "test subscribableTypeStatus is set": { + channelableStatus: &v1beta1.ChannelableStatus{ + SubscribableStatus: v1beta1.SubscribableStatus{ + // Populate ALL fields + Subscribers: []v1beta1.SubscriberStatus{{ + UID: "2f9b5e8e-deb6-11e8-9f32-f2801f1b9fd1", + ObservedGeneration: 1, + Ready: corev1.ConditionTrue, + Message: "Some message", + }, { + UID: "34c5aec8-deb6-11e8-9f32-f2801f1b9fd1", + ObservedGeneration: 2, + Ready: corev1.ConditionFalse, + Message: "Some message", + }}, + }, + }, + wantConditionStatus: corev1.ConditionFalse, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + cs := &ChannelStatus{} + cs.PropagateStatuses(tc.channelableStatus) + got := cs.GetTopLevelCondition().Status + if tc.wantConditionStatus != got { + t.Errorf("unexpected readiness: want %v, got %v", tc.wantConditionStatus, got) + } + }) + } +} diff --git a/pkg/apis/messaging/v1beta1/channel_template_types.go b/pkg/apis/messaging/v1beta1/channel_template_types.go new file mode 100644 index 00000000000..699fc40c20f --- /dev/null +++ b/pkg/apis/messaging/v1beta1/channel_template_types.go @@ -0,0 +1,32 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ChannelTemplateSpec struct { + metav1.TypeMeta `json:",inline"` + + // Spec defines the Spec to use for each channel created. Passed + // in verbatim to the Channel CRD as Spec section. + // +optional + Spec *runtime.RawExtension `json:"spec,omitempty"` +} diff --git a/pkg/apis/messaging/v1beta1/channel_types.go b/pkg/apis/messaging/v1beta1/channel_types.go new file mode 100644 index 00000000000..c6c07eee6e4 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/channel_types.go @@ -0,0 +1,97 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + eventingduck "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +genreconciler +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Channel represents a generic Channel. It is normally used when we want a Channel, but don't need a specific Channel implementation. +type Channel struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of the Channel. + Spec ChannelSpec `json:"spec,omitempty"` + + // Status represents the current state of the Channel. This data may be out of + // date. + // +optional + Status ChannelStatus `json:"status,omitempty"` +} + +var ( + // Check that Channel can be validated and defaulted. + _ apis.Validatable = (*Channel)(nil) + _ apis.Defaultable = (*Channel)(nil) + + // Check that Channel can return its spec untyped. + _ apis.HasSpec = (*Channel)(nil) + + _ runtime.Object = (*Channel)(nil) + + // Check that we can create OwnerReferences to a Channel. + _ kmeta.OwnerRefable = (*Channel)(nil) + + // Check that the type conforms to the duck Knative Resource shape. + _ duckv1.KRShaped = (*Channel)(nil) +) + +// ChannelSpec defines which subscribers have expressed interest in receiving events from this Channel. +// It also defines the ChannelTemplate to use in order to create the CRD Channel backing this Channel. +type ChannelSpec struct { + // ChannelTemplate specifies which Channel CRD to use to create the CRD Channel backing this Channel. + // This is immutable after creation. Normally this is set by the Channel defaulter, not directly by the user. + ChannelTemplate *ChannelTemplateSpec `json:"channelTemplate"` + + // Channel conforms to ChannelableSpec + eventingduck.ChannelableSpec `json:",inline"` +} + +// ChannelStatus represents the current state of a Channel. +type ChannelStatus struct { + // Channel conforms to ChannelableStatus + eventingduck.ChannelableStatus `json:",inline"` + + // Channel is an KReference to the Channel CRD backing this Channel. + Channel *duckv1.KReference `json:"channel,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ChannelList is a collection of Channels. +type ChannelList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + Items []Channel `json:"items"` +} + +// GetStatus retrieves the status of the Channel. Implements the KRShaped interface. +func (t *Channel) GetStatus() *duckv1.Status { + return &t.Status.Status +} diff --git a/pkg/apis/messaging/v1beta1/channel_types_test.go b/pkg/apis/messaging/v1beta1/channel_types_test.go new file mode 100644 index 00000000000..505f4d7de2a --- /dev/null +++ b/pkg/apis/messaging/v1beta1/channel_types_test.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 v1beta1 + +import "testing" + +func TestChannelGetStatus(t *testing.T) { + r := &Channel{ + Status: ChannelStatus{}, + } + if got, want := r.GetStatus(), &r.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} + +func TestChannel_GetGroupVersionKind(t *testing.T) { + c := Channel{} + gvk := c.GetGroupVersionKind() + if gvk.Kind != "Channel" { + t.Errorf("Should be Channel.") + } +} diff --git a/pkg/apis/messaging/v1beta1/channel_validation.go b/pkg/apis/messaging/v1beta1/channel_validation.go new file mode 100644 index 00000000000..a26e1ba0b08 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/channel_validation.go @@ -0,0 +1,81 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + "github.com/google/go-cmp/cmp/cmpopts" + "knative.dev/pkg/apis" + "knative.dev/pkg/kmp" +) + +func (c *Channel) Validate(ctx context.Context) *apis.FieldError { + withNS := apis.WithinParent(ctx, c.ObjectMeta) + return c.Spec.Validate(withNS).ViaField("spec") +} + +func (cs *ChannelSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + + if cs.ChannelTemplate == nil { + // The Channel defaulter is expected to set this, not the users. + errs = errs.Also(apis.ErrMissingField("channelTemplate")) + } else { + if cte := IsValidChannelTemplate(cs.ChannelTemplate); cte != nil { + errs = errs.Also(cte.ViaField("channelTemplate")) + } + } + + if len(cs.SubscribableSpec.Subscribers) > 0 { + errs = errs.Also(apis.ErrDisallowedFields("subscribers").ViaField("subscribable")) + } + return errs +} + +func IsValidChannelTemplate(ct *ChannelTemplateSpec) *apis.FieldError { + var errs *apis.FieldError + if ct.Kind == "" { + errs = errs.Also(apis.ErrMissingField("kind")) + } + if ct.APIVersion == "" { + errs = errs.Also(apis.ErrMissingField("apiVersion")) + } + return errs +} + +func (c *Channel) CheckImmutableFields(ctx context.Context, original *Channel) *apis.FieldError { + if original == nil { + return nil + } + + ignoreArguments := cmpopts.IgnoreFields(ChannelSpec{}, "SubscribableSpec") + if diff, err := kmp.ShortDiff(original.Spec, c.Spec, ignoreArguments); err != nil { + return &apis.FieldError{ + Message: "Failed to diff Channel", + Paths: []string{"spec"}, + Details: err.Error(), + } + } else if diff != "" { + return &apis.FieldError{ + Message: "Immutable fields changed (-old +new)", + Paths: []string{"spec"}, + Details: diff, + } + } + return nil +} diff --git a/pkg/apis/messaging/v1beta1/channel_validation_test.go b/pkg/apis/messaging/v1beta1/channel_validation_test.go new file mode 100644 index 00000000000..58e4a5aa418 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/channel_validation_test.go @@ -0,0 +1,232 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + eventingduck "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/pkg/apis" +) + +func TestChannelValidation(t *testing.T) { + tests := []CRDTest{{ + name: "empty", + cr: &Channel{ + Spec: ChannelSpec{}, + }, + want: apis.ErrMissingField("spec.channelTemplate"), + }, { + name: "channel template with no kind", + cr: &Channel{ + Spec: ChannelSpec{ + ChannelTemplate: &ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + APIVersion: SchemeGroupVersion.String(), + }, + }}, + }, + want: apis.ErrMissingField("spec.channelTemplate.kind"), + }, { + name: "channel template with no apiVersion", + cr: &Channel{ + Spec: ChannelSpec{ + ChannelTemplate: &ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + Kind: "InMemoryChannel", + }, + }}, + }, + want: apis.ErrMissingField("spec.channelTemplate.apiVersion"), + }, { + name: "invalid subscribers array, not allowed", + cr: &Channel{ + Spec: ChannelSpec{ + ChannelTemplate: &ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + Kind: "InMemoryChannel", + APIVersion: SchemeGroupVersion.String(), + }, + }, + ChannelableSpec: eventingduck.ChannelableSpec{ + SubscribableSpec: eventingduck.SubscribableSpec{ + Subscribers: []eventingduck.SubscriberSpec{{ + SubscriberURI: apis.HTTP("subscriberendpoint"), + ReplyURI: apis.HTTP("resultendpoint"), + }}, + }}, + }}, + want: apis.ErrDisallowedFields("spec.subscribable.subscribers"), + }, { + name: "nil channelTemplate and disallowed at index 1", + cr: &Channel{ + Spec: ChannelSpec{ + ChannelableSpec: eventingduck.ChannelableSpec{ + SubscribableSpec: eventingduck.SubscribableSpec{ + Subscribers: []eventingduck.SubscriberSpec{{ + SubscriberURI: apis.HTTP("subscriberendpoint"), + ReplyURI: apis.HTTP("replyendpoint"), + }, {}}, + }}, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + errs = errs.Also(apis.ErrMissingField("spec.channelTemplate")) + errs = errs.Also(apis.ErrDisallowedFields("spec.subscribable.subscribers")) + return errs + }(), + }} + + doValidateTest(t, tests) +} + +func TestChannelImmutableFields(t *testing.T) { + tests := []struct { + name string + current *Channel + original *Channel + want *apis.FieldError + }{{ + name: "good (no change)", + current: &Channel{ + Spec: ChannelSpec{ + ChannelTemplate: &ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + Kind: "InMemoryChannel", + APIVersion: SchemeGroupVersion.String(), + }, + Spec: &runtime.RawExtension{ + Raw: []byte(`"foo":"baz"`), + }, + }, + }, + }, + original: &Channel{ + Spec: ChannelSpec{ + ChannelTemplate: &ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + Kind: "InMemoryChannel", + APIVersion: SchemeGroupVersion.String(), + }, + Spec: &runtime.RawExtension{ + Raw: []byte(`"foo":"baz"`), + }, + }, + }, + }, + want: nil, + }, { + name: "new nil is ok", + current: &Channel{ + Spec: ChannelSpec{ + ChannelTemplate: &ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + Kind: "InMemoryChannel", + APIVersion: SchemeGroupVersion.String(), + }, + Spec: &runtime.RawExtension{ + Raw: []byte(`"foo":"baz"`), + }, + }, + }, + }, + original: nil, + want: nil, + }, { + name: "bad (channelTemplate change)", + current: &Channel{ + Spec: ChannelSpec{ + ChannelTemplate: &ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + Kind: "OtherChannel", + APIVersion: SchemeGroupVersion.String(), + }, + Spec: &runtime.RawExtension{ + Raw: []byte(`"foo":"baz"`), + }, + }, + }, + }, + original: &Channel{ + Spec: ChannelSpec{ + ChannelTemplate: &ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + Kind: "InMemoryChannel", + APIVersion: SchemeGroupVersion.String(), + }, + Spec: &runtime.RawExtension{ + Raw: []byte(`"foo":"baz"`), + }, + }, + }, + }, + want: &apis.FieldError{ + Message: "Immutable fields changed (-old +new)", + Paths: []string{"spec"}, + Details: `{v1beta1.ChannelSpec}.ChannelTemplate.TypeMeta.Kind: + -: "InMemoryChannel" + +: "OtherChannel" +`, + }, + }, { + name: "good (subscribable change)", + current: &Channel{ + Spec: ChannelSpec{ + ChannelTemplate: &ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + Kind: "InMemoryChannel", + APIVersion: SchemeGroupVersion.String(), + }, + }, + ChannelableSpec: eventingduck.ChannelableSpec{ + SubscribableSpec: eventingduck.SubscribableSpec{ + Subscribers: []eventingduck.SubscriberSpec{{ + SubscriberURI: apis.HTTP("subscriberendpoint"), + ReplyURI: apis.HTTP("replyendpoint"), + }}, + }, + }, + }, + }, + original: &Channel{ + Spec: ChannelSpec{ + ChannelTemplate: &ChannelTemplateSpec{ + TypeMeta: v1.TypeMeta{ + Kind: "InMemoryChannel", + APIVersion: SchemeGroupVersion.String(), + }, + }, + }, + }, + want: nil, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.current.CheckImmutableFields(context.TODO(), test.original) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("CheckImmutableFields (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/messaging/v1beta1/crd_validation_test.go b/pkg/apis/messaging/v1beta1/crd_validation_test.go new file mode 100644 index 00000000000..1daeee4c0de --- /dev/null +++ b/pkg/apis/messaging/v1beta1/crd_validation_test.go @@ -0,0 +1,43 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "knative.dev/pkg/apis" + "knative.dev/pkg/webhook/resourcesemantics" +) + +type CRDTest struct { + name string + cr resourcesemantics.GenericCRD + want *apis.FieldError +} + +func doValidateTest(t *testing.T, tests []CRDTest) { + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.cr.Validate(context.TODO()) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Errorf("%s: validate (-want, +got) = %v", test.name, diff) + } + }) + } +} diff --git a/pkg/apis/messaging/v1beta1/doc.go b/pkg/apis/messaging/v1beta1/doc.go new file mode 100644 index 00000000000..82d5675cc90 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/doc.go @@ -0,0 +1,24 @@ +/* +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. +*/ + +// Api versions allow the api contract for a resource to be changed while keeping +// backward compatibility by support multiple concurrent versions +// of the same resource + +// Package v1beta1 is the v1beta1 version of the API. +// +k8s:deepcopy-gen=package +// +groupName=messaging.knative.dev +package v1beta1 diff --git a/pkg/apis/messaging/v1beta1/in_memory_channel_conversion.go b/pkg/apis/messaging/v1beta1/in_memory_channel_conversion.go new file mode 100644 index 00000000000..e40ebe2b56b --- /dev/null +++ b/pkg/apis/messaging/v1beta1/in_memory_channel_conversion.go @@ -0,0 +1,102 @@ +/* +Copyright 2020 The Knative Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "fmt" + + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/eventing/pkg/apis/messaging" + v1 "knative.dev/eventing/pkg/apis/messaging/v1" + "knative.dev/pkg/apis" + "knative.dev/pkg/kmeta" +) + +// ConvertTo implements apis.Convertible +// Converts source (from v1beta1.InMemoryChannel) into v1.InMemoryChannel +func (source *InMemoryChannel) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1.InMemoryChannel: + sink.ObjectMeta = source.ObjectMeta + // Does a deep copy, adds our duck version. + sink.Annotations = kmeta.UnionMaps(source.Annotations, map[string]string{messaging.SubscribableDuckVersionAnnotation: "v1"}) + source.Status.ConvertTo(ctx, &sink.Status) + return source.Spec.ConvertTo(ctx, &sink.Spec) + default: + return fmt.Errorf("unknown version, got: %T", sink) + } +} + +// ConvertTo helps implement apis.Convertible +func (source *InMemoryChannelSpec) ConvertTo(ctx context.Context, sink *v1.InMemoryChannelSpec) error { + sink.SubscribableSpec = eventingduckv1.SubscribableSpec{} + source.SubscribableSpec.ConvertTo(ctx, &sink.SubscribableSpec) + if source.Delivery != nil { + sink.Delivery = &eventingduckv1.DeliverySpec{} + return source.Delivery.ConvertTo(ctx, sink.Delivery) + } + return nil +} + +// ConvertTo helps implement apis.Convertible +func (source *InMemoryChannelStatus) ConvertTo(ctx context.Context, sink *v1.InMemoryChannelStatus) { + sink.Status = source.Status + sink.AddressStatus = source.AddressStatus + sink.SubscribableStatus = eventingduckv1.SubscribableStatus{} + source.SubscribableStatus.ConvertTo(ctx, &sink.SubscribableStatus) + sink.DeadLetterChannel = source.DeadLetterChannel +} + +// ConvertFrom implements apis.Convertible. +// Converts obj v1.InMemoryChannel into v1beta1.InMemoryChannel +func (sink *InMemoryChannel) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1.InMemoryChannel: + sink.ObjectMeta = source.ObjectMeta + sink.Status.ConvertFrom(ctx, source.Status) + sink.Spec.ConvertFrom(ctx, source.Spec) + // Does a deep copy, adds our duck version. + sink.Annotations = kmeta.UnionMaps(source.Annotations, map[string]string{messaging.SubscribableDuckVersionAnnotation: "v1beta1"}) + return nil + default: + return fmt.Errorf("unknown version, got: %T", source) + } +} + +// ConvertFrom helps implement apis.Convertible +func (sink *InMemoryChannelSpec) ConvertFrom(ctx context.Context, source v1.InMemoryChannelSpec) error { + if source.Delivery != nil { + sink.Delivery = &eventingduckv1beta1.DeliverySpec{} + if err := sink.Delivery.ConvertFrom(ctx, source.Delivery); err != nil { + return err + } + } + sink.SubscribableSpec = eventingduckv1beta1.SubscribableSpec{} + sink.SubscribableSpec.ConvertFrom(ctx, &source.SubscribableSpec) + return nil +} + +// ConvertFrom helps implement apis.Convertible +func (sink *InMemoryChannelStatus) ConvertFrom(ctx context.Context, source v1.InMemoryChannelStatus) { + sink.Status = source.Status + sink.AddressStatus = source.AddressStatus + sink.SubscribableStatus = eventingduckv1beta1.SubscribableStatus{} + sink.SubscribableStatus.ConvertFrom(ctx, &source.SubscribableStatus) + sink.DeadLetterChannel = source.DeadLetterChannel +} diff --git a/pkg/apis/messaging/v1beta1/in_memory_channel_conversion_test.go b/pkg/apis/messaging/v1beta1/in_memory_channel_conversion_test.go new file mode 100644 index 00000000000..cc8a2befa99 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/in_memory_channel_conversion_test.go @@ -0,0 +1,520 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + eventingduck "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/eventing/pkg/apis/messaging" + v1 "knative.dev/eventing/pkg/apis/messaging/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestInMemoryChannelConversionBadType(t *testing.T) { + good, bad := &InMemoryChannel{}, &Subscription{} + + 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) + } +} + +// Test v1beta1 -> v1 -> v1beta1 +func TestInMemoryChannelConversion(t *testing.T) { + linear := eventingduck.BackoffPolicyLinear + + tests := []struct { + name string + in *InMemoryChannel + }{{ + name: "min configuration", + in: &InMemoryChannel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: InMemoryChannelSpec{}, + Status: InMemoryChannelStatus{ + ChannelableStatus: eventingduck.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{}, + }, + }, + }, + }, + }, { + name: "full configuration", + in: &InMemoryChannel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: InMemoryChannelSpec{ + ChannelableSpec: eventingduck.ChannelableSpec{ + SubscribableSpec: eventingduck.SubscribableSpec{ + Subscribers: []eventingduck.SubscriberSpec{ + { + UID: "uid-1", + Generation: 7, + SubscriberURI: apis.HTTP("subscriber.example.com"), + ReplyURI: apis.HTTP("reply.example.com"), + Delivery: &eventingduck.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("subscriber.dls.example.com"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + }, + Delivery: &eventingduck.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("dls"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + Status: InMemoryChannelStatus{ + ChannelableStatus: eventingduck.ChannelableStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: apis.HTTP("addressstatus.example.com"), + }, + }, + SubscribableStatus: eventingduck.SubscribableStatus{ + Subscribers: []eventingduck.SubscriberStatus{ + { + UID: "status-uid-1", + ObservedGeneration: 99, + Ready: corev1.ConditionTrue, + Message: "msg", + }, + }, + }, + }, + }, + }, + }, { + name: "full configuration, no delivery on subscriber", + in: &InMemoryChannel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: InMemoryChannelSpec{ + ChannelableSpec: eventingduck.ChannelableSpec{ + SubscribableSpec: eventingduck.SubscribableSpec{ + Subscribers: []eventingduck.SubscriberSpec{ + { + UID: "uid-1", + Generation: 7, + SubscriberURI: apis.HTTP("subscriber.example.com"), + ReplyURI: apis.HTTP("reply.example.com"), + }, + }, + }, + Delivery: &eventingduck.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("dls"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + Status: InMemoryChannelStatus{ + ChannelableStatus: eventingduck.ChannelableStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: apis.HTTP("addressstatus.example.com"), + }, + }, + SubscribableStatus: eventingduck.SubscribableStatus{ + Subscribers: []eventingduck.SubscriberStatus{ + { + UID: "status-uid-1", + ObservedGeneration: 99, + Ready: corev1.ConditionTrue, + Message: "msg", + }, + }, + }, + }, + }, + }, + }, { + name: "full configuration, no delivery on subscriber or channel", + in: &InMemoryChannel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: InMemoryChannelSpec{ + ChannelableSpec: eventingduck.ChannelableSpec{ + SubscribableSpec: eventingduck.SubscribableSpec{ + Subscribers: []eventingduck.SubscriberSpec{ + { + UID: "uid-1", + Generation: 7, + SubscriberURI: apis.HTTP("subscriber.example.com"), + ReplyURI: apis.HTTP("reply.example.com"), + }, + }, + }, + }, + }, + Status: InMemoryChannelStatus{ + ChannelableStatus: eventingduck.ChannelableStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: apis.HTTP("addressstatus.example.com"), + }, + }, + SubscribableStatus: eventingduck.SubscribableStatus{ + Subscribers: []eventingduck.SubscriberStatus{ + { + UID: "status-uid-1", + ObservedGeneration: 99, + Ready: corev1.ConditionTrue, + Message: "msg", + }, + }, + }, + }, + }, + }, + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ver := &v1.InMemoryChannel{} + if err := test.in.ConvertTo(context.Background(), ver); err != nil { + t.Error("ConvertTo() =", err) + } + got := &InMemoryChannel{} + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Error("ConvertFrom() =", err) + } + // Make sure the annotation specifies the correct duck. + if test.in.Annotations == nil { + test.in.Annotations = make(map[string]string) + } + test.in.Annotations[messaging.SubscribableDuckVersionAnnotation] = "v1beta1" + + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } +} + +// Test v1 -> v1beta1 -> v1 +func TestInMemoryChannelConversionWithV1Beta1(t *testing.T) { + linear := eventingduckv1.BackoffPolicyLinear + + tests := []struct { + name string + in *v1.InMemoryChannel + }{{ + name: "min configuration", + in: &v1.InMemoryChannel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: v1.InMemoryChannelSpec{}, + Status: v1.InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{}, + }, + }, + }, + }, + }, { + name: "full configuration - no delivery on subscriber", + in: &v1.InMemoryChannel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: v1.InMemoryChannelSpec{ + ChannelableSpec: eventingduckv1.ChannelableSpec{ + SubscribableSpec: eventingduckv1.SubscribableSpec{ + Subscribers: []eventingduckv1.SubscriberSpec{ + { + UID: "uid-1", + Generation: 7, + SubscriberURI: apis.HTTP("subscriber.example.com"), + ReplyURI: apis.HTTP("reply.example.com"), + }, + }, + }, + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("dls"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + Status: v1.InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1.ChannelableStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: apis.HTTP("addressstatus.example.com"), + }, + }, + SubscribableStatus: eventingduckv1.SubscribableStatus{ + Subscribers: []eventingduckv1.SubscriberStatus{ + { + UID: "status-uid-1", + ObservedGeneration: 99, + Ready: corev1.ConditionTrue, + Message: "msg", + }, + }, + }, + }, + }, + }, + }, { + name: "full configuration - no delivery on subscriber or channel", + in: &v1.InMemoryChannel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: v1.InMemoryChannelSpec{ + ChannelableSpec: eventingduckv1.ChannelableSpec{ + SubscribableSpec: eventingduckv1.SubscribableSpec{ + Subscribers: []eventingduckv1.SubscriberSpec{ + { + UID: "uid-1", + Generation: 7, + SubscriberURI: apis.HTTP("subscriber.example.com"), + ReplyURI: apis.HTTP("reply.example.com"), + }, + }, + }, + }, + }, + Status: v1.InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1.ChannelableStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: apis.HTTP("addressstatus.example.com"), + }, + }, + SubscribableStatus: eventingduckv1.SubscribableStatus{ + Subscribers: []eventingduckv1.SubscriberStatus{ + { + UID: "status-uid-1", + ObservedGeneration: 99, + Ready: corev1.ConditionTrue, + Message: "msg", + }, + }, + }, + }, + }, + }, + }, { + name: "full configuration", + in: &v1.InMemoryChannel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "channel-name", + Namespace: "channel-ns", + Generation: 17, + }, + Spec: v1.InMemoryChannelSpec{ + ChannelableSpec: eventingduckv1.ChannelableSpec{ + SubscribableSpec: eventingduckv1.SubscribableSpec{ + Subscribers: []eventingduckv1.SubscriberSpec{ + { + UID: "uid-1", + Generation: 7, + SubscriberURI: apis.HTTP("subscriber.example.com"), + ReplyURI: apis.HTTP("reply.example.com"), + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("subscriber.dls.example.com"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + }, + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("dls"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + }, + Status: v1.InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1.ChannelableStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: apis.HTTP("addressstatus.example.com"), + }, + }, + SubscribableStatus: eventingduckv1.SubscribableStatus{ + Subscribers: []eventingduckv1.SubscriberStatus{ + { + UID: "status-uid-1", + ObservedGeneration: 99, + Ready: corev1.ConditionTrue, + Message: "msg", + }, + }, + }, + }, + }, + }, + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + version := &InMemoryChannel{} + if err := version.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertTo() =", err) + } + got := &v1.InMemoryChannel{} + if err := version.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertFrom() =", err) + } + // Make sure the annotation specifies the correct duck. + if test.in.Annotations == nil { + test.in.Annotations = make(map[string]string) + } + test.in.Annotations[messaging.SubscribableDuckVersionAnnotation] = "v1" + + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + + } +} diff --git a/pkg/apis/messaging/v1beta1/in_memory_channel_defaults.go b/pkg/apis/messaging/v1beta1/in_memory_channel_defaults.go new file mode 100644 index 00000000000..f290b242317 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/in_memory_channel_defaults.go @@ -0,0 +1,43 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + "knative.dev/eventing/pkg/apis/messaging" +) + +func (imc *InMemoryChannel) SetDefaults(ctx context.Context) { + // Set the duck subscription to the stored version of the duck + // we support. Reason for this is that the stored version will + // not get a chance to get modified, but for newer versions + // conversion webhook will be able to take a crack at it and + // can modify it to match the duck shape. + if imc.Annotations == nil { + imc.Annotations = make(map[string]string) + } + if _, ok := imc.Annotations[messaging.SubscribableDuckVersionAnnotation]; !ok { + imc.Annotations[messaging.SubscribableDuckVersionAnnotation] = "v1beta1" + } + + imc.Spec.SetDefaults(ctx) +} + +func (imcs *InMemoryChannelSpec) SetDefaults(ctx context.Context) { + // TODO: Nothing to default here... +} diff --git a/pkg/apis/messaging/v1beta1/in_memory_channel_defaults_test.go b/pkg/apis/messaging/v1beta1/in_memory_channel_defaults_test.go new file mode 100644 index 00000000000..5185cda68d8 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/in_memory_channel_defaults_test.go @@ -0,0 +1,56 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/eventing/pkg/apis/messaging/config" + + "github.com/google/go-cmp/cmp" +) + +func TestInMemoryChannelSetDefaults(t *testing.T) { + testCases := map[string]struct { + channelTemplate *config.ChannelTemplateSpec + initial InMemoryChannel + expected InMemoryChannel + }{ + "nil gets annotations": { + initial: InMemoryChannel{}, + expected: InMemoryChannel{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"messaging.knative.dev/subscribable": "v1beta1"}}}, + }, + "empty gets annotations": { + initial: InMemoryChannel{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}}, + expected: InMemoryChannel{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"messaging.knative.dev/subscribable": "v1beta1"}}}, + }, + "non-empty gets added ChannelDefaulter": { + initial: InMemoryChannel{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"somethingelse": "yup"}}}, + expected: InMemoryChannel{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"messaging.knative.dev/subscribable": "v1beta1", "somethingelse": "yup"}}}, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + tc.initial.SetDefaults(context.Background()) + if diff := cmp.Diff(tc.expected, tc.initial); diff != "" { + t.Fatal("Unexpected defaults (-want, +got):", diff) + } + }) + } +} diff --git a/pkg/apis/messaging/v1beta1/in_memory_channel_lifecycle.go b/pkg/apis/messaging/v1beta1/in_memory_channel_lifecycle.go new file mode 100644 index 00000000000..a1634ced754 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/in_memory_channel_lifecycle.go @@ -0,0 +1,152 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/pkg/apis" + v1 "knative.dev/pkg/apis/duck/v1" +) + +var imcCondSet = apis.NewLivingConditionSet(InMemoryChannelConditionDispatcherReady, InMemoryChannelConditionServiceReady, InMemoryChannelConditionEndpointsReady, InMemoryChannelConditionAddressable, InMemoryChannelConditionChannelServiceReady) + +const ( + // InMemoryChannelConditionReady has status True when all subconditions below have been set to True. + InMemoryChannelConditionReady = apis.ConditionReady + + // InMemoryChannelConditionDispatcherReady has status True when a Dispatcher deployment is ready + // Keyed off appsv1.DeploymentAvailable, which means minimum available replicas required are up + // and running for at least minReadySeconds. + InMemoryChannelConditionDispatcherReady apis.ConditionType = "DispatcherReady" + + // InMemoryChannelConditionServiceReady has status True when a k8s Service is ready. This + // basically just means it exists because there's no meaningful status in Service. See Endpoints + // below. + InMemoryChannelConditionServiceReady apis.ConditionType = "ServiceReady" + + // InMemoryChannelConditionEndpointsReady has status True when a k8s Service Endpoints are backed + // by at least one endpoint. + InMemoryChannelConditionEndpointsReady apis.ConditionType = "EndpointsReady" + + // InMemoryChannelConditionAddressable has status true when this InMemoryChannel meets + // the Addressable contract and has a non-empty hostname. + InMemoryChannelConditionAddressable apis.ConditionType = "Addressable" + + // InMemoryChannelConditionServiceReady has status True when a k8s Service representing the channel is ready. + // Because this uses ExternalName, there are no endpoints to check. + InMemoryChannelConditionChannelServiceReady apis.ConditionType = "ChannelServiceReady" +) + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (*InMemoryChannel) GetConditionSet() apis.ConditionSet { + return imcCondSet +} + +// GetGroupVersionKind returns GroupVersionKind for InMemoryChannels +func (*InMemoryChannel) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("InMemoryChannel") +} + +// GetUntypedSpec returns the spec of the InMemoryChannel. +func (i *InMemoryChannel) GetUntypedSpec() interface{} { + return i.Spec +} + +// GetCondition returns the condition currently associated with the given type, or nil. +func (imcs *InMemoryChannelStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return imcCondSet.Manage(imcs).GetCondition(t) +} + +// IsReady returns true if the resource is ready overall. +func (imcs *InMemoryChannelStatus) IsReady() bool { + return imcCondSet.Manage(imcs).IsHappy() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (imcs *InMemoryChannelStatus) InitializeConditions() { + imcCondSet.Manage(imcs).InitializeConditions() +} + +func (imcs *InMemoryChannelStatus) SetAddress(url *apis.URL) { + imcs.Address = &v1.Addressable{URL: url} + if url != nil { + imcCondSet.Manage(imcs).MarkTrue(InMemoryChannelConditionAddressable) + } else { + imcCondSet.Manage(imcs).MarkFalse(InMemoryChannelConditionAddressable, "emptyHostname", "hostname is the empty string") + } +} + +func (imcs *InMemoryChannelStatus) MarkDispatcherFailed(reason, messageFormat string, messageA ...interface{}) { + imcCondSet.Manage(imcs).MarkFalse(InMemoryChannelConditionDispatcherReady, reason, messageFormat, messageA...) +} + +func (imcs *InMemoryChannelStatus) MarkDispatcherUnknown(reason, messageFormat string, messageA ...interface{}) { + imcCondSet.Manage(imcs).MarkUnknown(InMemoryChannelConditionDispatcherReady, reason, messageFormat, messageA...) +} + +// TODO: Unify this with the ones from Eventing. Say: Broker, Trigger. +func (imcs *InMemoryChannelStatus) PropagateDispatcherStatus(ds *appsv1.DeploymentStatus) { + for _, cond := range ds.Conditions { + if cond.Type == appsv1.DeploymentAvailable { + if cond.Status == corev1.ConditionTrue { + imcCondSet.Manage(imcs).MarkTrue(InMemoryChannelConditionDispatcherReady) + } else if cond.Status == corev1.ConditionFalse { + imcs.MarkDispatcherFailed("DispatcherDeploymentFalse", "The status of Dispatcher Deployment is False: %s : %s", cond.Reason, cond.Message) + } else if cond.Status == corev1.ConditionUnknown { + imcs.MarkDispatcherUnknown("DispatcherDeploymentUnknown", "The status of Dispatcher Deployment is Unknown: %s : %s", cond.Reason, cond.Message) + } + } + } +} + +func (imcs *InMemoryChannelStatus) MarkServiceFailed(reason, messageFormat string, messageA ...interface{}) { + imcCondSet.Manage(imcs).MarkFalse(InMemoryChannelConditionServiceReady, reason, messageFormat, messageA...) +} + +func (imcs *InMemoryChannelStatus) MarkServiceUnknown(reason, messageFormat string, messageA ...interface{}) { + imcCondSet.Manage(imcs).MarkUnknown(InMemoryChannelConditionServiceReady, reason, messageFormat, messageA...) +} + +func (imcs *InMemoryChannelStatus) MarkServiceTrue() { + imcCondSet.Manage(imcs).MarkTrue(InMemoryChannelConditionServiceReady) +} + +func (imcs *InMemoryChannelStatus) MarkChannelServiceFailed(reason, messageFormat string, messageA ...interface{}) { + imcCondSet.Manage(imcs).MarkFalse(InMemoryChannelConditionChannelServiceReady, reason, messageFormat, messageA...) +} + +func (imcs *InMemoryChannelStatus) MarkChannelServiceUnknown(reason, messageFormat string, messageA ...interface{}) { + imcCondSet.Manage(imcs).MarkUnknown(InMemoryChannelConditionChannelServiceReady, reason, messageFormat, messageA...) +} + +func (imcs *InMemoryChannelStatus) MarkChannelServiceTrue() { + imcCondSet.Manage(imcs).MarkTrue(InMemoryChannelConditionChannelServiceReady) +} + +func (imcs *InMemoryChannelStatus) MarkEndpointsFailed(reason, messageFormat string, messageA ...interface{}) { + imcCondSet.Manage(imcs).MarkFalse(InMemoryChannelConditionEndpointsReady, reason, messageFormat, messageA...) +} + +func (imcs *InMemoryChannelStatus) MarkEndpointsUnknown(reason, messageFormat string, messageA ...interface{}) { + imcCondSet.Manage(imcs).MarkUnknown(InMemoryChannelConditionEndpointsReady, reason, messageFormat, messageA...) +} + +func (imcs *InMemoryChannelStatus) MarkEndpointsTrue() { + imcCondSet.Manage(imcs).MarkTrue(InMemoryChannelConditionEndpointsReady) +} diff --git a/pkg/apis/messaging/v1beta1/in_memory_channel_lifecycle_test.go b/pkg/apis/messaging/v1beta1/in_memory_channel_lifecycle_test.go new file mode 100644 index 00000000000..f24882e80fc --- /dev/null +++ b/pkg/apis/messaging/v1beta1/in_memory_channel_lifecycle_test.go @@ -0,0 +1,382 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +var condReady = apis.Condition{ + Type: InMemoryChannelConditionReady, + Status: corev1.ConditionTrue, +} + +var condDispatcherNotReady = apis.Condition{ + Type: InMemoryChannelConditionDispatcherReady, + Status: corev1.ConditionFalse, +} + +var deploymentConditionReady = appsv1.DeploymentCondition{ + Type: appsv1.DeploymentAvailable, + Status: corev1.ConditionTrue, +} + +var deploymentConditionNotReady = appsv1.DeploymentCondition{ + Type: appsv1.DeploymentAvailable, + Status: corev1.ConditionFalse, +} + +var deploymentStatusReady = &appsv1.DeploymentStatus{Conditions: []appsv1.DeploymentCondition{deploymentConditionReady}} +var deploymentStatusNotReady = &appsv1.DeploymentStatus{Conditions: []appsv1.DeploymentCondition{deploymentConditionNotReady}} + +var ignoreAllButTypeAndStatus = cmpopts.IgnoreFields( + apis.Condition{}, + "LastTransitionTime", "Message", "Reason", "Severity") + +func TestInMemoryChannelGetConditionSet(t *testing.T) { + r := &InMemoryChannel{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestInMemoryChannelGetCondition(t *testing.T) { + tests := []struct { + name string + cs *InMemoryChannelStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "single condition", + cs: &InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + condReady, + }, + }, + }, + }, + condQuery: apis.ConditionReady, + want: &condReady, + }, { + name: "unknown condition", + cs: &InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + condReady, + condDispatcherNotReady, + }, + }, + }, + }, + condQuery: apis.ConditionType("foo"), + want: nil, + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.cs.GetCondition(test.condQuery) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Error("unexpected condition (-want, +got) =", diff) + } + }) + } +} + +func TestInMemoryChannelInitializeConditions(t *testing.T) { + tests := []struct { + name string + cs *InMemoryChannelStatus + want *InMemoryChannelStatus + }{{ + name: "empty", + cs: &InMemoryChannelStatus{}, + want: &InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: InMemoryChannelConditionAddressable, + Status: corev1.ConditionUnknown, + }, { + Type: InMemoryChannelConditionChannelServiceReady, + Status: corev1.ConditionUnknown, + }, { + Type: InMemoryChannelConditionDispatcherReady, + Status: corev1.ConditionUnknown, + }, { + Type: InMemoryChannelConditionEndpointsReady, + Status: corev1.ConditionUnknown, + }, { + Type: InMemoryChannelConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: InMemoryChannelConditionServiceReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, + }, { + name: "one false", + cs: &InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: InMemoryChannelConditionDispatcherReady, + Status: corev1.ConditionFalse, + }}, + }, + }, + }, + want: &InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: InMemoryChannelConditionAddressable, + Status: corev1.ConditionUnknown, + }, { + Type: InMemoryChannelConditionChannelServiceReady, + Status: corev1.ConditionUnknown, + }, { + Type: InMemoryChannelConditionDispatcherReady, + Status: corev1.ConditionFalse, + }, { + Type: InMemoryChannelConditionEndpointsReady, + Status: corev1.ConditionUnknown, + }, { + Type: InMemoryChannelConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: InMemoryChannelConditionServiceReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, + }, { + name: "one true", + cs: &InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: InMemoryChannelConditionDispatcherReady, + Status: corev1.ConditionTrue, + }}, + }, + }, + }, + want: &InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: InMemoryChannelConditionAddressable, + Status: corev1.ConditionUnknown, + }, { + Type: InMemoryChannelConditionChannelServiceReady, + Status: corev1.ConditionUnknown, + }, { + Type: InMemoryChannelConditionDispatcherReady, + Status: corev1.ConditionTrue, + }, { + Type: InMemoryChannelConditionEndpointsReady, + Status: corev1.ConditionUnknown, + }, { + Type: InMemoryChannelConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: InMemoryChannelConditionServiceReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.cs.InitializeConditions() + if diff := cmp.Diff(test.want, test.cs, ignoreAllButTypeAndStatus); diff != "" { + t.Error("unexpected conditions (-want, +got) =", diff) + } + }) + } +} + +func TestInMemoryChannelIsReady(t *testing.T) { + tests := []struct { + name string + markServiceReady bool + markChannelServiceReady bool + setAddress bool + markEndpointsReady bool + wantReady bool + dispatcherStatus *appsv1.DeploymentStatus + }{{ + name: "all happy", + markServiceReady: true, + markChannelServiceReady: true, + markEndpointsReady: true, + dispatcherStatus: deploymentStatusReady, + setAddress: true, + wantReady: true, + }, { + name: "service not ready", + markServiceReady: false, + markChannelServiceReady: false, + markEndpointsReady: true, + dispatcherStatus: deploymentStatusReady, + setAddress: true, + wantReady: false, + }, { + name: "endpoints not ready", + markServiceReady: true, + markChannelServiceReady: false, + markEndpointsReady: false, + dispatcherStatus: deploymentStatusReady, + setAddress: true, + wantReady: false, + }, { + name: "deployment not ready", + markServiceReady: true, + markEndpointsReady: true, + markChannelServiceReady: false, + dispatcherStatus: deploymentStatusNotReady, + setAddress: true, + wantReady: false, + }, { + name: "address not set", + markServiceReady: true, + markChannelServiceReady: false, + markEndpointsReady: true, + dispatcherStatus: deploymentStatusReady, + setAddress: false, + wantReady: false, + }, { + name: "channel service not ready", + markServiceReady: true, + markChannelServiceReady: false, + markEndpointsReady: true, + dispatcherStatus: deploymentStatusReady, + setAddress: true, + wantReady: false, + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cs := &InMemoryChannelStatus{} + cs.InitializeConditions() + if test.markServiceReady { + cs.MarkServiceTrue() + } else { + cs.MarkServiceFailed("NotReadyService", "testing") + } + if test.markChannelServiceReady { + cs.MarkChannelServiceTrue() + } else { + cs.MarkChannelServiceFailed("NotReadyChannelService", "testing") + } + if test.setAddress { + cs.SetAddress(&apis.URL{Scheme: "http", Host: "foo.bar"}) + } + if test.markEndpointsReady { + cs.MarkEndpointsTrue() + } else { + cs.MarkEndpointsFailed("NotReadyEndpoints", "testing") + } + if test.dispatcherStatus != nil { + cs.PropagateDispatcherStatus(test.dispatcherStatus) + } else { + cs.MarkDispatcherFailed("NotReadyDispatcher", "testing") + } + got := cs.IsReady() + if test.wantReady != got { + t.Errorf("unexpected readiness: want %v, got %v", test.wantReady, got) + } + }) + } +} + +func TestInMemoryChannelStatus_SetAddressable(t *testing.T) { + testCases := map[string]struct { + url *apis.URL + want *InMemoryChannelStatus + }{ + "empty string": { + want: &InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1beta1.ChannelableStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: InMemoryChannelConditionAddressable, + Status: corev1.ConditionFalse, + }, { + // Note that Ready is here because when the condition is marked False, duck + // automatically sets Ready to false. + Type: InMemoryChannelConditionReady, + Status: corev1.ConditionFalse, + }}, + }, + AddressStatus: duckv1.AddressStatus{Address: &duckv1.Addressable{}}, + }, + }, + }, + "has domain - unknown": { + url: &apis.URL{Scheme: "http", Host: "test-domain"}, + want: &InMemoryChannelStatus{ + ChannelableStatus: eventingduckv1beta1.ChannelableStatus{ + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: "test-domain", + }, + }, + }, + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: InMemoryChannelConditionAddressable, + Status: corev1.ConditionTrue, + }, { + // Note: Ready is here because when the condition + // is marked True, duck automatically sets Ready to + // Unknown because of missing ChannelConditionBackingChannelReady. + Type: InMemoryChannelConditionReady, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + cs := &InMemoryChannelStatus{} + cs.SetAddress(tc.url) + if diff := cmp.Diff(tc.want, cs, ignoreAllButTypeAndStatus); diff != "" { + t.Error("unexpected conditions (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/messaging/v1beta1/in_memory_channel_types.go b/pkg/apis/messaging/v1beta1/in_memory_channel_types.go new file mode 100644 index 00000000000..19cc5920ad9 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/in_memory_channel_types.go @@ -0,0 +1,91 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +genreconciler +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// InMemoryChannel is a resource representing an in memory channel +type InMemoryChannel struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of the Channel. + Spec InMemoryChannelSpec `json:"spec,omitempty"` + + // Status represents the current state of the Channel. This data may be out of + // date. + // +optional + Status InMemoryChannelStatus `json:"status,omitempty"` +} + +var ( + // Check that InMemoryChannel can be validated and defaulted. + _ apis.Validatable = (*InMemoryChannel)(nil) + _ apis.Defaultable = (*InMemoryChannel)(nil) + + // Check that InMemoryChannel can return its spec untyped. + _ apis.HasSpec = (*InMemoryChannel)(nil) + + _ runtime.Object = (*InMemoryChannel)(nil) + + // Check that we can create OwnerReferences to an InMemoryChannel. + _ kmeta.OwnerRefable = (*InMemoryChannel)(nil) + + // Check that the type conforms to the duck Knative Resource shape. + _ duckv1.KRShaped = (*InMemoryChannel)(nil) +) + +// InMemoryChannelSpec defines which subscribers have expressed interest in +// receiving events from this InMemoryChannel. +// arguments for a Channel. +type InMemoryChannelSpec struct { + // Channel conforms to Duck type Channelable. + eventingduckv1beta1.ChannelableSpec `json:",inline"` +} + +// ChannelStatus represents the current state of a Channel. +type InMemoryChannelStatus struct { + // Channel conforms to Duck type Channelable. + eventingduckv1beta1.ChannelableStatus `json:",inline"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// InMemoryChannelList is a collection of in-memory channels. +type InMemoryChannelList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + Items []InMemoryChannel `json:"items"` +} + +// GetStatus retrieves the status of the InMemoryChannel. Implements the KRShaped interface. +func (t *InMemoryChannel) GetStatus() *duckv1.Status { + return &t.Status.Status +} diff --git a/pkg/apis/messaging/v1beta1/in_memory_channel_types_test.go b/pkg/apis/messaging/v1beta1/in_memory_channel_types_test.go new file mode 100644 index 00000000000..7f4a97ef94b --- /dev/null +++ b/pkg/apis/messaging/v1beta1/in_memory_channel_types_test.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 v1beta1 + +import "testing" + +func TestInMemoryChannelGetStatus(t *testing.T) { + r := &InMemoryChannel{ + Status: InMemoryChannelStatus{}, + } + if got, want := r.GetStatus(), &r.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} + +func TestInMemoryChannel_GetGroupVersionKind(t *testing.T) { + imc := InMemoryChannel{} + gvk := imc.GetGroupVersionKind() + if gvk.Kind != "InMemoryChannel" { + t.Errorf("Should be InMemoryChannel.") + } +} diff --git a/pkg/apis/messaging/v1beta1/in_memory_channel_validation.go b/pkg/apis/messaging/v1beta1/in_memory_channel_validation.go new file mode 100644 index 00000000000..7ed4dc11b73 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/in_memory_channel_validation.go @@ -0,0 +1,56 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "fmt" + + "knative.dev/pkg/apis" + + "knative.dev/eventing/pkg/apis/eventing" +) + +func (imc *InMemoryChannel) Validate(ctx context.Context) *apis.FieldError { + errs := imc.Spec.Validate(ctx).ViaField("spec") + + // Validate annotations + if imc.Annotations != nil { + if scope, ok := imc.Annotations[eventing.ScopeAnnotationKey]; ok { + if scope != eventing.ScopeNamespace && scope != eventing.ScopeCluster { + iv := apis.ErrInvalidValue(scope, "") + iv.Details = "expected either 'cluster' or 'namespace'" + errs = errs.Also(iv.ViaFieldKey("annotations", eventing.ScopeAnnotationKey).ViaField("metadata")) + } + } + } + + return errs +} + +func (imcs *InMemoryChannelSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + for i, subscriber := range imcs.SubscribableSpec.Subscribers { + if subscriber.ReplyURI == nil && subscriber.SubscriberURI == nil { + fe := apis.ErrMissingField("replyURI", "subscriberURI") + fe.Details = "expected at least one of, got none" + errs = errs.Also(fe.ViaField(fmt.Sprintf("subscriber[%d]", i)).ViaField("subscribable")) + } + } + + return errs +} diff --git a/pkg/apis/messaging/v1beta1/in_memory_channel_validation_test.go b/pkg/apis/messaging/v1beta1/in_memory_channel_validation_test.go new file mode 100644 index 00000000000..4615ed755dd --- /dev/null +++ b/pkg/apis/messaging/v1beta1/in_memory_channel_validation_test.go @@ -0,0 +1,107 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + + eventingduck "knative.dev/eventing/pkg/apis/duck/v1beta1" + "knative.dev/eventing/pkg/apis/eventing" +) + +func TestInMemoryChannelValidation(t *testing.T) { + tests := []CRDTest{{ + name: "empty", + cr: &InMemoryChannel{ + Spec: InMemoryChannelSpec{}, + }, + want: nil, + }, { + name: "valid subscribers array", + cr: &InMemoryChannel{ + Spec: InMemoryChannelSpec{ + ChannelableSpec: eventingduck.ChannelableSpec{ + SubscribableSpec: eventingduck.SubscribableSpec{ + Subscribers: []eventingduck.SubscriberSpec{{ + SubscriberURI: apis.HTTP("subscriberendpoint"), + ReplyURI: apis.HTTP("resultendpoint"), + }}, + }}, + }, + }, + want: nil, + }, { + name: "empty subscriber at index 1", + cr: &InMemoryChannel{ + Spec: InMemoryChannelSpec{ + ChannelableSpec: eventingduck.ChannelableSpec{ + SubscribableSpec: eventingduck.SubscribableSpec{ + Subscribers: []eventingduck.SubscriberSpec{{ + SubscriberURI: apis.HTTP("subscriberendpoint"), + ReplyURI: apis.HTTP("replyendpoint"), + }, {}}, + }}, + }, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("spec.subscribable.subscriber[1].replyURI", "spec.subscribable.subscriber[1].subscriberURI") + fe.Details = "expected at least one of, got none" + return fe + }(), + }, { + name: "2 empty subscribers", + cr: &InMemoryChannel{ + Spec: InMemoryChannelSpec{ + ChannelableSpec: eventingduck.ChannelableSpec{ + SubscribableSpec: eventingduck.SubscribableSpec{ + Subscribers: []eventingduck.SubscriberSpec{{}, {}}, + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrMissingField("spec.subscribable.subscriber[0].replyURI", "spec.subscribable.subscriber[0].subscriberURI") + fe.Details = "expected at least one of, got none" + errs = errs.Also(fe) + fe = apis.ErrMissingField("spec.subscribable.subscriber[1].replyURI", "spec.subscribable.subscriber[1].subscriberURI") + fe.Details = "expected at least one of, got none" + errs = errs.Also(fe) + return errs + }(), + }, { + name: "invalid scope annotation", + cr: &InMemoryChannel{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + eventing.ScopeAnnotationKey: "notvalid", + }, + }, + Spec: InMemoryChannelSpec{}, + }, + want: func() *apis.FieldError { + fe := apis.ErrInvalidValue("notvalid", "metadata.annotations.[eventing.knative.dev/scope]") + fe.Details = "expected either 'cluster' or 'namespace'" + return fe + }(), + }} + + doValidateTest(t, tests) +} diff --git a/pkg/apis/messaging/v1beta1/register.go b/pkg/apis/messaging/v1beta1/register.go new file mode 100644 index 00000000000..9c3b54f60ec --- /dev/null +++ b/pkg/apis/messaging/v1beta1/register.go @@ -0,0 +1,57 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "knative.dev/eventing/pkg/apis/messaging" + + 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: messaging.GroupName, Version: "v1beta1"} + +// 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, + &InMemoryChannel{}, + &InMemoryChannelList{}, + &Subscription{}, + &SubscriptionList{}, + &Channel{}, + &ChannelList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/apis/messaging/v1beta1/roundtrip_test.go b/pkg/apis/messaging/v1beta1/roundtrip_test.go new file mode 100644 index 00000000000..10041f27f78 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/roundtrip_test.go @@ -0,0 +1,135 @@ +/* +Copyright 2020 The Knative Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "math/rand" + "testing" + + fuzz "github.com/google/gofuzz" + "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + duckv1 "knative.dev/eventing/pkg/apis/duck/v1" + "knative.dev/eventing/pkg/apis/messaging" + v1 "knative.dev/eventing/pkg/apis/messaging/v1" + pkgfuzzer "knative.dev/pkg/apis/testing/fuzzer" + "knative.dev/pkg/apis/testing/roundtrip" +) + +var ( + linear = duckv1.BackoffPolicyLinear + exponential = duckv1.BackoffPolicyExponential + bops = []*duckv1.BackoffPolicyType{nil, &linear, &exponential} +) + +// FuzzerFuncs includes fuzzing funcs for knative.dev/messaging v1 types +// +// For other examples see +// https://github.com/kubernetes/apimachinery/blob/master/pkg/apis/meta/fuzzer/fuzzer.go +var FuzzerFuncs = fuzzer.MergeFuzzerFuncs( + func(codecs serializer.CodecFactory) []interface{} { + return []interface{}{ + func(ch *v1.Channel, c fuzz.Continue) { + c.FuzzNoCustom(ch) // fuzz the Channel + if ch != nil { + if ch.Annotations == nil { + ch.Annotations = make(map[string]string) + } + ch.Annotations[messaging.SubscribableDuckVersionAnnotation] = "v1" + } + // Clear the random fuzzed condition + ch.Status.SetConditions(nil) + + // Fuzz the known conditions except their type value + ch.Status.InitializeConditions() + pkgfuzzer.FuzzConditions(&ch.Status, c) + }, + func(imc *v1.InMemoryChannel, c fuzz.Continue) { + c.FuzzNoCustom(imc) // fuzz the InMemoryChannel + if imc != nil { + if imc.Annotations == nil { + imc.Annotations = make(map[string]string) + } + imc.Annotations[messaging.SubscribableDuckVersionAnnotation] = "v1" + } + // Clear the random fuzzed condition + imc.Status.SetConditions(nil) + + // Fuzz the known conditions except their type value + imc.Status.InitializeConditions() + pkgfuzzer.FuzzConditions(&imc.Status, c) + }, + func(s *v1.SubscriptionStatus, c fuzz.Continue) { + c.FuzzNoCustom(s) // fuzz the status object + + // Clear the random fuzzed condition + s.Status.SetConditions(nil) + + // Fuzz the known conditions except their type value + s.InitializeConditions() + pkgfuzzer.FuzzConditions(&s.Status, c) + }, + func(ds *duckv1.DeliverySpec, c fuzz.Continue) { + c.FuzzNoCustom(ds) // fuzz the DeliverySpec + if ds.BackoffPolicy != nil && *ds.BackoffPolicy == "" { + ds.BackoffPolicy = nil + } else { + //nolint:gosec // Cryptographic randomness is not necessary. + ds.BackoffPolicy = bops[rand.Intn(3)] + } + }, + } + }, +) + +func TestMessagingRoundTripTypesToJSON(t *testing.T) { + scheme := runtime.NewScheme() + utilruntime.Must(AddToScheme(scheme)) + + fuzzerFuncs := fuzzer.MergeFuzzerFuncs( + pkgfuzzer.Funcs, + FuzzerFuncs, + ) + roundtrip.ExternalTypesViaJSON(t, scheme, fuzzerFuncs) +} + +func TestMessagingRoundTripTypesToBetaHub(t *testing.T) { + scheme := runtime.NewScheme() + + sb := runtime.SchemeBuilder{ + AddToScheme, + v1.AddToScheme, + } + + utilruntime.Must(sb.AddToScheme(scheme)) + + hubs := runtime.NewScheme() + hubs.AddKnownTypes(SchemeGroupVersion, + &Channel{}, + &Subscription{}, + &InMemoryChannel{}, + ) + + fuzzerFuncs := fuzzer.MergeFuzzerFuncs( + pkgfuzzer.Funcs, + FuzzerFuncs, + ) + + roundtrip.ExternalTypesViaHub(t, scheme, hubs, fuzzerFuncs) +} diff --git a/pkg/apis/messaging/v1beta1/subscribable_channelable_validation.go b/pkg/apis/messaging/v1beta1/subscribable_channelable_validation.go new file mode 100644 index 00000000000..6429fbc72b0 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/subscribable_channelable_validation.go @@ -0,0 +1,85 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "reflect" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + "knative.dev/pkg/apis" +) + +func isChannelEmpty(f corev1.ObjectReference) bool { + return equality.Semantic.DeepEqual(f, corev1.ObjectReference{}) +} + +// Valid if it is a valid object reference. +func isValidChannel(f corev1.ObjectReference) *apis.FieldError { + return IsValidObjectReference(f) +} + +func IsValidObjectReference(f corev1.ObjectReference) *apis.FieldError { + return checkRequiredObjectReferenceFields(f). + Also(checkDisallowedObjectReferenceFields(f)) +} + +// Check the corev1.ObjectReference to make sure it has the required fields. They +// are not checked for anything more except that they are set. +func checkRequiredObjectReferenceFields(f corev1.ObjectReference) *apis.FieldError { + var errs *apis.FieldError + if f.Name == "" { + errs = errs.Also(apis.ErrMissingField("name")) + } + if f.APIVersion == "" { + errs = errs.Also(apis.ErrMissingField("apiVersion")) + } + if f.Kind == "" { + errs = errs.Also(apis.ErrMissingField("kind")) + } + return errs +} + +// Check the corev1.ObjectReference to make sure it only has the following fields set: +// Name, Kind, APIVersion +// If any other fields are set and is not the Zero value, returns an apis.FieldError +// with the fieldpaths for all those fields. +func checkDisallowedObjectReferenceFields(f corev1.ObjectReference) *apis.FieldError { + disallowedFields := []string{} + // See if there are any fields that have been set that should not be. + // TODO: Hoist this kind of stuff into pkg repository. + s := reflect.ValueOf(f) + typeOf := s.Type() + for i := 0; i < s.NumField(); i++ { + field := s.Field(i) + fieldName := typeOf.Field(i).Name + if fieldName == "Name" || fieldName == "Kind" || fieldName == "APIVersion" { + continue + } + if !cmp.Equal(field.Interface(), reflect.Zero(field.Type()).Interface()) { + disallowedFields = append(disallowedFields, fieldName) + } + } + if len(disallowedFields) > 0 { + fe := apis.ErrDisallowedFields(disallowedFields...) + fe.Details = "only name, apiVersion and kind are supported fields" + return fe + } + return nil + +} diff --git a/pkg/apis/messaging/v1beta1/subscribable_channelable_validation_test.go b/pkg/apis/messaging/v1beta1/subscribable_channelable_validation_test.go new file mode 100644 index 00000000000..bbcc156646e --- /dev/null +++ b/pkg/apis/messaging/v1beta1/subscribable_channelable_validation_test.go @@ -0,0 +1,150 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" +) + +var validationTests = []struct { + name string + ref corev1.ObjectReference + want *apis.FieldError +}{ + { + name: "valid object ref", + ref: corev1.ObjectReference{ + Name: "boaty-mcboatface", + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "MyChannel", + }, + want: nil, + }, + { + name: "invalid object ref", + ref: corev1.ObjectReference{ + Name: "boaty-mcboatface", + APIVersion: "messaging.knative.dev/v1alpha1", + Kind: "", + }, + want: apis.ErrMissingField("kind"), + }, +} + +func TestIsChannelEmpty(t *testing.T) { + name := "non empty" + t.Run(name, func(t *testing.T) { + r := corev1.ObjectReference{ + Name: "boaty-mcboatface", + APIVersion: "messaging.knative.dev/v1beta1", + Kind: "Channel", + } + if isChannelEmpty(r) { + t.Errorf("%s: isChannelEmpty(%s) should be false", name, r) + } + }) + + name = "empty" + t.Run(name, func(t *testing.T) { + r := corev1.ObjectReference{} + if !isChannelEmpty(r) { + t.Errorf("%s: isChannelEmpty(%s) should be true", name, r) + } + }) +} + +func TestIsValidChannel(t *testing.T) { + for _, test := range validationTests { + t.Run(test.name, func(t *testing.T) { + got := isValidChannel(test.ref) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Errorf("%s: validation (-want, +got) = %v", test.name, diff) + } + }) + } +} + +func TestIsValidObjectReference(t *testing.T) { + tests := []struct { + name string + ref corev1.ObjectReference + want []*apis.FieldError + }{ + { + name: "missing api version and kind", + ref: corev1.ObjectReference{ + Name: "boaty-mcboatface", + APIVersion: "", + Kind: "", + }, + want: []*apis.FieldError{ + apis.ErrMissingField("apiVersion"), + apis.ErrMissingField("kind"), + }, + }, + { + name: "missing name", + ref: corev1.ObjectReference{ + Name: "", + APIVersion: "eventing.knative.dev/v1beta1", + Kind: "Strait", + }, + want: []*apis.FieldError{ + apis.ErrMissingField("name"), + }, + }, + { + name: "missing all", + ref: corev1.ObjectReference{ + Name: "", + APIVersion: "", + Kind: "", + }, + want: []*apis.FieldError{ + apis.ErrMissingField("name"), + apis.ErrMissingField("apiVersion"), + apis.ErrMissingField("kind"), + }, + }, + { + name: "missing none", + ref: corev1.ObjectReference{ + Name: "kind", + APIVersion: "messaging.knative.dev/v1beta1", + Kind: "Channel", + }, + want: []*apis.FieldError{}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + allWanted := &apis.FieldError{} + for _, fe := range test.want { + allWanted = allWanted.Also(fe) + } + got := IsValidObjectReference(test.ref) + if diff := cmp.Diff(allWanted.Error(), got.Error()); diff != "" { + t.Errorf("%s: validation (-want, +got) = %v", test.name, diff) + } + }) + } +} diff --git a/pkg/apis/messaging/v1beta1/subscription_conversion.go b/pkg/apis/messaging/v1beta1/subscription_conversion.go new file mode 100644 index 00000000000..2196e795244 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/subscription_conversion.go @@ -0,0 +1,82 @@ +/* +Copyright 2020 The Knative Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "fmt" + + duckv1 "knative.dev/eventing/pkg/apis/duck/v1" + duckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + + v1 "knative.dev/eventing/pkg/apis/messaging/v1" + "knative.dev/pkg/apis" +) + +// ConvertTo implements apis.Convertible. +// Converts source (from v1beta1.Subscription) into v1.Subscription +func (source *Subscription) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1.Subscription: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.Channel = source.Spec.Channel + if source.Spec.Delivery != nil { + sink.Spec.Delivery = &duckv1.DeliverySpec{} + if err := source.Spec.Delivery.ConvertTo(ctx, sink.Spec.Delivery); err != nil { + return err + } + } + sink.Spec.Subscriber = source.Spec.Subscriber + sink.Spec.Reply = source.Spec.Reply + + sink.Status.Status = source.Status.Status + sink.Status.PhysicalSubscription.SubscriberURI = source.Status.PhysicalSubscription.SubscriberURI + sink.Status.PhysicalSubscription.ReplyURI = source.Status.PhysicalSubscription.ReplyURI + sink.Status.PhysicalSubscription.DeadLetterSinkURI = source.Status.PhysicalSubscription.DeadLetterSinkURI + return nil + default: + return fmt.Errorf("Unknown conversion, got: %T", sink) + + } +} + +// ConvertFrom implements apis.Convertible. +// Converts obj from v1.Subscription into v1beta1.Subscription +func (sink *Subscription) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1.Subscription: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.Channel = source.Spec.Channel + if source.Spec.Delivery != nil { + sink.Spec.Delivery = &duckv1beta1.DeliverySpec{} + if err := sink.Spec.Delivery.ConvertFrom(ctx, source.Spec.Delivery); err != nil { + return err + } + } + sink.Spec.Subscriber = source.Spec.Subscriber + sink.Spec.Reply = source.Spec.Reply + + sink.Status.Status = source.Status.Status + sink.Status.PhysicalSubscription.SubscriberURI = source.Status.PhysicalSubscription.SubscriberURI + sink.Status.PhysicalSubscription.ReplyURI = source.Status.PhysicalSubscription.ReplyURI + sink.Status.PhysicalSubscription.DeadLetterSinkURI = source.Status.PhysicalSubscription.DeadLetterSinkURI + + return nil + default: + return fmt.Errorf("Unknown conversion, got: %T", source) + } +} diff --git a/pkg/apis/messaging/v1beta1/subscription_conversion_test.go b/pkg/apis/messaging/v1beta1/subscription_conversion_test.go new file mode 100644 index 00000000000..c4ee0907d2e --- /dev/null +++ b/pkg/apis/messaging/v1beta1/subscription_conversion_test.go @@ -0,0 +1,266 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + eventingduckv1 "knative.dev/eventing/pkg/apis/duck/v1" + duckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + v1 "knative.dev/eventing/pkg/apis/messaging/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestSubscriptionConversionBadType(t *testing.T) { + good, bad := &Subscription{}, &Channel{} + + 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) + } +} + +func TestBrokerConversionBadVersion(t *testing.T) { + good, bad := &Subscription{}, &Subscription{} + + 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) + } +} + +// Test v1beta1 -> v1 -> v1beta1 +func TestSubscriptionConversionRoundTripV1beta1(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&v1.Subscription{}} + + linear := duckv1beta1.BackoffPolicyLinear + + tests := []struct { + name string + in *Subscription + }{{ + name: "min configuration", + in: &Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Name: "subscription-name", + Namespace: "subscription-ns", + Generation: 17, + }, + Spec: SubscriptionSpec{}, + }, + }, { + name: "full configuration", + in: &Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Name: "subscription-name", + Namespace: "subscription-ns", + Generation: 17, + }, + Spec: SubscriptionSpec{ + Channel: corev1.ObjectReference{ + Kind: "channelKind", + Namespace: "channelNamespace", + Name: "channelName", + APIVersion: "channelAPIVersion", + }, + Subscriber: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "subscriber-dest-kind", + Namespace: "subscriber-dest-ns", + Name: "subscriber-dest-name", + APIVersion: "subscriber-dest-version", + }, + URI: apis.HTTP("address"), + }, + Reply: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "reply-dest-kind", + Namespace: "reply-dest-ns", + Name: "reply-dest-name", + APIVersion: "reply-dest-version", + }, + URI: apis.HTTP("address"), + }, + Delivery: &duckv1beta1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("dls"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + Status: SubscriptionStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + PhysicalSubscription: SubscriptionStatusPhysicalSubscription{ + SubscriberURI: apis.HTTP("subscriber.example.com"), + ReplyURI: apis.HTTP("reply.example.com"), + DeadLetterSinkURI: apis.HTTP("dlc.example.com"), + }, + }, + }, + }} + + 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 := &Subscription{} + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Error("ConvertFrom() =", err) + } + + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} + +// Test v1 -> v1beta1 -> v1 +func TestBrokerConversionRoundTripV1(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&Subscription{}} + + linear := eventingduckv1.BackoffPolicyLinear + + tests := []struct { + name string + in *v1.Subscription + }{{ + name: "min configuration", + in: &v1.Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Name: "subscription-name", + Namespace: "subscription-ns", + Generation: 17, + }, + Spec: v1.SubscriptionSpec{}, + }, + }, { + name: "full configuration", + in: &v1.Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Name: "subscription-name", + Namespace: "subscription-ns", + Generation: 17, + }, + Spec: v1.SubscriptionSpec{ + Channel: corev1.ObjectReference{ + Kind: "channelKind", + Namespace: "channelNamespace", + Name: "channelName", + APIVersion: "channelAPIVersion", + }, + Subscriber: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "subscriber-dest-kind", + Namespace: "subscriber-dest-ns", + Name: "subscriber-dest-name", + APIVersion: "subscriber-dest-version", + }, + URI: apis.HTTP("address"), + }, + Reply: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "reply-dest-kind", + Namespace: "reply-dest-ns", + Name: "reply-dest-name", + APIVersion: "reply-dest-version", + }, + URI: apis.HTTP("address"), + }, + Delivery: &eventingduckv1.DeliverySpec{ + DeadLetterSink: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "dlKind", + Namespace: "dlNamespace", + Name: "dlName", + APIVersion: "dlAPIVersion", + }, + URI: apis.HTTP("dls"), + }, + Retry: pointer.Int32Ptr(5), + BackoffPolicy: &linear, + BackoffDelay: pointer.StringPtr("5s"), + }, + }, + Status: v1.SubscriptionStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + PhysicalSubscription: v1.SubscriptionStatusPhysicalSubscription{ + SubscriberURI: apis.HTTP("subscriber.example.com"), + ReplyURI: apis.HTTP("reply.example.com"), + DeadLetterSinkURI: apis.HTTP("dlc.example.com"), + }, + }, + }, + }} + + for _, test := range tests { + for _, version := range versions { + t.Run(test.name, func(t *testing.T) { + ver := version + if err := ver.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertFrom() =", err) + } + got := &v1.Subscription{} + if err := ver.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertTo() =", err) + } + + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} diff --git a/pkg/apis/messaging/v1beta1/subscription_defaults.go b/pkg/apis/messaging/v1beta1/subscription_defaults.go new file mode 100644 index 00000000000..6694bb17a45 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/subscription_defaults.go @@ -0,0 +1,33 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" +) + +func (s *Subscription) SetDefaults(ctx context.Context) { + s.Spec.SetDefaults(ctx) +} + +func (ss *SubscriptionSpec) SetDefaults(ctx context.Context) { + // HACK if a channel ref is a kafka channel ref, we need to hack it around to use only v1beta1 + // TODO(slinkydeveloper) REMOVE AFTER 0.22 release + if ss.Channel.Kind == "KafkaChannel" && ss.Channel.APIVersion == "messaging.knative.dev/v1alpha1" { + ss.Channel.APIVersion = "messaging.knative.dev/v1beta1" + } +} diff --git a/pkg/apis/messaging/v1beta1/subscription_defaults_test.go b/pkg/apis/messaging/v1beta1/subscription_defaults_test.go new file mode 100644 index 00000000000..8e30351f70a --- /dev/null +++ b/pkg/apis/messaging/v1beta1/subscription_defaults_test.go @@ -0,0 +1,28 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" +) + +// No-op test because method does nothing. +func TestSubscriptionDefaults(t *testing.T) { + s := Subscription{} + s.SetDefaults(context.TODO()) +} diff --git a/pkg/apis/messaging/v1beta1/subscription_lifecycle.go b/pkg/apis/messaging/v1beta1/subscription_lifecycle.go new file mode 100644 index 00000000000..db2dda91635 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/subscription_lifecycle.go @@ -0,0 +1,115 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "knative.dev/pkg/apis" +) + +// SubCondSet is a condition set with Ready as the happy condition and +// ReferencesResolved and ChannelReady as the dependent conditions. +var SubCondSet = apis.NewLivingConditionSet(SubscriptionConditionReferencesResolved, SubscriptionConditionAddedToChannel, SubscriptionConditionChannelReady) + +const ( + // SubscriptionConditionReady has status True when all subconditions below have been set to True. + SubscriptionConditionReady = apis.ConditionReady + // SubscriptionConditionReferencesResolved has status True when all the specified references have been successfully + // resolved. + SubscriptionConditionReferencesResolved apis.ConditionType = "ReferencesResolved" + + // SubscriptionConditionAddedToChannel has status True when controller has successfully added a + // subscription to the spec.channel resource. + SubscriptionConditionAddedToChannel apis.ConditionType = "AddedToChannel" + + // SubscriptionConditionChannelReady has status True when the channel has marked the subscriber as 'ready' + SubscriptionConditionChannelReady apis.ConditionType = "ChannelReady" +) + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (*Subscription) GetConditionSet() apis.ConditionSet { + return SubCondSet +} + +// GetCondition returns the condition currently associated with the given type, or nil. +func (ss *SubscriptionStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return SubCondSet.Manage(ss).GetCondition(t) +} + +// GetTopLevelCondition returns the top level Condition. +func (ss *SubscriptionStatus) GetTopLevelCondition() *apis.Condition { + return SubCondSet.Manage(ss).GetTopLevelCondition() +} + +// IsReady returns true if the resource is ready overall. +func (ss *SubscriptionStatus) IsReady() bool { + return SubCondSet.Manage(ss).IsHappy() +} + +// IsAddedToChannel returns true if SubscriptionConditionAddedToChannel is true +func (ss *SubscriptionStatus) IsAddedToChannel() bool { + return ss.GetCondition(SubscriptionConditionAddedToChannel).IsTrue() +} + +// AreReferencesResolved returns true if SubscriptionConditionReferencesResolved is true +func (ss *SubscriptionStatus) AreReferencesResolved() bool { + return ss.GetCondition(SubscriptionConditionReferencesResolved).IsTrue() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (ss *SubscriptionStatus) InitializeConditions() { + SubCondSet.Manage(ss).InitializeConditions() +} + +// MarkReferencesResolved sets the ReferencesResolved condition to True state. +func (ss *SubscriptionStatus) MarkReferencesResolved() { + SubCondSet.Manage(ss).MarkTrue(SubscriptionConditionReferencesResolved) +} + +// MarkChannelReady sets the ChannelReady condition to True state. +func (ss *SubscriptionStatus) MarkChannelReady() { + SubCondSet.Manage(ss).MarkTrue(SubscriptionConditionChannelReady) +} + +// MarkAddedToChannel sets the AddedToChannel condition to True state. +func (ss *SubscriptionStatus) MarkAddedToChannel() { + SubCondSet.Manage(ss).MarkTrue(SubscriptionConditionAddedToChannel) +} + +// MarkReferencesNotResolved sets the ReferencesResolved condition to False state. +func (ss *SubscriptionStatus) MarkReferencesNotResolved(reason, messageFormat string, messageA ...interface{}) { + SubCondSet.Manage(ss).MarkFalse(SubscriptionConditionReferencesResolved, reason, messageFormat, messageA...) +} + +// MarkReferencesResolvedUnknown sets the ReferencesResolved condition to Unknown state. +func (ss *SubscriptionStatus) MarkReferencesResolvedUnknown(reason, messageFormat string, messageA ...interface{}) { + SubCondSet.Manage(ss).MarkUnknown(SubscriptionConditionReferencesResolved, reason, messageFormat, messageA...) +} + +// MarkChannelFailed sets the ChannelReady condition to False state. +func (ss *SubscriptionStatus) MarkChannelFailed(reason, messageFormat string, messageA ...interface{}) { + SubCondSet.Manage(ss).MarkFalse(SubscriptionConditionChannelReady, reason, messageFormat, messageA...) +} + +// MarkChannelUnknown sets the ChannelReady condition to Unknown state. +func (ss *SubscriptionStatus) MarkChannelUnknown(reason, messageFormat string, messageA ...interface{}) { + SubCondSet.Manage(ss).MarkUnknown(SubscriptionConditionChannelReady, reason, messageFormat, messageA...) +} + +// MarkNotAddedToChannel sets the AddedToChannel condition to False state. +func (ss *SubscriptionStatus) MarkNotAddedToChannel(reason, messageFormat string, messageA ...interface{}) { + SubCondSet.Manage(ss).MarkFalse(SubscriptionConditionAddedToChannel, reason, messageFormat, messageA...) +} diff --git a/pkg/apis/messaging/v1beta1/subscription_lifecycle_test.go b/pkg/apis/messaging/v1beta1/subscription_lifecycle_test.go new file mode 100644 index 00000000000..a1833da755e --- /dev/null +++ b/pkg/apis/messaging/v1beta1/subscription_lifecycle_test.go @@ -0,0 +1,275 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "testing" + + "knative.dev/pkg/apis" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +var subscriptionConditionReady = apis.Condition{ + Type: SubscriptionConditionReady, + Status: corev1.ConditionTrue, +} + +var subscriptionConditionReferencesResolved = apis.Condition{ + Type: SubscriptionConditionReferencesResolved, + Status: corev1.ConditionFalse, +} + +var subscriptionConditionChannelReady = apis.Condition{ + Type: SubscriptionConditionChannelReady, + Status: corev1.ConditionTrue, +} + +func TestSubscriptionGetConditionSet(t *testing.T) { + r := &Subscription{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestSubscriptionGetCondition(t *testing.T) { + tests := []struct { + name string + ss *SubscriptionStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "single condition", + ss: &SubscriptionStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + subscriptionConditionReady, + }, + }, + }, + condQuery: apis.ConditionReady, + want: &subscriptionConditionReady, + }, { + name: "multiple conditions", + ss: &SubscriptionStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + subscriptionConditionReady, + subscriptionConditionReferencesResolved, + }, + }, + }, + condQuery: SubscriptionConditionReferencesResolved, + want: &subscriptionConditionReferencesResolved, + }, { + name: "multiple conditions, condition true", + ss: &SubscriptionStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + subscriptionConditionReady, + subscriptionConditionChannelReady, + }, + }, + }, + condQuery: SubscriptionConditionChannelReady, + want: &subscriptionConditionChannelReady, + }, { + name: "unknown condition", + ss: &SubscriptionStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + subscriptionConditionReady, + subscriptionConditionReferencesResolved, + }, + }, + }, + condQuery: apis.ConditionType("foo"), + want: nil, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.ss.GetCondition(test.condQuery) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Error("unexpected condition (-want, +got) =", diff) + } + }) + } +} + +func TestSubscriptionInitializeConditions(t *testing.T) { + tests := []struct { + name string + ss *SubscriptionStatus + want *SubscriptionStatus + }{{ + name: "empty", + ss: &SubscriptionStatus{}, + want: &SubscriptionStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: SubscriptionConditionAddedToChannel, + Status: corev1.ConditionUnknown, + }, { + Type: SubscriptionConditionChannelReady, + Status: corev1.ConditionUnknown, + }, { + Type: SubscriptionConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: SubscriptionConditionReferencesResolved, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, { + name: "one false", + ss: &SubscriptionStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: SubscriptionConditionChannelReady, + Status: corev1.ConditionFalse, + }}, + }, + }, + want: &SubscriptionStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: SubscriptionConditionAddedToChannel, + Status: corev1.ConditionUnknown, + }, { + Type: SubscriptionConditionChannelReady, + Status: corev1.ConditionFalse, + }, { + Type: SubscriptionConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: SubscriptionConditionReferencesResolved, + Status: corev1.ConditionUnknown, + }}, + }, + }, + }, { + name: "one true", + ss: &SubscriptionStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: SubscriptionConditionReferencesResolved, + Status: corev1.ConditionTrue, + }}, + }, + }, + want: &SubscriptionStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: SubscriptionConditionAddedToChannel, + Status: corev1.ConditionUnknown, + }, { + Type: SubscriptionConditionChannelReady, + Status: corev1.ConditionUnknown, + }, { + Type: SubscriptionConditionReady, + Status: corev1.ConditionUnknown, + }, { + Type: SubscriptionConditionReferencesResolved, + Status: corev1.ConditionTrue, + }}, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.ss.InitializeConditions() + if diff := cmp.Diff(test.want, test.ss, ignoreAllButTypeAndStatus); diff != "" { + t.Error("unexpected conditions (-want, +got) =", diff) + } + }) + } +} + +func TestSubscriptionIsReady(t *testing.T) { + tests := []struct { + name string + markResolved bool + markChannelReady bool + wantReady bool + markAddedToChannel bool + }{{ + name: "all happy", + markResolved: true, + markChannelReady: true, + markAddedToChannel: true, + wantReady: true, + }, { + name: "one sad - markResolved", + markResolved: false, + markChannelReady: true, + markAddedToChannel: true, + wantReady: false, + }, { + name: "one sad - markChannelReady", + markResolved: true, + markChannelReady: false, + markAddedToChannel: true, + wantReady: false, + }, { + name: "one sad - markAddedToChannel", + markResolved: true, + markChannelReady: true, + markAddedToChannel: false, + wantReady: false, + }, { + name: "other sad", + markResolved: true, + markChannelReady: false, + wantReady: false, + }, { + name: "all sad", + markResolved: false, + markChannelReady: false, + markAddedToChannel: false, + wantReady: false, + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ss := &SubscriptionStatus{} + if test.markResolved { + ss.MarkReferencesResolved() + if !ss.AreReferencesResolved() { + t.Errorf("References marked resolved, but not reflected in AreReferencesResolved") + } + } + if test.markChannelReady { + ss.MarkChannelReady() + } + if test.markAddedToChannel { + ss.MarkAddedToChannel() + if !ss.IsAddedToChannel() { + t.Errorf("Channel added, but not reflected in IsAddedToChannel") + } + } + got := ss.IsReady() + if test.wantReady != got { + t.Errorf("unexpected readiness: want %v, got %v", test.wantReady, got) + } + }) + } +} diff --git a/pkg/apis/messaging/v1beta1/subscription_types.go b/pkg/apis/messaging/v1beta1/subscription_types.go new file mode 100644 index 00000000000..209ff7c6b96 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/subscription_types.go @@ -0,0 +1,156 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmeta" + + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" +) + +// +genclient +// +genreconciler +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:defaulter-gen=true + +// Subscription routes events received on a Channel to a DNS name and +// corresponds to the subscriptions.channels.knative.dev CRD. +type Subscription struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + Spec SubscriptionSpec `json:"spec"` + Status SubscriptionStatus `json:"status,omitempty"` +} + +var ( + // Check that Subscription can be validated, can be defaulted, and has immutable fields. + _ apis.Validatable = (*Subscription)(nil) + _ apis.Defaultable = (*Subscription)(nil) + + // Check that Subscription can return its spec untyped. + _ apis.HasSpec = (*Subscription)(nil) + + _ runtime.Object = (*Subscription)(nil) + + // Check that we can create OwnerReferences to a Subscription. + _ kmeta.OwnerRefable = (*Subscription)(nil) + + // Check that the type conforms to the duck Knative Resource shape. + _ duckv1.KRShaped = (*Subscription)(nil) +) + +// SubscriptionSpec specifies the Channel for incoming events, a Subscriber target +// for processing those events and where to put the result of the processing. Only +// From (where the events are coming from) is always required. You can optionally +// only Process the events (results in no output events) by leaving out the Result. +// You can also perform an identity transformation on the incoming events by leaving +// out the Subscriber and only specifying Result. +// +// The following are all valid specifications: +// channel --[subscriber]--> reply +// Sink, no outgoing events: +// channel -- subscriber +// no-op function (identity transformation): +// channel --> reply +type SubscriptionSpec struct { + // Reference to a channel that will be used to create the subscription + // You can specify only the following fields of the ObjectReference: + // - Kind + // - APIVersion + // - Name + // The resource pointed by this ObjectReference must meet the + // contract to the ChannelableSpec duck type. If the resource does not + // meet this contract it will be reflected in the Subscription's status. + // + // This field is immutable. We have no good answer on what happens to + // the events that are currently in the channel being consumed from + // and what the semantics there should be. For now, you can always + // delete the Subscription and recreate it to point to a different + // channel, giving the user more control over what semantics should + // be used (drain the channel first, possibly have events dropped, + // etc.) + Channel corev1.ObjectReference `json:"channel"` + + // Subscriber is reference to (optional) function for processing events. + // Events from the Channel will be delivered here and replies are + // sent to a Destination as specified by the Reply. + // +optional + Subscriber *duckv1.Destination `json:"subscriber,omitempty"` + + // Reply specifies (optionally) how to handle events returned from + // the Subscriber target. + // +optional + Reply *duckv1.Destination `json:"reply,omitempty"` + + // Delivery configuration + // +optional + Delivery *eventingduckv1beta1.DeliverySpec `json:"delivery,omitempty"` +} + +// SubscriptionStatus (computed) for a subscription +type SubscriptionStatus struct { + // inherits duck/v1 Status, 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. + duckv1.Status `json:",inline"` + + // PhysicalSubscription is the fully resolved values that this Subscription represents. + PhysicalSubscription SubscriptionStatusPhysicalSubscription `json:"physicalSubscription,omitempty"` +} + +// SubscriptionStatusPhysicalSubscription represents the fully resolved values for this +// Subscription. +type SubscriptionStatusPhysicalSubscription struct { + // SubscriberURI is the fully resolved URI for spec.subscriber. + SubscriberURI *apis.URL `json:"subscriberUri,omitempty"` + + // ReplyURI is the fully resolved URI for the spec.reply. + ReplyURI *apis.URL `json:"replyUri,omitempty"` + + // ReplyURI is the fully resolved URI for the spec.delivery.deadLetterSink. + DeadLetterSinkURI *apis.URL `json:"deadLetterSinkUri,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// SubscriptionList returned in list operations +type SubscriptionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + Items []Subscription `json:"items"` +} + +// GetGroupVersionKind returns GroupVersionKind for Subscriptions +func (*Subscription) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("Subscription") +} + +// GetUntypedSpec returns the spec of the Subscription. +func (s *Subscription) GetUntypedSpec() interface{} { + return s.Spec +} + +// GetStatus retrieves the status of the Subscription. Implements the KRShaped interface. +func (s *Subscription) GetStatus() *duckv1.Status { + return &s.Status.Status +} diff --git a/pkg/apis/messaging/v1beta1/subscription_types_test.go b/pkg/apis/messaging/v1beta1/subscription_types_test.go new file mode 100644 index 00000000000..d68927aff3f --- /dev/null +++ b/pkg/apis/messaging/v1beta1/subscription_types_test.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 v1beta1 + +import "testing" + +func TestSubscriptionGetStatus(t *testing.T) { + r := &Subscription{ + Status: SubscriptionStatus{}, + } + if got, want := r.GetStatus(), &r.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} + +func TestSubscription_GetGroupVersionKind(t *testing.T) { + c := Subscription{} + gvk := c.GetGroupVersionKind() + if gvk.Kind != "Subscription" { + t.Errorf("Should be Subscription.") + } +} diff --git a/pkg/apis/messaging/v1beta1/subscription_validation.go b/pkg/apis/messaging/v1beta1/subscription_validation.go new file mode 100644 index 00000000000..6d316efbb70 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/subscription_validation.go @@ -0,0 +1,102 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + "github.com/google/go-cmp/cmp/cmpopts" + "k8s.io/apimachinery/pkg/api/equality" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmp" +) + +func (s *Subscription) Validate(ctx context.Context) *apis.FieldError { + return s.Spec.Validate(ctx).ViaField("spec") +} + +func (ss *SubscriptionSpec) Validate(ctx context.Context) *apis.FieldError { + // We require always Channel. + // Also at least one of 'subscriber' and 'reply' must be defined (non-nil and non-empty). + + var errs *apis.FieldError + if isChannelEmpty(ss.Channel) { + fe := apis.ErrMissingField("channel") + fe.Details = "the Subscription must reference a channel" + return fe + } else if fe := isValidChannel(ss.Channel); fe != nil { + errs = errs.Also(fe.ViaField("channel")) + } + + missingSubscriber := isDestinationNilOrEmpty(ss.Subscriber) + missingReply := isDestinationNilOrEmpty(ss.Reply) + if missingSubscriber && missingReply { + fe := apis.ErrMissingField("reply", "subscriber") + fe.Details = "the Subscription must reference at least one of (reply or a subscriber)" + errs = errs.Also(fe) + } + + if !missingSubscriber { + if fe := ss.Subscriber.Validate(ctx); fe != nil { + errs = errs.Also(fe.ViaField("subscriber")) + } + } + + if !missingReply { + if fe := ss.Reply.Validate(ctx); fe != nil { + errs = errs.Also(fe.ViaField("reply")) + } + } + + return errs +} + +func isDestinationNilOrEmpty(d *duckv1.Destination) bool { + return d == nil || equality.Semantic.DeepEqual(d, &duckv1.Destination{}) +} + +func (s *Subscription) CheckImmutableFields(ctx context.Context, original *Subscription) *apis.FieldError { + if original == nil { + return nil + } + + // TODO(slinkydeveloper) + // HACK around the immutability check to make sure the update script can upgrade the api version + // REMOVE AFTER 0.22 release + ignoredFields := []string{"Subscriber", "Reply"} + if original.Spec.Channel.Kind == "KafkaChannel" && original.Spec.Channel.APIVersion == "messaging.knative.dev/v1alpha1" && s.Spec.Channel.APIVersion == "messaging.knative.dev/v1beta1" { + ignoredFields = append(ignoredFields, "Channel.APIVersion") + } + + // Only Subscriber and Reply are mutable. + ignoreArguments := cmpopts.IgnoreFields(SubscriptionSpec{}, ignoredFields...) + if diff, err := kmp.ShortDiff(original.Spec, s.Spec, ignoreArguments); err != nil { + return &apis.FieldError{ + Message: "Failed to diff Subscription", + Paths: []string{"spec"}, + Details: err.Error(), + } + } else if diff != "" { + return &apis.FieldError{ + Message: "Immutable fields changed (-old +new)", + Paths: []string{"spec"}, + Details: diff, + } + } + return nil +} diff --git a/pkg/apis/messaging/v1beta1/subscription_validation_test.go b/pkg/apis/messaging/v1beta1/subscription_validation_test.go new file mode 100644 index 00000000000..71e20fa4495 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/subscription_validation_test.go @@ -0,0 +1,464 @@ +/* +Copyright 2020 The Knative Authors. All Rights Reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +const ( + channelKind = "MyChannel" + channelAPIVersion = "eventing.knative.dev/v1alpha1" + routeKind = "Route" + routeAPIVersion = "serving.knative.dev/v1alpha1" + channelName = "subscribedChannel" + replyChannelName = "toChannel" + subscriberName = "subscriber" + namespace = "namespace" +) + +func getValidChannelRef() corev1.ObjectReference { + return corev1.ObjectReference{ + Name: channelName, + Kind: channelKind, + APIVersion: channelAPIVersion, + } +} + +func getValidReply() *duckv1.Destination { + return &duckv1.Destination{ + Ref: &duckv1.KReference{ + Namespace: namespace, + Name: replyChannelName, + Kind: channelKind, + APIVersion: channelAPIVersion, + }, + } +} + +func getValidDestination() *duckv1.Destination { + return &duckv1.Destination{ + Ref: &duckv1.KReference{ + Namespace: namespace, + Name: subscriberName, + Kind: routeKind, + APIVersion: routeAPIVersion, + }, + } +} + +func TestSubscriptionValidation(t *testing.T) { + name := "empty channel" + c := &Subscription{ + Spec: SubscriptionSpec{ + Channel: corev1.ObjectReference{}, + }, + } + want := &apis.FieldError{ + Paths: []string{"spec.channel"}, + Message: "missing field(s)", + Details: "the Subscription must reference a channel", + } + + t.Run(name, func(t *testing.T) { + got := c.Validate(context.TODO()) + if diff := cmp.Diff(want.Error(), got.Error()); diff != "" { + t.Error("Subscription.Validate (-want, +got) =", diff) + } + }) + +} + +func TestSubscriptionSpecValidation(t *testing.T) { + tests := []struct { + name string + c *SubscriptionSpec + want *apis.FieldError + }{{ + name: "valid", + c: &SubscriptionSpec{ + Channel: getValidChannelRef(), + Subscriber: getValidDestination(), + }, + want: nil, + }, { + name: "valid with reply", + c: &SubscriptionSpec{ + Channel: getValidChannelRef(), + Subscriber: getValidDestination(), + Reply: getValidReply(), + }, + want: nil, + }, { + name: "empty Channel", + c: &SubscriptionSpec{ + Channel: corev1.ObjectReference{}, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("channel") + fe.Details = "the Subscription must reference a channel" + return fe + }(), + }, { + name: "missing name in Channel", + c: &SubscriptionSpec{ + Channel: corev1.ObjectReference{ + Kind: channelKind, + APIVersion: channelAPIVersion, + }, + Subscriber: getValidDestination(), + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("channel.name") + return fe + }(), + }, { + name: "missing Subscriber and Reply", + c: &SubscriptionSpec{ + Channel: getValidChannelRef(), + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("reply", "subscriber") + fe.Details = "the Subscription must reference at least one of (reply or a subscriber)" + return fe + }(), + }, { + name: "empty Subscriber and Reply", + c: &SubscriptionSpec{ + Channel: getValidChannelRef(), + Subscriber: &duckv1.Destination{}, + Reply: &duckv1.Destination{}, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("reply", "subscriber") + fe.Details = "the Subscription must reference at least one of (reply or a subscriber)" + return fe + }(), + }, { + name: "missing Reply", + c: &SubscriptionSpec{ + Channel: getValidChannelRef(), + Subscriber: getValidDestination(), + }, + want: nil, + }, { + name: "empty Reply", + c: &SubscriptionSpec{ + Channel: getValidChannelRef(), + Subscriber: getValidDestination(), + Reply: &duckv1.Destination{}, + }, + want: nil, + }, { + name: "missing Subscriber", + c: &SubscriptionSpec{ + Channel: getValidChannelRef(), + Reply: getValidReply(), + }, + want: nil, + }, { + name: "empty Subscriber", + c: &SubscriptionSpec{ + Channel: getValidChannelRef(), + Subscriber: &duckv1.Destination{}, + Reply: getValidReply(), + }, + want: nil, + }, { + name: "missing name in channel, and missing subscriber, reply", + c: &SubscriptionSpec{ + Channel: corev1.ObjectReference{ + Kind: channelKind, + APIVersion: channelAPIVersion, + }, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("reply", "subscriber") + fe.Details = "the Subscription must reference at least one of (reply or a subscriber)" + return apis.ErrMissingField("channel.name").Also(fe) + }(), + }, { + name: "empty", + c: &SubscriptionSpec{}, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("channel") + fe.Details = "the Subscription must reference a channel" + return fe + }(), + }, { + name: "missing name in Subscriber.Ref", + c: &SubscriptionSpec{ + Channel: getValidChannelRef(), + Subscriber: &duckv1.Destination{ + Ref: &duckv1.KReference{ + Namespace: namespace, + Kind: channelKind, + APIVersion: channelAPIVersion, + }, + }, + }, + want: apis.ErrMissingField("subscriber.ref.name"), + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.c.Validate(context.TODO()) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Errorf("%s: validateChannel (-want, +got) = %v", test.name, diff) + } + }) + } +} + +func TestSubscriptionImmutable(t *testing.T) { + newChannel := getValidChannelRef() + newChannel.Name = "newChannel" + + newSubscriber := getValidDestination() + newSubscriber.Ref.Name = "newSubscriber" + + newReply := getValidReply() + newReply.Ref.Name = "newReplyChannel" + + tests := []struct { + name string + c *Subscription + og *Subscription + want *apis.FieldError + }{{ + name: "valid", + c: &Subscription{ + Spec: SubscriptionSpec{ + Channel: getValidChannelRef(), + }, + }, + og: &Subscription{ + Spec: SubscriptionSpec{ + Channel: getValidChannelRef(), + }, + }, + want: nil, + }, { + name: "new nil is ok", + c: &Subscription{ + Spec: SubscriptionSpec{ + Channel: getValidChannelRef(), + Subscriber: getValidDestination(), + }, + }, + og: nil, + want: nil, + }, { + name: "valid, new Subscriber", + c: &Subscription{ + Spec: SubscriptionSpec{ + Channel: getValidChannelRef(), + Subscriber: getValidDestination(), + }, + }, + og: &Subscription{ + Spec: SubscriptionSpec{ + Channel: getValidChannelRef(), + Subscriber: newSubscriber, + }, + }, + want: nil, + }, { + name: "valid, kafkachannel hack", + c: &Subscription{ + Spec: SubscriptionSpec{ + Channel: corev1.ObjectReference{ + Name: channelName, + Kind: "KafkaChannel", + APIVersion: "messaging.knative.dev/v1beta1", + }, + Subscriber: getValidDestination(), + }, + }, + og: &Subscription{ + Spec: SubscriptionSpec{ + Channel: corev1.ObjectReference{ + Name: channelName, + Kind: "KafkaChannel", + APIVersion: "messaging.knative.dev/v1alpha1", + }, + Subscriber: getValidDestination(), + }, + }, + want: nil, + }, { + name: "valid, new Reply", + c: &Subscription{ + Spec: SubscriptionSpec{ + Channel: getValidChannelRef(), + Reply: getValidReply(), + }, + }, + og: &Subscription{ + Spec: SubscriptionSpec{ + Channel: getValidChannelRef(), + Reply: newReply, + }, + }, + want: nil, + }, { + name: "valid, have Reply, remove and replace with Subscriber", + c: &Subscription{ + Spec: SubscriptionSpec{ + Channel: getValidChannelRef(), + Reply: getValidReply(), + }, + }, + og: &Subscription{ + Spec: SubscriptionSpec{ + Channel: getValidChannelRef(), + Subscriber: getValidDestination(), + }, + }, + want: nil, + }, { + name: "valid, have Subscriber, remove and replace with Reply", + c: &Subscription{ + Spec: SubscriptionSpec{ + Channel: getValidChannelRef(), + Subscriber: getValidDestination(), + }, + }, + og: &Subscription{ + Spec: SubscriptionSpec{ + Channel: getValidChannelRef(), + Reply: getValidReply(), + }, + }, + want: nil, + }, { + name: "Channel changed", + c: &Subscription{ + Spec: SubscriptionSpec{ + Channel: getValidChannelRef(), + }, + }, + og: &Subscription{ + Spec: SubscriptionSpec{ + Channel: newChannel, + }, + }, + want: &apis.FieldError{ + Message: "Immutable fields changed (-old +new)", + Paths: []string{"spec"}, + Details: `{v1beta1.SubscriptionSpec}.Channel.Name: + -: "newChannel" + +: "subscribedChannel" +`, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.c.CheckImmutableFields(context.TODO(), test.og) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("CheckImmutableFields (-want, +got) =", diff) + } + }) + } +} + +func TestValidChannel(t *testing.T) { + tests := []struct { + name string + c corev1.ObjectReference + want *apis.FieldError + }{{ + name: "valid", + c: corev1.ObjectReference{ + Name: channelName, + APIVersion: channelAPIVersion, + Kind: channelKind, + }, + want: nil, + }, { + name: "missing name", + c: corev1.ObjectReference{ + APIVersion: channelAPIVersion, + Kind: channelKind, + }, + want: func() *apis.FieldError { + fe := apis.ErrMissingField("name") + return fe + }(), + }, { + name: "missing apiVersion", + c: corev1.ObjectReference{ + Name: channelName, + Kind: channelKind, + }, + want: func() *apis.FieldError { + return apis.ErrMissingField("apiVersion") + }(), + }, { + name: "extra field, namespace", + c: corev1.ObjectReference{ + Name: channelName, + APIVersion: channelAPIVersion, + Kind: channelKind, + Namespace: "secretnamespace", + }, + want: func() *apis.FieldError { + fe := apis.ErrDisallowedFields("Namespace") + fe.Details = "only name, apiVersion and kind are supported fields" + return fe + }(), + }, { + name: "extra field, namespace and resourceVersion", + c: corev1.ObjectReference{ + Name: channelName, + APIVersion: channelAPIVersion, + Kind: channelKind, + Namespace: "secretnamespace", + ResourceVersion: "myresourceversion", + }, + want: func() *apis.FieldError { + fe := apis.ErrDisallowedFields("Namespace", "ResourceVersion") + fe.Details = "only name, apiVersion and kind are supported fields" + return fe + }(), + }, { + // Make sure that if an empty field for namespace is given, it's treated as not there. + name: "valid extra field, namespace empty", + c: corev1.ObjectReference{ + Name: channelName, + APIVersion: channelAPIVersion, + Kind: channelKind, + Namespace: "", + }, + want: nil, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := isValidChannel(test.c) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("isValidChannel (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/messaging/v1beta1/zz_generated.deepcopy.go b/pkg/apis/messaging/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..a666f9a7c91 --- /dev/null +++ b/pkg/apis/messaging/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,400 @@ +// +build !ignore_autogenerated + +/* +Copyright 2021 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 v1beta1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" + duckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" + apis "knative.dev/pkg/apis" + v1 "knative.dev/pkg/apis/duck/v1" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Channel) DeepCopyInto(out *Channel) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Channel. +func (in *Channel) DeepCopy() *Channel { + if in == nil { + return nil + } + out := new(Channel) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Channel) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ChannelList) DeepCopyInto(out *ChannelList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Channel, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelList. +func (in *ChannelList) DeepCopy() *ChannelList { + if in == nil { + return nil + } + out := new(ChannelList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ChannelList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ChannelSpec) DeepCopyInto(out *ChannelSpec) { + *out = *in + if in.ChannelTemplate != nil { + in, out := &in.ChannelTemplate, &out.ChannelTemplate + *out = new(ChannelTemplateSpec) + (*in).DeepCopyInto(*out) + } + in.ChannelableSpec.DeepCopyInto(&out.ChannelableSpec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelSpec. +func (in *ChannelSpec) DeepCopy() *ChannelSpec { + if in == nil { + return nil + } + out := new(ChannelSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ChannelStatus) DeepCopyInto(out *ChannelStatus) { + *out = *in + in.ChannelableStatus.DeepCopyInto(&out.ChannelableStatus) + if in.Channel != nil { + in, out := &in.Channel, &out.Channel + *out = new(v1.KReference) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelStatus. +func (in *ChannelStatus) DeepCopy() *ChannelStatus { + if in == nil { + return nil + } + out := new(ChannelStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ChannelTemplateSpec) DeepCopyInto(out *ChannelTemplateSpec) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Spec != nil { + in, out := &in.Spec, &out.Spec + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelTemplateSpec. +func (in *ChannelTemplateSpec) DeepCopy() *ChannelTemplateSpec { + if in == nil { + return nil + } + out := new(ChannelTemplateSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ChannelTemplateSpec) 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 *InMemoryChannel) DeepCopyInto(out *InMemoryChannel) { + *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 InMemoryChannel. +func (in *InMemoryChannel) DeepCopy() *InMemoryChannel { + if in == nil { + return nil + } + out := new(InMemoryChannel) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InMemoryChannel) 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 *InMemoryChannelList) DeepCopyInto(out *InMemoryChannelList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]InMemoryChannel, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InMemoryChannelList. +func (in *InMemoryChannelList) DeepCopy() *InMemoryChannelList { + if in == nil { + return nil + } + out := new(InMemoryChannelList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InMemoryChannelList) 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 *InMemoryChannelSpec) DeepCopyInto(out *InMemoryChannelSpec) { + *out = *in + in.ChannelableSpec.DeepCopyInto(&out.ChannelableSpec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InMemoryChannelSpec. +func (in *InMemoryChannelSpec) DeepCopy() *InMemoryChannelSpec { + if in == nil { + return nil + } + out := new(InMemoryChannelSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InMemoryChannelStatus) DeepCopyInto(out *InMemoryChannelStatus) { + *out = *in + in.ChannelableStatus.DeepCopyInto(&out.ChannelableStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InMemoryChannelStatus. +func (in *InMemoryChannelStatus) DeepCopy() *InMemoryChannelStatus { + if in == nil { + return nil + } + out := new(InMemoryChannelStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Subscription) DeepCopyInto(out *Subscription) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subscription. +func (in *Subscription) DeepCopy() *Subscription { + if in == nil { + return nil + } + out := new(Subscription) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Subscription) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubscriptionList) DeepCopyInto(out *SubscriptionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Subscription, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionList. +func (in *SubscriptionList) DeepCopy() *SubscriptionList { + if in == nil { + return nil + } + out := new(SubscriptionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SubscriptionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubscriptionSpec) DeepCopyInto(out *SubscriptionSpec) { + *out = *in + out.Channel = in.Channel + if in.Subscriber != nil { + in, out := &in.Subscriber, &out.Subscriber + *out = new(v1.Destination) + (*in).DeepCopyInto(*out) + } + if in.Reply != nil { + in, out := &in.Reply, &out.Reply + *out = new(v1.Destination) + (*in).DeepCopyInto(*out) + } + if in.Delivery != nil { + in, out := &in.Delivery, &out.Delivery + *out = new(duckv1beta1.DeliverySpec) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionSpec. +func (in *SubscriptionSpec) DeepCopy() *SubscriptionSpec { + if in == nil { + return nil + } + out := new(SubscriptionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubscriptionStatus) DeepCopyInto(out *SubscriptionStatus) { + *out = *in + in.Status.DeepCopyInto(&out.Status) + in.PhysicalSubscription.DeepCopyInto(&out.PhysicalSubscription) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionStatus. +func (in *SubscriptionStatus) DeepCopy() *SubscriptionStatus { + if in == nil { + return nil + } + out := new(SubscriptionStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SubscriptionStatusPhysicalSubscription) DeepCopyInto(out *SubscriptionStatusPhysicalSubscription) { + *out = *in + if in.SubscriberURI != nil { + in, out := &in.SubscriberURI, &out.SubscriberURI + *out = new(apis.URL) + (*in).DeepCopyInto(*out) + } + if in.ReplyURI != nil { + in, out := &in.ReplyURI, &out.ReplyURI + *out = new(apis.URL) + (*in).DeepCopyInto(*out) + } + if in.DeadLetterSinkURI != nil { + in, out := &in.DeadLetterSinkURI, &out.DeadLetterSinkURI + *out = new(apis.URL) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubscriptionStatusPhysicalSubscription. +func (in *SubscriptionStatusPhysicalSubscription) DeepCopy() *SubscriptionStatusPhysicalSubscription { + if in == nil { + return nil + } + out := new(SubscriptionStatusPhysicalSubscription) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/sources/v1alpha1/apiserver_conversion.go b/pkg/apis/sources/v1alpha1/apiserver_conversion.go new file mode 100644 index 00000000000..619ff14fce9 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/apiserver_conversion.go @@ -0,0 +1,162 @@ +/* +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 v1alpha1 + +import ( + "context" + "reflect" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + corev1 "k8s.io/api/core/v1" + "knative.dev/eventing/pkg/apis/sources/v1alpha2" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" +) + +// ConvertTo implements apis.Convertible. +// Converts source (from v1alpha1.ApiServerSource) into into a higher version. +func (source *ApiServerSource) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1alpha2.ApiServerSource: + // Meta + sink.ObjectMeta = source.ObjectMeta + + // Spec + + if len(source.Spec.Resources) > 0 { + sink.Spec.Resources = make([]v1alpha2.APIVersionKindSelector, len(source.Spec.Resources)) + } + for i, v := range source.Spec.Resources { + sink.Spec.Resources[i] = v1alpha2.APIVersionKindSelector{ + APIVersion: v.APIVersion, + Kind: v.Kind, + } + + if !cmp.Equal(v.LabelSelector, metav1.LabelSelector{}) { + sink.Spec.Resources[i].LabelSelector = &metav1.LabelSelector{} + v.LabelSelector.DeepCopyInto(sink.Spec.Resources[i].LabelSelector) + } + } + + switch source.Spec.Mode { + case RefMode: + sink.Spec.EventMode = v1alpha2.ReferenceMode + case ResourceMode: + sink.Spec.EventMode = v1alpha2.ResourceMode + } + + // Optional Spec + + if source.Spec.ResourceOwner != nil { + sink.Spec.ResourceOwner = source.Spec.ResourceOwner + } + + if source.Spec.Sink != nil { + var ref *duckv1.KReference + if source.Spec.Sink.Ref != nil { + ref = &duckv1.KReference{ + Kind: source.Spec.Sink.Ref.Kind, + Namespace: source.Spec.Sink.Ref.Namespace, + Name: source.Spec.Sink.Ref.Name, + APIVersion: source.Spec.Sink.Ref.APIVersion, + } + } + sink.Spec.Sink = duckv1.Destination{ + Ref: ref, + URI: source.Spec.Sink.URI, + } + } + + if source.Spec.CloudEventOverrides != nil { + sink.Spec.CloudEventOverrides = source.Spec.CloudEventOverrides.DeepCopy() + } + + sink.Spec.ServiceAccountName = source.Spec.ServiceAccountName + + // Status + source.Status.SourceStatus.DeepCopyInto(&sink.Status.SourceStatus) + return nil + default: + return apis.ConvertToViaProxy(ctx, source, &v1alpha2.ApiServerSource{}, sink) + } +} + +// ConvertFrom implements apis.Convertible. +// Converts obj from a higher version into v1alpha1.ApiServerSource. +func (sink *ApiServerSource) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1alpha2.ApiServerSource: + // Meta + sink.ObjectMeta = source.ObjectMeta + + // Spec + + switch source.Spec.EventMode { + case v1alpha2.ReferenceMode: + sink.Spec.Mode = RefMode + case v1alpha2.ResourceMode: + sink.Spec.Mode = ResourceMode + } + + sink.Spec.CloudEventOverrides = source.Spec.CloudEventOverrides + + sink.Spec.Sink = &duckv1beta1.Destination{ + URI: source.Spec.Sink.URI, + } + if source.Spec.Sink.Ref != nil { + sink.Spec.Sink.Ref = &corev1.ObjectReference{ + Kind: source.Spec.Sink.Ref.Kind, + Namespace: source.Spec.Sink.Ref.Namespace, + Name: source.Spec.Sink.Ref.Name, + APIVersion: source.Spec.Sink.Ref.APIVersion, + } + } + if sink.Spec.Sink != nil && reflect.DeepEqual(*sink.Spec.Sink, duckv1beta1.Destination{}) { + sink.Spec.Sink = nil + } + + if len(source.Spec.Resources) > 0 { + sink.Spec.Resources = make([]ApiServerResource, len(source.Spec.Resources)) + } + for i, v := range source.Spec.Resources { + sink.Spec.Resources[i] = ApiServerResource{} + sink.Spec.Resources[i].APIVersion = v.APIVersion + sink.Spec.Resources[i].Kind = v.Kind + if v.LabelSelector != nil { + sink.Spec.Resources[i].LabelSelector = *v.LabelSelector + } + } + + // Spec Optionals + + if source.Spec.ResourceOwner != nil { + sink.Spec.ResourceOwner = source.Spec.ResourceOwner + } + + sink.Spec.ServiceAccountName = source.Spec.ServiceAccountName + + // Status + source.Status.SourceStatus.DeepCopyInto(&sink.Status.SourceStatus) + + return nil + default: + return apis.ConvertFromViaProxy(ctx, source, &v1alpha2.ApiServerSource{}, sink) + } +} diff --git a/pkg/apis/sources/v1alpha1/apiserver_conversion_test.go b/pkg/apis/sources/v1alpha1/apiserver_conversion_test.go new file mode 100644 index 00000000000..f059cefac6c --- /dev/null +++ b/pkg/apis/sources/v1alpha1/apiserver_conversion_test.go @@ -0,0 +1,334 @@ +/* +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 v1alpha1 + +import ( + "context" + "errors" + "reflect" + "testing" + + corev1 "k8s.io/api/core/v1" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" + + v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/eventing/pkg/apis/sources/v1alpha2" + "knative.dev/eventing/pkg/apis/sources/v1beta1" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +// implement apis.Convertible +type testObject struct{} + +func (*testObject) ConvertTo(ctx context.Context, obj apis.Convertible) error { + return errors.New("Won't go") +} + +func (*testObject) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + return errors.New("Won't go") +} + +func TestApiServerSourceConversionBadType(t *testing.T) { + good, bad := &ApiServerSource{}, &testObject{} + + 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) + } +} + +func TestApiServerSourceConversionRoundTripUp(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&v1alpha2.ApiServerSource{}, &v1beta1.ApiServerSource{}, &v1.ApiServerSource{}} + + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1beta1.Destination{ + Ref: &corev1.ObjectReference{ + APIVersion: "Baf", + Kind: "Foo", + Name: "Baz", + Namespace: "Baz", + }, + DeprecatedAPIVersion: "depApi", + DeprecatedKind: "depKind", + DeprecatedName: "depName", + DeprecatedNamespace: "depNamespace", + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + tests := []struct { + name string + in *ApiServerSource + }{{name: "empty", + in: &ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: ApiServerSourceSpec{}, + Status: ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, + }, {name: "simple configuration", + in: &ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: ApiServerSourceSpec{ + Resources: []ApiServerResource{{ + APIVersion: "A1", + Kind: "K1", + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"A1": "K1"}, + }, + }, { + APIVersion: "A2", + Kind: "K2", + }}, + Sink: &sink, + Mode: "Ref", + }, + Status: ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "Unknown", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: ApiServerSourceSpec{ + Resources: []ApiServerResource{{ + APIVersion: "A1", + Kind: "K1", + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "aKey", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"the", "house"}, + }}, + }, + ControllerSelector: metav1.OwnerReference{}, + Controller: true, + }, { + APIVersion: "A2", + Kind: "K2", + }}, + ServiceAccountName: "adult", + Sink: &sink, + CloudEventOverrides: &duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + }, + Mode: "Resource", + }, + Status: ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }} + for _, test := range tests { + for _, version := range versions { + t.Run(test.name, func(t *testing.T) { + ver := version + if err := test.in.ConvertTo(context.Background(), ver); err != nil { + t.Error("ConvertTo() =", err) + } + + got := &ApiServerSource{} + + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Error("ConvertFrom() =", err) + } + fixed := fixApiServerSourceDeprecated(test.in) + if diff := cmp.Diff(fixed, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} + +// This tests round tripping from a higher version -> v1alpha1 and back to the higher version. +func TestApiServerSourceConversionRoundTripDown(t *testing.T) { + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + ceOverrides := duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + } + + tests := []struct { + name string + in apis.Convertible + }{{name: "empty", + in: &v1.ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: v1.ApiServerSourceSpec{}, + Status: v1.ApiServerSourceStatus{}, + }, + }, {name: "simple configuration", + in: &v1.ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: v1.ApiServerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + }, + Status: v1.ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &v1.ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: v1.ApiServerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + }, + Status: v1.ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }} + for _, test := range tests { + + t.Run(test.name, func(t *testing.T) { + down := &ApiServerSource{} + if err := down.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertTo() =", err) + } + + got := (reflect.New(reflect.TypeOf(test.in).Elem()).Interface()).(apis.Convertible) + + if err := down.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } +} + +// Since v1alpha1 to v1alpha2 is lossy. +func fixApiServerSourceDeprecated(in *ApiServerSource) *ApiServerSource { + for i := range in.Spec.Resources { + in.Spec.Resources[i].Controller = false + in.Spec.Resources[i].ControllerSelector = metav1.OwnerReference{} + } + if in.Spec.Sink != nil { + in.Spec.Sink.DeprecatedAPIVersion = "" + in.Spec.Sink.DeprecatedKind = "" + in.Spec.Sink.DeprecatedName = "" + in.Spec.Sink.DeprecatedNamespace = "" + } + return in +} diff --git a/pkg/apis/sources/v1alpha1/apiserver_defaults.go b/pkg/apis/sources/v1alpha1/apiserver_defaults.go new file mode 100644 index 00000000000..bf2992d9716 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/apiserver_defaults.go @@ -0,0 +1,29 @@ +/* +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 v1alpha1 + +import ( + "context" +) + +func (s *ApiServerSource) SetDefaults(ctx context.Context) { + s.Spec.SetDefaults(ctx) +} + +func (ss *ApiServerSourceSpec) SetDefaults(ctx context.Context) { + // TODO anything? +} diff --git a/pkg/apis/sources/v1alpha1/apiserver_defaults_test.go b/pkg/apis/sources/v1alpha1/apiserver_defaults_test.go new file mode 100644 index 00000000000..25f9e7defeb --- /dev/null +++ b/pkg/apis/sources/v1alpha1/apiserver_defaults_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 v1alpha1 + +import ( + "context" + "testing" +) + +// No-op test because method does nothing. +func TestAPIServerSourceDefaults(t *testing.T) { + s := ApiServerSource{} + s.SetDefaults(context.TODO()) +} diff --git a/pkg/apis/sources/v1alpha1/apiserver_lifecycle.go b/pkg/apis/sources/v1alpha1/apiserver_lifecycle.go new file mode 100644 index 00000000000..fba140a7e9a --- /dev/null +++ b/pkg/apis/sources/v1alpha1/apiserver_lifecycle.go @@ -0,0 +1,146 @@ +/* +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 v1alpha1 + +import ( + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/pkg/apis" +) + +const ( + // ApiServerConditionReady has status True when the ApiServerSource is ready to send events. + ApiServerConditionReady = apis.ConditionReady + + // ApiServerConditionSinkProvided has status True when the ApiServerSource has been configured with a sink target. + ApiServerConditionSinkProvided apis.ConditionType = "SinkProvided" + + // ApiServerConditionDeployed has status True when the ApiServerSource has had it's deployment created. + ApiServerConditionDeployed apis.ConditionType = "Deployed" + + // ApiServerConditionSufficientPermissions has status True when the ApiServerSource has sufficient permissions to access resources. + ApiServerConditionSufficientPermissions apis.ConditionType = "SufficientPermissions" +) + +var apiserverCondSet = apis.NewLivingConditionSet( + ApiServerConditionSinkProvided, + ApiServerConditionDeployed, + ApiServerConditionSufficientPermissions, +) + +// GetGroupVersionKind returns the GroupVersionKind. +func (s *ApiServerSource) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("ApiServerSource") +} + +// GetUntypedSpec returns the spec of the ApiServerSource. +func (s *ApiServerSource) GetUntypedSpec() interface{} { + return s.Spec +} + +// GetCondition returns the condition currently associated with the given type, or nil. +func (s *ApiServerSourceStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return apiserverCondSet.Manage(s).GetCondition(t) +} + +// GetTopLevelCondition returns the top level condition. +func (s *ApiServerSourceStatus) GetTopLevelCondition() *apis.Condition { + return apiserverCondSet.Manage(s).GetTopLevelCondition() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (s *ApiServerSourceStatus) InitializeConditions() { + apiserverCondSet.Manage(s).InitializeConditions() +} + +// MarkSink sets the condition that the source has a sink configured. +func (s *ApiServerSourceStatus) MarkSink(uri string) { + s.SinkURI = nil + if len(uri) > 0 { + if u, err := apis.ParseURL(uri); err != nil { + apiserverCondSet.Manage(s).MarkFalse(ApiServerConditionSinkProvided, "SinkInvalid", "Failed to parse sink: %v", err) + } else { + s.SinkURI = u + apiserverCondSet.Manage(s).MarkTrue(ApiServerConditionSinkProvided) + } + + } else { + apiserverCondSet.Manage(s).MarkFalse(ApiServerConditionSinkProvided, "SinkEmpty", "Sink has resolved to empty.") + } +} + +// MarkSinkWarnDeprecated sets the condition that the source has a sink configured and warns ref is deprecated. +func (s *ApiServerSourceStatus) MarkSinkWarnRefDeprecated(uri string) { + if u, err := apis.ParseURL(uri); err != nil { + s.SinkURI = nil + } else { + s.SinkURI = u + } + if len(uri) > 0 { + c := apis.Condition{ + Type: ApiServerConditionSinkProvided, + Status: corev1.ConditionTrue, + Severity: apis.ConditionSeverityError, + Message: "Using deprecated object ref fields when specifying spec.sink. Update to spec.sink.ref. These will be removed in the future.", + } + apiserverCondSet.Manage(s).SetCondition(c) + } else { + apiserverCondSet.Manage(s).MarkUnknown(ApiServerConditionSinkProvided, "SinkEmpty", "Sink has resolved to empty.%s", "") + } +} + +// MarkNoSink sets the condition that the source does not have a sink configured. +func (s *ApiServerSourceStatus) MarkNoSink(reason, messageFormat string, messageA ...interface{}) { + apiserverCondSet.Manage(s).MarkFalse(ApiServerConditionSinkProvided, reason, messageFormat, messageA...) +} + +// PropagateDeploymentAvailability uses the availability of the provided Deployment to determine if +// ApiServerConditionDeployed should be marked as true or false. +func (s *ApiServerSourceStatus) PropagateDeploymentAvailability(d *appsv1.Deployment) { + deploymentAvailableFound := false + for _, cond := range d.Status.Conditions { + if cond.Type == appsv1.DeploymentAvailable { + deploymentAvailableFound = true + if cond.Status == corev1.ConditionTrue { + apiserverCondSet.Manage(s).MarkTrue(ApiServerConditionDeployed) + } else if cond.Status == corev1.ConditionFalse { + apiserverCondSet.Manage(s).MarkFalse(ApiServerConditionDeployed, cond.Reason, cond.Message) + } else if cond.Status == corev1.ConditionUnknown { + apiserverCondSet.Manage(s).MarkUnknown(ApiServerConditionDeployed, cond.Reason, cond.Message) + } + } + } + if !deploymentAvailableFound { + apiserverCondSet.Manage(s).MarkUnknown(ApiServerConditionDeployed, "DeploymentUnavailable", "The Deployment '%s' is unavailable.", d.Name) + } +} + +// MarkSufficientPermissions sets the condition that the source has enough permissions to access the resources. +func (s *ApiServerSourceStatus) MarkSufficientPermissions() { + apiserverCondSet.Manage(s).MarkTrue(ApiServerConditionSufficientPermissions) +} + +// MarkNoSufficientPermissions sets the condition that the source does not have enough permissions to access the resources +func (s *ApiServerSourceStatus) MarkNoSufficientPermissions(reason, messageFormat string, messageA ...interface{}) { + apiserverCondSet.Manage(s).MarkFalse(ApiServerConditionSufficientPermissions, reason, messageFormat, messageA...) +} + +// IsReady returns true if the resource is ready overall. +func (s *ApiServerSourceStatus) IsReady() bool { + return apiserverCondSet.Manage(s).IsHappy() +} diff --git a/pkg/apis/sources/v1alpha1/apiserver_lifecycle_test.go b/pkg/apis/sources/v1alpha1/apiserver_lifecycle_test.go new file mode 100644 index 00000000000..f35cf2188b0 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/apiserver_lifecycle_test.go @@ -0,0 +1,304 @@ +/* +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 v1alpha1 + +import ( + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "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, + }, + }, + }, + } + + unavailableDeployment = &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unavailable", + }, + Status: appsv1.DeploymentStatus{ + Conditions: []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentAvailable, + Status: corev1.ConditionFalse, + }, + }, + }, + } + + unknownDeployment = &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unknown", + }, + Status: appsv1.DeploymentStatus{ + Conditions: []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentAvailable, + Status: corev1.ConditionUnknown, + }, + }, + }, + } +) + +func TestApiServerSourceGetGroupVersionKind(t *testing.T) { + r := &ApiServerSource{} + want := schema.GroupVersionKind{ + Group: "sources.knative.dev", + Version: "v1alpha1", + Kind: "ApiServerSource", + } + if got := r.GetGroupVersionKind(); got != want { + t.Errorf("got: %v, want: %v", got, want) + } +} + +func TestApiServerSourceStatusIsReady(t *testing.T) { + tests := []struct { + name string + s *ApiServerSourceStatus + wantConditionStatus corev1.ConditionStatus + want bool + }{{ + name: "uninitialized", + s: &ApiServerSourceStatus{}, + want: false, + }, { + name: "initialized", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink("uri://example") + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sufficient permissions", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSufficientPermissions() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink and sufficient permissions and deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink("uri://example") + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }, { + name: "mark sink and sufficient permissions and unavailable deployment", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink("uri://example") + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(unavailableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark sink and sufficient permissions and unknown deployment", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink("uri://example") + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(unknownDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink and sufficient permissions and not deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink("uri://example") + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(&appsv1.Deployment{}) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink and not enough permissions", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink("uri://example") + s.MarkNoSufficientPermissions("areason", "amessage") + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }} + + 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 TestApiServerSourceStatusGetCondition(t *testing.T) { + tests := []struct { + name string + s *ApiServerSourceStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "uninitialized", + s: &ApiServerSourceStatus{}, + condQuery: ApiServerConditionReady, + want: nil, + }, { + name: "initialized", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark sink", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink("uri://example") + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark sink and enough permissions and deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink("uri://example") + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionTrue, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.s.GetCondition(test.condQuery) + ignoreTime := cmpopts.IgnoreFields(apis.Condition{}, + "LastTransitionTime", "Severity") + if diff := cmp.Diff(test.want, got, ignoreTime); diff != "" { + t.Error("unexpected condition (-want, +got) =", diff) + } + }) + } +} + +func TestApiServerSourceGetters(t *testing.T) { + r := &ApiServerSource{ + Spec: ApiServerSourceSpec{ + ServiceAccountName: "test", + Mode: "test", + }, + } + if got, want := r.GetUntypedSpec(), r.Spec; !reflect.DeepEqual(got, want) { + t.Errorf("GetUntypedSpec() = %v, want: %v", got, want) + } +} diff --git a/pkg/apis/sources/v1alpha1/apiserver_types.go b/pkg/apis/sources/v1alpha1/apiserver_types.go new file mode 100644 index 00000000000..0dc3811a237 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/apiserver_types.go @@ -0,0 +1,123 @@ +/* +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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/eventing/pkg/apis/sources/v1alpha2" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ApiServerSource is the Schema for the apiserversources API +type ApiServerSource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ApiServerSourceSpec `json:"spec,omitempty"` + Status ApiServerSourceStatus `json:"status,omitempty"` +} + +var ( + // Check that we can create OwnerReferences to an ApiServerSource. + _ kmeta.OwnerRefable = (*ApiServerSource)(nil) + + // Check that ApiServerSource can return its spec untyped. + _ apis.HasSpec = (*ApiServerSource)(nil) +) + +// ApiServerSourceSpec defines the desired state of ApiServerSource +type ApiServerSourceSpec struct { + // Resources is the list of resources to watch + Resources []ApiServerResource `json:"resources"` + + // ServiceAccountName is the name of the ServiceAccount to use to run this + // source. + // +optional + ServiceAccountName string `json:"serviceAccountName,omitempty"` + + // Sink is a reference to an object that will resolve to a domain name to use as the sink. + // +optional + Sink *duckv1beta1.Destination `json:"sink,omitempty"` + + // CloudEventOverrides defines overrides to control the output format and + // modifications of the event sent to the sink. + // +optional + CloudEventOverrides *duckv1.CloudEventOverrides `json:"ceOverrides,omitempty"` + + // ResourceOwner is an additional filter to only track resources that are + // owned by a specific resource type. If ResourceOwner matches Resources[n] + // then Resources[n] is allowed to pass the ResourceOwner filter. + // +optional + ResourceOwner *v1alpha2.APIVersionKind `json:"owner,omitempty"` + + // Mode is the mode the receive adapter controller runs under: Ref or Resource. + // `Ref` sends only the reference to the resource. + // `Resource` send the full resource. + Mode string `json:"mode,omitempty"` +} + +// ApiServerSourceStatus defines the observed state of ApiServerSource +type ApiServerSourceStatus 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"` +} + +// ApiServerResource defines the resource to watch +type ApiServerResource struct { + // API version of the resource to watch. + APIVersion string `json:"apiVersion"` + + // Kind of the resource to watch. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + Kind string `json:"kind"` + + // LabelSelector restricts this source to objects with the selected labels + // More info: http://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + LabelSelector metav1.LabelSelector `json:"labelSelector"` + + // ControllerSelector restricts this source to objects with a controlling owner reference of the specified kind. + // Only apiVersion and kind are used. Both are optional. + // Deprecated: Per-resource owner refs will no longer be supported in + // v1alpha2, please use Spec.Owner as a GKV. + ControllerSelector metav1.OwnerReference `json:"controllerSelector"` + + // If true, send an event referencing the object controlling the resource + // Deprecated: Per-resource controller flag will no longer be supported in + // v1alpha2, please use Spec.Owner as a GKV. + Controller bool `json:"controller"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ApiServerSourceList contains a list of ApiServerSource +type ApiServerSourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ApiServerSource `json:"items"` +} diff --git a/pkg/apis/sources/v1alpha1/apiserver_types_test.go b/pkg/apis/sources/v1alpha1/apiserver_types_test.go new file mode 100644 index 00000000000..166ef50c11c --- /dev/null +++ b/pkg/apis/sources/v1alpha1/apiserver_types_test.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. +*/ + +package v1alpha1 + +import "testing" + +func TestApiServerSource_GetGroupVersionKind(t *testing.T) { + ass := ApiServerSource{} + gvk := ass.GetGroupVersionKind() + if gvk.Kind != "ApiServerSource" { + t.Errorf("Should be ApiServerSource.") + } +} diff --git a/pkg/apis/sources/v1alpha1/apiserver_validation.go b/pkg/apis/sources/v1alpha1/apiserver_validation.go new file mode 100644 index 00000000000..8c906871ec8 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/apiserver_validation.go @@ -0,0 +1,56 @@ +/* +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 v1alpha1 + +import ( + "context" + + "knative.dev/pkg/apis" +) + +const ( + // RefMode produces payloads of ObjectReference + RefMode = "Ref" + // ResourceMode produces payloads of ResourceEvent + ResourceMode = "Resource" +) + +func (c *ApiServerSource) Validate(ctx context.Context) *apis.FieldError { + return c.Spec.Validate(ctx).ViaField("spec") +} + +func (cs *ApiServerSourceSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + + // Validate mode, if can be empty or set as certain value + if cs.Mode != "" && cs.Mode != RefMode && cs.Mode != ResourceMode { + fe := &apis.FieldError{ + Message: "Mode is not valid", + Paths: []string{"mode"}, + } + errs = errs.Also(fe) + } + + // Validate sink + if cs.Sink == nil { + fe := apis.ErrMissingField("sink") + errs = errs.Also(fe) + } else if fe := cs.Sink.Validate(ctx); fe != nil { + errs = errs.Also(fe.ViaField("sink")) + } + return errs +} diff --git a/pkg/apis/sources/v1alpha1/apiserver_validation_test.go b/pkg/apis/sources/v1alpha1/apiserver_validation_test.go new file mode 100644 index 00000000000..6ec51607a87 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/apiserver_validation_test.go @@ -0,0 +1,98 @@ +/* +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 v1alpha1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" +) + +func TestAPIServerValidation(t *testing.T) { + tests := []struct { + name string + spec ApiServerSourceSpec + want *apis.FieldError + }{{ + name: "valid spec", + spec: ApiServerSourceSpec{ + Mode: "Resource", + Resources: []ApiServerResource{ + {}, + }, + Sink: &duckv1beta1.Destination{ + Ref: &corev1.ObjectReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + want: nil, + }, { + name: "empty sink", + spec: ApiServerSourceSpec{ + Mode: "Resource", + Resources: []ApiServerResource{ + {}, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrMissingField("sink") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "invalid mode", + spec: ApiServerSourceSpec{ + Mode: "Test", + Resources: []ApiServerResource{ + {}, + }, + Sink: &duckv1beta1.Destination{ + Ref: &corev1.ObjectReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := &apis.FieldError{ + Message: "Mode is not valid", + Paths: []string{"mode"}, + } + errs = errs.Also(fe) + return errs + }(), + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.spec.Validate(context.TODO()) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("APIServerSourceSpec.Validate (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha1/doc.go b/pkg/apis/sources/v1alpha1/doc.go new file mode 100644 index 00000000000..ecb0830e681 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/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 v1alpha1 contains API Schema definitions for the sources v1alpha1 API group +// +k8s:deepcopy-gen=package +// +groupName=sources.knative.dev +package v1alpha1 diff --git a/pkg/apis/sources/v1alpha1/implements_test.go b/pkg/apis/sources/v1alpha1/implements_test.go new file mode 100644 index 00000000000..da15422299c --- /dev/null +++ b/pkg/apis/sources/v1alpha1/implements_test.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. +*/ + +package v1alpha1 + +import ( + "testing" + + "knative.dev/pkg/apis/duck" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" +) + +func TestTypesImplements(t *testing.T) { + testCases := []struct { + instance interface{} + iface duck.Implementable + }{ + // ApiServerSource + {instance: &ApiServerSource{}, iface: &duckv1.Conditions{}}, + // SinkBinding + {instance: &SinkBinding{}, iface: &duckv1.Conditions{}}, + {instance: &SinkBinding{}, iface: &duckv1beta1.Source{}}, + {instance: &SinkBinding{}, iface: &duckv1alpha1.Binding{}}, + } + for _, tc := range testCases { + if err := duck.VerifyType(tc.instance, tc.iface); err != nil { + t.Error(err) + } + } +} diff --git a/pkg/apis/sources/v1alpha1/register.go b/pkg/apis/sources/v1alpha1/register.go new file mode 100644 index 00000000000..8120c413cad --- /dev/null +++ b/pkg/apis/sources/v1alpha1/register.go @@ -0,0 +1,55 @@ +/* +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 v1alpha1 + +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: "v1alpha1"} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &ApiServerSource{}, + &ApiServerSourceList{}, + &SinkBinding{}, + &SinkBindingList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/apis/eventing/v1beta1/register_test.go b/pkg/apis/sources/v1alpha1/register_test.go similarity index 52% rename from pkg/apis/eventing/v1beta1/register_test.go rename to pkg/apis/sources/v1alpha1/register_test.go index 589138701d3..fad04bc1a8a 100644 --- a/pkg/apis/eventing/v1beta1/register_test.go +++ b/pkg/apis/sources/v1alpha1/register_test.go @@ -1,5 +1,5 @@ /* -Copyright 2021 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. @@ -14,39 +14,60 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1beta1 +package v1alpha1 import ( "testing" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/google/go-cmp/cmp" ) -func TestKind(t *testing.T) { - schemaGroupKind := Kind("EventType") - if schemaGroupKind.Kind != "EventType" || schemaGroupKind.Group != "eventing.knative.dev" { - t.Errorf("Unexpected GroupKind: %+v", schemaGroupKind) +// 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) } } -func TestResource(t *testing.T) { - schemaGroupResource := Resource("EventType") - if schemaGroupResource.Group != "eventing.knative.dev" || schemaGroupResource.Resource != "EventType" { - t.Errorf("Unexpected GroupResource: %+v", schemaGroupResource) +// 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{ - "EventType", - "EventTypeList", + "ApiServerSource", + "ApiServerSourceList", + "SinkBinding", + "SinkBindingList", } { if _, ok := types[name]; !ok { t.Errorf("Did not find %q as registered type", name) } } + } diff --git a/pkg/apis/sources/v1alpha1/sinkbinding_context.go b/pkg/apis/sources/v1alpha1/sinkbinding_context.go new file mode 100644 index 00000000000..c419f3ec634 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/sinkbinding_context.go @@ -0,0 +1,43 @@ +/* +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 v1alpha1 + +import ( + "context" + + "knative.dev/pkg/apis" +) + +// sinkURIKey is used as the key for associating information +// with a context.Context. +type sinkURIKey struct{} + +// WithSinkURI notes on the context for binding that the resolved SinkURI +// is the provided apis.URL. +func WithSinkURI(ctx context.Context, uri *apis.URL) context.Context { + return context.WithValue(ctx, sinkURIKey{}, uri) +} + +// GetSinkURI accesses the apis.URL for the Sink URI that has been associated +// with this context. +func GetSinkURI(ctx context.Context) *apis.URL { + value := ctx.Value(sinkURIKey{}) + if value == nil { + return nil + } + return value.(*apis.URL) +} diff --git a/pkg/apis/sources/v1alpha1/sinkbinding_context_test.go b/pkg/apis/sources/v1alpha1/sinkbinding_context_test.go new file mode 100644 index 00000000000..8bcf03e683b --- /dev/null +++ b/pkg/apis/sources/v1alpha1/sinkbinding_context_test.go @@ -0,0 +1,43 @@ +/* +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 v1alpha1 + +import ( + "context" + "testing" + + "knative.dev/pkg/apis" +) + +func TestGetSinkURI(t *testing.T) { + ctx := context.Background() + + if uri := GetSinkURI(ctx); uri != nil { + t.Errorf("GetSinkURI() = %v, wanted nil", uri) + } + + want := &apis.URL{ + Scheme: "https", + Host: "knobots.io", + Path: "/omg/a/path", + } + ctx = WithSinkURI(ctx, want) + + if got := GetSinkURI(ctx); got != want { + t.Errorf("GetSinkURI() = %v, wanted %v", got, want) + } +} diff --git a/pkg/apis/sources/v1alpha1/sinkbinding_conversion.go b/pkg/apis/sources/v1alpha1/sinkbinding_conversion.go new file mode 100644 index 00000000000..ba35d565054 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/sinkbinding_conversion.go @@ -0,0 +1,55 @@ +/* +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 v1alpha1 + +import ( + "context" + + "knative.dev/eventing/pkg/apis/sources/v1alpha2" + "knative.dev/pkg/apis" +) + +// ConvertTo implements apis.Convertible. +// Converts source from v1alpha1.SinkBinding into a higher version. +func (source *SinkBinding) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1alpha2.SinkBinding: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.SourceSpec = source.Spec.SourceSpec + sink.Spec.BindingSpec = source.Spec.BindingSpec + sink.Status.SourceStatus = source.Status.SourceStatus + return nil + default: + return apis.ConvertToViaProxy(ctx, source, &v1alpha2.SinkBinding{}, sink) + } +} + +// ConvertFrom implements apis.Convertible. +// Converts obj from a higher version into v1alpha1.SinkBinding. +func (sink *SinkBinding) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1alpha2.SinkBinding: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.Subject = source.Spec.Subject + sink.Spec.SourceSpec = source.Spec.SourceSpec + sink.Spec.BindingSpec = source.Spec.BindingSpec + sink.Status.SourceStatus = source.Status.SourceStatus + return nil + default: + return apis.ConvertFromViaProxy(ctx, source, &v1alpha2.SinkBinding{}, sink) + } +} diff --git a/pkg/apis/sources/v1alpha1/sinkbinding_conversion_test.go b/pkg/apis/sources/v1alpha1/sinkbinding_conversion_test.go new file mode 100644 index 00000000000..1eca4442fb5 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/sinkbinding_conversion_test.go @@ -0,0 +1,294 @@ +/* +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 v1alpha1 + +import ( + "context" + "reflect" + "testing" + + "knative.dev/pkg/tracker" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/eventing/pkg/apis/sources/v1alpha2" + "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" +) + +func TestSinkBindingConversionBadType(t *testing.T) { + good, bad := &SinkBinding{}, &testObject{} + + 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) + } +} + +func TestSinkBindingConversionRoundTripUp(t *testing.T) { + versions := []apis.Convertible{&v1.SinkBinding{}, &v1beta1.SinkBinding{}, &v1alpha2.SinkBinding{}} + + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + subject := tracker.Reference{ + APIVersion: "API", + Kind: "K", + Namespace: "NS", + Name: "N", + } + + tests := []struct { + name string + in *SinkBinding + }{{name: "empty", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: SinkBindingSpec{}, + Status: SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, + }, {name: "simple configuration", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: subject, + }, + }, + Status: SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "Unknown", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + }, + }, + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: subject, + }, + }, + Status: SinkBindingStatus{ + 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 := &SinkBinding{} + + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} + +// This tests round tripping from a higher version -> v1alpha1 and back to the higher version. +func TestSinkBindingConversionRoundTripDown(t *testing.T) { + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + ceOverrides := duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + } + + subject := tracker.Reference{ + APIVersion: "API", + Kind: "K", + Namespace: "NS", + Name: "N", + } + + tests := []struct { + name string + in apis.Convertible + }{{name: "empty", + in: &v1alpha2.SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1alpha2.SinkBindingSpec{}, + Status: v1alpha2.SinkBindingStatus{}, + }, + }, {name: "simple configuration", + in: &v1alpha2.SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1alpha2.SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + }, + Status: v1alpha2.SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &v1beta1.SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta1.SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + BindingSpec: duckv1beta1.BindingSpec{Subject: subject}, + }, + Status: v1beta1.SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }} + for _, test := range tests { + + t.Run(test.name, func(t *testing.T) { + down := &SinkBinding{} + if err := down.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertTo() =", err) + } + + got := (reflect.New(reflect.TypeOf(test.in).Elem()).Interface()).(apis.Convertible) + + if err := down.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha1/sinkbinding_defaults.go b/pkg/apis/sources/v1alpha1/sinkbinding_defaults.go new file mode 100644 index 00000000000..44cde50939d --- /dev/null +++ b/pkg/apis/sources/v1alpha1/sinkbinding_defaults.go @@ -0,0 +1,34 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "context" + + "knative.dev/pkg/apis" +) + +// SetDefaults implements apis.Defaultable +func (fb *SinkBinding) SetDefaults(ctx context.Context) { + if fb.Spec.Subject.Namespace == "" { + // Default the subject's namespace to our namespace. + fb.Spec.Subject.Namespace = fb.Namespace + } + + withNS := apis.WithinParent(ctx, fb.ObjectMeta) + fb.Spec.Sink.SetDefaults(withNS) +} diff --git a/pkg/apis/sources/v1alpha1/sinkbinding_defaults_test.go b/pkg/apis/sources/v1alpha1/sinkbinding_defaults_test.go new file mode 100644 index 00000000000..573f5d538a2 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/sinkbinding_defaults_test.go @@ -0,0 +1,151 @@ +/* +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 v1alpha1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" + "knative.dev/pkg/tracker" +) + +func TestSinkBindingDefaulting(t *testing.T) { + tests := []struct { + name string + in *SinkBinding + want *SinkBinding + }{{ + name: "namespace is defaulted", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "serving.knative.dev/v1", + Kind: "Service", + Name: "gemma", + }, + }, + }, + }, + }, + want: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + // This is filled in by defaulting. + Namespace: "moore", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "serving.knative.dev/v1", + Kind: "Service", + Name: "gemma", + // This is filled in by defaulting. + Namespace: "moore", + }, + }, + }, + }, + }, + }, { + name: "no ref, given namespace", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + Namespace: "lorefice", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + URI: &apis.URL{ + Scheme: "http", + Host: "moore.dev", + }, + }, + }, + }, + }, + want: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + Namespace: "lorefice", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + URI: &apis.URL{ + Scheme: "http", + Host: "moore.dev", + }, + }, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.in + got.SetDefaults(context.Background()) + if !cmp.Equal(test.want, got) { + t.Error("SetDefaults (-want, +got) =", cmp.Diff(test.want, got)) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha1/sinkbinding_lifecycle.go b/pkg/apis/sources/v1alpha1/sinkbinding_lifecycle.go new file mode 100644 index 00000000000..2e9fad2ebd0 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/sinkbinding_lifecycle.go @@ -0,0 +1,152 @@ +/* +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 v1alpha1 + +import ( + "context" + "encoding/json" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + "knative.dev/pkg/apis" + "knative.dev/pkg/apis/duck" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/logging" + "knative.dev/pkg/tracker" +) + +var sbCondSet = apis.NewLivingConditionSet() + +// GetGroupVersionKind returns the GroupVersionKind. +func (s *SinkBinding) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("SinkBinding") +} + +// GetUntypedSpec implements apis.HasSpec +func (s *SinkBinding) GetUntypedSpec() interface{} { + return s.Spec +} + +// GetSubject implements psbinding.Bindable +func (sb *SinkBinding) GetSubject() tracker.Reference { + return sb.Spec.Subject +} + +// GetBindingStatus implements psbinding.Bindable +func (sb *SinkBinding) GetBindingStatus() duck.BindableStatus { + return &sb.Status +} + +// SetObservedGeneration implements psbinding.BindableStatus +func (sbs *SinkBindingStatus) SetObservedGeneration(gen int64) { + sbs.ObservedGeneration = gen +} + +// InitializeConditions populates the SinkBindingStatus's conditions field +// with all of its conditions configured to Unknown. +func (sbs *SinkBindingStatus) InitializeConditions() { + sbCondSet.Manage(sbs).InitializeConditions() +} + +// MarkBindingUnavailable marks the SinkBinding's Ready condition to False with +// the provided reason and message. +func (sbs *SinkBindingStatus) MarkBindingUnavailable(reason, message string) { + sbCondSet.Manage(sbs).MarkFalse(SinkBindingConditionReady, reason, message) +} + +// MarkBindingAvailable marks the SinkBinding's Ready condition to True. +func (sbs *SinkBindingStatus) MarkBindingAvailable() { + sbCondSet.Manage(sbs).MarkTrue(SinkBindingConditionReady) +} + +// Do implements psbinding.Bindable +func (sb *SinkBinding) Do(ctx context.Context, ps *duckv1.WithPod) { + // First undo so that we can just unconditionally append below. + sb.Undo(ctx, ps) + + uri := GetSinkURI(ctx) + if uri == nil { + logging.FromContext(ctx).Errorf("No sink URI associated with context for %+v", sb) + return + } + + var ceOverrides string + if sb.Spec.CloudEventOverrides != nil { + if co, err := json.Marshal(sb.Spec.SourceSpec.CloudEventOverrides); err != nil { + logging.FromContext(ctx).Errorf("Failed to marshal CloudEventOverrides into JSON for %+v, %v", sb, err) + } else if len(co) > 0 { + ceOverrides = string(co) + } + } + + spec := ps.Spec.Template.Spec + for i := range spec.InitContainers { + spec.InitContainers[i].Env = append(spec.InitContainers[i].Env, corev1.EnvVar{ + Name: "K_SINK", + Value: uri.String(), + }) + spec.InitContainers[i].Env = append(spec.InitContainers[i].Env, corev1.EnvVar{ + Name: "K_CE_OVERRIDES", + Value: ceOverrides, + }) + } + for i := range spec.Containers { + spec.Containers[i].Env = append(spec.Containers[i].Env, corev1.EnvVar{ + Name: "K_SINK", + Value: uri.String(), + }) + spec.Containers[i].Env = append(spec.Containers[i].Env, corev1.EnvVar{ + Name: "K_CE_OVERRIDES", + Value: ceOverrides, + }) + } +} + +func (sb *SinkBinding) Undo(ctx context.Context, ps *duckv1.WithPod) { + spec := ps.Spec.Template.Spec + for i, c := range spec.InitContainers { + if len(c.Env) == 0 { + continue + } + env := make([]corev1.EnvVar, 0, len(spec.InitContainers[i].Env)) + for j, ev := range c.Env { + switch ev.Name { + case "K_SINK", "K_CE_OVERRIDES": + continue + default: + env = append(env, spec.InitContainers[i].Env[j]) + } + } + spec.InitContainers[i].Env = env + } + for i, c := range spec.Containers { + if len(c.Env) == 0 { + continue + } + env := make([]corev1.EnvVar, 0, len(spec.Containers[i].Env)) + for j, ev := range c.Env { + switch ev.Name { + case "K_SINK", "K_CE_OVERRIDES": + continue + default: + env = append(env, spec.Containers[i].Env[j]) + } + } + spec.Containers[i].Env = env + } +} diff --git a/pkg/apis/sources/v1alpha1/sinkbinding_lifecycle_test.go b/pkg/apis/sources/v1alpha1/sinkbinding_lifecycle_test.go new file mode 100644 index 00000000000..3d47298981a --- /dev/null +++ b/pkg/apis/sources/v1alpha1/sinkbinding_lifecycle_test.go @@ -0,0 +1,506 @@ +/* +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 v1alpha1 + +import ( + "context" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" + "knative.dev/pkg/tracker" +) + +func TestSinkBindingGetGroupVersionKind(t *testing.T) { + r := &SinkBinding{} + want := schema.GroupVersionKind{ + Group: "sources.knative.dev", + Version: "v1alpha1", + Kind: "SinkBinding", + } + if got := r.GetGroupVersionKind(); got != want { + t.Errorf("got: %v, want: %v", got, want) + } +} + +func TestSinkBindingGetters(t *testing.T) { + r := &SinkBinding{ + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "foo", + }, + }, + }, + } + if got, want := r.GetUntypedSpec(), r.Spec; !reflect.DeepEqual(got, want) { + t.Errorf("GetUntypedSpec() = %v, want: %v", got, want) + } + if got, want := r.GetSubject(), r.Spec.Subject; !reflect.DeepEqual(got, want) { + t.Errorf("GetSubject() = %v, want: %v", got, want) + } + if got, want := r.GetBindingStatus(), &r.Status; !reflect.DeepEqual(got, want) { + t.Errorf("GetBindingStatus() = %v, want: %v", got, want) + } +} + +func TestSinkBindingSetObsGen(t *testing.T) { + r := &SinkBinding{ + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "foo", + }, + }, + }, + } + want := int64(3762) + r.GetBindingStatus().SetObservedGeneration(want) + if got := r.Status.ObservedGeneration; got != want { + t.Errorf("SetObservedGeneration() = %d, wanted %d", got, want) + } +} + +func TestSinkBindingStatusIsReady(t *testing.T) { + tests := []struct { + name string + s *SinkBindingStatus + want bool + }{{ + name: "uninitialized", + s: &SinkBindingStatus{}, + want: false, + }, { + name: "initialized", + s: func() *SinkBindingStatus { + s := &SinkBindingStatus{} + s.InitializeConditions() + return s + }(), + want: false, + }, { + name: "mark available", + s: func() *SinkBindingStatus { + s := &SinkBindingStatus{} + s.InitializeConditions() + s.MarkBindingUnavailable("TheReason", "this is the message") + return s + }(), + want: false, + }, { + name: "mark available", + s: func() *SinkBindingStatus { + s := &SinkBindingStatus{} + s.InitializeConditions() + s.MarkBindingAvailable() + return s + }(), + want: true, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.s.IsReady() + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("%s: unexpected condition (-want, +got) = %v", test.name, diff) + } + }) + } +} + +func TestSinkBindingUndo(t *testing.T) { + tests := []struct { + name string + in *duckv1.WithPod + want *duckv1.WithPod + }{{ + name: "nothing to remove", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + }}, + }, + }, + }, + }, + }, { + name: "lots to remove", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{ + Name: "setup", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "K_SINK", + Value: "http://localhost:8080", + }, { + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "K_SINK", + Value: "http://localhost:8080", + }, { + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }, { + Name: "sidecar", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: "http://localhost:8080", + }, { + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{ + Name: "setup", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "BAZ", + Value: "INGA", + }}, + }}, + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "BAZ", + Value: "INGA", + }}, + }, { + Name: "sidecar", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "BAZ", + Value: "INGA", + }}, + }}, + }, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.in + sb := &SinkBinding{} + sb.Undo(context.Background(), got) + + if !cmp.Equal(got, test.want) { + t.Error("Undo (-want, +got):", cmp.Diff(test.want, got)) + } + }) + } +} + +func TestSinkBindingDo(t *testing.T) { + sinkURI := &apis.URL{ + Scheme: "http", + Host: "thing.ns.svc.cluster.local", + Path: "/a/path", + } + + overrides := duckv1.CloudEventOverrides{Extensions: map[string]string{"foo": "bar"}} + + tests := []struct { + name string + in *duckv1.WithPod + want *duckv1.WithPod + }{{ + name: "nothing to add", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: sinkURI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: sinkURI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + }, { + name: "fix the URI", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: "the wrong value", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"wrong":"value"}}`, + }}, + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: sinkURI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + }, { + name: "lots to add", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{ + Name: "setup", + Image: "busybox", + }}, + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "BAZ", + Value: "INGA", + }}, + }, { + Name: "sidecar", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "BAZ", + Value: "INGA", + }}, + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{ + Name: "setup", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: sinkURI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_SINK", + Value: sinkURI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }, { + Name: "sidecar", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_SINK", + Value: sinkURI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.in + + ctx := WithSinkURI(context.Background(), sinkURI) + + sb := &SinkBinding{Spec: SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + CloudEventOverrides: &overrides, + }, + }} + sb.Do(ctx, got) + + if !cmp.Equal(got, test.want) { + t.Error("Undo (-want, +got):", cmp.Diff(test.want, got)) + } + }) + } +} + +func TestSinkBindingDoNoURI(t *testing.T) { + want := &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{}, + }}, + }, + }, + }, + } + got := &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: "this should be removed", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"tobe":"removed"}}`, + }}, + }}, + }, + }, + }, + } + + sb := &SinkBinding{} + sb.Do(context.Background(), got) + + if !cmp.Equal(got, want) { + t.Error("Undo (-want, +got):", cmp.Diff(want, got)) + } +} diff --git a/pkg/apis/sources/v1alpha1/sinkbinding_types.go b/pkg/apis/sources/v1alpha1/sinkbinding_types.go new file mode 100644 index 00000000000..9a902b934f9 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/sinkbinding_types.go @@ -0,0 +1,79 @@ +/* +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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:defaulter-gen=true + +// SinkBinding describes a Binding that is also a Source. +// The `sink` (from the Source duck) is resolved to a URL and +// then projected into the `subject` by augmenting the runtime +// contract of the referenced containers to have a `K_SINK` +// environment variable holding the endpoint to which to send +// cloud events. +type SinkBinding struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SinkBindingSpec `json:"spec"` + Status SinkBindingStatus `json:"status"` +} + +// Check the interfaces that SinkBinding should be implementing. +var ( + _ runtime.Object = (*SinkBinding)(nil) + _ kmeta.OwnerRefable = (*SinkBinding)(nil) + _ apis.Validatable = (*SinkBinding)(nil) + _ apis.Defaultable = (*SinkBinding)(nil) + _ apis.HasSpec = (*SinkBinding)(nil) +) + +// SinkBindingSpec holds the desired state of the SinkBinding (from the client). +type SinkBindingSpec struct { + duckv1.SourceSpec `json:",inline"` + duckv1alpha1.BindingSpec `json:",inline"` +} + +const ( + // SinkBindingConditionReady is configured to indicate whether the Binding + // has been configured for resources subject to its runtime contract. + SinkBindingConditionReady = apis.ConditionReady +) + +// SinkBindingStatus communicates the observed state of the SinkBinding (from the controller). +type SinkBindingStatus struct { + duckv1.SourceStatus `json:",inline"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// SinkBindingList contains a list of SinkBinding +type SinkBindingList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []SinkBinding `json:"items"` +} diff --git a/pkg/apis/sources/v1alpha1/sinkbinding_types_test.go b/pkg/apis/sources/v1alpha1/sinkbinding_types_test.go new file mode 100644 index 00000000000..9242af7f80b --- /dev/null +++ b/pkg/apis/sources/v1alpha1/sinkbinding_types_test.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. +*/ + +package v1alpha1 + +import "testing" + +func TestSinkBinding_GetGroupVersionKind(t *testing.T) { + sb := SinkBinding{} + gvk := sb.GetGroupVersionKind() + if gvk.Kind != "SinkBinding" { + t.Errorf("Should be SinkBinding.") + } +} diff --git a/pkg/apis/sources/v1alpha1/sinkbinding_validation.go b/pkg/apis/sources/v1alpha1/sinkbinding_validation.go new file mode 100644 index 00000000000..1d82e89ce93 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/sinkbinding_validation.go @@ -0,0 +1,38 @@ +/* +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 v1alpha1 + +import ( + "context" + + "knative.dev/pkg/apis" +) + +// Validate implements apis.Validatable +func (fb *SinkBinding) Validate(ctx context.Context) *apis.FieldError { + err := fb.Spec.Validate(ctx).ViaField("spec") + if fb.Spec.Subject.Namespace != "" && fb.Namespace != fb.Spec.Subject.Namespace { + err = err.Also(apis.ErrInvalidValue(fb.Spec.Subject.Namespace, "spec.subject.namespace")) + } + return err +} + +// Validate implements apis.Validatable +func (fbs *SinkBindingSpec) Validate(ctx context.Context) *apis.FieldError { + return fbs.Subject.Validate(ctx).ViaField("subject").Also( + fbs.Sink.Validate(ctx).ViaField("sink")) +} diff --git a/pkg/apis/sources/v1alpha1/sinkbinding_validation_test.go b/pkg/apis/sources/v1alpha1/sinkbinding_validation_test.go new file mode 100644 index 00000000000..e37f77732cc --- /dev/null +++ b/pkg/apis/sources/v1alpha1/sinkbinding_validation_test.go @@ -0,0 +1,126 @@ +/* +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 v1alpha1 + +import ( + "context" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" + "knative.dev/pkg/tracker" +) + +func TestSinkBindingValidation(t *testing.T) { + tests := []struct { + name string + in *SinkBinding + want *apis.FieldError + }{{ + name: "missing subject namespace", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "serving.knative.dev/v1", + Kind: "Service", + Name: "gemma", + Namespace: "namespace", + }, + }, + }, + }, + }, + want: apis.ErrMissingField("spec.subject.namespace"), + }, { + name: "invalid subject namespace", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + Namespace: "lorefice", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "serving.knative.dev/v1", + Kind: "Service", + Name: "gemma", + Namespace: "namespace", + }, + }, + }, + }, + }, + want: apis.ErrInvalidValue("lorefice", "spec.subject.namespace"), + }, { + name: "missing sink information", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + Namespace: "moore", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{}, + }, + }, + }, + want: apis.ErrGeneric("expected at least one, got none", "spec.sink.ref", "spec.sink.uri"), + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.in.Validate(context.Background()) + if (test.want != nil) != (got != nil) { + t.Errorf("Validation() = %v, wanted %v", got, test.want) + } else if test.want != nil && test.want.Error() != got.Error() { + t.Errorf("Validation() = %v, wanted %v", got, test.want) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/sources/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..1c73a0b5df1 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,258 @@ +// +build !ignore_autogenerated + +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + v1 "knative.dev/pkg/apis/duck/v1" + v1beta1 "knative.dev/pkg/apis/duck/v1beta1" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApiServerResource) DeepCopyInto(out *ApiServerResource) { + *out = *in + in.LabelSelector.DeepCopyInto(&out.LabelSelector) + in.ControllerSelector.DeepCopyInto(&out.ControllerSelector) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApiServerResource. +func (in *ApiServerResource) DeepCopy() *ApiServerResource { + if in == nil { + return nil + } + out := new(ApiServerResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApiServerSource) DeepCopyInto(out *ApiServerSource) { + *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 ApiServerSource. +func (in *ApiServerSource) DeepCopy() *ApiServerSource { + if in == nil { + return nil + } + out := new(ApiServerSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ApiServerSource) 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 *ApiServerSourceList) DeepCopyInto(out *ApiServerSourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ApiServerSource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApiServerSourceList. +func (in *ApiServerSourceList) DeepCopy() *ApiServerSourceList { + if in == nil { + return nil + } + out := new(ApiServerSourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ApiServerSourceList) 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 *ApiServerSourceSpec) DeepCopyInto(out *ApiServerSourceSpec) { + *out = *in + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make([]ApiServerResource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Sink != nil { + in, out := &in.Sink, &out.Sink + *out = new(v1beta1.Destination) + (*in).DeepCopyInto(*out) + } + if in.CloudEventOverrides != nil { + in, out := &in.CloudEventOverrides, &out.CloudEventOverrides + *out = new(v1.CloudEventOverrides) + (*in).DeepCopyInto(*out) + } + if in.ResourceOwner != nil { + in, out := &in.ResourceOwner, &out.ResourceOwner + *out = new(v1alpha2.APIVersionKind) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApiServerSourceSpec. +func (in *ApiServerSourceSpec) DeepCopy() *ApiServerSourceSpec { + if in == nil { + return nil + } + out := new(ApiServerSourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApiServerSourceStatus) DeepCopyInto(out *ApiServerSourceStatus) { + *out = *in + in.SourceStatus.DeepCopyInto(&out.SourceStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApiServerSourceStatus. +func (in *ApiServerSourceStatus) DeepCopy() *ApiServerSourceStatus { + if in == nil { + return nil + } + out := new(ApiServerSourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SinkBinding) DeepCopyInto(out *SinkBinding) { + *out = *in + 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 SinkBinding. +func (in *SinkBinding) DeepCopy() *SinkBinding { + if in == nil { + return nil + } + out := new(SinkBinding) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SinkBinding) 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 *SinkBindingList) DeepCopyInto(out *SinkBindingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SinkBinding, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SinkBindingList. +func (in *SinkBindingList) DeepCopy() *SinkBindingList { + if in == nil { + return nil + } + out := new(SinkBindingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SinkBindingList) 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 *SinkBindingSpec) DeepCopyInto(out *SinkBindingSpec) { + *out = *in + in.SourceSpec.DeepCopyInto(&out.SourceSpec) + in.BindingSpec.DeepCopyInto(&out.BindingSpec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SinkBindingSpec. +func (in *SinkBindingSpec) DeepCopy() *SinkBindingSpec { + if in == nil { + return nil + } + out := new(SinkBindingSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SinkBindingStatus) DeepCopyInto(out *SinkBindingStatus) { + *out = *in + in.SourceStatus.DeepCopyInto(&out.SourceStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SinkBindingStatus. +func (in *SinkBindingStatus) DeepCopy() *SinkBindingStatus { + if in == nil { + return nil + } + out := new(SinkBindingStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/sources/v1alpha2/apiserver_conversion.go b/pkg/apis/sources/v1alpha2/apiserver_conversion.go new file mode 100644 index 00000000000..89660e600c0 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/apiserver_conversion.go @@ -0,0 +1,137 @@ +/* +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 v1alpha2 + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +// ConvertTo implements apis.Convertible +// Converts source (from v1alpha2.ApiServerSource) into into a higher version. +func (source *ApiServerSource) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1beta1.ApiServerSource: + // Meta + sink.ObjectMeta = source.ObjectMeta + + // Spec + + if len(source.Spec.Resources) > 0 { + sink.Spec.Resources = make([]v1beta1.APIVersionKindSelector, len(source.Spec.Resources)) + } + for i, v := range source.Spec.Resources { + sink.Spec.Resources[i] = v1beta1.APIVersionKindSelector{ + APIVersion: v.APIVersion, + Kind: v.Kind, + } + + if v.LabelSelector != nil { + sink.Spec.Resources[i].LabelSelector = &metav1.LabelSelector{} + v.LabelSelector.DeepCopyInto(sink.Spec.Resources[i].LabelSelector) + } + } + + sink.Spec.EventMode = source.Spec.EventMode + + // Optional Spec + + if source.Spec.ResourceOwner != nil { + sink.Spec.ResourceOwner = &v1beta1.APIVersionKind{ + Kind: source.Spec.ResourceOwner.Kind, + APIVersion: source.Spec.ResourceOwner.APIVersion, + } + } + + var ref *duckv1.KReference + if source.Spec.Sink.Ref != nil { + ref = &duckv1.KReference{ + Kind: source.Spec.Sink.Ref.Kind, + Namespace: source.Spec.Sink.Ref.Namespace, + Name: source.Spec.Sink.Ref.Name, + APIVersion: source.Spec.Sink.Ref.APIVersion, + } + } + sink.Spec.Sink = duckv1.Destination{ + Ref: ref, + URI: source.Spec.Sink.URI, + } + + if source.Spec.CloudEventOverrides != nil { + sink.Spec.CloudEventOverrides = source.Spec.CloudEventOverrides.DeepCopy() + } + + sink.Spec.ServiceAccountName = source.Spec.ServiceAccountName + + // Status + source.Status.SourceStatus.DeepCopyInto(&sink.Status.SourceStatus) + return nil + default: + return apis.ConvertToViaProxy(ctx, source, &v1beta1.ApiServerSource{}, sink) + } +} + +// ConvertFrom implements apis.Convertible +// Converts obj from a higher version into v1alpha2.ApiServerSource. +func (sink *ApiServerSource) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1beta1.ApiServerSource: + // Meta + sink.ObjectMeta = source.ObjectMeta + + // Spec + sink.Spec.EventMode = source.Spec.EventMode + + sink.Spec.CloudEventOverrides = source.Spec.CloudEventOverrides + + sink.Spec.Sink = source.Spec.Sink + + if len(source.Spec.Resources) > 0 { + sink.Spec.Resources = make([]APIVersionKindSelector, len(source.Spec.Resources)) + } + for i, v := range source.Spec.Resources { + sink.Spec.Resources[i] = APIVersionKindSelector{} + sink.Spec.Resources[i].APIVersion = v.APIVersion + sink.Spec.Resources[i].Kind = v.Kind + if v.LabelSelector != nil { + sink.Spec.Resources[i].LabelSelector = v.LabelSelector.DeepCopy() + } + } + + // Spec Optionals + + if source.Spec.ResourceOwner != nil { + sink.Spec.ResourceOwner = &APIVersionKind{ + Kind: source.Spec.ResourceOwner.Kind, + APIVersion: source.Spec.ResourceOwner.APIVersion, + } + } + + sink.Spec.ServiceAccountName = source.Spec.ServiceAccountName + + // Status + source.Status.SourceStatus.DeepCopyInto(&sink.Status.SourceStatus) + + return nil + default: + return apis.ConvertFromViaProxy(ctx, source, &v1beta1.ApiServerSource{}, sink) + } +} diff --git a/pkg/apis/sources/v1alpha2/apiserver_conversion_test.go b/pkg/apis/sources/v1alpha2/apiserver_conversion_test.go new file mode 100644 index 00000000000..21bfc998d2f --- /dev/null +++ b/pkg/apis/sources/v1alpha2/apiserver_conversion_test.go @@ -0,0 +1,350 @@ +/* +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 v1alpha2 + +import ( + "context" + "errors" + "reflect" + "testing" + + v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/eventing/pkg/apis/sources/v1beta1" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +// implement apis.Convertible +type testObject struct{} + +func (*testObject) ConvertTo(ctx context.Context, obj apis.Convertible) error { + return errors.New("Won't go") +} + +func (*testObject) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + return errors.New("Won't go") +} + +func TestApiServerSourceConversionBadType(t *testing.T) { + good, bad := &ApiServerSource{}, &testObject{} + + 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) + } +} + +func TestApiServerSourceConversionRoundTripUp(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&v1beta1.ApiServerSource{}, &v1.ApiServerSource{}} + + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + tests := []struct { + name string + in *ApiServerSource + }{{name: "empty", + in: &ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: ApiServerSourceSpec{}, + Status: ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, + }, {name: "simple configuration", + in: &ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: ApiServerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Resources: []APIVersionKindSelector{{ + APIVersion: "A1", + Kind: "K1", + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"A1": "K1"}, + }, + }, { + APIVersion: "A2", + Kind: "K2", + }}, + EventMode: "Ref", + }, + Status: ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "Unknown", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: ApiServerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + }, + }, + Resources: []APIVersionKindSelector{{ + APIVersion: "A1", + Kind: "K1", + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "aKey", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"the", "house"}, + }}, + }, + }, { + APIVersion: "A2", + Kind: "K2", + }}, + ResourceOwner: &APIVersionKind{ + APIVersion: "custom/v1", + Kind: "Parent", + }, + EventMode: "Resource", + ServiceAccountName: "adult", + }, + Status: ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }} + for _, test := range tests { + for _, version := range versions { + t.Run(test.name, func(t *testing.T) { + ver := version + if err := test.in.ConvertTo(context.Background(), ver); err != nil { + t.Error("ConvertTo() =", err) + } + + got := &ApiServerSource{} + + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} + +// This tests round tripping from a higher version -> v1alpha1 and back to the higher version. +func TestApiServerSourceConversionRoundTripDown(t *testing.T) { + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + ceOverrides := duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + } + + tests := []struct { + name string + in apis.Convertible + }{{name: "empty", + in: &v1.ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: v1.ApiServerSourceSpec{}, + Status: v1.ApiServerSourceStatus{}, + }, + }, {name: "simple configuration", + in: &v1.ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: v1.ApiServerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Resources: []v1.APIVersionKindSelector{{ + APIVersion: "A1", + Kind: "K1", + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"A1": "K1"}, + }, + }, { + APIVersion: "A2", + Kind: "K2", + }}, + EventMode: "Ref", + }, + Status: v1.ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &v1.ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: v1.ApiServerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Resources: []v1.APIVersionKindSelector{{ + APIVersion: "A1", + Kind: "K1", + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "aKey", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"the", "house"}, + }}, + }, + }, { + APIVersion: "A2", + Kind: "K2", + }}, + ResourceOwner: &v1.APIVersionKind{ + APIVersion: "custom/v1", + Kind: "Parent", + }, + EventMode: "Resource", + ServiceAccountName: "adult", + }, + Status: v1.ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }} + for _, test := range tests { + + t.Run(test.name, func(t *testing.T) { + down := &ApiServerSource{} + if err := down.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertTo() =", err) + } + + got := (reflect.New(reflect.TypeOf(test.in).Elem()).Interface()).(apis.Convertible) + + if err := down.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha2/apiserver_defaults.go b/pkg/apis/sources/v1alpha2/apiserver_defaults.go new file mode 100644 index 00000000000..11122974fb5 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/apiserver_defaults.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 v1alpha2 + +import ( + "context" +) + +func (s *ApiServerSource) SetDefaults(ctx context.Context) { + s.Spec.SetDefaults(ctx) +} + +func (ss *ApiServerSourceSpec) SetDefaults(ctx context.Context) { + + if ss.EventMode == "" { + ss.EventMode = ReferenceMode + } + + if ss.ServiceAccountName == "" { + ss.ServiceAccountName = "default" + } +} diff --git a/pkg/apis/sources/v1alpha2/apiserver_defaults_test.go b/pkg/apis/sources/v1alpha2/apiserver_defaults_test.go new file mode 100644 index 00000000000..f7d5f0f4dfd --- /dev/null +++ b/pkg/apis/sources/v1alpha2/apiserver_defaults_test.go @@ -0,0 +1,136 @@ +/* +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 v1alpha2 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestApiServerSourceDefaults(t *testing.T) { + testCases := map[string]struct { + initial ApiServerSource + expected ApiServerSource + }{ + "no EventMode": { + initial: ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ApiServerSourceSpec{ + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Foo", + }}, + ServiceAccountName: "default", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + expected: ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ApiServerSourceSpec{ + EventMode: ReferenceMode, + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Foo", + }}, + ServiceAccountName: "default", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + }, + "no ServiceAccountName": { + initial: ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ApiServerSourceSpec{ + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Foo", + }}, + EventMode: ReferenceMode, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + expected: ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ApiServerSourceSpec{ + EventMode: ReferenceMode, + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Foo", + }}, + ServiceAccountName: "default", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + tc.initial.SetDefaults(context.Background()) + if diff := cmp.Diff(tc.expected, tc.initial); diff != "" { + t.Fatal("Unexpected defaults (-want, +got):", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha2/apiserver_lifecycle.go b/pkg/apis/sources/v1alpha2/apiserver_lifecycle.go new file mode 100644 index 00000000000..4404a3d5699 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/apiserver_lifecycle.go @@ -0,0 +1,126 @@ +/* +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 v1alpha2 + +import ( + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + "knative.dev/pkg/apis" +) + +const ( + // ApiServerConditionReady has status True when the ApiServerSource is ready to send events. + ApiServerConditionReady = apis.ConditionReady + + // ApiServerConditionSinkProvided has status True when the ApiServerSource has been configured with a sink target. + ApiServerConditionSinkProvided apis.ConditionType = "SinkProvided" + + // ApiServerConditionDeployed has status True when the ApiServerSource has had it's deployment created. + ApiServerConditionDeployed apis.ConditionType = "Deployed" + + // ApiServerConditionSufficientPermissions has status True when the ApiServerSource has sufficient permissions to access resources. + ApiServerConditionSufficientPermissions apis.ConditionType = "SufficientPermissions" +) + +var apiserverCondSet = apis.NewLivingConditionSet( + ApiServerConditionSinkProvided, + ApiServerConditionDeployed, + ApiServerConditionSufficientPermissions, +) + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (*ApiServerSource) GetConditionSet() apis.ConditionSet { + return apiserverCondSet +} + +// GetGroupVersionKind returns the GroupVersionKind. +func (*ApiServerSource) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("ApiServerSource") +} + +// GetUntypedSpec returns the spec of the ApiServerSource. +func (s *ApiServerSource) GetUntypedSpec() interface{} { + return s.Spec +} + +// GetCondition returns the condition currently associated with the given type, or nil. +func (s *ApiServerSourceStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return apiserverCondSet.Manage(s).GetCondition(t) +} + +// GetTopLevelCondition returns the top level condition. +func (s *ApiServerSourceStatus) GetTopLevelCondition() *apis.Condition { + return apiserverCondSet.Manage(s).GetTopLevelCondition() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (s *ApiServerSourceStatus) InitializeConditions() { + apiserverCondSet.Manage(s).InitializeConditions() +} + +// MarkSink sets the condition that the source has a sink configured. +func (s *ApiServerSourceStatus) MarkSink(uri *apis.URL) { + s.SinkURI = uri + if uri != nil { + apiserverCondSet.Manage(s).MarkTrue(ApiServerConditionSinkProvided) + } else { + apiserverCondSet.Manage(s).MarkFalse(ApiServerConditionSinkProvided, "SinkEmpty", "Sink has resolved to empty.%s", "") + } +} + +// MarkNoSink sets the condition that the source does not have a sink configured. +func (s *ApiServerSourceStatus) MarkNoSink(reason, messageFormat string, messageA ...interface{}) { + apiserverCondSet.Manage(s).MarkFalse(ApiServerConditionSinkProvided, reason, messageFormat, messageA...) +} + +// PropagateDeploymentAvailability uses the availability of the provided Deployment to determine if +// ApiServerConditionDeployed should be marked as true or false. +func (s *ApiServerSourceStatus) PropagateDeploymentAvailability(d *appsv1.Deployment) { + deploymentAvailableFound := false + for _, cond := range d.Status.Conditions { + if cond.Type == appsv1.DeploymentAvailable { + deploymentAvailableFound = true + if cond.Status == corev1.ConditionTrue { + apiserverCondSet.Manage(s).MarkTrue(ApiServerConditionDeployed) + } else if cond.Status == corev1.ConditionFalse { + apiserverCondSet.Manage(s).MarkFalse(ApiServerConditionDeployed, cond.Reason, cond.Message) + } else if cond.Status == corev1.ConditionUnknown { + apiserverCondSet.Manage(s).MarkUnknown(ApiServerConditionDeployed, cond.Reason, cond.Message) + } + } + } + if !deploymentAvailableFound { + apiserverCondSet.Manage(s).MarkUnknown(ApiServerConditionDeployed, "DeploymentUnavailable", "The Deployment '%s' is unavailable.", d.Name) + } +} + +// MarkSufficientPermissions sets the condition that the source has enough permissions to access the resources. +func (s *ApiServerSourceStatus) MarkSufficientPermissions() { + apiserverCondSet.Manage(s).MarkTrue(ApiServerConditionSufficientPermissions) +} + +// MarkNoSufficientPermissions sets the condition that the source does not have enough permissions to access the resources +func (s *ApiServerSourceStatus) MarkNoSufficientPermissions(reason, messageFormat string, messageA ...interface{}) { + apiserverCondSet.Manage(s).MarkFalse(ApiServerConditionSufficientPermissions, reason, messageFormat, messageA...) +} + +// IsReady returns true if the resource is ready overall. +func (s *ApiServerSourceStatus) IsReady() bool { + return apiserverCondSet.Manage(s).IsHappy() +} diff --git a/pkg/apis/sources/v1alpha2/apiserver_lifecycle_test.go b/pkg/apis/sources/v1alpha2/apiserver_lifecycle_test.go new file mode 100644 index 00000000000..bee50baca45 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/apiserver_lifecycle_test.go @@ -0,0 +1,368 @@ +/* +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 v1alpha2 + +import ( + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "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, + }, + }, + }, + } + + unavailableDeployment = &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unavailable", + }, + Status: appsv1.DeploymentStatus{ + Conditions: []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentAvailable, + Status: corev1.ConditionFalse, + }, + }, + }, + } + + unknownDeployment = &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unknown", + }, + Status: appsv1.DeploymentStatus{ + Conditions: []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentAvailable, + Status: corev1.ConditionUnknown, + }, + }, + }, + } +) + +func TestApiServerSourceGetConditionSet(t *testing.T) { + r := &ApiServerSource{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestApiServerSourceGetGroupVersionKind(t *testing.T) { + r := &ApiServerSource{} + want := schema.GroupVersionKind{ + Group: "sources.knative.dev", + Version: "v1alpha2", + Kind: "ApiServerSource", + } + if got := r.GetGroupVersionKind(); got != want { + t.Errorf("got: %v, want: %v", got, want) + } +} + +func TestApiServerSourceStatusIsReady(t *testing.T) { + sink, _ := apis.ParseURL("uri://example") + + tests := []struct { + name string + s *ApiServerSourceStatus + wantConditionStatus corev1.ConditionStatus + want bool + }{{ + name: "uninitialized", + s: &ApiServerSourceStatus{}, + want: false, + }, { + name: "initialized", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sufficient permissions", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSufficientPermissions() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark event types", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink and sufficient permissions and deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }, { + name: "mark sink and sufficient permissions and unavailable deployment", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(unavailableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark sink and sufficient permissions and unknown deployment", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(unknownDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink and sufficient permissions and not deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(&appsv1.Deployment{}) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink and sufficient permissions and deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }, { + name: "mark sink and not enough permissions", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkNoSufficientPermissions("areason", "amessage") + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }} + + 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 TestApiServerSourceStatusGetCondition(t *testing.T) { + sink, _ := apis.ParseURL("uri://example") + + tests := []struct { + name string + s *ApiServerSourceStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "uninitialized", + s: &ApiServerSourceStatus{}, + condQuery: ApiServerConditionReady, + want: nil, + }, { + name: "initialized", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark sink", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark sink and enough permissions and deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark sink and enough permissions and deployed and event types", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark sink empty and enough permissions and deployed and event types", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(nil) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionFalse, + Reason: "SinkEmpty", + Message: "Sink has resolved to empty.", + }, + }} + + 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) + } + }) + } +} + +func TestApiServerSourceGetters(t *testing.T) { + r := &ApiServerSource{ + Spec: ApiServerSourceSpec{ + ServiceAccountName: "test", + EventMode: "test", + }, + } + if got, want := r.GetUntypedSpec(), r.Spec; !reflect.DeepEqual(got, want) { + t.Errorf("GetUntypedSpec() = %v, want: %v", got, want) + } +} diff --git a/pkg/apis/sources/v1alpha2/apiserver_types.go b/pkg/apis/sources/v1alpha2/apiserver_types.go new file mode 100644 index 00000000000..c89747e4e73 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/apiserver_types.go @@ -0,0 +1,135 @@ +/* +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 v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "knative.dev/pkg/apis" + 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 + +// ApiServerSource is the Schema for the apiserversources API +type ApiServerSource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ApiServerSourceSpec `json:"spec,omitempty"` + Status ApiServerSourceStatus `json:"status,omitempty"` +} + +// Check the interfaces that ApiServerSource should be implementing. +var ( + _ runtime.Object = (*ApiServerSource)(nil) + _ kmeta.OwnerRefable = (*ApiServerSource)(nil) + _ apis.Validatable = (*ApiServerSource)(nil) + _ apis.Defaultable = (*ApiServerSource)(nil) + _ apis.HasSpec = (*ApiServerSource)(nil) + _ duckv1.KRShaped = (*ApiServerSource)(nil) +) + +// ApiServerSourceSpec defines the desired state of ApiServerSource +type ApiServerSourceSpec 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"` + + // Resource are the resources this source will track and send related + // lifecycle events from the Kubernetes ApiServer, with an optional label + // selector to help filter. + // +required + Resources []APIVersionKindSelector `json:"resources,omitempty"` + + // ResourceOwner is an additional filter to only track resources that are + // owned by a specific resource type. If ResourceOwner matches Resources[n] + // then Resources[n] is allowed to pass the ResourceOwner filter. + // +optional + ResourceOwner *APIVersionKind `json:"owner,omitempty"` + + // EventMode controls the format of the event. + // `Reference` sends a dataref event type for the resource under watch. + // `Resource` send the full resource lifecycle event. + // Defaults to `Reference` + // +optional + EventMode string `json:"mode,omitempty"` + + // ServiceAccountName is the name of the ServiceAccount to use to run this + // source. Defaults to default if not set. + // +optional + ServiceAccountName string `json:"serviceAccountName,omitempty"` +} + +// ApiServerSourceStatus defines the observed state of ApiServerSource +type ApiServerSourceStatus 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"` +} + +// APIVersionKind is an APIVersion and Kind tuple. +type APIVersionKind struct { + // APIVersion - the API version of the resource to watch. + APIVersion string `json:"apiVersion"` + + // Kind of the resource to watch. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + Kind string `json:"kind"` +} + +// APIVersionKindSelector is an APIVersion Kind tuple with a LabelSelector. +type APIVersionKindSelector struct { + // APIVersion - the API version of the resource to watch. + APIVersion string `json:"apiVersion"` + + // Kind of the resource to watch. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + Kind string `json:"kind"` + + // LabelSelector filters this source to objects to those resources pass the + // label selector. + // More info: http://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + // +optional + LabelSelector *metav1.LabelSelector `json:"selector,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ApiServerSourceList contains a list of ApiServerSource +type ApiServerSourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ApiServerSource `json:"items"` +} + +// GetStatus retrieves the status of the ApiServerSource . Implements the KRShaped interface. +func (a *ApiServerSource) GetStatus() *duckv1.Status { + return &a.Status.Status +} diff --git a/pkg/apis/sources/v1alpha2/apiserver_types_test.go b/pkg/apis/sources/v1alpha2/apiserver_types_test.go new file mode 100644 index 00000000000..ce9e2b2ac1a --- /dev/null +++ b/pkg/apis/sources/v1alpha2/apiserver_types_test.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 v1alpha2 + +import "testing" + +func TestApiServerSource_GetStatus(t *testing.T) { + r := &ApiServerSource{ + Status: ApiServerSourceStatus{}, + } + if got, want := r.GetStatus(), &r.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} + +func TestApiServerSource_GetGroupVersionKind(t *testing.T) { + ass := ApiServerSource{} + gvk := ass.GetGroupVersionKind() + if gvk.Kind != "ApiServerSource" { + t.Errorf("Should be ApiServerSource.") + } +} diff --git a/pkg/apis/sources/v1alpha2/apiserver_validation.go b/pkg/apis/sources/v1alpha2/apiserver_validation.go new file mode 100644 index 00000000000..9efcf2d7674 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/apiserver_validation.go @@ -0,0 +1,77 @@ +/* +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 v1alpha2 + +import ( + "context" + "strings" + + "k8s.io/apimachinery/pkg/runtime/schema" + + "knative.dev/pkg/apis" +) + +const ( + // ReferenceMode produces payloads of ObjectReference + ReferenceMode = "Reference" + // ResourceMode produces payloads of ResourceEvent + ResourceMode = "Resource" +) + +func (c *ApiServerSource) Validate(ctx context.Context) *apis.FieldError { + return c.Spec.Validate(ctx).ViaField("spec") +} + +func (cs *ApiServerSourceSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + + // Validate mode, if can be empty or set as certain value + switch cs.EventMode { + case ReferenceMode, ResourceMode: + // EventMode is valid. + default: + errs = errs.Also(apis.ErrInvalidValue(cs.EventMode, "mode")) + } + + // Validate sink + errs = errs.Also(cs.Sink.Validate(ctx).ViaField("sink")) + + if len(cs.Resources) == 0 { + errs = errs.Also(apis.ErrMissingField("resources")) + } + for i, res := range cs.Resources { + _, err := schema.ParseGroupVersion(res.APIVersion) + if err != nil { + errs = errs.Also(apis.ErrInvalidValue(res.APIVersion, "apiVersion").ViaFieldIndex("resources", i)) + } + if strings.TrimSpace(res.Kind) == "" { + errs = errs.Also(apis.ErrMissingField("kind").ViaFieldIndex("resources", i)) + } + } + + if cs.ResourceOwner != nil { + _, err := schema.ParseGroupVersion(cs.ResourceOwner.APIVersion) + if err != nil { + errs = errs.Also(apis.ErrInvalidValue(cs.ResourceOwner.APIVersion, "apiVersion").ViaField("owner")) + } + if strings.TrimSpace(cs.ResourceOwner.Kind) == "" { + errs = errs.Also(apis.ErrMissingField("kind").ViaField("owner")) + } + } + + return errs +} diff --git a/pkg/apis/sources/v1alpha2/apiserver_validation_test.go b/pkg/apis/sources/v1alpha2/apiserver_validation_test.go new file mode 100644 index 00000000000..b8e96099fad --- /dev/null +++ b/pkg/apis/sources/v1alpha2/apiserver_validation_test.go @@ -0,0 +1,187 @@ +/* +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 v1alpha2 + +import ( + "context" + "errors" + "testing" + + duckv1 "knative.dev/pkg/apis/duck/v1" + + "github.com/google/go-cmp/cmp" + "knative.dev/pkg/apis" +) + +func TestAPIServerValidation(t *testing.T) { + tests := []struct { + name string + spec ApiServerSourceSpec + want error + }{{ + name: "valid spec", + spec: ApiServerSourceSpec{ + EventMode: "Resource", + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Foo", + }}, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + want: nil, + }, { + name: "empty sink", + spec: ApiServerSourceSpec{ + EventMode: "Resource", + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Foo", + }}, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + errs = errs.Also(apis.ErrGeneric("expected at least one, got none", "ref", "uri").ViaField("sink")) + return errs + }(), + }, { + name: "invalid mode", + spec: ApiServerSourceSpec{ + EventMode: "Test", + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Foo", + }}, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + errs = errs.Also(apis.ErrInvalidValue("Test", "mode")) + return errs + }(), + }, { + name: "invalid apiVersion", + spec: ApiServerSourceSpec{ + EventMode: "Resource", + Resources: []APIVersionKindSelector{{ + APIVersion: "v1/v2/v3", + Kind: "Foo", + }}, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + want: errors.New("invalid value: v1/v2/v3: resources[0].apiVersion"), + }, { + name: "missing kind", + spec: ApiServerSourceSpec{ + EventMode: "Resource", + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + }}, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + want: errors.New("missing field(s): resources[0].kind"), + }, { + name: "owner - invalid apiVersion", + spec: ApiServerSourceSpec{ + EventMode: "Resource", + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Bar", + }}, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + ResourceOwner: &APIVersionKind{ + APIVersion: "v1/v2/v3", + Kind: "Foo", + }, + }, + want: errors.New("invalid value: v1/v2/v3: owner.apiVersion"), + }, { + name: "missing kind", + spec: ApiServerSourceSpec{ + EventMode: "Resource", + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Bar", + }}, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + ResourceOwner: &APIVersionKind{ + APIVersion: "v1", + }, + }, + want: errors.New("missing field(s): owner.kind"), + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.spec.Validate(context.TODO()) + if test.want != nil { + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("APIServerSourceSpec.Validate (-want, +got) =", diff) + } + } else if got != nil { + t.Error("APIServerSourceSpec.Validate wanted nil, got =", got.Error()) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha2/container_conversion.go b/pkg/apis/sources/v1alpha2/container_conversion.go new file mode 100644 index 00000000000..f7dc55efd87 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/container_conversion.go @@ -0,0 +1,54 @@ +/* +Copyright 2020 The Knative Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + "context" + + "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/pkg/apis" +) + +// ConvertTo implements apis.Convertible. +// Converts source from v1alpha2.ContainerSource into a higher version. +func (source *ContainerSource) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1beta1.ContainerSource: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.SourceSpec = source.Spec.SourceSpec + sink.Spec.Template = source.Spec.Template + sink.Status.SourceStatus = source.Status.SourceStatus + return nil + default: + return apis.ConvertToViaProxy(ctx, source, &v1beta1.ContainerSource{}, sink) + } +} + +// ConvertFrom implements apis.Convertible. +// Converts obj from a higher version into v1alpha2.ContainerSource. +func (sink *ContainerSource) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1beta1.ContainerSource: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.SourceSpec = source.Spec.SourceSpec + sink.Spec.Template = source.Spec.Template + sink.Status.SourceStatus = source.Status.SourceStatus + return nil + default: + return apis.ConvertFromViaProxy(ctx, source, &v1beta1.ContainerSource{}, sink) + } +} diff --git a/pkg/apis/sources/v1alpha2/container_conversion_test.go b/pkg/apis/sources/v1alpha2/container_conversion_test.go new file mode 100644 index 00000000000..803efcf1316 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/container_conversion_test.go @@ -0,0 +1,400 @@ +/* +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 v1alpha2 + +import ( + "context" + "reflect" + "testing" + + corev1 "k8s.io/api/core/v1" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestContainerSourceConversionBadType(t *testing.T) { + good, bad := &ContainerSource{}, &testObject{} + + if err := good.ConvertTo(context.Background(), bad); err == nil { + t.Error("ConvertTo() = nil, wanted error") + } + + if err := good.ConvertFrom(context.Background(), bad); err == nil { + t.Error("ConvertFrom() = nil, wanted error") + } +} + +// This tests round tripping from v1alpha2 to a higher version and back to v1alpha2. +func TestContainerSourceConversionRoundTripUp(t *testing.T) { + versions := []apis.Convertible{&v1beta1.ContainerSource{}, &v1.ContainerSource{}} + + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + tests := []struct { + name string + in *ContainerSource + }{{name: "empty", + in: &ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: ContainerSourceSpec{}, + Status: ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, + }, {name: "simple configuration", + in: &ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: ContainerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test", + Image: "test-image", + }}, + }, + }, + }, + Status: ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "Unknown", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: ContainerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + }, + }, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test", + Image: "test-image", + }}, + }, + }, + }, + Status: ContainerSourceStatus{ + 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 := &ContainerSource{} + + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} + +// This tests round tripping from a higher version -> v1alpha2 and back to the higher version. +func TestContainerSourceConversionRoundTripDown(t *testing.T) { + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + ceOverrides := duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + } + + tests := []struct { + name string + in apis.Convertible + }{{name: "empty-v1", + in: &v1.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1.ContainerSourceSpec{}, + Status: v1.ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, + }, {name: "simple configuration-v1", + in: &v1.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1.ContainerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test", + Image: "test-image", + }}, + }, + }, + }, + Status: v1.ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "Unknown", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full-v1", + in: &v1.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1.ContainerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test", + Image: "test-image", + }}, + }, + }, + }, + Status: v1.ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "empty-v1beta1", + in: &v1beta1.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta1.ContainerSourceSpec{}, + Status: v1beta1.ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, + }, {name: "simple configuration-v1beta1", + in: &v1beta1.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta1.ContainerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test", + Image: "test-image", + }}, + }, + }, + }, + Status: v1beta1.ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "Unknown", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full-v1beta1", + in: &v1beta1.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta1.ContainerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test", + Image: "test-image", + }}, + }, + }, + }, + Status: v1beta1.ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + down := &ContainerSource{} + if err := down.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertTo() =", err) + } + + got := (reflect.New(reflect.TypeOf(test.in).Elem()).Interface()).(apis.Convertible) + + if err := down.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha2/container_defaults.go b/pkg/apis/sources/v1alpha2/container_defaults.go new file mode 100644 index 00000000000..adaadb3fb96 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/container_defaults.go @@ -0,0 +1,42 @@ +/* +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 v1alpha2 + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" +) + +func (s *ContainerSource) SetDefaults(ctx context.Context) { + withName := apis.WithinParent(ctx, s.ObjectMeta) + s.Spec.SetDefaults(withName) +} + +func (ss *ContainerSourceSpec) SetDefaults(ctx context.Context) { + containers := make([]corev1.Container, 0, len(ss.Template.Spec.Containers)) + for i, c := range ss.Template.Spec.Containers { + // If the Container specified has no name, then default to "_". + if c.Name == "" { + c.Name = fmt.Sprintf("%s-%d", apis.ParentMeta(ctx).Name, i) + } + containers = append(containers, c) + } + ss.Template.Spec.Containers = containers +} diff --git a/pkg/apis/sources/v1alpha2/container_defaults_test.go b/pkg/apis/sources/v1alpha2/container_defaults_test.go new file mode 100644 index 00000000000..a9843daa4ba --- /dev/null +++ b/pkg/apis/sources/v1alpha2/container_defaults_test.go @@ -0,0 +1,114 @@ +/* +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 v1alpha2 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestContainerSourceDefaults(t *testing.T) { + testCases := map[string]struct { + initial ContainerSource + expected ContainerSource + }{ + "no container name": { + initial: ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Image: "test-image", + }}, + }, + }, + }, + }, + expected: ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test-name-0", + Image: "test-image", + }}, + }, + }, + }, + }, + }, + "one with container name one without": { + initial: ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test-container", + Image: "test-image", + }, { + Image: "test-another-image", + }}, + }, + }, + }, + }, + expected: ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test-container", + Image: "test-image", + }, { + Name: "test-name-1", + Image: "test-another-image", + }}, + }, + }, + }, + }, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + tc.initial.SetDefaults(context.Background()) + if diff := cmp.Diff(tc.expected, tc.initial); diff != "" { + t.Fatal("Unexpected defaults (-want, +got):", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha2/container_lifecycle.go b/pkg/apis/sources/v1alpha2/container_lifecycle.go new file mode 100644 index 00000000000..2bb2a91d1de --- /dev/null +++ b/pkg/apis/sources/v1alpha2/container_lifecycle.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 v1alpha2 + +import ( + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" +) + +const ( + // ContainerSourceConditionReady has status True when the ContainerSource is ready to send events. + ContainerSourceConditionReady = apis.ConditionReady + + // ContainerSourceConditionSinkBindingReady has status True when the ContainerSource's SinkBinding is ready. + ContainerSourceConditionSinkBindingReady apis.ConditionType = "SinkBindingReady" + + // ContainerSourceConditionReceiveAdapterReady has status True when the ContainerSource's ReceiveAdapter is ready. + ContainerSourceConditionReceiveAdapterReady apis.ConditionType = "ReceiveAdapterReady" +) + +var containerCondSet = apis.NewLivingConditionSet( + ContainerSourceConditionSinkBindingReady, + ContainerSourceConditionReceiveAdapterReady, +) + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (*ContainerSource) GetConditionSet() apis.ConditionSet { + return containerCondSet +} + +// GetCondition returns the condition currently associated with the given type, or nil. +func (s *ContainerSourceStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return containerCondSet.Manage(s).GetCondition(t) +} + +// GetTopLevelCondition returns the top level condition. +func (s *ContainerSourceStatus) GetTopLevelCondition() *apis.Condition { + return containerCondSet.Manage(s).GetTopLevelCondition() +} + +// IsReady returns true if the resource is ready overall. +func (s *ContainerSourceStatus) IsReady() bool { + return containerCondSet.Manage(s).IsHappy() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (s *ContainerSourceStatus) InitializeConditions() { + containerCondSet.Manage(s).InitializeConditions() +} + +// PropagateSinkBindingStatus uses the availability of the provided Deployment to determine if +// ContainerSourceConditionSinkBindingReady should be marked as true, false or unknown. +func (s *ContainerSourceStatus) PropagateSinkBindingStatus(status *SinkBindingStatus) { + // Do not copy conditions nor observedGeneration + conditions := s.Conditions + observedGeneration := s.ObservedGeneration + s.SourceStatus = status.SourceStatus + s.Conditions = conditions + s.ObservedGeneration = observedGeneration + + cond := status.GetCondition(apis.ConditionReady) + switch { + case cond == nil: + containerCondSet.Manage(s).MarkUnknown(ContainerSourceConditionSinkBindingReady, "", "") + case cond.Status == corev1.ConditionTrue: + containerCondSet.Manage(s).MarkTrue(ContainerSourceConditionSinkBindingReady) + case cond.Status == corev1.ConditionFalse: + containerCondSet.Manage(s).MarkFalse(ContainerSourceConditionSinkBindingReady, cond.Reason, cond.Message) + case cond.Status == corev1.ConditionUnknown: + containerCondSet.Manage(s).MarkUnknown(ContainerSourceConditionSinkBindingReady, cond.Reason, cond.Message) + default: + containerCondSet.Manage(s).MarkUnknown(ContainerSourceConditionSinkBindingReady, cond.Reason, cond.Message) + } +} + +// PropagateReceiveAdapterStatus uses the availability of the provided Deployment to determine if +// ContainerSourceConditionReceiveAdapterReady should be marked as true or false. +func (s *ContainerSourceStatus) PropagateReceiveAdapterStatus(d *appsv1.Deployment) { + deploymentAvailableFound := false + for _, cond := range d.Status.Conditions { + if cond.Type == appsv1.DeploymentAvailable { + deploymentAvailableFound = true + if cond.Status == corev1.ConditionTrue { + containerCondSet.Manage(s).MarkTrue(ContainerSourceConditionReceiveAdapterReady) + } else if cond.Status == corev1.ConditionFalse { + containerCondSet.Manage(s).MarkFalse(ContainerSourceConditionReceiveAdapterReady, cond.Reason, cond.Message) + } else if cond.Status == corev1.ConditionUnknown { + containerCondSet.Manage(s).MarkUnknown(ContainerSourceConditionReceiveAdapterReady, cond.Reason, cond.Message) + } + } + } + if !deploymentAvailableFound { + containerCondSet.Manage(s).MarkUnknown(ContainerSourceConditionReceiveAdapterReady, "DeploymentUnavailable", "The Deployment '%s' is unavailable.", d.Name) + } +} diff --git a/pkg/apis/sources/v1alpha2/container_lifecycle_test.go b/pkg/apis/sources/v1alpha2/container_lifecycle_test.go new file mode 100644 index 00000000000..7218c3fa98b --- /dev/null +++ b/pkg/apis/sources/v1alpha2/container_lifecycle_test.go @@ -0,0 +1,338 @@ +/* +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 v1alpha2 + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +var ( + readySinkBinding = &SinkBinding{ + Status: SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: apis.ConditionReady, + Status: corev1.ConditionTrue, + }}, + }, + }, + }, + } + + notReadySinkBinding = &SinkBinding{ + Status: SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: apis.ConditionReady, + Status: corev1.ConditionFalse, + Reason: "Testing", + Message: "hi", + }}, + }, + }, + }, + } +) + +func TestContainerSourceGetConditionSet(t *testing.T) { + r := &ContainerSource{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestContainerSourceStatusIsReady(t *testing.T) { + tests := []struct { + name string + s *ContainerSourceStatus + wantConditionStatus corev1.ConditionStatus + want bool + }{{ + name: "uninitialized", + s: &ContainerSourceStatus{}, + want: false, + }, { + name: "initialized", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark ready ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateReceiveAdapterStatus(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark ready sb", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark ready sb and ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }, { + name: "mark ready sb and unavailable ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(unavailableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark ready sb and unknown ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(unknownDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark ready sb and not deployed ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(&appsv1.Deployment{}) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark ready sb and ra the no sb", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + s.PropagateSinkBindingStatus(¬ReadySinkBinding.Status) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark ready sb and ra then not ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateReceiveAdapterStatus(availableDeployment) + s.PropagateSinkBindingStatus(¬ReadySinkBinding.Status) + s.PropagateReceiveAdapterStatus(unavailableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark not ready sb and ready ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(¬ReadySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark not ready sb and ra then ready sb", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(¬ReadySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + 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 TestContainerSourceStatusGetCondition(t *testing.T) { + tests := []struct { + name string + s *ContainerSourceStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "uninitialized", + s: &ContainerSourceStatus{}, + condQuery: ContainerSourceConditionReady, + want: nil, + }, { + name: "initialized", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + return s + }(), + condQuery: ContainerSourceConditionReady, + want: &apis.Condition{ + Type: ContainerSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark ready ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateReceiveAdapterStatus(availableDeployment) + return s + }(), + condQuery: ContainerSourceConditionReady, + want: &apis.Condition{ + Type: ContainerSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark ready sb", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + return s + }(), + condQuery: ContainerSourceConditionReady, + want: &apis.Condition{ + Type: ContainerSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark ready sb and ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + return s + }(), + condQuery: ContainerSourceConditionReady, + want: &apis.Condition{ + Type: ContainerSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark ready sb and ra then no sb", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + s.PropagateSinkBindingStatus(¬ReadySinkBinding.Status) + return s + }(), + condQuery: ContainerSourceConditionReady, + want: &apis.Condition{ + Type: ContainerSourceConditionReady, + Status: corev1.ConditionFalse, + Reason: "Testing", + Message: "hi", + }, + }, { + name: "mark ready sb and ra then no ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + s.PropagateReceiveAdapterStatus(unavailableDeployment) + return s + }(), + condQuery: ContainerSourceConditionReady, + want: &apis.Condition{ + Type: ContainerSourceConditionReady, + Status: corev1.ConditionFalse, + }, + }, { + name: "mark not ready sb and ready ra then ready sb", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(¬ReadySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + return s + }(), + condQuery: ContainerSourceConditionReady, + want: &apis.Condition{ + Type: ContainerSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.s.GetCondition(test.condQuery) + ignoreTime := cmpopts.IgnoreFields(apis.Condition{}, + "LastTransitionTime", "Severity") + if diff := cmp.Diff(test.want, got, ignoreTime); diff != "" { + t.Error("unexpected condition (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha2/container_types.go b/pkg/apis/sources/v1alpha2/container_types.go new file mode 100644 index 00000000000..6b761aeff63 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/container_types.go @@ -0,0 +1,98 @@ +/* +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 v1alpha2 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +genreconciler +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ContainerSource is the Schema for the containersources API +type ContainerSource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ContainerSourceSpec `json:"spec,omitempty"` + Status ContainerSourceStatus `json:"status,omitempty"` +} + +var ( + _ runtime.Object = (*ContainerSource)(nil) + _ kmeta.OwnerRefable = (*ContainerSource)(nil) + _ apis.Validatable = (*ContainerSource)(nil) + _ apis.Defaultable = (*ContainerSource)(nil) + _ apis.HasSpec = (*ContainerSource)(nil) + _ duckv1.KRShaped = (*ContainerSource)(nil) +) + +// ContainerSourceSpec defines the desired state of ContainerSource +type ContainerSourceSpec 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"` + + // Template describes the pods that will be created + Template corev1.PodTemplateSpec `json:"template"` +} + +// GetGroupVersionKind returns the GroupVersionKind. +func (*ContainerSource) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("ContainerSource") +} + +// ContainerSourceStatus defines the observed state of ContainerSource +type ContainerSourceStatus 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 + +// ContainerSourceList contains a list of ContainerSource +type ContainerSourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ContainerSource `json:"items"` +} + +// GetUntypedSpec returns the spec of the ContainerSource. +func (c *ContainerSource) GetUntypedSpec() interface{} { + return c.Spec +} + +// GetStatus retrieves the status of the ContainerSource. Implements the KRShaped interface. +func (c *ContainerSource) GetStatus() *duckv1.Status { + return &c.Status.Status +} diff --git a/pkg/apis/sources/v1alpha2/container_validation.go b/pkg/apis/sources/v1alpha2/container_validation.go new file mode 100644 index 00000000000..ffeb91bc2b8 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/container_validation.go @@ -0,0 +1,59 @@ +/* +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 v1alpha2 + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" +) + +func (c *ContainerSource) Validate(ctx context.Context) *apis.FieldError { + return c.Spec.Validate(ctx).ViaField("spec") +} + +func (cs *ContainerSourceSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + if fe := cs.Sink.Validate(ctx); fe != nil { + errs = errs.Also(fe.ViaField("sink")) + } + + // Validate there is at least a container + if cs.Template.Spec.Containers == nil || len(cs.Template.Spec.Containers) == 0 { + fe := apis.ErrMissingField("containers") + errs = errs.Also(fe) + } else { + for i := range cs.Template.Spec.Containers { + if ce := isValidContainer(&cs.Template.Spec.Containers[i]); ce != nil { + errs = errs.Also(ce.ViaFieldIndex("containers", i)) + } + } + } + return errs +} + +func isValidContainer(c *corev1.Container) *apis.FieldError { + var errs *apis.FieldError + if c.Name == "" { + errs = errs.Also(apis.ErrMissingField("name")) + } + if c.Image == "" { + errs = errs.Also(apis.ErrMissingField("image")) + } + return errs +} diff --git a/pkg/apis/sources/v1alpha2/container_validation_test.go b/pkg/apis/sources/v1alpha2/container_validation_test.go new file mode 100644 index 00000000000..dfb14b78371 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/container_validation_test.go @@ -0,0 +1,112 @@ +/* +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 v1alpha2 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestContainerSourceValidation(t *testing.T) { + tests := []struct { + name string + spec ContainerSourceSpec + want *apis.FieldError + }{{ + name: "missing container", + spec: ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{}, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "Broker", + Name: "default", + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrMissingField("containers") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "missing container image", + spec: ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test-name", + }}, + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "eventing.knative.dev/v1alpha1", + Kind: "Broker", + Name: "default", + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrMissingField("containers[0].image") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "empty sink", + spec: ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "name", + Image: "image", + }}, + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{}, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrGeneric("expected at least one, got none", "sink.ref", "sink.uri") + errs = errs.Also(fe) + return errs + }(), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.spec.Validate(context.TODO()) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("ContainerSourceSpec.Validate (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha2/doc.go b/pkg/apis/sources/v1alpha2/doc.go new file mode 100644 index 00000000000..a85efe8655a --- /dev/null +++ b/pkg/apis/sources/v1alpha2/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 v1alpha2 contains API Schema definitions for the sources v1beta1 API group +// +k8s:deepcopy-gen=package +// +groupName=sources.knative.dev +package v1alpha2 diff --git a/pkg/apis/sources/v1alpha2/implements_test.go b/pkg/apis/sources/v1alpha2/implements_test.go new file mode 100644 index 00000000000..a23b1958d10 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/implements_test.go @@ -0,0 +1,50 @@ +/* +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 v1alpha2 + +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 + }{ + // PingSource + {instance: &PingSource{}, iface: &duckv1.Conditions{}}, + {instance: &PingSource{}, iface: &duckv1.Source{}}, + // ContainerSource + {instance: &ContainerSource{}, iface: &duckv1.Conditions{}}, + {instance: &ContainerSource{}, iface: &duckv1.Source{}}, + // ApiServerSource + //{instance: &ApiServerSource{}, iface: &duckv1.Conditions{}}, + //{instance: &ApiServerSource{}, iface: &duckv1.Source{}}, + // SinkBinding + //{instance: &SinkBinding{}, iface: &duckv1.Conditions{}}, + //{instance: &SinkBinding{}, iface: &duckv1.Source{}}, + //{instance: &SinkBinding{}, iface: &duckv1alpha1.Binding{}}, + } + for _, tc := range testCases { + if err := duck.VerifyType(tc.instance, tc.iface); err != nil { + t.Error(err) + } + } +} diff --git a/pkg/apis/sources/v1alpha2/ping_conversion.go b/pkg/apis/sources/v1alpha2/ping_conversion.go new file mode 100644 index 00000000000..916eee31820 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/ping_conversion.go @@ -0,0 +1,82 @@ +/* +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 v1alpha2 + +import ( + "context" + "fmt" + "strings" + + "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/pkg/apis" +) + +// ConvertTo implements apis.Convertible +// Converts source from v1alpha2.PingSource into a higher version. +func (source *PingSource) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1beta1.PingSource: + sink.ObjectMeta = source.ObjectMeta + sink.Spec = v1beta1.PingSourceSpec{ + JsonData: source.Spec.JsonData, + SourceSpec: source.Spec.SourceSpec, + } + sink.Status = v1beta1.PingSourceStatus{ + SourceStatus: source.Status.SourceStatus, + } + + // in v1beta1, timezone has its own field + schedule := source.Spec.Schedule + if strings.HasPrefix(schedule, "TZ=") || strings.HasPrefix(schedule, "CRON_TZ=") { + i := strings.Index(schedule, " ") + eq := strings.Index(schedule, "=") + sink.Spec.Timezone = schedule[eq+1 : i] + sink.Spec.Schedule = strings.TrimSpace(schedule[i:]) + } else { + sink.Spec.Schedule = schedule + } + + return nil + default: + return apis.ConvertToViaProxy(ctx, source, &v1beta1.PingSource{}, sink) + } +} + +// ConvertFrom implements apis.Convertible +// Converts obj from a higher version into v1alpha2.PingSource. +func (sink *PingSource) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1beta1.PingSource: + sink.ObjectMeta = source.ObjectMeta + sink.Spec = PingSourceSpec{ + JsonData: source.Spec.JsonData, + SourceSpec: source.Spec.SourceSpec, + } + sink.Status = PingSourceStatus{ + SourceStatus: source.Status.SourceStatus, + } + + if source.Spec.Timezone != "" { + sink.Spec.Schedule = fmt.Sprintf("CRON_TZ=%s %s", source.Spec.Timezone, source.Spec.Schedule) + } else { + sink.Spec.Schedule = source.Spec.Schedule + } + return nil + default: + return apis.ConvertFromViaProxy(ctx, source, &v1beta1.PingSource{}, sink) + } +} diff --git a/pkg/apis/sources/v1alpha2/ping_conversion_test.go b/pkg/apis/sources/v1alpha2/ping_conversion_test.go new file mode 100644 index 00000000000..a8b22bc2606 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/ping_conversion_test.go @@ -0,0 +1,330 @@ +/* +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 v1alpha2 + +import ( + "context" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestPingSourceConversionBadType(t *testing.T) { + good, bad := &PingSource{}, &testObject{} + + 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) + } +} + +func TestPingSourceConversionRoundTripUp(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&v1beta1.PingSource{}} + + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + tests := []struct { + name string + in *PingSource + }{{name: "empty", + in: &PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: PingSourceSpec{}, + Status: PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, + }, {name: "simple configuration", + in: &PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + }, + Status: PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "Unknown", + }}, + }, + }, + }, + }, + }, {name: "full", + in: &PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Schedule: "* * * * *", + JsonData: "{}", + }, + Status: PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full with timezone", + in: &PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Schedule: "CRON_TZ=Knative/Land * * * * *", + JsonData: "{}", + }, + 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 := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} + +// This tests round tripping from a higher version -> v1alpha2 and back to the higher version. +func TestPingSourceConversionRoundTripDown(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "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 apis.Convertible + }{{name: "empty", + in: &v1beta1.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta1.PingSourceSpec{}, + Status: v1beta1.PingSourceStatus{}, + }, + }, {name: "simple configuration", + in: &v1beta1.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta1.PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + }, + Status: v1beta1.PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &v1beta1.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta1.PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Schedule: "1 2 3 4 5", + JsonData: `{"foo":"bar"}`, + }, + Status: v1beta1.PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + CloudEventAttributes: ceAttributes, + }, + }, + }, + }, {name: "full with timezone", + in: &v1beta1.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta1.PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Schedule: "1 2 3 4 5", + Timezone: "Knative/Land", + JsonData: `{"foo":"bar"}`, + }, + Status: v1beta1.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 := (reflect.New(reflect.TypeOf(test.in).Elem()).Interface()).(apis.Convertible) + + if err := down.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha2/ping_defaults.go b/pkg/apis/sources/v1alpha2/ping_defaults.go new file mode 100644 index 00000000000..27cb46b8820 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/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 v1alpha2 + +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/v1alpha2/ping_defaults_test.go b/pkg/apis/sources/v1alpha2/ping_defaults_test.go new file mode 100644 index 00000000000..209f6f3fc4b --- /dev/null +++ b/pkg/apis/sources/v1alpha2/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 v1alpha2 + +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/v1alpha2/ping_lifecycle.go b/pkg/apis/sources/v1alpha2/ping_lifecycle.go new file mode 100644 index 00000000000..ec39b5d8926 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/ping_lifecycle.go @@ -0,0 +1,165 @@ +/* +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 v1alpha2 + +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 + + // PingSourceConditionValidSchedule has status True when the PingSource has been configured with a valid schedule. + PingSourceConditionValidSchedule apis.ConditionType = "ValidSchedule" + + // 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" + + // PingSourceConditionEventTypeProvided has status True when the PingSource has been configured with its event type. + PingSourceConditionEventTypeProvided apis.ConditionType = "EventTypeProvided" + + // PingSourceConditionResources is True when the resources listed for the PingSource have been properly + // parsed and match specified syntax for resource quantities + PingSourceConditionResources apis.ConditionType = "ResourcesCorrect" +) + +var PingSourceCondSet = apis.NewLivingConditionSet( + PingSourceConditionValidSchedule, + 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() +} + +// TODO: this is a bad method name, change it. +// MarkSchedule sets the condition that the source has a valid schedule configured. +func (s *PingSourceStatus) MarkSchedule() { + PingSourceCondSet.Manage(s).MarkTrue(PingSourceConditionValidSchedule) +} + +// MarkInvalidSchedule sets the condition that the source does not have a valid schedule configured. +func (s *PingSourceStatus) MarkInvalidSchedule(reason, messageFormat string, messageA ...interface{}) { + PingSourceCondSet.Manage(s).MarkFalse(PingSourceConditionValidSchedule, reason, messageFormat, messageA...) +} + +// MarkSink sets the condition that the source has a sink configured. +func (s *PingSourceStatus) MarkSink(uri *apis.URL) { + // TODO: Update sources to use MarkSink(url.URL or apis.URI) + 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) + } +} + +// MarkEventType sets the condition that the source has set its event type. +func (s *PingSourceStatus) MarkEventType() { + PingSourceCondSet.Manage(s).MarkTrue(PingSourceConditionEventTypeProvided) +} + +// MarkNoEventType sets the condition that the source does not its event type configured. +func (s *PingSourceStatus) MarkNoEventType(reason, messageFormat string, messageA ...interface{}) { + PingSourceCondSet.Manage(s).MarkFalse(PingSourceConditionEventTypeProvided, reason, messageFormat, messageA...) +} + +// MarkResourcesCorrect sets the condition that the source resources are properly parsable quantities +func (s *PingSourceStatus) MarkResourcesCorrect() { + PingSourceCondSet.Manage(s).MarkTrue(PingSourceConditionResources) +} + +// MarkResourcesIncorrect sets the condition that the source resources are not properly parsable quantities +func (s *PingSourceStatus) MarkResourcesIncorrect(reason, messageFormat string, messageA ...interface{}) { + PingSourceCondSet.Manage(s).MarkFalse(PingSourceConditionResources, reason, messageFormat, messageA...) +} diff --git a/pkg/apis/sources/v1alpha2/ping_lifecycle_test.go b/pkg/apis/sources/v1alpha2/ping_lifecycle_test.go new file mode 100644 index 00000000000..a3a3f1c483c --- /dev/null +++ b/pkg/apis/sources/v1alpha2/ping_lifecycle_test.go @@ -0,0 +1,687 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + "testing" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "knative.dev/pkg/apis" +) + +func TestPingSourceGetConditionSet(t *testing.T) { + r := &PingSource{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestPingSource_GetGroupVersionKind(t *testing.T) { + src := PingSource{} + gvk := src.GetGroupVersionKind() + + if gvk.Kind != "PingSource" { + t.Errorf("Should be PingSource.") + } +} + +func TestPingSource_PingSourceSource(t *testing.T) { + cePingSource := PingSourceSource("ns1", "job1") + + if cePingSource != "/apis/v1/namespaces/ns1/pingsources/job1" { + t.Errorf("Should be '/apis/v1/namespaces/ns1/pingsources/job1'") + } +} + +func TestPingSourceStatusIsReady(t *testing.T) { + exampleUri, _ := apis.ParseURL("uri://example") + + tests := []struct { + name string + s *PingSourceStatus + wantConditionStatus corev1.ConditionStatus + want bool + }{{ + name: "uninitialized", + s: &PingSourceStatus{}, + want: false, + }, { + name: "initialized", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + + s.MarkSink(exampleUri) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark schedule", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkEventType() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark schedule and sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark schedule, sink and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }, { + name: "mark schedule, sink and unavailable deployment", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(unavailableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark schedule, sink and unknown deployment", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(unknownDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark schedule, sink, deployed, and event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + s.MarkEventType() + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }, { + name: "mark schedule, sink and deployed then not deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + s.PropagateDeploymentAvailability(&appsv1.Deployment{}) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark schedule, sink, deployed and event types then no event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + s.MarkNoEventType("Testing", "") + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }, { + name: "mark schedule validated, sink empty and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(nil) + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark schedule validated, sink empty and deployed then sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(nil) + s.PropagateDeploymentAvailability(availableDeployment) + s.MarkSink(exampleUri) + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.wantConditionStatus != "" { + gotConditionStatus := test.s.GetTopLevelCondition().Status + if gotConditionStatus != test.wantConditionStatus { + t.Errorf("unexpected condition status: want %v, got %v", test.wantConditionStatus, gotConditionStatus) + } + } + got := test.s.IsReady() + if got != test.want { + t.Errorf("unexpected readiness: want %v, got %v", test.want, got) + } + }) + } +} + +func TestPingSourceStatusGetTopLevelCondition(t *testing.T) { + exampleUri, _ := apis.ParseURL("uri://example") + + tests := []struct { + name string + s *PingSourceStatus + want *apis.Condition + }{{ + name: "uninitialized", + s: &PingSourceStatus{}, + want: nil, + }, { + name: "initialized", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSink(exampleUri) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark schedule", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkEventType() + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark sink and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark schedule and sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark schedule, sink and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark schedule, sink, deployed, and event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + s.MarkEventType() + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark schedule, sink and deployed then not deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + s.PropagateDeploymentAvailability(&appsv1.Deployment{}) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Reason: "DeploymentUnavailable", + Status: corev1.ConditionUnknown, + Message: "The Deployment '' is unavailable.", + }, + }, { + name: "mark schedule, sink, deployed and event types then no event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + s.MarkNoEventType("Testing", "") + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark schedule validated, sink empty and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(nil) + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Reason: "SinkEmpty", + Status: corev1.ConditionFalse, + Message: "Sink has resolved to empty.", + }, + }, { + name: "mark schedule validated, sink empty and deployed then sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(nil) + s.PropagateDeploymentAvailability(availableDeployment) + s.MarkSink(exampleUri) + return s + }(), + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.s.GetTopLevelCondition() + ignoreTime := cmpopts.IgnoreFields(apis.Condition{}, + "LastTransitionTime", "Severity") + if diff := cmp.Diff(test.want, got, ignoreTime); diff != "" { + t.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, + }, + }, { + name: "mark schedule", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark schedule, sink and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark schedule, sink, deployed, and event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + s.MarkEventType() + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark schedule, sink and deployed then no sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + s.MarkNoSink("Testing", "hi%s", "") + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionFalse, + Reason: "Testing", + Message: "hi", + }, + }, { + name: "mark schedule, sink and deployed then invalid schedule", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + s.MarkInvalidSchedule("Testing", "hi%s", "") + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionFalse, + Reason: "Testing", + Message: "hi", + }, + }, { + name: "mark schedule, sink and deployed then deploying", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + s.PropagateDeploymentAvailability(&appsv1.Deployment{}) + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + Reason: "DeploymentUnavailable", + Message: "The Deployment '' is unavailable.", + }, + }, { + name: "mark schedule, sink and deployed then not deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + s.PropagateDeploymentAvailability(&appsv1.Deployment{}) + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionUnknown, + Reason: "DeploymentUnavailable", + Message: "The Deployment '' is unavailable.", + }, + }, { + name: "mark schedule, sink, deployed and event types, then no event types", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(exampleUri) + s.PropagateDeploymentAvailability(availableDeployment) + s.MarkEventType() + s.MarkNoEventType("Testing", "hi") + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark schedule, sink empty and deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(nil) + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionFalse, + Reason: "SinkEmpty", + Message: "Sink has resolved to empty.", + }, + }, { + name: "mark schedule, sink empty and deployed then sink", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.MarkSchedule() + s.MarkSink(nil) + s.PropagateDeploymentAvailability(availableDeployment) + s.MarkSink(exampleUri) + return s + }(), + condQuery: PingSourceConditionReady, + want: &apis.Condition{ + Type: PingSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.s.GetCondition(test.condQuery) + ignoreTime := cmpopts.IgnoreFields(apis.Condition{}, + "LastTransitionTime", "Severity") + if diff := cmp.Diff(test.want, got, ignoreTime); diff != "" { + t.Error("unexpected condition (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha2/ping_types.go b/pkg/apis/sources/v1alpha2/ping_types.go new file mode 100644 index 00000000000..4d3921f2fca --- /dev/null +++ b/pkg/apis/sources/v1alpha2/ping_types.go @@ -0,0 +1,96 @@ +/* +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 v1alpha2 + +import ( + "knative.dev/pkg/apis" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +genreconciler +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:defaulter-gen=true + +// PingSource is the Schema for the PingSources API. +type PingSource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec PingSourceSpec `json:"spec,omitempty"` + Status PingSourceStatus `json:"status,omitempty"` +} + +// Check the interfaces that PingSource should be implementing. +var ( + _ runtime.Object = (*PingSource)(nil) + _ kmeta.OwnerRefable = (*PingSource)(nil) + _ apis.Validatable = (*PingSource)(nil) + _ apis.Defaultable = (*PingSource)(nil) + _ apis.HasSpec = (*PingSource)(nil) + _ duckv1.KRShaped = (*PingSource)(nil) +) + +// PingSourceSpec defines the desired state of the PingSource. +type PingSourceSpec struct { + // inherits duck/v1 SourceSpec, which currently provides: + // * Sink - a reference to an object that will resolve to a domain name or + // a URI directly to use as the sink. + // * CloudEventOverrides - defines overrides to control the output format + // and modifications of the event sent to the sink. + duckv1.SourceSpec `json:",inline"` + + // Schedule is the cronjob schedule. Defaults to `* * * * *`. + // +optional + Schedule string `json:"schedule"` + + // JsonData is json encoded data used as the body of the event posted to + // the sink. Default is empty. If set, datacontenttype will also be set + // to "application/json". + // +optional + JsonData string `json:"jsonData,omitempty"` +} + +// PingSourceStatus defines the observed state of PingSource. +type PingSourceStatus struct { + // inherits duck/v1 SourceStatus, which currently provides: + // * ObservedGeneration - the 'Generation' of the Service that was last + // processed by the controller. + // * Conditions - the latest available observations of a resource's current + // state. + // * SinkURI - the current active sink URI that has been configured for the + // Source. + duckv1.SourceStatus `json:",inline"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PingSourceList contains a list of PingSources. +type PingSourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []PingSource `json:"items"` +} + +// GetStatus retrieves the status of the PingSource. Implements the KRShaped interface. +func (p *PingSource) GetStatus() *duckv1.Status { + return &p.Status.Status +} diff --git a/pkg/apis/sources/v1alpha2/ping_types_test.go b/pkg/apis/sources/v1alpha2/ping_types_test.go new file mode 100644 index 00000000000..cd1cd4ff25b --- /dev/null +++ b/pkg/apis/sources/v1alpha2/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 v1alpha2 + +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/v1alpha2/ping_validation.go b/pkg/apis/sources/v1alpha2/ping_validation.go new file mode 100644 index 00000000000..84a01f3ae90 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/ping_validation.go @@ -0,0 +1,68 @@ +/* +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 v1alpha2 + +import ( + "context" + "fmt" + + "github.com/robfig/cron/v3" + "knative.dev/pkg/apis" + + "knative.dev/eventing/pkg/apis/eventing" + "knative.dev/eventing/pkg/apis/sources/config" +) + +func (c *PingSource) Validate(ctx context.Context) *apis.FieldError { + errs := c.Spec.Validate(ctx).ViaField("spec") + return ValidateAnnotations(errs, c.Annotations) +} + +func (cs *PingSourceSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + + if _, err := cron.ParseStandard(cs.Schedule); err != nil { + fe := apis.ErrInvalidValue(cs.Schedule, "schedule") + errs = errs.Also(fe) + } + + pingConfig := config.FromContextOrDefaults(ctx) + pingDefaults := pingConfig.PingDefaults.GetPingConfig() + + if bsize := int64(len(cs.JsonData)); pingDefaults.DataMaxSize > -1 && bsize > pingDefaults.DataMaxSize { + fe := apis.ErrInvalidValue(fmt.Sprintf("the jsonData length of %d bytes exceeds limit set at %d.", bsize, pingDefaults.DataMaxSize), "jsonData") + errs = errs.Also(fe) + } + + if fe := cs.Sink.Validate(ctx); fe != nil { + errs = errs.Also(fe.ViaField("sink")) + } + return errs +} + +func ValidateAnnotations(errs *apis.FieldError, annotations map[string]string) *apis.FieldError { + if annotations != nil { + if scope, ok := annotations[eventing.ScopeAnnotationKey]; ok { + if scope != eventing.ScopeResource && scope != eventing.ScopeCluster { + iv := apis.ErrInvalidValue(scope, "") + iv.Details = "expected either 'cluster' or 'resource'" + errs = errs.Also(iv.ViaFieldKey("annotations", eventing.ScopeAnnotationKey).ViaField("metadata")) + } + } + } + return errs +} diff --git a/pkg/apis/sources/v1alpha2/ping_validation_test.go b/pkg/apis/sources/v1alpha2/ping_validation_test.go new file mode 100644 index 00000000000..381b30035ec --- /dev/null +++ b/pkg/apis/sources/v1alpha2/ping_validation_test.go @@ -0,0 +1,187 @@ +/* +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 v1alpha2 + +import ( + "context" + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/eventing/pkg/apis/eventing" + "knative.dev/eventing/pkg/apis/sources/config" + + 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 + ctx func(ctx context.Context) context.Context + want *apis.FieldError + }{{ + name: "valid spec", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: nil, + }, { + name: "empty sink", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + }, + }, + want: func() *apis.FieldError { + return apis.ErrGeneric("expected at least one, got none", "ref", "uri").ViaField("spec.sink") + }(), + }, { + name: "invalid schedule", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "2", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrInvalidValue("2", "spec.schedule") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "invalid annotation", + source: PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + eventing.ScopeAnnotationKey: "notvalid", + }, + }, + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + Namespace: "namespace", + }, + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrInvalidValue("notvalid", "metadata.annotations.[eventing.knative.dev/scope]\nexpected either 'cluster' or 'resource'") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "too big json", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + JsonData: bigString(), + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + ctx: func(ctx context.Context) context.Context { + + return config.ToContext(ctx, &config.Config{PingDefaults: &config.PingDefaults{DataMaxSize: 4096}}) + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrInvalidValue("the jsonData length of 5000 bytes exceeds limit set at 4096.", "spec.jsonData") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "too big json not checked ok", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + JsonData: bigString(), + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: nil, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := context.TODO() + if test.ctx != nil { + ctx = test.ctx(ctx) + } + got := test.source.Validate(ctx) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("ContainerSourceSpec.Validate (-want, +got) =", diff) + } + }) + } +} + +func bigString() string { + var b strings.Builder + b.Grow(5000) + b.WriteString("\"") + for i := 0; i < 4998; i++ { + b.WriteString("a") + } + b.WriteString("\"") + return b.String() +} diff --git a/pkg/apis/sources/v1alpha2/register.go b/pkg/apis/sources/v1alpha2/register.go new file mode 100644 index 00000000000..3e19edcf665 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/register.go @@ -0,0 +1,59 @@ +/* +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 v1alpha2 + +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: "v1alpha2"} + +// 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, + &ApiServerSource{}, + &ApiServerSourceList{}, + &PingSource{}, + &PingSourceList{}, + &SinkBinding{}, + &SinkBindingList{}, + &ContainerSource{}, + &ContainerSourceList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/apis/sources/v1alpha2/register_test.go b/pkg/apis/sources/v1alpha2/register_test.go new file mode 100644 index 00000000000..93756277caf --- /dev/null +++ b/pkg/apis/sources/v1alpha2/register_test.go @@ -0,0 +1,77 @@ +/* +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 v1alpha2 + +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{ + "ApiServerSource", + "ApiServerSourceList", + "SinkBinding", + "SinkBindingList", + "PingSource", + "PingSourceList", + "ContainerSource", + "ContainerSourceList", + } { + if _, ok := types[name]; !ok { + t.Errorf("Did not find %q as registered type", name) + } + } + +} diff --git a/pkg/apis/sources/v1alpha2/sinkbinding_context.go b/pkg/apis/sources/v1alpha2/sinkbinding_context.go new file mode 100644 index 00000000000..a22b3c8e072 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/sinkbinding_context.go @@ -0,0 +1,43 @@ +/* +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 v1alpha2 + +import ( + "context" + + "knative.dev/pkg/apis" +) + +// sinkURIKey is used as the key for associating information +// with a context.Context. +type sinkURIKey struct{} + +// WithSinkURI notes on the context for binding that the resolved SinkURI +// is the provided apis.URL. +func WithSinkURI(ctx context.Context, uri *apis.URL) context.Context { + return context.WithValue(ctx, sinkURIKey{}, uri) +} + +// GetSinkURI accesses the apis.URL for the Sink URI that has been associated +// with this context. +func GetSinkURI(ctx context.Context) *apis.URL { + value := ctx.Value(sinkURIKey{}) + if value == nil { + return nil + } + return value.(*apis.URL) +} diff --git a/pkg/apis/sources/v1alpha2/sinkbinding_context_test.go b/pkg/apis/sources/v1alpha2/sinkbinding_context_test.go new file mode 100644 index 00000000000..bb7b0f1757e --- /dev/null +++ b/pkg/apis/sources/v1alpha2/sinkbinding_context_test.go @@ -0,0 +1,43 @@ +/* +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 v1alpha2 + +import ( + "context" + "testing" + + "knative.dev/pkg/apis" +) + +func TestGetSinkURI(t *testing.T) { + ctx := context.Background() + + if uri := GetSinkURI(ctx); uri != nil { + t.Errorf("GetSinkURI() = %v, wanted nil", uri) + } + + want := &apis.URL{ + Scheme: "https", + Host: "knobots.io", + Path: "/omg/a/path", + } + ctx = WithSinkURI(ctx, want) + + if got := GetSinkURI(ctx); got != want { + t.Errorf("GetSinkURI() = %v, wanted %v", got, want) + } +} diff --git a/pkg/apis/sources/v1alpha2/sinkbinding_conversion.go b/pkg/apis/sources/v1alpha2/sinkbinding_conversion.go new file mode 100644 index 00000000000..7f2621ab7a4 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/sinkbinding_conversion.go @@ -0,0 +1,60 @@ +/* +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 v1alpha2 + +import ( + "context" + + "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/pkg/apis" + duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" +) + +// ConvertTo implements apis.Convertible. +// Converts source from v1alpha2.SinkBinding into a higher version. +func (source *SinkBinding) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1beta1.SinkBinding: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.SourceSpec = source.Spec.SourceSpec + sink.Spec.BindingSpec = duckv1beta1.BindingSpec{ + Subject: source.Spec.BindingSpec.Subject, + } + sink.Status.SourceStatus = source.Status.SourceStatus + return nil + default: + return apis.ConvertToViaProxy(ctx, source, &v1beta1.SinkBinding{}, sink) + } +} + +// ConvertFrom implements apis.Convertible. +// Converts obj from a higher version into v1alpha2.SinkBinding. +func (sink *SinkBinding) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1beta1.SinkBinding: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.SourceSpec = source.Spec.SourceSpec + sink.Spec.BindingSpec = duckv1alpha1.BindingSpec{ + Subject: source.Spec.BindingSpec.Subject, + } + sink.Status.SourceStatus = source.Status.SourceStatus + return nil + default: + return apis.ConvertFromViaProxy(ctx, source, &v1beta1.SinkBinding{}, sink) + } +} diff --git a/pkg/apis/sources/v1alpha2/sinkbinding_conversion_test.go b/pkg/apis/sources/v1alpha2/sinkbinding_conversion_test.go new file mode 100644 index 00000000000..8bc25fd2daa --- /dev/null +++ b/pkg/apis/sources/v1alpha2/sinkbinding_conversion_test.go @@ -0,0 +1,293 @@ +/* +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 v1alpha2 + +import ( + "context" + "reflect" + "testing" + + "knative.dev/pkg/tracker" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" +) + +func TestSinkBindingConversionBadType(t *testing.T) { + good, bad := &SinkBinding{}, &testObject{} + + 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) + } +} + +func TestSinkBindingConversionRoundTripUp(t *testing.T) { + versions := []apis.Convertible{&v1.SinkBinding{}, &v1beta1.SinkBinding{}} + + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + subject := tracker.Reference{ + APIVersion: "API", + Kind: "K", + Namespace: "NS", + Name: "N", + } + + tests := []struct { + name string + in *SinkBinding + }{{name: "empty", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: SinkBindingSpec{}, + Status: SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, + }, {name: "simple configuration", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: subject, + }, + }, + Status: SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "Unknown", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + }, + }, + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: subject, + }, + }, + Status: SinkBindingStatus{ + 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 := &SinkBinding{} + + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} + +// This tests round tripping from a higher version -> v1alpha1 and back to the higher version. +func TestSinkBindingConversionRoundTripDown(t *testing.T) { + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + ceOverrides := duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + } + + subject := tracker.Reference{ + APIVersion: "API", + Kind: "K", + Namespace: "NS", + Name: "N", + } + + tests := []struct { + name string + in apis.Convertible + }{{name: "empty", + in: &v1.SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1.SinkBindingSpec{}, + Status: v1.SinkBindingStatus{}, + }, + }, {name: "simple configuration", + in: &v1.SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1.SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + }, + Status: v1.SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &v1beta1.SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta1.SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + BindingSpec: duckv1beta1.BindingSpec{Subject: subject}, + }, + Status: v1beta1.SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }} + for _, test := range tests { + + t.Run(test.name, func(t *testing.T) { + down := &SinkBinding{} + if err := down.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertTo() =", err) + } + + got := (reflect.New(reflect.TypeOf(test.in).Elem()).Interface()).(apis.Convertible) + + if err := down.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha2/sinkbinding_defaults.go b/pkg/apis/sources/v1alpha2/sinkbinding_defaults.go new file mode 100644 index 00000000000..7dd706d97fa --- /dev/null +++ b/pkg/apis/sources/v1alpha2/sinkbinding_defaults.go @@ -0,0 +1,34 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + "context" + + "knative.dev/pkg/apis" +) + +// SetDefaults implements apis.Defaultable +func (fb *SinkBinding) SetDefaults(ctx context.Context) { + if fb.Spec.Subject.Namespace == "" { + // Default the subject's namespace to our namespace. + fb.Spec.Subject.Namespace = fb.Namespace + } + + withNS := apis.WithinParent(ctx, fb.ObjectMeta) + fb.Spec.Sink.SetDefaults(withNS) +} diff --git a/pkg/apis/sources/v1alpha2/sinkbinding_defaults_test.go b/pkg/apis/sources/v1alpha2/sinkbinding_defaults_test.go new file mode 100644 index 00000000000..782a1e48375 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/sinkbinding_defaults_test.go @@ -0,0 +1,151 @@ +/* +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 v1alpha2 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" + "knative.dev/pkg/tracker" +) + +func TestSinkBindingDefaulting(t *testing.T) { + tests := []struct { + name string + in *SinkBinding + want *SinkBinding + }{{ + name: "namespace is defaulted", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "serving.knative.dev/v1", + Kind: "Service", + Name: "gemma", + }, + }, + }, + }, + }, + want: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + // This is filled in by defaulting. + Namespace: "moore", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "serving.knative.dev/v1", + Kind: "Service", + Name: "gemma", + // This is filled in by defaulting. + Namespace: "moore", + }, + }, + }, + }, + }, + }, { + name: "no ref, given namespace", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + Namespace: "lorefice", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + URI: &apis.URL{ + Scheme: "http", + Host: "moore.dev", + }, + }, + }, + }, + }, + want: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + Namespace: "lorefice", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + URI: &apis.URL{ + Scheme: "http", + Host: "moore.dev", + }, + }, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.in + got.SetDefaults(context.Background()) + if !cmp.Equal(test.want, got) { + t.Error("SetDefaults (-want, +got) =", cmp.Diff(test.want, got)) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha2/sinkbinding_lifecycle.go b/pkg/apis/sources/v1alpha2/sinkbinding_lifecycle.go new file mode 100644 index 00000000000..ccd4474734a --- /dev/null +++ b/pkg/apis/sources/v1alpha2/sinkbinding_lifecycle.go @@ -0,0 +1,160 @@ +/* +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 v1alpha2 + +import ( + "context" + "encoding/json" + "fmt" + + "go.uber.org/zap" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + "knative.dev/pkg/apis" + "knative.dev/pkg/apis/duck" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/logging" + "knative.dev/pkg/tracker" +) + +var sbCondSet = apis.NewLivingConditionSet() + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (*SinkBinding) GetConditionSet() apis.ConditionSet { + return sbCondSet +} + +// GetGroupVersionKind returns the GroupVersionKind. +func (*SinkBinding) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("SinkBinding") +} + +// GetUntypedSpec implements apis.HasSpec +func (s *SinkBinding) GetUntypedSpec() interface{} { + return s.Spec +} + +// GetSubject implements psbinding.Bindable +func (sb *SinkBinding) GetSubject() tracker.Reference { + return sb.Spec.Subject +} + +// GetBindingStatus implements psbinding.Bindable +func (sb *SinkBinding) GetBindingStatus() duck.BindableStatus { + return &sb.Status +} + +// SetObservedGeneration implements psbinding.BindableStatus +func (sbs *SinkBindingStatus) SetObservedGeneration(gen int64) { + sbs.ObservedGeneration = gen +} + +// InitializeConditions populates the SinkBindingStatus's conditions field +// with all of its conditions configured to Unknown. +func (sbs *SinkBindingStatus) InitializeConditions() { + sbCondSet.Manage(sbs).InitializeConditions() +} + +// MarkBindingUnavailable marks the SinkBinding's Ready condition to False with +// the provided reason and message. +func (sbs *SinkBindingStatus) MarkBindingUnavailable(reason, message string) { + sbCondSet.Manage(sbs).MarkFalse(SinkBindingConditionReady, reason, message) +} + +// MarkBindingAvailable marks the SinkBinding's Ready condition to True. +func (sbs *SinkBindingStatus) MarkBindingAvailable() { + sbCondSet.Manage(sbs).MarkTrue(SinkBindingConditionReady) +} + +// Do implements psbinding.Bindable +func (sb *SinkBinding) Do(ctx context.Context, ps *duckv1.WithPod) { + // First undo so that we can just unconditionally append below. + sb.Undo(ctx, ps) + + uri := GetSinkURI(ctx) + if uri == nil { + logging.FromContext(ctx).Errorf("No sink URI associated with context for %+v", sb) + return + } + + var ceOverrides string + if sb.Spec.CloudEventOverrides != nil { + if co, err := json.Marshal(sb.Spec.SourceSpec.CloudEventOverrides); err != nil { + logging.FromContext(ctx).Errorw(fmt.Sprintf("Failed to marshal CloudEventOverrides into JSON for %+v", sb), zap.Error(err)) + } else if len(co) > 0 { + ceOverrides = string(co) + } + } + + spec := ps.Spec.Template.Spec + for i := range spec.InitContainers { + spec.InitContainers[i].Env = append(spec.InitContainers[i].Env, corev1.EnvVar{ + Name: "K_SINK", + Value: uri.String(), + }) + spec.InitContainers[i].Env = append(spec.InitContainers[i].Env, corev1.EnvVar{ + Name: "K_CE_OVERRIDES", + Value: ceOverrides, + }) + } + for i := range spec.Containers { + spec.Containers[i].Env = append(spec.Containers[i].Env, corev1.EnvVar{ + Name: "K_SINK", + Value: uri.String(), + }) + spec.Containers[i].Env = append(spec.Containers[i].Env, corev1.EnvVar{ + Name: "K_CE_OVERRIDES", + Value: ceOverrides, + }) + } +} + +func (sb *SinkBinding) Undo(ctx context.Context, ps *duckv1.WithPod) { + spec := ps.Spec.Template.Spec + for i, c := range spec.InitContainers { + if len(c.Env) == 0 { + continue + } + env := make([]corev1.EnvVar, 0, len(spec.InitContainers[i].Env)) + for j, ev := range c.Env { + switch ev.Name { + case "K_SINK", "K_CE_OVERRIDES": + continue + default: + env = append(env, spec.InitContainers[i].Env[j]) + } + } + spec.InitContainers[i].Env = env + } + for i, c := range spec.Containers { + if len(c.Env) == 0 { + continue + } + env := make([]corev1.EnvVar, 0, len(spec.Containers[i].Env)) + for j, ev := range c.Env { + switch ev.Name { + case "K_SINK", "K_CE_OVERRIDES": + continue + default: + env = append(env, spec.Containers[i].Env[j]) + } + } + spec.Containers[i].Env = env + } +} diff --git a/pkg/apis/sources/v1alpha2/sinkbinding_lifecycle_test.go b/pkg/apis/sources/v1alpha2/sinkbinding_lifecycle_test.go new file mode 100644 index 00000000000..46b8c6b4b3e --- /dev/null +++ b/pkg/apis/sources/v1alpha2/sinkbinding_lifecycle_test.go @@ -0,0 +1,514 @@ +/* +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 v1alpha2 + +import ( + "context" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" + "knative.dev/pkg/tracker" +) + +func TestSinkBindingGetConditionSet(t *testing.T) { + r := &SinkBinding{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestSinkBindingGetGroupVersionKind(t *testing.T) { + r := &SinkBinding{} + want := schema.GroupVersionKind{ + Group: "sources.knative.dev", + Version: "v1alpha2", + Kind: "SinkBinding", + } + if got := r.GetGroupVersionKind(); got != want { + t.Errorf("got: %v, want: %v", got, want) + } +} + +func TestSinkBindingGetters(t *testing.T) { + r := &SinkBinding{ + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "foo", + }, + }, + }, + } + if got, want := r.GetUntypedSpec(), r.Spec; !reflect.DeepEqual(got, want) { + t.Errorf("GetUntypedSpec() = %v, want: %v", got, want) + } + if got, want := r.GetSubject(), r.Spec.Subject; !reflect.DeepEqual(got, want) { + t.Errorf("GetSubject() = %v, want: %v", got, want) + } + if got, want := r.GetBindingStatus(), &r.Status; !reflect.DeepEqual(got, want) { + t.Errorf("GetBindingStatus() = %v, want: %v", got, want) + } +} + +func TestSinkBindingSetObsGen(t *testing.T) { + r := &SinkBinding{ + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "foo", + }, + }, + }, + } + want := int64(3762) + r.GetBindingStatus().SetObservedGeneration(want) + if got := r.Status.ObservedGeneration; got != want { + t.Errorf("SetObservedGeneration() = %d, wanted %d", got, want) + } +} + +func TestSinkBindingStatusIsReady(t *testing.T) { + tests := []struct { + name string + s *SinkBindingStatus + want bool + }{{ + name: "uninitialized", + s: &SinkBindingStatus{}, + want: false, + }, { + name: "initialized", + s: func() *SinkBindingStatus { + s := &SinkBindingStatus{} + s.InitializeConditions() + return s + }(), + want: false, + }, { + name: "mark available", + s: func() *SinkBindingStatus { + s := &SinkBindingStatus{} + s.InitializeConditions() + s.MarkBindingUnavailable("TheReason", "this is the message") + return s + }(), + want: false, + }, { + name: "mark available", + s: func() *SinkBindingStatus { + s := &SinkBindingStatus{} + s.InitializeConditions() + s.MarkBindingAvailable() + return s + }(), + want: true, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.s.IsReady() + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("%s: unexpected condition (-want, +got) = %v", test.name, diff) + } + }) + } +} + +func TestSinkBindingUndo(t *testing.T) { + tests := []struct { + name string + in *duckv1.WithPod + want *duckv1.WithPod + }{{ + name: "nothing to remove", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + }}, + }, + }, + }, + }, + }, { + name: "lots to remove", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{ + Name: "setup", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "K_SINK", + Value: "http://localhost:8080", + }, { + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "K_SINK", + Value: "http://localhost:8080", + }, { + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }, { + Name: "sidecar", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: "http://localhost:8080", + }, { + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{ + Name: "setup", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "BAZ", + Value: "INGA", + }}, + }}, + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "BAZ", + Value: "INGA", + }}, + }, { + Name: "sidecar", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "BAZ", + Value: "INGA", + }}, + }}, + }, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.in + sb := &SinkBinding{} + sb.Undo(context.Background(), got) + + if !cmp.Equal(got, test.want) { + t.Error("Undo (-want, +got):", cmp.Diff(test.want, got)) + } + }) + } +} + +func TestSinkBindingDo(t *testing.T) { + sinkURI := &apis.URL{ + Scheme: "http", + Host: "thing.ns.svc.cluster.local", + Path: "/a/path", + } + + overrides := duckv1.CloudEventOverrides{Extensions: map[string]string{"foo": "bar"}} + + tests := []struct { + name string + in *duckv1.WithPod + want *duckv1.WithPod + }{{ + name: "nothing to add", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: sinkURI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: sinkURI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + }, { + name: "fix the URI", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: "the wrong value", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"wrong":"value"}}`, + }}, + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: sinkURI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + }, { + name: "lots to add", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{ + Name: "setup", + Image: "busybox", + }}, + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "BAZ", + Value: "INGA", + }}, + }, { + Name: "sidecar", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "BAZ", + Value: "INGA", + }}, + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{ + Name: "setup", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: sinkURI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_SINK", + Value: sinkURI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }, { + Name: "sidecar", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_SINK", + Value: sinkURI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.in + + ctx := WithSinkURI(context.Background(), sinkURI) + + sb := &SinkBinding{Spec: SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + CloudEventOverrides: &overrides, + }, + }} + sb.Do(ctx, got) + + if !cmp.Equal(got, test.want) { + t.Error("Undo (-want, +got):", cmp.Diff(test.want, got)) + } + }) + } +} + +func TestSinkBindingDoNoURI(t *testing.T) { + want := &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{}, + }}, + }, + }, + }, + } + got := &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: "this should be removed", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"tobe":"removed"}}`, + }}, + }}, + }, + }, + }, + } + + sb := &SinkBinding{} + sb.Do(context.Background(), got) + + if !cmp.Equal(got, want) { + t.Error("Undo (-want, +got):", cmp.Diff(want, got)) + } +} diff --git a/pkg/apis/sources/v1alpha2/sinkbinding_types.go b/pkg/apis/sources/v1alpha2/sinkbinding_types.go new file mode 100644 index 00000000000..c05c2f52da9 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/sinkbinding_types.go @@ -0,0 +1,101 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:defaulter-gen=true + +// SinkBinding describes a Binding that is also a Source. +// The `sink` (from the Source duck) is resolved to a URL and +// then projected into the `subject` by augmenting the runtime +// contract of the referenced containers to have a `K_SINK` +// environment variable holding the endpoint to which to send +// cloud events. +type SinkBinding struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SinkBindingSpec `json:"spec"` + Status SinkBindingStatus `json:"status"` +} + +// Check the interfaces that SinkBinding should be implementing. +var ( + _ runtime.Object = (*SinkBinding)(nil) + _ kmeta.OwnerRefable = (*SinkBinding)(nil) + _ apis.Validatable = (*SinkBinding)(nil) + _ apis.Defaultable = (*SinkBinding)(nil) + _ apis.HasSpec = (*SinkBinding)(nil) + _ duckv1.KRShaped = (*SinkBinding)(nil) +) + +// SinkBindingSpec holds the desired state of the SinkBinding (from the client). +type SinkBindingSpec 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"` + + // inherits duck/v1alpha1 BindingSpec, which currently provides: + // * Subject - Subject references the resource(s) whose "runtime contract" + // should be augmented by Binding implementations. + duckv1alpha1.BindingSpec `json:",inline"` +} + +const ( + // SinkBindingConditionReady is configured to indicate whether the Binding + // has been configured for resources subject to its runtime contract. + SinkBindingConditionReady = apis.ConditionReady +) + +// SinkBindingStatus communicates the observed state of the SinkBinding (from the controller). +type SinkBindingStatus 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 + +// SinkBindingList contains a list of SinkBinding +type SinkBindingList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []SinkBinding `json:"items"` +} + +// GetStatus retrieves the status of the SinkBinding. Implements the KRShaped interface. +func (s *SinkBinding) GetStatus() *duckv1.Status { + return &s.Status.Status +} diff --git a/pkg/apis/sources/v1alpha2/sinkbinding_types_test.go b/pkg/apis/sources/v1alpha2/sinkbinding_types_test.go new file mode 100644 index 00000000000..1b0fb2bf68d --- /dev/null +++ b/pkg/apis/sources/v1alpha2/sinkbinding_types_test.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 v1alpha2 + +import "testing" + +func TestSinkBinding_GetStatus(t *testing.T) { + r := &SinkBinding{ + Status: SinkBindingStatus{}, + } + if got, want := r.GetStatus(), &r.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} + +func TestSinkBinding_GetGroupVersionKind(t *testing.T) { + sb := SinkBinding{} + gvk := sb.GetGroupVersionKind() + if gvk.Kind != "SinkBinding" { + t.Errorf("Should be SinkBinding.") + } +} diff --git a/pkg/apis/sources/v1alpha2/sinkbinding_validation.go b/pkg/apis/sources/v1alpha2/sinkbinding_validation.go new file mode 100644 index 00000000000..f96be019729 --- /dev/null +++ b/pkg/apis/sources/v1alpha2/sinkbinding_validation.go @@ -0,0 +1,38 @@ +/* +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 v1alpha2 + +import ( + "context" + + "knative.dev/pkg/apis" +) + +// Validate implements apis.Validatable +func (fb *SinkBinding) Validate(ctx context.Context) *apis.FieldError { + err := fb.Spec.Validate(ctx).ViaField("spec") + if fb.Spec.Subject.Namespace != "" && fb.Namespace != fb.Spec.Subject.Namespace { + err = err.Also(apis.ErrInvalidValue(fb.Spec.Subject.Namespace, "spec.subject.namespace")) + } + return err +} + +// Validate implements apis.Validatable +func (fbs *SinkBindingSpec) Validate(ctx context.Context) *apis.FieldError { + return fbs.Subject.Validate(ctx).ViaField("subject").Also( + fbs.Sink.Validate(ctx).ViaField("sink")) +} diff --git a/pkg/apis/sources/v1alpha2/sinkbinding_validation_test.go b/pkg/apis/sources/v1alpha2/sinkbinding_validation_test.go new file mode 100644 index 00000000000..8fbfb18c1fa --- /dev/null +++ b/pkg/apis/sources/v1alpha2/sinkbinding_validation_test.go @@ -0,0 +1,126 @@ +/* +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 v1alpha2 + +import ( + "context" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" + "knative.dev/pkg/tracker" +) + +func TestSinkBindingValidation(t *testing.T) { + tests := []struct { + name string + in *SinkBinding + want *apis.FieldError + }{{ + name: "missing subject namespace", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "serving.knative.dev/v1", + Kind: "Service", + Name: "gemma", + Namespace: "namespace", + }, + }, + }, + }, + }, + want: apis.ErrMissingField("spec.subject.namespace"), + }, { + name: "invalid subject namespace", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + Namespace: "lorefice", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "serving.knative.dev/v1", + Kind: "Service", + Name: "gemma", + Namespace: "namespace", + }, + }, + }, + }, + }, + want: apis.ErrInvalidValue("lorefice", "spec.subject.namespace"), + }, { + name: "missing sink information", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1alpha1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + Namespace: "moore", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{}, + }, + }, + }, + want: apis.ErrGeneric("expected at least one, got none", "spec.sink.ref", "spec.sink.uri"), + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.in.Validate(context.Background()) + if (test.want != nil) != (got != nil) { + t.Errorf("Validation() = %v, wanted %v", got, test.want) + } else if test.want != nil && test.want.Error() != got.Error() { + t.Errorf("Validation() = %v, wanted %v", got, test.want) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/sources/v1alpha2/zz_generated.deepcopy.go new file mode 100644 index 00000000000..8389ed9a8fe --- /dev/null +++ b/pkg/apis/sources/v1alpha2/zz_generated.deepcopy.go @@ -0,0 +1,457 @@ +// +build !ignore_autogenerated + +/* +Copyright 2021 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 v1alpha2 + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIVersionKind) DeepCopyInto(out *APIVersionKind) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIVersionKind. +func (in *APIVersionKind) DeepCopy() *APIVersionKind { + if in == nil { + return nil + } + out := new(APIVersionKind) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIVersionKindSelector) DeepCopyInto(out *APIVersionKindSelector) { + *out = *in + if in.LabelSelector != nil { + in, out := &in.LabelSelector, &out.LabelSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIVersionKindSelector. +func (in *APIVersionKindSelector) DeepCopy() *APIVersionKindSelector { + if in == nil { + return nil + } + out := new(APIVersionKindSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApiServerSource) DeepCopyInto(out *ApiServerSource) { + *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 ApiServerSource. +func (in *ApiServerSource) DeepCopy() *ApiServerSource { + if in == nil { + return nil + } + out := new(ApiServerSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ApiServerSource) 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 *ApiServerSourceList) DeepCopyInto(out *ApiServerSourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ApiServerSource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApiServerSourceList. +func (in *ApiServerSourceList) DeepCopy() *ApiServerSourceList { + if in == nil { + return nil + } + out := new(ApiServerSourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ApiServerSourceList) 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 *ApiServerSourceSpec) DeepCopyInto(out *ApiServerSourceSpec) { + *out = *in + in.SourceSpec.DeepCopyInto(&out.SourceSpec) + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make([]APIVersionKindSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ResourceOwner != nil { + in, out := &in.ResourceOwner, &out.ResourceOwner + *out = new(APIVersionKind) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApiServerSourceSpec. +func (in *ApiServerSourceSpec) DeepCopy() *ApiServerSourceSpec { + if in == nil { + return nil + } + out := new(ApiServerSourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApiServerSourceStatus) DeepCopyInto(out *ApiServerSourceStatus) { + *out = *in + in.SourceStatus.DeepCopyInto(&out.SourceStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApiServerSourceStatus. +func (in *ApiServerSourceStatus) DeepCopy() *ApiServerSourceStatus { + if in == nil { + return nil + } + out := new(ApiServerSourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerSource) DeepCopyInto(out *ContainerSource) { + *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 ContainerSource. +func (in *ContainerSource) DeepCopy() *ContainerSource { + if in == nil { + return nil + } + out := new(ContainerSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ContainerSource) 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 *ContainerSourceList) DeepCopyInto(out *ContainerSourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ContainerSource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerSourceList. +func (in *ContainerSourceList) DeepCopy() *ContainerSourceList { + if in == nil { + return nil + } + out := new(ContainerSourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ContainerSourceList) 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 *ContainerSourceSpec) DeepCopyInto(out *ContainerSourceSpec) { + *out = *in + in.SourceSpec.DeepCopyInto(&out.SourceSpec) + in.Template.DeepCopyInto(&out.Template) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerSourceSpec. +func (in *ContainerSourceSpec) DeepCopy() *ContainerSourceSpec { + if in == nil { + return nil + } + out := new(ContainerSourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerSourceStatus) DeepCopyInto(out *ContainerSourceStatus) { + *out = *in + in.SourceStatus.DeepCopyInto(&out.SourceStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerSourceStatus. +func (in *ContainerSourceStatus) DeepCopy() *ContainerSourceStatus { + if in == nil { + return nil + } + out := new(ContainerSourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PingSource) DeepCopyInto(out *PingSource) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PingSource. +func (in *PingSource) DeepCopy() *PingSource { + if in == nil { + return nil + } + out := new(PingSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PingSource) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PingSourceList) DeepCopyInto(out *PingSourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PingSource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PingSourceList. +func (in *PingSourceList) DeepCopy() *PingSourceList { + if in == nil { + return nil + } + out := new(PingSourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PingSourceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PingSourceSpec) DeepCopyInto(out *PingSourceSpec) { + *out = *in + in.SourceSpec.DeepCopyInto(&out.SourceSpec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PingSourceSpec. +func (in *PingSourceSpec) DeepCopy() *PingSourceSpec { + if in == nil { + return nil + } + out := new(PingSourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PingSourceStatus) DeepCopyInto(out *PingSourceStatus) { + *out = *in + in.SourceStatus.DeepCopyInto(&out.SourceStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PingSourceStatus. +func (in *PingSourceStatus) DeepCopy() *PingSourceStatus { + if in == nil { + return nil + } + out := new(PingSourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SinkBinding) DeepCopyInto(out *SinkBinding) { + *out = *in + 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 SinkBinding. +func (in *SinkBinding) DeepCopy() *SinkBinding { + if in == nil { + return nil + } + out := new(SinkBinding) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SinkBinding) 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 *SinkBindingList) DeepCopyInto(out *SinkBindingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SinkBinding, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SinkBindingList. +func (in *SinkBindingList) DeepCopy() *SinkBindingList { + if in == nil { + return nil + } + out := new(SinkBindingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SinkBindingList) 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 *SinkBindingSpec) DeepCopyInto(out *SinkBindingSpec) { + *out = *in + in.SourceSpec.DeepCopyInto(&out.SourceSpec) + in.BindingSpec.DeepCopyInto(&out.BindingSpec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SinkBindingSpec. +func (in *SinkBindingSpec) DeepCopy() *SinkBindingSpec { + if in == nil { + return nil + } + out := new(SinkBindingSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SinkBindingStatus) DeepCopyInto(out *SinkBindingStatus) { + *out = *in + in.SourceStatus.DeepCopyInto(&out.SourceStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SinkBindingStatus. +func (in *SinkBindingStatus) DeepCopy() *SinkBindingStatus { + if in == nil { + return nil + } + out := new(SinkBindingStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/sources/v1beta1/apiserver_conversion.go b/pkg/apis/sources/v1beta1/apiserver_conversion.go new file mode 100644 index 00000000000..cc79c69bead --- /dev/null +++ b/pkg/apis/sources/v1beta1/apiserver_conversion.go @@ -0,0 +1,138 @@ +/* +Copyright 2020 The Knative Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "knative.dev/eventing/pkg/apis/sources/v1" + duckv1 "knative.dev/pkg/apis/duck/v1" + + "knative.dev/pkg/apis" +) + +// ConvertTo implements apis.Convertible +// Converts source (from v1beta1.ApiServerSource) into into a higher version. +func (source *ApiServerSource) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1.ApiServerSource: + // Meta + sink.ObjectMeta = source.ObjectMeta + + // Spec + + if len(source.Spec.Resources) > 0 { + sink.Spec.Resources = make([]v1.APIVersionKindSelector, len(source.Spec.Resources)) + } + for i, v := range source.Spec.Resources { + sink.Spec.Resources[i] = v1.APIVersionKindSelector{ + APIVersion: v.APIVersion, + Kind: v.Kind, + } + + if v.LabelSelector != nil { + sink.Spec.Resources[i].LabelSelector = &metav1.LabelSelector{} + v.LabelSelector.DeepCopyInto(sink.Spec.Resources[i].LabelSelector) + } + } + + sink.Spec.EventMode = source.Spec.EventMode + + // Optional Spec + + if source.Spec.ResourceOwner != nil { + sink.Spec.ResourceOwner = &v1.APIVersionKind{ + Kind: source.Spec.ResourceOwner.Kind, + APIVersion: source.Spec.ResourceOwner.APIVersion, + } + } + + var ref *duckv1.KReference + if source.Spec.Sink.Ref != nil { + ref = &duckv1.KReference{ + Kind: source.Spec.Sink.Ref.Kind, + Namespace: source.Spec.Sink.Ref.Namespace, + Name: source.Spec.Sink.Ref.Name, + APIVersion: source.Spec.Sink.Ref.APIVersion, + } + } + sink.Spec.Sink = duckv1.Destination{ + Ref: ref, + URI: source.Spec.Sink.URI, + } + + if source.Spec.CloudEventOverrides != nil { + sink.Spec.CloudEventOverrides = source.Spec.CloudEventOverrides.DeepCopy() + } + + sink.Spec.ServiceAccountName = source.Spec.ServiceAccountName + + // Status + source.Status.SourceStatus.DeepCopyInto(&sink.Status.SourceStatus) + return nil + default: + return apis.ConvertToViaProxy(ctx, source, &v1.ApiServerSource{}, sink) + } +} + +// ConvertFrom implements apis.Convertible +// Converts obj from a higher version into v1beta1.ApiServerSource. +func (sink *ApiServerSource) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1.ApiServerSource: + // Meta + sink.ObjectMeta = source.ObjectMeta + + // Spec + sink.Spec.EventMode = source.Spec.EventMode + + sink.Spec.CloudEventOverrides = source.Spec.CloudEventOverrides + + sink.Spec.Sink = source.Spec.Sink + + if len(source.Spec.Resources) > 0 { + sink.Spec.Resources = make([]APIVersionKindSelector, len(source.Spec.Resources)) + } + for i, v := range source.Spec.Resources { + sink.Spec.Resources[i] = APIVersionKindSelector{} + sink.Spec.Resources[i].APIVersion = v.APIVersion + sink.Spec.Resources[i].Kind = v.Kind + if v.LabelSelector != nil { + sink.Spec.Resources[i].LabelSelector = v.LabelSelector.DeepCopy() + } + } + + // Spec Optionals + + if source.Spec.ResourceOwner != nil { + sink.Spec.ResourceOwner = &APIVersionKind{ + Kind: source.Spec.ResourceOwner.Kind, + APIVersion: source.Spec.ResourceOwner.APIVersion, + } + } + + sink.Spec.ServiceAccountName = source.Spec.ServiceAccountName + + // Status + source.Status.SourceStatus.DeepCopyInto(&sink.Status.SourceStatus) + + return nil + default: + return apis.ConvertFromViaProxy(ctx, source, &v1.ApiServerSource{}, sink) + } +} diff --git a/pkg/apis/sources/v1beta1/apiserver_conversion_test.go b/pkg/apis/sources/v1beta1/apiserver_conversion_test.go new file mode 100644 index 00000000000..31ada36fabd --- /dev/null +++ b/pkg/apis/sources/v1beta1/apiserver_conversion_test.go @@ -0,0 +1,347 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "errors" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +type testObject struct{} + +func (*testObject) ConvertTo(ctx context.Context, obj apis.Convertible) error { + return errors.New("Won't go") +} + +func (*testObject) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + return errors.New("Won't go") +} + +func TestApiServerSourceConversionBadType(t *testing.T) { + good, bad := &ApiServerSource{}, &testObject{} + + 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) + } +} + +func TestApiServerSourceConversionRoundTripUp(t *testing.T) { + // Just one for now, just adding the for loop for ease of future changes. + versions := []apis.Convertible{&v1.ApiServerSource{}} + + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + tests := []struct { + name string + in *ApiServerSource + }{{name: "empty", + in: &ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: ApiServerSourceSpec{}, + Status: ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, + }, {name: "simple configuration", + in: &ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: ApiServerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Resources: []APIVersionKindSelector{{ + APIVersion: "A1", + Kind: "K1", + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"A1": "K1"}, + }, + }, { + APIVersion: "A2", + Kind: "K2", + }}, + EventMode: "Ref", + }, + Status: ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "Unknown", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: ApiServerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + }, + }, + Resources: []APIVersionKindSelector{{ + APIVersion: "A1", + Kind: "K1", + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "aKey", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"the", "house"}, + }}, + }, + }, { + APIVersion: "A2", + Kind: "K2", + }}, + ResourceOwner: &APIVersionKind{ + APIVersion: "custom/v1", + Kind: "Parent", + }, + EventMode: "Resource", + ServiceAccountName: "adult", + }, + Status: ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }} + for _, test := range tests { + for _, version := range versions { + t.Run(test.name, func(t *testing.T) { + ver := version + if err := test.in.ConvertTo(context.Background(), ver); err != nil { + t.Errorf("ConvertTo() = %v", err) + } + + got := &ApiServerSource{} + + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Errorf("ConvertFrom() = %v", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Errorf("roundtrip (-want, +got) = %v", diff) + } + }) + } + } +} + +// This tests round tripping from a higher version -> v1beta1 and back to the higher version. +func TestApiServerSourceConversionRoundTripDown(t *testing.T) { + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + ceOverrides := duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + } + + tests := []struct { + name string + in apis.Convertible + }{{name: "empty", + in: &v1.ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: v1.ApiServerSourceSpec{}, + Status: v1.ApiServerSourceStatus{}, + }, + }, {name: "simple configuration", + in: &v1.ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: v1.ApiServerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Resources: []v1.APIVersionKindSelector{{ + APIVersion: "A1", + Kind: "K1", + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"A1": "K1"}, + }, + }, { + APIVersion: "A2", + Kind: "K2", + }}, + EventMode: "Ref", + }, + Status: v1.ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &v1.ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apiserver-name", + Namespace: "apiserver-ns", + Generation: 17, + }, + Spec: v1.ApiServerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Resources: []v1.APIVersionKindSelector{{ + APIVersion: "A1", + Kind: "K1", + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "aKey", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"the", "house"}, + }}, + }, + }, { + APIVersion: "A2", + Kind: "K2", + }}, + ResourceOwner: &v1.APIVersionKind{ + APIVersion: "custom/v1", + Kind: "Parent", + }, + EventMode: "Resource", + ServiceAccountName: "adult", + }, + Status: v1.ApiServerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }} + for _, test := range tests { + + t.Run(test.name, func(t *testing.T) { + down := &ApiServerSource{} + if err := down.ConvertFrom(context.Background(), test.in); err != nil { + t.Errorf("ConvertTo() = %v", err) + } + + got := (reflect.New(reflect.TypeOf(test.in).Elem()).Interface()).(apis.Convertible) + + if err := down.ConvertTo(context.Background(), got); err != nil { + t.Errorf("ConvertFrom() = %v", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Errorf("roundtrip (-want, +got) = %v", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta1/apiserver_defaults.go b/pkg/apis/sources/v1beta1/apiserver_defaults.go new file mode 100644 index 00000000000..31fcba4bcbc --- /dev/null +++ b/pkg/apis/sources/v1beta1/apiserver_defaults.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 v1beta1 + +import ( + "context" +) + +func (s *ApiServerSource) SetDefaults(ctx context.Context) { + s.Spec.SetDefaults(ctx) +} + +func (ss *ApiServerSourceSpec) SetDefaults(ctx context.Context) { + + if ss.EventMode == "" { + ss.EventMode = ReferenceMode + } + + if ss.ServiceAccountName == "" { + ss.ServiceAccountName = "default" + } +} diff --git a/pkg/apis/sources/v1beta1/apiserver_defaults_test.go b/pkg/apis/sources/v1beta1/apiserver_defaults_test.go new file mode 100644 index 00000000000..ecdd11deb46 --- /dev/null +++ b/pkg/apis/sources/v1beta1/apiserver_defaults_test.go @@ -0,0 +1,136 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestApiServerSourceDefaults(t *testing.T) { + testCases := map[string]struct { + initial ApiServerSource + expected ApiServerSource + }{ + "no EventMode": { + initial: ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ApiServerSourceSpec{ + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Foo", + }}, + ServiceAccountName: "default", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + expected: ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ApiServerSourceSpec{ + EventMode: ReferenceMode, + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Foo", + }}, + ServiceAccountName: "default", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + }, + "no ServiceAccountName": { + initial: ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ApiServerSourceSpec{ + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Foo", + }}, + EventMode: ReferenceMode, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + expected: ApiServerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ApiServerSourceSpec{ + EventMode: ReferenceMode, + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Foo", + }}, + ServiceAccountName: "default", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + tc.initial.SetDefaults(context.Background()) + if diff := cmp.Diff(tc.expected, tc.initial); diff != "" { + t.Fatal("Unexpected defaults (-want, +got):", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta1/apiserver_lifecycle.go b/pkg/apis/sources/v1beta1/apiserver_lifecycle.go new file mode 100644 index 00000000000..1990cabec5e --- /dev/null +++ b/pkg/apis/sources/v1beta1/apiserver_lifecycle.go @@ -0,0 +1,126 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + "knative.dev/pkg/apis" +) + +const ( + // ApiServerConditionReady has status True when the ApiServerSource is ready to send events. + ApiServerConditionReady = apis.ConditionReady + + // ApiServerConditionSinkProvided has status True when the ApiServerSource has been configured with a sink target. + ApiServerConditionSinkProvided apis.ConditionType = "SinkProvided" + + // ApiServerConditionDeployed has status True when the ApiServerSource has had it's deployment created. + ApiServerConditionDeployed apis.ConditionType = "Deployed" + + // ApiServerConditionSufficientPermissions has status True when the ApiServerSource has sufficient permissions to access resources. + ApiServerConditionSufficientPermissions apis.ConditionType = "SufficientPermissions" +) + +var apiserverCondSet = apis.NewLivingConditionSet( + ApiServerConditionSinkProvided, + ApiServerConditionDeployed, + ApiServerConditionSufficientPermissions, +) + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (*ApiServerSource) GetConditionSet() apis.ConditionSet { + return apiserverCondSet +} + +// GetGroupVersionKind returns the GroupVersionKind. +func (*ApiServerSource) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("ApiServerSource") +} + +// GetUntypedSpec returns the spec of the ApiServerSource. +func (s *ApiServerSource) GetUntypedSpec() interface{} { + return s.Spec +} + +// GetCondition returns the condition currently associated with the given type, or nil. +func (s *ApiServerSourceStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return apiserverCondSet.Manage(s).GetCondition(t) +} + +// GetTopLevelCondition returns the top level condition. +func (s *ApiServerSourceStatus) GetTopLevelCondition() *apis.Condition { + return apiserverCondSet.Manage(s).GetTopLevelCondition() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (s *ApiServerSourceStatus) InitializeConditions() { + apiserverCondSet.Manage(s).InitializeConditions() +} + +// MarkSink sets the condition that the source has a sink configured. +func (s *ApiServerSourceStatus) MarkSink(uri *apis.URL) { + s.SinkURI = uri + if uri != nil { + apiserverCondSet.Manage(s).MarkTrue(ApiServerConditionSinkProvided) + } else { + apiserverCondSet.Manage(s).MarkFalse(ApiServerConditionSinkProvided, "SinkEmpty", "Sink has resolved to empty.%s", "") + } +} + +// MarkNoSink sets the condition that the source does not have a sink configured. +func (s *ApiServerSourceStatus) MarkNoSink(reason, messageFormat string, messageA ...interface{}) { + apiserverCondSet.Manage(s).MarkFalse(ApiServerConditionSinkProvided, reason, messageFormat, messageA...) +} + +// PropagateDeploymentAvailability uses the availability of the provided Deployment to determine if +// ApiServerConditionDeployed should be marked as true or false. +func (s *ApiServerSourceStatus) PropagateDeploymentAvailability(d *appsv1.Deployment) { + deploymentAvailableFound := false + for _, cond := range d.Status.Conditions { + if cond.Type == appsv1.DeploymentAvailable { + deploymentAvailableFound = true + if cond.Status == corev1.ConditionTrue { + apiserverCondSet.Manage(s).MarkTrue(ApiServerConditionDeployed) + } else if cond.Status == corev1.ConditionFalse { + apiserverCondSet.Manage(s).MarkFalse(ApiServerConditionDeployed, cond.Reason, cond.Message) + } else if cond.Status == corev1.ConditionUnknown { + apiserverCondSet.Manage(s).MarkUnknown(ApiServerConditionDeployed, cond.Reason, cond.Message) + } + } + } + if !deploymentAvailableFound { + apiserverCondSet.Manage(s).MarkUnknown(ApiServerConditionDeployed, "DeploymentUnavailable", "The Deployment '%s' is unavailable.", d.Name) + } +} + +// MarkSufficientPermissions sets the condition that the source has enough permissions to access the resources. +func (s *ApiServerSourceStatus) MarkSufficientPermissions() { + apiserverCondSet.Manage(s).MarkTrue(ApiServerConditionSufficientPermissions) +} + +// MarkNoSufficientPermissions sets the condition that the source does not have enough permissions to access the resources +func (s *ApiServerSourceStatus) MarkNoSufficientPermissions(reason, messageFormat string, messageA ...interface{}) { + apiserverCondSet.Manage(s).MarkFalse(ApiServerConditionSufficientPermissions, reason, messageFormat, messageA...) +} + +// IsReady returns true if the resource is ready overall. +func (s *ApiServerSourceStatus) IsReady() bool { + return apiserverCondSet.Manage(s).IsHappy() +} diff --git a/pkg/apis/sources/v1beta1/apiserver_lifecycle_test.go b/pkg/apis/sources/v1beta1/apiserver_lifecycle_test.go new file mode 100644 index 00000000000..a9d4a95d45b --- /dev/null +++ b/pkg/apis/sources/v1beta1/apiserver_lifecycle_test.go @@ -0,0 +1,370 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "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, + }, + }, + }, + } + + unavailableDeployment = &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unavailable", + }, + Status: appsv1.DeploymentStatus{ + Conditions: []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentAvailable, + Status: corev1.ConditionFalse, + }, + }, + }, + } + + unknownDeployment = &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unknown", + }, + Status: appsv1.DeploymentStatus{ + Conditions: []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentAvailable, + Status: corev1.ConditionUnknown, + }, + }, + }, + } +) + +func TestApiServerSourceGetConditionSet(t *testing.T) { + r := &ApiServerSource{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestApiServerSourceGetGroupVersionKind(t *testing.T) { + r := &ApiServerSource{} + want := schema.GroupVersionKind{ + Group: "sources.knative.dev", + Version: "v1beta1", + Kind: "ApiServerSource", + } + if got := r.GetGroupVersionKind(); got != want { + t.Errorf("got: %v, want: %v", got, want) + } +} + +func TestApiServerSourceStatusIsReady(t *testing.T) { + sink := apis.HTTP("example") + sink.Scheme = "uri" + + tests := []struct { + name string + s *ApiServerSourceStatus + wantConditionStatus corev1.ConditionStatus + want bool + }{{ + name: "uninitialized", + s: &ApiServerSourceStatus{}, + want: false, + }, { + name: "initialized", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sufficient permissions", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSufficientPermissions() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark event types", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink and sufficient permissions and deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }, { + name: "mark sink and sufficient permissions and unavailable deployment", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(unavailableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark sink and sufficient permissions and unknown deployment", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(unknownDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink and sufficient permissions and not deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(&appsv1.Deployment{}) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark sink and sufficient permissions and deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }, { + name: "mark sink and not enough permissions", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkNoSufficientPermissions("areason", "amessage") + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }} + + 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 TestApiServerSourceStatusGetCondition(t *testing.T) { + sink := apis.HTTP("example") + sink.Scheme = "uri" + + tests := []struct { + name string + s *ApiServerSourceStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "uninitialized", + s: &ApiServerSourceStatus{}, + condQuery: ApiServerConditionReady, + want: nil, + }, { + name: "initialized", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark sink", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark sink and enough permissions and deployed", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark sink and enough permissions and deployed and event types", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark sink empty and enough permissions and deployed and event types", + s: func() *ApiServerSourceStatus { + s := &ApiServerSourceStatus{} + s.InitializeConditions() + s.MarkSink(nil) + s.MarkSufficientPermissions() + s.PropagateDeploymentAvailability(availableDeployment) + return s + }(), + condQuery: ApiServerConditionReady, + want: &apis.Condition{ + Type: ApiServerConditionReady, + Status: corev1.ConditionFalse, + Reason: "SinkEmpty", + Message: "Sink has resolved to empty.", + }, + }} + + 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) + } + }) + } +} + +func TestApiServerSourceGetters(t *testing.T) { + r := &ApiServerSource{ + Spec: ApiServerSourceSpec{ + ServiceAccountName: "test", + EventMode: "test", + }, + } + if got, want := r.GetUntypedSpec(), r.Spec; !reflect.DeepEqual(got, want) { + t.Errorf("GetUntypedSpec() = %v, want: %v", got, want) + } +} diff --git a/pkg/apis/sources/v1beta1/apiserver_types.go b/pkg/apis/sources/v1beta1/apiserver_types.go new file mode 100644 index 00000000000..17728f8b596 --- /dev/null +++ b/pkg/apis/sources/v1beta1/apiserver_types.go @@ -0,0 +1,135 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "knative.dev/pkg/apis" + 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 + +// ApiServerSource is the Schema for the apiserversources API +type ApiServerSource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ApiServerSourceSpec `json:"spec,omitempty"` + Status ApiServerSourceStatus `json:"status,omitempty"` +} + +// Check the interfaces that ApiServerSource should be implementing. +var ( + _ runtime.Object = (*ApiServerSource)(nil) + _ kmeta.OwnerRefable = (*ApiServerSource)(nil) + _ apis.Validatable = (*ApiServerSource)(nil) + _ apis.Defaultable = (*ApiServerSource)(nil) + _ apis.HasSpec = (*ApiServerSource)(nil) + _ duckv1.KRShaped = (*ApiServerSource)(nil) +) + +// ApiServerSourceSpec defines the desired state of ApiServerSource +type ApiServerSourceSpec 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"` + + // Resource are the resources this source will track and send related + // lifecycle events from the Kubernetes ApiServer, with an optional label + // selector to help filter. + // +required + Resources []APIVersionKindSelector `json:"resources,omitempty"` + + // ResourceOwner is an additional filter to only track resources that are + // owned by a specific resource type. If ResourceOwner matches Resources[n] + // then Resources[n] is allowed to pass the ResourceOwner filter. + // +optional + ResourceOwner *APIVersionKind `json:"owner,omitempty"` + + // EventMode controls the format of the event. + // `Reference` sends a dataref event type for the resource under watch. + // `Resource` send the full resource lifecycle event. + // Defaults to `Reference` + // +optional + EventMode string `json:"mode,omitempty"` + + // ServiceAccountName is the name of the ServiceAccount to use to run this + // source. Defaults to default if not set. + // +optional + ServiceAccountName string `json:"serviceAccountName,omitempty"` +} + +// ApiServerSourceStatus defines the observed state of ApiServerSource +type ApiServerSourceStatus 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"` +} + +// APIVersionKind is an APIVersion and Kind tuple. +type APIVersionKind struct { + // APIVersion - the API version of the resource to watch. + APIVersion string `json:"apiVersion"` + + // Kind of the resource to watch. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + Kind string `json:"kind"` +} + +// APIVersionKindSelector is an APIVersion Kind tuple with a LabelSelector. +type APIVersionKindSelector struct { + // APIVersion - the API version of the resource to watch. + APIVersion string `json:"apiVersion"` + + // Kind of the resource to watch. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + Kind string `json:"kind"` + + // LabelSelector filters this source to objects to those resources pass the + // label selector. + // More info: http://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + // +optional + LabelSelector *metav1.LabelSelector `json:"selector,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ApiServerSourceList contains a list of ApiServerSource +type ApiServerSourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ApiServerSource `json:"items"` +} + +// GetStatus retrieves the status of the ApiServerSource . Implements the KRShaped interface. +func (a *ApiServerSource) GetStatus() *duckv1.Status { + return &a.Status.Status +} diff --git a/pkg/apis/sources/v1beta1/apiserver_types_test.go b/pkg/apis/sources/v1beta1/apiserver_types_test.go new file mode 100644 index 00000000000..5ec67585b1f --- /dev/null +++ b/pkg/apis/sources/v1beta1/apiserver_types_test.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 v1beta1 + +import "testing" + +func TestApiServerSource_GetStatus(t *testing.T) { + r := &ApiServerSource{ + Status: ApiServerSourceStatus{}, + } + if got, want := r.GetStatus(), &r.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} + +func TestApiServerSource_GetGroupVersionKind(t *testing.T) { + ass := ApiServerSource{} + gvk := ass.GetGroupVersionKind() + if gvk.Kind != "ApiServerSource" { + t.Errorf("Should be ApiServerSource.") + } +} diff --git a/pkg/apis/sources/v1beta1/apiserver_validation.go b/pkg/apis/sources/v1beta1/apiserver_validation.go new file mode 100644 index 00000000000..97cb1d35d82 --- /dev/null +++ b/pkg/apis/sources/v1beta1/apiserver_validation.go @@ -0,0 +1,77 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "strings" + + "k8s.io/apimachinery/pkg/runtime/schema" + + "knative.dev/pkg/apis" +) + +const ( + // ReferenceMode produces payloads of ObjectReference + ReferenceMode = "Reference" + // ResourceMode produces payloads of ResourceEvent + ResourceMode = "Resource" +) + +func (c *ApiServerSource) Validate(ctx context.Context) *apis.FieldError { + return c.Spec.Validate(ctx).ViaField("spec") +} + +func (cs *ApiServerSourceSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + + // Validate mode, if can be empty or set as certain value + switch cs.EventMode { + case ReferenceMode, ResourceMode: + // EventMode is valid. + default: + errs = errs.Also(apis.ErrInvalidValue(cs.EventMode, "mode")) + } + + // Validate sink + errs = errs.Also(cs.Sink.Validate(ctx).ViaField("sink")) + + if len(cs.Resources) == 0 { + errs = errs.Also(apis.ErrMissingField("resources")) + } + for i, res := range cs.Resources { + _, err := schema.ParseGroupVersion(res.APIVersion) + if err != nil { + errs = errs.Also(apis.ErrInvalidValue(res.APIVersion, "apiVersion").ViaFieldIndex("resources", i)) + } + if strings.TrimSpace(res.Kind) == "" { + errs = errs.Also(apis.ErrMissingField("kind").ViaFieldIndex("resources", i)) + } + } + + if cs.ResourceOwner != nil { + _, err := schema.ParseGroupVersion(cs.ResourceOwner.APIVersion) + if err != nil { + errs = errs.Also(apis.ErrInvalidValue(cs.ResourceOwner.APIVersion, "apiVersion").ViaField("owner")) + } + if strings.TrimSpace(cs.ResourceOwner.Kind) == "" { + errs = errs.Also(apis.ErrMissingField("kind").ViaField("owner")) + } + } + + return errs +} diff --git a/pkg/apis/sources/v1beta1/apiserver_validation_test.go b/pkg/apis/sources/v1beta1/apiserver_validation_test.go new file mode 100644 index 00000000000..eee51d28d38 --- /dev/null +++ b/pkg/apis/sources/v1beta1/apiserver_validation_test.go @@ -0,0 +1,187 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "errors" + "testing" + + duckv1 "knative.dev/pkg/apis/duck/v1" + + "github.com/google/go-cmp/cmp" + "knative.dev/pkg/apis" +) + +func TestAPIServerValidation(t *testing.T) { + tests := []struct { + name string + spec ApiServerSourceSpec + want error + }{{ + name: "valid spec", + spec: ApiServerSourceSpec{ + EventMode: "Resource", + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Foo", + }}, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + want: nil, + }, { + name: "empty sink", + spec: ApiServerSourceSpec{ + EventMode: "Resource", + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Foo", + }}, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + errs = errs.Also(apis.ErrGeneric("expected at least one, got none", "ref", "uri").ViaField("sink")) + return errs + }(), + }, { + name: "invalid mode", + spec: ApiServerSourceSpec{ + EventMode: "Test", + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Foo", + }}, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + errs = errs.Also(apis.ErrInvalidValue("Test", "mode")) + return errs + }(), + }, { + name: "invalid apiVersion", + spec: ApiServerSourceSpec{ + EventMode: "Resource", + Resources: []APIVersionKindSelector{{ + APIVersion: "v1/v2/v3", + Kind: "Foo", + }}, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + want: errors.New("invalid value: v1/v2/v3: resources[0].apiVersion"), + }, { + name: "missing kind", + spec: ApiServerSourceSpec{ + EventMode: "Resource", + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + }}, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + want: errors.New("missing field(s): resources[0].kind"), + }, { + name: "owner - invalid apiVersion", + spec: ApiServerSourceSpec{ + EventMode: "Resource", + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Bar", + }}, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + ResourceOwner: &APIVersionKind{ + APIVersion: "v1/v2/v3", + Kind: "Foo", + }, + }, + want: errors.New("invalid value: v1/v2/v3: owner.apiVersion"), + }, { + name: "missing kind", + spec: ApiServerSourceSpec{ + EventMode: "Resource", + Resources: []APIVersionKindSelector{{ + APIVersion: "v1", + Kind: "Bar", + }}, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + ResourceOwner: &APIVersionKind{ + APIVersion: "v1", + }, + }, + want: errors.New("missing field(s): owner.kind"), + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.spec.Validate(context.TODO()) + if test.want != nil { + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("APIServerSourceSpec.Validate (-want, +got) =", diff) + } + } else if got != nil { + t.Error("APIServerSourceSpec.Validate wanted nil, got =", got.Error()) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta1/container_conversion.go b/pkg/apis/sources/v1beta1/container_conversion.go new file mode 100644 index 00000000000..0588f83a546 --- /dev/null +++ b/pkg/apis/sources/v1beta1/container_conversion.go @@ -0,0 +1,54 @@ +/* +Copyright 2020 The Knative Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/pkg/apis" +) + +// ConvertTo implements apis.Convertible. +// Converts source from v1beta1.ContainerSource into a higher version. +func (source *ContainerSource) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1.ContainerSource: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.SourceSpec = source.Spec.SourceSpec + sink.Spec.Template = source.Spec.Template + sink.Status.SourceStatus = source.Status.SourceStatus + return nil + default: + return apis.ConvertToViaProxy(ctx, source, &v1.ContainerSource{}, sink) + } +} + +// ConvertFrom implements apis.Convertible. +// Converts obj from a higher version into v1beta1.ContainerSource. +func (sink *ContainerSource) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1.ContainerSource: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.SourceSpec = source.Spec.SourceSpec + sink.Spec.Template = source.Spec.Template + sink.Status.SourceStatus = source.Status.SourceStatus + return nil + default: + return apis.ConvertFromViaProxy(ctx, source, &v1.ContainerSource{}, sink) + } +} diff --git a/pkg/apis/sources/v1beta1/container_conversion_test.go b/pkg/apis/sources/v1beta1/container_conversion_test.go new file mode 100644 index 00000000000..1680246ef80 --- /dev/null +++ b/pkg/apis/sources/v1beta1/container_conversion_test.go @@ -0,0 +1,311 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestContainerSourceConversionBadType(t *testing.T) { + good, bad := &ContainerSource{}, &testObject{} + + if err := good.ConvertTo(context.Background(), bad); err == nil { + t.Error("ConvertTo() = nil, wanted error") + } + + if err := good.ConvertFrom(context.Background(), bad); err == nil { + t.Error("ConvertFrom() = nil, wanted error") + } +} + +// This tests round tripping from v1beta1 to a higher version and back to v1beta1. +func TestContainerSourceConversionRoundTripUp(t *testing.T) { + versions := []apis.Convertible{&v1.ContainerSource{}} + + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + tests := []struct { + name string + in *ContainerSource + }{{name: "empty", + in: &ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: ContainerSourceSpec{}, + Status: ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, + }, {name: "simple configuration", + in: &ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: ContainerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test", + Image: "test-image", + }}, + }, + }, + }, + Status: ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "Unknown", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: ContainerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + }, + }, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test", + Image: "test-image", + }}, + }, + }, + }, + Status: ContainerSourceStatus{ + 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 := &ContainerSource{} + + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := cmp.Diff(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 TestContainerSourceConversionRoundTripDown(t *testing.T) { + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + ceOverrides := duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + } + + tests := []struct { + name string + in apis.Convertible + }{{name: "empty-v1", + in: &v1.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1.ContainerSourceSpec{}, + Status: v1.ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, + }, {name: "simple configuration-v1", + in: &v1.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1.ContainerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test", + Image: "test-image", + }}, + }, + }, + }, + Status: v1.ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "Unknown", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full-v1", + in: &v1.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1.ContainerSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test", + Image: "test-image", + }}, + }, + }, + }, + Status: v1.ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + down := &ContainerSource{} + if err := down.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertTo() =", err) + } + + got := (reflect.New(reflect.TypeOf(test.in).Elem()).Interface()).(apis.Convertible) + + if err := down.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta1/container_defaults.go b/pkg/apis/sources/v1beta1/container_defaults.go new file mode 100644 index 00000000000..624976a1990 --- /dev/null +++ b/pkg/apis/sources/v1beta1/container_defaults.go @@ -0,0 +1,42 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" +) + +func (s *ContainerSource) SetDefaults(ctx context.Context) { + withName := apis.WithinParent(ctx, s.ObjectMeta) + s.Spec.SetDefaults(withName) +} + +func (ss *ContainerSourceSpec) SetDefaults(ctx context.Context) { + containers := make([]corev1.Container, 0, len(ss.Template.Spec.Containers)) + for i, c := range ss.Template.Spec.Containers { + // If the Container specified has no name, then default to "_". + if c.Name == "" { + c.Name = fmt.Sprintf("%s-%d", apis.ParentMeta(ctx).Name, i) + } + containers = append(containers, c) + } + ss.Template.Spec.Containers = containers +} diff --git a/pkg/apis/sources/v1beta1/container_defaults_test.go b/pkg/apis/sources/v1beta1/container_defaults_test.go new file mode 100644 index 00000000000..a6b899c30dd --- /dev/null +++ b/pkg/apis/sources/v1beta1/container_defaults_test.go @@ -0,0 +1,114 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestContainerSourceDefaults(t *testing.T) { + testCases := map[string]struct { + initial ContainerSource + expected ContainerSource + }{ + "no container name": { + initial: ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Image: "test-image", + }}, + }, + }, + }, + }, + expected: ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test-name-0", + Image: "test-image", + }}, + }, + }, + }, + }, + }, + "one with container name one without": { + initial: ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test-container", + Image: "test-image", + }, { + Image: "test-another-image", + }}, + }, + }, + }, + }, + expected: ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-namespace", + }, + Spec: ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test-container", + Image: "test-image", + }, { + Name: "test-name-1", + Image: "test-another-image", + }}, + }, + }, + }, + }, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + tc.initial.SetDefaults(context.Background()) + if diff := cmp.Diff(tc.expected, tc.initial); diff != "" { + t.Fatal("Unexpected defaults (-want, +got):", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta1/container_lifecycle.go b/pkg/apis/sources/v1beta1/container_lifecycle.go new file mode 100644 index 00000000000..fff83cba7da --- /dev/null +++ b/pkg/apis/sources/v1beta1/container_lifecycle.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 v1beta1 + +import ( + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" +) + +const ( + // ContainerSourceConditionReady has status True when the ContainerSource is ready to send events. + ContainerSourceConditionReady = apis.ConditionReady + + // ContainerSourceConditionSinkBindingReady has status True when the ContainerSource's SinkBinding is ready. + ContainerSourceConditionSinkBindingReady apis.ConditionType = "SinkBindingReady" + + // ContainerSourceConditionReceiveAdapterReady has status True when the ContainerSource's ReceiveAdapter is ready. + ContainerSourceConditionReceiveAdapterReady apis.ConditionType = "ReceiveAdapterReady" +) + +var containerCondSet = apis.NewLivingConditionSet( + ContainerSourceConditionSinkBindingReady, + ContainerSourceConditionReceiveAdapterReady, +) + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (*ContainerSource) GetConditionSet() apis.ConditionSet { + return containerCondSet +} + +// GetCondition returns the condition currently associated with the given type, or nil. +func (s *ContainerSourceStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return containerCondSet.Manage(s).GetCondition(t) +} + +// GetTopLevelCondition returns the top level condition. +func (s *ContainerSourceStatus) GetTopLevelCondition() *apis.Condition { + return containerCondSet.Manage(s).GetTopLevelCondition() +} + +// IsReady returns true if the resource is ready overall. +func (s *ContainerSourceStatus) IsReady() bool { + return containerCondSet.Manage(s).IsHappy() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (s *ContainerSourceStatus) InitializeConditions() { + containerCondSet.Manage(s).InitializeConditions() +} + +// PropagateSinkBindingStatus uses the availability of the provided Deployment to determine if +// ContainerSourceConditionSinkBindingReady should be marked as true, false or unknown. +func (s *ContainerSourceStatus) PropagateSinkBindingStatus(status *SinkBindingStatus) { + // Do not copy conditions nor observedGeneration + conditions := s.Conditions + observedGeneration := s.ObservedGeneration + s.SourceStatus = status.SourceStatus + s.Conditions = conditions + s.ObservedGeneration = observedGeneration + + cond := status.GetCondition(apis.ConditionReady) + switch { + case cond == nil: + containerCondSet.Manage(s).MarkUnknown(ContainerSourceConditionSinkBindingReady, "", "") + case cond.Status == corev1.ConditionTrue: + containerCondSet.Manage(s).MarkTrue(ContainerSourceConditionSinkBindingReady) + case cond.Status == corev1.ConditionFalse: + containerCondSet.Manage(s).MarkFalse(ContainerSourceConditionSinkBindingReady, cond.Reason, cond.Message) + case cond.Status == corev1.ConditionUnknown: + containerCondSet.Manage(s).MarkUnknown(ContainerSourceConditionSinkBindingReady, cond.Reason, cond.Message) + default: + containerCondSet.Manage(s).MarkUnknown(ContainerSourceConditionSinkBindingReady, cond.Reason, cond.Message) + } +} + +// PropagateReceiveAdapterStatus uses the availability of the provided Deployment to determine if +// ContainerSourceConditionReceiveAdapterReady should be marked as true or false. +func (s *ContainerSourceStatus) PropagateReceiveAdapterStatus(d *appsv1.Deployment) { + deploymentAvailableFound := false + for _, cond := range d.Status.Conditions { + if cond.Type == appsv1.DeploymentAvailable { + deploymentAvailableFound = true + if cond.Status == corev1.ConditionTrue { + containerCondSet.Manage(s).MarkTrue(ContainerSourceConditionReceiveAdapterReady) + } else if cond.Status == corev1.ConditionFalse { + containerCondSet.Manage(s).MarkFalse(ContainerSourceConditionReceiveAdapterReady, cond.Reason, cond.Message) + } else if cond.Status == corev1.ConditionUnknown { + containerCondSet.Manage(s).MarkUnknown(ContainerSourceConditionReceiveAdapterReady, cond.Reason, cond.Message) + } + } + } + if !deploymentAvailableFound { + containerCondSet.Manage(s).MarkUnknown(ContainerSourceConditionReceiveAdapterReady, "DeploymentUnavailable", "The Deployment '%s' is unavailable.", d.Name) + } +} diff --git a/pkg/apis/sources/v1beta1/container_lifecycle_test.go b/pkg/apis/sources/v1beta1/container_lifecycle_test.go new file mode 100644 index 00000000000..4a8e5db8199 --- /dev/null +++ b/pkg/apis/sources/v1beta1/container_lifecycle_test.go @@ -0,0 +1,338 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +var ( + readySinkBinding = &SinkBinding{ + Status: SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: apis.ConditionReady, + Status: corev1.ConditionTrue, + }}, + }, + }, + }, + } + + notReadySinkBinding = &SinkBinding{ + Status: SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: apis.ConditionReady, + Status: corev1.ConditionFalse, + Reason: "Testing", + Message: "hi", + }}, + }, + }, + }, + } +) + +func TestContainerSourceGetConditionSet(t *testing.T) { + r := &ContainerSource{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestContainerSourceStatusIsReady(t *testing.T) { + tests := []struct { + name string + s *ContainerSourceStatus + wantConditionStatus corev1.ConditionStatus + want bool + }{{ + name: "uninitialized", + s: &ContainerSourceStatus{}, + want: false, + }, { + name: "initialized", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark ready ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateReceiveAdapterStatus(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark ready sb", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark ready sb and ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }, { + name: "mark ready sb and unavailable ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(unavailableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark ready sb and unknown ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(unknownDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark ready sb and not deployed ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(&appsv1.Deployment{}) + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark ready sb and ra the no sb", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + s.PropagateSinkBindingStatus(¬ReadySinkBinding.Status) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark ready sb and ra then not ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateReceiveAdapterStatus(availableDeployment) + s.PropagateSinkBindingStatus(¬ReadySinkBinding.Status) + s.PropagateReceiveAdapterStatus(unavailableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark not ready sb and ready ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(¬ReadySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + return s + }(), + wantConditionStatus: corev1.ConditionFalse, + want: false, + }, { + name: "mark not ready sb and ra then ready sb", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(¬ReadySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + 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 TestContainerSourceStatusGetCondition(t *testing.T) { + tests := []struct { + name string + s *ContainerSourceStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "uninitialized", + s: &ContainerSourceStatus{}, + condQuery: ContainerSourceConditionReady, + want: nil, + }, { + name: "initialized", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + return s + }(), + condQuery: ContainerSourceConditionReady, + want: &apis.Condition{ + Type: ContainerSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark ready ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateReceiveAdapterStatus(availableDeployment) + return s + }(), + condQuery: ContainerSourceConditionReady, + want: &apis.Condition{ + Type: ContainerSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark ready sb", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + return s + }(), + condQuery: ContainerSourceConditionReady, + want: &apis.Condition{ + Type: ContainerSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark ready sb and ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + return s + }(), + condQuery: ContainerSourceConditionReady, + want: &apis.Condition{ + Type: ContainerSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark ready sb and ra then no sb", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + s.PropagateSinkBindingStatus(¬ReadySinkBinding.Status) + return s + }(), + condQuery: ContainerSourceConditionReady, + want: &apis.Condition{ + Type: ContainerSourceConditionReady, + Status: corev1.ConditionFalse, + Reason: "Testing", + Message: "hi", + }, + }, { + name: "mark ready sb and ra then no ra", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + s.PropagateReceiveAdapterStatus(unavailableDeployment) + return s + }(), + condQuery: ContainerSourceConditionReady, + want: &apis.Condition{ + Type: ContainerSourceConditionReady, + Status: corev1.ConditionFalse, + }, + }, { + name: "mark not ready sb and ready ra then ready sb", + s: func() *ContainerSourceStatus { + s := &ContainerSourceStatus{} + s.InitializeConditions() + s.PropagateSinkBindingStatus(¬ReadySinkBinding.Status) + s.PropagateReceiveAdapterStatus(availableDeployment) + s.PropagateSinkBindingStatus(&readySinkBinding.Status) + return s + }(), + condQuery: ContainerSourceConditionReady, + want: &apis.Condition{ + Type: ContainerSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.s.GetCondition(test.condQuery) + ignoreTime := cmpopts.IgnoreFields(apis.Condition{}, + "LastTransitionTime", "Severity") + if diff := cmp.Diff(test.want, got, ignoreTime); diff != "" { + t.Error("unexpected condition (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta1/container_types.go b/pkg/apis/sources/v1beta1/container_types.go new file mode 100644 index 00000000000..444ae6b50e0 --- /dev/null +++ b/pkg/apis/sources/v1beta1/container_types.go @@ -0,0 +1,98 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +genreconciler +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ContainerSource is the Schema for the containersources API +type ContainerSource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ContainerSourceSpec `json:"spec,omitempty"` + Status ContainerSourceStatus `json:"status,omitempty"` +} + +var ( + _ runtime.Object = (*ContainerSource)(nil) + _ kmeta.OwnerRefable = (*ContainerSource)(nil) + _ apis.Validatable = (*ContainerSource)(nil) + _ apis.Defaultable = (*ContainerSource)(nil) + _ apis.HasSpec = (*ContainerSource)(nil) + _ duckv1.KRShaped = (*ContainerSource)(nil) +) + +// ContainerSourceSpec defines the desired state of ContainerSource +type ContainerSourceSpec 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"` + + // Template describes the pods that will be created + Template corev1.PodTemplateSpec `json:"template"` +} + +// GetGroupVersionKind returns the GroupVersionKind. +func (*ContainerSource) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("ContainerSource") +} + +// ContainerSourceStatus defines the observed state of ContainerSource +type ContainerSourceStatus 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 + +// ContainerSourceList contains a list of ContainerSource +type ContainerSourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ContainerSource `json:"items"` +} + +// GetUntypedSpec returns the spec of the ContainerSource. +func (c *ContainerSource) GetUntypedSpec() interface{} { + return c.Spec +} + +// GetStatus retrieves the status of the ContainerSource. Implements the KRShaped interface. +func (c *ContainerSource) GetStatus() *duckv1.Status { + return &c.Status.Status +} diff --git a/pkg/apis/sources/v1beta1/container_types_test.go b/pkg/apis/sources/v1beta1/container_types_test.go new file mode 100644 index 00000000000..dce4c233437 --- /dev/null +++ b/pkg/apis/sources/v1beta1/container_types_test.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 v1beta1 + +import "testing" + +func TestContainerSource_GetStatus(t *testing.T) { + r := &ContainerSource{ + Status: ContainerSourceStatus{}, + } + if got, want := r.GetStatus(), &r.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} + +func TestContainerSource_GetGroupVersionKind(t *testing.T) { + cs := ContainerSource{} + gvk := cs.GetGroupVersionKind() + if gvk.Kind != "ContainerSource" { + t.Errorf("Should be ContainerSource.") + } +} diff --git a/pkg/apis/sources/v1beta1/container_validation.go b/pkg/apis/sources/v1beta1/container_validation.go new file mode 100644 index 00000000000..5d6f9d1846b --- /dev/null +++ b/pkg/apis/sources/v1beta1/container_validation.go @@ -0,0 +1,59 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" +) + +func (c *ContainerSource) Validate(ctx context.Context) *apis.FieldError { + return c.Spec.Validate(ctx).ViaField("spec") +} + +func (cs *ContainerSourceSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + if fe := cs.Sink.Validate(ctx); fe != nil { + errs = errs.Also(fe.ViaField("sink")) + } + + // Validate there is at least a container + if cs.Template.Spec.Containers == nil || len(cs.Template.Spec.Containers) == 0 { + fe := apis.ErrMissingField("containers") + errs = errs.Also(fe) + } else { + for i := range cs.Template.Spec.Containers { + if ce := isValidContainer(&cs.Template.Spec.Containers[i]); ce != nil { + errs = errs.Also(ce.ViaFieldIndex("containers", i)) + } + } + } + return errs +} + +func isValidContainer(c *corev1.Container) *apis.FieldError { + var errs *apis.FieldError + if c.Name == "" { + errs = errs.Also(apis.ErrMissingField("name")) + } + if c.Image == "" { + errs = errs.Also(apis.ErrMissingField("image")) + } + return errs +} diff --git a/pkg/apis/sources/v1beta1/container_validation_test.go b/pkg/apis/sources/v1beta1/container_validation_test.go new file mode 100644 index 00000000000..dd9579876ef --- /dev/null +++ b/pkg/apis/sources/v1beta1/container_validation_test.go @@ -0,0 +1,112 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestContainerSourceValidation(t *testing.T) { + tests := []struct { + name string + spec ContainerSourceSpec + want *apis.FieldError + }{{ + name: "missing container", + spec: ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{}, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1beta1", + Kind: "Broker", + Name: "default", + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrMissingField("containers") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "missing container image", + spec: ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "test-name", + }}, + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "eventing.knative.dev/v1", + Kind: "Broker", + Name: "default", + }, + }, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrMissingField("containers[0].image") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "empty sink", + spec: ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "name", + Image: "image", + }}, + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{}, + }, + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrGeneric("expected at least one, got none", "sink.ref", "sink.uri") + errs = errs.Also(fe) + return errs + }(), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.spec.Validate(context.TODO()) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("ContainerSourceSpec.Validate (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta1/doc.go b/pkg/apis/sources/v1beta1/doc.go new file mode 100644 index 00000000000..c49210a401a --- /dev/null +++ b/pkg/apis/sources/v1beta1/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 v1beta1 contains API Schema definitions for the sources v1beta1 API group. +// +k8s:deepcopy-gen=package +// +groupName=sources.knative.dev +package v1beta1 diff --git a/pkg/apis/sources/v1beta1/implements_test.go b/pkg/apis/sources/v1beta1/implements_test.go new file mode 100644 index 00000000000..9e9f8209e6a --- /dev/null +++ b/pkg/apis/sources/v1beta1/implements_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package v1beta1 + +import ( + "testing" + + "knative.dev/pkg/apis/duck" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" +) + +func TestTypesImplements(t *testing.T) { + testCases := []struct { + instance interface{} + iface duck.Implementable + }{ + // ContainerSource + {instance: &ContainerSource{}, iface: &duckv1.Conditions{}}, + {instance: &ContainerSource{}, iface: &duckv1.Source{}}, + // SinkBinding + {instance: &SinkBinding{}, iface: &duckv1.Conditions{}}, + {instance: &SinkBinding{}, iface: &duckv1.Source{}}, + {instance: &SinkBinding{}, iface: &duckv1beta1.Binding{}}, + // ApiServerSource + {instance: &ApiServerSource{}, iface: &duckv1.Conditions{}}, + {instance: &ApiServerSource{}, 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/v1beta1/ping_conversion.go b/pkg/apis/sources/v1beta1/ping_conversion.go new file mode 100644 index 00000000000..44bd379e920 --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_conversion.go @@ -0,0 +1,98 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "encoding/json" + "fmt" + + cloudevents "github.com/cloudevents/sdk-go/v2" + + "knative.dev/eventing/pkg/apis/sources/v1beta2" + "knative.dev/pkg/apis" +) + +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 +// 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, + } + 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) + } + + return nil + default: + return apis.ConvertToViaProxy(ctx, source, &v1beta2.PingSource{}, sink) + } +} + +// ConvertFrom implements apis.Convertible +// 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, + } + + sink.Spec = PingSourceSpec{ + SourceSpec: source.Spec.SourceSpec, + Schedule: source.Spec.Schedule, + Timezone: source.Spec.Timezone, + } + + if source.Spec.ContentType == cloudevents.ApplicationJSON { + sink.Spec.JsonData = source.Spec.Data + } + + 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 new file mode 100644 index 00000000000..999ea8db313 --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_conversion_test.go @@ -0,0 +1,581 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + 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) { + good, bad := &PingSource{}, &testObject{} + + if err := good.ConvertTo(context.Background(), bad); err == nil { + t.Error("ConvertTo() = nil, wanted error") + } + + if err := good.ConvertFrom(context.Background(), bad); err == nil { + 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, + }, + }, + }, + }} + + 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 := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} + +// one way conversion: v1beta1 -> higher version +func TestPingSourceConversionOneWayUp(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 *PingSource + out *v1beta2.PingSource + }{{name: "empty", + in: &PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: PingSourceSpec{}, + Status: PingSourceStatus{}, + }, + out: &v1beta2.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta2.PingSourceSpec{}, + Status: v1beta2.PingSourceStatus{}, + }, + }, {name: "full configuration: marshalable jsonData", + in: &PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Schedule: "1 2 3 4 5", + Timezone: "Knative/Land", + JsonData: `{"foo":"bar"}`, + }, + Status: PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + CloudEventAttributes: ceAttributes, + }, + }, + }, + out: &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 configuration: unmarshalable jsonData", + in: &PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Schedule: "1 2 3 4 5", + Timezone: "Knative/Land", + JsonData: "hello", + }, + Status: PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + CloudEventAttributes: ceAttributes, + }, + }, + }, + out: &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) { + result := &v1beta2.PingSource{} + if err := test.in.ConvertTo(context.Background(), result); err != nil { + t.Error("ConvertFrom() =", err) + } + + if diff := cmp.Diff(test.out, result); diff != "" { + t.Error("one-way up conversion (-want, +got) =", diff) + } + }) + } +} + +// one way conversion: higher version -> v1beta1 +func TestPingSourceConversionOneWayDown(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 + out *PingSource + }{{name: "empty", + in: &v1beta2.PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1beta2.PingSourceSpec{}, + Status: v1beta2.PingSourceStatus{}, + }, + out: &PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: PingSourceSpec{}, + Status: PingSourceStatus{}, + }, + }, {name: "full configuration: json", + 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, + }, + }, + }, + out: &PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Schedule: "1 2 3 4 5", + Timezone: "Knative/Land", + JsonData: `{"foo":"bar"}`, + }, + Status: PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + CloudEventAttributes: ceAttributes, + }, + }, + }, + }, {name: "full configuration: xml payload", + 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.ApplicationXML, + Data: "hello world", + }, + Status: v1beta2.PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + CloudEventAttributes: ceAttributes, + }, + }, + }, + out: &PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Schedule: "1 2 3 4 5", + Timezone: "Knative/Land", + }, + Status: PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + CloudEventAttributes: ceAttributes, + }, + }, + }, + }, {name: "full configuration: binary payload", + 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.TextPlain, + Data: "ZGF0YQ==", + }, + Status: v1beta2.PingSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + CloudEventAttributes: ceAttributes, + }, + }, + }, + out: &PingSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: PingSourceSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + Schedule: "1 2 3 4 5", + Timezone: "Knative/Land", + }, + Status: 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) { + result := &PingSource{} + if err := result.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertFrom() =", err) + } + + if diff := cmp.Diff(test.out, result); diff != "" { + t.Error("one-way down conversion (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta1/ping_defaults.go b/pkg/apis/sources/v1beta1/ping_defaults.go new file mode 100644 index 00000000000..9fef43260f6 --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_defaults.go @@ -0,0 +1,35 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" +) + +const ( + defaultSchedule = "* * * * *" +) + +func (s *PingSource) SetDefaults(ctx context.Context) { + s.Spec.SetDefaults(ctx) +} + +func (ss *PingSourceSpec) SetDefaults(ctx context.Context) { + if ss.Schedule == "" { + ss.Schedule = defaultSchedule + } +} diff --git a/pkg/apis/sources/v1beta1/ping_defaults_test.go b/pkg/apis/sources/v1beta1/ping_defaults_test.go new file mode 100644 index 00000000000..9cd376afd38 --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_defaults_test.go @@ -0,0 +1,67 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestPingSourceSetDefaults(t *testing.T) { + testCases := map[string]struct { + initial PingSource + expected PingSource + }{ + "nil": { + expected: PingSource{ + Spec: PingSourceSpec{ + Schedule: defaultSchedule, + }, + }, + }, + "empty": { + initial: PingSource{}, + expected: PingSource{ + Spec: PingSourceSpec{ + Schedule: defaultSchedule, + }, + }, + }, + "with schedule": { + initial: PingSource{ + Spec: PingSourceSpec{ + Schedule: "1 2 3 4 5", + }, + }, + expected: PingSource{ + Spec: PingSourceSpec{ + Schedule: "1 2 3 4 5", + }, + }, + }, + } + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + tc.initial.SetDefaults(context.TODO()) + if diff := cmp.Diff(tc.expected, tc.initial); diff != "" { + t.Fatal("Unexpected defaults (-want, +got):", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta1/ping_lifecycle.go b/pkg/apis/sources/v1beta1/ping_lifecycle.go new file mode 100644 index 00000000000..454f492a451 --- /dev/null +++ b/pkg/apis/sources/v1beta1/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 v1beta1 + +import ( + "fmt" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/pkg/apis" +) + +const ( + // PingSourceConditionReady has status True when the PingSource is ready to send events. + PingSourceConditionReady = apis.ConditionReady + + // PingSourceConditionSinkProvided has status True when the PingSource has been configured with a sink target. + PingSourceConditionSinkProvided apis.ConditionType = "SinkProvided" + + // PingSourceConditionDeployed has status True when the PingSource has had it's receive adapter deployment created. + PingSourceConditionDeployed apis.ConditionType = "Deployed" +) + +var PingSourceCondSet = apis.NewLivingConditionSet( + PingSourceConditionSinkProvided, + PingSourceConditionDeployed) + +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/v1beta1/ping_lifecycle_test.go b/pkg/apis/sources/v1beta1/ping_lifecycle_test.go new file mode 100644 index 00000000000..3b9f1981a71 --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_lifecycle_test.go @@ -0,0 +1,260 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "knative.dev/pkg/apis" +) + +func TestPingSourceGetConditionSet(t *testing.T) { + r := &PingSource{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestPingSource_GetGroupVersionKind(t *testing.T) { + src := PingSource{} + gvk := src.GetGroupVersionKind() + + if gvk.Kind != "PingSource" { + t.Errorf("Should be PingSource.") + } +} + +func TestPingSource_PingSourceSource(t *testing.T) { + cePingSource := PingSourceSource("ns1", "job1") + + if cePingSource != "/apis/v1/namespaces/ns1/pingsources/job1" { + t.Errorf("Should be '/apis/v1/namespaces/ns1/pingsources/job1'") + } +} + +func TestPingSourceStatusIsReady(t *testing.T) { + exampleUri, _ := apis.ParseURL("uri://example") + + tests := []struct { + name string + s *PingSourceStatus + wantConditionStatus corev1.ConditionStatus + want bool + }{{ + name: "uninitialized", + s: &PingSourceStatus{}, + want: false, + }, { + name: "initialized", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark deployed", + s: func() *PingSourceStatus { + s := &PingSourceStatus{} + s.InitializeConditions() + s.PropagateDeploymentAvailability(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/v1beta1/ping_types.go b/pkg/apis/sources/v1beta1/ping_types.go new file mode 100644 index 00000000000..c421eeb91cd --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_types.go @@ -0,0 +1,102 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "knative.dev/pkg/apis" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +genreconciler +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:defaulter-gen=true + +// PingSource is the Schema for the PingSources API. +type PingSource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec PingSourceSpec `json:"spec,omitempty"` + Status PingSourceStatus `json:"status,omitempty"` +} + +// Check the interfaces that PingSource should be implementing. +var ( + _ runtime.Object = (*PingSource)(nil) + _ kmeta.OwnerRefable = (*PingSource)(nil) + _ apis.Validatable = (*PingSource)(nil) + _ apis.Defaultable = (*PingSource)(nil) + _ apis.HasSpec = (*PingSource)(nil) + _ duckv1.KRShaped = (*PingSource)(nil) +) + +// PingSourceSpec defines the desired state of the PingSource. +type PingSourceSpec struct { + // inherits duck/v1 SourceSpec, which currently provides: + // * Sink - a reference to an object that will resolve to a domain name or + // a URI directly to use as the sink. + // * CloudEventOverrides - defines overrides to control the output format + // and modifications of the event sent to the sink. + duckv1.SourceSpec `json:",inline"` + + // Schedule is the cronjob schedule. Defaults to `* * * * *`. + // +optional + Schedule string `json:"schedule,omitempty"` + + // Timezone modifies the actual time relative to the specified timezone. + // Defaults to the system time zone. + // More general information about time zones: https://www.iana.org/time-zones + // List of valid timezone values: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + Timezone string `json:"timezone,omitempty"` + + // JsonData is json encoded data used as the body of the event posted to + // the sink. Default is empty. If set, datacontenttype will also be set + // to "application/json". + // +optional + JsonData string `json:"jsonData,omitempty"` +} + +// PingSourceStatus defines the observed state of PingSource. +type PingSourceStatus struct { + // inherits duck/v1 SourceStatus, which currently provides: + // * ObservedGeneration - the 'Generation' of the Service that was last + // processed by the controller. + // * Conditions - the latest available observations of a resource's current + // state. + // * SinkURI - the current active sink URI that has been configured for the + // Source. + duckv1.SourceStatus `json:",inline"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PingSourceList contains a list of PingSources. +type PingSourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []PingSource `json:"items"` +} + +// GetStatus retrieves the status of the PingSource. Implements the KRShaped interface. +func (p *PingSource) GetStatus() *duckv1.Status { + return &p.Status.Status +} diff --git a/pkg/apis/sources/v1beta1/ping_types_test.go b/pkg/apis/sources/v1beta1/ping_types_test.go new file mode 100644 index 00000000000..bdcadebc0f5 --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_types_test.go @@ -0,0 +1,28 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import "testing" + +func TestPingSource_GetStatus(t *testing.T) { + p := &PingSource{ + Status: PingSourceStatus{}, + } + if got, want := p.GetStatus(), &p.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} diff --git a/pkg/apis/sources/v1beta1/ping_validation.go b/pkg/apis/sources/v1beta1/ping_validation.go new file mode 100644 index 00000000000..b4ca07f4b27 --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_validation.go @@ -0,0 +1,65 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "fmt" + "strings" + + "github.com/robfig/cron/v3" + "knative.dev/pkg/apis" + + "knative.dev/eventing/pkg/apis/sources/config" +) + +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) + } + } + + pingConfig := config.FromContextOrDefaults(ctx) + + pingDefaults := pingConfig.PingDefaults.GetPingConfig() + + if bsize := int64(len(cs.JsonData)); pingDefaults.DataMaxSize > -1 && bsize > pingDefaults.DataMaxSize { + fe := apis.ErrInvalidValue(fmt.Sprintf("the jsonData length of %d bytes exceeds limit set at %d.", bsize, pingDefaults.DataMaxSize), "jsonData") + errs = errs.Also(fe) + } + + if fe := cs.Sink.Validate(ctx); fe != nil { + errs = errs.Also(fe.ViaField("sink")) + } + return errs +} diff --git a/pkg/apis/sources/v1beta1/ping_validation_test.go b/pkg/apis/sources/v1beta1/ping_validation_test.go new file mode 100644 index 00000000000..b751d000716 --- /dev/null +++ b/pkg/apis/sources/v1beta1/ping_validation_test.go @@ -0,0 +1,197 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "strings" + "testing" + + duckv1 "knative.dev/pkg/apis/duck/v1" + + "github.com/google/go-cmp/cmp" + "knative.dev/pkg/apis" + + "knative.dev/eventing/pkg/apis/sources/config" +) + +func TestPingSourceValidation(t *testing.T) { + tests := []struct { + name string + source PingSource + ctx func(ctx context.Context) context.Context + want *apis.FieldError + }{{ + name: "valid spec", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: nil, + }, { + name: "valid spec with timezone", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + Timezone: "Europe/Paris", + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: 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: "v1alpha1", + 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: "v1alpha1", + 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: "too big json", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + JsonData: bigString(), + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + ctx: func(ctx context.Context) context.Context { + return config.ToContext(ctx, &config.Config{PingDefaults: &config.PingDefaults{DataMaxSize: 4096}}) + }, + want: func() *apis.FieldError { + var errs *apis.FieldError + fe := apis.ErrInvalidValue("the jsonData length of 5000 bytes exceeds limit set at 4096.", "spec.jsonData") + errs = errs.Also(fe) + return errs + }(), + }, { + name: "too big json but ok", + source: PingSource{ + Spec: PingSourceSpec{ + Schedule: "*/2 * * * *", + JsonData: bigString(), + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "v1alpha1", + Kind: "broker", + Name: "default", + }, + }, + }, + }, + }, + want: nil, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := context.TODO() + if test.ctx != nil { + ctx = test.ctx(ctx) + } + got := test.source.Validate(ctx) + if diff := cmp.Diff(test.want.Error(), got.Error()); diff != "" { + t.Error("PingSourceSpec.Validate (-want, +got) =", diff) + } + }) + } +} + +func bigString() string { + var b strings.Builder + b.Grow(5000) + b.WriteString("\"") + for i := 0; i < 4998; i++ { + b.WriteString("a") + } + b.WriteString("\"") + return b.String() +} diff --git a/pkg/apis/sources/v1beta1/register.go b/pkg/apis/sources/v1beta1/register.go new file mode 100644 index 00000000000..b577a1953e2 --- /dev/null +++ b/pkg/apis/sources/v1beta1/register.go @@ -0,0 +1,59 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "knative.dev/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: "v1beta1"} + +// 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, + &ApiServerSource{}, + &ApiServerSourceList{}, + &PingSource{}, + &PingSourceList{}, + &SinkBinding{}, + &SinkBindingList{}, + &ContainerSource{}, + &ContainerSourceList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/apis/sources/v1beta1/register_test.go b/pkg/apis/sources/v1beta1/register_test.go new file mode 100644 index 00000000000..fe0492dc875 --- /dev/null +++ b/pkg/apis/sources/v1beta1/register_test.go @@ -0,0 +1,77 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "testing" + + "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{ + "ApiServerSource", + "ApiServerSourceList", + "PingSource", + "PingSourceList", + "SinkBinding", + "SinkBindingList", + "ContainerSource", + "ContainerSourceList", + } { + if _, ok := types[name]; !ok { + t.Errorf("Did not find %q as registered type", name) + } + } + +} diff --git a/pkg/apis/sources/v1beta1/sinkbinding_context.go b/pkg/apis/sources/v1beta1/sinkbinding_context.go new file mode 100644 index 00000000000..8f4f7672cd8 --- /dev/null +++ b/pkg/apis/sources/v1beta1/sinkbinding_context.go @@ -0,0 +1,57 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + "knative.dev/pkg/apis" + "knative.dev/pkg/resolver" +) + +// sinkURIKey is used as the key for associating information +// with a context.Context. +type sinkURIKey struct{} +type resolverKey struct{} + +// WithSinkURI notes on the context for binding that the resolved SinkURI +// is the provided apis.URL. +func WithSinkURI(ctx context.Context, uri *apis.URL) context.Context { + return context.WithValue(ctx, sinkURIKey{}, uri) +} + +func WithURIResolver(ctx context.Context, resolver *resolver.URIResolver) context.Context { + return context.WithValue(ctx, resolverKey{}, resolver) +} + +// GetSinkURI accesses the apis.URL for the Sink URI that has been associated +// with this context. +func GetSinkURI(ctx context.Context) *apis.URL { + value := ctx.Value(sinkURIKey{}) + if value == nil { + return nil + } + return value.(*apis.URL) +} + +func GetURIResolver(ctx context.Context) *resolver.URIResolver { + value := ctx.Value(resolverKey{}) + if value == nil { + return nil + } + return value.(*resolver.URIResolver) +} diff --git a/pkg/apis/sources/v1beta1/sinkbinding_context_test.go b/pkg/apis/sources/v1beta1/sinkbinding_context_test.go new file mode 100644 index 00000000000..7cbac4095eb --- /dev/null +++ b/pkg/apis/sources/v1beta1/sinkbinding_context_test.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. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "knative.dev/pkg/apis" +) + +func TestGetSinkURI(t *testing.T) { + ctx := context.Background() + + if uri := GetSinkURI(ctx); uri != nil { + t.Errorf("GetSinkURI() = %v, wanted nil", uri) + } + + want := &apis.URL{ + Scheme: "https", + Host: "knobots.io", + Path: "/omg/a/path", + } + ctx = WithSinkURI(ctx, want) + + if got := GetSinkURI(ctx); got != want { + t.Errorf("GetSinkURI() = %v, wanted %v", got, want) + } +} + +func TestGetURIResolver(t *testing.T) { + ctx := context.Background() + + if resolver := GetURIResolver(ctx); resolver != nil { + t.Errorf("GetURIResolver() = %v, wanted nil", resolver) + } + +} diff --git a/pkg/apis/sources/v1beta1/sinkbinding_conversion.go b/pkg/apis/sources/v1beta1/sinkbinding_conversion.go new file mode 100644 index 00000000000..ed196e250b3 --- /dev/null +++ b/pkg/apis/sources/v1beta1/sinkbinding_conversion.go @@ -0,0 +1,60 @@ +/* +Copyright 2020 The Knative Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" +) + +// ConvertTo implements apis.Convertible. +// Converts source from v1beta1.SinkBinding into a higher version. +func (source *SinkBinding) ConvertTo(ctx context.Context, obj apis.Convertible) error { + switch sink := obj.(type) { + case *v1.SinkBinding: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.SourceSpec = source.Spec.SourceSpec + sink.Spec.BindingSpec = duckv1.BindingSpec{ + Subject: source.Spec.BindingSpec.Subject, + } + sink.Status.SourceStatus = source.Status.SourceStatus + return nil + default: + return apis.ConvertToViaProxy(ctx, source, &v1.SinkBinding{}, sink) + } +} + +// ConvertFrom implements apis.Convertible. +// Converts obj from a higher version into v1beta1.SinkBinding. +func (sink *SinkBinding) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + switch source := obj.(type) { + case *v1.SinkBinding: + sink.ObjectMeta = source.ObjectMeta + sink.Spec.SourceSpec = source.Spec.SourceSpec + sink.Spec.BindingSpec = duckv1beta1.BindingSpec{ + Subject: source.Spec.BindingSpec.Subject, + } + sink.Status.SourceStatus = source.Status.SourceStatus + return nil + default: + return apis.ConvertFromViaProxy(ctx, source, &v1.SinkBinding{}, sink) + } +} diff --git a/pkg/apis/sources/v1beta1/sinkbinding_conversion_test.go b/pkg/apis/sources/v1beta1/sinkbinding_conversion_test.go new file mode 100644 index 00000000000..7d4edccb883 --- /dev/null +++ b/pkg/apis/sources/v1beta1/sinkbinding_conversion_test.go @@ -0,0 +1,292 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "reflect" + "testing" + + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" + + "knative.dev/pkg/tracker" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +func TestSinkBindingConversionBadType(t *testing.T) { + good, bad := &SinkBinding{}, &testObject{} + + 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) + } +} + +func TestSinkBindingConversionRoundTripUp(t *testing.T) { + versions := []apis.Convertible{&v1.SinkBinding{}} + + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + subject := tracker.Reference{ + APIVersion: "API", + Kind: "K", + Namespace: "NS", + Name: "N", + } + + tests := []struct { + name string + in *SinkBinding + }{{name: "empty", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: SinkBindingSpec{}, + Status: SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }, + }, + }, + }, {name: "simple configuration", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + BindingSpec: duckv1beta1.BindingSpec{ + Subject: subject, + }, + }, + Status: SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "Unknown", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + }, + }, + BindingSpec: duckv1beta1.BindingSpec{ + Subject: subject, + }, + }, + Status: SinkBindingStatus{ + 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 := &SinkBinding{} + + if err := got.ConvertFrom(context.Background(), ver); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } + } +} + +// This tests round tripping from a higher version -> v1alpha1 and back to the higher version. +func TestSinkBindingConversionRoundTripDown(t *testing.T) { + path := apis.HTTP("") + path.Path = "/path" + sink := duckv1.Destination{ + Ref: &duckv1.KReference{ + Kind: "Foo", + Namespace: "Bar", + Name: "Baz", + APIVersion: "Baf", + }, + URI: path, + } + sinkUri := apis.HTTP("example.com") + sinkUri.Path = "path" + + ceOverrides := duckv1.CloudEventOverrides{ + Extensions: map[string]string{ + "foo": "bar", + "baz": "baf", + }, + } + + subject := tracker.Reference{ + APIVersion: "API", + Kind: "K", + Namespace: "NS", + Name: "N", + } + + tests := []struct { + name string + in apis.Convertible + }{{name: "empty", + in: &v1.SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1.SinkBindingSpec{}, + Status: v1.SinkBindingStatus{}, + }, + }, {name: "simple configuration", + in: &v1.SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1.SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + }, + Status: v1.SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }, {name: "full", + in: &v1.SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ping-name", + Namespace: "ping-ns", + Generation: 17, + }, + Spec: v1.SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + CloudEventOverrides: &ceOverrides, + }, + BindingSpec: duckv1.BindingSpec{Subject: subject}, + }, + Status: v1.SinkBindingStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + ObservedGeneration: 1, + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + SinkURI: sinkUri, + }, + }, + }, + }} + for _, test := range tests { + + t.Run(test.name, func(t *testing.T) { + down := &SinkBinding{} + if err := down.ConvertFrom(context.Background(), test.in); err != nil { + t.Error("ConvertTo() =", err) + } + + got := (reflect.New(reflect.TypeOf(test.in).Elem()).Interface()).(apis.Convertible) + + if err := down.ConvertTo(context.Background(), got); err != nil { + t.Error("ConvertFrom() =", err) + } + if diff := cmp.Diff(test.in, got); diff != "" { + t.Error("roundtrip (-want, +got) =", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta1/sinkbinding_defaults.go b/pkg/apis/sources/v1beta1/sinkbinding_defaults.go new file mode 100644 index 00000000000..b04ced1170f --- /dev/null +++ b/pkg/apis/sources/v1beta1/sinkbinding_defaults.go @@ -0,0 +1,34 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + "knative.dev/pkg/apis" +) + +// SetDefaults implements apis.Defaultable +func (fb *SinkBinding) SetDefaults(ctx context.Context) { + if fb.Spec.Subject.Namespace == "" { + // Default the subject's namespace to our namespace. + fb.Spec.Subject.Namespace = fb.Namespace + } + + withNS := apis.WithinParent(ctx, fb.ObjectMeta) + fb.Spec.Sink.SetDefaults(withNS) +} diff --git a/pkg/apis/sources/v1beta1/sinkbinding_defaults_test.go b/pkg/apis/sources/v1beta1/sinkbinding_defaults_test.go new file mode 100644 index 00000000000..e9226420bbe --- /dev/null +++ b/pkg/apis/sources/v1beta1/sinkbinding_defaults_test.go @@ -0,0 +1,151 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" + "knative.dev/pkg/tracker" +) + +func TestSinkBindingDefaulting(t *testing.T) { + tests := []struct { + name string + in *SinkBinding + want *SinkBinding + }{{ + name: "namespace is defaulted", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1beta1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "serving.knative.dev/v1", + Kind: "Service", + Name: "gemma", + }, + }, + }, + }, + }, + want: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1beta1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + // This is filled in by defaulting. + Namespace: "moore", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "serving.knative.dev/v1", + Kind: "Service", + Name: "gemma", + // This is filled in by defaulting. + Namespace: "moore", + }, + }, + }, + }, + }, + }, { + name: "no ref, given namespace", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1beta1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + Namespace: "lorefice", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + URI: &apis.URL{ + Scheme: "http", + Host: "moore.dev", + }, + }, + }, + }, + }, + want: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1beta1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + Namespace: "lorefice", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + URI: &apis.URL{ + Scheme: "http", + Host: "moore.dev", + }, + }, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.in + got.SetDefaults(context.Background()) + if !cmp.Equal(test.want, got) { + t.Error("SetDefaults (-want, +got) =", cmp.Diff(test.want, got)) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta1/sinkbinding_lifecycle.go b/pkg/apis/sources/v1beta1/sinkbinding_lifecycle.go new file mode 100644 index 00000000000..d628307778b --- /dev/null +++ b/pkg/apis/sources/v1beta1/sinkbinding_lifecycle.go @@ -0,0 +1,175 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "encoding/json" + "fmt" + + "go.uber.org/zap" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + "knative.dev/pkg/apis" + "knative.dev/pkg/apis/duck" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/logging" + "knative.dev/pkg/tracker" +) + +var sbCondSet = apis.NewLivingConditionSet( + SinkBindingConditionSinkProvided, +) + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (*SinkBinding) GetConditionSet() apis.ConditionSet { + return sbCondSet +} + +// GetGroupVersionKind returns the GroupVersionKind. +func (*SinkBinding) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("SinkBinding") +} + +// GetUntypedSpec implements apis.HasSpec +func (s *SinkBinding) GetUntypedSpec() interface{} { + return s.Spec +} + +// GetSubject implements psbinding.Bindable +func (sb *SinkBinding) GetSubject() tracker.Reference { + return sb.Spec.Subject +} + +// GetBindingStatus implements psbinding.Bindable +func (sb *SinkBinding) GetBindingStatus() duck.BindableStatus { + return &sb.Status +} + +// SetObservedGeneration implements psbinding.BindableStatus +func (sbs *SinkBindingStatus) SetObservedGeneration(gen int64) { + sbs.ObservedGeneration = gen +} + +// InitializeConditions populates the SinkBindingStatus's conditions field +// with all of its conditions configured to Unknown. +func (sbs *SinkBindingStatus) InitializeConditions() { + sbCondSet.Manage(sbs).InitializeConditions() +} + +// MarkBindingUnavailable marks the SinkBinding's Ready condition to False with +// the provided reason and message. +func (sbs *SinkBindingStatus) MarkBindingUnavailable(reason, message string) { + sbCondSet.Manage(sbs).MarkFalse(SinkBindingConditionReady, reason, message) +} + +// MarkBindingAvailable marks the SinkBinding's Ready condition to True. +func (sbs *SinkBindingStatus) MarkBindingAvailable() { + sbCondSet.Manage(sbs).MarkTrue(SinkBindingConditionReady) +} + +// MarkSink sets the condition that the source has a sink configured. +func (sbs *SinkBindingStatus) MarkSink(uri *apis.URL) { + sbs.SinkURI = uri + if uri != nil { + sbCondSet.Manage(sbs).MarkTrue(SinkBindingConditionSinkProvided) + } else { + sbCondSet.Manage(sbs).MarkFalse(SinkBindingConditionSinkProvided, "SinkEmpty", "Sink has resolved to empty.%s", "") + } +} + +// Do implements psbinding.Bindable +func (sb *SinkBinding) Do(ctx context.Context, ps *duckv1.WithPod) { + // First undo so that we can just unconditionally append below. + sb.Undo(ctx, ps) + + resolver := GetURIResolver(ctx) + if resolver == nil { + logging.FromContext(ctx).Errorf("No Resolver associated with context for sink: %+v", sb) + } + uri, err := resolver.URIFromDestinationV1(ctx, sb.Spec.Sink, sb) + if err != nil { + logging.FromContext(ctx).Errorw("URI could not be extracted from destination: ", zap.Error(err)) + return + } + sb.Status.MarkSink(uri) + + var ceOverrides string + if sb.Spec.CloudEventOverrides != nil { + if co, err := json.Marshal(sb.Spec.SourceSpec.CloudEventOverrides); err != nil { + logging.FromContext(ctx).Errorw(fmt.Sprintf("Failed to marshal CloudEventOverrides into JSON for %+v", sb), zap.Error(err)) + } else if len(co) > 0 { + ceOverrides = string(co) + } + } + + spec := ps.Spec.Template.Spec + for i := range spec.InitContainers { + spec.InitContainers[i].Env = append(spec.InitContainers[i].Env, corev1.EnvVar{ + Name: "K_SINK", + Value: uri.String(), + }, corev1.EnvVar{ + Name: "K_CE_OVERRIDES", + Value: ceOverrides, + }) + } + for i := range spec.Containers { + spec.Containers[i].Env = append(spec.Containers[i].Env, corev1.EnvVar{ + Name: "K_SINK", + Value: uri.String(), + }, corev1.EnvVar{ + Name: "K_CE_OVERRIDES", + Value: ceOverrides, + }) + } +} + +func (sb *SinkBinding) Undo(ctx context.Context, ps *duckv1.WithPod) { + spec := ps.Spec.Template.Spec + for i, c := range spec.InitContainers { + if len(c.Env) == 0 { + continue + } + env := make([]corev1.EnvVar, 0, len(spec.InitContainers[i].Env)) + for j, ev := range c.Env { + switch ev.Name { + case "K_SINK", "K_CE_OVERRIDES": + continue + default: + env = append(env, spec.InitContainers[i].Env[j]) + } + } + spec.InitContainers[i].Env = env + } + for i, c := range spec.Containers { + if len(c.Env) == 0 { + continue + } + env := make([]corev1.EnvVar, 0, len(spec.Containers[i].Env)) + for j, ev := range c.Env { + switch ev.Name { + case "K_SINK", "K_CE_OVERRIDES": + continue + default: + env = append(env, spec.Containers[i].Env[j]) + } + } + spec.Containers[i].Env = env + } +} diff --git a/pkg/apis/sources/v1beta1/sinkbinding_lifecycle_test.go b/pkg/apis/sources/v1beta1/sinkbinding_lifecycle_test.go new file mode 100644 index 00000000000..fd4d189ca6f --- /dev/null +++ b/pkg/apis/sources/v1beta1/sinkbinding_lifecycle_test.go @@ -0,0 +1,542 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" + "knative.dev/pkg/client/injection/ducks/duck/v1/addressable" + fakedynamicclient "knative.dev/pkg/injection/clients/dynamicclient/fake" + "knative.dev/pkg/resolver" + "knative.dev/pkg/tracker" +) + +func init() { + duckv1beta1.AddToScheme(scheme.Scheme) + duckv1.AddToScheme(scheme.Scheme) +} + +func TestSinkBindingGetConditionSet(t *testing.T) { + r := &SinkBinding{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestSinkBindingGetGroupVersionKind(t *testing.T) { + r := &SinkBinding{} + want := schema.GroupVersionKind{ + Group: "sources.knative.dev", + Version: "v1beta1", + Kind: "SinkBinding", + } + if got := r.GetGroupVersionKind(); got != want { + t.Errorf("got: %v, want: %v", got, want) + } +} + +func TestSinkBindingGetters(t *testing.T) { + r := &SinkBinding{ + Spec: SinkBindingSpec{ + BindingSpec: duckv1beta1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "foo", + }, + }, + }, + } + if got, want := r.GetUntypedSpec(), r.Spec; !reflect.DeepEqual(got, want) { + t.Errorf("GetUntypedSpec() = %v, want: %v", got, want) + } + if got, want := r.GetSubject(), r.Spec.Subject; !reflect.DeepEqual(got, want) { + t.Errorf("GetSubject() = %v, want: %v", got, want) + } + if got, want := r.GetBindingStatus(), &r.Status; !reflect.DeepEqual(got, want) { + t.Errorf("GetBindingStatus() = %v, want: %v", got, want) + } +} + +func TestSinkBindingSetObsGen(t *testing.T) { + r := &SinkBinding{ + Spec: SinkBindingSpec{ + BindingSpec: duckv1beta1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "foo", + }, + }, + }, + } + want := int64(3762) + r.GetBindingStatus().SetObservedGeneration(want) + if got := r.Status.ObservedGeneration; got != want { + t.Errorf("SetObservedGeneration() = %d, wanted %d", got, want) + } +} + +func TestSinkBindingStatusIsReady(t *testing.T) { + sink := apis.HTTP("table.ns.svc.cluster.local/flip") + sink.Scheme = "uri" + tests := []struct { + name string + s *SinkBindingStatus + want bool + }{{ + name: "uninitialized", + s: &SinkBindingStatus{}, + want: false, + }, { + name: "initialized", + s: func() *SinkBindingStatus { + s := &SinkBindingStatus{} + s.InitializeConditions() + return s + }(), + want: false, + }, { + name: "mark binding unavailable", + s: func() *SinkBindingStatus { + s := &SinkBindingStatus{} + s.InitializeConditions() + s.MarkBindingUnavailable("TheReason", "this is the message") + return s + }(), + want: false, + }, { + name: "mark sink", + s: func() *SinkBindingStatus { + s := &SinkBindingStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkBindingUnavailable("TheReason", "this is the message") + return s + }(), + want: false, + }, { + name: "mark available", + s: func() *SinkBindingStatus { + s := &SinkBindingStatus{} + s.InitializeConditions() + s.MarkSink(sink) + s.MarkBindingAvailable() + return s + }(), + want: true, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.s.IsReady() + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("%s: unexpected condition (-want, +got) = %v", test.name, diff) + } + }) + } +} + +func TestSinkBindingUndo(t *testing.T) { + tests := []struct { + name string + in *duckv1.WithPod + want *duckv1.WithPod + }{{ + name: "nothing to remove", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + }}, + }, + }, + }, + }, + }, { + name: "lots to remove", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{ + Name: "setup", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "K_SINK", + Value: "http://localhost:8080", + }, { + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "K_SINK", + Value: "http://localhost:8080", + }, { + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }, { + Name: "sidecar", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: "http://localhost:8080", + }, { + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{ + Name: "setup", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "BAZ", + Value: "INGA", + }}, + }}, + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "BAZ", + Value: "INGA", + }}, + }, { + Name: "sidecar", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "BAZ", + Value: "INGA", + }}, + }}, + }, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.in + sb := &SinkBinding{} + sb.Undo(context.Background(), got) + + if !cmp.Equal(got, test.want) { + t.Error("Undo (-want, +got):", cmp.Diff(test.want, got)) + } + }) + } +} + +func TestSinkBindingDo(t *testing.T) { + destination := duckv1.Destination{ + URI: &apis.URL{ + Scheme: "http", + Host: "thing.ns.svc.cluster.local", + Path: "/a/path", + }, + } + + overrides := duckv1.CloudEventOverrides{Extensions: map[string]string{"foo": "bar"}} + + tests := []struct { + name string + in *duckv1.WithPod + want *duckv1.WithPod + }{{ + name: "nothing to add", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: destination.URI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: destination.URI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + }, { + name: "fix the URI", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: "the wrong value", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"wrong":"value"}}`, + }}, + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: destination.URI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + }, { + name: "lots to add", + in: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{ + Name: "setup", + Image: "busybox", + }}, + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "BAZ", + Value: "INGA", + }}, + }, { + Name: "sidecar", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "BAZ", + Value: "INGA", + }}, + }}, + }, + }, + }, + }, + want: &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{{ + Name: "setup", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: destination.URI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "FOO", + Value: "BAR", + }, { + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_SINK", + Value: destination.URI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }, { + Name: "sidecar", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "BAZ", + Value: "INGA", + }, { + Name: "K_SINK", + Value: destination.URI.String(), + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"foo":"bar"}}`, + }}, + }}, + }, + }, + }, + }, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.in + ctx, _ := fakedynamicclient.With(context.Background(), scheme.Scheme, got) + ctx = addressable.WithDuck(ctx) + r := resolver.NewURIResolver(ctx, func(types.NamespacedName) {}) + ctx = WithURIResolver(context.Background(), r) + + sb := &SinkBinding{Spec: SinkBindingSpec{ + SourceSpec: duckv1.SourceSpec{ + Sink: destination, + CloudEventOverrides: &overrides, + }, + }} + sb.Do(ctx, got) + + if !cmp.Equal(got, test.want) { + t.Error("Undo (-want, +got):", cmp.Diff(test.want, got)) + } + }) + } +} + +func TestSinkBindingDoNoURI(t *testing.T) { + want := &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{}, + }}, + }, + }, + }, + } + got := &duckv1.WithPod{ + Spec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "blah", + Image: "busybox", + Env: []corev1.EnvVar{{ + Name: "K_SINK", + Value: "this should be removed", + }, { + Name: "K_CE_OVERRIDES", + Value: `{"extensions":{"tobe":"removed"}}`, + }}, + }}, + }, + }, + }, + } + + sb := &SinkBinding{} + sb.Do(context.Background(), got) + + if !cmp.Equal(got, want) { + t.Error("Undo (-want, +got):", cmp.Diff(want, got)) + } +} diff --git a/pkg/apis/sources/v1beta1/sinkbinding_types.go b/pkg/apis/sources/v1beta1/sinkbinding_types.go new file mode 100644 index 00000000000..9e5c2c2c44a --- /dev/null +++ b/pkg/apis/sources/v1beta1/sinkbinding_types.go @@ -0,0 +1,105 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:defaulter-gen=true + +// SinkBinding describes a Binding that is also a Source. +// The `sink` (from the Source duck) is resolved to a URL and +// then projected into the `subject` by augmenting the runtime +// contract of the referenced containers to have a `K_SINK` +// environment variable holding the endpoint to which to send +// cloud events. +type SinkBinding struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SinkBindingSpec `json:"spec"` + Status SinkBindingStatus `json:"status"` +} + +// Check the interfaces that SinkBinding should be implementing. +var ( + _ runtime.Object = (*SinkBinding)(nil) + _ kmeta.OwnerRefable = (*SinkBinding)(nil) + _ apis.Validatable = (*SinkBinding)(nil) + _ apis.Defaultable = (*SinkBinding)(nil) + _ apis.HasSpec = (*SinkBinding)(nil) + _ duckv1.KRShaped = (*SinkBinding)(nil) +) + +// SinkBindingSpec holds the desired state of the SinkBinding (from the client). +type SinkBindingSpec 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"` + + // inherits duck/v1beta1 BindingSpec, which currently provides: + // * Subject - Subject references the resource(s) whose "runtime contract" + // should be augmented by Binding implementations. + duckv1beta1.BindingSpec `json:",inline"` +} + +const ( + // SinkBindingConditionReady is configured to indicate whether the Binding + // has been configured for resources subject to its runtime contract. + SinkBindingConditionReady = apis.ConditionReady + + // SinkBindingConditionSinkProvided is configured to indicate whether the + // sink has been properly extracted from the resolver. + SinkBindingConditionSinkProvided apis.ConditionType = "SinkProvided" +) + +// SinkBindingStatus communicates the observed state of the SinkBinding (from the controller). +type SinkBindingStatus 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 + +// SinkBindingList contains a list of SinkBinding +type SinkBindingList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []SinkBinding `json:"items"` +} + +// GetStatus retrieves the status of the SinkBinding. Implements the KRShaped interface. +func (s *SinkBinding) GetStatus() *duckv1.Status { + return &s.Status.Status +} diff --git a/pkg/apis/sources/v1beta1/sinkbinding_types_test.go b/pkg/apis/sources/v1beta1/sinkbinding_types_test.go new file mode 100644 index 00000000000..a30f7230815 --- /dev/null +++ b/pkg/apis/sources/v1beta1/sinkbinding_types_test.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 v1beta1 + +import "testing" + +func TestSinkBinding_GetStatus(t *testing.T) { + r := &SinkBinding{ + Status: SinkBindingStatus{}, + } + if got, want := r.GetStatus(), &r.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} + +func TestSinkBinding_GetGroupVersionKind(t *testing.T) { + sb := SinkBinding{} + gvk := sb.GetGroupVersionKind() + if gvk.Kind != "SinkBinding" { + t.Errorf("Should be SinkBinding.") + } +} diff --git a/pkg/apis/sources/v1beta1/sinkbinding_validation.go b/pkg/apis/sources/v1beta1/sinkbinding_validation.go new file mode 100644 index 00000000000..a8447905c19 --- /dev/null +++ b/pkg/apis/sources/v1beta1/sinkbinding_validation.go @@ -0,0 +1,38 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + + "knative.dev/pkg/apis" +) + +// Validate implements apis.Validatable +func (fb *SinkBinding) Validate(ctx context.Context) *apis.FieldError { + err := fb.Spec.Validate(ctx).ViaField("spec") + if fb.Spec.Subject.Namespace != "" && fb.Namespace != fb.Spec.Subject.Namespace { + err = err.Also(apis.ErrInvalidValue(fb.Spec.Subject.Namespace, "spec.subject.namespace")) + } + return err +} + +// Validate implements apis.Validatable +func (fbs *SinkBindingSpec) Validate(ctx context.Context) *apis.FieldError { + return fbs.Subject.Validate(ctx).ViaField("subject").Also( + fbs.Sink.Validate(ctx).ViaField("sink")) +} diff --git a/pkg/apis/sources/v1beta1/sinkbinding_validation_test.go b/pkg/apis/sources/v1beta1/sinkbinding_validation_test.go new file mode 100644 index 00000000000..25729615dde --- /dev/null +++ b/pkg/apis/sources/v1beta1/sinkbinding_validation_test.go @@ -0,0 +1,126 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "context" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" + "knative.dev/pkg/tracker" +) + +func TestSinkBindingValidation(t *testing.T) { + tests := []struct { + name string + in *SinkBinding + want *apis.FieldError + }{{ + name: "missing subject namespace", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1beta1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "serving.knative.dev/v1", + Kind: "Service", + Name: "gemma", + Namespace: "namespace", + }, + }, + }, + }, + }, + want: apis.ErrMissingField("spec.subject.namespace"), + }, { + name: "invalid subject namespace", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1beta1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + Namespace: "lorefice", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + Ref: &duckv1.KReference{ + APIVersion: "serving.knative.dev/v1", + Kind: "Service", + Name: "gemma", + Namespace: "namespace", + }, + }, + }, + }, + }, + want: apis.ErrInvalidValue("lorefice", "spec.subject.namespace"), + }, { + name: "missing sink information", + in: &SinkBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "matt", + Namespace: "moore", + }, + Spec: SinkBindingSpec{ + BindingSpec: duckv1beta1.BindingSpec{ + Subject: tracker.Reference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: "jeanne", + Namespace: "moore", + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{}, + }, + }, + }, + want: apis.ErrGeneric("expected at least one, got none", "spec.sink.ref", "spec.sink.uri"), + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := test.in.Validate(context.Background()) + if (test.want != nil) != (got != nil) { + t.Errorf("Validation() = %v, wanted %v", got, test.want) + } else if test.want != nil && test.want.Error() != got.Error() { + t.Errorf("Validation() = %v, wanted %v", got, test.want) + } + }) + } +} diff --git a/pkg/apis/sources/v1beta1/zz_generated.deepcopy.go b/pkg/apis/sources/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..8d20cb3ecf4 --- /dev/null +++ b/pkg/apis/sources/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,457 @@ +// +build !ignore_autogenerated + +/* +Copyright 2021 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 v1beta1 + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIVersionKind) DeepCopyInto(out *APIVersionKind) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIVersionKind. +func (in *APIVersionKind) DeepCopy() *APIVersionKind { + if in == nil { + return nil + } + out := new(APIVersionKind) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIVersionKindSelector) DeepCopyInto(out *APIVersionKindSelector) { + *out = *in + if in.LabelSelector != nil { + in, out := &in.LabelSelector, &out.LabelSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIVersionKindSelector. +func (in *APIVersionKindSelector) DeepCopy() *APIVersionKindSelector { + if in == nil { + return nil + } + out := new(APIVersionKindSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApiServerSource) DeepCopyInto(out *ApiServerSource) { + *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 ApiServerSource. +func (in *ApiServerSource) DeepCopy() *ApiServerSource { + if in == nil { + return nil + } + out := new(ApiServerSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ApiServerSource) 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 *ApiServerSourceList) DeepCopyInto(out *ApiServerSourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ApiServerSource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApiServerSourceList. +func (in *ApiServerSourceList) DeepCopy() *ApiServerSourceList { + if in == nil { + return nil + } + out := new(ApiServerSourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ApiServerSourceList) 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 *ApiServerSourceSpec) DeepCopyInto(out *ApiServerSourceSpec) { + *out = *in + in.SourceSpec.DeepCopyInto(&out.SourceSpec) + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make([]APIVersionKindSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ResourceOwner != nil { + in, out := &in.ResourceOwner, &out.ResourceOwner + *out = new(APIVersionKind) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApiServerSourceSpec. +func (in *ApiServerSourceSpec) DeepCopy() *ApiServerSourceSpec { + if in == nil { + return nil + } + out := new(ApiServerSourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApiServerSourceStatus) DeepCopyInto(out *ApiServerSourceStatus) { + *out = *in + in.SourceStatus.DeepCopyInto(&out.SourceStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApiServerSourceStatus. +func (in *ApiServerSourceStatus) DeepCopy() *ApiServerSourceStatus { + if in == nil { + return nil + } + out := new(ApiServerSourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerSource) DeepCopyInto(out *ContainerSource) { + *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 ContainerSource. +func (in *ContainerSource) DeepCopy() *ContainerSource { + if in == nil { + return nil + } + out := new(ContainerSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ContainerSource) 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 *ContainerSourceList) DeepCopyInto(out *ContainerSourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ContainerSource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerSourceList. +func (in *ContainerSourceList) DeepCopy() *ContainerSourceList { + if in == nil { + return nil + } + out := new(ContainerSourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ContainerSourceList) 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 *ContainerSourceSpec) DeepCopyInto(out *ContainerSourceSpec) { + *out = *in + in.SourceSpec.DeepCopyInto(&out.SourceSpec) + in.Template.DeepCopyInto(&out.Template) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerSourceSpec. +func (in *ContainerSourceSpec) DeepCopy() *ContainerSourceSpec { + if in == nil { + return nil + } + out := new(ContainerSourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerSourceStatus) DeepCopyInto(out *ContainerSourceStatus) { + *out = *in + in.SourceStatus.DeepCopyInto(&out.SourceStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerSourceStatus. +func (in *ContainerSourceStatus) DeepCopy() *ContainerSourceStatus { + if in == nil { + return nil + } + out := new(ContainerSourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PingSource) DeepCopyInto(out *PingSource) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PingSource. +func (in *PingSource) DeepCopy() *PingSource { + if in == nil { + return nil + } + out := new(PingSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PingSource) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PingSourceList) DeepCopyInto(out *PingSourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PingSource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PingSourceList. +func (in *PingSourceList) DeepCopy() *PingSourceList { + if in == nil { + return nil + } + out := new(PingSourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PingSourceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PingSourceSpec) DeepCopyInto(out *PingSourceSpec) { + *out = *in + in.SourceSpec.DeepCopyInto(&out.SourceSpec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PingSourceSpec. +func (in *PingSourceSpec) DeepCopy() *PingSourceSpec { + if in == nil { + return nil + } + out := new(PingSourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PingSourceStatus) DeepCopyInto(out *PingSourceStatus) { + *out = *in + in.SourceStatus.DeepCopyInto(&out.SourceStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PingSourceStatus. +func (in *PingSourceStatus) DeepCopy() *PingSourceStatus { + if in == nil { + return nil + } + out := new(PingSourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SinkBinding) DeepCopyInto(out *SinkBinding) { + *out = *in + 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 SinkBinding. +func (in *SinkBinding) DeepCopy() *SinkBinding { + if in == nil { + return nil + } + out := new(SinkBinding) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SinkBinding) 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 *SinkBindingList) DeepCopyInto(out *SinkBindingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SinkBinding, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SinkBindingList. +func (in *SinkBindingList) DeepCopy() *SinkBindingList { + if in == nil { + return nil + } + out := new(SinkBindingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SinkBindingList) 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 *SinkBindingSpec) DeepCopyInto(out *SinkBindingSpec) { + *out = *in + in.SourceSpec.DeepCopyInto(&out.SourceSpec) + in.BindingSpec.DeepCopyInto(&out.BindingSpec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SinkBindingSpec. +func (in *SinkBindingSpec) DeepCopy() *SinkBindingSpec { + if in == nil { + return nil + } + out := new(SinkBindingSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SinkBindingStatus) DeepCopyInto(out *SinkBindingStatus) { + *out = *in + in.SourceStatus.DeepCopyInto(&out.SourceStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SinkBindingStatus. +func (in *SinkBindingStatus) DeepCopy() *SinkBindingStatus { + if in == nil { + return nil + } + out := new(SinkBindingStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index dfdaf91fa5f..5938d605329 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -27,8 +27,13 @@ import ( eventingv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1" eventingv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1" flowsv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/flows/v1" + flowsv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/flows/v1beta1" messagingv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1" + messagingv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1beta1" sourcesv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1" + 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" ) @@ -36,8 +41,13 @@ type Interface interface { Discovery() discovery.DiscoveryInterface EventingV1beta1() eventingv1beta1.EventingV1beta1Interface EventingV1() eventingv1.EventingV1Interface + FlowsV1beta1() flowsv1beta1.FlowsV1beta1Interface FlowsV1() flowsv1.FlowsV1Interface + MessagingV1beta1() messagingv1beta1.MessagingV1beta1Interface MessagingV1() messagingv1.MessagingV1Interface + SourcesV1alpha1() sourcesv1alpha1.SourcesV1alpha1Interface + SourcesV1alpha2() sourcesv1alpha2.SourcesV1alpha2Interface + SourcesV1beta1() sourcesv1beta1.SourcesV1beta1Interface SourcesV1beta2() sourcesv1beta2.SourcesV1beta2Interface SourcesV1() sourcesv1.SourcesV1Interface } @@ -46,12 +56,17 @@ type Interface interface { // version included in a Clientset. type Clientset struct { *discovery.DiscoveryClient - eventingV1beta1 *eventingv1beta1.EventingV1beta1Client - eventingV1 *eventingv1.EventingV1Client - flowsV1 *flowsv1.FlowsV1Client - messagingV1 *messagingv1.MessagingV1Client - sourcesV1beta2 *sourcesv1beta2.SourcesV1beta2Client - sourcesV1 *sourcesv1.SourcesV1Client + eventingV1beta1 *eventingv1beta1.EventingV1beta1Client + eventingV1 *eventingv1.EventingV1Client + flowsV1beta1 *flowsv1beta1.FlowsV1beta1Client + flowsV1 *flowsv1.FlowsV1Client + messagingV1beta1 *messagingv1beta1.MessagingV1beta1Client + messagingV1 *messagingv1.MessagingV1Client + sourcesV1alpha1 *sourcesv1alpha1.SourcesV1alpha1Client + sourcesV1alpha2 *sourcesv1alpha2.SourcesV1alpha2Client + sourcesV1beta1 *sourcesv1beta1.SourcesV1beta1Client + sourcesV1beta2 *sourcesv1beta2.SourcesV1beta2Client + sourcesV1 *sourcesv1.SourcesV1Client } // EventingV1beta1 retrieves the EventingV1beta1Client @@ -64,16 +79,41 @@ func (c *Clientset) EventingV1() eventingv1.EventingV1Interface { return c.eventingV1 } +// FlowsV1beta1 retrieves the FlowsV1beta1Client +func (c *Clientset) FlowsV1beta1() flowsv1beta1.FlowsV1beta1Interface { + return c.flowsV1beta1 +} + // FlowsV1 retrieves the FlowsV1Client func (c *Clientset) FlowsV1() flowsv1.FlowsV1Interface { return c.flowsV1 } +// MessagingV1beta1 retrieves the MessagingV1beta1Client +func (c *Clientset) MessagingV1beta1() messagingv1beta1.MessagingV1beta1Interface { + return c.messagingV1beta1 +} + // MessagingV1 retrieves the MessagingV1Client func (c *Clientset) MessagingV1() messagingv1.MessagingV1Interface { return c.messagingV1 } +// SourcesV1alpha1 retrieves the SourcesV1alpha1Client +func (c *Clientset) SourcesV1alpha1() sourcesv1alpha1.SourcesV1alpha1Interface { + return c.sourcesV1alpha1 +} + +// SourcesV1alpha2 retrieves the SourcesV1alpha2Client +func (c *Clientset) SourcesV1alpha2() sourcesv1alpha2.SourcesV1alpha2Interface { + return c.sourcesV1alpha2 +} + +// SourcesV1beta1 retrieves the SourcesV1beta1Client +func (c *Clientset) SourcesV1beta1() sourcesv1beta1.SourcesV1beta1Interface { + return c.sourcesV1beta1 +} + // SourcesV1beta2 retrieves the SourcesV1beta2Client func (c *Clientset) SourcesV1beta2() sourcesv1beta2.SourcesV1beta2Interface { return c.sourcesV1beta2 @@ -113,14 +153,34 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { if err != nil { return nil, err } + cs.flowsV1beta1, err = flowsv1beta1.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } cs.flowsV1, err = flowsv1.NewForConfig(&configShallowCopy) if err != nil { return nil, err } + cs.messagingV1beta1, err = messagingv1beta1.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } cs.messagingV1, err = messagingv1.NewForConfig(&configShallowCopy) if err != nil { return nil, err } + cs.sourcesV1alpha1, err = sourcesv1alpha1.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } + cs.sourcesV1alpha2, err = sourcesv1alpha2.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } + cs.sourcesV1beta1, err = sourcesv1beta1.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } cs.sourcesV1beta2, err = sourcesv1beta2.NewForConfig(&configShallowCopy) if err != nil { return nil, err @@ -143,8 +203,13 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { var cs Clientset cs.eventingV1beta1 = eventingv1beta1.NewForConfigOrDie(c) cs.eventingV1 = eventingv1.NewForConfigOrDie(c) + cs.flowsV1beta1 = flowsv1beta1.NewForConfigOrDie(c) cs.flowsV1 = flowsv1.NewForConfigOrDie(c) + cs.messagingV1beta1 = messagingv1beta1.NewForConfigOrDie(c) cs.messagingV1 = messagingv1.NewForConfigOrDie(c) + 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) @@ -157,8 +222,13 @@ func New(c rest.Interface) *Clientset { var cs Clientset cs.eventingV1beta1 = eventingv1beta1.New(c) cs.eventingV1 = eventingv1.New(c) + cs.flowsV1beta1 = flowsv1beta1.New(c) cs.flowsV1 = flowsv1.New(c) + cs.messagingV1beta1 = messagingv1beta1.New(c) cs.messagingV1 = messagingv1.New(c) + 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) diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go index 72ba5e0dc45..82bc05de5e7 100644 --- a/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -31,10 +31,20 @@ import ( fakeeventingv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake" flowsv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/flows/v1" fakeflowsv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/flows/v1/fake" + flowsv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/flows/v1beta1" + fakeflowsv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/flows/v1beta1/fake" messagingv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1" fakemessagingv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1/fake" + messagingv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1beta1" + fakemessagingv1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake" sourcesv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1" fakesourcesv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1/fake" + sourcesv1alpha1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha1" + fakesourcesv1alpha1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake" + sourcesv1alpha2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha2" + 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" ) @@ -96,16 +106,41 @@ func (c *Clientset) EventingV1() eventingv1.EventingV1Interface { return &fakeeventingv1.FakeEventingV1{Fake: &c.Fake} } +// FlowsV1beta1 retrieves the FlowsV1beta1Client +func (c *Clientset) FlowsV1beta1() flowsv1beta1.FlowsV1beta1Interface { + return &fakeflowsv1beta1.FakeFlowsV1beta1{Fake: &c.Fake} +} + // FlowsV1 retrieves the FlowsV1Client func (c *Clientset) FlowsV1() flowsv1.FlowsV1Interface { return &fakeflowsv1.FakeFlowsV1{Fake: &c.Fake} } +// MessagingV1beta1 retrieves the MessagingV1beta1Client +func (c *Clientset) MessagingV1beta1() messagingv1beta1.MessagingV1beta1Interface { + return &fakemessagingv1beta1.FakeMessagingV1beta1{Fake: &c.Fake} +} + // MessagingV1 retrieves the MessagingV1Client func (c *Clientset) MessagingV1() messagingv1.MessagingV1Interface { return &fakemessagingv1.FakeMessagingV1{Fake: &c.Fake} } +// SourcesV1alpha1 retrieves the SourcesV1alpha1Client +func (c *Clientset) SourcesV1alpha1() sourcesv1alpha1.SourcesV1alpha1Interface { + return &fakesourcesv1alpha1.FakeSourcesV1alpha1{Fake: &c.Fake} +} + +// SourcesV1alpha2 retrieves the SourcesV1alpha2Client +func (c *Clientset) SourcesV1alpha2() sourcesv1alpha2.SourcesV1alpha2Interface { + return &fakesourcesv1alpha2.FakeSourcesV1alpha2{Fake: &c.Fake} +} + +// SourcesV1beta1 retrieves the SourcesV1beta1Client +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} diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go index b8d42a16945..91981f28aef 100644 --- a/pkg/client/clientset/versioned/fake/register.go +++ b/pkg/client/clientset/versioned/fake/register.go @@ -27,8 +27,13 @@ import ( eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" flowsv1 "knative.dev/eventing/pkg/apis/flows/v1" + flowsv1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + 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" ) @@ -38,8 +43,13 @@ var codecs = serializer.NewCodecFactory(scheme) var localSchemeBuilder = runtime.SchemeBuilder{ eventingv1beta1.AddToScheme, eventingv1.AddToScheme, + flowsv1beta1.AddToScheme, flowsv1.AddToScheme, + messagingv1beta1.AddToScheme, messagingv1.AddToScheme, + 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 7cade492835..f08304b8a9d 100644 --- a/pkg/client/clientset/versioned/scheme/register.go +++ b/pkg/client/clientset/versioned/scheme/register.go @@ -27,8 +27,13 @@ import ( eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" flowsv1 "knative.dev/eventing/pkg/apis/flows/v1" + flowsv1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + 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" ) @@ -38,8 +43,13 @@ var ParameterCodec = runtime.NewParameterCodec(Scheme) var localSchemeBuilder = runtime.SchemeBuilder{ eventingv1beta1.AddToScheme, eventingv1.AddToScheme, + flowsv1beta1.AddToScheme, flowsv1.AddToScheme, + messagingv1beta1.AddToScheme, messagingv1.AddToScheme, + sourcesv1alpha1.AddToScheme, + sourcesv1alpha2.AddToScheme, + sourcesv1beta1.AddToScheme, sourcesv1beta2.AddToScheme, sourcesv1.AddToScheme, } diff --git a/pkg/client/clientset/versioned/typed/eventing/v1beta1/broker.go b/pkg/client/clientset/versioned/typed/eventing/v1beta1/broker.go new file mode 100644 index 00000000000..234ca97e07b --- /dev/null +++ b/pkg/client/clientset/versioned/typed/eventing/v1beta1/broker.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// BrokersGetter has a method to return a BrokerInterface. +// A group's client should implement this interface. +type BrokersGetter interface { + Brokers(namespace string) BrokerInterface +} + +// BrokerInterface has methods to work with Broker resources. +type BrokerInterface interface { + Create(ctx context.Context, broker *v1beta1.Broker, opts v1.CreateOptions) (*v1beta1.Broker, error) + Update(ctx context.Context, broker *v1beta1.Broker, opts v1.UpdateOptions) (*v1beta1.Broker, error) + UpdateStatus(ctx context.Context, broker *v1beta1.Broker, opts v1.UpdateOptions) (*v1beta1.Broker, 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) (*v1beta1.Broker, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta1.BrokerList, 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 *v1beta1.Broker, err error) + BrokerExpansion +} + +// brokers implements BrokerInterface +type brokers struct { + client rest.Interface + ns string +} + +// newBrokers returns a Brokers +func newBrokers(c *EventingV1beta1Client, namespace string) *brokers { + return &brokers{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the broker, and returns the corresponding broker object, and an error if there is any. +func (c *brokers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Broker, err error) { + result = &v1beta1.Broker{} + err = c.client.Get(). + Namespace(c.ns). + Resource("brokers"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Brokers that match those selectors. +func (c *brokers) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.BrokerList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta1.BrokerList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("brokers"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested brokers. +func (c *brokers) 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("brokers"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a broker and creates it. Returns the server's representation of the broker, and an error, if there is any. +func (c *brokers) Create(ctx context.Context, broker *v1beta1.Broker, opts v1.CreateOptions) (result *v1beta1.Broker, err error) { + result = &v1beta1.Broker{} + err = c.client.Post(). + Namespace(c.ns). + Resource("brokers"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(broker). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a broker and updates it. Returns the server's representation of the broker, and an error, if there is any. +func (c *brokers) Update(ctx context.Context, broker *v1beta1.Broker, opts v1.UpdateOptions) (result *v1beta1.Broker, err error) { + result = &v1beta1.Broker{} + err = c.client.Put(). + Namespace(c.ns). + Resource("brokers"). + Name(broker.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(broker). + 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 *brokers) UpdateStatus(ctx context.Context, broker *v1beta1.Broker, opts v1.UpdateOptions) (result *v1beta1.Broker, err error) { + result = &v1beta1.Broker{} + err = c.client.Put(). + Namespace(c.ns). + Resource("brokers"). + Name(broker.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(broker). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the broker and deletes it. Returns an error if one occurs. +func (c *brokers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("brokers"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *brokers) 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("brokers"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched broker. +func (c *brokers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Broker, err error) { + result = &v1beta1.Broker{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("brokers"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1beta1/eventing_client.go b/pkg/client/clientset/versioned/typed/eventing/v1beta1/eventing_client.go index 9787f6e434b..d9162a3e96a 100644 --- a/pkg/client/clientset/versioned/typed/eventing/v1beta1/eventing_client.go +++ b/pkg/client/clientset/versioned/typed/eventing/v1beta1/eventing_client.go @@ -26,7 +26,9 @@ import ( type EventingV1beta1Interface interface { RESTClient() rest.Interface + BrokersGetter EventTypesGetter + TriggersGetter } // EventingV1beta1Client is used to interact with features provided by the eventing.knative.dev group. @@ -34,10 +36,18 @@ type EventingV1beta1Client struct { restClient rest.Interface } +func (c *EventingV1beta1Client) Brokers(namespace string) BrokerInterface { + return newBrokers(c, namespace) +} + func (c *EventingV1beta1Client) EventTypes(namespace string) EventTypeInterface { return newEventTypes(c, namespace) } +func (c *EventingV1beta1Client) Triggers(namespace string) TriggerInterface { + return newTriggers(c, namespace) +} + // NewForConfig creates a new EventingV1beta1Client for the given config. func NewForConfig(c *rest.Config) (*EventingV1beta1Client, error) { config := *c diff --git a/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_broker.go b/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_broker.go new file mode 100644 index 00000000000..793d9603f06 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_broker.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" +) + +// FakeBrokers implements BrokerInterface +type FakeBrokers struct { + Fake *FakeEventingV1beta1 + ns string +} + +var brokersResource = schema.GroupVersionResource{Group: "eventing.knative.dev", Version: "v1beta1", Resource: "brokers"} + +var brokersKind = schema.GroupVersionKind{Group: "eventing.knative.dev", Version: "v1beta1", Kind: "Broker"} + +// Get takes name of the broker, and returns the corresponding broker object, and an error if there is any. +func (c *FakeBrokers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Broker, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(brokersResource, c.ns, name), &v1beta1.Broker{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Broker), err +} + +// List takes label and field selectors, and returns the list of Brokers that match those selectors. +func (c *FakeBrokers) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.BrokerList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(brokersResource, brokersKind, c.ns, opts), &v1beta1.BrokerList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.BrokerList{ListMeta: obj.(*v1beta1.BrokerList).ListMeta} + for _, item := range obj.(*v1beta1.BrokerList).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 brokers. +func (c *FakeBrokers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(brokersResource, c.ns, opts)) + +} + +// Create takes the representation of a broker and creates it. Returns the server's representation of the broker, and an error, if there is any. +func (c *FakeBrokers) Create(ctx context.Context, broker *v1beta1.Broker, opts v1.CreateOptions) (result *v1beta1.Broker, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(brokersResource, c.ns, broker), &v1beta1.Broker{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Broker), err +} + +// Update takes the representation of a broker and updates it. Returns the server's representation of the broker, and an error, if there is any. +func (c *FakeBrokers) Update(ctx context.Context, broker *v1beta1.Broker, opts v1.UpdateOptions) (result *v1beta1.Broker, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(brokersResource, c.ns, broker), &v1beta1.Broker{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Broker), 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 *FakeBrokers) UpdateStatus(ctx context.Context, broker *v1beta1.Broker, opts v1.UpdateOptions) (*v1beta1.Broker, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(brokersResource, "status", c.ns, broker), &v1beta1.Broker{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Broker), err +} + +// Delete takes name of the broker and deletes it. Returns an error if one occurs. +func (c *FakeBrokers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(brokersResource, c.ns, name), &v1beta1.Broker{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeBrokers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(brokersResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.BrokerList{}) + return err +} + +// Patch applies the patch and returns the patched broker. +func (c *FakeBrokers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Broker, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(brokersResource, c.ns, name, pt, data, subresources...), &v1beta1.Broker{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Broker), err +} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_eventing_client.go b/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_eventing_client.go index 0a6b788fcc6..5a399890518 100644 --- a/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_eventing_client.go +++ b/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_eventing_client.go @@ -28,10 +28,18 @@ type FakeEventingV1beta1 struct { *testing.Fake } +func (c *FakeEventingV1beta1) Brokers(namespace string) v1beta1.BrokerInterface { + return &FakeBrokers{c, namespace} +} + func (c *FakeEventingV1beta1) EventTypes(namespace string) v1beta1.EventTypeInterface { return &FakeEventTypes{c, namespace} } +func (c *FakeEventingV1beta1) Triggers(namespace string) v1beta1.TriggerInterface { + return &FakeTriggers{c, namespace} +} + // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. func (c *FakeEventingV1beta1) RESTClient() rest.Interface { diff --git a/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_trigger.go b/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_trigger.go new file mode 100644 index 00000000000..b09ee4c6a8f --- /dev/null +++ b/pkg/client/clientset/versioned/typed/eventing/v1beta1/fake/fake_trigger.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" +) + +// FakeTriggers implements TriggerInterface +type FakeTriggers struct { + Fake *FakeEventingV1beta1 + ns string +} + +var triggersResource = schema.GroupVersionResource{Group: "eventing.knative.dev", Version: "v1beta1", Resource: "triggers"} + +var triggersKind = schema.GroupVersionKind{Group: "eventing.knative.dev", Version: "v1beta1", Kind: "Trigger"} + +// Get takes name of the trigger, and returns the corresponding trigger object, and an error if there is any. +func (c *FakeTriggers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Trigger, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(triggersResource, c.ns, name), &v1beta1.Trigger{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Trigger), err +} + +// List takes label and field selectors, and returns the list of Triggers that match those selectors. +func (c *FakeTriggers) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.TriggerList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(triggersResource, triggersKind, c.ns, opts), &v1beta1.TriggerList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.TriggerList{ListMeta: obj.(*v1beta1.TriggerList).ListMeta} + for _, item := range obj.(*v1beta1.TriggerList).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 triggers. +func (c *FakeTriggers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(triggersResource, c.ns, opts)) + +} + +// Create takes the representation of a trigger and creates it. Returns the server's representation of the trigger, and an error, if there is any. +func (c *FakeTriggers) Create(ctx context.Context, trigger *v1beta1.Trigger, opts v1.CreateOptions) (result *v1beta1.Trigger, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(triggersResource, c.ns, trigger), &v1beta1.Trigger{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Trigger), err +} + +// Update takes the representation of a trigger and updates it. Returns the server's representation of the trigger, and an error, if there is any. +func (c *FakeTriggers) Update(ctx context.Context, trigger *v1beta1.Trigger, opts v1.UpdateOptions) (result *v1beta1.Trigger, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(triggersResource, c.ns, trigger), &v1beta1.Trigger{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Trigger), 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 *FakeTriggers) UpdateStatus(ctx context.Context, trigger *v1beta1.Trigger, opts v1.UpdateOptions) (*v1beta1.Trigger, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(triggersResource, "status", c.ns, trigger), &v1beta1.Trigger{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Trigger), err +} + +// Delete takes name of the trigger and deletes it. Returns an error if one occurs. +func (c *FakeTriggers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(triggersResource, c.ns, name), &v1beta1.Trigger{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeTriggers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(triggersResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.TriggerList{}) + return err +} + +// Patch applies the patch and returns the patched trigger. +func (c *FakeTriggers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Trigger, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(triggersResource, c.ns, name, pt, data, subresources...), &v1beta1.Trigger{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Trigger), err +} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1beta1/generated_expansion.go b/pkg/client/clientset/versioned/typed/eventing/v1beta1/generated_expansion.go index 245a728f63b..3bf86fe619f 100644 --- a/pkg/client/clientset/versioned/typed/eventing/v1beta1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/eventing/v1beta1/generated_expansion.go @@ -18,4 +18,8 @@ limitations under the License. package v1beta1 +type BrokerExpansion interface{} + type EventTypeExpansion interface{} + +type TriggerExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/eventing/v1beta1/trigger.go b/pkg/client/clientset/versioned/typed/eventing/v1beta1/trigger.go new file mode 100644 index 00000000000..56594745298 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/eventing/v1beta1/trigger.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// TriggersGetter has a method to return a TriggerInterface. +// A group's client should implement this interface. +type TriggersGetter interface { + Triggers(namespace string) TriggerInterface +} + +// TriggerInterface has methods to work with Trigger resources. +type TriggerInterface interface { + Create(ctx context.Context, trigger *v1beta1.Trigger, opts v1.CreateOptions) (*v1beta1.Trigger, error) + Update(ctx context.Context, trigger *v1beta1.Trigger, opts v1.UpdateOptions) (*v1beta1.Trigger, error) + UpdateStatus(ctx context.Context, trigger *v1beta1.Trigger, opts v1.UpdateOptions) (*v1beta1.Trigger, 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) (*v1beta1.Trigger, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta1.TriggerList, 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 *v1beta1.Trigger, err error) + TriggerExpansion +} + +// triggers implements TriggerInterface +type triggers struct { + client rest.Interface + ns string +} + +// newTriggers returns a Triggers +func newTriggers(c *EventingV1beta1Client, namespace string) *triggers { + return &triggers{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the trigger, and returns the corresponding trigger object, and an error if there is any. +func (c *triggers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Trigger, err error) { + result = &v1beta1.Trigger{} + err = c.client.Get(). + Namespace(c.ns). + Resource("triggers"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Triggers that match those selectors. +func (c *triggers) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.TriggerList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta1.TriggerList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("triggers"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested triggers. +func (c *triggers) 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("triggers"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a trigger and creates it. Returns the server's representation of the trigger, and an error, if there is any. +func (c *triggers) Create(ctx context.Context, trigger *v1beta1.Trigger, opts v1.CreateOptions) (result *v1beta1.Trigger, err error) { + result = &v1beta1.Trigger{} + err = c.client.Post(). + Namespace(c.ns). + Resource("triggers"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(trigger). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a trigger and updates it. Returns the server's representation of the trigger, and an error, if there is any. +func (c *triggers) Update(ctx context.Context, trigger *v1beta1.Trigger, opts v1.UpdateOptions) (result *v1beta1.Trigger, err error) { + result = &v1beta1.Trigger{} + err = c.client.Put(). + Namespace(c.ns). + Resource("triggers"). + Name(trigger.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(trigger). + 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 *triggers) UpdateStatus(ctx context.Context, trigger *v1beta1.Trigger, opts v1.UpdateOptions) (result *v1beta1.Trigger, err error) { + result = &v1beta1.Trigger{} + err = c.client.Put(). + Namespace(c.ns). + Resource("triggers"). + Name(trigger.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(trigger). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the trigger and deletes it. Returns an error if one occurs. +func (c *triggers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("triggers"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *triggers) 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("triggers"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched trigger. +func (c *triggers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Trigger, err error) { + result = &v1beta1.Trigger{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("triggers"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/flows/v1beta1/doc.go b/pkg/client/clientset/versioned/typed/flows/v1beta1/doc.go new file mode 100644 index 00000000000..8d5037f0771 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/flows/v1beta1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2021 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 v1beta1 diff --git a/pkg/client/clientset/versioned/typed/flows/v1beta1/fake/doc.go b/pkg/client/clientset/versioned/typed/flows/v1beta1/fake/doc.go new file mode 100644 index 00000000000..d5a46ef5532 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/flows/v1beta1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2021 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/flows/v1beta1/fake/fake_flows_client.go b/pkg/client/clientset/versioned/typed/flows/v1beta1/fake/fake_flows_client.go new file mode 100644 index 00000000000..a7ace6f9a2e --- /dev/null +++ b/pkg/client/clientset/versioned/typed/flows/v1beta1/fake/fake_flows_client.go @@ -0,0 +1,44 @@ +/* +Copyright 2021 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" + v1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/flows/v1beta1" +) + +type FakeFlowsV1beta1 struct { + *testing.Fake +} + +func (c *FakeFlowsV1beta1) Parallels(namespace string) v1beta1.ParallelInterface { + return &FakeParallels{c, namespace} +} + +func (c *FakeFlowsV1beta1) Sequences(namespace string) v1beta1.SequenceInterface { + return &FakeSequences{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeFlowsV1beta1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/clientset/versioned/typed/flows/v1beta1/fake/fake_parallel.go b/pkg/client/clientset/versioned/typed/flows/v1beta1/fake/fake_parallel.go new file mode 100644 index 00000000000..3f647954242 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/flows/v1beta1/fake/fake_parallel.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" +) + +// FakeParallels implements ParallelInterface +type FakeParallels struct { + Fake *FakeFlowsV1beta1 + ns string +} + +var parallelsResource = schema.GroupVersionResource{Group: "flows.knative.dev", Version: "v1beta1", Resource: "parallels"} + +var parallelsKind = schema.GroupVersionKind{Group: "flows.knative.dev", Version: "v1beta1", Kind: "Parallel"} + +// Get takes name of the parallel, and returns the corresponding parallel object, and an error if there is any. +func (c *FakeParallels) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Parallel, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(parallelsResource, c.ns, name), &v1beta1.Parallel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Parallel), err +} + +// List takes label and field selectors, and returns the list of Parallels that match those selectors. +func (c *FakeParallels) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ParallelList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(parallelsResource, parallelsKind, c.ns, opts), &v1beta1.ParallelList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.ParallelList{ListMeta: obj.(*v1beta1.ParallelList).ListMeta} + for _, item := range obj.(*v1beta1.ParallelList).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 parallels. +func (c *FakeParallels) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(parallelsResource, c.ns, opts)) + +} + +// Create takes the representation of a parallel and creates it. Returns the server's representation of the parallel, and an error, if there is any. +func (c *FakeParallels) Create(ctx context.Context, parallel *v1beta1.Parallel, opts v1.CreateOptions) (result *v1beta1.Parallel, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(parallelsResource, c.ns, parallel), &v1beta1.Parallel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Parallel), err +} + +// Update takes the representation of a parallel and updates it. Returns the server's representation of the parallel, and an error, if there is any. +func (c *FakeParallels) Update(ctx context.Context, parallel *v1beta1.Parallel, opts v1.UpdateOptions) (result *v1beta1.Parallel, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(parallelsResource, c.ns, parallel), &v1beta1.Parallel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Parallel), 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 *FakeParallels) UpdateStatus(ctx context.Context, parallel *v1beta1.Parallel, opts v1.UpdateOptions) (*v1beta1.Parallel, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(parallelsResource, "status", c.ns, parallel), &v1beta1.Parallel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Parallel), err +} + +// Delete takes name of the parallel and deletes it. Returns an error if one occurs. +func (c *FakeParallels) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(parallelsResource, c.ns, name), &v1beta1.Parallel{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeParallels) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(parallelsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.ParallelList{}) + return err +} + +// Patch applies the patch and returns the patched parallel. +func (c *FakeParallels) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Parallel, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(parallelsResource, c.ns, name, pt, data, subresources...), &v1beta1.Parallel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Parallel), err +} diff --git a/pkg/client/clientset/versioned/typed/flows/v1beta1/fake/fake_sequence.go b/pkg/client/clientset/versioned/typed/flows/v1beta1/fake/fake_sequence.go new file mode 100644 index 00000000000..152fcb0cd05 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/flows/v1beta1/fake/fake_sequence.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" +) + +// FakeSequences implements SequenceInterface +type FakeSequences struct { + Fake *FakeFlowsV1beta1 + ns string +} + +var sequencesResource = schema.GroupVersionResource{Group: "flows.knative.dev", Version: "v1beta1", Resource: "sequences"} + +var sequencesKind = schema.GroupVersionKind{Group: "flows.knative.dev", Version: "v1beta1", Kind: "Sequence"} + +// Get takes name of the sequence, and returns the corresponding sequence object, and an error if there is any. +func (c *FakeSequences) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Sequence, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(sequencesResource, c.ns, name), &v1beta1.Sequence{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Sequence), err +} + +// List takes label and field selectors, and returns the list of Sequences that match those selectors. +func (c *FakeSequences) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.SequenceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(sequencesResource, sequencesKind, c.ns, opts), &v1beta1.SequenceList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.SequenceList{ListMeta: obj.(*v1beta1.SequenceList).ListMeta} + for _, item := range obj.(*v1beta1.SequenceList).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 sequences. +func (c *FakeSequences) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(sequencesResource, c.ns, opts)) + +} + +// Create takes the representation of a sequence and creates it. Returns the server's representation of the sequence, and an error, if there is any. +func (c *FakeSequences) Create(ctx context.Context, sequence *v1beta1.Sequence, opts v1.CreateOptions) (result *v1beta1.Sequence, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(sequencesResource, c.ns, sequence), &v1beta1.Sequence{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Sequence), err +} + +// Update takes the representation of a sequence and updates it. Returns the server's representation of the sequence, and an error, if there is any. +func (c *FakeSequences) Update(ctx context.Context, sequence *v1beta1.Sequence, opts v1.UpdateOptions) (result *v1beta1.Sequence, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(sequencesResource, c.ns, sequence), &v1beta1.Sequence{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Sequence), 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 *FakeSequences) UpdateStatus(ctx context.Context, sequence *v1beta1.Sequence, opts v1.UpdateOptions) (*v1beta1.Sequence, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(sequencesResource, "status", c.ns, sequence), &v1beta1.Sequence{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Sequence), err +} + +// Delete takes name of the sequence and deletes it. Returns an error if one occurs. +func (c *FakeSequences) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(sequencesResource, c.ns, name), &v1beta1.Sequence{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeSequences) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(sequencesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.SequenceList{}) + return err +} + +// Patch applies the patch and returns the patched sequence. +func (c *FakeSequences) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Sequence, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(sequencesResource, c.ns, name, pt, data, subresources...), &v1beta1.Sequence{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Sequence), err +} diff --git a/pkg/client/clientset/versioned/typed/flows/v1beta1/flows_client.go b/pkg/client/clientset/versioned/typed/flows/v1beta1/flows_client.go new file mode 100644 index 00000000000..414b139f14c --- /dev/null +++ b/pkg/client/clientset/versioned/typed/flows/v1beta1/flows_client.go @@ -0,0 +1,94 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + rest "k8s.io/client-go/rest" + v1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" + "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +type FlowsV1beta1Interface interface { + RESTClient() rest.Interface + ParallelsGetter + SequencesGetter +} + +// FlowsV1beta1Client is used to interact with features provided by the flows.knative.dev group. +type FlowsV1beta1Client struct { + restClient rest.Interface +} + +func (c *FlowsV1beta1Client) Parallels(namespace string) ParallelInterface { + return newParallels(c, namespace) +} + +func (c *FlowsV1beta1Client) Sequences(namespace string) SequenceInterface { + return newSequences(c, namespace) +} + +// NewForConfig creates a new FlowsV1beta1Client for the given config. +func NewForConfig(c *rest.Config) (*FlowsV1beta1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &FlowsV1beta1Client{client}, nil +} + +// NewForConfigOrDie creates a new FlowsV1beta1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *FlowsV1beta1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new FlowsV1beta1Client for the given RESTClient. +func New(c rest.Interface) *FlowsV1beta1Client { + return &FlowsV1beta1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1beta1.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 *FlowsV1beta1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/clientset/versioned/typed/flows/v1beta1/generated_expansion.go b/pkg/client/clientset/versioned/typed/flows/v1beta1/generated_expansion.go new file mode 100644 index 00000000000..df2db614a88 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/flows/v1beta1/generated_expansion.go @@ -0,0 +1,23 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +type ParallelExpansion interface{} + +type SequenceExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/flows/v1beta1/parallel.go b/pkg/client/clientset/versioned/typed/flows/v1beta1/parallel.go new file mode 100644 index 00000000000..a2ea96c967b --- /dev/null +++ b/pkg/client/clientset/versioned/typed/flows/v1beta1/parallel.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + v1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// ParallelsGetter has a method to return a ParallelInterface. +// A group's client should implement this interface. +type ParallelsGetter interface { + Parallels(namespace string) ParallelInterface +} + +// ParallelInterface has methods to work with Parallel resources. +type ParallelInterface interface { + Create(ctx context.Context, parallel *v1beta1.Parallel, opts v1.CreateOptions) (*v1beta1.Parallel, error) + Update(ctx context.Context, parallel *v1beta1.Parallel, opts v1.UpdateOptions) (*v1beta1.Parallel, error) + UpdateStatus(ctx context.Context, parallel *v1beta1.Parallel, opts v1.UpdateOptions) (*v1beta1.Parallel, 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) (*v1beta1.Parallel, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta1.ParallelList, 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 *v1beta1.Parallel, err error) + ParallelExpansion +} + +// parallels implements ParallelInterface +type parallels struct { + client rest.Interface + ns string +} + +// newParallels returns a Parallels +func newParallels(c *FlowsV1beta1Client, namespace string) *parallels { + return ¶llels{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the parallel, and returns the corresponding parallel object, and an error if there is any. +func (c *parallels) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Parallel, err error) { + result = &v1beta1.Parallel{} + err = c.client.Get(). + Namespace(c.ns). + Resource("parallels"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Parallels that match those selectors. +func (c *parallels) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ParallelList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta1.ParallelList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("parallels"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested parallels. +func (c *parallels) 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("parallels"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a parallel and creates it. Returns the server's representation of the parallel, and an error, if there is any. +func (c *parallels) Create(ctx context.Context, parallel *v1beta1.Parallel, opts v1.CreateOptions) (result *v1beta1.Parallel, err error) { + result = &v1beta1.Parallel{} + err = c.client.Post(). + Namespace(c.ns). + Resource("parallels"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(parallel). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a parallel and updates it. Returns the server's representation of the parallel, and an error, if there is any. +func (c *parallels) Update(ctx context.Context, parallel *v1beta1.Parallel, opts v1.UpdateOptions) (result *v1beta1.Parallel, err error) { + result = &v1beta1.Parallel{} + err = c.client.Put(). + Namespace(c.ns). + Resource("parallels"). + Name(parallel.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(parallel). + 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 *parallels) UpdateStatus(ctx context.Context, parallel *v1beta1.Parallel, opts v1.UpdateOptions) (result *v1beta1.Parallel, err error) { + result = &v1beta1.Parallel{} + err = c.client.Put(). + Namespace(c.ns). + Resource("parallels"). + Name(parallel.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(parallel). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the parallel and deletes it. Returns an error if one occurs. +func (c *parallels) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("parallels"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *parallels) 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("parallels"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched parallel. +func (c *parallels) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Parallel, err error) { + result = &v1beta1.Parallel{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("parallels"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/flows/v1beta1/sequence.go b/pkg/client/clientset/versioned/typed/flows/v1beta1/sequence.go new file mode 100644 index 00000000000..4b4c5d6fe7d --- /dev/null +++ b/pkg/client/clientset/versioned/typed/flows/v1beta1/sequence.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + v1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// SequencesGetter has a method to return a SequenceInterface. +// A group's client should implement this interface. +type SequencesGetter interface { + Sequences(namespace string) SequenceInterface +} + +// SequenceInterface has methods to work with Sequence resources. +type SequenceInterface interface { + Create(ctx context.Context, sequence *v1beta1.Sequence, opts v1.CreateOptions) (*v1beta1.Sequence, error) + Update(ctx context.Context, sequence *v1beta1.Sequence, opts v1.UpdateOptions) (*v1beta1.Sequence, error) + UpdateStatus(ctx context.Context, sequence *v1beta1.Sequence, opts v1.UpdateOptions) (*v1beta1.Sequence, 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) (*v1beta1.Sequence, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta1.SequenceList, 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 *v1beta1.Sequence, err error) + SequenceExpansion +} + +// sequences implements SequenceInterface +type sequences struct { + client rest.Interface + ns string +} + +// newSequences returns a Sequences +func newSequences(c *FlowsV1beta1Client, namespace string) *sequences { + return &sequences{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the sequence, and returns the corresponding sequence object, and an error if there is any. +func (c *sequences) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Sequence, err error) { + result = &v1beta1.Sequence{} + err = c.client.Get(). + Namespace(c.ns). + Resource("sequences"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Sequences that match those selectors. +func (c *sequences) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.SequenceList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta1.SequenceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("sequences"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested sequences. +func (c *sequences) 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("sequences"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a sequence and creates it. Returns the server's representation of the sequence, and an error, if there is any. +func (c *sequences) Create(ctx context.Context, sequence *v1beta1.Sequence, opts v1.CreateOptions) (result *v1beta1.Sequence, err error) { + result = &v1beta1.Sequence{} + err = c.client.Post(). + Namespace(c.ns). + Resource("sequences"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(sequence). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a sequence and updates it. Returns the server's representation of the sequence, and an error, if there is any. +func (c *sequences) Update(ctx context.Context, sequence *v1beta1.Sequence, opts v1.UpdateOptions) (result *v1beta1.Sequence, err error) { + result = &v1beta1.Sequence{} + err = c.client.Put(). + Namespace(c.ns). + Resource("sequences"). + Name(sequence.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(sequence). + 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 *sequences) UpdateStatus(ctx context.Context, sequence *v1beta1.Sequence, opts v1.UpdateOptions) (result *v1beta1.Sequence, err error) { + result = &v1beta1.Sequence{} + err = c.client.Put(). + Namespace(c.ns). + Resource("sequences"). + Name(sequence.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(sequence). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the sequence and deletes it. Returns an error if one occurs. +func (c *sequences) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("sequences"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *sequences) 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("sequences"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched sequence. +func (c *sequences) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Sequence, err error) { + result = &v1beta1.Sequence{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("sequences"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/messaging/v1beta1/channel.go b/pkg/client/clientset/versioned/typed/messaging/v1beta1/channel.go new file mode 100644 index 00000000000..90af647f2b0 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/messaging/v1beta1/channel.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// ChannelsGetter has a method to return a ChannelInterface. +// A group's client should implement this interface. +type ChannelsGetter interface { + Channels(namespace string) ChannelInterface +} + +// ChannelInterface has methods to work with Channel resources. +type ChannelInterface interface { + Create(ctx context.Context, channel *v1beta1.Channel, opts v1.CreateOptions) (*v1beta1.Channel, error) + Update(ctx context.Context, channel *v1beta1.Channel, opts v1.UpdateOptions) (*v1beta1.Channel, error) + UpdateStatus(ctx context.Context, channel *v1beta1.Channel, opts v1.UpdateOptions) (*v1beta1.Channel, 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) (*v1beta1.Channel, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta1.ChannelList, 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 *v1beta1.Channel, err error) + ChannelExpansion +} + +// channels implements ChannelInterface +type channels struct { + client rest.Interface + ns string +} + +// newChannels returns a Channels +func newChannels(c *MessagingV1beta1Client, namespace string) *channels { + return &channels{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the channel, and returns the corresponding channel object, and an error if there is any. +func (c *channels) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Channel, err error) { + result = &v1beta1.Channel{} + err = c.client.Get(). + Namespace(c.ns). + Resource("channels"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Channels that match those selectors. +func (c *channels) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ChannelList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta1.ChannelList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("channels"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested channels. +func (c *channels) 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("channels"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a channel and creates it. Returns the server's representation of the channel, and an error, if there is any. +func (c *channels) Create(ctx context.Context, channel *v1beta1.Channel, opts v1.CreateOptions) (result *v1beta1.Channel, err error) { + result = &v1beta1.Channel{} + err = c.client.Post(). + Namespace(c.ns). + Resource("channels"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(channel). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a channel and updates it. Returns the server's representation of the channel, and an error, if there is any. +func (c *channels) Update(ctx context.Context, channel *v1beta1.Channel, opts v1.UpdateOptions) (result *v1beta1.Channel, err error) { + result = &v1beta1.Channel{} + err = c.client.Put(). + Namespace(c.ns). + Resource("channels"). + Name(channel.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(channel). + 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 *channels) UpdateStatus(ctx context.Context, channel *v1beta1.Channel, opts v1.UpdateOptions) (result *v1beta1.Channel, err error) { + result = &v1beta1.Channel{} + err = c.client.Put(). + Namespace(c.ns). + Resource("channels"). + Name(channel.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(channel). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the channel and deletes it. Returns an error if one occurs. +func (c *channels) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("channels"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *channels) 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("channels"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched channel. +func (c *channels) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Channel, err error) { + result = &v1beta1.Channel{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("channels"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/messaging/v1beta1/doc.go b/pkg/client/clientset/versioned/typed/messaging/v1beta1/doc.go new file mode 100644 index 00000000000..8d5037f0771 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/messaging/v1beta1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2021 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 v1beta1 diff --git a/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake/doc.go b/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake/doc.go new file mode 100644 index 00000000000..d5a46ef5532 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2021 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/messaging/v1beta1/fake/fake_channel.go b/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake/fake_channel.go new file mode 100644 index 00000000000..f8bac2a9b04 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake/fake_channel.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" +) + +// FakeChannels implements ChannelInterface +type FakeChannels struct { + Fake *FakeMessagingV1beta1 + ns string +} + +var channelsResource = schema.GroupVersionResource{Group: "messaging.knative.dev", Version: "v1beta1", Resource: "channels"} + +var channelsKind = schema.GroupVersionKind{Group: "messaging.knative.dev", Version: "v1beta1", Kind: "Channel"} + +// Get takes name of the channel, and returns the corresponding channel object, and an error if there is any. +func (c *FakeChannels) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Channel, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(channelsResource, c.ns, name), &v1beta1.Channel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Channel), err +} + +// List takes label and field selectors, and returns the list of Channels that match those selectors. +func (c *FakeChannels) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ChannelList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(channelsResource, channelsKind, c.ns, opts), &v1beta1.ChannelList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.ChannelList{ListMeta: obj.(*v1beta1.ChannelList).ListMeta} + for _, item := range obj.(*v1beta1.ChannelList).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 channels. +func (c *FakeChannels) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(channelsResource, c.ns, opts)) + +} + +// Create takes the representation of a channel and creates it. Returns the server's representation of the channel, and an error, if there is any. +func (c *FakeChannels) Create(ctx context.Context, channel *v1beta1.Channel, opts v1.CreateOptions) (result *v1beta1.Channel, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(channelsResource, c.ns, channel), &v1beta1.Channel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Channel), err +} + +// Update takes the representation of a channel and updates it. Returns the server's representation of the channel, and an error, if there is any. +func (c *FakeChannels) Update(ctx context.Context, channel *v1beta1.Channel, opts v1.UpdateOptions) (result *v1beta1.Channel, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(channelsResource, c.ns, channel), &v1beta1.Channel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Channel), 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 *FakeChannels) UpdateStatus(ctx context.Context, channel *v1beta1.Channel, opts v1.UpdateOptions) (*v1beta1.Channel, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(channelsResource, "status", c.ns, channel), &v1beta1.Channel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Channel), err +} + +// Delete takes name of the channel and deletes it. Returns an error if one occurs. +func (c *FakeChannels) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(channelsResource, c.ns, name), &v1beta1.Channel{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeChannels) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(channelsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.ChannelList{}) + return err +} + +// Patch applies the patch and returns the patched channel. +func (c *FakeChannels) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Channel, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(channelsResource, c.ns, name, pt, data, subresources...), &v1beta1.Channel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Channel), err +} diff --git a/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake/fake_inmemorychannel.go b/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake/fake_inmemorychannel.go new file mode 100644 index 00000000000..05a3a3e9a55 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake/fake_inmemorychannel.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" +) + +// FakeInMemoryChannels implements InMemoryChannelInterface +type FakeInMemoryChannels struct { + Fake *FakeMessagingV1beta1 + ns string +} + +var inmemorychannelsResource = schema.GroupVersionResource{Group: "messaging.knative.dev", Version: "v1beta1", Resource: "inmemorychannels"} + +var inmemorychannelsKind = schema.GroupVersionKind{Group: "messaging.knative.dev", Version: "v1beta1", Kind: "InMemoryChannel"} + +// Get takes name of the inMemoryChannel, and returns the corresponding inMemoryChannel object, and an error if there is any. +func (c *FakeInMemoryChannels) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.InMemoryChannel, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(inmemorychannelsResource, c.ns, name), &v1beta1.InMemoryChannel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.InMemoryChannel), err +} + +// List takes label and field selectors, and returns the list of InMemoryChannels that match those selectors. +func (c *FakeInMemoryChannels) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.InMemoryChannelList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(inmemorychannelsResource, inmemorychannelsKind, c.ns, opts), &v1beta1.InMemoryChannelList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.InMemoryChannelList{ListMeta: obj.(*v1beta1.InMemoryChannelList).ListMeta} + for _, item := range obj.(*v1beta1.InMemoryChannelList).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 inMemoryChannels. +func (c *FakeInMemoryChannels) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(inmemorychannelsResource, c.ns, opts)) + +} + +// Create takes the representation of a inMemoryChannel and creates it. Returns the server's representation of the inMemoryChannel, and an error, if there is any. +func (c *FakeInMemoryChannels) Create(ctx context.Context, inMemoryChannel *v1beta1.InMemoryChannel, opts v1.CreateOptions) (result *v1beta1.InMemoryChannel, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(inmemorychannelsResource, c.ns, inMemoryChannel), &v1beta1.InMemoryChannel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.InMemoryChannel), err +} + +// Update takes the representation of a inMemoryChannel and updates it. Returns the server's representation of the inMemoryChannel, and an error, if there is any. +func (c *FakeInMemoryChannels) Update(ctx context.Context, inMemoryChannel *v1beta1.InMemoryChannel, opts v1.UpdateOptions) (result *v1beta1.InMemoryChannel, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(inmemorychannelsResource, c.ns, inMemoryChannel), &v1beta1.InMemoryChannel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.InMemoryChannel), 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 *FakeInMemoryChannels) UpdateStatus(ctx context.Context, inMemoryChannel *v1beta1.InMemoryChannel, opts v1.UpdateOptions) (*v1beta1.InMemoryChannel, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(inmemorychannelsResource, "status", c.ns, inMemoryChannel), &v1beta1.InMemoryChannel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.InMemoryChannel), err +} + +// Delete takes name of the inMemoryChannel and deletes it. Returns an error if one occurs. +func (c *FakeInMemoryChannels) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(inmemorychannelsResource, c.ns, name), &v1beta1.InMemoryChannel{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeInMemoryChannels) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(inmemorychannelsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.InMemoryChannelList{}) + return err +} + +// Patch applies the patch and returns the patched inMemoryChannel. +func (c *FakeInMemoryChannels) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.InMemoryChannel, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(inmemorychannelsResource, c.ns, name, pt, data, subresources...), &v1beta1.InMemoryChannel{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.InMemoryChannel), err +} diff --git a/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake/fake_messaging_client.go b/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake/fake_messaging_client.go new file mode 100644 index 00000000000..9fc21f7f91c --- /dev/null +++ b/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake/fake_messaging_client.go @@ -0,0 +1,48 @@ +/* +Copyright 2021 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" + v1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1beta1" +) + +type FakeMessagingV1beta1 struct { + *testing.Fake +} + +func (c *FakeMessagingV1beta1) Channels(namespace string) v1beta1.ChannelInterface { + return &FakeChannels{c, namespace} +} + +func (c *FakeMessagingV1beta1) InMemoryChannels(namespace string) v1beta1.InMemoryChannelInterface { + return &FakeInMemoryChannels{c, namespace} +} + +func (c *FakeMessagingV1beta1) Subscriptions(namespace string) v1beta1.SubscriptionInterface { + return &FakeSubscriptions{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeMessagingV1beta1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake/fake_subscription.go b/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake/fake_subscription.go new file mode 100644 index 00000000000..979feccafd7 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/messaging/v1beta1/fake/fake_subscription.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" +) + +// FakeSubscriptions implements SubscriptionInterface +type FakeSubscriptions struct { + Fake *FakeMessagingV1beta1 + ns string +} + +var subscriptionsResource = schema.GroupVersionResource{Group: "messaging.knative.dev", Version: "v1beta1", Resource: "subscriptions"} + +var subscriptionsKind = schema.GroupVersionKind{Group: "messaging.knative.dev", Version: "v1beta1", Kind: "Subscription"} + +// Get takes name of the subscription, and returns the corresponding subscription object, and an error if there is any. +func (c *FakeSubscriptions) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Subscription, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(subscriptionsResource, c.ns, name), &v1beta1.Subscription{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Subscription), err +} + +// List takes label and field selectors, and returns the list of Subscriptions that match those selectors. +func (c *FakeSubscriptions) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.SubscriptionList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(subscriptionsResource, subscriptionsKind, c.ns, opts), &v1beta1.SubscriptionList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.SubscriptionList{ListMeta: obj.(*v1beta1.SubscriptionList).ListMeta} + for _, item := range obj.(*v1beta1.SubscriptionList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested subscriptions. +func (c *FakeSubscriptions) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(subscriptionsResource, c.ns, opts)) + +} + +// Create takes the representation of a subscription and creates it. Returns the server's representation of the subscription, and an error, if there is any. +func (c *FakeSubscriptions) Create(ctx context.Context, subscription *v1beta1.Subscription, opts v1.CreateOptions) (result *v1beta1.Subscription, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(subscriptionsResource, c.ns, subscription), &v1beta1.Subscription{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Subscription), err +} + +// Update takes the representation of a subscription and updates it. Returns the server's representation of the subscription, and an error, if there is any. +func (c *FakeSubscriptions) Update(ctx context.Context, subscription *v1beta1.Subscription, opts v1.UpdateOptions) (result *v1beta1.Subscription, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(subscriptionsResource, c.ns, subscription), &v1beta1.Subscription{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Subscription), 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 *FakeSubscriptions) UpdateStatus(ctx context.Context, subscription *v1beta1.Subscription, opts v1.UpdateOptions) (*v1beta1.Subscription, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(subscriptionsResource, "status", c.ns, subscription), &v1beta1.Subscription{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Subscription), err +} + +// Delete takes name of the subscription and deletes it. Returns an error if one occurs. +func (c *FakeSubscriptions) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(subscriptionsResource, c.ns, name), &v1beta1.Subscription{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeSubscriptions) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(subscriptionsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.SubscriptionList{}) + return err +} + +// Patch applies the patch and returns the patched subscription. +func (c *FakeSubscriptions) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Subscription, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(subscriptionsResource, c.ns, name, pt, data, subresources...), &v1beta1.Subscription{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Subscription), err +} diff --git a/pkg/client/clientset/versioned/typed/messaging/v1beta1/generated_expansion.go b/pkg/client/clientset/versioned/typed/messaging/v1beta1/generated_expansion.go new file mode 100644 index 00000000000..7b4c25cc35c --- /dev/null +++ b/pkg/client/clientset/versioned/typed/messaging/v1beta1/generated_expansion.go @@ -0,0 +1,25 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +type ChannelExpansion interface{} + +type InMemoryChannelExpansion interface{} + +type SubscriptionExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/messaging/v1beta1/inmemorychannel.go b/pkg/client/clientset/versioned/typed/messaging/v1beta1/inmemorychannel.go new file mode 100644 index 00000000000..3811b789c49 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/messaging/v1beta1/inmemorychannel.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// InMemoryChannelsGetter has a method to return a InMemoryChannelInterface. +// A group's client should implement this interface. +type InMemoryChannelsGetter interface { + InMemoryChannels(namespace string) InMemoryChannelInterface +} + +// InMemoryChannelInterface has methods to work with InMemoryChannel resources. +type InMemoryChannelInterface interface { + Create(ctx context.Context, inMemoryChannel *v1beta1.InMemoryChannel, opts v1.CreateOptions) (*v1beta1.InMemoryChannel, error) + Update(ctx context.Context, inMemoryChannel *v1beta1.InMemoryChannel, opts v1.UpdateOptions) (*v1beta1.InMemoryChannel, error) + UpdateStatus(ctx context.Context, inMemoryChannel *v1beta1.InMemoryChannel, opts v1.UpdateOptions) (*v1beta1.InMemoryChannel, 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) (*v1beta1.InMemoryChannel, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta1.InMemoryChannelList, 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 *v1beta1.InMemoryChannel, err error) + InMemoryChannelExpansion +} + +// inMemoryChannels implements InMemoryChannelInterface +type inMemoryChannels struct { + client rest.Interface + ns string +} + +// newInMemoryChannels returns a InMemoryChannels +func newInMemoryChannels(c *MessagingV1beta1Client, namespace string) *inMemoryChannels { + return &inMemoryChannels{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the inMemoryChannel, and returns the corresponding inMemoryChannel object, and an error if there is any. +func (c *inMemoryChannels) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.InMemoryChannel, err error) { + result = &v1beta1.InMemoryChannel{} + err = c.client.Get(). + Namespace(c.ns). + Resource("inmemorychannels"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of InMemoryChannels that match those selectors. +func (c *inMemoryChannels) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.InMemoryChannelList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta1.InMemoryChannelList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("inmemorychannels"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested inMemoryChannels. +func (c *inMemoryChannels) 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("inmemorychannels"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a inMemoryChannel and creates it. Returns the server's representation of the inMemoryChannel, and an error, if there is any. +func (c *inMemoryChannels) Create(ctx context.Context, inMemoryChannel *v1beta1.InMemoryChannel, opts v1.CreateOptions) (result *v1beta1.InMemoryChannel, err error) { + result = &v1beta1.InMemoryChannel{} + err = c.client.Post(). + Namespace(c.ns). + Resource("inmemorychannels"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(inMemoryChannel). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a inMemoryChannel and updates it. Returns the server's representation of the inMemoryChannel, and an error, if there is any. +func (c *inMemoryChannels) Update(ctx context.Context, inMemoryChannel *v1beta1.InMemoryChannel, opts v1.UpdateOptions) (result *v1beta1.InMemoryChannel, err error) { + result = &v1beta1.InMemoryChannel{} + err = c.client.Put(). + Namespace(c.ns). + Resource("inmemorychannels"). + Name(inMemoryChannel.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(inMemoryChannel). + 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 *inMemoryChannels) UpdateStatus(ctx context.Context, inMemoryChannel *v1beta1.InMemoryChannel, opts v1.UpdateOptions) (result *v1beta1.InMemoryChannel, err error) { + result = &v1beta1.InMemoryChannel{} + err = c.client.Put(). + Namespace(c.ns). + Resource("inmemorychannels"). + Name(inMemoryChannel.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(inMemoryChannel). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the inMemoryChannel and deletes it. Returns an error if one occurs. +func (c *inMemoryChannels) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("inmemorychannels"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *inMemoryChannels) 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("inmemorychannels"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched inMemoryChannel. +func (c *inMemoryChannels) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.InMemoryChannel, err error) { + result = &v1beta1.InMemoryChannel{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("inmemorychannels"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/messaging/v1beta1/messaging_client.go b/pkg/client/clientset/versioned/typed/messaging/v1beta1/messaging_client.go new file mode 100644 index 00000000000..d305cb57463 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/messaging/v1beta1/messaging_client.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + rest "k8s.io/client-go/rest" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +type MessagingV1beta1Interface interface { + RESTClient() rest.Interface + ChannelsGetter + InMemoryChannelsGetter + SubscriptionsGetter +} + +// MessagingV1beta1Client is used to interact with features provided by the messaging.knative.dev group. +type MessagingV1beta1Client struct { + restClient rest.Interface +} + +func (c *MessagingV1beta1Client) Channels(namespace string) ChannelInterface { + return newChannels(c, namespace) +} + +func (c *MessagingV1beta1Client) InMemoryChannels(namespace string) InMemoryChannelInterface { + return newInMemoryChannels(c, namespace) +} + +func (c *MessagingV1beta1Client) Subscriptions(namespace string) SubscriptionInterface { + return newSubscriptions(c, namespace) +} + +// NewForConfig creates a new MessagingV1beta1Client for the given config. +func NewForConfig(c *rest.Config) (*MessagingV1beta1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &MessagingV1beta1Client{client}, nil +} + +// NewForConfigOrDie creates a new MessagingV1beta1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *MessagingV1beta1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new MessagingV1beta1Client for the given RESTClient. +func New(c rest.Interface) *MessagingV1beta1Client { + return &MessagingV1beta1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1beta1.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 *MessagingV1beta1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/clientset/versioned/typed/messaging/v1beta1/subscription.go b/pkg/client/clientset/versioned/typed/messaging/v1beta1/subscription.go new file mode 100644 index 00000000000..b95cb09711e --- /dev/null +++ b/pkg/client/clientset/versioned/typed/messaging/v1beta1/subscription.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// SubscriptionsGetter has a method to return a SubscriptionInterface. +// A group's client should implement this interface. +type SubscriptionsGetter interface { + Subscriptions(namespace string) SubscriptionInterface +} + +// SubscriptionInterface has methods to work with Subscription resources. +type SubscriptionInterface interface { + Create(ctx context.Context, subscription *v1beta1.Subscription, opts v1.CreateOptions) (*v1beta1.Subscription, error) + Update(ctx context.Context, subscription *v1beta1.Subscription, opts v1.UpdateOptions) (*v1beta1.Subscription, error) + UpdateStatus(ctx context.Context, subscription *v1beta1.Subscription, opts v1.UpdateOptions) (*v1beta1.Subscription, 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) (*v1beta1.Subscription, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta1.SubscriptionList, 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 *v1beta1.Subscription, err error) + SubscriptionExpansion +} + +// subscriptions implements SubscriptionInterface +type subscriptions struct { + client rest.Interface + ns string +} + +// newSubscriptions returns a Subscriptions +func newSubscriptions(c *MessagingV1beta1Client, namespace string) *subscriptions { + return &subscriptions{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the subscription, and returns the corresponding subscription object, and an error if there is any. +func (c *subscriptions) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Subscription, err error) { + result = &v1beta1.Subscription{} + err = c.client.Get(). + Namespace(c.ns). + Resource("subscriptions"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Subscriptions that match those selectors. +func (c *subscriptions) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.SubscriptionList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta1.SubscriptionList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("subscriptions"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested subscriptions. +func (c *subscriptions) 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("subscriptions"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a subscription and creates it. Returns the server's representation of the subscription, and an error, if there is any. +func (c *subscriptions) Create(ctx context.Context, subscription *v1beta1.Subscription, opts v1.CreateOptions) (result *v1beta1.Subscription, err error) { + result = &v1beta1.Subscription{} + err = c.client.Post(). + Namespace(c.ns). + Resource("subscriptions"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(subscription). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a subscription and updates it. Returns the server's representation of the subscription, and an error, if there is any. +func (c *subscriptions) Update(ctx context.Context, subscription *v1beta1.Subscription, opts v1.UpdateOptions) (result *v1beta1.Subscription, err error) { + result = &v1beta1.Subscription{} + err = c.client.Put(). + Namespace(c.ns). + Resource("subscriptions"). + Name(subscription.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(subscription). + 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 *subscriptions) UpdateStatus(ctx context.Context, subscription *v1beta1.Subscription, opts v1.UpdateOptions) (result *v1beta1.Subscription, err error) { + result = &v1beta1.Subscription{} + err = c.client.Put(). + Namespace(c.ns). + Resource("subscriptions"). + Name(subscription.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(subscription). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the subscription and deletes it. Returns an error if one occurs. +func (c *subscriptions) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("subscriptions"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *subscriptions) 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("subscriptions"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched subscription. +func (c *subscriptions) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.Subscription, err error) { + result = &v1beta1.Subscription{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("subscriptions"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha1/apiserversource.go b/pkg/client/clientset/versioned/typed/sources/v1alpha1/apiserversource.go new file mode 100644 index 00000000000..d57720afe0e --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha1/apiserversource.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "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" + v1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// ApiServerSourcesGetter has a method to return a ApiServerSourceInterface. +// A group's client should implement this interface. +type ApiServerSourcesGetter interface { + ApiServerSources(namespace string) ApiServerSourceInterface +} + +// ApiServerSourceInterface has methods to work with ApiServerSource resources. +type ApiServerSourceInterface interface { + Create(ctx context.Context, apiServerSource *v1alpha1.ApiServerSource, opts v1.CreateOptions) (*v1alpha1.ApiServerSource, error) + Update(ctx context.Context, apiServerSource *v1alpha1.ApiServerSource, opts v1.UpdateOptions) (*v1alpha1.ApiServerSource, error) + UpdateStatus(ctx context.Context, apiServerSource *v1alpha1.ApiServerSource, opts v1.UpdateOptions) (*v1alpha1.ApiServerSource, 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) (*v1alpha1.ApiServerSource, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.ApiServerSourceList, 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 *v1alpha1.ApiServerSource, err error) + ApiServerSourceExpansion +} + +// apiServerSources implements ApiServerSourceInterface +type apiServerSources struct { + client rest.Interface + ns string +} + +// newApiServerSources returns a ApiServerSources +func newApiServerSources(c *SourcesV1alpha1Client, namespace string) *apiServerSources { + return &apiServerSources{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the apiServerSource, and returns the corresponding apiServerSource object, and an error if there is any. +func (c *apiServerSources) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ApiServerSource, err error) { + result = &v1alpha1.ApiServerSource{} + err = c.client.Get(). + Namespace(c.ns). + Resource("apiserversources"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ApiServerSources that match those selectors. +func (c *apiServerSources) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ApiServerSourceList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.ApiServerSourceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("apiserversources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested apiServerSources. +func (c *apiServerSources) 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("apiserversources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a apiServerSource and creates it. Returns the server's representation of the apiServerSource, and an error, if there is any. +func (c *apiServerSources) Create(ctx context.Context, apiServerSource *v1alpha1.ApiServerSource, opts v1.CreateOptions) (result *v1alpha1.ApiServerSource, err error) { + result = &v1alpha1.ApiServerSource{} + err = c.client.Post(). + Namespace(c.ns). + Resource("apiserversources"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(apiServerSource). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a apiServerSource and updates it. Returns the server's representation of the apiServerSource, and an error, if there is any. +func (c *apiServerSources) Update(ctx context.Context, apiServerSource *v1alpha1.ApiServerSource, opts v1.UpdateOptions) (result *v1alpha1.ApiServerSource, err error) { + result = &v1alpha1.ApiServerSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("apiserversources"). + Name(apiServerSource.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(apiServerSource). + 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 *apiServerSources) UpdateStatus(ctx context.Context, apiServerSource *v1alpha1.ApiServerSource, opts v1.UpdateOptions) (result *v1alpha1.ApiServerSource, err error) { + result = &v1alpha1.ApiServerSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("apiserversources"). + Name(apiServerSource.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(apiServerSource). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the apiServerSource and deletes it. Returns an error if one occurs. +func (c *apiServerSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("apiserversources"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *apiServerSources) 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("apiserversources"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched apiServerSource. +func (c *apiServerSources) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ApiServerSource, err error) { + result = &v1alpha1.ApiServerSource{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("apiserversources"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha1/doc.go b/pkg/client/clientset/versioned/typed/sources/v1alpha1/doc.go new file mode 100644 index 00000000000..f905d3a2158 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/doc.go b/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/doc.go new file mode 100644 index 00000000000..d5a46ef5532 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2021 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/v1alpha1/fake/fake_apiserversource.go b/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/fake_apiserversource.go new file mode 100644 index 00000000000..b57e34b0398 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/fake_apiserversource.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" +) + +// FakeApiServerSources implements ApiServerSourceInterface +type FakeApiServerSources struct { + Fake *FakeSourcesV1alpha1 + ns string +} + +var apiserversourcesResource = schema.GroupVersionResource{Group: "sources.knative.dev", Version: "v1alpha1", Resource: "apiserversources"} + +var apiserversourcesKind = schema.GroupVersionKind{Group: "sources.knative.dev", Version: "v1alpha1", Kind: "ApiServerSource"} + +// Get takes name of the apiServerSource, and returns the corresponding apiServerSource object, and an error if there is any. +func (c *FakeApiServerSources) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ApiServerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(apiserversourcesResource, c.ns, name), &v1alpha1.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ApiServerSource), err +} + +// List takes label and field selectors, and returns the list of ApiServerSources that match those selectors. +func (c *FakeApiServerSources) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ApiServerSourceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(apiserversourcesResource, apiserversourcesKind, c.ns, opts), &v1alpha1.ApiServerSourceList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.ApiServerSourceList{ListMeta: obj.(*v1alpha1.ApiServerSourceList).ListMeta} + for _, item := range obj.(*v1alpha1.ApiServerSourceList).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 apiServerSources. +func (c *FakeApiServerSources) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(apiserversourcesResource, c.ns, opts)) + +} + +// Create takes the representation of a apiServerSource and creates it. Returns the server's representation of the apiServerSource, and an error, if there is any. +func (c *FakeApiServerSources) Create(ctx context.Context, apiServerSource *v1alpha1.ApiServerSource, opts v1.CreateOptions) (result *v1alpha1.ApiServerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(apiserversourcesResource, c.ns, apiServerSource), &v1alpha1.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ApiServerSource), err +} + +// Update takes the representation of a apiServerSource and updates it. Returns the server's representation of the apiServerSource, and an error, if there is any. +func (c *FakeApiServerSources) Update(ctx context.Context, apiServerSource *v1alpha1.ApiServerSource, opts v1.UpdateOptions) (result *v1alpha1.ApiServerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(apiserversourcesResource, c.ns, apiServerSource), &v1alpha1.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ApiServerSource), 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 *FakeApiServerSources) UpdateStatus(ctx context.Context, apiServerSource *v1alpha1.ApiServerSource, opts v1.UpdateOptions) (*v1alpha1.ApiServerSource, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(apiserversourcesResource, "status", c.ns, apiServerSource), &v1alpha1.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ApiServerSource), err +} + +// Delete takes name of the apiServerSource and deletes it. Returns an error if one occurs. +func (c *FakeApiServerSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(apiserversourcesResource, c.ns, name), &v1alpha1.ApiServerSource{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeApiServerSources) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(apiserversourcesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.ApiServerSourceList{}) + return err +} + +// Patch applies the patch and returns the patched apiServerSource. +func (c *FakeApiServerSources) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ApiServerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(apiserversourcesResource, c.ns, name, pt, data, subresources...), &v1alpha1.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ApiServerSource), err +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/fake_sinkbinding.go b/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/fake_sinkbinding.go new file mode 100644 index 00000000000..10b94e09bba --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/fake_sinkbinding.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" +) + +// FakeSinkBindings implements SinkBindingInterface +type FakeSinkBindings struct { + Fake *FakeSourcesV1alpha1 + ns string +} + +var sinkbindingsResource = schema.GroupVersionResource{Group: "sources.knative.dev", Version: "v1alpha1", Resource: "sinkbindings"} + +var sinkbindingsKind = schema.GroupVersionKind{Group: "sources.knative.dev", Version: "v1alpha1", Kind: "SinkBinding"} + +// Get takes name of the sinkBinding, and returns the corresponding sinkBinding object, and an error if there is any. +func (c *FakeSinkBindings) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.SinkBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(sinkbindingsResource, c.ns, name), &v1alpha1.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SinkBinding), err +} + +// List takes label and field selectors, and returns the list of SinkBindings that match those selectors. +func (c *FakeSinkBindings) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.SinkBindingList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(sinkbindingsResource, sinkbindingsKind, c.ns, opts), &v1alpha1.SinkBindingList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.SinkBindingList{ListMeta: obj.(*v1alpha1.SinkBindingList).ListMeta} + for _, item := range obj.(*v1alpha1.SinkBindingList).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 sinkBindings. +func (c *FakeSinkBindings) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(sinkbindingsResource, c.ns, opts)) + +} + +// Create takes the representation of a sinkBinding and creates it. Returns the server's representation of the sinkBinding, and an error, if there is any. +func (c *FakeSinkBindings) Create(ctx context.Context, sinkBinding *v1alpha1.SinkBinding, opts v1.CreateOptions) (result *v1alpha1.SinkBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(sinkbindingsResource, c.ns, sinkBinding), &v1alpha1.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SinkBinding), err +} + +// Update takes the representation of a sinkBinding and updates it. Returns the server's representation of the sinkBinding, and an error, if there is any. +func (c *FakeSinkBindings) Update(ctx context.Context, sinkBinding *v1alpha1.SinkBinding, opts v1.UpdateOptions) (result *v1alpha1.SinkBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(sinkbindingsResource, c.ns, sinkBinding), &v1alpha1.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SinkBinding), 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 *FakeSinkBindings) UpdateStatus(ctx context.Context, sinkBinding *v1alpha1.SinkBinding, opts v1.UpdateOptions) (*v1alpha1.SinkBinding, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(sinkbindingsResource, "status", c.ns, sinkBinding), &v1alpha1.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SinkBinding), err +} + +// Delete takes name of the sinkBinding and deletes it. Returns an error if one occurs. +func (c *FakeSinkBindings) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(sinkbindingsResource, c.ns, name), &v1alpha1.SinkBinding{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeSinkBindings) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(sinkbindingsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.SinkBindingList{}) + return err +} + +// Patch applies the patch and returns the patched sinkBinding. +func (c *FakeSinkBindings) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.SinkBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(sinkbindingsResource, c.ns, name, pt, data, subresources...), &v1alpha1.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.SinkBinding), err +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/fake_sources_client.go b/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/fake_sources_client.go new file mode 100644 index 00000000000..a84fb96fbb0 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/fake_sources_client.go @@ -0,0 +1,44 @@ +/* +Copyright 2021 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" + v1alpha1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha1" +) + +type FakeSourcesV1alpha1 struct { + *testing.Fake +} + +func (c *FakeSourcesV1alpha1) ApiServerSources(namespace string) v1alpha1.ApiServerSourceInterface { + return &FakeApiServerSources{c, namespace} +} + +func (c *FakeSourcesV1alpha1) SinkBindings(namespace string) v1alpha1.SinkBindingInterface { + return &FakeSinkBindings{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeSourcesV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/sources/v1alpha1/generated_expansion.go new file mode 100644 index 00000000000..9a0bdd4992e --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha1/generated_expansion.go @@ -0,0 +1,23 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +type ApiServerSourceExpansion interface{} + +type SinkBindingExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha1/sinkbinding.go b/pkg/client/clientset/versioned/typed/sources/v1alpha1/sinkbinding.go new file mode 100644 index 00000000000..8711d7e6a33 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha1/sinkbinding.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "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" + v1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// SinkBindingsGetter has a method to return a SinkBindingInterface. +// A group's client should implement this interface. +type SinkBindingsGetter interface { + SinkBindings(namespace string) SinkBindingInterface +} + +// SinkBindingInterface has methods to work with SinkBinding resources. +type SinkBindingInterface interface { + Create(ctx context.Context, sinkBinding *v1alpha1.SinkBinding, opts v1.CreateOptions) (*v1alpha1.SinkBinding, error) + Update(ctx context.Context, sinkBinding *v1alpha1.SinkBinding, opts v1.UpdateOptions) (*v1alpha1.SinkBinding, error) + UpdateStatus(ctx context.Context, sinkBinding *v1alpha1.SinkBinding, opts v1.UpdateOptions) (*v1alpha1.SinkBinding, 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) (*v1alpha1.SinkBinding, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.SinkBindingList, 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 *v1alpha1.SinkBinding, err error) + SinkBindingExpansion +} + +// sinkBindings implements SinkBindingInterface +type sinkBindings struct { + client rest.Interface + ns string +} + +// newSinkBindings returns a SinkBindings +func newSinkBindings(c *SourcesV1alpha1Client, namespace string) *sinkBindings { + return &sinkBindings{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the sinkBinding, and returns the corresponding sinkBinding object, and an error if there is any. +func (c *sinkBindings) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.SinkBinding, err error) { + result = &v1alpha1.SinkBinding{} + err = c.client.Get(). + Namespace(c.ns). + Resource("sinkbindings"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of SinkBindings that match those selectors. +func (c *sinkBindings) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.SinkBindingList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.SinkBindingList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("sinkbindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested sinkBindings. +func (c *sinkBindings) 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("sinkbindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a sinkBinding and creates it. Returns the server's representation of the sinkBinding, and an error, if there is any. +func (c *sinkBindings) Create(ctx context.Context, sinkBinding *v1alpha1.SinkBinding, opts v1.CreateOptions) (result *v1alpha1.SinkBinding, err error) { + result = &v1alpha1.SinkBinding{} + err = c.client.Post(). + Namespace(c.ns). + Resource("sinkbindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(sinkBinding). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a sinkBinding and updates it. Returns the server's representation of the sinkBinding, and an error, if there is any. +func (c *sinkBindings) Update(ctx context.Context, sinkBinding *v1alpha1.SinkBinding, opts v1.UpdateOptions) (result *v1alpha1.SinkBinding, err error) { + result = &v1alpha1.SinkBinding{} + err = c.client.Put(). + Namespace(c.ns). + Resource("sinkbindings"). + Name(sinkBinding.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(sinkBinding). + 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 *sinkBindings) UpdateStatus(ctx context.Context, sinkBinding *v1alpha1.SinkBinding, opts v1.UpdateOptions) (result *v1alpha1.SinkBinding, err error) { + result = &v1alpha1.SinkBinding{} + err = c.client.Put(). + Namespace(c.ns). + Resource("sinkbindings"). + Name(sinkBinding.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(sinkBinding). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the sinkBinding and deletes it. Returns an error if one occurs. +func (c *sinkBindings) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("sinkbindings"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *sinkBindings) 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("sinkbindings"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched sinkBinding. +func (c *sinkBindings) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.SinkBinding, err error) { + result = &v1alpha1.SinkBinding{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("sinkbindings"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha1/sources_client.go b/pkg/client/clientset/versioned/typed/sources/v1alpha1/sources_client.go new file mode 100644 index 00000000000..43796ea86fa --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha1/sources_client.go @@ -0,0 +1,94 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + rest "k8s.io/client-go/rest" + v1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" + "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +type SourcesV1alpha1Interface interface { + RESTClient() rest.Interface + ApiServerSourcesGetter + SinkBindingsGetter +} + +// SourcesV1alpha1Client is used to interact with features provided by the sources.knative.dev group. +type SourcesV1alpha1Client struct { + restClient rest.Interface +} + +func (c *SourcesV1alpha1Client) ApiServerSources(namespace string) ApiServerSourceInterface { + return newApiServerSources(c, namespace) +} + +func (c *SourcesV1alpha1Client) SinkBindings(namespace string) SinkBindingInterface { + return newSinkBindings(c, namespace) +} + +// NewForConfig creates a new SourcesV1alpha1Client for the given config. +func NewForConfig(c *rest.Config) (*SourcesV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &SourcesV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new SourcesV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *SourcesV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new SourcesV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *SourcesV1alpha1Client { + return &SourcesV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha1.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 *SourcesV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha2/apiserversource.go b/pkg/client/clientset/versioned/typed/sources/v1alpha2/apiserversource.go new file mode 100644 index 00000000000..1df169d5f8f --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha2/apiserversource.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 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 v1alpha2 + +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" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// ApiServerSourcesGetter has a method to return a ApiServerSourceInterface. +// A group's client should implement this interface. +type ApiServerSourcesGetter interface { + ApiServerSources(namespace string) ApiServerSourceInterface +} + +// ApiServerSourceInterface has methods to work with ApiServerSource resources. +type ApiServerSourceInterface interface { + Create(ctx context.Context, apiServerSource *v1alpha2.ApiServerSource, opts v1.CreateOptions) (*v1alpha2.ApiServerSource, error) + Update(ctx context.Context, apiServerSource *v1alpha2.ApiServerSource, opts v1.UpdateOptions) (*v1alpha2.ApiServerSource, error) + UpdateStatus(ctx context.Context, apiServerSource *v1alpha2.ApiServerSource, opts v1.UpdateOptions) (*v1alpha2.ApiServerSource, 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) (*v1alpha2.ApiServerSource, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha2.ApiServerSourceList, 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 *v1alpha2.ApiServerSource, err error) + ApiServerSourceExpansion +} + +// apiServerSources implements ApiServerSourceInterface +type apiServerSources struct { + client rest.Interface + ns string +} + +// newApiServerSources returns a ApiServerSources +func newApiServerSources(c *SourcesV1alpha2Client, namespace string) *apiServerSources { + return &apiServerSources{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the apiServerSource, and returns the corresponding apiServerSource object, and an error if there is any. +func (c *apiServerSources) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.ApiServerSource, err error) { + result = &v1alpha2.ApiServerSource{} + err = c.client.Get(). + Namespace(c.ns). + Resource("apiserversources"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ApiServerSources that match those selectors. +func (c *apiServerSources) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.ApiServerSourceList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha2.ApiServerSourceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("apiserversources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested apiServerSources. +func (c *apiServerSources) 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("apiserversources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a apiServerSource and creates it. Returns the server's representation of the apiServerSource, and an error, if there is any. +func (c *apiServerSources) Create(ctx context.Context, apiServerSource *v1alpha2.ApiServerSource, opts v1.CreateOptions) (result *v1alpha2.ApiServerSource, err error) { + result = &v1alpha2.ApiServerSource{} + err = c.client.Post(). + Namespace(c.ns). + Resource("apiserversources"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(apiServerSource). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a apiServerSource and updates it. Returns the server's representation of the apiServerSource, and an error, if there is any. +func (c *apiServerSources) Update(ctx context.Context, apiServerSource *v1alpha2.ApiServerSource, opts v1.UpdateOptions) (result *v1alpha2.ApiServerSource, err error) { + result = &v1alpha2.ApiServerSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("apiserversources"). + Name(apiServerSource.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(apiServerSource). + 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 *apiServerSources) UpdateStatus(ctx context.Context, apiServerSource *v1alpha2.ApiServerSource, opts v1.UpdateOptions) (result *v1alpha2.ApiServerSource, err error) { + result = &v1alpha2.ApiServerSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("apiserversources"). + Name(apiServerSource.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(apiServerSource). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the apiServerSource and deletes it. Returns an error if one occurs. +func (c *apiServerSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("apiserversources"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *apiServerSources) 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("apiserversources"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched apiServerSource. +func (c *apiServerSources) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.ApiServerSource, err error) { + result = &v1alpha2.ApiServerSource{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("apiserversources"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha2/containersource.go b/pkg/client/clientset/versioned/typed/sources/v1alpha2/containersource.go new file mode 100644 index 00000000000..b49e62416fb --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha2/containersource.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 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 v1alpha2 + +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" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// ContainerSourcesGetter has a method to return a ContainerSourceInterface. +// A group's client should implement this interface. +type ContainerSourcesGetter interface { + ContainerSources(namespace string) ContainerSourceInterface +} + +// ContainerSourceInterface has methods to work with ContainerSource resources. +type ContainerSourceInterface interface { + Create(ctx context.Context, containerSource *v1alpha2.ContainerSource, opts v1.CreateOptions) (*v1alpha2.ContainerSource, error) + Update(ctx context.Context, containerSource *v1alpha2.ContainerSource, opts v1.UpdateOptions) (*v1alpha2.ContainerSource, error) + UpdateStatus(ctx context.Context, containerSource *v1alpha2.ContainerSource, opts v1.UpdateOptions) (*v1alpha2.ContainerSource, 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) (*v1alpha2.ContainerSource, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha2.ContainerSourceList, 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 *v1alpha2.ContainerSource, err error) + ContainerSourceExpansion +} + +// containerSources implements ContainerSourceInterface +type containerSources struct { + client rest.Interface + ns string +} + +// newContainerSources returns a ContainerSources +func newContainerSources(c *SourcesV1alpha2Client, namespace string) *containerSources { + return &containerSources{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the containerSource, and returns the corresponding containerSource object, and an error if there is any. +func (c *containerSources) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.ContainerSource, err error) { + result = &v1alpha2.ContainerSource{} + err = c.client.Get(). + Namespace(c.ns). + Resource("containersources"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ContainerSources that match those selectors. +func (c *containerSources) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.ContainerSourceList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha2.ContainerSourceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("containersources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested containerSources. +func (c *containerSources) 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("containersources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a containerSource and creates it. Returns the server's representation of the containerSource, and an error, if there is any. +func (c *containerSources) Create(ctx context.Context, containerSource *v1alpha2.ContainerSource, opts v1.CreateOptions) (result *v1alpha2.ContainerSource, err error) { + result = &v1alpha2.ContainerSource{} + err = c.client.Post(). + Namespace(c.ns). + Resource("containersources"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(containerSource). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a containerSource and updates it. Returns the server's representation of the containerSource, and an error, if there is any. +func (c *containerSources) Update(ctx context.Context, containerSource *v1alpha2.ContainerSource, opts v1.UpdateOptions) (result *v1alpha2.ContainerSource, err error) { + result = &v1alpha2.ContainerSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("containersources"). + Name(containerSource.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(containerSource). + 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 *containerSources) UpdateStatus(ctx context.Context, containerSource *v1alpha2.ContainerSource, opts v1.UpdateOptions) (result *v1alpha2.ContainerSource, err error) { + result = &v1alpha2.ContainerSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("containersources"). + Name(containerSource.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(containerSource). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the containerSource and deletes it. Returns an error if one occurs. +func (c *containerSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("containersources"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *containerSources) 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("containersources"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched containerSource. +func (c *containerSources) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.ContainerSource, err error) { + result = &v1alpha2.ContainerSource{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("containersources"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha2/doc.go b/pkg/client/clientset/versioned/typed/sources/v1alpha2/doc.go new file mode 100644 index 00000000000..bfad4140d90 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha2/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2021 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 v1alpha2 diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/doc.go b/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/doc.go new file mode 100644 index 00000000000..d5a46ef5532 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2021 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/v1alpha2/fake/fake_apiserversource.go b/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/fake_apiserversource.go new file mode 100644 index 00000000000..f7359619192 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/fake_apiserversource.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" +) + +// FakeApiServerSources implements ApiServerSourceInterface +type FakeApiServerSources struct { + Fake *FakeSourcesV1alpha2 + ns string +} + +var apiserversourcesResource = schema.GroupVersionResource{Group: "sources.knative.dev", Version: "v1alpha2", Resource: "apiserversources"} + +var apiserversourcesKind = schema.GroupVersionKind{Group: "sources.knative.dev", Version: "v1alpha2", Kind: "ApiServerSource"} + +// Get takes name of the apiServerSource, and returns the corresponding apiServerSource object, and an error if there is any. +func (c *FakeApiServerSources) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.ApiServerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(apiserversourcesResource, c.ns, name), &v1alpha2.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ApiServerSource), err +} + +// List takes label and field selectors, and returns the list of ApiServerSources that match those selectors. +func (c *FakeApiServerSources) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.ApiServerSourceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(apiserversourcesResource, apiserversourcesKind, c.ns, opts), &v1alpha2.ApiServerSourceList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.ApiServerSourceList{ListMeta: obj.(*v1alpha2.ApiServerSourceList).ListMeta} + for _, item := range obj.(*v1alpha2.ApiServerSourceList).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 apiServerSources. +func (c *FakeApiServerSources) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(apiserversourcesResource, c.ns, opts)) + +} + +// Create takes the representation of a apiServerSource and creates it. Returns the server's representation of the apiServerSource, and an error, if there is any. +func (c *FakeApiServerSources) Create(ctx context.Context, apiServerSource *v1alpha2.ApiServerSource, opts v1.CreateOptions) (result *v1alpha2.ApiServerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(apiserversourcesResource, c.ns, apiServerSource), &v1alpha2.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ApiServerSource), err +} + +// Update takes the representation of a apiServerSource and updates it. Returns the server's representation of the apiServerSource, and an error, if there is any. +func (c *FakeApiServerSources) Update(ctx context.Context, apiServerSource *v1alpha2.ApiServerSource, opts v1.UpdateOptions) (result *v1alpha2.ApiServerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(apiserversourcesResource, c.ns, apiServerSource), &v1alpha2.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ApiServerSource), 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 *FakeApiServerSources) UpdateStatus(ctx context.Context, apiServerSource *v1alpha2.ApiServerSource, opts v1.UpdateOptions) (*v1alpha2.ApiServerSource, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(apiserversourcesResource, "status", c.ns, apiServerSource), &v1alpha2.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ApiServerSource), err +} + +// Delete takes name of the apiServerSource and deletes it. Returns an error if one occurs. +func (c *FakeApiServerSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(apiserversourcesResource, c.ns, name), &v1alpha2.ApiServerSource{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeApiServerSources) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(apiserversourcesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha2.ApiServerSourceList{}) + return err +} + +// Patch applies the patch and returns the patched apiServerSource. +func (c *FakeApiServerSources) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.ApiServerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(apiserversourcesResource, c.ns, name, pt, data, subresources...), &v1alpha2.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ApiServerSource), err +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/fake_containersource.go b/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/fake_containersource.go new file mode 100644 index 00000000000..331628b22e6 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/fake_containersource.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" +) + +// FakeContainerSources implements ContainerSourceInterface +type FakeContainerSources struct { + Fake *FakeSourcesV1alpha2 + ns string +} + +var containersourcesResource = schema.GroupVersionResource{Group: "sources.knative.dev", Version: "v1alpha2", Resource: "containersources"} + +var containersourcesKind = schema.GroupVersionKind{Group: "sources.knative.dev", Version: "v1alpha2", Kind: "ContainerSource"} + +// Get takes name of the containerSource, and returns the corresponding containerSource object, and an error if there is any. +func (c *FakeContainerSources) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.ContainerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(containersourcesResource, c.ns, name), &v1alpha2.ContainerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ContainerSource), err +} + +// List takes label and field selectors, and returns the list of ContainerSources that match those selectors. +func (c *FakeContainerSources) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.ContainerSourceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(containersourcesResource, containersourcesKind, c.ns, opts), &v1alpha2.ContainerSourceList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.ContainerSourceList{ListMeta: obj.(*v1alpha2.ContainerSourceList).ListMeta} + for _, item := range obj.(*v1alpha2.ContainerSourceList).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 containerSources. +func (c *FakeContainerSources) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(containersourcesResource, c.ns, opts)) + +} + +// Create takes the representation of a containerSource and creates it. Returns the server's representation of the containerSource, and an error, if there is any. +func (c *FakeContainerSources) Create(ctx context.Context, containerSource *v1alpha2.ContainerSource, opts v1.CreateOptions) (result *v1alpha2.ContainerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(containersourcesResource, c.ns, containerSource), &v1alpha2.ContainerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ContainerSource), err +} + +// Update takes the representation of a containerSource and updates it. Returns the server's representation of the containerSource, and an error, if there is any. +func (c *FakeContainerSources) Update(ctx context.Context, containerSource *v1alpha2.ContainerSource, opts v1.UpdateOptions) (result *v1alpha2.ContainerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(containersourcesResource, c.ns, containerSource), &v1alpha2.ContainerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ContainerSource), 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 *FakeContainerSources) UpdateStatus(ctx context.Context, containerSource *v1alpha2.ContainerSource, opts v1.UpdateOptions) (*v1alpha2.ContainerSource, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(containersourcesResource, "status", c.ns, containerSource), &v1alpha2.ContainerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ContainerSource), err +} + +// Delete takes name of the containerSource and deletes it. Returns an error if one occurs. +func (c *FakeContainerSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(containersourcesResource, c.ns, name), &v1alpha2.ContainerSource{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeContainerSources) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(containersourcesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha2.ContainerSourceList{}) + return err +} + +// Patch applies the patch and returns the patched containerSource. +func (c *FakeContainerSources) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.ContainerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(containersourcesResource, c.ns, name, pt, data, subresources...), &v1alpha2.ContainerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.ContainerSource), err +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/fake_pingsource.go b/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/fake_pingsource.go new file mode 100644 index 00000000000..677cc5cd096 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/fake_pingsource.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" +) + +// FakePingSources implements PingSourceInterface +type FakePingSources struct { + Fake *FakeSourcesV1alpha2 + ns string +} + +var pingsourcesResource = schema.GroupVersionResource{Group: "sources.knative.dev", Version: "v1alpha2", Resource: "pingsources"} + +var pingsourcesKind = schema.GroupVersionKind{Group: "sources.knative.dev", Version: "v1alpha2", 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 *v1alpha2.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(pingsourcesResource, c.ns, name), &v1alpha2.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.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 *v1alpha2.PingSourceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(pingsourcesResource, pingsourcesKind, c.ns, opts), &v1alpha2.PingSourceList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.PingSourceList{ListMeta: obj.(*v1alpha2.PingSourceList).ListMeta} + for _, item := range obj.(*v1alpha2.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 *v1alpha2.PingSource, opts v1.CreateOptions) (result *v1alpha2.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(pingsourcesResource, c.ns, pingSource), &v1alpha2.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.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 *v1alpha2.PingSource, opts v1.UpdateOptions) (result *v1alpha2.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(pingsourcesResource, c.ns, pingSource), &v1alpha2.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.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 *v1alpha2.PingSource, opts v1.UpdateOptions) (*v1alpha2.PingSource, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(pingsourcesResource, "status", c.ns, pingSource), &v1alpha2.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.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), &v1alpha2.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, &v1alpha2.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 *v1alpha2.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(pingsourcesResource, c.ns, name, pt, data, subresources...), &v1alpha2.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.PingSource), err +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/fake_sinkbinding.go b/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/fake_sinkbinding.go new file mode 100644 index 00000000000..48c01a22d46 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/fake_sinkbinding.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" +) + +// FakeSinkBindings implements SinkBindingInterface +type FakeSinkBindings struct { + Fake *FakeSourcesV1alpha2 + ns string +} + +var sinkbindingsResource = schema.GroupVersionResource{Group: "sources.knative.dev", Version: "v1alpha2", Resource: "sinkbindings"} + +var sinkbindingsKind = schema.GroupVersionKind{Group: "sources.knative.dev", Version: "v1alpha2", Kind: "SinkBinding"} + +// Get takes name of the sinkBinding, and returns the corresponding sinkBinding object, and an error if there is any. +func (c *FakeSinkBindings) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.SinkBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(sinkbindingsResource, c.ns, name), &v1alpha2.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.SinkBinding), err +} + +// List takes label and field selectors, and returns the list of SinkBindings that match those selectors. +func (c *FakeSinkBindings) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.SinkBindingList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(sinkbindingsResource, sinkbindingsKind, c.ns, opts), &v1alpha2.SinkBindingList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.SinkBindingList{ListMeta: obj.(*v1alpha2.SinkBindingList).ListMeta} + for _, item := range obj.(*v1alpha2.SinkBindingList).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 sinkBindings. +func (c *FakeSinkBindings) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(sinkbindingsResource, c.ns, opts)) + +} + +// Create takes the representation of a sinkBinding and creates it. Returns the server's representation of the sinkBinding, and an error, if there is any. +func (c *FakeSinkBindings) Create(ctx context.Context, sinkBinding *v1alpha2.SinkBinding, opts v1.CreateOptions) (result *v1alpha2.SinkBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(sinkbindingsResource, c.ns, sinkBinding), &v1alpha2.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.SinkBinding), err +} + +// Update takes the representation of a sinkBinding and updates it. Returns the server's representation of the sinkBinding, and an error, if there is any. +func (c *FakeSinkBindings) Update(ctx context.Context, sinkBinding *v1alpha2.SinkBinding, opts v1.UpdateOptions) (result *v1alpha2.SinkBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(sinkbindingsResource, c.ns, sinkBinding), &v1alpha2.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.SinkBinding), 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 *FakeSinkBindings) UpdateStatus(ctx context.Context, sinkBinding *v1alpha2.SinkBinding, opts v1.UpdateOptions) (*v1alpha2.SinkBinding, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(sinkbindingsResource, "status", c.ns, sinkBinding), &v1alpha2.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.SinkBinding), err +} + +// Delete takes name of the sinkBinding and deletes it. Returns an error if one occurs. +func (c *FakeSinkBindings) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(sinkbindingsResource, c.ns, name), &v1alpha2.SinkBinding{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeSinkBindings) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(sinkbindingsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha2.SinkBindingList{}) + return err +} + +// Patch applies the patch and returns the patched sinkBinding. +func (c *FakeSinkBindings) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.SinkBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(sinkbindingsResource, c.ns, name, pt, data, subresources...), &v1alpha2.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.SinkBinding), err +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/fake_sources_client.go b/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/fake_sources_client.go new file mode 100644 index 00000000000..abfc70d2c84 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha2/fake/fake_sources_client.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + v1alpha2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha2" +) + +type FakeSourcesV1alpha2 struct { + *testing.Fake +} + +func (c *FakeSourcesV1alpha2) ApiServerSources(namespace string) v1alpha2.ApiServerSourceInterface { + return &FakeApiServerSources{c, namespace} +} + +func (c *FakeSourcesV1alpha2) ContainerSources(namespace string) v1alpha2.ContainerSourceInterface { + return &FakeContainerSources{c, namespace} +} + +func (c *FakeSourcesV1alpha2) PingSources(namespace string) v1alpha2.PingSourceInterface { + return &FakePingSources{c, namespace} +} + +func (c *FakeSourcesV1alpha2) SinkBindings(namespace string) v1alpha2.SinkBindingInterface { + return &FakeSinkBindings{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeSourcesV1alpha2) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha2/generated_expansion.go b/pkg/client/clientset/versioned/typed/sources/v1alpha2/generated_expansion.go new file mode 100644 index 00000000000..6eca2c9befe --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha2/generated_expansion.go @@ -0,0 +1,27 @@ +/* +Copyright 2021 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 v1alpha2 + +type ApiServerSourceExpansion interface{} + +type ContainerSourceExpansion interface{} + +type PingSourceExpansion interface{} + +type SinkBindingExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha2/pingsource.go b/pkg/client/clientset/versioned/typed/sources/v1alpha2/pingsource.go new file mode 100644 index 00000000000..42588535890 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha2/pingsource.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 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 v1alpha2 + +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" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + 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 *v1alpha2.PingSource, opts v1.CreateOptions) (*v1alpha2.PingSource, error) + Update(ctx context.Context, pingSource *v1alpha2.PingSource, opts v1.UpdateOptions) (*v1alpha2.PingSource, error) + UpdateStatus(ctx context.Context, pingSource *v1alpha2.PingSource, opts v1.UpdateOptions) (*v1alpha2.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) (*v1alpha2.PingSource, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha2.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 *v1alpha2.PingSource, err error) + PingSourceExpansion +} + +// pingSources implements PingSourceInterface +type pingSources struct { + client rest.Interface + ns string +} + +// newPingSources returns a PingSources +func newPingSources(c *SourcesV1alpha2Client, 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 *v1alpha2.PingSource, err error) { + result = &v1alpha2.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 *v1alpha2.PingSourceList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha2.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 *v1alpha2.PingSource, opts v1.CreateOptions) (result *v1alpha2.PingSource, err error) { + result = &v1alpha2.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 *v1alpha2.PingSource, opts v1.UpdateOptions) (result *v1alpha2.PingSource, err error) { + result = &v1alpha2.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 *v1alpha2.PingSource, opts v1.UpdateOptions) (result *v1alpha2.PingSource, err error) { + result = &v1alpha2.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 *v1alpha2.PingSource, err error) { + result = &v1alpha2.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/v1alpha2/sinkbinding.go b/pkg/client/clientset/versioned/typed/sources/v1alpha2/sinkbinding.go new file mode 100644 index 00000000000..fec5aa3208d --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha2/sinkbinding.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 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 v1alpha2 + +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" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// SinkBindingsGetter has a method to return a SinkBindingInterface. +// A group's client should implement this interface. +type SinkBindingsGetter interface { + SinkBindings(namespace string) SinkBindingInterface +} + +// SinkBindingInterface has methods to work with SinkBinding resources. +type SinkBindingInterface interface { + Create(ctx context.Context, sinkBinding *v1alpha2.SinkBinding, opts v1.CreateOptions) (*v1alpha2.SinkBinding, error) + Update(ctx context.Context, sinkBinding *v1alpha2.SinkBinding, opts v1.UpdateOptions) (*v1alpha2.SinkBinding, error) + UpdateStatus(ctx context.Context, sinkBinding *v1alpha2.SinkBinding, opts v1.UpdateOptions) (*v1alpha2.SinkBinding, 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) (*v1alpha2.SinkBinding, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha2.SinkBindingList, 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 *v1alpha2.SinkBinding, err error) + SinkBindingExpansion +} + +// sinkBindings implements SinkBindingInterface +type sinkBindings struct { + client rest.Interface + ns string +} + +// newSinkBindings returns a SinkBindings +func newSinkBindings(c *SourcesV1alpha2Client, namespace string) *sinkBindings { + return &sinkBindings{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the sinkBinding, and returns the corresponding sinkBinding object, and an error if there is any. +func (c *sinkBindings) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.SinkBinding, err error) { + result = &v1alpha2.SinkBinding{} + err = c.client.Get(). + Namespace(c.ns). + Resource("sinkbindings"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of SinkBindings that match those selectors. +func (c *sinkBindings) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.SinkBindingList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha2.SinkBindingList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("sinkbindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested sinkBindings. +func (c *sinkBindings) 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("sinkbindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a sinkBinding and creates it. Returns the server's representation of the sinkBinding, and an error, if there is any. +func (c *sinkBindings) Create(ctx context.Context, sinkBinding *v1alpha2.SinkBinding, opts v1.CreateOptions) (result *v1alpha2.SinkBinding, err error) { + result = &v1alpha2.SinkBinding{} + err = c.client.Post(). + Namespace(c.ns). + Resource("sinkbindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(sinkBinding). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a sinkBinding and updates it. Returns the server's representation of the sinkBinding, and an error, if there is any. +func (c *sinkBindings) Update(ctx context.Context, sinkBinding *v1alpha2.SinkBinding, opts v1.UpdateOptions) (result *v1alpha2.SinkBinding, err error) { + result = &v1alpha2.SinkBinding{} + err = c.client.Put(). + Namespace(c.ns). + Resource("sinkbindings"). + Name(sinkBinding.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(sinkBinding). + 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 *sinkBindings) UpdateStatus(ctx context.Context, sinkBinding *v1alpha2.SinkBinding, opts v1.UpdateOptions) (result *v1alpha2.SinkBinding, err error) { + result = &v1alpha2.SinkBinding{} + err = c.client.Put(). + Namespace(c.ns). + Resource("sinkbindings"). + Name(sinkBinding.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(sinkBinding). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the sinkBinding and deletes it. Returns an error if one occurs. +func (c *sinkBindings) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("sinkbindings"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *sinkBindings) 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("sinkbindings"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched sinkBinding. +func (c *sinkBindings) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.SinkBinding, err error) { + result = &v1alpha2.SinkBinding{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("sinkbindings"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha2/sources_client.go b/pkg/client/clientset/versioned/typed/sources/v1alpha2/sources_client.go new file mode 100644 index 00000000000..94f9ca4f3d6 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha2/sources_client.go @@ -0,0 +1,104 @@ +/* +Copyright 2021 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 v1alpha2 + +import ( + rest "k8s.io/client-go/rest" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +type SourcesV1alpha2Interface interface { + RESTClient() rest.Interface + ApiServerSourcesGetter + ContainerSourcesGetter + PingSourcesGetter + SinkBindingsGetter +} + +// SourcesV1alpha2Client is used to interact with features provided by the sources.knative.dev group. +type SourcesV1alpha2Client struct { + restClient rest.Interface +} + +func (c *SourcesV1alpha2Client) ApiServerSources(namespace string) ApiServerSourceInterface { + return newApiServerSources(c, namespace) +} + +func (c *SourcesV1alpha2Client) ContainerSources(namespace string) ContainerSourceInterface { + return newContainerSources(c, namespace) +} + +func (c *SourcesV1alpha2Client) PingSources(namespace string) PingSourceInterface { + return newPingSources(c, namespace) +} + +func (c *SourcesV1alpha2Client) SinkBindings(namespace string) SinkBindingInterface { + return newSinkBindings(c, namespace) +} + +// NewForConfig creates a new SourcesV1alpha2Client for the given config. +func NewForConfig(c *rest.Config) (*SourcesV1alpha2Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &SourcesV1alpha2Client{client}, nil +} + +// NewForConfigOrDie creates a new SourcesV1alpha2Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *SourcesV1alpha2Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new SourcesV1alpha2Client for the given RESTClient. +func New(c rest.Interface) *SourcesV1alpha2Client { + return &SourcesV1alpha2Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha2.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 *SourcesV1alpha2Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/apiserversource.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/apiserversource.go new file mode 100644 index 00000000000..2e0cad0f263 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/apiserversource.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// ApiServerSourcesGetter has a method to return a ApiServerSourceInterface. +// A group's client should implement this interface. +type ApiServerSourcesGetter interface { + ApiServerSources(namespace string) ApiServerSourceInterface +} + +// ApiServerSourceInterface has methods to work with ApiServerSource resources. +type ApiServerSourceInterface interface { + Create(ctx context.Context, apiServerSource *v1beta1.ApiServerSource, opts v1.CreateOptions) (*v1beta1.ApiServerSource, error) + Update(ctx context.Context, apiServerSource *v1beta1.ApiServerSource, opts v1.UpdateOptions) (*v1beta1.ApiServerSource, error) + UpdateStatus(ctx context.Context, apiServerSource *v1beta1.ApiServerSource, opts v1.UpdateOptions) (*v1beta1.ApiServerSource, 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) (*v1beta1.ApiServerSource, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta1.ApiServerSourceList, 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 *v1beta1.ApiServerSource, err error) + ApiServerSourceExpansion +} + +// apiServerSources implements ApiServerSourceInterface +type apiServerSources struct { + client rest.Interface + ns string +} + +// newApiServerSources returns a ApiServerSources +func newApiServerSources(c *SourcesV1beta1Client, namespace string) *apiServerSources { + return &apiServerSources{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the apiServerSource, and returns the corresponding apiServerSource object, and an error if there is any. +func (c *apiServerSources) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.ApiServerSource, err error) { + result = &v1beta1.ApiServerSource{} + err = c.client.Get(). + Namespace(c.ns). + Resource("apiserversources"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ApiServerSources that match those selectors. +func (c *apiServerSources) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ApiServerSourceList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta1.ApiServerSourceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("apiserversources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested apiServerSources. +func (c *apiServerSources) 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("apiserversources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a apiServerSource and creates it. Returns the server's representation of the apiServerSource, and an error, if there is any. +func (c *apiServerSources) Create(ctx context.Context, apiServerSource *v1beta1.ApiServerSource, opts v1.CreateOptions) (result *v1beta1.ApiServerSource, err error) { + result = &v1beta1.ApiServerSource{} + err = c.client.Post(). + Namespace(c.ns). + Resource("apiserversources"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(apiServerSource). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a apiServerSource and updates it. Returns the server's representation of the apiServerSource, and an error, if there is any. +func (c *apiServerSources) Update(ctx context.Context, apiServerSource *v1beta1.ApiServerSource, opts v1.UpdateOptions) (result *v1beta1.ApiServerSource, err error) { + result = &v1beta1.ApiServerSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("apiserversources"). + Name(apiServerSource.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(apiServerSource). + 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 *apiServerSources) UpdateStatus(ctx context.Context, apiServerSource *v1beta1.ApiServerSource, opts v1.UpdateOptions) (result *v1beta1.ApiServerSource, err error) { + result = &v1beta1.ApiServerSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("apiserversources"). + Name(apiServerSource.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(apiServerSource). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the apiServerSource and deletes it. Returns an error if one occurs. +func (c *apiServerSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("apiserversources"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *apiServerSources) 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("apiserversources"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched apiServerSource. +func (c *apiServerSources) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ApiServerSource, err error) { + result = &v1beta1.ApiServerSource{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("apiserversources"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/containersource.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/containersource.go new file mode 100644 index 00000000000..4e5a42ec9dc --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/containersource.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// ContainerSourcesGetter has a method to return a ContainerSourceInterface. +// A group's client should implement this interface. +type ContainerSourcesGetter interface { + ContainerSources(namespace string) ContainerSourceInterface +} + +// ContainerSourceInterface has methods to work with ContainerSource resources. +type ContainerSourceInterface interface { + Create(ctx context.Context, containerSource *v1beta1.ContainerSource, opts v1.CreateOptions) (*v1beta1.ContainerSource, error) + Update(ctx context.Context, containerSource *v1beta1.ContainerSource, opts v1.UpdateOptions) (*v1beta1.ContainerSource, error) + UpdateStatus(ctx context.Context, containerSource *v1beta1.ContainerSource, opts v1.UpdateOptions) (*v1beta1.ContainerSource, 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) (*v1beta1.ContainerSource, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta1.ContainerSourceList, 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 *v1beta1.ContainerSource, err error) + ContainerSourceExpansion +} + +// containerSources implements ContainerSourceInterface +type containerSources struct { + client rest.Interface + ns string +} + +// newContainerSources returns a ContainerSources +func newContainerSources(c *SourcesV1beta1Client, namespace string) *containerSources { + return &containerSources{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the containerSource, and returns the corresponding containerSource object, and an error if there is any. +func (c *containerSources) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.ContainerSource, err error) { + result = &v1beta1.ContainerSource{} + err = c.client.Get(). + Namespace(c.ns). + Resource("containersources"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ContainerSources that match those selectors. +func (c *containerSources) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ContainerSourceList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta1.ContainerSourceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("containersources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested containerSources. +func (c *containerSources) 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("containersources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a containerSource and creates it. Returns the server's representation of the containerSource, and an error, if there is any. +func (c *containerSources) Create(ctx context.Context, containerSource *v1beta1.ContainerSource, opts v1.CreateOptions) (result *v1beta1.ContainerSource, err error) { + result = &v1beta1.ContainerSource{} + err = c.client.Post(). + Namespace(c.ns). + Resource("containersources"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(containerSource). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a containerSource and updates it. Returns the server's representation of the containerSource, and an error, if there is any. +func (c *containerSources) Update(ctx context.Context, containerSource *v1beta1.ContainerSource, opts v1.UpdateOptions) (result *v1beta1.ContainerSource, err error) { + result = &v1beta1.ContainerSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("containersources"). + Name(containerSource.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(containerSource). + 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 *containerSources) UpdateStatus(ctx context.Context, containerSource *v1beta1.ContainerSource, opts v1.UpdateOptions) (result *v1beta1.ContainerSource, err error) { + result = &v1beta1.ContainerSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("containersources"). + Name(containerSource.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(containerSource). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the containerSource and deletes it. Returns an error if one occurs. +func (c *containerSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("containersources"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *containerSources) 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("containersources"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched containerSource. +func (c *containerSources) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ContainerSource, err error) { + result = &v1beta1.ContainerSource{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("containersources"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/doc.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/doc.go new file mode 100644 index 00000000000..8d5037f0771 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2021 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 v1beta1 diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/doc.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/doc.go new file mode 100644 index 00000000000..d5a46ef5532 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2021 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/v1beta1/fake/fake_apiserversource.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_apiserversource.go new file mode 100644 index 00000000000..31c87f2e60a --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_apiserversource.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" +) + +// FakeApiServerSources implements ApiServerSourceInterface +type FakeApiServerSources struct { + Fake *FakeSourcesV1beta1 + ns string +} + +var apiserversourcesResource = schema.GroupVersionResource{Group: "sources.knative.dev", Version: "v1beta1", Resource: "apiserversources"} + +var apiserversourcesKind = schema.GroupVersionKind{Group: "sources.knative.dev", Version: "v1beta1", Kind: "ApiServerSource"} + +// Get takes name of the apiServerSource, and returns the corresponding apiServerSource object, and an error if there is any. +func (c *FakeApiServerSources) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.ApiServerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(apiserversourcesResource, c.ns, name), &v1beta1.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ApiServerSource), err +} + +// List takes label and field selectors, and returns the list of ApiServerSources that match those selectors. +func (c *FakeApiServerSources) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ApiServerSourceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(apiserversourcesResource, apiserversourcesKind, c.ns, opts), &v1beta1.ApiServerSourceList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.ApiServerSourceList{ListMeta: obj.(*v1beta1.ApiServerSourceList).ListMeta} + for _, item := range obj.(*v1beta1.ApiServerSourceList).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 apiServerSources. +func (c *FakeApiServerSources) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(apiserversourcesResource, c.ns, opts)) + +} + +// Create takes the representation of a apiServerSource and creates it. Returns the server's representation of the apiServerSource, and an error, if there is any. +func (c *FakeApiServerSources) Create(ctx context.Context, apiServerSource *v1beta1.ApiServerSource, opts v1.CreateOptions) (result *v1beta1.ApiServerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(apiserversourcesResource, c.ns, apiServerSource), &v1beta1.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ApiServerSource), err +} + +// Update takes the representation of a apiServerSource and updates it. Returns the server's representation of the apiServerSource, and an error, if there is any. +func (c *FakeApiServerSources) Update(ctx context.Context, apiServerSource *v1beta1.ApiServerSource, opts v1.UpdateOptions) (result *v1beta1.ApiServerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(apiserversourcesResource, c.ns, apiServerSource), &v1beta1.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ApiServerSource), 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 *FakeApiServerSources) UpdateStatus(ctx context.Context, apiServerSource *v1beta1.ApiServerSource, opts v1.UpdateOptions) (*v1beta1.ApiServerSource, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(apiserversourcesResource, "status", c.ns, apiServerSource), &v1beta1.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ApiServerSource), err +} + +// Delete takes name of the apiServerSource and deletes it. Returns an error if one occurs. +func (c *FakeApiServerSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(apiserversourcesResource, c.ns, name), &v1beta1.ApiServerSource{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeApiServerSources) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(apiserversourcesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.ApiServerSourceList{}) + return err +} + +// Patch applies the patch and returns the patched apiServerSource. +func (c *FakeApiServerSources) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ApiServerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(apiserversourcesResource, c.ns, name, pt, data, subresources...), &v1beta1.ApiServerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ApiServerSource), err +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_containersource.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_containersource.go new file mode 100644 index 00000000000..849e6d365dd --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_containersource.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" +) + +// FakeContainerSources implements ContainerSourceInterface +type FakeContainerSources struct { + Fake *FakeSourcesV1beta1 + ns string +} + +var containersourcesResource = schema.GroupVersionResource{Group: "sources.knative.dev", Version: "v1beta1", Resource: "containersources"} + +var containersourcesKind = schema.GroupVersionKind{Group: "sources.knative.dev", Version: "v1beta1", Kind: "ContainerSource"} + +// Get takes name of the containerSource, and returns the corresponding containerSource object, and an error if there is any. +func (c *FakeContainerSources) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.ContainerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(containersourcesResource, c.ns, name), &v1beta1.ContainerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ContainerSource), err +} + +// List takes label and field selectors, and returns the list of ContainerSources that match those selectors. +func (c *FakeContainerSources) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.ContainerSourceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(containersourcesResource, containersourcesKind, c.ns, opts), &v1beta1.ContainerSourceList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.ContainerSourceList{ListMeta: obj.(*v1beta1.ContainerSourceList).ListMeta} + for _, item := range obj.(*v1beta1.ContainerSourceList).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 containerSources. +func (c *FakeContainerSources) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(containersourcesResource, c.ns, opts)) + +} + +// Create takes the representation of a containerSource and creates it. Returns the server's representation of the containerSource, and an error, if there is any. +func (c *FakeContainerSources) Create(ctx context.Context, containerSource *v1beta1.ContainerSource, opts v1.CreateOptions) (result *v1beta1.ContainerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(containersourcesResource, c.ns, containerSource), &v1beta1.ContainerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ContainerSource), err +} + +// Update takes the representation of a containerSource and updates it. Returns the server's representation of the containerSource, and an error, if there is any. +func (c *FakeContainerSources) Update(ctx context.Context, containerSource *v1beta1.ContainerSource, opts v1.UpdateOptions) (result *v1beta1.ContainerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(containersourcesResource, c.ns, containerSource), &v1beta1.ContainerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ContainerSource), 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 *FakeContainerSources) UpdateStatus(ctx context.Context, containerSource *v1beta1.ContainerSource, opts v1.UpdateOptions) (*v1beta1.ContainerSource, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(containersourcesResource, "status", c.ns, containerSource), &v1beta1.ContainerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ContainerSource), err +} + +// Delete takes name of the containerSource and deletes it. Returns an error if one occurs. +func (c *FakeContainerSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(containersourcesResource, c.ns, name), &v1beta1.ContainerSource{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeContainerSources) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(containersourcesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.ContainerSourceList{}) + return err +} + +// Patch applies the patch and returns the patched containerSource. +func (c *FakeContainerSources) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.ContainerSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(containersourcesResource, c.ns, name, pt, data, subresources...), &v1beta1.ContainerSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ContainerSource), err +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_pingsource.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_pingsource.go new file mode 100644 index 00000000000..30b61c056b1 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_pingsource.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" +) + +// FakePingSources implements PingSourceInterface +type FakePingSources struct { + Fake *FakeSourcesV1beta1 + ns string +} + +var pingsourcesResource = schema.GroupVersionResource{Group: "sources.knative.dev", Version: "v1beta1", Resource: "pingsources"} + +var pingsourcesKind = schema.GroupVersionKind{Group: "sources.knative.dev", Version: "v1beta1", Kind: "PingSource"} + +// Get takes name of the pingSource, and returns the corresponding pingSource object, and an error if there is any. +func (c *FakePingSources) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(pingsourcesResource, c.ns, name), &v1beta1.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PingSource), err +} + +// List takes label and field selectors, and returns the list of PingSources that match those selectors. +func (c *FakePingSources) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.PingSourceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(pingsourcesResource, pingsourcesKind, c.ns, opts), &v1beta1.PingSourceList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.PingSourceList{ListMeta: obj.(*v1beta1.PingSourceList).ListMeta} + for _, item := range obj.(*v1beta1.PingSourceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested pingSources. +func (c *FakePingSources) Watch(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 *v1beta1.PingSource, opts v1.CreateOptions) (result *v1beta1.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(pingsourcesResource, c.ns, pingSource), &v1beta1.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PingSource), err +} + +// Update takes the representation of a pingSource and updates it. Returns the server's representation of the pingSource, and an error, if there is any. +func (c *FakePingSources) Update(ctx context.Context, pingSource *v1beta1.PingSource, opts v1.UpdateOptions) (result *v1beta1.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(pingsourcesResource, c.ns, pingSource), &v1beta1.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PingSource), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakePingSources) UpdateStatus(ctx context.Context, pingSource *v1beta1.PingSource, opts v1.UpdateOptions) (*v1beta1.PingSource, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(pingsourcesResource, "status", c.ns, pingSource), &v1beta1.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PingSource), err +} + +// Delete takes name of the pingSource and deletes it. Returns an error if one occurs. +func (c *FakePingSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(pingsourcesResource, c.ns, name), &v1beta1.PingSource{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakePingSources) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(pingsourcesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.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 *v1beta1.PingSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(pingsourcesResource, c.ns, name, pt, data, subresources...), &v1beta1.PingSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.PingSource), err +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_sinkbinding.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_sinkbinding.go new file mode 100644 index 00000000000..3c52de76131 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_sinkbinding.go @@ -0,0 +1,142 @@ +/* +Copyright 2021 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" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" +) + +// FakeSinkBindings implements SinkBindingInterface +type FakeSinkBindings struct { + Fake *FakeSourcesV1beta1 + ns string +} + +var sinkbindingsResource = schema.GroupVersionResource{Group: "sources.knative.dev", Version: "v1beta1", Resource: "sinkbindings"} + +var sinkbindingsKind = schema.GroupVersionKind{Group: "sources.knative.dev", Version: "v1beta1", Kind: "SinkBinding"} + +// Get takes name of the sinkBinding, and returns the corresponding sinkBinding object, and an error if there is any. +func (c *FakeSinkBindings) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.SinkBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(sinkbindingsResource, c.ns, name), &v1beta1.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.SinkBinding), err +} + +// List takes label and field selectors, and returns the list of SinkBindings that match those selectors. +func (c *FakeSinkBindings) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.SinkBindingList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(sinkbindingsResource, sinkbindingsKind, c.ns, opts), &v1beta1.SinkBindingList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.SinkBindingList{ListMeta: obj.(*v1beta1.SinkBindingList).ListMeta} + for _, item := range obj.(*v1beta1.SinkBindingList).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 sinkBindings. +func (c *FakeSinkBindings) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(sinkbindingsResource, c.ns, opts)) + +} + +// Create takes the representation of a sinkBinding and creates it. Returns the server's representation of the sinkBinding, and an error, if there is any. +func (c *FakeSinkBindings) Create(ctx context.Context, sinkBinding *v1beta1.SinkBinding, opts v1.CreateOptions) (result *v1beta1.SinkBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(sinkbindingsResource, c.ns, sinkBinding), &v1beta1.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.SinkBinding), err +} + +// Update takes the representation of a sinkBinding and updates it. Returns the server's representation of the sinkBinding, and an error, if there is any. +func (c *FakeSinkBindings) Update(ctx context.Context, sinkBinding *v1beta1.SinkBinding, opts v1.UpdateOptions) (result *v1beta1.SinkBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(sinkbindingsResource, c.ns, sinkBinding), &v1beta1.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.SinkBinding), 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 *FakeSinkBindings) UpdateStatus(ctx context.Context, sinkBinding *v1beta1.SinkBinding, opts v1.UpdateOptions) (*v1beta1.SinkBinding, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(sinkbindingsResource, "status", c.ns, sinkBinding), &v1beta1.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.SinkBinding), err +} + +// Delete takes name of the sinkBinding and deletes it. Returns an error if one occurs. +func (c *FakeSinkBindings) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(sinkbindingsResource, c.ns, name), &v1beta1.SinkBinding{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeSinkBindings) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(sinkbindingsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.SinkBindingList{}) + return err +} + +// Patch applies the patch and returns the patched sinkBinding. +func (c *FakeSinkBindings) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.SinkBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(sinkbindingsResource, c.ns, name, pt, data, subresources...), &v1beta1.SinkBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.SinkBinding), err +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_sources_client.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_sources_client.go new file mode 100644 index 00000000000..54e6b361917 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/fake/fake_sources_client.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + v1beta1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1beta1" +) + +type FakeSourcesV1beta1 struct { + *testing.Fake +} + +func (c *FakeSourcesV1beta1) ApiServerSources(namespace string) v1beta1.ApiServerSourceInterface { + return &FakeApiServerSources{c, namespace} +} + +func (c *FakeSourcesV1beta1) ContainerSources(namespace string) v1beta1.ContainerSourceInterface { + return &FakeContainerSources{c, namespace} +} + +func (c *FakeSourcesV1beta1) PingSources(namespace string) v1beta1.PingSourceInterface { + return &FakePingSources{c, namespace} +} + +func (c *FakeSourcesV1beta1) SinkBindings(namespace string) v1beta1.SinkBindingInterface { + return &FakeSinkBindings{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeSourcesV1beta1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/generated_expansion.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/generated_expansion.go new file mode 100644 index 00000000000..10c4bf7ac00 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/generated_expansion.go @@ -0,0 +1,27 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +type ApiServerSourceExpansion interface{} + +type ContainerSourceExpansion interface{} + +type PingSourceExpansion interface{} + +type SinkBindingExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/pingsource.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/pingsource.go new file mode 100644 index 00000000000..f11e78af17b --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/pingsource.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// PingSourcesGetter has a method to return a PingSourceInterface. +// A group's client should implement this interface. +type PingSourcesGetter interface { + PingSources(namespace string) PingSourceInterface +} + +// PingSourceInterface has methods to work with PingSource resources. +type PingSourceInterface interface { + Create(ctx context.Context, pingSource *v1beta1.PingSource, opts v1.CreateOptions) (*v1beta1.PingSource, error) + Update(ctx context.Context, pingSource *v1beta1.PingSource, opts v1.UpdateOptions) (*v1beta1.PingSource, error) + UpdateStatus(ctx context.Context, pingSource *v1beta1.PingSource, opts v1.UpdateOptions) (*v1beta1.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) (*v1beta1.PingSource, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta1.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 *v1beta1.PingSource, err error) + PingSourceExpansion +} + +// pingSources implements PingSourceInterface +type pingSources struct { + client rest.Interface + ns string +} + +// newPingSources returns a PingSources +func newPingSources(c *SourcesV1beta1Client, namespace string) *pingSources { + return &pingSources{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the pingSource, and returns the corresponding pingSource object, and an error if there is any. +func (c *pingSources) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.PingSource, err error) { + result = &v1beta1.PingSource{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pingsources"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(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 *v1beta1.PingSourceList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta1.PingSourceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pingsources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(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 *v1beta1.PingSource, opts v1.CreateOptions) (result *v1beta1.PingSource, err error) { + result = &v1beta1.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 *v1beta1.PingSource, opts v1.UpdateOptions) (result *v1beta1.PingSource, err error) { + result = &v1beta1.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 *v1beta1.PingSource, opts v1.UpdateOptions) (result *v1beta1.PingSource, err error) { + result = &v1beta1.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 *v1beta1.PingSource, err error) { + result = &v1beta1.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/v1beta1/sinkbinding.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/sinkbinding.go new file mode 100644 index 00000000000..6384caf0dac --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/sinkbinding.go @@ -0,0 +1,195 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + scheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +// SinkBindingsGetter has a method to return a SinkBindingInterface. +// A group's client should implement this interface. +type SinkBindingsGetter interface { + SinkBindings(namespace string) SinkBindingInterface +} + +// SinkBindingInterface has methods to work with SinkBinding resources. +type SinkBindingInterface interface { + Create(ctx context.Context, sinkBinding *v1beta1.SinkBinding, opts v1.CreateOptions) (*v1beta1.SinkBinding, error) + Update(ctx context.Context, sinkBinding *v1beta1.SinkBinding, opts v1.UpdateOptions) (*v1beta1.SinkBinding, error) + UpdateStatus(ctx context.Context, sinkBinding *v1beta1.SinkBinding, opts v1.UpdateOptions) (*v1beta1.SinkBinding, 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) (*v1beta1.SinkBinding, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta1.SinkBindingList, 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 *v1beta1.SinkBinding, err error) + SinkBindingExpansion +} + +// sinkBindings implements SinkBindingInterface +type sinkBindings struct { + client rest.Interface + ns string +} + +// newSinkBindings returns a SinkBindings +func newSinkBindings(c *SourcesV1beta1Client, namespace string) *sinkBindings { + return &sinkBindings{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the sinkBinding, and returns the corresponding sinkBinding object, and an error if there is any. +func (c *sinkBindings) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.SinkBinding, err error) { + result = &v1beta1.SinkBinding{} + err = c.client.Get(). + Namespace(c.ns). + Resource("sinkbindings"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of SinkBindings that match those selectors. +func (c *sinkBindings) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.SinkBindingList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta1.SinkBindingList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("sinkbindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested sinkBindings. +func (c *sinkBindings) 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("sinkbindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a sinkBinding and creates it. Returns the server's representation of the sinkBinding, and an error, if there is any. +func (c *sinkBindings) Create(ctx context.Context, sinkBinding *v1beta1.SinkBinding, opts v1.CreateOptions) (result *v1beta1.SinkBinding, err error) { + result = &v1beta1.SinkBinding{} + err = c.client.Post(). + Namespace(c.ns). + Resource("sinkbindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(sinkBinding). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a sinkBinding and updates it. Returns the server's representation of the sinkBinding, and an error, if there is any. +func (c *sinkBindings) Update(ctx context.Context, sinkBinding *v1beta1.SinkBinding, opts v1.UpdateOptions) (result *v1beta1.SinkBinding, err error) { + result = &v1beta1.SinkBinding{} + err = c.client.Put(). + Namespace(c.ns). + Resource("sinkbindings"). + Name(sinkBinding.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(sinkBinding). + 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 *sinkBindings) UpdateStatus(ctx context.Context, sinkBinding *v1beta1.SinkBinding, opts v1.UpdateOptions) (result *v1beta1.SinkBinding, err error) { + result = &v1beta1.SinkBinding{} + err = c.client.Put(). + Namespace(c.ns). + Resource("sinkbindings"). + Name(sinkBinding.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(sinkBinding). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the sinkBinding and deletes it. Returns an error if one occurs. +func (c *sinkBindings) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("sinkbindings"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *sinkBindings) 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("sinkbindings"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched sinkBinding. +func (c *sinkBindings) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.SinkBinding, err error) { + result = &v1beta1.SinkBinding{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("sinkbindings"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/sources/v1beta1/sources_client.go b/pkg/client/clientset/versioned/typed/sources/v1beta1/sources_client.go new file mode 100644 index 00000000000..1b2a8cc83b2 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1beta1/sources_client.go @@ -0,0 +1,104 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + rest "k8s.io/client-go/rest" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + "knative.dev/eventing/pkg/client/clientset/versioned/scheme" +) + +type SourcesV1beta1Interface interface { + RESTClient() rest.Interface + ApiServerSourcesGetter + ContainerSourcesGetter + PingSourcesGetter + SinkBindingsGetter +} + +// SourcesV1beta1Client is used to interact with features provided by the sources.knative.dev group. +type SourcesV1beta1Client struct { + restClient rest.Interface +} + +func (c *SourcesV1beta1Client) ApiServerSources(namespace string) ApiServerSourceInterface { + return newApiServerSources(c, namespace) +} + +func (c *SourcesV1beta1Client) ContainerSources(namespace string) ContainerSourceInterface { + return newContainerSources(c, namespace) +} + +func (c *SourcesV1beta1Client) PingSources(namespace string) PingSourceInterface { + return newPingSources(c, namespace) +} + +func (c *SourcesV1beta1Client) SinkBindings(namespace string) SinkBindingInterface { + return newSinkBindings(c, namespace) +} + +// NewForConfig creates a new SourcesV1beta1Client for the given config. +func NewForConfig(c *rest.Config) (*SourcesV1beta1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &SourcesV1beta1Client{client}, nil +} + +// NewForConfigOrDie creates a new SourcesV1beta1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *SourcesV1beta1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new SourcesV1beta1Client for the given RESTClient. +func New(c rest.Interface) *SourcesV1beta1Client { + return &SourcesV1beta1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1beta1.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 *SourcesV1beta1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/informers/externalversions/eventing/v1beta1/broker.go b/pkg/client/informers/externalversions/eventing/v1beta1/broker.go new file mode 100644 index 00000000000..026445e056c --- /dev/null +++ b/pkg/client/informers/externalversions/eventing/v1beta1/broker.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1beta1 "knative.dev/eventing/pkg/client/listers/eventing/v1beta1" +) + +// BrokerInformer provides access to a shared informer and lister for +// Brokers. +type BrokerInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta1.BrokerLister +} + +type brokerInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewBrokerInformer constructs a new informer for Broker 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 NewBrokerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredBrokerInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredBrokerInformer constructs a new informer for Broker 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 NewFilteredBrokerInformer(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.EventingV1beta1().Brokers(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.EventingV1beta1().Brokers(namespace).Watch(context.TODO(), options) + }, + }, + &eventingv1beta1.Broker{}, + resyncPeriod, + indexers, + ) +} + +func (f *brokerInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredBrokerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *brokerInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&eventingv1beta1.Broker{}, f.defaultInformer) +} + +func (f *brokerInformer) Lister() v1beta1.BrokerLister { + return v1beta1.NewBrokerLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/eventing/v1beta1/interface.go b/pkg/client/informers/externalversions/eventing/v1beta1/interface.go index 95aa7ac8f57..80b6b4fdef8 100644 --- a/pkg/client/informers/externalversions/eventing/v1beta1/interface.go +++ b/pkg/client/informers/externalversions/eventing/v1beta1/interface.go @@ -24,8 +24,12 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { + // Brokers returns a BrokerInformer. + Brokers() BrokerInformer // EventTypes returns a EventTypeInformer. EventTypes() EventTypeInformer + // Triggers returns a TriggerInformer. + Triggers() TriggerInformer } type version struct { @@ -39,7 +43,17 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// Brokers returns a BrokerInformer. +func (v *version) Brokers() BrokerInformer { + return &brokerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // EventTypes returns a EventTypeInformer. func (v *version) EventTypes() EventTypeInformer { return &eventTypeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } + +// Triggers returns a TriggerInformer. +func (v *version) Triggers() TriggerInformer { + return &triggerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/eventing/v1beta1/trigger.go b/pkg/client/informers/externalversions/eventing/v1beta1/trigger.go new file mode 100644 index 00000000000..43c77d1f8b7 --- /dev/null +++ b/pkg/client/informers/externalversions/eventing/v1beta1/trigger.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1beta1 "knative.dev/eventing/pkg/client/listers/eventing/v1beta1" +) + +// TriggerInformer provides access to a shared informer and lister for +// Triggers. +type TriggerInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta1.TriggerLister +} + +type triggerInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewTriggerInformer constructs a new informer for Trigger 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 NewTriggerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredTriggerInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredTriggerInformer constructs a new informer for Trigger 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 NewFilteredTriggerInformer(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.EventingV1beta1().Triggers(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.EventingV1beta1().Triggers(namespace).Watch(context.TODO(), options) + }, + }, + &eventingv1beta1.Trigger{}, + resyncPeriod, + indexers, + ) +} + +func (f *triggerInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredTriggerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *triggerInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&eventingv1beta1.Trigger{}, f.defaultInformer) +} + +func (f *triggerInformer) Lister() v1beta1.TriggerLister { + return v1beta1.NewTriggerLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/flows/interface.go b/pkg/client/informers/externalversions/flows/interface.go index 14464f3fd94..d395edcbb9f 100644 --- a/pkg/client/informers/externalversions/flows/interface.go +++ b/pkg/client/informers/externalversions/flows/interface.go @@ -20,11 +20,14 @@ package flows import ( v1 "knative.dev/eventing/pkg/client/informers/externalversions/flows/v1" + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/flows/v1beta1" internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" ) // Interface provides access to each of this group's versions. type Interface interface { + // V1beta1 provides access to shared informers for resources in V1beta1. + V1beta1() v1beta1.Interface // V1 provides access to shared informers for resources in V1. V1() v1.Interface } @@ -40,6 +43,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// V1beta1 returns a new v1beta1.Interface. +func (g *group) V1beta1() v1beta1.Interface { + return v1beta1.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/flows/v1beta1/interface.go b/pkg/client/informers/externalversions/flows/v1beta1/interface.go new file mode 100644 index 00000000000..eaf6258f55b --- /dev/null +++ b/pkg/client/informers/externalversions/flows/v1beta1/interface.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // Parallels returns a ParallelInformer. + Parallels() ParallelInformer + // Sequences returns a SequenceInformer. + Sequences() SequenceInformer +} + +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} +} + +// Parallels returns a ParallelInformer. +func (v *version) Parallels() ParallelInformer { + return ¶llelInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// Sequences returns a SequenceInformer. +func (v *version) Sequences() SequenceInformer { + return &sequenceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/flows/v1beta1/parallel.go b/pkg/client/informers/externalversions/flows/v1beta1/parallel.go new file mode 100644 index 00000000000..bd2fee6a3d9 --- /dev/null +++ b/pkg/client/informers/externalversions/flows/v1beta1/parallel.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + flowsv1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1beta1 "knative.dev/eventing/pkg/client/listers/flows/v1beta1" +) + +// ParallelInformer provides access to a shared informer and lister for +// Parallels. +type ParallelInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta1.ParallelLister +} + +type parallelInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewParallelInformer constructs a new informer for Parallel 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 NewParallelInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredParallelInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredParallelInformer constructs a new informer for Parallel 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 NewFilteredParallelInformer(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.FlowsV1beta1().Parallels(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.FlowsV1beta1().Parallels(namespace).Watch(context.TODO(), options) + }, + }, + &flowsv1beta1.Parallel{}, + resyncPeriod, + indexers, + ) +} + +func (f *parallelInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredParallelInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *parallelInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&flowsv1beta1.Parallel{}, f.defaultInformer) +} + +func (f *parallelInformer) Lister() v1beta1.ParallelLister { + return v1beta1.NewParallelLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/flows/v1beta1/sequence.go b/pkg/client/informers/externalversions/flows/v1beta1/sequence.go new file mode 100644 index 00000000000..b61393b68eb --- /dev/null +++ b/pkg/client/informers/externalversions/flows/v1beta1/sequence.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + flowsv1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1beta1 "knative.dev/eventing/pkg/client/listers/flows/v1beta1" +) + +// SequenceInformer provides access to a shared informer and lister for +// Sequences. +type SequenceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta1.SequenceLister +} + +type sequenceInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewSequenceInformer constructs a new informer for Sequence 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 NewSequenceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredSequenceInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredSequenceInformer constructs a new informer for Sequence 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 NewFilteredSequenceInformer(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.FlowsV1beta1().Sequences(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.FlowsV1beta1().Sequences(namespace).Watch(context.TODO(), options) + }, + }, + &flowsv1beta1.Sequence{}, + resyncPeriod, + indexers, + ) +} + +func (f *sequenceInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredSequenceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *sequenceInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&flowsv1beta1.Sequence{}, f.defaultInformer) +} + +func (f *sequenceInformer) Lister() v1beta1.SequenceLister { + return v1beta1.NewSequenceLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 94b41212161..453553ee5f3 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -26,8 +26,13 @@ import ( v1 "knative.dev/eventing/pkg/apis/eventing/v1" v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" flowsv1 "knative.dev/eventing/pkg/apis/flows/v1" + flowsv1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + v1alpha1 "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" ) @@ -64,8 +69,12 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Eventing().V1().Triggers().Informer()}, nil // Group=eventing.knative.dev, Version=v1beta1 + case v1beta1.SchemeGroupVersion.WithResource("brokers"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Eventing().V1beta1().Brokers().Informer()}, nil case v1beta1.SchemeGroupVersion.WithResource("eventtypes"): return &genericInformer{resource: resource.GroupResource(), informer: f.Eventing().V1beta1().EventTypes().Informer()}, nil + case v1beta1.SchemeGroupVersion.WithResource("triggers"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Eventing().V1beta1().Triggers().Informer()}, nil // Group=flows.knative.dev, Version=v1 case flowsv1.SchemeGroupVersion.WithResource("parallels"): @@ -73,6 +82,12 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case flowsv1.SchemeGroupVersion.WithResource("sequences"): return &genericInformer{resource: resource.GroupResource(), informer: f.Flows().V1().Sequences().Informer()}, nil + // Group=flows.knative.dev, Version=v1beta1 + case flowsv1beta1.SchemeGroupVersion.WithResource("parallels"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Flows().V1beta1().Parallels().Informer()}, nil + case flowsv1beta1.SchemeGroupVersion.WithResource("sequences"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Flows().V1beta1().Sequences().Informer()}, nil + // Group=messaging.knative.dev, Version=v1 case messagingv1.SchemeGroupVersion.WithResource("channels"): return &genericInformer{resource: resource.GroupResource(), informer: f.Messaging().V1().Channels().Informer()}, nil @@ -81,6 +96,14 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case messagingv1.SchemeGroupVersion.WithResource("subscriptions"): return &genericInformer{resource: resource.GroupResource(), informer: f.Messaging().V1().Subscriptions().Informer()}, nil + // Group=messaging.knative.dev, Version=v1beta1 + case messagingv1beta1.SchemeGroupVersion.WithResource("channels"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Messaging().V1beta1().Channels().Informer()}, nil + case messagingv1beta1.SchemeGroupVersion.WithResource("inmemorychannels"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Messaging().V1beta1().InMemoryChannels().Informer()}, nil + case messagingv1beta1.SchemeGroupVersion.WithResource("subscriptions"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Messaging().V1beta1().Subscriptions().Informer()}, nil + // Group=sources.knative.dev, Version=v1 case sourcesv1.SchemeGroupVersion.WithResource("apiserversources"): return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1().ApiServerSources().Informer()}, nil @@ -91,6 +114,32 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case sourcesv1.SchemeGroupVersion.WithResource("sinkbindings"): return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1().SinkBindings().Informer()}, nil + // Group=sources.knative.dev, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("apiserversources"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1alpha1().ApiServerSources().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("sinkbindings"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1alpha1().SinkBindings().Informer()}, nil + + // Group=sources.knative.dev, Version=v1alpha2 + case v1alpha2.SchemeGroupVersion.WithResource("apiserversources"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1alpha2().ApiServerSources().Informer()}, nil + case v1alpha2.SchemeGroupVersion.WithResource("containersources"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1alpha2().ContainerSources().Informer()}, nil + case v1alpha2.SchemeGroupVersion.WithResource("pingsources"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1alpha2().PingSources().Informer()}, nil + case v1alpha2.SchemeGroupVersion.WithResource("sinkbindings"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1alpha2().SinkBindings().Informer()}, nil + + // Group=sources.knative.dev, Version=v1beta1 + case sourcesv1beta1.SchemeGroupVersion.WithResource("apiserversources"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1beta1().ApiServerSources().Informer()}, nil + case sourcesv1beta1.SchemeGroupVersion.WithResource("containersources"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1beta1().ContainerSources().Informer()}, nil + case sourcesv1beta1.SchemeGroupVersion.WithResource("pingsources"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1beta1().PingSources().Informer()}, nil + case sourcesv1beta1.SchemeGroupVersion.WithResource("sinkbindings"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1beta1().SinkBindings().Informer()}, nil + // Group=sources.knative.dev, Version=v1beta2 case v1beta2.SchemeGroupVersion.WithResource("pingsources"): return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1beta2().PingSources().Informer()}, nil diff --git a/pkg/client/informers/externalversions/messaging/interface.go b/pkg/client/informers/externalversions/messaging/interface.go index e8d25975dfc..743d5bc77f9 100644 --- a/pkg/client/informers/externalversions/messaging/interface.go +++ b/pkg/client/informers/externalversions/messaging/interface.go @@ -21,10 +21,13 @@ package messaging import ( internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" v1 "knative.dev/eventing/pkg/client/informers/externalversions/messaging/v1" + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/messaging/v1beta1" ) // Interface provides access to each of this group's versions. type Interface interface { + // V1beta1 provides access to shared informers for resources in V1beta1. + V1beta1() v1beta1.Interface // V1 provides access to shared informers for resources in V1. V1() v1.Interface } @@ -40,6 +43,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// V1beta1 returns a new v1beta1.Interface. +func (g *group) V1beta1() v1beta1.Interface { + return v1beta1.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/messaging/v1beta1/channel.go b/pkg/client/informers/externalversions/messaging/v1beta1/channel.go new file mode 100644 index 00000000000..b350ac10a10 --- /dev/null +++ b/pkg/client/informers/externalversions/messaging/v1beta1/channel.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1beta1 "knative.dev/eventing/pkg/client/listers/messaging/v1beta1" +) + +// ChannelInformer provides access to a shared informer and lister for +// Channels. +type ChannelInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta1.ChannelLister +} + +type channelInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewChannelInformer constructs a new informer for Channel 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 NewChannelInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredChannelInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredChannelInformer constructs a new informer for Channel 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 NewFilteredChannelInformer(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.MessagingV1beta1().Channels(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.MessagingV1beta1().Channels(namespace).Watch(context.TODO(), options) + }, + }, + &messagingv1beta1.Channel{}, + resyncPeriod, + indexers, + ) +} + +func (f *channelInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredChannelInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *channelInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&messagingv1beta1.Channel{}, f.defaultInformer) +} + +func (f *channelInformer) Lister() v1beta1.ChannelLister { + return v1beta1.NewChannelLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/messaging/v1beta1/inmemorychannel.go b/pkg/client/informers/externalversions/messaging/v1beta1/inmemorychannel.go new file mode 100644 index 00000000000..292c6f204f1 --- /dev/null +++ b/pkg/client/informers/externalversions/messaging/v1beta1/inmemorychannel.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1beta1 "knative.dev/eventing/pkg/client/listers/messaging/v1beta1" +) + +// InMemoryChannelInformer provides access to a shared informer and lister for +// InMemoryChannels. +type InMemoryChannelInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta1.InMemoryChannelLister +} + +type inMemoryChannelInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewInMemoryChannelInformer constructs a new informer for InMemoryChannel 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 NewInMemoryChannelInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredInMemoryChannelInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredInMemoryChannelInformer constructs a new informer for InMemoryChannel 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 NewFilteredInMemoryChannelInformer(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.MessagingV1beta1().InMemoryChannels(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.MessagingV1beta1().InMemoryChannels(namespace).Watch(context.TODO(), options) + }, + }, + &messagingv1beta1.InMemoryChannel{}, + resyncPeriod, + indexers, + ) +} + +func (f *inMemoryChannelInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredInMemoryChannelInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *inMemoryChannelInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&messagingv1beta1.InMemoryChannel{}, f.defaultInformer) +} + +func (f *inMemoryChannelInformer) Lister() v1beta1.InMemoryChannelLister { + return v1beta1.NewInMemoryChannelLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/messaging/v1beta1/interface.go b/pkg/client/informers/externalversions/messaging/v1beta1/interface.go new file mode 100644 index 00000000000..7dbe7ccfd62 --- /dev/null +++ b/pkg/client/informers/externalversions/messaging/v1beta1/interface.go @@ -0,0 +1,59 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // Channels returns a ChannelInformer. + Channels() ChannelInformer + // InMemoryChannels returns a InMemoryChannelInformer. + InMemoryChannels() InMemoryChannelInformer + // Subscriptions returns a SubscriptionInformer. + Subscriptions() SubscriptionInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// Channels returns a ChannelInformer. +func (v *version) Channels() ChannelInformer { + return &channelInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// InMemoryChannels returns a InMemoryChannelInformer. +func (v *version) InMemoryChannels() InMemoryChannelInformer { + return &inMemoryChannelInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// Subscriptions returns a SubscriptionInformer. +func (v *version) Subscriptions() SubscriptionInformer { + return &subscriptionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/messaging/v1beta1/subscription.go b/pkg/client/informers/externalversions/messaging/v1beta1/subscription.go new file mode 100644 index 00000000000..51b2f3ea058 --- /dev/null +++ b/pkg/client/informers/externalversions/messaging/v1beta1/subscription.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + messagingv1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1beta1 "knative.dev/eventing/pkg/client/listers/messaging/v1beta1" +) + +// SubscriptionInformer provides access to a shared informer and lister for +// Subscriptions. +type SubscriptionInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta1.SubscriptionLister +} + +type subscriptionInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewSubscriptionInformer constructs a new informer for Subscription type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewSubscriptionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredSubscriptionInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredSubscriptionInformer constructs a new informer for Subscription type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredSubscriptionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.MessagingV1beta1().Subscriptions(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.MessagingV1beta1().Subscriptions(namespace).Watch(context.TODO(), options) + }, + }, + &messagingv1beta1.Subscription{}, + resyncPeriod, + indexers, + ) +} + +func (f *subscriptionInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredSubscriptionInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *subscriptionInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&messagingv1beta1.Subscription{}, f.defaultInformer) +} + +func (f *subscriptionInformer) Lister() v1beta1.SubscriptionLister { + return v1beta1.NewSubscriptionLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/sources/interface.go b/pkg/client/informers/externalversions/sources/interface.go index 4e96b791b62..7608c5d167f 100644 --- a/pkg/client/informers/externalversions/sources/interface.go +++ b/pkg/client/informers/externalversions/sources/interface.go @@ -21,11 +21,20 @@ package sources import ( internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" v1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1" + 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. type Interface interface { + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() v1alpha1.Interface + // V1alpha2 provides access to shared informers for resources in V1alpha2. + 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. @@ -43,6 +52,21 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// V1alpha1 returns a new v1alpha1.Interface. +func (g *group) V1alpha1() v1alpha1.Interface { + return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) +} + +// V1alpha2 returns a new v1alpha2.Interface. +func (g *group) V1alpha2() v1alpha2.Interface { + return v1alpha2.New(g.factory, g.namespace, g.tweakListOptions) +} + +// V1beta1 returns a new v1beta1.Interface. +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) diff --git a/pkg/client/informers/externalversions/sources/v1alpha1/apiserversource.go b/pkg/client/informers/externalversions/sources/v1alpha1/apiserversource.go new file mode 100644 index 00000000000..9ddb73a95a8 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1alpha1/apiserversource.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "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" + sourcesv1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "knative.dev/eventing/pkg/client/listers/sources/v1alpha1" +) + +// ApiServerSourceInformer provides access to a shared informer and lister for +// ApiServerSources. +type ApiServerSourceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.ApiServerSourceLister +} + +type apiServerSourceInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewApiServerSourceInformer constructs a new informer for ApiServerSource 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 NewApiServerSourceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredApiServerSourceInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredApiServerSourceInformer constructs a new informer for ApiServerSource 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 NewFilteredApiServerSourceInformer(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.SourcesV1alpha1().ApiServerSources(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1alpha1().ApiServerSources(namespace).Watch(context.TODO(), options) + }, + }, + &sourcesv1alpha1.ApiServerSource{}, + resyncPeriod, + indexers, + ) +} + +func (f *apiServerSourceInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredApiServerSourceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *apiServerSourceInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&sourcesv1alpha1.ApiServerSource{}, f.defaultInformer) +} + +func (f *apiServerSourceInformer) Lister() v1alpha1.ApiServerSourceLister { + return v1alpha1.NewApiServerSourceLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/sources/v1alpha1/interface.go b/pkg/client/informers/externalversions/sources/v1alpha1/interface.go new file mode 100644 index 00000000000..ee9accd8f50 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1alpha1/interface.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // ApiServerSources returns a ApiServerSourceInformer. + ApiServerSources() ApiServerSourceInformer + // SinkBindings returns a SinkBindingInformer. + SinkBindings() SinkBindingInformer +} + +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} +} + +// ApiServerSources returns a ApiServerSourceInformer. +func (v *version) ApiServerSources() ApiServerSourceInformer { + return &apiServerSourceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// SinkBindings returns a SinkBindingInformer. +func (v *version) SinkBindings() SinkBindingInformer { + return &sinkBindingInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/sources/v1alpha1/sinkbinding.go b/pkg/client/informers/externalversions/sources/v1alpha1/sinkbinding.go new file mode 100644 index 00000000000..bda0da9a662 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1alpha1/sinkbinding.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "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" + sourcesv1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "knative.dev/eventing/pkg/client/listers/sources/v1alpha1" +) + +// SinkBindingInformer provides access to a shared informer and lister for +// SinkBindings. +type SinkBindingInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.SinkBindingLister +} + +type sinkBindingInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewSinkBindingInformer constructs a new informer for SinkBinding 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 NewSinkBindingInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredSinkBindingInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredSinkBindingInformer constructs a new informer for SinkBinding 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 NewFilteredSinkBindingInformer(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.SourcesV1alpha1().SinkBindings(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1alpha1().SinkBindings(namespace).Watch(context.TODO(), options) + }, + }, + &sourcesv1alpha1.SinkBinding{}, + resyncPeriod, + indexers, + ) +} + +func (f *sinkBindingInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredSinkBindingInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *sinkBindingInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&sourcesv1alpha1.SinkBinding{}, f.defaultInformer) +} + +func (f *sinkBindingInformer) Lister() v1alpha1.SinkBindingLister { + return v1alpha1.NewSinkBindingLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/sources/v1alpha2/apiserversource.go b/pkg/client/informers/externalversions/sources/v1alpha2/apiserversource.go new file mode 100644 index 00000000000..c3370344767 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1alpha2/apiserversource.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 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 v1alpha2 + +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" + sourcesv1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1alpha2 "knative.dev/eventing/pkg/client/listers/sources/v1alpha2" +) + +// ApiServerSourceInformer provides access to a shared informer and lister for +// ApiServerSources. +type ApiServerSourceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha2.ApiServerSourceLister +} + +type apiServerSourceInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewApiServerSourceInformer constructs a new informer for ApiServerSource 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 NewApiServerSourceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredApiServerSourceInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredApiServerSourceInformer constructs a new informer for ApiServerSource 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 NewFilteredApiServerSourceInformer(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.SourcesV1alpha2().ApiServerSources(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1alpha2().ApiServerSources(namespace).Watch(context.TODO(), options) + }, + }, + &sourcesv1alpha2.ApiServerSource{}, + resyncPeriod, + indexers, + ) +} + +func (f *apiServerSourceInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredApiServerSourceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *apiServerSourceInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&sourcesv1alpha2.ApiServerSource{}, f.defaultInformer) +} + +func (f *apiServerSourceInformer) Lister() v1alpha2.ApiServerSourceLister { + return v1alpha2.NewApiServerSourceLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/sources/v1alpha2/containersource.go b/pkg/client/informers/externalversions/sources/v1alpha2/containersource.go new file mode 100644 index 00000000000..cfbc4d690e2 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1alpha2/containersource.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 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 v1alpha2 + +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" + sourcesv1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1alpha2 "knative.dev/eventing/pkg/client/listers/sources/v1alpha2" +) + +// ContainerSourceInformer provides access to a shared informer and lister for +// ContainerSources. +type ContainerSourceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha2.ContainerSourceLister +} + +type containerSourceInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewContainerSourceInformer constructs a new informer for ContainerSource 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 NewContainerSourceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredContainerSourceInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredContainerSourceInformer constructs a new informer for ContainerSource 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 NewFilteredContainerSourceInformer(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.SourcesV1alpha2().ContainerSources(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1alpha2().ContainerSources(namespace).Watch(context.TODO(), options) + }, + }, + &sourcesv1alpha2.ContainerSource{}, + resyncPeriod, + indexers, + ) +} + +func (f *containerSourceInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredContainerSourceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *containerSourceInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&sourcesv1alpha2.ContainerSource{}, f.defaultInformer) +} + +func (f *containerSourceInformer) Lister() v1alpha2.ContainerSourceLister { + return v1alpha2.NewContainerSourceLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/sources/v1alpha2/interface.go b/pkg/client/informers/externalversions/sources/v1alpha2/interface.go new file mode 100644 index 00000000000..5cb1f492bd6 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1alpha2/interface.go @@ -0,0 +1,66 @@ +/* +Copyright 2021 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 v1alpha2 + +import ( + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // ApiServerSources returns a ApiServerSourceInformer. + ApiServerSources() ApiServerSourceInformer + // ContainerSources returns a ContainerSourceInformer. + ContainerSources() ContainerSourceInformer + // PingSources returns a PingSourceInformer. + PingSources() PingSourceInformer + // SinkBindings returns a SinkBindingInformer. + SinkBindings() SinkBindingInformer +} + +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} +} + +// ApiServerSources returns a ApiServerSourceInformer. +func (v *version) ApiServerSources() ApiServerSourceInformer { + return &apiServerSourceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// ContainerSources returns a ContainerSourceInformer. +func (v *version) ContainerSources() ContainerSourceInformer { + return &containerSourceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// PingSources returns a PingSourceInformer. +func (v *version) PingSources() PingSourceInformer { + return &pingSourceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// SinkBindings returns a SinkBindingInformer. +func (v *version) SinkBindings() SinkBindingInformer { + return &sinkBindingInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/sources/v1alpha2/pingsource.go b/pkg/client/informers/externalversions/sources/v1alpha2/pingsource.go new file mode 100644 index 00000000000..4694afd0778 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1alpha2/pingsource.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 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 v1alpha2 + +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" + sourcesv1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1alpha2 "knative.dev/eventing/pkg/client/listers/sources/v1alpha2" +) + +// PingSourceInformer provides access to a shared informer and lister for +// PingSources. +type PingSourceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha2.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.SourcesV1alpha2().PingSources(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1alpha2().PingSources(namespace).Watch(context.TODO(), options) + }, + }, + &sourcesv1alpha2.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(&sourcesv1alpha2.PingSource{}, f.defaultInformer) +} + +func (f *pingSourceInformer) Lister() v1alpha2.PingSourceLister { + return v1alpha2.NewPingSourceLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/sources/v1alpha2/sinkbinding.go b/pkg/client/informers/externalversions/sources/v1alpha2/sinkbinding.go new file mode 100644 index 00000000000..f7f050aec6a --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1alpha2/sinkbinding.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 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 v1alpha2 + +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" + sourcesv1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1alpha2 "knative.dev/eventing/pkg/client/listers/sources/v1alpha2" +) + +// SinkBindingInformer provides access to a shared informer and lister for +// SinkBindings. +type SinkBindingInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha2.SinkBindingLister +} + +type sinkBindingInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewSinkBindingInformer constructs a new informer for SinkBinding 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 NewSinkBindingInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredSinkBindingInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredSinkBindingInformer constructs a new informer for SinkBinding 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 NewFilteredSinkBindingInformer(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.SourcesV1alpha2().SinkBindings(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1alpha2().SinkBindings(namespace).Watch(context.TODO(), options) + }, + }, + &sourcesv1alpha2.SinkBinding{}, + resyncPeriod, + indexers, + ) +} + +func (f *sinkBindingInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredSinkBindingInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *sinkBindingInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&sourcesv1alpha2.SinkBinding{}, f.defaultInformer) +} + +func (f *sinkBindingInformer) Lister() v1alpha2.SinkBindingLister { + return v1alpha2.NewSinkBindingLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/sources/v1beta1/apiserversource.go b/pkg/client/informers/externalversions/sources/v1beta1/apiserversource.go new file mode 100644 index 00000000000..341dda3984a --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1beta1/apiserversource.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1beta1 "knative.dev/eventing/pkg/client/listers/sources/v1beta1" +) + +// ApiServerSourceInformer provides access to a shared informer and lister for +// ApiServerSources. +type ApiServerSourceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta1.ApiServerSourceLister +} + +type apiServerSourceInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewApiServerSourceInformer constructs a new informer for ApiServerSource 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 NewApiServerSourceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredApiServerSourceInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredApiServerSourceInformer constructs a new informer for ApiServerSource 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 NewFilteredApiServerSourceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1beta1().ApiServerSources(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1beta1().ApiServerSources(namespace).Watch(context.TODO(), options) + }, + }, + &sourcesv1beta1.ApiServerSource{}, + resyncPeriod, + indexers, + ) +} + +func (f *apiServerSourceInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredApiServerSourceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *apiServerSourceInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&sourcesv1beta1.ApiServerSource{}, f.defaultInformer) +} + +func (f *apiServerSourceInformer) Lister() v1beta1.ApiServerSourceLister { + return v1beta1.NewApiServerSourceLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/sources/v1beta1/containersource.go b/pkg/client/informers/externalversions/sources/v1beta1/containersource.go new file mode 100644 index 00000000000..6484ac73538 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1beta1/containersource.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1beta1 "knative.dev/eventing/pkg/client/listers/sources/v1beta1" +) + +// ContainerSourceInformer provides access to a shared informer and lister for +// ContainerSources. +type ContainerSourceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta1.ContainerSourceLister +} + +type containerSourceInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewContainerSourceInformer constructs a new informer for ContainerSource 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 NewContainerSourceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredContainerSourceInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredContainerSourceInformer constructs a new informer for ContainerSource 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 NewFilteredContainerSourceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1beta1().ContainerSources(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1beta1().ContainerSources(namespace).Watch(context.TODO(), options) + }, + }, + &sourcesv1beta1.ContainerSource{}, + resyncPeriod, + indexers, + ) +} + +func (f *containerSourceInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredContainerSourceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *containerSourceInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&sourcesv1beta1.ContainerSource{}, f.defaultInformer) +} + +func (f *containerSourceInformer) Lister() v1beta1.ContainerSourceLister { + return v1beta1.NewContainerSourceLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/sources/v1beta1/interface.go b/pkg/client/informers/externalversions/sources/v1beta1/interface.go new file mode 100644 index 00000000000..6eff4bf4ad0 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1beta1/interface.go @@ -0,0 +1,66 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // ApiServerSources returns a ApiServerSourceInformer. + ApiServerSources() ApiServerSourceInformer + // ContainerSources returns a ContainerSourceInformer. + ContainerSources() ContainerSourceInformer + // PingSources returns a PingSourceInformer. + PingSources() PingSourceInformer + // SinkBindings returns a SinkBindingInformer. + SinkBindings() SinkBindingInformer +} + +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} +} + +// ApiServerSources returns a ApiServerSourceInformer. +func (v *version) ApiServerSources() ApiServerSourceInformer { + return &apiServerSourceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// ContainerSources returns a ContainerSourceInformer. +func (v *version) ContainerSources() ContainerSourceInformer { + return &containerSourceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// PingSources returns a PingSourceInformer. +func (v *version) PingSources() PingSourceInformer { + return &pingSourceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// SinkBindings returns a SinkBindingInformer. +func (v *version) SinkBindings() SinkBindingInformer { + return &sinkBindingInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/sources/v1beta1/pingsource.go b/pkg/client/informers/externalversions/sources/v1beta1/pingsource.go new file mode 100644 index 00000000000..d4b5ca0486e --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1beta1/pingsource.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1beta1 "knative.dev/eventing/pkg/client/listers/sources/v1beta1" +) + +// PingSourceInformer provides access to a shared informer and lister for +// PingSources. +type PingSourceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta1.PingSourceLister +} + +type pingSourceInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewPingSourceInformer constructs a new informer for PingSource type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPingSourceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredPingSourceInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredPingSourceInformer constructs a new informer for PingSource type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredPingSourceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1beta1().PingSources(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1beta1().PingSources(namespace).Watch(context.TODO(), options) + }, + }, + &sourcesv1beta1.PingSource{}, + resyncPeriod, + indexers, + ) +} + +func (f *pingSourceInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredPingSourceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *pingSourceInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&sourcesv1beta1.PingSource{}, f.defaultInformer) +} + +func (f *pingSourceInformer) Lister() v1beta1.PingSourceLister { + return v1beta1.NewPingSourceLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/sources/v1beta1/sinkbinding.go b/pkg/client/informers/externalversions/sources/v1beta1/sinkbinding.go new file mode 100644 index 00000000000..0a05e66b036 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1beta1/sinkbinding.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "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" + sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + internalinterfaces "knative.dev/eventing/pkg/client/informers/externalversions/internalinterfaces" + v1beta1 "knative.dev/eventing/pkg/client/listers/sources/v1beta1" +) + +// SinkBindingInformer provides access to a shared informer and lister for +// SinkBindings. +type SinkBindingInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta1.SinkBindingLister +} + +type sinkBindingInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewSinkBindingInformer constructs a new informer for SinkBinding 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 NewSinkBindingInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredSinkBindingInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredSinkBindingInformer constructs a new informer for SinkBinding 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 NewFilteredSinkBindingInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1beta1().SinkBindings(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1beta1().SinkBindings(namespace).Watch(context.TODO(), options) + }, + }, + &sourcesv1beta1.SinkBinding{}, + resyncPeriod, + indexers, + ) +} + +func (f *sinkBindingInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredSinkBindingInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *sinkBindingInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&sourcesv1beta1.SinkBinding{}, f.defaultInformer) +} + +func (f *sinkBindingInformer) Lister() v1beta1.SinkBindingLister { + return v1beta1.NewSinkBindingLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/injection/informers/eventing/v1beta1/broker/broker.go b/pkg/client/injection/informers/eventing/v1beta1/broker/broker.go new file mode 100644 index 00000000000..5b4fdd453b4 --- /dev/null +++ b/pkg/client/injection/informers/eventing/v1beta1/broker/broker.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 broker + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/eventing/v1beta1" + factory "knative.dev/eventing/pkg/client/injection/informers/factory" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Eventing().V1beta1().Brokers() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1beta1.BrokerInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/eventing/v1beta1.BrokerInformer from context.") + } + return untyped.(v1beta1.BrokerInformer) +} diff --git a/pkg/client/injection/informers/eventing/v1beta1/broker/fake/fake.go b/pkg/client/injection/informers/eventing/v1beta1/broker/fake/fake.go new file mode 100644 index 00000000000..7cc2f75ad4d --- /dev/null +++ b/pkg/client/injection/informers/eventing/v1beta1/broker/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + + broker "knative.dev/eventing/pkg/client/injection/informers/eventing/v1beta1/broker" + fake "knative.dev/eventing/pkg/client/injection/informers/factory/fake" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = broker.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Eventing().V1beta1().Brokers() + return context.WithValue(ctx, broker.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/eventing/v1beta1/broker/filtered/broker.go b/pkg/client/injection/informers/eventing/v1beta1/broker/filtered/broker.go new file mode 100644 index 00000000000..c447f3c8275 --- /dev/null +++ b/pkg/client/injection/informers/eventing/v1beta1/broker/filtered/broker.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/eventing/v1beta1" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Eventing().V1beta1().Brokers() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1beta1.BrokerInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/eventing/v1beta1.BrokerInformer with selector %s from context.", selector) + } + return untyped.(v1beta1.BrokerInformer) +} diff --git a/pkg/client/injection/informers/eventing/v1beta1/broker/filtered/fake/fake.go b/pkg/client/injection/informers/eventing/v1beta1/broker/filtered/fake/fake.go new file mode 100644 index 00000000000..3d9938df27a --- /dev/null +++ b/pkg/client/injection/informers/eventing/v1beta1/broker/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + filtered "knative.dev/eventing/pkg/client/injection/informers/eventing/v1beta1/broker/filtered" + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Eventing().V1beta1().Brokers() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/eventing/v1beta1/trigger/fake/fake.go b/pkg/client/injection/informers/eventing/v1beta1/trigger/fake/fake.go new file mode 100644 index 00000000000..82df00bf75e --- /dev/null +++ b/pkg/client/injection/informers/eventing/v1beta1/trigger/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + + trigger "knative.dev/eventing/pkg/client/injection/informers/eventing/v1beta1/trigger" + fake "knative.dev/eventing/pkg/client/injection/informers/factory/fake" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = trigger.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Eventing().V1beta1().Triggers() + return context.WithValue(ctx, trigger.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/eventing/v1beta1/trigger/filtered/fake/fake.go b/pkg/client/injection/informers/eventing/v1beta1/trigger/filtered/fake/fake.go new file mode 100644 index 00000000000..d71c30610db --- /dev/null +++ b/pkg/client/injection/informers/eventing/v1beta1/trigger/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + filtered "knative.dev/eventing/pkg/client/injection/informers/eventing/v1beta1/trigger/filtered" + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Eventing().V1beta1().Triggers() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/eventing/v1beta1/trigger/filtered/trigger.go b/pkg/client/injection/informers/eventing/v1beta1/trigger/filtered/trigger.go new file mode 100644 index 00000000000..2ddcbd26870 --- /dev/null +++ b/pkg/client/injection/informers/eventing/v1beta1/trigger/filtered/trigger.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/eventing/v1beta1" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Eventing().V1beta1().Triggers() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1beta1.TriggerInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/eventing/v1beta1.TriggerInformer with selector %s from context.", selector) + } + return untyped.(v1beta1.TriggerInformer) +} diff --git a/pkg/client/injection/informers/eventing/v1beta1/trigger/trigger.go b/pkg/client/injection/informers/eventing/v1beta1/trigger/trigger.go new file mode 100644 index 00000000000..782290b7cf0 --- /dev/null +++ b/pkg/client/injection/informers/eventing/v1beta1/trigger/trigger.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 trigger + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/eventing/v1beta1" + factory "knative.dev/eventing/pkg/client/injection/informers/factory" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Eventing().V1beta1().Triggers() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1beta1.TriggerInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/eventing/v1beta1.TriggerInformer from context.") + } + return untyped.(v1beta1.TriggerInformer) +} diff --git a/pkg/client/injection/informers/flows/v1beta1/parallel/fake/fake.go b/pkg/client/injection/informers/flows/v1beta1/parallel/fake/fake.go new file mode 100644 index 00000000000..76b937cb57f --- /dev/null +++ b/pkg/client/injection/informers/flows/v1beta1/parallel/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + parallel "knative.dev/eventing/pkg/client/injection/informers/flows/v1beta1/parallel" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = parallel.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Flows().V1beta1().Parallels() + return context.WithValue(ctx, parallel.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/flows/v1beta1/parallel/filtered/fake/fake.go b/pkg/client/injection/informers/flows/v1beta1/parallel/filtered/fake/fake.go new file mode 100644 index 00000000000..a3e610bc7a6 --- /dev/null +++ b/pkg/client/injection/informers/flows/v1beta1/parallel/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/flows/v1beta1/parallel/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Flows().V1beta1().Parallels() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/flows/v1beta1/parallel/filtered/parallel.go b/pkg/client/injection/informers/flows/v1beta1/parallel/filtered/parallel.go new file mode 100644 index 00000000000..05c77e29246 --- /dev/null +++ b/pkg/client/injection/informers/flows/v1beta1/parallel/filtered/parallel.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/flows/v1beta1" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Flows().V1beta1().Parallels() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1beta1.ParallelInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/flows/v1beta1.ParallelInformer with selector %s from context.", selector) + } + return untyped.(v1beta1.ParallelInformer) +} diff --git a/pkg/client/injection/informers/flows/v1beta1/parallel/parallel.go b/pkg/client/injection/informers/flows/v1beta1/parallel/parallel.go new file mode 100644 index 00000000000..09bb108b549 --- /dev/null +++ b/pkg/client/injection/informers/flows/v1beta1/parallel/parallel.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 parallel + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/flows/v1beta1" + factory "knative.dev/eventing/pkg/client/injection/informers/factory" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Flows().V1beta1().Parallels() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1beta1.ParallelInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/flows/v1beta1.ParallelInformer from context.") + } + return untyped.(v1beta1.ParallelInformer) +} diff --git a/pkg/client/injection/informers/flows/v1beta1/sequence/fake/fake.go b/pkg/client/injection/informers/flows/v1beta1/sequence/fake/fake.go new file mode 100644 index 00000000000..dab8562d42d --- /dev/null +++ b/pkg/client/injection/informers/flows/v1beta1/sequence/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + sequence "knative.dev/eventing/pkg/client/injection/informers/flows/v1beta1/sequence" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = sequence.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Flows().V1beta1().Sequences() + return context.WithValue(ctx, sequence.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/flows/v1beta1/sequence/filtered/fake/fake.go b/pkg/client/injection/informers/flows/v1beta1/sequence/filtered/fake/fake.go new file mode 100644 index 00000000000..383e536b67d --- /dev/null +++ b/pkg/client/injection/informers/flows/v1beta1/sequence/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/flows/v1beta1/sequence/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Flows().V1beta1().Sequences() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/flows/v1beta1/sequence/filtered/sequence.go b/pkg/client/injection/informers/flows/v1beta1/sequence/filtered/sequence.go new file mode 100644 index 00000000000..e206840d52a --- /dev/null +++ b/pkg/client/injection/informers/flows/v1beta1/sequence/filtered/sequence.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/flows/v1beta1" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Flows().V1beta1().Sequences() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1beta1.SequenceInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/flows/v1beta1.SequenceInformer with selector %s from context.", selector) + } + return untyped.(v1beta1.SequenceInformer) +} diff --git a/pkg/client/injection/informers/flows/v1beta1/sequence/sequence.go b/pkg/client/injection/informers/flows/v1beta1/sequence/sequence.go new file mode 100644 index 00000000000..20acbf41fc4 --- /dev/null +++ b/pkg/client/injection/informers/flows/v1beta1/sequence/sequence.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 sequence + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/flows/v1beta1" + factory "knative.dev/eventing/pkg/client/injection/informers/factory" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Flows().V1beta1().Sequences() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1beta1.SequenceInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/flows/v1beta1.SequenceInformer from context.") + } + return untyped.(v1beta1.SequenceInformer) +} diff --git a/pkg/client/injection/informers/messaging/v1beta1/channel/channel.go b/pkg/client/injection/informers/messaging/v1beta1/channel/channel.go new file mode 100644 index 00000000000..fa63724b7ea --- /dev/null +++ b/pkg/client/injection/informers/messaging/v1beta1/channel/channel.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 channel + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/messaging/v1beta1" + factory "knative.dev/eventing/pkg/client/injection/informers/factory" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Messaging().V1beta1().Channels() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1beta1.ChannelInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/messaging/v1beta1.ChannelInformer from context.") + } + return untyped.(v1beta1.ChannelInformer) +} diff --git a/pkg/client/injection/informers/messaging/v1beta1/channel/fake/fake.go b/pkg/client/injection/informers/messaging/v1beta1/channel/fake/fake.go new file mode 100644 index 00000000000..be50c8f38d2 --- /dev/null +++ b/pkg/client/injection/informers/messaging/v1beta1/channel/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + channel "knative.dev/eventing/pkg/client/injection/informers/messaging/v1beta1/channel" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = channel.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Messaging().V1beta1().Channels() + return context.WithValue(ctx, channel.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/messaging/v1beta1/channel/filtered/channel.go b/pkg/client/injection/informers/messaging/v1beta1/channel/filtered/channel.go new file mode 100644 index 00000000000..f971a019951 --- /dev/null +++ b/pkg/client/injection/informers/messaging/v1beta1/channel/filtered/channel.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/messaging/v1beta1" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Messaging().V1beta1().Channels() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1beta1.ChannelInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/messaging/v1beta1.ChannelInformer with selector %s from context.", selector) + } + return untyped.(v1beta1.ChannelInformer) +} diff --git a/pkg/client/injection/informers/messaging/v1beta1/channel/filtered/fake/fake.go b/pkg/client/injection/informers/messaging/v1beta1/channel/filtered/fake/fake.go new file mode 100644 index 00000000000..ac75d6260d3 --- /dev/null +++ b/pkg/client/injection/informers/messaging/v1beta1/channel/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/messaging/v1beta1/channel/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Messaging().V1beta1().Channels() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel/fake/fake.go b/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel/fake/fake.go new file mode 100644 index 00000000000..72b9954d8c5 --- /dev/null +++ b/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + inmemorychannel "knative.dev/eventing/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = inmemorychannel.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Messaging().V1beta1().InMemoryChannels() + return context.WithValue(ctx, inmemorychannel.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel/filtered/fake/fake.go b/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel/filtered/fake/fake.go new file mode 100644 index 00000000000..9bdfe90553d --- /dev/null +++ b/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Messaging().V1beta1().InMemoryChannels() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel/filtered/inmemorychannel.go b/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel/filtered/inmemorychannel.go new file mode 100644 index 00000000000..27619577e54 --- /dev/null +++ b/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel/filtered/inmemorychannel.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/messaging/v1beta1" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Messaging().V1beta1().InMemoryChannels() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1beta1.InMemoryChannelInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/messaging/v1beta1.InMemoryChannelInformer with selector %s from context.", selector) + } + return untyped.(v1beta1.InMemoryChannelInformer) +} diff --git a/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel/inmemorychannel.go b/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel/inmemorychannel.go new file mode 100644 index 00000000000..c16e7787166 --- /dev/null +++ b/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel/inmemorychannel.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 inmemorychannel + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/messaging/v1beta1" + factory "knative.dev/eventing/pkg/client/injection/informers/factory" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Messaging().V1beta1().InMemoryChannels() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1beta1.InMemoryChannelInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/messaging/v1beta1.InMemoryChannelInformer from context.") + } + return untyped.(v1beta1.InMemoryChannelInformer) +} diff --git a/pkg/client/injection/informers/messaging/v1beta1/subscription/fake/fake.go b/pkg/client/injection/informers/messaging/v1beta1/subscription/fake/fake.go new file mode 100644 index 00000000000..66f2c6f59bf --- /dev/null +++ b/pkg/client/injection/informers/messaging/v1beta1/subscription/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + subscription "knative.dev/eventing/pkg/client/injection/informers/messaging/v1beta1/subscription" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = subscription.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Messaging().V1beta1().Subscriptions() + return context.WithValue(ctx, subscription.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/messaging/v1beta1/subscription/filtered/fake/fake.go b/pkg/client/injection/informers/messaging/v1beta1/subscription/filtered/fake/fake.go new file mode 100644 index 00000000000..fabf3eb22fd --- /dev/null +++ b/pkg/client/injection/informers/messaging/v1beta1/subscription/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/messaging/v1beta1/subscription/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Messaging().V1beta1().Subscriptions() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/messaging/v1beta1/subscription/filtered/subscription.go b/pkg/client/injection/informers/messaging/v1beta1/subscription/filtered/subscription.go new file mode 100644 index 00000000000..8db49fea9d7 --- /dev/null +++ b/pkg/client/injection/informers/messaging/v1beta1/subscription/filtered/subscription.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/messaging/v1beta1" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Messaging().V1beta1().Subscriptions() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1beta1.SubscriptionInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/messaging/v1beta1.SubscriptionInformer with selector %s from context.", selector) + } + return untyped.(v1beta1.SubscriptionInformer) +} diff --git a/pkg/client/injection/informers/messaging/v1beta1/subscription/subscription.go b/pkg/client/injection/informers/messaging/v1beta1/subscription/subscription.go new file mode 100644 index 00000000000..84c2e875134 --- /dev/null +++ b/pkg/client/injection/informers/messaging/v1beta1/subscription/subscription.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 subscription + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/messaging/v1beta1" + factory "knative.dev/eventing/pkg/client/injection/informers/factory" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Messaging().V1beta1().Subscriptions() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1beta1.SubscriptionInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/messaging/v1beta1.SubscriptionInformer from context.") + } + return untyped.(v1beta1.SubscriptionInformer) +} diff --git a/pkg/client/injection/informers/sources/v1alpha1/apiserversource/apiserversource.go b/pkg/client/injection/informers/sources/v1alpha1/apiserversource/apiserversource.go new file mode 100644 index 00000000000..29c470e88fc --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha1/apiserversource/apiserversource.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 apiserversource + +import ( + context "context" + + v1alpha1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha1" + 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().V1alpha1().ApiServerSources() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1alpha1.ApiServerSourceInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha1.ApiServerSourceInformer from context.") + } + return untyped.(v1alpha1.ApiServerSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1alpha1/apiserversource/fake/fake.go b/pkg/client/injection/informers/sources/v1alpha1/apiserversource/fake/fake.go new file mode 100644 index 00000000000..02537746b62 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha1/apiserversource/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + apiserversource "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha1/apiserversource" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = apiserversource.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Sources().V1alpha1().ApiServerSources() + return context.WithValue(ctx, apiserversource.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/sources/v1alpha1/apiserversource/filtered/apiserversource.go b/pkg/client/injection/informers/sources/v1alpha1/apiserversource/filtered/apiserversource.go new file mode 100644 index 00000000000..0137600017a --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha1/apiserversource/filtered/apiserversource.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1alpha1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha1" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Sources().V1alpha1().ApiServerSources() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1alpha1.ApiServerSourceInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha1.ApiServerSourceInformer with selector %s from context.", selector) + } + return untyped.(v1alpha1.ApiServerSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1alpha1/apiserversource/filtered/fake/fake.go b/pkg/client/injection/informers/sources/v1alpha1/apiserversource/filtered/fake/fake.go new file mode 100644 index 00000000000..d4e9029a56d --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha1/apiserversource/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha1/apiserversource/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Sources().V1alpha1().ApiServerSources() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/sources/v1alpha1/sinkbinding/fake/fake.go b/pkg/client/injection/informers/sources/v1alpha1/sinkbinding/fake/fake.go new file mode 100644 index 00000000000..41166d9527f --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha1/sinkbinding/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + sinkbinding "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha1/sinkbinding" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = sinkbinding.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Sources().V1alpha1().SinkBindings() + return context.WithValue(ctx, sinkbinding.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/sources/v1alpha1/sinkbinding/filtered/fake/fake.go b/pkg/client/injection/informers/sources/v1alpha1/sinkbinding/filtered/fake/fake.go new file mode 100644 index 00000000000..3ce49e474ef --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha1/sinkbinding/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha1/sinkbinding/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Sources().V1alpha1().SinkBindings() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/sources/v1alpha1/sinkbinding/filtered/sinkbinding.go b/pkg/client/injection/informers/sources/v1alpha1/sinkbinding/filtered/sinkbinding.go new file mode 100644 index 00000000000..78f76110afa --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha1/sinkbinding/filtered/sinkbinding.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1alpha1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha1" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Sources().V1alpha1().SinkBindings() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1alpha1.SinkBindingInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha1.SinkBindingInformer with selector %s from context.", selector) + } + return untyped.(v1alpha1.SinkBindingInformer) +} diff --git a/pkg/client/injection/informers/sources/v1alpha1/sinkbinding/sinkbinding.go b/pkg/client/injection/informers/sources/v1alpha1/sinkbinding/sinkbinding.go new file mode 100644 index 00000000000..8ee4fa06df2 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha1/sinkbinding/sinkbinding.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 sinkbinding + +import ( + context "context" + + v1alpha1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha1" + 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().V1alpha1().SinkBindings() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1alpha1.SinkBindingInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha1.SinkBindingInformer from context.") + } + return untyped.(v1alpha1.SinkBindingInformer) +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/apiserversource/apiserversource.go b/pkg/client/injection/informers/sources/v1alpha2/apiserversource/apiserversource.go new file mode 100644 index 00000000000..b94a53e61cb --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/apiserversource/apiserversource.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 apiserversource + +import ( + context "context" + + v1alpha2 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2" + 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().V1alpha2().ApiServerSources() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1alpha2.ApiServerSourceInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2.ApiServerSourceInformer from context.") + } + return untyped.(v1alpha2.ApiServerSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/apiserversource/fake/fake.go b/pkg/client/injection/informers/sources/v1alpha2/apiserversource/fake/fake.go new file mode 100644 index 00000000000..d68438ce7ed --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/apiserversource/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + apiserversource "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha2/apiserversource" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = apiserversource.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Sources().V1alpha2().ApiServerSources() + return context.WithValue(ctx, apiserversource.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/apiserversource/filtered/apiserversource.go b/pkg/client/injection/informers/sources/v1alpha2/apiserversource/filtered/apiserversource.go new file mode 100644 index 00000000000..71c231e6d27 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/apiserversource/filtered/apiserversource.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1alpha2 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Sources().V1alpha2().ApiServerSources() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1alpha2.ApiServerSourceInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2.ApiServerSourceInformer with selector %s from context.", selector) + } + return untyped.(v1alpha2.ApiServerSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/apiserversource/filtered/fake/fake.go b/pkg/client/injection/informers/sources/v1alpha2/apiserversource/filtered/fake/fake.go new file mode 100644 index 00000000000..81ce87e7883 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/apiserversource/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha2/apiserversource/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Sources().V1alpha2().ApiServerSources() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/containersource/containersource.go b/pkg/client/injection/informers/sources/v1alpha2/containersource/containersource.go new file mode 100644 index 00000000000..4f393319a09 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/containersource/containersource.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 containersource + +import ( + context "context" + + v1alpha2 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2" + 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().V1alpha2().ContainerSources() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1alpha2.ContainerSourceInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2.ContainerSourceInformer from context.") + } + return untyped.(v1alpha2.ContainerSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/containersource/fake/fake.go b/pkg/client/injection/informers/sources/v1alpha2/containersource/fake/fake.go new file mode 100644 index 00000000000..12beaa58bc5 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/containersource/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + containersource "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha2/containersource" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = containersource.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Sources().V1alpha2().ContainerSources() + return context.WithValue(ctx, containersource.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/containersource/filtered/containersource.go b/pkg/client/injection/informers/sources/v1alpha2/containersource/filtered/containersource.go new file mode 100644 index 00000000000..87d6e930ce8 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/containersource/filtered/containersource.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1alpha2 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Sources().V1alpha2().ContainerSources() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1alpha2.ContainerSourceInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2.ContainerSourceInformer with selector %s from context.", selector) + } + return untyped.(v1alpha2.ContainerSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/containersource/filtered/fake/fake.go b/pkg/client/injection/informers/sources/v1alpha2/containersource/filtered/fake/fake.go new file mode 100644 index 00000000000..1ec721613c5 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/containersource/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha2/containersource/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Sources().V1alpha2().ContainerSources() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/pingsource/fake/fake.go b/pkg/client/injection/informers/sources/v1alpha2/pingsource/fake/fake.go new file mode 100644 index 00000000000..4d216587059 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/pingsource/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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/v1alpha2/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().V1alpha2().PingSources() + return context.WithValue(ctx, pingsource.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/pingsource/filtered/fake/fake.go b/pkg/client/injection/informers/sources/v1alpha2/pingsource/filtered/fake/fake.go new file mode 100644 index 00000000000..12f1b8ca45a --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/pingsource/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha2/pingsource/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Sources().V1alpha2().PingSources() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/pingsource/filtered/pingsource.go b/pkg/client/injection/informers/sources/v1alpha2/pingsource/filtered/pingsource.go new file mode 100644 index 00000000000..dae2d97a54e --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/pingsource/filtered/pingsource.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1alpha2 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Sources().V1alpha2().PingSources() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1alpha2.PingSourceInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2.PingSourceInformer with selector %s from context.", selector) + } + return untyped.(v1alpha2.PingSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/pingsource/pingsource.go b/pkg/client/injection/informers/sources/v1alpha2/pingsource/pingsource.go new file mode 100644 index 00000000000..079266bc193 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/pingsource/pingsource.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + v1alpha2 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2" + 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().V1alpha2().PingSources() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1alpha2.PingSourceInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2.PingSourceInformer from context.") + } + return untyped.(v1alpha2.PingSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/sinkbinding/fake/fake.go b/pkg/client/injection/informers/sources/v1alpha2/sinkbinding/fake/fake.go new file mode 100644 index 00000000000..48293580929 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/sinkbinding/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + sinkbinding "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha2/sinkbinding" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = sinkbinding.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Sources().V1alpha2().SinkBindings() + return context.WithValue(ctx, sinkbinding.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/sinkbinding/filtered/fake/fake.go b/pkg/client/injection/informers/sources/v1alpha2/sinkbinding/filtered/fake/fake.go new file mode 100644 index 00000000000..b216c74f1f2 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/sinkbinding/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha2/sinkbinding/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Sources().V1alpha2().SinkBindings() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/sinkbinding/filtered/sinkbinding.go b/pkg/client/injection/informers/sources/v1alpha2/sinkbinding/filtered/sinkbinding.go new file mode 100644 index 00000000000..213673e2b7b --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/sinkbinding/filtered/sinkbinding.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1alpha2 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Sources().V1alpha2().SinkBindings() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1alpha2.SinkBindingInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2.SinkBindingInformer with selector %s from context.", selector) + } + return untyped.(v1alpha2.SinkBindingInformer) +} diff --git a/pkg/client/injection/informers/sources/v1alpha2/sinkbinding/sinkbinding.go b/pkg/client/injection/informers/sources/v1alpha2/sinkbinding/sinkbinding.go new file mode 100644 index 00000000000..d8c2e089014 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha2/sinkbinding/sinkbinding.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 sinkbinding + +import ( + context "context" + + v1alpha2 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2" + 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().V1alpha2().SinkBindings() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1alpha2.SinkBindingInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha2.SinkBindingInformer from context.") + } + return untyped.(v1alpha2.SinkBindingInformer) +} diff --git a/pkg/client/injection/informers/sources/v1beta1/apiserversource/apiserversource.go b/pkg/client/injection/informers/sources/v1beta1/apiserversource/apiserversource.go new file mode 100644 index 00000000000..02adfb66c2a --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/apiserversource/apiserversource.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 apiserversource + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1" + factory "knative.dev/eventing/pkg/client/injection/informers/factory" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Sources().V1beta1().ApiServerSources() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1beta1.ApiServerSourceInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1.ApiServerSourceInformer from context.") + } + return untyped.(v1beta1.ApiServerSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1beta1/apiserversource/fake/fake.go b/pkg/client/injection/informers/sources/v1beta1/apiserversource/fake/fake.go new file mode 100644 index 00000000000..1476cbc05e0 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/apiserversource/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + apiserversource "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/apiserversource" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = apiserversource.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Sources().V1beta1().ApiServerSources() + return context.WithValue(ctx, apiserversource.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/sources/v1beta1/apiserversource/filtered/apiserversource.go b/pkg/client/injection/informers/sources/v1beta1/apiserversource/filtered/apiserversource.go new file mode 100644 index 00000000000..fd9fafb3865 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/apiserversource/filtered/apiserversource.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Sources().V1beta1().ApiServerSources() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1beta1.ApiServerSourceInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1.ApiServerSourceInformer with selector %s from context.", selector) + } + return untyped.(v1beta1.ApiServerSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1beta1/apiserversource/filtered/fake/fake.go b/pkg/client/injection/informers/sources/v1beta1/apiserversource/filtered/fake/fake.go new file mode 100644 index 00000000000..1bf0dcf7a52 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/apiserversource/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/apiserversource/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Sources().V1beta1().ApiServerSources() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/sources/v1beta1/containersource/containersource.go b/pkg/client/injection/informers/sources/v1beta1/containersource/containersource.go new file mode 100644 index 00000000000..3ec6c98d979 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/containersource/containersource.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 containersource + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1" + factory "knative.dev/eventing/pkg/client/injection/informers/factory" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Sources().V1beta1().ContainerSources() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1beta1.ContainerSourceInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1.ContainerSourceInformer from context.") + } + return untyped.(v1beta1.ContainerSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1beta1/containersource/fake/fake.go b/pkg/client/injection/informers/sources/v1beta1/containersource/fake/fake.go new file mode 100644 index 00000000000..694bc749a2f --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/containersource/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + containersource "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/containersource" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = containersource.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Sources().V1beta1().ContainerSources() + return context.WithValue(ctx, containersource.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/sources/v1beta1/containersource/filtered/containersource.go b/pkg/client/injection/informers/sources/v1beta1/containersource/filtered/containersource.go new file mode 100644 index 00000000000..48a68bf2dea --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/containersource/filtered/containersource.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Sources().V1beta1().ContainerSources() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1beta1.ContainerSourceInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1.ContainerSourceInformer with selector %s from context.", selector) + } + return untyped.(v1beta1.ContainerSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1beta1/containersource/filtered/fake/fake.go b/pkg/client/injection/informers/sources/v1beta1/containersource/filtered/fake/fake.go new file mode 100644 index 00000000000..2b0855a2a06 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/containersource/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/containersource/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Sources().V1beta1().ContainerSources() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/sources/v1beta1/pingsource/fake/fake.go b/pkg/client/injection/informers/sources/v1beta1/pingsource/fake/fake.go new file mode 100644 index 00000000000..40bab1fd169 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/pingsource/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package fake + +import ( + context "context" + + fake "knative.dev/eventing/pkg/client/injection/informers/factory/fake" + pingsource "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/pingsource" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = pingsource.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Sources().V1beta1().PingSources() + return context.WithValue(ctx, pingsource.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/sources/v1beta1/pingsource/filtered/fake/fake.go b/pkg/client/injection/informers/sources/v1beta1/pingsource/filtered/fake/fake.go new file mode 100644 index 00000000000..499cf91aa08 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/pingsource/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/pingsource/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Sources().V1beta1().PingSources() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/sources/v1beta1/pingsource/filtered/pingsource.go b/pkg/client/injection/informers/sources/v1beta1/pingsource/filtered/pingsource.go new file mode 100644 index 00000000000..54e4e8af541 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/pingsource/filtered/pingsource.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Sources().V1beta1().PingSources() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1beta1.PingSourceInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1.PingSourceInformer with selector %s from context.", selector) + } + return untyped.(v1beta1.PingSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1beta1/pingsource/pingsource.go b/pkg/client/injection/informers/sources/v1beta1/pingsource/pingsource.go new file mode 100644 index 00000000000..e965fddfb8a --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/pingsource/pingsource.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package pingsource + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1" + factory "knative.dev/eventing/pkg/client/injection/informers/factory" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Sources().V1beta1().PingSources() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1beta1.PingSourceInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1.PingSourceInformer from context.") + } + return untyped.(v1beta1.PingSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1beta1/sinkbinding/fake/fake.go b/pkg/client/injection/informers/sources/v1beta1/sinkbinding/fake/fake.go new file mode 100644 index 00000000000..82449c4e148 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/sinkbinding/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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" + sinkbinding "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/sinkbinding" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = sinkbinding.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Sources().V1beta1().SinkBindings() + return context.WithValue(ctx, sinkbinding.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/sources/v1beta1/sinkbinding/filtered/fake/fake.go b/pkg/client/injection/informers/sources/v1beta1/sinkbinding/filtered/fake/fake.go new file mode 100644 index 00000000000..a6234d9869a --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/sinkbinding/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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" + + factoryfiltered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + filtered "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/sinkbinding/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Sources().V1beta1().SinkBindings() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/sources/v1beta1/sinkbinding/filtered/sinkbinding.go b/pkg/client/injection/informers/sources/v1beta1/sinkbinding/filtered/sinkbinding.go new file mode 100644 index 00000000000..44e9e9abbff --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/sinkbinding/filtered/sinkbinding.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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 filtered + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1" + filtered "knative.dev/eventing/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Sources().V1beta1().SinkBindings() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1beta1.SinkBindingInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1.SinkBindingInformer with selector %s from context.", selector) + } + return untyped.(v1beta1.SinkBindingInformer) +} diff --git a/pkg/client/injection/informers/sources/v1beta1/sinkbinding/sinkbinding.go b/pkg/client/injection/informers/sources/v1beta1/sinkbinding/sinkbinding.go new file mode 100644 index 00000000000..847c94b429a --- /dev/null +++ b/pkg/client/injection/informers/sources/v1beta1/sinkbinding/sinkbinding.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 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 sinkbinding + +import ( + context "context" + + v1beta1 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1" + factory "knative.dev/eventing/pkg/client/injection/informers/factory" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Sources().V1beta1().SinkBindings() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1beta1.SinkBindingInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta1.SinkBindingInformer from context.") + } + return untyped.(v1beta1.SinkBindingInformer) +} diff --git a/pkg/client/injection/reconciler/eventing/v1beta1/broker/controller.go b/pkg/client/injection/reconciler/eventing/v1beta1/broker/controller.go new file mode 100644 index 00000000000..7598a582afd --- /dev/null +++ b/pkg/client/injection/reconciler/eventing/v1beta1/broker/controller.go @@ -0,0 +1,157 @@ +/* +Copyright 2021 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 broker + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + zap "go.uber.org/zap" + 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" + broker "knative.dev/eventing/pkg/client/injection/informers/eventing/v1beta1/broker" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "broker-controller" + defaultFinalizerName = "brokers.eventing.knative.dev" + + // ClassAnnotationKey points to the annotation for the class of this resource. + ClassAnnotationKey = "eventing.knative.dev/broker.class" +) + +// 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 by the internal reconciler. +func NewImpl(ctx context.Context, r Interface, classValue string, 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)) + } + + brokerInformer := broker.Get(ctx) + + lister := brokerInformer.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, + classValue: classValue, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "eventing.knative.dev.Broker"), + ) + + impl := controller.NewImpl(rec, logger, ctrTypeName) + 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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/eventing/v1beta1/broker/reconciler.go b/pkg/client/injection/reconciler/eventing/v1beta1/broker/reconciler.go new file mode 100644 index 00000000000..e062444d26e --- /dev/null +++ b/pkg/client/injection/reconciler/eventing/v1beta1/broker/reconciler.go @@ -0,0 +1,466 @@ +/* +Copyright 2021 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 broker + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + 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" + v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + eventingv1beta1 "knative.dev/eventing/pkg/client/listers/eventing/v1beta1" + 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 v1beta1.Broker. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1beta1.Broker. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1beta1.Broker) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.Broker. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1beta1.Broker. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1beta1.Broker) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta1.Broker if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1beta1.Broker. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1beta1.Broker) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.Broker if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1beta1.Broker. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1beta1.Broker) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1beta1.Broker) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1beta1.Broker 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 eventingv1beta1.BrokerLister + + // 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 + + // classValue is the resource annotation[eventing.knative.dev/broker.class] instance value this reconciler instance filters on. + classValue string +} + +// 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 eventingv1beta1.BrokerLister, recorder record.EventRecorder, r Interface, classValue string, 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, + classValue: classValue, + } + + 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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 controller.NewSkipKey(key) + } + + // 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.Brokers(s.namespace) + + original, err := getter.Get(s.name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + return nil + } else if err != nil { + return err + } + + if classValue, found := original.GetAnnotations()[ClassAnnotationKey]; !found || classValue != r.classValue { + logger.Debugw("Skip reconciling resource, class annotation value does not match reconciler instance value.", + zap.String("classKey", ClassAnnotationKey), + zap.String("issue", classValue+"!="+r.classValue)) + return nil + } + + // 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: + // 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.Event(resource, event.EventType, event.Reason, event.Error()) + + // 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 *v1beta1.Broker, desired *v1beta1.Broker) 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.EventingV1beta1().Brokers(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 equality.Semantic.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.EventingV1beta1().Brokers(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 *v1beta1.Broker) (*v1beta1.Broker, error) { + + getter := r.Lister.Brokers(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.EventingV1beta1().Brokers(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 *v1beta1.Broker) (*v1beta1.Broker, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1beta1.Broker, reconcileEvent reconciler.Event) (*v1beta1.Broker, 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/eventing/v1beta1/broker/state.go b/pkg/client/injection/reconciler/eventing/v1beta1/broker/state.go new file mode 100644 index 00000000000..aaa269ca503 --- /dev/null +++ b/pkg/client/injection/reconciler/eventing/v1beta1/broker/state.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 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 broker + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + 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 + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi is the read only interface cast of the reconciler. + roi ReadOnlyInterface + // isROI (Read Only Interface) the reconciler only observes reconciliation. + isROI bool + // rof is the read only finalizer cast of the reconciler. + rof ReadOnlyFinalizer + // isROF (Read Only Finalizer) the reconciler only observes finalize. + isROF bool + // isLeader the instance of the reconciler is the elected leader. + isLeader bool +} + +func newState(key string, r *reconcilerImpl) (*state, error) { + // Convert the namespace/name string into a distinct namespace and name. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, fmt.Errorf("invalid resource key: %s", key) + } + + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + rof: rof, + isROF: isROF, + isLeader: isLeader, + }, nil +} + +// isNotLeaderNorObserver checks to see if this reconciler with the current +// state is enabled to do any work or not. +// isNotLeaderNorObserver returns true when there is no work possible for the +// reconciler. +func (s *state) isNotLeaderNorObserver() bool { + if !s.isLeader && !s.isROI && !s.isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1beta1.Broker) (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/injection/reconciler/eventing/v1beta1/trigger/controller.go b/pkg/client/injection/reconciler/eventing/v1beta1/trigger/controller.go new file mode 100644 index 00000000000..30af81ac788 --- /dev/null +++ b/pkg/client/injection/reconciler/eventing/v1beta1/trigger/controller.go @@ -0,0 +1,153 @@ +/* +Copyright 2021 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 trigger + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + zap "go.uber.org/zap" + 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" + trigger "knative.dev/eventing/pkg/client/injection/informers/eventing/v1beta1/trigger" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "trigger-controller" + defaultFinalizerName = "triggers.eventing.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 by 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)) + } + + triggerInformer := trigger.Get(ctx) + + lister := triggerInformer.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, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "eventing.knative.dev.Trigger"), + ) + + impl := controller.NewImpl(rec, logger, ctrTypeName) + 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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/eventing/v1beta1/trigger/reconciler.go b/pkg/client/injection/reconciler/eventing/v1beta1/trigger/reconciler.go new file mode 100644 index 00000000000..aac76aedb0e --- /dev/null +++ b/pkg/client/injection/reconciler/eventing/v1beta1/trigger/reconciler.go @@ -0,0 +1,455 @@ +/* +Copyright 2021 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 trigger + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + 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" + v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + eventingv1beta1 "knative.dev/eventing/pkg/client/listers/eventing/v1beta1" + 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 v1beta1.Trigger. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1beta1.Trigger. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1beta1.Trigger) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.Trigger. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1beta1.Trigger. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1beta1.Trigger) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta1.Trigger if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1beta1.Trigger. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1beta1.Trigger) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.Trigger if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1beta1.Trigger. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1beta1.Trigger) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1beta1.Trigger) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1beta1.Trigger 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 eventingv1beta1.TriggerLister + + // 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 eventingv1beta1.TriggerLister, 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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 controller.NewSkipKey(key) + } + + // 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.Triggers(s.namespace) + + original, err := getter.Get(s.name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + 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: + // 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.Event(resource, event.EventType, event.Reason, event.Error()) + + // 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 *v1beta1.Trigger, desired *v1beta1.Trigger) 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.EventingV1beta1().Triggers(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 equality.Semantic.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.EventingV1beta1().Triggers(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 *v1beta1.Trigger) (*v1beta1.Trigger, error) { + + getter := r.Lister.Triggers(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.EventingV1beta1().Triggers(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 *v1beta1.Trigger) (*v1beta1.Trigger, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1beta1.Trigger, reconcileEvent reconciler.Event) (*v1beta1.Trigger, 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/eventing/v1beta1/trigger/state.go b/pkg/client/injection/reconciler/eventing/v1beta1/trigger/state.go new file mode 100644 index 00000000000..ab15d679d70 --- /dev/null +++ b/pkg/client/injection/reconciler/eventing/v1beta1/trigger/state.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 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 trigger + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + 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 + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi is the read only interface cast of the reconciler. + roi ReadOnlyInterface + // isROI (Read Only Interface) the reconciler only observes reconciliation. + isROI bool + // rof is the read only finalizer cast of the reconciler. + rof ReadOnlyFinalizer + // isROF (Read Only Finalizer) the reconciler only observes finalize. + isROF bool + // isLeader the instance of the reconciler is the elected leader. + isLeader bool +} + +func newState(key string, r *reconcilerImpl) (*state, error) { + // Convert the namespace/name string into a distinct namespace and name. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, fmt.Errorf("invalid resource key: %s", key) + } + + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + rof: rof, + isROF: isROF, + isLeader: isLeader, + }, nil +} + +// isNotLeaderNorObserver checks to see if this reconciler with the current +// state is enabled to do any work or not. +// isNotLeaderNorObserver returns true when there is no work possible for the +// reconciler. +func (s *state) isNotLeaderNorObserver() bool { + if !s.isLeader && !s.isROI && !s.isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1beta1.Trigger) (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/injection/reconciler/flows/v1beta1/parallel/controller.go b/pkg/client/injection/reconciler/flows/v1beta1/parallel/controller.go new file mode 100644 index 00000000000..f4a1e9dd7b7 --- /dev/null +++ b/pkg/client/injection/reconciler/flows/v1beta1/parallel/controller.go @@ -0,0 +1,153 @@ +/* +Copyright 2021 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 parallel + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + zap "go.uber.org/zap" + 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" + parallel "knative.dev/eventing/pkg/client/injection/informers/flows/v1beta1/parallel" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "parallel-controller" + defaultFinalizerName = "parallels.flows.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 by 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)) + } + + parallelInformer := parallel.Get(ctx) + + lister := parallelInformer.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, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "flows.knative.dev.Parallel"), + ) + + impl := controller.NewImpl(rec, logger, ctrTypeName) + 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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/flows/v1beta1/parallel/reconciler.go b/pkg/client/injection/reconciler/flows/v1beta1/parallel/reconciler.go new file mode 100644 index 00000000000..4cb393b992c --- /dev/null +++ b/pkg/client/injection/reconciler/flows/v1beta1/parallel/reconciler.go @@ -0,0 +1,455 @@ +/* +Copyright 2021 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 parallel + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + 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" + v1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + flowsv1beta1 "knative.dev/eventing/pkg/client/listers/flows/v1beta1" + 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 v1beta1.Parallel. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1beta1.Parallel. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1beta1.Parallel) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.Parallel. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1beta1.Parallel. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1beta1.Parallel) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta1.Parallel if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1beta1.Parallel. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1beta1.Parallel) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.Parallel if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1beta1.Parallel. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1beta1.Parallel) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1beta1.Parallel) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1beta1.Parallel 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 flowsv1beta1.ParallelLister + + // 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 flowsv1beta1.ParallelLister, 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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 controller.NewSkipKey(key) + } + + // 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.Parallels(s.namespace) + + original, err := getter.Get(s.name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + 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: + // 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.Event(resource, event.EventType, event.Reason, event.Error()) + + // 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 *v1beta1.Parallel, desired *v1beta1.Parallel) 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.FlowsV1beta1().Parallels(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 equality.Semantic.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.FlowsV1beta1().Parallels(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 *v1beta1.Parallel) (*v1beta1.Parallel, error) { + + getter := r.Lister.Parallels(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.FlowsV1beta1().Parallels(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 *v1beta1.Parallel) (*v1beta1.Parallel, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1beta1.Parallel, reconcileEvent reconciler.Event) (*v1beta1.Parallel, 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/flows/v1beta1/parallel/state.go b/pkg/client/injection/reconciler/flows/v1beta1/parallel/state.go new file mode 100644 index 00000000000..f5e99065b31 --- /dev/null +++ b/pkg/client/injection/reconciler/flows/v1beta1/parallel/state.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 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 parallel + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" + 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 + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi is the read only interface cast of the reconciler. + roi ReadOnlyInterface + // isROI (Read Only Interface) the reconciler only observes reconciliation. + isROI bool + // rof is the read only finalizer cast of the reconciler. + rof ReadOnlyFinalizer + // isROF (Read Only Finalizer) the reconciler only observes finalize. + isROF bool + // isLeader the instance of the reconciler is the elected leader. + isLeader bool +} + +func newState(key string, r *reconcilerImpl) (*state, error) { + // Convert the namespace/name string into a distinct namespace and name. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, fmt.Errorf("invalid resource key: %s", key) + } + + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + rof: rof, + isROF: isROF, + isLeader: isLeader, + }, nil +} + +// isNotLeaderNorObserver checks to see if this reconciler with the current +// state is enabled to do any work or not. +// isNotLeaderNorObserver returns true when there is no work possible for the +// reconciler. +func (s *state) isNotLeaderNorObserver() bool { + if !s.isLeader && !s.isROI && !s.isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1beta1.Parallel) (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/injection/reconciler/flows/v1beta1/sequence/controller.go b/pkg/client/injection/reconciler/flows/v1beta1/sequence/controller.go new file mode 100644 index 00000000000..405c7d18c4e --- /dev/null +++ b/pkg/client/injection/reconciler/flows/v1beta1/sequence/controller.go @@ -0,0 +1,153 @@ +/* +Copyright 2021 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 sequence + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + zap "go.uber.org/zap" + 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" + sequence "knative.dev/eventing/pkg/client/injection/informers/flows/v1beta1/sequence" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "sequence-controller" + defaultFinalizerName = "sequences.flows.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 by 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)) + } + + sequenceInformer := sequence.Get(ctx) + + lister := sequenceInformer.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, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "flows.knative.dev.Sequence"), + ) + + impl := controller.NewImpl(rec, logger, ctrTypeName) + 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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/flows/v1beta1/sequence/reconciler.go b/pkg/client/injection/reconciler/flows/v1beta1/sequence/reconciler.go new file mode 100644 index 00000000000..aced5a0bb7b --- /dev/null +++ b/pkg/client/injection/reconciler/flows/v1beta1/sequence/reconciler.go @@ -0,0 +1,455 @@ +/* +Copyright 2021 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 sequence + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + 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" + v1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + flowsv1beta1 "knative.dev/eventing/pkg/client/listers/flows/v1beta1" + 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 v1beta1.Sequence. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1beta1.Sequence. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1beta1.Sequence) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.Sequence. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1beta1.Sequence. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1beta1.Sequence) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta1.Sequence if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1beta1.Sequence. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1beta1.Sequence) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.Sequence if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1beta1.Sequence. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1beta1.Sequence) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1beta1.Sequence) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1beta1.Sequence 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 flowsv1beta1.SequenceLister + + // 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 flowsv1beta1.SequenceLister, 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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 controller.NewSkipKey(key) + } + + // 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.Sequences(s.namespace) + + original, err := getter.Get(s.name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + 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: + // 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.Event(resource, event.EventType, event.Reason, event.Error()) + + // 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 *v1beta1.Sequence, desired *v1beta1.Sequence) 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.FlowsV1beta1().Sequences(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 equality.Semantic.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.FlowsV1beta1().Sequences(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 *v1beta1.Sequence) (*v1beta1.Sequence, error) { + + getter := r.Lister.Sequences(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.FlowsV1beta1().Sequences(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 *v1beta1.Sequence) (*v1beta1.Sequence, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1beta1.Sequence, reconcileEvent reconciler.Event) (*v1beta1.Sequence, 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/flows/v1beta1/sequence/state.go b/pkg/client/injection/reconciler/flows/v1beta1/sequence/state.go new file mode 100644 index 00000000000..563f6e0cf68 --- /dev/null +++ b/pkg/client/injection/reconciler/flows/v1beta1/sequence/state.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 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 sequence + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" + 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 + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi is the read only interface cast of the reconciler. + roi ReadOnlyInterface + // isROI (Read Only Interface) the reconciler only observes reconciliation. + isROI bool + // rof is the read only finalizer cast of the reconciler. + rof ReadOnlyFinalizer + // isROF (Read Only Finalizer) the reconciler only observes finalize. + isROF bool + // isLeader the instance of the reconciler is the elected leader. + isLeader bool +} + +func newState(key string, r *reconcilerImpl) (*state, error) { + // Convert the namespace/name string into a distinct namespace and name. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, fmt.Errorf("invalid resource key: %s", key) + } + + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + rof: rof, + isROF: isROF, + isLeader: isLeader, + }, nil +} + +// isNotLeaderNorObserver checks to see if this reconciler with the current +// state is enabled to do any work or not. +// isNotLeaderNorObserver returns true when there is no work possible for the +// reconciler. +func (s *state) isNotLeaderNorObserver() bool { + if !s.isLeader && !s.isROI && !s.isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1beta1.Sequence) (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/injection/reconciler/messaging/v1beta1/channel/controller.go b/pkg/client/injection/reconciler/messaging/v1beta1/channel/controller.go new file mode 100644 index 00000000000..fa6a3b4b208 --- /dev/null +++ b/pkg/client/injection/reconciler/messaging/v1beta1/channel/controller.go @@ -0,0 +1,153 @@ +/* +Copyright 2021 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 channel + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + zap "go.uber.org/zap" + 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" + channel "knative.dev/eventing/pkg/client/injection/informers/messaging/v1beta1/channel" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "channel-controller" + defaultFinalizerName = "channels.messaging.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 by 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)) + } + + channelInformer := channel.Get(ctx) + + lister := channelInformer.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, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "messaging.knative.dev.Channel"), + ) + + impl := controller.NewImpl(rec, logger, ctrTypeName) + 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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/messaging/v1beta1/channel/reconciler.go b/pkg/client/injection/reconciler/messaging/v1beta1/channel/reconciler.go new file mode 100644 index 00000000000..ce8c416b4ad --- /dev/null +++ b/pkg/client/injection/reconciler/messaging/v1beta1/channel/reconciler.go @@ -0,0 +1,455 @@ +/* +Copyright 2021 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 channel + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + 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" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + messagingv1beta1 "knative.dev/eventing/pkg/client/listers/messaging/v1beta1" + 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 v1beta1.Channel. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1beta1.Channel. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1beta1.Channel) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.Channel. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1beta1.Channel. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1beta1.Channel) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta1.Channel if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1beta1.Channel. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1beta1.Channel) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.Channel if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1beta1.Channel. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1beta1.Channel) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1beta1.Channel) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1beta1.Channel 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 messagingv1beta1.ChannelLister + + // 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 messagingv1beta1.ChannelLister, 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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 controller.NewSkipKey(key) + } + + // 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.Channels(s.namespace) + + original, err := getter.Get(s.name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + 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: + // 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.Event(resource, event.EventType, event.Reason, event.Error()) + + // 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 *v1beta1.Channel, desired *v1beta1.Channel) 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.MessagingV1beta1().Channels(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 equality.Semantic.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.MessagingV1beta1().Channels(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 *v1beta1.Channel) (*v1beta1.Channel, error) { + + getter := r.Lister.Channels(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.MessagingV1beta1().Channels(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 *v1beta1.Channel) (*v1beta1.Channel, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1beta1.Channel, reconcileEvent reconciler.Event) (*v1beta1.Channel, 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/messaging/v1beta1/channel/state.go b/pkg/client/injection/reconciler/messaging/v1beta1/channel/state.go new file mode 100644 index 00000000000..252d68c1c5e --- /dev/null +++ b/pkg/client/injection/reconciler/messaging/v1beta1/channel/state.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 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 channel + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + 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 + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi is the read only interface cast of the reconciler. + roi ReadOnlyInterface + // isROI (Read Only Interface) the reconciler only observes reconciliation. + isROI bool + // rof is the read only finalizer cast of the reconciler. + rof ReadOnlyFinalizer + // isROF (Read Only Finalizer) the reconciler only observes finalize. + isROF bool + // isLeader the instance of the reconciler is the elected leader. + isLeader bool +} + +func newState(key string, r *reconcilerImpl) (*state, error) { + // Convert the namespace/name string into a distinct namespace and name. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, fmt.Errorf("invalid resource key: %s", key) + } + + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + rof: rof, + isROF: isROF, + isLeader: isLeader, + }, nil +} + +// isNotLeaderNorObserver checks to see if this reconciler with the current +// state is enabled to do any work or not. +// isNotLeaderNorObserver returns true when there is no work possible for the +// reconciler. +func (s *state) isNotLeaderNorObserver() bool { + if !s.isLeader && !s.isROI && !s.isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1beta1.Channel) (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/injection/reconciler/messaging/v1beta1/inmemorychannel/controller.go b/pkg/client/injection/reconciler/messaging/v1beta1/inmemorychannel/controller.go new file mode 100644 index 00000000000..3d4cf46de14 --- /dev/null +++ b/pkg/client/injection/reconciler/messaging/v1beta1/inmemorychannel/controller.go @@ -0,0 +1,153 @@ +/* +Copyright 2021 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 inmemorychannel + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + zap "go.uber.org/zap" + 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" + inmemorychannel "knative.dev/eventing/pkg/client/injection/informers/messaging/v1beta1/inmemorychannel" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "inmemorychannel-controller" + defaultFinalizerName = "inmemorychannels.messaging.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 by 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)) + } + + inmemorychannelInformer := inmemorychannel.Get(ctx) + + lister := inmemorychannelInformer.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, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "messaging.knative.dev.InMemoryChannel"), + ) + + impl := controller.NewImpl(rec, logger, ctrTypeName) + 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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/messaging/v1beta1/inmemorychannel/reconciler.go b/pkg/client/injection/reconciler/messaging/v1beta1/inmemorychannel/reconciler.go new file mode 100644 index 00000000000..32a9929a0aa --- /dev/null +++ b/pkg/client/injection/reconciler/messaging/v1beta1/inmemorychannel/reconciler.go @@ -0,0 +1,455 @@ +/* +Copyright 2021 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 inmemorychannel + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + 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" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + messagingv1beta1 "knative.dev/eventing/pkg/client/listers/messaging/v1beta1" + 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 v1beta1.InMemoryChannel. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1beta1.InMemoryChannel. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1beta1.InMemoryChannel) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.InMemoryChannel. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1beta1.InMemoryChannel. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1beta1.InMemoryChannel) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta1.InMemoryChannel if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1beta1.InMemoryChannel. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1beta1.InMemoryChannel) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.InMemoryChannel if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1beta1.InMemoryChannel. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1beta1.InMemoryChannel) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1beta1.InMemoryChannel) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1beta1.InMemoryChannel 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 messagingv1beta1.InMemoryChannelLister + + // 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 messagingv1beta1.InMemoryChannelLister, 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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 controller.NewSkipKey(key) + } + + // 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.InMemoryChannels(s.namespace) + + original, err := getter.Get(s.name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + 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: + // 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.Event(resource, event.EventType, event.Reason, event.Error()) + + // 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 *v1beta1.InMemoryChannel, desired *v1beta1.InMemoryChannel) 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.MessagingV1beta1().InMemoryChannels(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 equality.Semantic.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.MessagingV1beta1().InMemoryChannels(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 *v1beta1.InMemoryChannel) (*v1beta1.InMemoryChannel, error) { + + getter := r.Lister.InMemoryChannels(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.MessagingV1beta1().InMemoryChannels(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 *v1beta1.InMemoryChannel) (*v1beta1.InMemoryChannel, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1beta1.InMemoryChannel, reconcileEvent reconciler.Event) (*v1beta1.InMemoryChannel, 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/messaging/v1beta1/inmemorychannel/state.go b/pkg/client/injection/reconciler/messaging/v1beta1/inmemorychannel/state.go new file mode 100644 index 00000000000..80aa0dc7477 --- /dev/null +++ b/pkg/client/injection/reconciler/messaging/v1beta1/inmemorychannel/state.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 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 inmemorychannel + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + 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 + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi is the read only interface cast of the reconciler. + roi ReadOnlyInterface + // isROI (Read Only Interface) the reconciler only observes reconciliation. + isROI bool + // rof is the read only finalizer cast of the reconciler. + rof ReadOnlyFinalizer + // isROF (Read Only Finalizer) the reconciler only observes finalize. + isROF bool + // isLeader the instance of the reconciler is the elected leader. + isLeader bool +} + +func newState(key string, r *reconcilerImpl) (*state, error) { + // Convert the namespace/name string into a distinct namespace and name. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, fmt.Errorf("invalid resource key: %s", key) + } + + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + rof: rof, + isROF: isROF, + isLeader: isLeader, + }, nil +} + +// isNotLeaderNorObserver checks to see if this reconciler with the current +// state is enabled to do any work or not. +// isNotLeaderNorObserver returns true when there is no work possible for the +// reconciler. +func (s *state) isNotLeaderNorObserver() bool { + if !s.isLeader && !s.isROI && !s.isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1beta1.InMemoryChannel) (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/injection/reconciler/messaging/v1beta1/subscription/controller.go b/pkg/client/injection/reconciler/messaging/v1beta1/subscription/controller.go new file mode 100644 index 00000000000..5eacad2d373 --- /dev/null +++ b/pkg/client/injection/reconciler/messaging/v1beta1/subscription/controller.go @@ -0,0 +1,153 @@ +/* +Copyright 2021 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 subscription + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + zap "go.uber.org/zap" + 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" + subscription "knative.dev/eventing/pkg/client/injection/informers/messaging/v1beta1/subscription" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "subscription-controller" + defaultFinalizerName = "subscriptions.messaging.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 by 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)) + } + + subscriptionInformer := subscription.Get(ctx) + + lister := subscriptionInformer.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, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "messaging.knative.dev.Subscription"), + ) + + impl := controller.NewImpl(rec, logger, ctrTypeName) + 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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/messaging/v1beta1/subscription/reconciler.go b/pkg/client/injection/reconciler/messaging/v1beta1/subscription/reconciler.go new file mode 100644 index 00000000000..800726b00aa --- /dev/null +++ b/pkg/client/injection/reconciler/messaging/v1beta1/subscription/reconciler.go @@ -0,0 +1,455 @@ +/* +Copyright 2021 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 subscription + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + 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" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + messagingv1beta1 "knative.dev/eventing/pkg/client/listers/messaging/v1beta1" + 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 v1beta1.Subscription. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1beta1.Subscription. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1beta1.Subscription) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.Subscription. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1beta1.Subscription. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1beta1.Subscription) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta1.Subscription if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1beta1.Subscription. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1beta1.Subscription) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.Subscription if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1beta1.Subscription. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1beta1.Subscription) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1beta1.Subscription) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1beta1.Subscription 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 messagingv1beta1.SubscriptionLister + + // 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 messagingv1beta1.SubscriptionLister, 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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 controller.NewSkipKey(key) + } + + // 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.Subscriptions(s.namespace) + + original, err := getter.Get(s.name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + 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: + // 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.Event(resource, event.EventType, event.Reason, event.Error()) + + // 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 *v1beta1.Subscription, desired *v1beta1.Subscription) 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.MessagingV1beta1().Subscriptions(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 equality.Semantic.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.MessagingV1beta1().Subscriptions(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 *v1beta1.Subscription) (*v1beta1.Subscription, error) { + + getter := r.Lister.Subscriptions(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.MessagingV1beta1().Subscriptions(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 *v1beta1.Subscription) (*v1beta1.Subscription, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1beta1.Subscription, reconcileEvent reconciler.Event) (*v1beta1.Subscription, 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/messaging/v1beta1/subscription/state.go b/pkg/client/injection/reconciler/messaging/v1beta1/subscription/state.go new file mode 100644 index 00000000000..27cc2325a29 --- /dev/null +++ b/pkg/client/injection/reconciler/messaging/v1beta1/subscription/state.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 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 subscription + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" + 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 + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi is the read only interface cast of the reconciler. + roi ReadOnlyInterface + // isROI (Read Only Interface) the reconciler only observes reconciliation. + isROI bool + // rof is the read only finalizer cast of the reconciler. + rof ReadOnlyFinalizer + // isROF (Read Only Finalizer) the reconciler only observes finalize. + isROF bool + // isLeader the instance of the reconciler is the elected leader. + isLeader bool +} + +func newState(key string, r *reconcilerImpl) (*state, error) { + // Convert the namespace/name string into a distinct namespace and name. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, fmt.Errorf("invalid resource key: %s", key) + } + + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + rof: rof, + isROF: isROF, + isLeader: isLeader, + }, nil +} + +// isNotLeaderNorObserver checks to see if this reconciler with the current +// state is enabled to do any work or not. +// isNotLeaderNorObserver returns true when there is no work possible for the +// reconciler. +func (s *state) isNotLeaderNorObserver() bool { + if !s.isLeader && !s.isROI && !s.isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1beta1.Subscription) (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/injection/reconciler/sources/v1alpha2/apiserversource/controller.go b/pkg/client/injection/reconciler/sources/v1alpha2/apiserversource/controller.go new file mode 100644 index 00000000000..7cd228ece1d --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1alpha2/apiserversource/controller.go @@ -0,0 +1,153 @@ +/* +Copyright 2021 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 apiserversource + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + zap "go.uber.org/zap" + 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" + apiserversource "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha2/apiserversource" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "apiserversource-controller" + defaultFinalizerName = "apiserversources.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 by 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)) + } + + apiserversourceInformer := apiserversource.Get(ctx) + + lister := apiserversourceInformer.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, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "sources.knative.dev.ApiServerSource"), + ) + + impl := controller.NewImpl(rec, logger, ctrTypeName) + 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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/v1alpha2/apiserversource/reconciler.go b/pkg/client/injection/reconciler/sources/v1alpha2/apiserversource/reconciler.go new file mode 100644 index 00000000000..2f53ee67550 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1alpha2/apiserversource/reconciler.go @@ -0,0 +1,455 @@ +/* +Copyright 2021 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 apiserversource + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + 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" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + sourcesv1alpha2 "knative.dev/eventing/pkg/client/listers/sources/v1alpha2" + 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 v1alpha2.ApiServerSource. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1alpha2.ApiServerSource. 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 *v1alpha2.ApiServerSource) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1alpha2.ApiServerSource. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1alpha2.ApiServerSource. 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 *v1alpha2.ApiServerSource) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1alpha2.ApiServerSource if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1alpha2.ApiServerSource. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1alpha2.ApiServerSource) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1alpha2.ApiServerSource 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 v1alpha2.ApiServerSource. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1alpha2.ApiServerSource) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1alpha2.ApiServerSource) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1alpha2.ApiServerSource 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 sourcesv1alpha2.ApiServerSourceLister + + // 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 sourcesv1alpha2.ApiServerSourceLister, 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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 controller.NewSkipKey(key) + } + + // 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.ApiServerSources(s.namespace) + + original, err := getter.Get(s.name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + 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: + // 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.Event(resource, event.EventType, event.Reason, event.Error()) + + // 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 *v1alpha2.ApiServerSource, desired *v1alpha2.ApiServerSource) 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.SourcesV1alpha2().ApiServerSources(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 equality.Semantic.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.SourcesV1alpha2().ApiServerSources(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 *v1alpha2.ApiServerSource) (*v1alpha2.ApiServerSource, error) { + + getter := r.Lister.ApiServerSources(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.SourcesV1alpha2().ApiServerSources(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 *v1alpha2.ApiServerSource) (*v1alpha2.ApiServerSource, 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 *v1alpha2.ApiServerSource, reconcileEvent reconciler.Event) (*v1alpha2.ApiServerSource, 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/v1alpha2/apiserversource/state.go b/pkg/client/injection/reconciler/sources/v1alpha2/apiserversource/state.go new file mode 100644 index 00000000000..4765177eb21 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1alpha2/apiserversource/state.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 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 apiserversource + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + 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 + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi 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 *v1alpha2.ApiServerSource) (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/injection/reconciler/sources/v1alpha2/containersource/controller.go b/pkg/client/injection/reconciler/sources/v1alpha2/containersource/controller.go new file mode 100644 index 00000000000..95bb4679443 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1alpha2/containersource/controller.go @@ -0,0 +1,153 @@ +/* +Copyright 2021 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 containersource + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + zap "go.uber.org/zap" + 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" + containersource "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha2/containersource" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "containersource-controller" + defaultFinalizerName = "containersources.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 by 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)) + } + + containersourceInformer := containersource.Get(ctx) + + lister := containersourceInformer.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, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "sources.knative.dev.ContainerSource"), + ) + + impl := controller.NewImpl(rec, logger, ctrTypeName) + 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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/v1alpha2/containersource/reconciler.go b/pkg/client/injection/reconciler/sources/v1alpha2/containersource/reconciler.go new file mode 100644 index 00000000000..abc3f0501c8 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1alpha2/containersource/reconciler.go @@ -0,0 +1,455 @@ +/* +Copyright 2021 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 containersource + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + 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" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + sourcesv1alpha2 "knative.dev/eventing/pkg/client/listers/sources/v1alpha2" + 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 v1alpha2.ContainerSource. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1alpha2.ContainerSource. 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 *v1alpha2.ContainerSource) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1alpha2.ContainerSource. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1alpha2.ContainerSource. 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 *v1alpha2.ContainerSource) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1alpha2.ContainerSource if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1alpha2.ContainerSource. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1alpha2.ContainerSource) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1alpha2.ContainerSource 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 v1alpha2.ContainerSource. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1alpha2.ContainerSource) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1alpha2.ContainerSource) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1alpha2.ContainerSource 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 sourcesv1alpha2.ContainerSourceLister + + // 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 sourcesv1alpha2.ContainerSourceLister, 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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 controller.NewSkipKey(key) + } + + // 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.ContainerSources(s.namespace) + + original, err := getter.Get(s.name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + 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: + // 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.Event(resource, event.EventType, event.Reason, event.Error()) + + // 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 *v1alpha2.ContainerSource, desired *v1alpha2.ContainerSource) 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.SourcesV1alpha2().ContainerSources(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 equality.Semantic.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.SourcesV1alpha2().ContainerSources(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 *v1alpha2.ContainerSource) (*v1alpha2.ContainerSource, error) { + + getter := r.Lister.ContainerSources(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.SourcesV1alpha2().ContainerSources(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 *v1alpha2.ContainerSource) (*v1alpha2.ContainerSource, 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 *v1alpha2.ContainerSource, reconcileEvent reconciler.Event) (*v1alpha2.ContainerSource, 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/v1alpha2/containersource/state.go b/pkg/client/injection/reconciler/sources/v1alpha2/containersource/state.go new file mode 100644 index 00000000000..d5e6883b282 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1alpha2/containersource/state.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 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 containersource + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + 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 + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi 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 *v1alpha2.ContainerSource) (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/injection/reconciler/sources/v1alpha2/pingsource/controller.go b/pkg/client/injection/reconciler/sources/v1alpha2/pingsource/controller.go new file mode 100644 index 00000000000..b66b5161126 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1alpha2/pingsource/controller.go @@ -0,0 +1,153 @@ +/* +Copyright 2021 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" + + zap "go.uber.org/zap" + 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/v1alpha2/pingsource" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + 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 by 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, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "sources.knative.dev.PingSource"), + ) + + impl := controller.NewImpl(rec, logger, ctrTypeName) + 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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/v1alpha2/pingsource/reconciler.go b/pkg/client/injection/reconciler/sources/v1alpha2/pingsource/reconciler.go new file mode 100644 index 00000000000..2797c78cc49 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1alpha2/pingsource/reconciler.go @@ -0,0 +1,455 @@ +/* +Copyright 2021 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" + + 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" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + sourcesv1alpha2 "knative.dev/eventing/pkg/client/listers/sources/v1alpha2" + 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 v1alpha2.PingSource. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1alpha2.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 *v1alpha2.PingSource) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1alpha2.PingSource. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1alpha2.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 *v1alpha2.PingSource) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1alpha2.PingSource if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1alpha2.PingSource. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1alpha2.PingSource) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1alpha2.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 v1alpha2.PingSource. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1alpha2.PingSource) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1alpha2.PingSource) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1alpha2.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 sourcesv1alpha2.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 sourcesv1alpha2.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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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 controller.NewSkipKey(key) + } + + // 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 and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + 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: + // 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.Event(resource, event.EventType, event.Reason, event.Error()) + + // 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 *v1alpha2.PingSource, desired *v1alpha2.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.SourcesV1alpha2().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 equality.Semantic.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.SourcesV1alpha2().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 *v1alpha2.PingSource) (*v1alpha2.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.SourcesV1alpha2().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 *v1alpha2.PingSource) (*v1alpha2.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 *v1alpha2.PingSource, reconcileEvent reconciler.Event) (*v1alpha2.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/v1alpha2/pingsource/state.go b/pkg/client/injection/reconciler/sources/v1alpha2/pingsource/state.go new file mode 100644 index 00000000000..596235ca16a --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1alpha2/pingsource/state.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 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" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + 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 + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi 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 *v1alpha2.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/injection/reconciler/sources/v1beta1/apiserversource/controller.go b/pkg/client/injection/reconciler/sources/v1beta1/apiserversource/controller.go new file mode 100644 index 00000000000..d8aa1cd8f3d --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta1/apiserversource/controller.go @@ -0,0 +1,153 @@ +/* +Copyright 2021 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 apiserversource + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + zap "go.uber.org/zap" + 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" + apiserversource "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/apiserversource" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "apiserversource-controller" + defaultFinalizerName = "apiserversources.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 by 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)) + } + + apiserversourceInformer := apiserversource.Get(ctx) + + lister := apiserversourceInformer.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, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "sources.knative.dev.ApiServerSource"), + ) + + impl := controller.NewImpl(rec, logger, ctrTypeName) + 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + rec.Recorder = createRecorder(ctx, agentName) + + return impl +} + +func createRecorder(ctx context.Context, agentName string) record.EventRecorder { + logger := logging.FromContext(ctx) + + recorder := controller.GetEventRecorder(ctx) + if recorder == nil { + // Create event broadcaster + logger.Debug("Creating event broadcaster") + eventBroadcaster := record.NewBroadcaster() + watches := []watch.Interface{ + eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof), + eventBroadcaster.StartRecordingToSink( + &v1.EventSinkImpl{Interface: kubeclient.Get(ctx).CoreV1().Events("")}), + } + recorder = eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: agentName}) + go func() { + <-ctx.Done() + for _, w := range watches { + w.Stop() + } + }() + } + + return recorder +} + +func init() { + versionedscheme.AddToScheme(scheme.Scheme) +} diff --git a/pkg/client/injection/reconciler/sources/v1beta1/apiserversource/reconciler.go b/pkg/client/injection/reconciler/sources/v1beta1/apiserversource/reconciler.go new file mode 100644 index 00000000000..cafcf4c81f4 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta1/apiserversource/reconciler.go @@ -0,0 +1,455 @@ +/* +Copyright 2021 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 apiserversource + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + 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" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + sourcesv1beta1 "knative.dev/eventing/pkg/client/listers/sources/v1beta1" + controller "knative.dev/pkg/controller" + 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 v1beta1.ApiServerSource. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1beta1.ApiServerSource. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1beta1.ApiServerSource) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.ApiServerSource. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1beta1.ApiServerSource. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1beta1.ApiServerSource) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta1.ApiServerSource if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1beta1.ApiServerSource. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1beta1.ApiServerSource) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.ApiServerSource if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1beta1.ApiServerSource. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1beta1.ApiServerSource) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1beta1.ApiServerSource) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1beta1.ApiServerSource resources. +type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware. + reconciler.LeaderAwareFuncs + + // Client is used to write back status updates. + Client versioned.Interface + + // Listers index properties about resources. + Lister sourcesv1beta1.ApiServerSourceLister + + // Recorder is an event recorder for recording Event resources to the + // Kubernetes API. + Recorder record.EventRecorder + + // configStore allows for decorating a context with config maps. + // +optional + configStore reconciler.ConfigStore + + // reconciler is the implementation of the business logic of the resource. + reconciler Interface + + // finalizerName is the name of the finalizer to reconcile. + finalizerName string + + // skipStatusUpdates configures whether or not this reconciler automatically updates + // the status of the reconciled resource. + skipStatusUpdates bool +} + +// Check that our Reconciler implements controller.Reconciler. +var _ controller.Reconciler = (*reconcilerImpl)(nil) + +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + +func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister sourcesv1beta1.ApiServerSourceLister, 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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 controller.NewSkipKey(key) + } + + // 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.ApiServerSources(s.namespace) + + original, err := getter.Get(s.name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + 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: + // 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.Event(resource, event.EventType, event.Reason, event.Error()) + + // 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 *v1beta1.ApiServerSource, desired *v1beta1.ApiServerSource) error { + existing = existing.DeepCopy() + return reconciler.RetryUpdateConflicts(func(attempts int) (err error) { + // The first iteration tries to use the injectionInformer's state, subsequent attempts fetch the latest state via API. + if attempts > 0 { + + getter := r.Client.SourcesV1beta1().ApiServerSources(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 equality.Semantic.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.SourcesV1beta1().ApiServerSources(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 *v1beta1.ApiServerSource) (*v1beta1.ApiServerSource, error) { + + getter := r.Lister.ApiServerSources(resource.Namespace) + + actual, err := getter.Get(resource.Name) + if err != nil { + return resource, err + } + + // Don't modify the informers copy. + existing := actual.DeepCopy() + + var finalizers []string + + // If there's nothing to update, just return. + existingFinalizers := sets.NewString(existing.Finalizers...) + desiredFinalizers := sets.NewString(resource.Finalizers...) + + if desiredFinalizers.Has(r.finalizerName) { + if existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Add the finalizer. + finalizers = append(existing.Finalizers, r.finalizerName) + } else { + if !existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Remove the finalizer. + existingFinalizers.Delete(r.finalizerName) + finalizers = existingFinalizers.List() + } + + mergePatch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "finalizers": finalizers, + "resourceVersion": existing.ResourceVersion, + }, + } + + patch, err := json.Marshal(mergePatch) + if err != nil { + return resource, err + } + + patcher := r.Client.SourcesV1beta1().ApiServerSources(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 *v1beta1.ApiServerSource) (*v1beta1.ApiServerSource, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1beta1.ApiServerSource, reconcileEvent reconciler.Event) (*v1beta1.ApiServerSource, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + if resource.GetDeletionTimestamp().IsZero() { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + if reconcileEvent != nil { + var event *reconciler.ReconcilerEvent + if reconciler.EventAs(reconcileEvent, &event) { + if event.EventType == v1.EventTypeNormal { + finalizers.Delete(r.finalizerName) + } + } + } else { + finalizers.Delete(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} diff --git a/pkg/client/injection/reconciler/sources/v1beta1/apiserversource/state.go b/pkg/client/injection/reconciler/sources/v1beta1/apiserversource/state.go new file mode 100644 index 00000000000..a42ad010160 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta1/apiserversource/state.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 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 apiserversource + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + 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 + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi is the read only interface cast of the reconciler. + roi ReadOnlyInterface + // isROI (Read Only Interface) the reconciler only observes reconciliation. + isROI bool + // rof is the read only finalizer cast of the reconciler. + rof ReadOnlyFinalizer + // isROF (Read Only Finalizer) the reconciler only observes finalize. + isROF bool + // isLeader the instance of the reconciler is the elected leader. + isLeader bool +} + +func newState(key string, r *reconcilerImpl) (*state, error) { + // Convert the namespace/name string into a distinct namespace and name. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, fmt.Errorf("invalid resource key: %s", key) + } + + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + rof: rof, + isROF: isROF, + isLeader: isLeader, + }, nil +} + +// isNotLeaderNorObserver checks to see if this reconciler with the current +// state is enabled to do any work or not. +// isNotLeaderNorObserver returns true when there is no work possible for the +// reconciler. +func (s *state) isNotLeaderNorObserver() bool { + if !s.isLeader && !s.isROI && !s.isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1beta1.ApiServerSource) (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/injection/reconciler/sources/v1beta1/containersource/controller.go b/pkg/client/injection/reconciler/sources/v1beta1/containersource/controller.go new file mode 100644 index 00000000000..1377b8f7eca --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta1/containersource/controller.go @@ -0,0 +1,153 @@ +/* +Copyright 2021 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 containersource + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + zap "go.uber.org/zap" + 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" + containersource "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/containersource" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "containersource-controller" + defaultFinalizerName = "containersources.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 by 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)) + } + + containersourceInformer := containersource.Get(ctx) + + lister := containersourceInformer.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, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "sources.knative.dev.ContainerSource"), + ) + + impl := controller.NewImpl(rec, logger, ctrTypeName) + 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + rec.Recorder = createRecorder(ctx, agentName) + + return impl +} + +func createRecorder(ctx context.Context, agentName string) record.EventRecorder { + logger := logging.FromContext(ctx) + + recorder := controller.GetEventRecorder(ctx) + if recorder == nil { + // Create event broadcaster + logger.Debug("Creating event broadcaster") + eventBroadcaster := record.NewBroadcaster() + watches := []watch.Interface{ + eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof), + eventBroadcaster.StartRecordingToSink( + &v1.EventSinkImpl{Interface: kubeclient.Get(ctx).CoreV1().Events("")}), + } + recorder = eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: agentName}) + go func() { + <-ctx.Done() + for _, w := range watches { + w.Stop() + } + }() + } + + return recorder +} + +func init() { + versionedscheme.AddToScheme(scheme.Scheme) +} diff --git a/pkg/client/injection/reconciler/sources/v1beta1/containersource/reconciler.go b/pkg/client/injection/reconciler/sources/v1beta1/containersource/reconciler.go new file mode 100644 index 00000000000..3123b47246c --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta1/containersource/reconciler.go @@ -0,0 +1,455 @@ +/* +Copyright 2021 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 containersource + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + 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" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + sourcesv1beta1 "knative.dev/eventing/pkg/client/listers/sources/v1beta1" + controller "knative.dev/pkg/controller" + 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 v1beta1.ContainerSource. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1beta1.ContainerSource. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1beta1.ContainerSource) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.ContainerSource. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1beta1.ContainerSource. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1beta1.ContainerSource) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta1.ContainerSource if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1beta1.ContainerSource. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1beta1.ContainerSource) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.ContainerSource if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1beta1.ContainerSource. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1beta1.ContainerSource) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1beta1.ContainerSource) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1beta1.ContainerSource resources. +type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware. + reconciler.LeaderAwareFuncs + + // Client is used to write back status updates. + Client versioned.Interface + + // Listers index properties about resources. + Lister sourcesv1beta1.ContainerSourceLister + + // Recorder is an event recorder for recording Event resources to the + // Kubernetes API. + Recorder record.EventRecorder + + // configStore allows for decorating a context with config maps. + // +optional + configStore reconciler.ConfigStore + + // reconciler is the implementation of the business logic of the resource. + reconciler Interface + + // finalizerName is the name of the finalizer to reconcile. + finalizerName string + + // skipStatusUpdates configures whether or not this reconciler automatically updates + // the status of the reconciled resource. + skipStatusUpdates bool +} + +// Check that our Reconciler implements controller.Reconciler. +var _ controller.Reconciler = (*reconcilerImpl)(nil) + +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + +func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister sourcesv1beta1.ContainerSourceLister, 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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 controller.NewSkipKey(key) + } + + // 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.ContainerSources(s.namespace) + + original, err := getter.Get(s.name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + 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: + // 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.Event(resource, event.EventType, event.Reason, event.Error()) + + // 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 *v1beta1.ContainerSource, desired *v1beta1.ContainerSource) error { + existing = existing.DeepCopy() + return reconciler.RetryUpdateConflicts(func(attempts int) (err error) { + // The first iteration tries to use the injectionInformer's state, subsequent attempts fetch the latest state via API. + if attempts > 0 { + + getter := r.Client.SourcesV1beta1().ContainerSources(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 equality.Semantic.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.SourcesV1beta1().ContainerSources(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 *v1beta1.ContainerSource) (*v1beta1.ContainerSource, error) { + + getter := r.Lister.ContainerSources(resource.Namespace) + + actual, err := getter.Get(resource.Name) + if err != nil { + return resource, err + } + + // Don't modify the informers copy. + existing := actual.DeepCopy() + + var finalizers []string + + // If there's nothing to update, just return. + existingFinalizers := sets.NewString(existing.Finalizers...) + desiredFinalizers := sets.NewString(resource.Finalizers...) + + if desiredFinalizers.Has(r.finalizerName) { + if existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Add the finalizer. + finalizers = append(existing.Finalizers, r.finalizerName) + } else { + if !existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Remove the finalizer. + existingFinalizers.Delete(r.finalizerName) + finalizers = existingFinalizers.List() + } + + mergePatch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "finalizers": finalizers, + "resourceVersion": existing.ResourceVersion, + }, + } + + patch, err := json.Marshal(mergePatch) + if err != nil { + return resource, err + } + + patcher := r.Client.SourcesV1beta1().ContainerSources(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 *v1beta1.ContainerSource) (*v1beta1.ContainerSource, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1beta1.ContainerSource, reconcileEvent reconciler.Event) (*v1beta1.ContainerSource, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + if resource.GetDeletionTimestamp().IsZero() { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + if reconcileEvent != nil { + var event *reconciler.ReconcilerEvent + if reconciler.EventAs(reconcileEvent, &event) { + if event.EventType == v1.EventTypeNormal { + finalizers.Delete(r.finalizerName) + } + } + } else { + finalizers.Delete(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} diff --git a/pkg/client/injection/reconciler/sources/v1beta1/containersource/state.go b/pkg/client/injection/reconciler/sources/v1beta1/containersource/state.go new file mode 100644 index 00000000000..3e656e14920 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta1/containersource/state.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 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 containersource + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + 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 + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi is the read only interface cast of the reconciler. + roi ReadOnlyInterface + // isROI (Read Only Interface) the reconciler only observes reconciliation. + isROI bool + // rof is the read only finalizer cast of the reconciler. + rof ReadOnlyFinalizer + // isROF (Read Only Finalizer) the reconciler only observes finalize. + isROF bool + // isLeader the instance of the reconciler is the elected leader. + isLeader bool +} + +func newState(key string, r *reconcilerImpl) (*state, error) { + // Convert the namespace/name string into a distinct namespace and name. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, fmt.Errorf("invalid resource key: %s", key) + } + + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + rof: rof, + isROF: isROF, + isLeader: isLeader, + }, nil +} + +// isNotLeaderNorObserver checks to see if this reconciler with the current +// state is enabled to do any work or not. +// isNotLeaderNorObserver returns true when there is no work possible for the +// reconciler. +func (s *state) isNotLeaderNorObserver() bool { + if !s.isLeader && !s.isROI && !s.isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1beta1.ContainerSource) (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/injection/reconciler/sources/v1beta1/pingsource/controller.go b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/controller.go new file mode 100644 index 00000000000..7f70ea32002 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/controller.go @@ -0,0 +1,153 @@ +/* +Copyright 2021 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" + + zap "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + scheme "k8s.io/client-go/kubernetes/scheme" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + record "k8s.io/client-go/tools/record" + versionedscheme "knative.dev/eventing/pkg/client/clientset/versioned/scheme" + client "knative.dev/eventing/pkg/client/injection/client" + pingsource "knative.dev/eventing/pkg/client/injection/informers/sources/v1beta1/pingsource" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + 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 by 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, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "sources.knative.dev.PingSource"), + ) + + impl := controller.NewImpl(rec, logger, ctrTypeName) + 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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + rec.Recorder = createRecorder(ctx, agentName) + + return impl +} + +func createRecorder(ctx context.Context, agentName string) record.EventRecorder { + logger := logging.FromContext(ctx) + + recorder := controller.GetEventRecorder(ctx) + if recorder == nil { + // Create event broadcaster + logger.Debug("Creating event broadcaster") + eventBroadcaster := record.NewBroadcaster() + watches := []watch.Interface{ + eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof), + eventBroadcaster.StartRecordingToSink( + &v1.EventSinkImpl{Interface: kubeclient.Get(ctx).CoreV1().Events("")}), + } + recorder = eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: agentName}) + go func() { + <-ctx.Done() + for _, w := range watches { + w.Stop() + } + }() + } + + return recorder +} + +func init() { + versionedscheme.AddToScheme(scheme.Scheme) +} diff --git a/pkg/client/injection/reconciler/sources/v1beta1/pingsource/reconciler.go b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/reconciler.go new file mode 100644 index 00000000000..1fa8c3b7bdf --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/reconciler.go @@ -0,0 +1,455 @@ +/* +Copyright 2021 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" + + 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" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + sourcesv1beta1 "knative.dev/eventing/pkg/client/listers/sources/v1beta1" + controller "knative.dev/pkg/controller" + 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 v1beta1.PingSource. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1beta1.PingSource. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1beta1.PingSource) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.PingSource. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1beta1.PingSource. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1beta1.PingSource) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta1.PingSource if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1beta1.PingSource. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1beta1.PingSource) reconciler.Event +} + +// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.PingSource if they want to process tombstoned resources +// even when they are not the leader. Due to the nature of how finalizers are handled +// there are no guarantees that this will be called. +type ReadOnlyFinalizer interface { + // ObserveFinalizeKind implements custom logic to observe the final state of v1beta1.PingSource. + // This method should not write to the API. + ObserveFinalizeKind(ctx context.Context, o *v1beta1.PingSource) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1beta1.PingSource) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1beta1.PingSource resources. +type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware. + reconciler.LeaderAwareFuncs + + // Client is used to write back status updates. + Client versioned.Interface + + // Listers index properties about resources. + Lister sourcesv1beta1.PingSourceLister + + // Recorder is an event recorder for recording Event resources to the + // Kubernetes API. + Recorder record.EventRecorder + + // configStore allows for decorating a context with config maps. + // +optional + configStore reconciler.ConfigStore + + // reconciler is the implementation of the business logic of the resource. + reconciler Interface + + // finalizerName is the name of the finalizer to reconcile. + finalizerName string + + // skipStatusUpdates configures whether or not this reconciler automatically updates + // the status of the reconciled resource. + skipStatusUpdates bool +} + +// Check that our Reconciler implements controller.Reconciler. +var _ controller.Reconciler = (*reconcilerImpl)(nil) + +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + +func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister sourcesv1beta1.PingSourceLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler { + // Check the options function input. It should be 0 or 1. + if len(options) > 1 { + logger.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 + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + 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 controller.NewSkipKey(key) + } + + // 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 and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + 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: + // 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.Event(resource, event.EventType, event.Reason, event.Error()) + + // 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 *v1beta1.PingSource, desired *v1beta1.PingSource) error { + existing = existing.DeepCopy() + return reconciler.RetryUpdateConflicts(func(attempts int) (err error) { + // The first iteration tries to use the injectionInformer's state, subsequent attempts fetch the latest state via API. + if attempts > 0 { + + getter := r.Client.SourcesV1beta1().PingSources(desired.Namespace) + + existing, err = getter.Get(ctx, desired.Name, metav1.GetOptions{}) + if err != nil { + return err + } + } + + // If there's nothing to update, just return. + if equality.Semantic.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.SourcesV1beta1().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 *v1beta1.PingSource) (*v1beta1.PingSource, error) { + + getter := r.Lister.PingSources(resource.Namespace) + + actual, err := getter.Get(resource.Name) + if err != nil { + return resource, err + } + + // Don't modify the informers copy. + existing := actual.DeepCopy() + + var finalizers []string + + // If there's nothing to update, just return. + existingFinalizers := sets.NewString(existing.Finalizers...) + desiredFinalizers := sets.NewString(resource.Finalizers...) + + if desiredFinalizers.Has(r.finalizerName) { + if existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Add the finalizer. + finalizers = append(existing.Finalizers, r.finalizerName) + } else { + if !existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Remove the finalizer. + existingFinalizers.Delete(r.finalizerName) + finalizers = existingFinalizers.List() + } + + mergePatch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "finalizers": finalizers, + "resourceVersion": existing.ResourceVersion, + }, + } + + patch, err := json.Marshal(mergePatch) + if err != nil { + return resource, err + } + + patcher := r.Client.SourcesV1beta1().PingSources(resource.Namespace) + + resourceName := resource.Name + 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 *v1beta1.PingSource) (*v1beta1.PingSource, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1beta1.PingSource, reconcileEvent reconciler.Event) (*v1beta1.PingSource, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + if resource.GetDeletionTimestamp().IsZero() { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + if reconcileEvent != nil { + var event *reconciler.ReconcilerEvent + if reconciler.EventAs(reconcileEvent, &event) { + if event.EventType == v1.EventTypeNormal { + finalizers.Delete(r.finalizerName) + } + } + } else { + finalizers.Delete(r.finalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} diff --git a/pkg/client/injection/reconciler/sources/v1beta1/pingsource/state.go b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/state.go new file mode 100644 index 00000000000..0f3041c78dc --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1beta1/pingsource/state.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package pingsource + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + 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 + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi is the read only interface cast of the reconciler. + roi ReadOnlyInterface + // isROI (Read Only Interface) the reconciler only observes reconciliation. + isROI bool + // rof is the read only finalizer cast of the reconciler. + rof ReadOnlyFinalizer + // isROF (Read Only Finalizer) the reconciler only observes finalize. + isROF bool + // isLeader the instance of the reconciler is the elected leader. + isLeader bool +} + +func newState(key string, r *reconcilerImpl) (*state, error) { + // Convert the namespace/name string into a distinct namespace and name. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, fmt.Errorf("invalid resource key: %s", key) + } + + roi, isROI := r.reconciler.(ReadOnlyInterface) + rof, isROF := r.reconciler.(ReadOnlyFinalizer) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + rof: rof, + isROF: isROF, + isLeader: isLeader, + }, nil +} + +// isNotLeaderNorObserver checks to see if this reconciler with the current +// state is enabled to do any work or not. +// isNotLeaderNorObserver returns true when there is no work possible for the +// reconciler. +func (s *state) isNotLeaderNorObserver() bool { + if !s.isLeader && !s.isROI && !s.isROF { + // If we are not the leader, and we don't implement either ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1beta1.PingSource) (string, doReconcile) { + if o.GetDeletionTimestamp().IsZero() { + if s.isLeader { + return 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/eventing/v1beta1/broker.go b/pkg/client/listers/eventing/v1beta1/broker.go new file mode 100644 index 00000000000..8cd5d9cdde4 --- /dev/null +++ b/pkg/client/listers/eventing/v1beta1/broker.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" +) + +// BrokerLister helps list Brokers. +// All objects returned here must be treated as read-only. +type BrokerLister interface { + // List lists all Brokers in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.Broker, err error) + // Brokers returns an object that can list and get Brokers. + Brokers(namespace string) BrokerNamespaceLister + BrokerListerExpansion +} + +// brokerLister implements the BrokerLister interface. +type brokerLister struct { + indexer cache.Indexer +} + +// NewBrokerLister returns a new BrokerLister. +func NewBrokerLister(indexer cache.Indexer) BrokerLister { + return &brokerLister{indexer: indexer} +} + +// List lists all Brokers in the indexer. +func (s *brokerLister) List(selector labels.Selector) (ret []*v1beta1.Broker, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.Broker)) + }) + return ret, err +} + +// Brokers returns an object that can list and get Brokers. +func (s *brokerLister) Brokers(namespace string) BrokerNamespaceLister { + return brokerNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// BrokerNamespaceLister helps list and get Brokers. +// All objects returned here must be treated as read-only. +type BrokerNamespaceLister interface { + // List lists all Brokers in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.Broker, err error) + // Get retrieves the Broker from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1beta1.Broker, error) + BrokerNamespaceListerExpansion +} + +// brokerNamespaceLister implements the BrokerNamespaceLister +// interface. +type brokerNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Brokers in the indexer for a given namespace. +func (s brokerNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.Broker, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.Broker)) + }) + return ret, err +} + +// Get retrieves the Broker from the indexer for a given namespace and name. +func (s brokerNamespaceLister) Get(name string) (*v1beta1.Broker, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta1.Resource("broker"), name) + } + return obj.(*v1beta1.Broker), nil +} diff --git a/pkg/client/listers/eventing/v1beta1/expansion_generated.go b/pkg/client/listers/eventing/v1beta1/expansion_generated.go index dd16eb0d7ca..d3417210e76 100644 --- a/pkg/client/listers/eventing/v1beta1/expansion_generated.go +++ b/pkg/client/listers/eventing/v1beta1/expansion_generated.go @@ -18,6 +18,14 @@ limitations under the License. package v1beta1 +// BrokerListerExpansion allows custom methods to be added to +// BrokerLister. +type BrokerListerExpansion interface{} + +// BrokerNamespaceListerExpansion allows custom methods to be added to +// BrokerNamespaceLister. +type BrokerNamespaceListerExpansion interface{} + // EventTypeListerExpansion allows custom methods to be added to // EventTypeLister. type EventTypeListerExpansion interface{} @@ -25,3 +33,11 @@ type EventTypeListerExpansion interface{} // EventTypeNamespaceListerExpansion allows custom methods to be added to // EventTypeNamespaceLister. type EventTypeNamespaceListerExpansion interface{} + +// TriggerListerExpansion allows custom methods to be added to +// TriggerLister. +type TriggerListerExpansion interface{} + +// TriggerNamespaceListerExpansion allows custom methods to be added to +// TriggerNamespaceLister. +type TriggerNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/eventing/v1beta1/trigger.go b/pkg/client/listers/eventing/v1beta1/trigger.go new file mode 100644 index 00000000000..f61054e4944 --- /dev/null +++ b/pkg/client/listers/eventing/v1beta1/trigger.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" +) + +// TriggerLister helps list Triggers. +// All objects returned here must be treated as read-only. +type TriggerLister interface { + // List lists all Triggers in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.Trigger, err error) + // Triggers returns an object that can list and get Triggers. + Triggers(namespace string) TriggerNamespaceLister + TriggerListerExpansion +} + +// triggerLister implements the TriggerLister interface. +type triggerLister struct { + indexer cache.Indexer +} + +// NewTriggerLister returns a new TriggerLister. +func NewTriggerLister(indexer cache.Indexer) TriggerLister { + return &triggerLister{indexer: indexer} +} + +// List lists all Triggers in the indexer. +func (s *triggerLister) List(selector labels.Selector) (ret []*v1beta1.Trigger, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.Trigger)) + }) + return ret, err +} + +// Triggers returns an object that can list and get Triggers. +func (s *triggerLister) Triggers(namespace string) TriggerNamespaceLister { + return triggerNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// TriggerNamespaceLister helps list and get Triggers. +// All objects returned here must be treated as read-only. +type TriggerNamespaceLister interface { + // List lists all Triggers in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.Trigger, err error) + // Get retrieves the Trigger from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1beta1.Trigger, error) + TriggerNamespaceListerExpansion +} + +// triggerNamespaceLister implements the TriggerNamespaceLister +// interface. +type triggerNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Triggers in the indexer for a given namespace. +func (s triggerNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.Trigger, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.Trigger)) + }) + return ret, err +} + +// Get retrieves the Trigger from the indexer for a given namespace and name. +func (s triggerNamespaceLister) Get(name string) (*v1beta1.Trigger, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta1.Resource("trigger"), name) + } + return obj.(*v1beta1.Trigger), nil +} diff --git a/pkg/client/listers/flows/v1beta1/expansion_generated.go b/pkg/client/listers/flows/v1beta1/expansion_generated.go new file mode 100644 index 00000000000..93a1d407292 --- /dev/null +++ b/pkg/client/listers/flows/v1beta1/expansion_generated.go @@ -0,0 +1,35 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +// ParallelListerExpansion allows custom methods to be added to +// ParallelLister. +type ParallelListerExpansion interface{} + +// ParallelNamespaceListerExpansion allows custom methods to be added to +// ParallelNamespaceLister. +type ParallelNamespaceListerExpansion interface{} + +// SequenceListerExpansion allows custom methods to be added to +// SequenceLister. +type SequenceListerExpansion interface{} + +// SequenceNamespaceListerExpansion allows custom methods to be added to +// SequenceNamespaceLister. +type SequenceNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/flows/v1beta1/parallel.go b/pkg/client/listers/flows/v1beta1/parallel.go new file mode 100644 index 00000000000..ddd405f7e44 --- /dev/null +++ b/pkg/client/listers/flows/v1beta1/parallel.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" +) + +// ParallelLister helps list Parallels. +// All objects returned here must be treated as read-only. +type ParallelLister interface { + // List lists all Parallels in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.Parallel, err error) + // Parallels returns an object that can list and get Parallels. + Parallels(namespace string) ParallelNamespaceLister + ParallelListerExpansion +} + +// parallelLister implements the ParallelLister interface. +type parallelLister struct { + indexer cache.Indexer +} + +// NewParallelLister returns a new ParallelLister. +func NewParallelLister(indexer cache.Indexer) ParallelLister { + return ¶llelLister{indexer: indexer} +} + +// List lists all Parallels in the indexer. +func (s *parallelLister) List(selector labels.Selector) (ret []*v1beta1.Parallel, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.Parallel)) + }) + return ret, err +} + +// Parallels returns an object that can list and get Parallels. +func (s *parallelLister) Parallels(namespace string) ParallelNamespaceLister { + return parallelNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// ParallelNamespaceLister helps list and get Parallels. +// All objects returned here must be treated as read-only. +type ParallelNamespaceLister interface { + // List lists all Parallels in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.Parallel, err error) + // Get retrieves the Parallel from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1beta1.Parallel, error) + ParallelNamespaceListerExpansion +} + +// parallelNamespaceLister implements the ParallelNamespaceLister +// interface. +type parallelNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Parallels in the indexer for a given namespace. +func (s parallelNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.Parallel, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.Parallel)) + }) + return ret, err +} + +// Get retrieves the Parallel from the indexer for a given namespace and name. +func (s parallelNamespaceLister) Get(name string) (*v1beta1.Parallel, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta1.Resource("parallel"), name) + } + return obj.(*v1beta1.Parallel), nil +} diff --git a/pkg/client/listers/flows/v1beta1/sequence.go b/pkg/client/listers/flows/v1beta1/sequence.go new file mode 100644 index 00000000000..d56571d36aa --- /dev/null +++ b/pkg/client/listers/flows/v1beta1/sequence.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/flows/v1beta1" +) + +// SequenceLister helps list Sequences. +// All objects returned here must be treated as read-only. +type SequenceLister interface { + // List lists all Sequences in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.Sequence, err error) + // Sequences returns an object that can list and get Sequences. + Sequences(namespace string) SequenceNamespaceLister + SequenceListerExpansion +} + +// sequenceLister implements the SequenceLister interface. +type sequenceLister struct { + indexer cache.Indexer +} + +// NewSequenceLister returns a new SequenceLister. +func NewSequenceLister(indexer cache.Indexer) SequenceLister { + return &sequenceLister{indexer: indexer} +} + +// List lists all Sequences in the indexer. +func (s *sequenceLister) List(selector labels.Selector) (ret []*v1beta1.Sequence, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.Sequence)) + }) + return ret, err +} + +// Sequences returns an object that can list and get Sequences. +func (s *sequenceLister) Sequences(namespace string) SequenceNamespaceLister { + return sequenceNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// SequenceNamespaceLister helps list and get Sequences. +// All objects returned here must be treated as read-only. +type SequenceNamespaceLister interface { + // List lists all Sequences in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.Sequence, err error) + // Get retrieves the Sequence from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1beta1.Sequence, error) + SequenceNamespaceListerExpansion +} + +// sequenceNamespaceLister implements the SequenceNamespaceLister +// interface. +type sequenceNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Sequences in the indexer for a given namespace. +func (s sequenceNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.Sequence, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.Sequence)) + }) + return ret, err +} + +// Get retrieves the Sequence from the indexer for a given namespace and name. +func (s sequenceNamespaceLister) Get(name string) (*v1beta1.Sequence, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta1.Resource("sequence"), name) + } + return obj.(*v1beta1.Sequence), nil +} diff --git a/pkg/client/listers/messaging/v1beta1/channel.go b/pkg/client/listers/messaging/v1beta1/channel.go new file mode 100644 index 00000000000..848531ef9b4 --- /dev/null +++ b/pkg/client/listers/messaging/v1beta1/channel.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" +) + +// ChannelLister helps list Channels. +// All objects returned here must be treated as read-only. +type ChannelLister interface { + // List lists all Channels in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.Channel, err error) + // Channels returns an object that can list and get Channels. + Channels(namespace string) ChannelNamespaceLister + ChannelListerExpansion +} + +// channelLister implements the ChannelLister interface. +type channelLister struct { + indexer cache.Indexer +} + +// NewChannelLister returns a new ChannelLister. +func NewChannelLister(indexer cache.Indexer) ChannelLister { + return &channelLister{indexer: indexer} +} + +// List lists all Channels in the indexer. +func (s *channelLister) List(selector labels.Selector) (ret []*v1beta1.Channel, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.Channel)) + }) + return ret, err +} + +// Channels returns an object that can list and get Channels. +func (s *channelLister) Channels(namespace string) ChannelNamespaceLister { + return channelNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// ChannelNamespaceLister helps list and get Channels. +// All objects returned here must be treated as read-only. +type ChannelNamespaceLister interface { + // List lists all Channels in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.Channel, err error) + // Get retrieves the Channel from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1beta1.Channel, error) + ChannelNamespaceListerExpansion +} + +// channelNamespaceLister implements the ChannelNamespaceLister +// interface. +type channelNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Channels in the indexer for a given namespace. +func (s channelNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.Channel, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.Channel)) + }) + return ret, err +} + +// Get retrieves the Channel from the indexer for a given namespace and name. +func (s channelNamespaceLister) Get(name string) (*v1beta1.Channel, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta1.Resource("channel"), name) + } + return obj.(*v1beta1.Channel), nil +} diff --git a/pkg/client/listers/messaging/v1beta1/expansion_generated.go b/pkg/client/listers/messaging/v1beta1/expansion_generated.go new file mode 100644 index 00000000000..b5c43a81e65 --- /dev/null +++ b/pkg/client/listers/messaging/v1beta1/expansion_generated.go @@ -0,0 +1,43 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +// ChannelListerExpansion allows custom methods to be added to +// ChannelLister. +type ChannelListerExpansion interface{} + +// ChannelNamespaceListerExpansion allows custom methods to be added to +// ChannelNamespaceLister. +type ChannelNamespaceListerExpansion interface{} + +// InMemoryChannelListerExpansion allows custom methods to be added to +// InMemoryChannelLister. +type InMemoryChannelListerExpansion interface{} + +// InMemoryChannelNamespaceListerExpansion allows custom methods to be added to +// InMemoryChannelNamespaceLister. +type InMemoryChannelNamespaceListerExpansion interface{} + +// SubscriptionListerExpansion allows custom methods to be added to +// SubscriptionLister. +type SubscriptionListerExpansion interface{} + +// SubscriptionNamespaceListerExpansion allows custom methods to be added to +// SubscriptionNamespaceLister. +type SubscriptionNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/messaging/v1beta1/inmemorychannel.go b/pkg/client/listers/messaging/v1beta1/inmemorychannel.go new file mode 100644 index 00000000000..19106e2168f --- /dev/null +++ b/pkg/client/listers/messaging/v1beta1/inmemorychannel.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" +) + +// InMemoryChannelLister helps list InMemoryChannels. +// All objects returned here must be treated as read-only. +type InMemoryChannelLister interface { + // List lists all InMemoryChannels in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.InMemoryChannel, err error) + // InMemoryChannels returns an object that can list and get InMemoryChannels. + InMemoryChannels(namespace string) InMemoryChannelNamespaceLister + InMemoryChannelListerExpansion +} + +// inMemoryChannelLister implements the InMemoryChannelLister interface. +type inMemoryChannelLister struct { + indexer cache.Indexer +} + +// NewInMemoryChannelLister returns a new InMemoryChannelLister. +func NewInMemoryChannelLister(indexer cache.Indexer) InMemoryChannelLister { + return &inMemoryChannelLister{indexer: indexer} +} + +// List lists all InMemoryChannels in the indexer. +func (s *inMemoryChannelLister) List(selector labels.Selector) (ret []*v1beta1.InMemoryChannel, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.InMemoryChannel)) + }) + return ret, err +} + +// InMemoryChannels returns an object that can list and get InMemoryChannels. +func (s *inMemoryChannelLister) InMemoryChannels(namespace string) InMemoryChannelNamespaceLister { + return inMemoryChannelNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// InMemoryChannelNamespaceLister helps list and get InMemoryChannels. +// All objects returned here must be treated as read-only. +type InMemoryChannelNamespaceLister interface { + // List lists all InMemoryChannels in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.InMemoryChannel, err error) + // Get retrieves the InMemoryChannel from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1beta1.InMemoryChannel, error) + InMemoryChannelNamespaceListerExpansion +} + +// inMemoryChannelNamespaceLister implements the InMemoryChannelNamespaceLister +// interface. +type inMemoryChannelNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all InMemoryChannels in the indexer for a given namespace. +func (s inMemoryChannelNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.InMemoryChannel, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.InMemoryChannel)) + }) + return ret, err +} + +// Get retrieves the InMemoryChannel from the indexer for a given namespace and name. +func (s inMemoryChannelNamespaceLister) Get(name string) (*v1beta1.InMemoryChannel, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta1.Resource("inmemorychannel"), name) + } + return obj.(*v1beta1.InMemoryChannel), nil +} diff --git a/pkg/client/listers/messaging/v1beta1/subscription.go b/pkg/client/listers/messaging/v1beta1/subscription.go new file mode 100644 index 00000000000..f3649c992b3 --- /dev/null +++ b/pkg/client/listers/messaging/v1beta1/subscription.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/messaging/v1beta1" +) + +// SubscriptionLister helps list Subscriptions. +// All objects returned here must be treated as read-only. +type SubscriptionLister interface { + // List lists all Subscriptions in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.Subscription, err error) + // Subscriptions returns an object that can list and get Subscriptions. + Subscriptions(namespace string) SubscriptionNamespaceLister + SubscriptionListerExpansion +} + +// subscriptionLister implements the SubscriptionLister interface. +type subscriptionLister struct { + indexer cache.Indexer +} + +// NewSubscriptionLister returns a new SubscriptionLister. +func NewSubscriptionLister(indexer cache.Indexer) SubscriptionLister { + return &subscriptionLister{indexer: indexer} +} + +// List lists all Subscriptions in the indexer. +func (s *subscriptionLister) List(selector labels.Selector) (ret []*v1beta1.Subscription, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.Subscription)) + }) + return ret, err +} + +// Subscriptions returns an object that can list and get Subscriptions. +func (s *subscriptionLister) Subscriptions(namespace string) SubscriptionNamespaceLister { + return subscriptionNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// SubscriptionNamespaceLister helps list and get Subscriptions. +// All objects returned here must be treated as read-only. +type SubscriptionNamespaceLister interface { + // List lists all Subscriptions in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.Subscription, err error) + // Get retrieves the Subscription from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1beta1.Subscription, error) + SubscriptionNamespaceListerExpansion +} + +// subscriptionNamespaceLister implements the SubscriptionNamespaceLister +// interface. +type subscriptionNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Subscriptions in the indexer for a given namespace. +func (s subscriptionNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.Subscription, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.Subscription)) + }) + return ret, err +} + +// Get retrieves the Subscription from the indexer for a given namespace and name. +func (s subscriptionNamespaceLister) Get(name string) (*v1beta1.Subscription, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta1.Resource("subscription"), name) + } + return obj.(*v1beta1.Subscription), nil +} diff --git a/pkg/client/listers/sources/v1alpha1/apiserversource.go b/pkg/client/listers/sources/v1alpha1/apiserversource.go new file mode 100644 index 00000000000..f8e7e359abc --- /dev/null +++ b/pkg/client/listers/sources/v1alpha1/apiserversource.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" +) + +// ApiServerSourceLister helps list ApiServerSources. +// All objects returned here must be treated as read-only. +type ApiServerSourceLister interface { + // List lists all ApiServerSources in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.ApiServerSource, err error) + // ApiServerSources returns an object that can list and get ApiServerSources. + ApiServerSources(namespace string) ApiServerSourceNamespaceLister + ApiServerSourceListerExpansion +} + +// apiServerSourceLister implements the ApiServerSourceLister interface. +type apiServerSourceLister struct { + indexer cache.Indexer +} + +// NewApiServerSourceLister returns a new ApiServerSourceLister. +func NewApiServerSourceLister(indexer cache.Indexer) ApiServerSourceLister { + return &apiServerSourceLister{indexer: indexer} +} + +// List lists all ApiServerSources in the indexer. +func (s *apiServerSourceLister) List(selector labels.Selector) (ret []*v1alpha1.ApiServerSource, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.ApiServerSource)) + }) + return ret, err +} + +// ApiServerSources returns an object that can list and get ApiServerSources. +func (s *apiServerSourceLister) ApiServerSources(namespace string) ApiServerSourceNamespaceLister { + return apiServerSourceNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// ApiServerSourceNamespaceLister helps list and get ApiServerSources. +// All objects returned here must be treated as read-only. +type ApiServerSourceNamespaceLister interface { + // List lists all ApiServerSources in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.ApiServerSource, err error) + // Get retrieves the ApiServerSource from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.ApiServerSource, error) + ApiServerSourceNamespaceListerExpansion +} + +// apiServerSourceNamespaceLister implements the ApiServerSourceNamespaceLister +// interface. +type apiServerSourceNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all ApiServerSources in the indexer for a given namespace. +func (s apiServerSourceNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.ApiServerSource, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.ApiServerSource)) + }) + return ret, err +} + +// Get retrieves the ApiServerSource from the indexer for a given namespace and name. +func (s apiServerSourceNamespaceLister) Get(name string) (*v1alpha1.ApiServerSource, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("apiserversource"), name) + } + return obj.(*v1alpha1.ApiServerSource), nil +} diff --git a/pkg/client/listers/sources/v1alpha1/expansion_generated.go b/pkg/client/listers/sources/v1alpha1/expansion_generated.go new file mode 100644 index 00000000000..7b13363e49d --- /dev/null +++ b/pkg/client/listers/sources/v1alpha1/expansion_generated.go @@ -0,0 +1,35 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +// ApiServerSourceListerExpansion allows custom methods to be added to +// ApiServerSourceLister. +type ApiServerSourceListerExpansion interface{} + +// ApiServerSourceNamespaceListerExpansion allows custom methods to be added to +// ApiServerSourceNamespaceLister. +type ApiServerSourceNamespaceListerExpansion interface{} + +// SinkBindingListerExpansion allows custom methods to be added to +// SinkBindingLister. +type SinkBindingListerExpansion interface{} + +// SinkBindingNamespaceListerExpansion allows custom methods to be added to +// SinkBindingNamespaceLister. +type SinkBindingNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/sources/v1alpha1/sinkbinding.go b/pkg/client/listers/sources/v1alpha1/sinkbinding.go new file mode 100644 index 00000000000..686511ec07e --- /dev/null +++ b/pkg/client/listers/sources/v1alpha1/sinkbinding.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" +) + +// SinkBindingLister helps list SinkBindings. +// All objects returned here must be treated as read-only. +type SinkBindingLister interface { + // List lists all SinkBindings in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.SinkBinding, err error) + // SinkBindings returns an object that can list and get SinkBindings. + SinkBindings(namespace string) SinkBindingNamespaceLister + SinkBindingListerExpansion +} + +// sinkBindingLister implements the SinkBindingLister interface. +type sinkBindingLister struct { + indexer cache.Indexer +} + +// NewSinkBindingLister returns a new SinkBindingLister. +func NewSinkBindingLister(indexer cache.Indexer) SinkBindingLister { + return &sinkBindingLister{indexer: indexer} +} + +// List lists all SinkBindings in the indexer. +func (s *sinkBindingLister) List(selector labels.Selector) (ret []*v1alpha1.SinkBinding, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.SinkBinding)) + }) + return ret, err +} + +// SinkBindings returns an object that can list and get SinkBindings. +func (s *sinkBindingLister) SinkBindings(namespace string) SinkBindingNamespaceLister { + return sinkBindingNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// SinkBindingNamespaceLister helps list and get SinkBindings. +// All objects returned here must be treated as read-only. +type SinkBindingNamespaceLister interface { + // List lists all SinkBindings in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.SinkBinding, err error) + // Get retrieves the SinkBinding from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.SinkBinding, error) + SinkBindingNamespaceListerExpansion +} + +// sinkBindingNamespaceLister implements the SinkBindingNamespaceLister +// interface. +type sinkBindingNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all SinkBindings in the indexer for a given namespace. +func (s sinkBindingNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.SinkBinding, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.SinkBinding)) + }) + return ret, err +} + +// Get retrieves the SinkBinding from the indexer for a given namespace and name. +func (s sinkBindingNamespaceLister) Get(name string) (*v1alpha1.SinkBinding, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("sinkbinding"), name) + } + return obj.(*v1alpha1.SinkBinding), nil +} diff --git a/pkg/client/listers/sources/v1alpha2/apiserversource.go b/pkg/client/listers/sources/v1alpha2/apiserversource.go new file mode 100644 index 00000000000..65b7bedcb35 --- /dev/null +++ b/pkg/client/listers/sources/v1alpha2/apiserversource.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 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 v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" +) + +// ApiServerSourceLister helps list ApiServerSources. +// All objects returned here must be treated as read-only. +type ApiServerSourceLister interface { + // List lists all ApiServerSources in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.ApiServerSource, err error) + // ApiServerSources returns an object that can list and get ApiServerSources. + ApiServerSources(namespace string) ApiServerSourceNamespaceLister + ApiServerSourceListerExpansion +} + +// apiServerSourceLister implements the ApiServerSourceLister interface. +type apiServerSourceLister struct { + indexer cache.Indexer +} + +// NewApiServerSourceLister returns a new ApiServerSourceLister. +func NewApiServerSourceLister(indexer cache.Indexer) ApiServerSourceLister { + return &apiServerSourceLister{indexer: indexer} +} + +// List lists all ApiServerSources in the indexer. +func (s *apiServerSourceLister) List(selector labels.Selector) (ret []*v1alpha2.ApiServerSource, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.ApiServerSource)) + }) + return ret, err +} + +// ApiServerSources returns an object that can list and get ApiServerSources. +func (s *apiServerSourceLister) ApiServerSources(namespace string) ApiServerSourceNamespaceLister { + return apiServerSourceNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// ApiServerSourceNamespaceLister helps list and get ApiServerSources. +// All objects returned here must be treated as read-only. +type ApiServerSourceNamespaceLister interface { + // List lists all ApiServerSources in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.ApiServerSource, err error) + // Get retrieves the ApiServerSource from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha2.ApiServerSource, error) + ApiServerSourceNamespaceListerExpansion +} + +// apiServerSourceNamespaceLister implements the ApiServerSourceNamespaceLister +// interface. +type apiServerSourceNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all ApiServerSources in the indexer for a given namespace. +func (s apiServerSourceNamespaceLister) List(selector labels.Selector) (ret []*v1alpha2.ApiServerSource, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.ApiServerSource)) + }) + return ret, err +} + +// Get retrieves the ApiServerSource from the indexer for a given namespace and name. +func (s apiServerSourceNamespaceLister) Get(name string) (*v1alpha2.ApiServerSource, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha2.Resource("apiserversource"), name) + } + return obj.(*v1alpha2.ApiServerSource), nil +} diff --git a/pkg/client/listers/sources/v1alpha2/containersource.go b/pkg/client/listers/sources/v1alpha2/containersource.go new file mode 100644 index 00000000000..064de46f0c2 --- /dev/null +++ b/pkg/client/listers/sources/v1alpha2/containersource.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 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 v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" +) + +// ContainerSourceLister helps list ContainerSources. +// All objects returned here must be treated as read-only. +type ContainerSourceLister interface { + // List lists all ContainerSources in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.ContainerSource, err error) + // ContainerSources returns an object that can list and get ContainerSources. + ContainerSources(namespace string) ContainerSourceNamespaceLister + ContainerSourceListerExpansion +} + +// containerSourceLister implements the ContainerSourceLister interface. +type containerSourceLister struct { + indexer cache.Indexer +} + +// NewContainerSourceLister returns a new ContainerSourceLister. +func NewContainerSourceLister(indexer cache.Indexer) ContainerSourceLister { + return &containerSourceLister{indexer: indexer} +} + +// List lists all ContainerSources in the indexer. +func (s *containerSourceLister) List(selector labels.Selector) (ret []*v1alpha2.ContainerSource, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.ContainerSource)) + }) + return ret, err +} + +// ContainerSources returns an object that can list and get ContainerSources. +func (s *containerSourceLister) ContainerSources(namespace string) ContainerSourceNamespaceLister { + return containerSourceNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// ContainerSourceNamespaceLister helps list and get ContainerSources. +// All objects returned here must be treated as read-only. +type ContainerSourceNamespaceLister interface { + // List lists all ContainerSources in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.ContainerSource, err error) + // Get retrieves the ContainerSource from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha2.ContainerSource, error) + ContainerSourceNamespaceListerExpansion +} + +// containerSourceNamespaceLister implements the ContainerSourceNamespaceLister +// interface. +type containerSourceNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all ContainerSources in the indexer for a given namespace. +func (s containerSourceNamespaceLister) List(selector labels.Selector) (ret []*v1alpha2.ContainerSource, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.ContainerSource)) + }) + return ret, err +} + +// Get retrieves the ContainerSource from the indexer for a given namespace and name. +func (s containerSourceNamespaceLister) Get(name string) (*v1alpha2.ContainerSource, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha2.Resource("containersource"), name) + } + return obj.(*v1alpha2.ContainerSource), nil +} diff --git a/pkg/client/listers/sources/v1alpha2/expansion_generated.go b/pkg/client/listers/sources/v1alpha2/expansion_generated.go new file mode 100644 index 00000000000..607e89caa00 --- /dev/null +++ b/pkg/client/listers/sources/v1alpha2/expansion_generated.go @@ -0,0 +1,51 @@ +/* +Copyright 2021 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 v1alpha2 + +// ApiServerSourceListerExpansion allows custom methods to be added to +// ApiServerSourceLister. +type ApiServerSourceListerExpansion interface{} + +// ApiServerSourceNamespaceListerExpansion allows custom methods to be added to +// ApiServerSourceNamespaceLister. +type ApiServerSourceNamespaceListerExpansion interface{} + +// ContainerSourceListerExpansion allows custom methods to be added to +// ContainerSourceLister. +type ContainerSourceListerExpansion interface{} + +// ContainerSourceNamespaceListerExpansion allows custom methods to be added to +// ContainerSourceNamespaceLister. +type ContainerSourceNamespaceListerExpansion interface{} + +// PingSourceListerExpansion allows custom methods to be added to +// PingSourceLister. +type PingSourceListerExpansion interface{} + +// PingSourceNamespaceListerExpansion allows custom methods to be added to +// PingSourceNamespaceLister. +type PingSourceNamespaceListerExpansion interface{} + +// SinkBindingListerExpansion allows custom methods to be added to +// SinkBindingLister. +type SinkBindingListerExpansion interface{} + +// SinkBindingNamespaceListerExpansion allows custom methods to be added to +// SinkBindingNamespaceLister. +type SinkBindingNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/sources/v1alpha2/pingsource.go b/pkg/client/listers/sources/v1alpha2/pingsource.go new file mode 100644 index 00000000000..8a0a52c9753 --- /dev/null +++ b/pkg/client/listers/sources/v1alpha2/pingsource.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 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 v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" +) + +// PingSourceLister helps list PingSources. +// All objects returned here must be treated as read-only. +type PingSourceLister interface { + // List lists all PingSources in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.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 []*v1alpha2.PingSource, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.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. +// All objects returned here must be treated as read-only. +type PingSourceNamespaceLister interface { + // List lists all PingSources in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.PingSource, err error) + // Get retrieves the PingSource from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha2.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 []*v1alpha2.PingSource, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.PingSource)) + }) + return ret, err +} + +// Get retrieves the PingSource from the indexer for a given namespace and name. +func (s pingSourceNamespaceLister) Get(name string) (*v1alpha2.PingSource, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha2.Resource("pingsource"), name) + } + return obj.(*v1alpha2.PingSource), nil +} diff --git a/pkg/client/listers/sources/v1alpha2/sinkbinding.go b/pkg/client/listers/sources/v1alpha2/sinkbinding.go new file mode 100644 index 00000000000..d33c37e19be --- /dev/null +++ b/pkg/client/listers/sources/v1alpha2/sinkbinding.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 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 v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" +) + +// SinkBindingLister helps list SinkBindings. +// All objects returned here must be treated as read-only. +type SinkBindingLister interface { + // List lists all SinkBindings in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.SinkBinding, err error) + // SinkBindings returns an object that can list and get SinkBindings. + SinkBindings(namespace string) SinkBindingNamespaceLister + SinkBindingListerExpansion +} + +// sinkBindingLister implements the SinkBindingLister interface. +type sinkBindingLister struct { + indexer cache.Indexer +} + +// NewSinkBindingLister returns a new SinkBindingLister. +func NewSinkBindingLister(indexer cache.Indexer) SinkBindingLister { + return &sinkBindingLister{indexer: indexer} +} + +// List lists all SinkBindings in the indexer. +func (s *sinkBindingLister) List(selector labels.Selector) (ret []*v1alpha2.SinkBinding, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.SinkBinding)) + }) + return ret, err +} + +// SinkBindings returns an object that can list and get SinkBindings. +func (s *sinkBindingLister) SinkBindings(namespace string) SinkBindingNamespaceLister { + return sinkBindingNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// SinkBindingNamespaceLister helps list and get SinkBindings. +// All objects returned here must be treated as read-only. +type SinkBindingNamespaceLister interface { + // List lists all SinkBindings in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.SinkBinding, err error) + // Get retrieves the SinkBinding from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha2.SinkBinding, error) + SinkBindingNamespaceListerExpansion +} + +// sinkBindingNamespaceLister implements the SinkBindingNamespaceLister +// interface. +type sinkBindingNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all SinkBindings in the indexer for a given namespace. +func (s sinkBindingNamespaceLister) List(selector labels.Selector) (ret []*v1alpha2.SinkBinding, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.SinkBinding)) + }) + return ret, err +} + +// Get retrieves the SinkBinding from the indexer for a given namespace and name. +func (s sinkBindingNamespaceLister) Get(name string) (*v1alpha2.SinkBinding, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha2.Resource("sinkbinding"), name) + } + return obj.(*v1alpha2.SinkBinding), nil +} diff --git a/pkg/client/listers/sources/v1beta1/apiserversource.go b/pkg/client/listers/sources/v1beta1/apiserversource.go new file mode 100644 index 00000000000..9f41e3faea3 --- /dev/null +++ b/pkg/client/listers/sources/v1beta1/apiserversource.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" +) + +// ApiServerSourceLister helps list ApiServerSources. +// All objects returned here must be treated as read-only. +type ApiServerSourceLister interface { + // List lists all ApiServerSources in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.ApiServerSource, err error) + // ApiServerSources returns an object that can list and get ApiServerSources. + ApiServerSources(namespace string) ApiServerSourceNamespaceLister + ApiServerSourceListerExpansion +} + +// apiServerSourceLister implements the ApiServerSourceLister interface. +type apiServerSourceLister struct { + indexer cache.Indexer +} + +// NewApiServerSourceLister returns a new ApiServerSourceLister. +func NewApiServerSourceLister(indexer cache.Indexer) ApiServerSourceLister { + return &apiServerSourceLister{indexer: indexer} +} + +// List lists all ApiServerSources in the indexer. +func (s *apiServerSourceLister) List(selector labels.Selector) (ret []*v1beta1.ApiServerSource, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.ApiServerSource)) + }) + return ret, err +} + +// ApiServerSources returns an object that can list and get ApiServerSources. +func (s *apiServerSourceLister) ApiServerSources(namespace string) ApiServerSourceNamespaceLister { + return apiServerSourceNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// ApiServerSourceNamespaceLister helps list and get ApiServerSources. +// All objects returned here must be treated as read-only. +type ApiServerSourceNamespaceLister interface { + // List lists all ApiServerSources in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.ApiServerSource, err error) + // Get retrieves the ApiServerSource from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1beta1.ApiServerSource, error) + ApiServerSourceNamespaceListerExpansion +} + +// apiServerSourceNamespaceLister implements the ApiServerSourceNamespaceLister +// interface. +type apiServerSourceNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all ApiServerSources in the indexer for a given namespace. +func (s apiServerSourceNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.ApiServerSource, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.ApiServerSource)) + }) + return ret, err +} + +// Get retrieves the ApiServerSource from the indexer for a given namespace and name. +func (s apiServerSourceNamespaceLister) Get(name string) (*v1beta1.ApiServerSource, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta1.Resource("apiserversource"), name) + } + return obj.(*v1beta1.ApiServerSource), nil +} diff --git a/pkg/client/listers/sources/v1beta1/containersource.go b/pkg/client/listers/sources/v1beta1/containersource.go new file mode 100644 index 00000000000..f1fb3148409 --- /dev/null +++ b/pkg/client/listers/sources/v1beta1/containersource.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" +) + +// ContainerSourceLister helps list ContainerSources. +// All objects returned here must be treated as read-only. +type ContainerSourceLister interface { + // List lists all ContainerSources in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.ContainerSource, err error) + // ContainerSources returns an object that can list and get ContainerSources. + ContainerSources(namespace string) ContainerSourceNamespaceLister + ContainerSourceListerExpansion +} + +// containerSourceLister implements the ContainerSourceLister interface. +type containerSourceLister struct { + indexer cache.Indexer +} + +// NewContainerSourceLister returns a new ContainerSourceLister. +func NewContainerSourceLister(indexer cache.Indexer) ContainerSourceLister { + return &containerSourceLister{indexer: indexer} +} + +// List lists all ContainerSources in the indexer. +func (s *containerSourceLister) List(selector labels.Selector) (ret []*v1beta1.ContainerSource, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.ContainerSource)) + }) + return ret, err +} + +// ContainerSources returns an object that can list and get ContainerSources. +func (s *containerSourceLister) ContainerSources(namespace string) ContainerSourceNamespaceLister { + return containerSourceNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// ContainerSourceNamespaceLister helps list and get ContainerSources. +// All objects returned here must be treated as read-only. +type ContainerSourceNamespaceLister interface { + // List lists all ContainerSources in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.ContainerSource, err error) + // Get retrieves the ContainerSource from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1beta1.ContainerSource, error) + ContainerSourceNamespaceListerExpansion +} + +// containerSourceNamespaceLister implements the ContainerSourceNamespaceLister +// interface. +type containerSourceNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all ContainerSources in the indexer for a given namespace. +func (s containerSourceNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.ContainerSource, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.ContainerSource)) + }) + return ret, err +} + +// Get retrieves the ContainerSource from the indexer for a given namespace and name. +func (s containerSourceNamespaceLister) Get(name string) (*v1beta1.ContainerSource, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta1.Resource("containersource"), name) + } + return obj.(*v1beta1.ContainerSource), nil +} diff --git a/pkg/client/listers/sources/v1beta1/expansion_generated.go b/pkg/client/listers/sources/v1beta1/expansion_generated.go new file mode 100644 index 00000000000..b4fd2dee124 --- /dev/null +++ b/pkg/client/listers/sources/v1beta1/expansion_generated.go @@ -0,0 +1,51 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +// ApiServerSourceListerExpansion allows custom methods to be added to +// ApiServerSourceLister. +type ApiServerSourceListerExpansion interface{} + +// ApiServerSourceNamespaceListerExpansion allows custom methods to be added to +// ApiServerSourceNamespaceLister. +type ApiServerSourceNamespaceListerExpansion interface{} + +// ContainerSourceListerExpansion allows custom methods to be added to +// ContainerSourceLister. +type ContainerSourceListerExpansion interface{} + +// ContainerSourceNamespaceListerExpansion allows custom methods to be added to +// ContainerSourceNamespaceLister. +type ContainerSourceNamespaceListerExpansion interface{} + +// PingSourceListerExpansion allows custom methods to be added to +// PingSourceLister. +type PingSourceListerExpansion interface{} + +// PingSourceNamespaceListerExpansion allows custom methods to be added to +// PingSourceNamespaceLister. +type PingSourceNamespaceListerExpansion interface{} + +// SinkBindingListerExpansion allows custom methods to be added to +// SinkBindingLister. +type SinkBindingListerExpansion interface{} + +// SinkBindingNamespaceListerExpansion allows custom methods to be added to +// SinkBindingNamespaceLister. +type SinkBindingNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/sources/v1beta1/pingsource.go b/pkg/client/listers/sources/v1beta1/pingsource.go new file mode 100644 index 00000000000..df16ff6ee76 --- /dev/null +++ b/pkg/client/listers/sources/v1beta1/pingsource.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" +) + +// PingSourceLister helps list PingSources. +// All objects returned here must be treated as read-only. +type PingSourceLister interface { + // List lists all PingSources in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.PingSource, err error) + // PingSources returns an object that can list and get PingSources. + PingSources(namespace string) PingSourceNamespaceLister + PingSourceListerExpansion +} + +// pingSourceLister implements the PingSourceLister interface. +type pingSourceLister struct { + indexer cache.Indexer +} + +// NewPingSourceLister returns a new PingSourceLister. +func NewPingSourceLister(indexer cache.Indexer) PingSourceLister { + return &pingSourceLister{indexer: indexer} +} + +// List lists all PingSources in the indexer. +func (s *pingSourceLister) List(selector labels.Selector) (ret []*v1beta1.PingSource, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.PingSource)) + }) + return ret, err +} + +// PingSources returns an object that can list and get PingSources. +func (s *pingSourceLister) PingSources(namespace string) PingSourceNamespaceLister { + return pingSourceNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// PingSourceNamespaceLister helps list and get PingSources. +// All objects returned here must be treated as read-only. +type PingSourceNamespaceLister interface { + // List lists all PingSources in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.PingSource, err error) + // Get retrieves the PingSource from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1beta1.PingSource, error) + PingSourceNamespaceListerExpansion +} + +// pingSourceNamespaceLister implements the PingSourceNamespaceLister +// interface. +type pingSourceNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all PingSources in the indexer for a given namespace. +func (s pingSourceNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.PingSource, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.PingSource)) + }) + return ret, err +} + +// Get retrieves the PingSource from the indexer for a given namespace and name. +func (s pingSourceNamespaceLister) Get(name string) (*v1beta1.PingSource, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta1.Resource("pingsource"), name) + } + return obj.(*v1beta1.PingSource), nil +} diff --git a/pkg/client/listers/sources/v1beta1/sinkbinding.go b/pkg/client/listers/sources/v1beta1/sinkbinding.go new file mode 100644 index 00000000000..1aaa0ead838 --- /dev/null +++ b/pkg/client/listers/sources/v1beta1/sinkbinding.go @@ -0,0 +1,99 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" +) + +// SinkBindingLister helps list SinkBindings. +// All objects returned here must be treated as read-only. +type SinkBindingLister interface { + // List lists all SinkBindings in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.SinkBinding, err error) + // SinkBindings returns an object that can list and get SinkBindings. + SinkBindings(namespace string) SinkBindingNamespaceLister + SinkBindingListerExpansion +} + +// sinkBindingLister implements the SinkBindingLister interface. +type sinkBindingLister struct { + indexer cache.Indexer +} + +// NewSinkBindingLister returns a new SinkBindingLister. +func NewSinkBindingLister(indexer cache.Indexer) SinkBindingLister { + return &sinkBindingLister{indexer: indexer} +} + +// List lists all SinkBindings in the indexer. +func (s *sinkBindingLister) List(selector labels.Selector) (ret []*v1beta1.SinkBinding, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.SinkBinding)) + }) + return ret, err +} + +// SinkBindings returns an object that can list and get SinkBindings. +func (s *sinkBindingLister) SinkBindings(namespace string) SinkBindingNamespaceLister { + return sinkBindingNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// SinkBindingNamespaceLister helps list and get SinkBindings. +// All objects returned here must be treated as read-only. +type SinkBindingNamespaceLister interface { + // List lists all SinkBindings in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.SinkBinding, err error) + // Get retrieves the SinkBinding from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1beta1.SinkBinding, error) + SinkBindingNamespaceListerExpansion +} + +// sinkBindingNamespaceLister implements the SinkBindingNamespaceLister +// interface. +type sinkBindingNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all SinkBindings in the indexer for a given namespace. +func (s sinkBindingNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.SinkBinding, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.SinkBinding)) + }) + return ret, err +} + +// Get retrieves the SinkBinding from the indexer for a given namespace and name. +func (s sinkBindingNamespaceLister) Get(name string) (*v1beta1.SinkBinding, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta1.Resource("sinkbinding"), name) + } + return obj.(*v1beta1.SinkBinding), nil +} diff --git a/pkg/reconciler/resources/service_account_test.go b/pkg/reconciler/resources/service_account_test.go index ce5bd74986e..68ff3af97bd 100644 --- a/pkg/reconciler/resources/service_account_test.go +++ b/pkg/reconciler/resources/service_account_test.go @@ -24,13 +24,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "knative.dev/pkg/kmeta" - "knative.dev/eventing/pkg/apis/sources/v1beta2" + "knative.dev/eventing/pkg/apis/sources/v1beta1" ) func TestNewServiceAccount(t *testing.T) { testNS := "test-ns" testName := "test-name" - obj := &v1beta2.PingSource{ + obj := &v1beta1.PingSource{ ObjectMeta: metav1.ObjectMeta{ Name: testName, Namespace: testNS, diff --git a/pkg/reconciler/testing/listers.go b/pkg/reconciler/testing/listers.go index d3a01164c8b..bec38446f3c 100644 --- a/pkg/reconciler/testing/listers.go +++ b/pkg/reconciler/testing/listers.go @@ -31,8 +31,12 @@ import ( corev1listers "k8s.io/client-go/listers/core/v1" rbacv1listers "k8s.io/client-go/listers/rbac/v1" "k8s.io/client-go/tools/cache" + 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" + 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" @@ -112,10 +116,26 @@ func (l *Listers) GetAllObjects() []runtime.Object { return all } +func (l *Listers) GetPingSourceV1beta1Lister() sourcev1beta1listers.PingSourceLister { + 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{})) +} + +func (l *Listers) GetSinkBindingV1beta1Lister() sourcev1beta1listers.SinkBindingLister { + return sourcev1beta1listers.NewSinkBindingLister(l.indexerFor(&sourcesv1beta1.SinkBinding{})) +} + +func (l *Listers) GetApiServerSourceV1alpha2Lister() sourcev1alpha2listers.ApiServerSourceLister { + return sourcev1alpha2listers.NewApiServerSourceLister(l.indexerFor(&sourcesv1alpha2.ApiServerSource{})) +} + func (l *Listers) GetDeploymentLister() appsv1listers.DeploymentLister { return appsv1listers.NewDeploymentLister(l.indexerFor(&appsv1.Deployment{})) } diff --git a/pkg/reconciler/testing/v1alpha2/apiserversource.go b/pkg/reconciler/testing/v1alpha2/apiserversource.go new file mode 100644 index 00000000000..280fde5401d --- /dev/null +++ b/pkg/reconciler/testing/v1alpha2/apiserversource.go @@ -0,0 +1,49 @@ +/* +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" +) + +// ApiServerSourceOption enables further configuration of a v1alpha2 ApiServer. +type ApiServerSourceOption func(*v1alpha2.ApiServerSource) + +// NewApiServerSource creates a v1alpha2 ApiServer with ApiServerOptions +func NewApiServerSource(name, namespace string, o ...ApiServerSourceOption) *v1alpha2.ApiServerSource { + c := &v1alpha2.ApiServerSource{ + 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 WithApiServerSourceSpec(spec v1alpha2.ApiServerSourceSpec) ApiServerSourceOption { + return func(c *v1alpha2.ApiServerSource) { + c.Spec = spec + c.Spec.SetDefaults(context.Background()) + } +} diff --git a/pkg/reconciler/testing/v1alpha2/containersource.go b/pkg/reconciler/testing/v1alpha2/containersource.go new file mode 100644 index 00000000000..b4b941c4efb --- /dev/null +++ b/pkg/reconciler/testing/v1alpha2/containersource.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" +) + +// ContainerSourceOption enables further configuration of a ContainerSource. +type ContainerSourceOption func(*v1alpha2.ContainerSource) + +// NewContainerSource creates a v1alpha2 ContainerSource with ContainerSourceOptions +func NewContainerSource(name, namespace string, o ...ContainerSourceOption) *v1alpha2.ContainerSource { + c := &v1alpha2.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } + for _, opt := range o { + opt(c) + } + c.SetDefaults(context.Background()) + return c +} + +func WithContainerSourceSpec(spec v1alpha2.ContainerSourceSpec) ContainerSourceOption { + return func(c *v1alpha2.ContainerSource) { + c.Spec = spec + } +} 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/v1alpha2/sinkbinding.go b/pkg/reconciler/testing/v1alpha2/sinkbinding.go new file mode 100644 index 00000000000..e7ec968ff35 --- /dev/null +++ b/pkg/reconciler/testing/v1alpha2/sinkbinding.go @@ -0,0 +1,63 @@ +/* +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 ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + sourcesv1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/tracker" +) + +// SinkBindingOption enables further configuration of a v1alpha2 SinkBinding. +type SinkBindingOption func(*sourcesv1alpha2.SinkBinding) + +// NewSinkBinding creates a SinkBinding with v1alpha2 SinkBindingOptions +func NewSinkBinding(name, namespace string, o ...SinkBindingOption) *sourcesv1alpha2.SinkBinding { + c := &sourcesv1alpha2.SinkBinding{ + 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 +} + +// WithSubject assigns the subject of the SinkBinding. +func WithSubject(subject tracker.Reference) SinkBindingOption { + return func(sb *sourcesv1alpha2.SinkBinding) { + sb.Spec.Subject = subject + } +} + +// WithSink assigns the sink of the SinkBinding. +func WithSink(sink duckv1.Destination) SinkBindingOption { + return func(sb *sourcesv1alpha2.SinkBinding) { + sb.Spec.Sink = sink + } +} + +// WithCloudEventOverrides assigns the CloudEventsOverrides of the SinkBinding. +func WithCloudEventOverrides(overrides duckv1.CloudEventOverrides) SinkBindingOption { + return func(sb *sourcesv1alpha2.SinkBinding) { + sb.Spec.CloudEventOverrides = &overrides + } +} diff --git a/pkg/reconciler/testing/v1beta1/apiserversource.go b/pkg/reconciler/testing/v1beta1/apiserversource.go new file mode 100644 index 00000000000..ff013c3d280 --- /dev/null +++ b/pkg/reconciler/testing/v1beta1/apiserversource.go @@ -0,0 +1,49 @@ +/* +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/v1beta1" +) + +// ApiServerSourceOption enables further configuration of a v1beta1 ApiServer. +type ApiServerSourceOption func(*v1beta1.ApiServerSource) + +// NewApiServerSource creates a v1beta1 ApiServer with ApiServerOptions +func NewApiServerSource(name, namespace string, o ...ApiServerSourceOption) *v1beta1.ApiServerSource { + c := &v1beta1.ApiServerSource{ + 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 WithApiServerSourceSpec(spec v1beta1.ApiServerSourceSpec) ApiServerSourceOption { + return func(c *v1beta1.ApiServerSource) { + c.Spec = spec + c.Spec.SetDefaults(context.Background()) + } +} diff --git a/pkg/reconciler/testing/v1beta1/containersource.go b/pkg/reconciler/testing/v1beta1/containersource.go new file mode 100644 index 00000000000..78f4b4008b9 --- /dev/null +++ b/pkg/reconciler/testing/v1beta1/containersource.go @@ -0,0 +1,112 @@ +/* +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/apis/sources/v1beta1" + + appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +// ContainerSourceOption enables further configuration of a ContainerSource. +type ContainerSourceOption func(*v1beta1.ContainerSource) + +// NewContainerSource creates a v1beta1 ContainerSource with ContainerSourceOptions +func NewContainerSource(name, namespace string, o ...ContainerSourceOption) *v1beta1.ContainerSource { + c := &v1beta1.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } + for _, opt := range o { + opt(c) + } + c.SetDefaults(context.Background()) + return c +} + +func WithContainerSourceUID(uid types.UID) ContainerSourceOption { + return func(s *v1beta1.ContainerSource) { + s.UID = uid + } +} + +// WithInitContainerSourceConditions initializes the ContainerSource's conditions. +func WithInitContainerSourceConditions(s *v1beta1.ContainerSource) { + s.Status.InitializeConditions() +} + +func WithContainerSourcePropagateReceiveAdapterStatus(d *appsv1.Deployment) ContainerSourceOption { + return func(s *v1beta1.ContainerSource) { + s.Status.PropagateReceiveAdapterStatus(d) + } +} + +func WithContainerSourcePropagateSinkbindingStatus(status *v1beta1.SinkBindingStatus) ContainerSourceOption { + return func(s *v1beta1.ContainerSource) { + s.Status.PropagateSinkBindingStatus(status) + } +} + +func WithContainerSourceDeleted(c *v1beta1.ContainerSource) { + t := metav1.NewTime(time.Unix(1e9, 0)) + c.ObjectMeta.SetDeletionTimestamp(&t) +} + +func WithContainerSourceSpec(spec v1beta1.ContainerSourceSpec) ContainerSourceOption { + return func(c *v1beta1.ContainerSource) { + c.Spec = spec + } +} + +func WithContainerSourceLabels(labels map[string]string) ContainerSourceOption { + return func(c *v1beta1.ContainerSource) { + c.Labels = labels + } +} + +func WithContainerSourceAnnotations(annotations map[string]string) ContainerSourceOption { + return func(c *v1beta1.ContainerSource) { + c.Annotations = annotations + } +} + +func WithContainerSourceStatusObservedGeneration(generation int64) ContainerSourceOption { + return func(c *v1beta1.ContainerSource) { + c.Status.ObservedGeneration = generation + } +} + +func WithContainerSourceObjectMetaGeneration(generation int64) ContainerSourceOption { + return func(c *v1beta1.ContainerSource) { + c.ObjectMeta.Generation = generation + } +} + +func WithContainerUnobservedGeneration() ContainerSourceOption { + return func(c *v1beta1.ContainerSource) { + condSet := c.GetConditionSet() + condSet.Manage(&c.Status).MarkUnknown( + condSet.GetTopLevelConditionType(), "NewObservedGenFailure", "unsuccessfully observed a new generation") + } +} diff --git a/pkg/reconciler/testing/v1beta1/listers.go b/pkg/reconciler/testing/v1beta1/listers.go index af39d8798f9..ec82595d98c 100644 --- a/pkg/reconciler/testing/v1beta1/listers.go +++ b/pkg/reconciler/testing/v1beta1/listers.go @@ -32,8 +32,12 @@ import ( rbacv1listers "k8s.io/client-go/listers/rbac/v1" "k8s.io/client-go/tools/cache" eventingv1beta1 "knative.dev/eventing/pkg/apis/eventing/v1beta1" + sourcesv1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2" + sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" fakeeventingclientset "knative.dev/eventing/pkg/client/clientset/versioned/fake" eventingv1beta1listers "knative.dev/eventing/pkg/client/listers/eventing/v1beta1" + sourcev1alpha2listers "knative.dev/eventing/pkg/client/listers/sources/v1alpha2" + sourcev1beta1listers "knative.dev/eventing/pkg/client/listers/sources/v1beta1" duckv1 "knative.dev/pkg/apis/duck/v1" "knative.dev/pkg/reconciler/testing" ) @@ -118,6 +122,26 @@ func (l *Listers) GetEventTypeLister() eventingv1beta1listers.EventTypeLister { return eventingv1beta1listers.NewEventTypeLister(l.indexerFor(&eventingv1beta1.EventType{})) } +func (l *Listers) GetPingSourceV1beta1Lister() sourcev1beta1listers.PingSourceLister { + return sourcev1beta1listers.NewPingSourceLister(l.indexerFor(&sourcesv1beta1.PingSource{})) +} + +func (l *Listers) GetContainerSourceLister() sourcev1alpha2listers.ContainerSourceLister { + return sourcev1alpha2listers.NewContainerSourceLister(l.indexerFor(&sourcesv1alpha2.ContainerSource{})) +} + +func (l *Listers) GetSinkBindingV1alpha2Lister() sourcev1alpha2listers.SinkBindingLister { + return sourcev1alpha2listers.NewSinkBindingLister(l.indexerFor(&sourcesv1alpha2.SinkBinding{})) +} + +func (l *Listers) GetApiServerSourceV1alpha2Lister() sourcev1alpha2listers.ApiServerSourceLister { + return sourcev1alpha2listers.NewApiServerSourceLister(l.indexerFor(&sourcesv1alpha2.ApiServerSource{})) +} + +func (l *Listers) GetApiServerSourceV1beta1Lister() sourcev1beta1listers.ApiServerSourceLister { + return sourcev1beta1listers.NewApiServerSourceLister(l.indexerFor(&sourcesv1beta1.ApiServerSource{})) +} + func (l *Listers) GetDeploymentLister() appsv1listers.DeploymentLister { return appsv1listers.NewDeploymentLister(l.indexerFor(&appsv1.Deployment{})) } 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/v1beta1/sinkbinding.go b/pkg/reconciler/testing/v1beta1/sinkbinding.go new file mode 100644 index 00000000000..82b98600a08 --- /dev/null +++ b/pkg/reconciler/testing/v1beta1/sinkbinding.go @@ -0,0 +1,63 @@ +/* +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 ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + sourcesv1beta1 "knative.dev/eventing/pkg/apis/sources/v1beta1" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/tracker" +) + +// SinkBindingOption enables further configuration of a v1beta1 SinkBinding. +type SinkBindingOption func(*sourcesv1beta1.SinkBinding) + +// NewSinkBinding creates a SinkBinding with SinkBindingOptions +func NewSinkBinding(name, namespace string, o ...SinkBindingOption) *sourcesv1beta1.SinkBinding { + c := &sourcesv1beta1.SinkBinding{ + 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 +} + +// WithSubject assigns the subject of the SinkBinding. +func WithSubject(subject tracker.Reference) SinkBindingOption { + return func(sb *sourcesv1beta1.SinkBinding) { + sb.Spec.Subject = subject + } +} + +// WithSink assigns the sink of the SinkBinding. +func WithSink(sink duckv1.Destination) SinkBindingOption { + return func(sb *sourcesv1beta1.SinkBinding) { + sb.Spec.Sink = sink + } +} + +// WithCloudEventOverrides assigns the CloudEventsOverrides of the SinkBinding. +func WithCloudEventOverrides(overrides duckv1.CloudEventOverrides) SinkBindingOption { + return func(sb *sourcesv1beta1.SinkBinding) { + sb.Spec.CloudEventOverrides = &overrides + } +} diff --git a/test/rekt/features/apiserversource/data_plane.go b/test/rekt/features/apiserversource/data_plane.go index 03728140963..e061b693cc1 100644 --- a/test/rekt/features/apiserversource/data_plane.go +++ b/test/rekt/features/apiserversource/data_plane.go @@ -37,7 +37,7 @@ import ( ) const ( - exampleImage = "ko://knative.dev/eventing/test/test_images/print" + exampleImage = "registry.ci.openshift.org/openshift/knative-v0.23.0:knative-eventing-test-print" ) func DataPlane_SinkTypes() *feature.FeatureSet { diff --git a/vendor/knative.dev/reconciler-test/pkg/images/ko.go b/vendor/knative.dev/reconciler-test/pkg/images/ko.go index adde2080f38..ff57c9d6bf8 100644 --- a/vendor/knative.dev/reconciler-test/pkg/images/ko.go +++ b/vendor/knative.dev/reconciler-test/pkg/images/ko.go @@ -16,20 +16,7 @@ limitations under the License. package images -import ( - "fmt" - "os" -) - // Use ko to publish the image. func KoPublish(path string) (string, error) { - platform := os.Getenv("PLATFORM") - if len(platform) > 0 { - platform = " --platform=" + platform - } - out, err := runCmd(fmt.Sprintf("ko publish%s -B %s", platform, path)) - if err != nil { - return "", err - } - return out, nil + return "", nil }