From 68b5a79bcee544c412076ded759e05c12e631fd1 Mon Sep 17 00:00:00 2001 From: Matthias Wessendorf Date: Thu, 31 Oct 2024 15:15:51 +0100 Subject: [PATCH] IntegrationSource CRD (#8238) * Add initial raw API types and controller/reconciler. Levering internally the container source for the handling of the kamelet workloads. the logic for applying the env-vars is a bit verbose, but can be improved. Tests/lifeccyle and some other improvements are still missing. but this works. Signed-off-by: Matthias Wessendorf * adding reflection to make the mapping less verbose, and more flexible to extend... Signed-off-by: Matthias Wessendorf * Add DDB Streams Signed-off-by: Matthias Wessendorf * polish and add minimal tests Signed-off-by: Matthias Wessendorf * Update pkg/apis/sources/v1alpha1/integration_lifecycle.go Co-authored-by: Pierangelo Di Pilato * Update pkg/apis/sources/v1alpha1/integration_lifecycle.go Co-authored-by: Pierangelo Di Pilato * Fix compiler warnings Signed-off-by: Matthias Wessendorf * use other image coordinates Signed-off-by: Matthias Wessendorf * first test for source Signed-off-by: Matthias Wessendorf * Adding initial rekt test Signed-off-by: Matthias Wessendorf * Adding SSL support and test for IntegrationSource Signed-off-by: Matthias Wessendorf * Formatting fixes Signed-off-by: Matthias Wessendorf * Disable OIDC tests for now Signed-off-by: Matthias Wessendorf * use knative nightly images Signed-off-by: Matthias Wessendorf * Fixing linters Signed-off-by: Matthias Wessendorf * addressing comments, Part1 Signed-off-by: Matthias Wessendorf --------- Signed-off-by: Matthias Wessendorf Co-authored-by: Pierangelo Di Pilato --- cmd/controller/main.go | 3 + cmd/webhook/main.go | 4 + config/300-integrationsource.yaml | 1 + config/core/resources/integrationsource.yaml | 447 +++++++++ .../roles/source-observer-clusterrole.yaml | 1 + .../sources-controller-clusterroles.yaml | 3 + docs/eventing-api.md | 926 ++++++++++++++++++ hack/update-codegen.sh | 2 +- pkg/apis/sources/register.go | 6 + pkg/apis/sources/v1alpha1/doc.go | 20 + .../v1alpha1/integration_conversion.go | 36 + .../v1alpha1/integration_conversion_test.go | 48 + .../sources/v1alpha1/integration_defaults.go | 26 + .../v1alpha1/integration_defaults_test.go | 39 + .../sources/v1alpha1/integration_lifecycle.go | 76 ++ .../v1alpha1/integration_lifecycle_test.go | 189 ++++ .../sources/v1alpha1/integration_types.go | 185 ++++ .../v1alpha1/integration_types_test.go | 109 +++ .../v1alpha1/integration_validation.go | 98 ++ .../v1alpha1/integration_validation_test.go | 240 +++++ pkg/apis/sources/v1alpha1/register.go | 52 + .../sources/v1alpha1/zz_generated.deepcopy.go | 308 ++++++ pkg/client/clientset/versioned/clientset.go | 13 + .../versioned/fake/clientset_generated.go | 7 + .../clientset/versioned/fake/register.go | 2 + .../clientset/versioned/scheme/register.go | 2 + .../versioned/typed/sources/v1alpha1/doc.go | 20 + .../typed/sources/v1alpha1/fake/doc.go | 20 + .../v1alpha1/fake/fake_integrationsource.go | 141 +++ .../v1alpha1/fake/fake_sources_client.go | 40 + .../sources/v1alpha1/generated_expansion.go | 21 + .../sources/v1alpha1/integrationsource.go | 195 ++++ .../typed/sources/v1alpha1/sources_client.go | 107 ++ .../informers/externalversions/generic.go | 5 + .../externalversions/sources/interface.go | 8 + .../sources/v1alpha1/integrationsource.go | 90 ++ .../sources/v1alpha1/interface.go | 45 + .../v1alpha1/integrationsource/fake/fake.go | 40 + .../integrationsource/filtered/fake/fake.go | 52 + .../filtered/integrationsource.go | 65 ++ .../integrationsource/integrationsource.go | 52 + .../v1alpha1/integrationsource/controller.go | 170 ++++ .../v1alpha1/integrationsource/reconciler.go | 440 +++++++++ .../v1alpha1/integrationsource/state.go | 97 ++ .../sources/v1alpha1/expansion_generated.go | 27 + .../sources/v1alpha1/integrationsource.go | 99 ++ .../containersource/containersource_test.go | 2 +- .../integrationsource/controller.go | 111 +++ .../integrationsource/controller_test.go | 60 ++ .../integrationsource/integrationsource.go | 92 ++ .../integrationsource_test.go | 258 +++++ .../resources/containersource.go | 231 +++++ .../resources/containersource_test.go | 133 +++ .../integrationsource/resources/names.go | 26 + pkg/reconciler/testing/v1/listers.go | 6 + .../testing/v1alpha1/integrationsource.go | 73 ++ .../features/integrationsource/features.go | 117 +++ .../integrationsource/oidc_feature.go | 65 ++ test/rekt/integrationsource_test.go | 63 ++ .../integrationsource/integrationsource.go | 103 ++ .../integrationsource/integrationsource.yaml | 47 + 61 files changed, 5962 insertions(+), 2 deletions(-) create mode 120000 config/300-integrationsource.yaml create mode 100644 config/core/resources/integrationsource.yaml create mode 100644 pkg/apis/sources/v1alpha1/doc.go create mode 100644 pkg/apis/sources/v1alpha1/integration_conversion.go create mode 100644 pkg/apis/sources/v1alpha1/integration_conversion_test.go create mode 100644 pkg/apis/sources/v1alpha1/integration_defaults.go create mode 100644 pkg/apis/sources/v1alpha1/integration_defaults_test.go create mode 100644 pkg/apis/sources/v1alpha1/integration_lifecycle.go create mode 100644 pkg/apis/sources/v1alpha1/integration_lifecycle_test.go create mode 100644 pkg/apis/sources/v1alpha1/integration_types.go create mode 100644 pkg/apis/sources/v1alpha1/integration_types_test.go create mode 100644 pkg/apis/sources/v1alpha1/integration_validation.go create mode 100644 pkg/apis/sources/v1alpha1/integration_validation_test.go create mode 100644 pkg/apis/sources/v1alpha1/register.go create mode 100644 pkg/apis/sources/v1alpha1/zz_generated.deepcopy.go create mode 100644 pkg/client/clientset/versioned/typed/sources/v1alpha1/doc.go create mode 100644 pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/doc.go create mode 100644 pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/fake_integrationsource.go create mode 100644 pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/fake_sources_client.go create mode 100644 pkg/client/clientset/versioned/typed/sources/v1alpha1/generated_expansion.go create mode 100644 pkg/client/clientset/versioned/typed/sources/v1alpha1/integrationsource.go create mode 100644 pkg/client/clientset/versioned/typed/sources/v1alpha1/sources_client.go create mode 100644 pkg/client/informers/externalversions/sources/v1alpha1/integrationsource.go create mode 100644 pkg/client/informers/externalversions/sources/v1alpha1/interface.go create mode 100644 pkg/client/injection/informers/sources/v1alpha1/integrationsource/fake/fake.go create mode 100644 pkg/client/injection/informers/sources/v1alpha1/integrationsource/filtered/fake/fake.go create mode 100644 pkg/client/injection/informers/sources/v1alpha1/integrationsource/filtered/integrationsource.go create mode 100644 pkg/client/injection/informers/sources/v1alpha1/integrationsource/integrationsource.go create mode 100644 pkg/client/injection/reconciler/sources/v1alpha1/integrationsource/controller.go create mode 100644 pkg/client/injection/reconciler/sources/v1alpha1/integrationsource/reconciler.go create mode 100644 pkg/client/injection/reconciler/sources/v1alpha1/integrationsource/state.go create mode 100644 pkg/client/listers/sources/v1alpha1/expansion_generated.go create mode 100644 pkg/client/listers/sources/v1alpha1/integrationsource.go create mode 100644 pkg/reconciler/integrationsource/controller.go create mode 100644 pkg/reconciler/integrationsource/controller_test.go create mode 100644 pkg/reconciler/integrationsource/integrationsource.go create mode 100644 pkg/reconciler/integrationsource/integrationsource_test.go create mode 100644 pkg/reconciler/integrationsource/resources/containersource.go create mode 100644 pkg/reconciler/integrationsource/resources/containersource_test.go create mode 100644 pkg/reconciler/integrationsource/resources/names.go create mode 100644 pkg/reconciler/testing/v1alpha1/integrationsource.go create mode 100644 test/rekt/features/integrationsource/features.go create mode 100644 test/rekt/features/integrationsource/oidc_feature.go create mode 100644 test/rekt/integrationsource_test.go create mode 100644 test/rekt/resources/integrationsource/integrationsource.go create mode 100644 test/rekt/resources/integrationsource/integrationsource.yaml diff --git a/cmd/controller/main.go b/cmd/controller/main.go index a5ce470eba0..616c42ee5a7 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -41,6 +41,7 @@ import ( "knative.dev/eventing/pkg/reconciler/channel" "knative.dev/eventing/pkg/reconciler/containersource" "knative.dev/eventing/pkg/reconciler/eventtype" + "knative.dev/eventing/pkg/reconciler/integrationsource" "knative.dev/eventing/pkg/reconciler/parallel" "knative.dev/eventing/pkg/reconciler/pingsource" "knative.dev/eventing/pkg/reconciler/sequence" @@ -104,6 +105,8 @@ func main() { apiserversource.NewController, pingsource.NewController, containersource.NewController, + integrationsource.NewController, + // Sources CRD sourcecrd.NewController, diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index e21ec0e9956..1f504c55abc 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -20,6 +20,8 @@ import ( "context" "os" + sourcesv1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" @@ -92,6 +94,8 @@ var ourTypes = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{ messagingv1.SchemeGroupVersion.WithKind("Subscription"): &messagingv1.Subscription{}, // For group sources.knative.dev. + // v1alpha1 + sourcesv1alpha1.SchemeGroupVersion.WithKind("IntegrationSource"): &sourcesv1alpha1.IntegrationSource{}, // v1beta2 sourcesv1beta2.SchemeGroupVersion.WithKind("PingSource"): &sourcesv1beta2.PingSource{}, // v1 diff --git a/config/300-integrationsource.yaml b/config/300-integrationsource.yaml new file mode 120000 index 00000000000..20ca829738a --- /dev/null +++ b/config/300-integrationsource.yaml @@ -0,0 +1 @@ +core/resources/integrationsource.yaml \ No newline at end of file diff --git a/config/core/resources/integrationsource.yaml b/config/core/resources/integrationsource.yaml new file mode 100644 index 00000000000..b671eaf5478 --- /dev/null +++ b/config/core/resources/integrationsource.yaml @@ -0,0 +1,447 @@ +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + eventing.knative.dev/source: "true" + duck.knative.dev/source: "true" + knative.dev/crd-install: "true" + app.kubernetes.io/version: devel + app.kubernetes.io/name: knative-eventing + name: integrationsources.sources.knative.dev +spec: + group: sources.knative.dev + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + description: 'IntegrationSource 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: + spec: + type: object + 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 + x-kubernetes-preserve-unknown-fields: true + 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 + CACerts: + description: CACerts is the Certification Authority (CA) certificates in PEM format that the source trusts when sending events to the sink. + type: string + audience: + description: Audience is the OIDC audience. This only needs to be set if the target is not an Addressable and thus the Audience can't be received from the target itself. If specified, it takes precedence over the target's Audience. + type: string + timer: + type: object + properties: + period: + type: integer + title: Period + description: The interval (in milliseconds) to wait between producing the + next message. + default: 1000 + message: + type: string + title: Message + description: The message to generate. + example: hello world + contentType: + type: string + title: Content Type + description: The content type of the generated message. + default: text/plain + repeatCount: + type: integer + title: Repeat Count + description: Specifies a maximum limit of number of fires + aws: + type: object + properties: + s3: + type: object + properties: + bucketNameOrArn: + type: string + title: Bucket Name + description: The S3 Bucket name or Amazon Resource Name (ARN). + deleteAfterRead: + type: boolean + title: Auto-delete Objects + description: Specifies to delete objects after consuming them. + default: true + moveAfterRead: + type: boolean + title: Move Objects After Delete + description: Move objects from S3 bucket to a different bucket after + they have been retrieved. + default: false + destinationBucket: + type: string + title: Destination Bucket + description: Define the destination bucket where an object must be moved + when moveAfterRead is set to true. + destinationBucketPrefix: + type: string + title: Destination Bucket Prefix + description: Define the destination bucket prefix to use when an object + must be moved, and moveAfterRead is set to true. + destinationBucketSuffix: + type: string + title: Destination Bucket Suffix + description: Define the destination bucket suffix to use when an object + must be moved, and moveAfterRead is set to true. + region: + type: string + title: AWS Region + description: The AWS region to access. + autoCreateBucket: + type: boolean + title: Autocreate Bucket + description: Specifies to automatically create the S3 bucket. + default: false + prefix: + type: string + title: Prefix + description: The AWS S3 bucket prefix to consider while searching. + example: folder/ + ignoreBody: + type: boolean + title: Ignore Body + description: If true, the S3 Object body is ignored. Setting this to + true overrides any behavior defined by the `includeBody` option. If + false, the S3 object is put in the body. + default: false + profileCredentialsName: + type: string + title: Profile Credentials Name + description: If using a profile credentials provider this parameter + will set the profile name. + sessionToken: + type: string + title: Session Token + description: Amazon AWS Session Token used when the user needs to assume + a IAM role. + uriEndpointOverride: + type: string + title: Overwrite Endpoint URI + description: The overriding endpoint URI. To use this option, you must + also select the `overrideEndpoint` option. + overrideEndpoint: + type: boolean + title: Endpoint Overwrite + description: Select this option to override the endpoint URI. To use + this option, you must also provide a URI for the `uriEndpointOverride` + option. + default: false + forcePathStyle: + type: boolean + title: Force Path Style + description: Forces path style when accessing AWS S3 buckets. + default: false + delay: + type: integer + title: Delay + description: The number of milliseconds before the next poll of the + selected bucket. + default: 500 + maxMessagesPerPoll: + type: integer + title: Max Messages Per Poll + description: Gets the maximum number of messages as a limit to poll + at each polling. Gets the maximum number of messages as a limit to + poll at each polling. The default value is 10. Use 0 or a negative + number to set it as unlimited. + default: 10 + sqs: + type: object + properties: + queueNameOrArn: + type: string + title: Queue Name + description: The SQS Queue Name or ARN + deleteAfterRead: + type: boolean + title: Auto-delete Messages + description: Delete messages after consuming them + default: true + region: + type: string + title: AWS Region + description: The AWS region to access. + autoCreateQueue: + type: boolean + title: Autocreate Queue + description: Setting the autocreation of the SQS queue. + default: false + amazonAWSHost: + type: string + title: AWS Host + description: The hostname of the Amazon AWS cloud. + default: amazonaws.com + protocol: + type: string + title: Protocol + description: The underlying protocol used to communicate with SQS + default: https + example: http or https + queueURL: + type: string + title: Queue URL + description: The full SQS Queue URL (required if using KEDA) + profileCredentialsName: + type: string + title: Profile Credentials Name + description: If using a profile credentials provider this parameter + will set the profile name. + sessionToken: + type: string + title: Session Token + description: Amazon AWS Session Token used when the user needs to assume + a IAM role. + uriEndpointOverride: + type: string + title: Overwrite Endpoint URI + description: The overriding endpoint URI. To use this option, you must + also select the `overrideEndpoint` option. + overrideEndpoint: + type: boolean + title: Endpoint Overwrite + description: Select this option to override the endpoint URI. To use + this option, you must also provide a URI for the `uriEndpointOverride` + option. + default: false + delay: + type: integer + title: Delay + description: The number of milliseconds before the next poll of the + selected stream + default: 500 + greedy: + type: boolean + title: Greedy Scheduler + description: If greedy is enabled, then the polling will happen immediately + again, if the previous run polled 1 or more messages. + default: false + maxMessagesPerPoll: + type: integer + title: Max Messages Per Poll + description: The maximum number of messages to return. Amazon SQS never + returns more messages than this value (however, fewer messages might + be returned). Valid values 1 to 10. Default 1. + default: 1 + waitTimeSeconds: + type: integer + title: Wait Time Seconds + description: The duration (in seconds) for which the call waits for + a message to arrive in the queue before returning. If a message is + available, the call returns sooner than WaitTimeSeconds. If no messages + are available and the wait time expires, the call does not return + a message list. + visibilityTimeout: + type: integer + title: Visibility Timeout + description: The duration (in seconds) that the received messages are + hidden from subsequent retrieve requests after being retrieved by + a ReceiveMessage request. + ddb-streams: + type: object + properties: + table: + type: string + title: Table + description: The name of the DynamoDB table. + region: + type: string + title: AWS Region + description: The AWS region to access. + streamIteratorType: + type: string + title: Stream Iterator Type + description: Defines where in the DynamoDB stream to start getting records. + There are two enums and the value can be one of FROM_LATEST and FROM_START. + Note that using FROM_START can cause a significant delay before the stream + has caught up to real-time. + default: FROM_LATEST + profileCredentialsName: + type: string + title: Profile Credentials Name + description: If using a profile credentials provider this parameter + will set the profile name. + sessionToken: + type: string + title: Session Token + description: Amazon AWS Session Token used when the user needs to assume + an IAM role. + uriEndpointOverride: + type: string + title: Overwrite Endpoint URI + description: The overriding endpoint URI. To use this option, you must + also select the `overrideEndpoint` option. + overrideEndpoint: + type: boolean + title: Endpoint Overwrite + description: Select this option to override the endpoint URI. To use + this option, you must also provide a URI for the `uriEndpointOverride` + option. + default: false + delay: + type: integer + title: Delay + description: The number of milliseconds before the next poll from the + database. + default: 500 + auth: + description: 'Auth configurations' + type: object + properties: + secret: + description: 'Auth secret' + type: object + properties: + ref: + description: | + Secret reference. + type: object + required: + - name + properties: + name: + description: 'Secret name' + type: string + status: + 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 + auth: + description: Auth provides the relevant information for OIDC authentication. + type: object + properties: + serviceAccountName: + description: ServiceAccountName is the name of the generated service account used for this components OIDC authentication. + type: string + serviceAccountNames: + description: ServiceAccountNames is the list of names of the generated service accounts used for this components OIDC authentication. + type: array + items: + type: string + 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 + sinkCACerts: + description: CACerts is the Certification Authority (CA) certificates in PEM format that the source trusts when sending events to the sink. + type: string + sinkAudience: + description: Audience is the OIDC audience of the sink. + 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 + - knative + - sources + kind: IntegrationSource + plural: integrationsources + singular: integrationsource + scope: Namespaced diff --git a/config/core/roles/source-observer-clusterrole.yaml b/config/core/roles/source-observer-clusterrole.yaml index 565c57984df..29d106c26af 100644 --- a/config/core/roles/source-observer-clusterrole.yaml +++ b/config/core/roles/source-observer-clusterrole.yaml @@ -45,6 +45,7 @@ rules: - pingsources - sinkbindings - containersources + - integrationsources verbs: - get - list diff --git a/config/core/roles/sources-controller-clusterroles.yaml b/config/core/roles/sources-controller-clusterroles.yaml index 7431ab6c8d9..3a87807003e 100644 --- a/config/core/roles/sources-controller-clusterroles.yaml +++ b/config/core/roles/sources-controller-clusterroles.yaml @@ -66,6 +66,9 @@ rules: - "containersources" - "containersources/status" - "containersources/finalizers" + - "integrationsources" + - "integrationsources/status" + - "integrationsources/finalizers" verbs: - "get" - "list" diff --git a/docs/eventing-api.md b/docs/eventing-api.md index 196ab597488..79a98c5983d 100644 --- a/docs/eventing-api.md +++ b/docs/eventing-api.md @@ -37,6 +37,9 @@ sources.knative.dev/v1
  • +sources.knative.dev/v1alpha1 +
  • +
  • sources.knative.dev/v1beta2
  • @@ -7278,6 +7281,929 @@ this SinkBindings OIDC authentication


    +

    sources.knative.dev/v1alpha1

    +

    +

    Package v1alpha1 contains API Schema definitions for the sources v1alpha1 API group.

    +

    +Resource Types: + +

    IntegrationSource +

    +

    +

    IntegrationSource is the Schema for the Integrationsources API

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +apiVersion
    +string
    + +sources.knative.dev/v1alpha1 + +
    +kind
    +string +
    IntegrationSource
    +metadata
    + + +Kubernetes meta/v1.ObjectMeta + + +
    +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
    +spec
    + + +IntegrationSourceSpec + + +
    +
    +
    + + + + + + + + + + + + + +
    +SourceSpec
    + + +knative.dev/pkg/apis/duck/v1.SourceSpec + + +
    +

    +(Members of SourceSpec are embedded into this type.) +

    +

    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.

    +
    +aws
    + + +Aws + + +
    +
    +timer
    + + +Timer + + +
    +

    AWS source configuration

    +
    +
    +status
    + + +IntegrationSourceStatus + + +
    +
    +

    AWSCommon +

    +

    +(Appears on:AWSDDBStreams, AWSS3, AWSSQS) +

    +

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +region
    + +string + +
    +

    Auth is the S3 authentication (accessKey/secretKey) configuration.

    +
    +profileCredentialsName
    + +string + +
    +

    AWS region

    +
    +sessionToken
    + +string + +
    +

    Profile name for profile credentials provider

    +
    +uriEndpointOverride
    + +string + +
    +

    Session token

    +
    +overrideEndpoint
    + +bool + +
    +

    Override endpoint URI

    +
    +

    AWSDDBStreams +

    +

    +(Appears on:Aws) +

    +

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +AWSCommon
    + + +AWSCommon + + +
    +

    +(Members of AWSCommon are embedded into this type.) +

    +
    +table
    + +string + +
    +

    Embeds AWSCommon to inherit its fields in JSON

    +
    +streamIteratorType
    + +string + +
    +

    The name of the DynamoDB table

    +
    +delay
    + +int + +
    +

    Defines where in the DynamoDB stream to start getting records

    +
    +

    AWSS3 +

    +

    +(Appears on:Aws) +

    +

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +AWSCommon
    + + +AWSCommon + + +
    +

    +(Members of AWSCommon are embedded into this type.) +

    +
    +bucketNameOrArn
    + +string + +
    +

    Embeds AWSCommon to inherit its fields in JSON

    +
    +deleteAfterRead
    + +bool + +
    +

    S3 Bucket name or ARN

    +
    +moveAfterRead
    + +bool + +
    +

    Auto-delete objects after reading

    +
    +destinationBucket
    + +string + +
    +

    Move objects after reading

    +
    +destinationBucketPrefix
    + +string + +
    +

    Destination bucket for moved objects

    +
    +destinationBucketSuffix
    + +string + +
    +

    Prefix for moved objects

    +
    +autoCreateBucket
    + +bool + +
    +

    Suffix for moved objects

    +
    +prefix
    + +string + +
    +

    Auto-create S3 bucket

    +
    +ignoreBody
    + +bool + +
    +

    S3 bucket prefix for search

    +
    +forcePathStyle
    + +bool + +
    +

    Ignore object body

    +
    +delay
    + +int + +
    +

    Force path style for bucket access

    +
    +maxMessagesPerPoll
    + +int + +
    +

    Delay between polls in milliseconds

    +
    +

    AWSSQS +

    +

    +(Appears on:Aws) +

    +

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +AWSCommon
    + + +AWSCommon + + +
    +

    +(Members of AWSCommon are embedded into this type.) +

    +
    +queueNameOrArn
    + +string + +
    +

    Embeds AWSCommon to inherit its fields in JSON

    +
    +deleteAfterRead
    + +bool + +
    +

    SQS Queue name or ARN

    +
    +autoCreateQueue
    + +bool + +
    +

    Auto-delete messages after reading

    +
    +amazonAWSHost
    + +string + +
    +

    Auto-create SQS queue

    +
    +protocol
    + +string + +
    +

    AWS host

    +
    +queueURL
    + +string + +
    +

    Communication protocol (http/https)

    +
    +greedy
    + +bool + +
    +

    Full SQS queue URL

    +
    +delay
    + +int + +
    +

    Greedy scheduler

    +
    +maxMessagesPerPoll
    + +int + +
    +

    Delay between polls in milliseconds

    +
    +waitTimeSeconds
    + +int + +
    +

    Max messages to return (1-10)

    +
    +visibilityTimeout
    + +int + +
    +

    Wait time for messages

    +
    +

    Auth +

    +

    +(Appears on:Aws) +

    +

    +

    + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +secret
    + + +Secret + + +
    +

    Auth Secret

    +
    +accessKey
    + +string + +
    +

    AccessKey is the AWS access key ID.

    +
    +secretKey
    + +string + +
    +

    SecretKey is the AWS secret access key.

    +
    +

    Aws +

    +

    +(Appears on:IntegrationSourceSpec) +

    +

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +s3
    + + +AWSS3 + + +
    +
    +sqs
    + + +AWSSQS + + +
    +

    S3 source configuration

    +
    +ddb-streams
    + + +AWSDDBStreams + + +
    +

    SQS source configuration

    +
    +auth
    + + +Auth + + +
    +

    DynamoDB Streams source configuration

    +
    +

    IntegrationSourceSpec +

    +

    +(Appears on:IntegrationSource) +

    +

    +

    IntegrationSourceSpec defines the desired state of IntegrationSource

    +

    + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +SourceSpec
    + + +knative.dev/pkg/apis/duck/v1.SourceSpec + + +
    +

    +(Members of SourceSpec are embedded into this type.) +

    +

    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.

    +
    +aws
    + + +Aws + + +
    +
    +timer
    + + +Timer + + +
    +

    AWS source configuration

    +
    +

    IntegrationSourceStatus +

    +

    +(Appears on:IntegrationSource) +

    +

    +

    IntegrationSourceStatus defines the observed state of IntegrationSource

    +

    + + + + + + + + + + + + + +
    FieldDescription
    +SourceStatus
    + + +knative.dev/pkg/apis/duck/v1.SourceStatus + + +
    +

    +(Members of SourceStatus are embedded into this type.) +

    +

    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.

    +
    +

    Secret +

    +

    +(Appears on:Auth) +

    +

    +

    + + + + + + + + + + + + + +
    FieldDescription
    +ref
    + + +SecretReference + + +
    +

    Secret reference for SASL and SSL configurations.

    +
    +

    SecretReference +

    +

    +(Appears on:Secret) +

    +

    +

    + + + + + + + + + + + + + +
    FieldDescription
    +name
    + +string + +
    +

    Secret name.

    +
    +

    Timer +

    +

    +(Appears on:IntegrationSourceSpec) +

    +

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +period
    + +int + +
    +
    +message
    + +string + +
    +

    Interval (in milliseconds) between producing messages

    +
    +contentType
    + +string + +
    +

    Message to generate

    +
    +repeatCount
    + +int + +
    +

    Content type of generated message

    +
    +

    sources.knative.dev/v1beta2

    Package v1beta2 contains API Schema definitions for the sources v1beta2 API group.

    diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index a0fe67dcbff..dc5206ae11d 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -49,7 +49,7 @@ group "Knative Codegen" # Knative Injection ${KNATIVE_CODEGEN_PKG}/hack/generate-knative.sh "injection" \ knative.dev/eventing/pkg/client knative.dev/eventing/pkg/apis \ - "sinks:v1alpha1 eventing:v1alpha1 eventing:v1beta1 eventing:v1beta2 eventing:v1beta3 eventing:v1 messaging:v1 flows:v1 sources:v1beta2 sources:v1 duck:v1beta1 duck:v1" \ + "sinks:v1alpha1 eventing:v1alpha1 eventing:v1beta1 eventing:v1beta2 eventing:v1beta3 eventing:v1 messaging:v1 flows:v1 sources:v1alpha1 sources:v1beta2 sources:v1 duck:v1beta1 duck:v1" \ --go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt group "Generating API reference docs" diff --git a/pkg/apis/sources/register.go b/pkg/apis/sources/register.go index 04716c8ca65..3571d0a17b4 100644 --- a/pkg/apis/sources/register.go +++ b/pkg/apis/sources/register.go @@ -56,4 +56,10 @@ var ( Group: GroupName, Resource: "containersources", } + + // IntegrationSourceResource respresents a Knative Eventing Sources IntegrationSource + IntegrationSourceResource = schema.GroupResource{ + Group: GroupName, + Resource: "integrationsources", + } ) diff --git a/pkg/apis/sources/v1alpha1/doc.go b/pkg/apis/sources/v1alpha1/doc.go new file mode 100644 index 00000000000..76cd299b7b5 --- /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/integration_conversion.go b/pkg/apis/sources/v1alpha1/integration_conversion.go new file mode 100644 index 00000000000..32bfc7cc4b9 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/integration_conversion.go @@ -0,0 +1,36 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "context" + "fmt" + + "knative.dev/pkg/apis" +) + +// ConvertTo implements apis.Convertible +// Converts source from v1alpha1.IntegrationSource into a higher version. +func (source *IntegrationSource) ConvertTo(ctx context.Context, obj apis.Convertible) error { + return fmt.Errorf("v1alpha1 is the highest known version, got: %T", source) +} + +// ConvertFrom implements apis.Convertible +// Converts source from a higher version into v1beta2.IntegrationSource +func (source *IntegrationSource) ConvertFrom(ctx context.Context, obj apis.Convertible) error { + return fmt.Errorf("v1alpha1 is the highest known version, got: %T", source) +} diff --git a/pkg/apis/sources/v1alpha1/integration_conversion_test.go b/pkg/apis/sources/v1alpha1/integration_conversion_test.go new file mode 100644 index 00000000000..2272ecce52e --- /dev/null +++ b/pkg/apis/sources/v1alpha1/integration_conversion_test.go @@ -0,0 +1,48 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "context" + "errors" + "testing" + + "knative.dev/pkg/apis" +) + +// 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 TestConversionConversionBadType(t *testing.T) { + good, bad := &IntegrationSource{}, &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) + } +} diff --git a/pkg/apis/sources/v1alpha1/integration_defaults.go b/pkg/apis/sources/v1alpha1/integration_defaults.go new file mode 100644 index 00000000000..70d82c73e5c --- /dev/null +++ b/pkg/apis/sources/v1alpha1/integration_defaults.go @@ -0,0 +1,26 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 (source *IntegrationSource) SetDefaults(ctx context.Context) { + source.Spec.SetDefaults(ctx) +} + +func (source *IntegrationSourceSpec) SetDefaults(ctx context.Context) { +} diff --git a/pkg/apis/sources/v1alpha1/integration_defaults_test.go b/pkg/apis/sources/v1alpha1/integration_defaults_test.go new file mode 100644 index 00000000000..ed586492d54 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/integration_defaults_test.go @@ -0,0 +1,39 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestSetDefaults(t *testing.T) { + testCases := map[string]struct { + initial IntegrationSource + expected IntegrationSource + }{} + 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/v1alpha1/integration_lifecycle.go b/pkg/apis/sources/v1alpha1/integration_lifecycle.go new file mode 100644 index 00000000000..4f25e70e520 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/integration_lifecycle.go @@ -0,0 +1,76 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 ( + corev1 "k8s.io/api/core/v1" + v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/pkg/apis" +) + +const ( + // IntegrationSourceConditionReady has status True when the IntegrationSource is ready to send events. + IntegrationSourceConditionReady = apis.ConditionReady + + // IntegrationSourceConditionReceiveAdapterReady has status True when the IntegrationSource's Deployment is ready. + IntegrationSourceConditionContainerSourceReady apis.ConditionType = "ContainerSourceReady" +) + +var IntegrationCondSet = apis.NewLivingConditionSet( + IntegrationSourceConditionContainerSourceReady, +) + +// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. +func (*IntegrationSource) GetConditionSet() apis.ConditionSet { + return IntegrationCondSet +} + +// GetTopLevelCondition returns the top level condition. +func (s *IntegrationSourceStatus) GetTopLevelCondition() *apis.Condition { + return IntegrationCondSet.Manage(s).GetTopLevelCondition() +} + +// InitializeConditions sets relevant unset conditions to Unknown state. +func (s *IntegrationSourceStatus) InitializeConditions() { + IntegrationCondSet.Manage(s).InitializeConditions() +} + +func (iss *IntegrationSourceStatus) IsReady() bool { + return IntegrationCondSet.Manage(iss).IsHappy() +} + +func (s *IntegrationSourceStatus) PropagateContainerSourceStatus(status *v1.ContainerSourceStatus) { + //// Do not copy conditions nor observedGeneration + s.SourceStatus = *status.SourceStatus.DeepCopy() + + cond := status.GetCondition(apis.ConditionReady) + switch { + case cond == nil: + IntegrationCondSet.Manage(s).MarkUnknown(IntegrationSourceConditionContainerSourceReady, "", "") + case cond.Status == corev1.ConditionTrue: + IntegrationCondSet.Manage(s).MarkTrue(IntegrationSourceConditionContainerSourceReady) + case cond.Status == corev1.ConditionFalse: + IntegrationCondSet.Manage(s).MarkFalse(IntegrationSourceConditionContainerSourceReady, cond.Reason, cond.Message) + case cond.Status == corev1.ConditionUnknown: + IntegrationCondSet.Manage(s).MarkUnknown(IntegrationSourceConditionContainerSourceReady, cond.Reason, cond.Message) + default: + IntegrationCondSet.Manage(s).MarkUnknown(IntegrationSourceConditionContainerSourceReady, cond.Reason, cond.Message) + } + + // Propagate ContainerSources AuthStatus to IntegrationSources AuthStatus + s.Auth = status.Auth +} diff --git a/pkg/apis/sources/v1alpha1/integration_lifecycle_test.go b/pkg/apis/sources/v1alpha1/integration_lifecycle_test.go new file mode 100644 index 00000000000..26b2a0046fa --- /dev/null +++ b/pkg/apis/sources/v1alpha1/integration_lifecycle_test.go @@ -0,0 +1,189 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + corev1 "k8s.io/api/core/v1" + v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +var ( + readyContainerSource = &v1.ContainerSource{ + Status: v1.ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: apis.ConditionReady, + Status: corev1.ConditionTrue, + }}, + }, + }, + }, + } + + notReadyContainerSource = &v1.ContainerSource{ + Status: v1.ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: apis.ConditionReady, + Status: corev1.ConditionFalse, + Reason: "Testing", + Message: "hi", + }}, + }, + }, + }, + } +) + +func TestIntegrationSourceGetConditionSet(t *testing.T) { + r := &IntegrationSource{} + + if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want { + t.Errorf("GetTopLevelCondition=%v, want=%v", got, want) + } +} + +func TestIntegrationSourceStatusIsReady(t *testing.T) { + tests := []struct { + name string + s *IntegrationSourceStatus + wantConditionStatus corev1.ConditionStatus + want bool + }{{ + name: "uninitialized", + s: &IntegrationSourceStatus{}, + want: false, + }, { + name: "initialized", + s: func() *IntegrationSourceStatus { + s := &IntegrationSourceStatus{} + s.InitializeConditions() + return s + }(), + wantConditionStatus: corev1.ConditionUnknown, + want: false, + }, { + name: "mark ready container source", + s: func() *IntegrationSourceStatus { + s := &IntegrationSourceStatus{} + s.InitializeConditions() + s.PropagateContainerSourceStatus(&readyContainerSource.Status) + return s + }(), + wantConditionStatus: corev1.ConditionTrue, + want: true, + }, { + name: "mark not ready container source", + s: func() *IntegrationSourceStatus { + s := &IntegrationSourceStatus{} + s.InitializeConditions() + s.PropagateContainerSourceStatus(¬ReadyContainerSource.Status) + 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 TestIntegrationSourceStatusGetCondition(t *testing.T) { + tests := []struct { + name string + s *IntegrationSourceStatus + condQuery apis.ConditionType + want *apis.Condition + }{{ + name: "uninitialized", + s: &IntegrationSourceStatus{}, + condQuery: IntegrationSourceConditionReady, + want: nil, + }, { + name: "initialized", + s: func() *IntegrationSourceStatus { + s := &IntegrationSourceStatus{} + s.InitializeConditions() + return s + }(), + condQuery: IntegrationSourceConditionReady, + want: &apis.Condition{ + Type: IntegrationSourceConditionReady, + Status: corev1.ConditionUnknown, + }, + }, { + name: "mark ready cs", + s: func() *IntegrationSourceStatus { + s := &IntegrationSourceStatus{} + s.InitializeConditions() + s.PropagateContainerSourceStatus(&readyContainerSource.Status) + return s + }(), + condQuery: IntegrationSourceConditionReady, + want: &apis.Condition{ + Type: IntegrationSourceConditionReady, + Status: corev1.ConditionTrue, + }, + }, { + name: "mark ready cs then no cs", + s: func() *IntegrationSourceStatus { + s := &IntegrationSourceStatus{} + s.InitializeConditions() + s.PropagateContainerSourceStatus(&readyContainerSource.Status) + s.PropagateContainerSourceStatus(¬ReadyContainerSource.Status) + return s + }(), + condQuery: IntegrationSourceConditionReady, + want: &apis.Condition{ + Type: IntegrationSourceConditionReady, + Status: corev1.ConditionFalse, + Reason: "Testing", + Message: "hi", + }, + }} + + 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/v1alpha1/integration_types.go b/pkg/apis/sources/v1alpha1/integration_types.go new file mode 100644 index 00000000000..15b4e60a2b0 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/integration_types.go @@ -0,0 +1,185 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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" + "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 + +// IntegrationSource is the Schema for the Integrationsources API +type IntegrationSource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec IntegrationSourceSpec `json:"spec,omitempty"` + Status IntegrationSourceStatus `json:"status,omitempty"` +} + +var ( + _ runtime.Object = (*IntegrationSource)(nil) + _ kmeta.OwnerRefable = (*IntegrationSource)(nil) + _ apis.Validatable = (*IntegrationSource)(nil) + _ apis.Defaultable = (*IntegrationSource)(nil) + _ apis.HasSpec = (*IntegrationSource)(nil) + _ duckv1.KRShaped = (*IntegrationSource)(nil) + _ apis.Convertible = (*IntegrationSource)(nil) +) + +// IntegrationSourceSpec defines the desired state of IntegrationSource +type IntegrationSourceSpec 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"` + + Aws *Aws `json:"aws,omitempty"` // AWS source configuration + Timer *Timer `json:"timer,omitempty"` // Timer configuration +} + +type Timer struct { + Period int `json:"period" default:"1000"` // Interval (in milliseconds) between producing messages + Message string `json:"message"` // Message to generate + ContentType string `json:"contentType" default:"text/plain"` // Content type of generated message + RepeatCount int `json:"repeatCount,omitempty"` // Max number of fires (optional) +} + +type AWSCommon struct { + // Auth is the S3 authentication (accessKey/secretKey) configuration. + Region string `json:"region,omitempty"` // AWS region + ProfileCredentialsName string `json:"profileCredentialsName,omitempty"` // Profile name for profile credentials provider + SessionToken string `json:"sessionToken,omitempty"` // Session token + URIEndpointOverride string `json:"uriEndpointOverride,omitempty"` // Override endpoint URI + OverrideEndpoint bool `json:"overrideEndpoint" default:"false"` // Override endpoint flag +} + +type AWSS3 struct { + AWSCommon `json:",inline"` // Embeds AWSCommon to inherit its fields in JSON + BucketNameOrArn string `json:"bucketNameOrArn,omitempty"` // S3 Bucket name or ARN + DeleteAfterRead bool `json:"deleteAfterRead" default:"true"` // Auto-delete objects after reading + MoveAfterRead bool `json:"moveAfterRead" default:"false"` // Move objects after reading + DestinationBucket string `json:"destinationBucket,omitempty"` // Destination bucket for moved objects + DestinationBucketPrefix string `json:"destinationBucketPrefix,omitempty"` // Prefix for moved objects + DestinationBucketSuffix string `json:"destinationBucketSuffix,omitempty"` // Suffix for moved objects + AutoCreateBucket bool `json:"autoCreateBucket" default:"false"` // Auto-create S3 bucket + Prefix string `json:"prefix,omitempty"` // S3 bucket prefix for search + IgnoreBody bool `json:"ignoreBody" default:"false"` // Ignore object body + ForcePathStyle bool `json:"forcePathStyle" default:"false"` // Force path style for bucket access + Delay int `json:"delay" default:"500"` // Delay between polls in milliseconds + MaxMessagesPerPoll int `json:"maxMessagesPerPoll" default:"10"` // Max messages to poll per request +} + +type AWSSQS struct { + AWSCommon `json:",inline"` // Embeds AWSCommon to inherit its fields in JSON + QueueNameOrArn string `json:"queueNameOrArn,omitempty"` // SQS Queue name or ARN + DeleteAfterRead bool `json:"deleteAfterRead" default:"true"` // Auto-delete messages after reading + AutoCreateQueue bool `json:"autoCreateQueue" default:"false"` // Auto-create SQS queue + AmazonAWSHost string `json:"amazonAWSHost" default:"amazonaws.com"` // AWS host + Protocol string `json:"protocol" default:"https"` // Communication protocol (http/https) + QueueURL string `json:"queueURL,omitempty"` // Full SQS queue URL + Greedy bool `json:"greedy" default:"false"` // Greedy scheduler + Delay int `json:"delay" default:"500"` // Delay between polls in milliseconds + MaxMessagesPerPoll int `json:"maxMessagesPerPoll" default:"1"` // Max messages to return (1-10) + WaitTimeSeconds int `json:"waitTimeSeconds,omitempty"` // Wait time for messages + VisibilityTimeout int `json:"visibilityTimeout,omitempty"` // Visibility timeout in seconds +} + +type AWSDDBStreams struct { + AWSCommon `json:",inline"` // Embeds AWSCommon to inherit its fields in JSON + Table string `json:"table,omitempty"` // The name of the DynamoDB table + StreamIteratorType string `json:"streamIteratorType,omitempty" default:"FROM_LATEST"` // Defines where in the DynamoDB stream to start getting records + Delay int `json:"delay,omitempty" default:"500"` // Delay in milliseconds before the next poll from the database +} + +type Aws struct { + S3 *AWSS3 `json:"s3,omitempty"` // S3 source configuration + SQS *AWSSQS `json:"sqs,omitempty"` // SQS source configuration + DDBStreams *AWSDDBStreams `json:"ddb-streams,omitempty"` // DynamoDB Streams source configuration + Auth *Auth `json:"auth,omitempty"` +} + +type Auth struct { + // Auth Secret + Secret *Secret `json:"secret,omitempty"` + + // AccessKey is the AWS access key ID. + AccessKey string `json:"accessKey,omitempty"` + + // SecretKey is the AWS secret access key. + SecretKey string `json:"secretKey,omitempty"` +} + +func (a *Auth) HasAuth() bool { + return a != nil && a.Secret != nil && + a.Secret.Ref != nil && a.Secret.Ref.Name != "" +} + +type Secret struct { + // Secret reference for SASL and SSL configurations. + Ref *SecretReference `json:"ref,omitempty"` +} + +type SecretReference struct { + // Secret name. + Name string `json:"name"` +} + +// GetGroupVersionKind returns the GroupVersionKind. +func (*IntegrationSource) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("IntegrationSource") +} + +// IntegrationSourceStatus defines the observed state of IntegrationSource +type IntegrationSourceStatus 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 + +// IntegrationSourceList contains a list of IntegrationSource +type IntegrationSourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []IntegrationSource `json:"items"` +} + +// GetUntypedSpec returns the spec of the IntegrationSource. +func (c *IntegrationSource) GetUntypedSpec() interface{} { + return c.Spec +} + +// GetStatus retrieves the status of the IntegrationSource. Implements the KRShaped interface. +func (c *IntegrationSource) GetStatus() *duckv1.Status { + return &c.Status.Status +} diff --git a/pkg/apis/sources/v1alpha1/integration_types_test.go b/pkg/apis/sources/v1alpha1/integration_types_test.go new file mode 100644 index 00000000000..83b2ff0ffc5 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/integration_types_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 v1alpha1 + +import ( + "testing" +) + +func TestIntegrationSource_GetStatus(t *testing.T) { + r := &IntegrationSource{ + Status: IntegrationSourceStatus{}, + } + if got, want := r.GetStatus(), &r.Status.Status; got != want { + t.Errorf("GetStatus=%v, want=%v", got, want) + } +} + +func TestIntegrationSource_GetGroupVersionKind(t *testing.T) { + src := &IntegrationSource{} + gvk := src.GetGroupVersionKind() + + if gvk.Kind != "IntegrationSource" { + t.Errorf("Should be IntegrationSource.") + } +} + +func TestTimer(t *testing.T) { + timer := Timer{ + Period: 1000, + Message: "test message", + } + + if timer.Period != 1000 { + t.Errorf("Timer.Period = %v, want '1000'", timer.Period) + } + if timer.Message != "test message" { + t.Errorf("Timer.Message = %v, want 'test message'", timer.Message) + } +} + +func TestAWS(t *testing.T) { + s3 := AWSS3{ + AWSCommon: AWSCommon{ + Region: "eu-north-1", + }, + BucketNameOrArn: "example-bucket", + } + + if s3.Region != "eu-north-1" { + t.Errorf("AWSS3.Region = %v, want 'eu-north-1'", s3.Region) + } + + sqs := AWSSQS{ + AWSCommon: AWSCommon{ + Region: "eu-north-1", + }, + QueueNameOrArn: "example-queue", + } + + if sqs.Region != "eu-north-1" { + t.Errorf("AWSSQS.Region = %v, want 'eu-north-1'", sqs.Region) + } + + ddbStreams := AWSDDBStreams{ + AWSCommon: AWSCommon{ + Region: "eu-north-1", + }, + Table: "example-table", + } + + if ddbStreams.Region != "eu-north-1" { + t.Errorf("AWSDDBStreams.Region = %v, want 'eu-north-1'", ddbStreams.Region) + } +} + +// TestAuthFieldAccess tests the HasAuth method and field access in Auth struct +func TestAuthFieldAccess(t *testing.T) { + auth := Auth{ + Secret: &Secret{ + Ref: &SecretReference{ + Name: "aws-secret", + }, + }, + AccessKey: "access-key", + SecretKey: "secret-key", + } + + if !auth.HasAuth() { + t.Error("Auth.HasAuth() = false, want true") + } + + if auth.Secret.Ref.Name != "aws-secret" { + t.Errorf("Auth.Secret.Ref.Name = %v, want 'aws-secret'", auth.Secret.Ref.Name) + } +} diff --git a/pkg/apis/sources/v1alpha1/integration_validation.go b/pkg/apis/sources/v1alpha1/integration_validation.go new file mode 100644 index 00000000000..4df223eb5dd --- /dev/null +++ b/pkg/apis/sources/v1alpha1/integration_validation.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" + + "knative.dev/pkg/apis" +) + +func (source *IntegrationSource) Validate(ctx context.Context) *apis.FieldError { + ctx = apis.WithinParent(ctx, source.ObjectMeta) + return source.Spec.Validate(ctx).ViaField("spec") +} + +func (spec *IntegrationSourceSpec) Validate(ctx context.Context) *apis.FieldError { + var errs *apis.FieldError + + // Count how many fields are set to ensure mutual exclusivity + sourceSetCount := 0 + if spec.Timer != nil { + sourceSetCount++ + } + if spec.Aws != nil { + if spec.Aws.S3 != nil { + sourceSetCount++ + } + if spec.Aws.SQS != nil { + sourceSetCount++ + } + if spec.Aws.DDBStreams != nil { + sourceSetCount++ + } + } + + // Validate that only one source field is set + if sourceSetCount > 1 { + errs = errs.Also(apis.ErrGeneric("only one source type can be set", "spec")) + } else if sourceSetCount == 0 { + errs = errs.Also(apis.ErrGeneric("at least one source type must be specified", "spec")) + } + + // Only perform AWS-specific validation if exactly one AWS source is configured + if sourceSetCount == 1 && spec.Aws != nil { + if spec.Aws.S3 != nil || spec.Aws.SQS != nil || spec.Aws.DDBStreams != nil { + // Check that AWS Auth is properly configured + if !spec.Aws.Auth.HasAuth() { + errs = errs.Also(apis.ErrMissingField("aws.auth.secret.ref.name")) + } + } + + // Additional validation for AWS S3 required fields + if spec.Aws.S3 != nil { + if spec.Aws.S3.BucketNameOrArn == "" { + errs = errs.Also(apis.ErrMissingField("aws.s3.bucketNameOrArn")) + } + if spec.Aws.S3.Region == "" { + errs = errs.Also(apis.ErrMissingField("aws.s3.region")) + } + } + + // Additional validation for AWS SQS required fields + if spec.Aws.SQS != nil { + if spec.Aws.SQS.QueueNameOrArn == "" { + errs = errs.Also(apis.ErrMissingField("aws.sqs.queueNameOrArn")) + } + if spec.Aws.SQS.Region == "" { + errs = errs.Also(apis.ErrMissingField("aws.sqs.region")) + } + } + + // Additional validation for AWS DDBStreams required fields + if spec.Aws.DDBStreams != nil { + if spec.Aws.DDBStreams.Table == "" { + errs = errs.Also(apis.ErrMissingField("aws.ddb-streams.table")) + } + if spec.Aws.DDBStreams.Region == "" { + errs = errs.Also(apis.ErrMissingField("aws.ddb-streams.region")) + } + } + } + + return errs +} diff --git a/pkg/apis/sources/v1alpha1/integration_validation_test.go b/pkg/apis/sources/v1alpha1/integration_validation_test.go new file mode 100644 index 00000000000..6e0071703d1 --- /dev/null +++ b/pkg/apis/sources/v1alpha1/integration_validation_test.go @@ -0,0 +1,240 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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" + "knative.dev/pkg/apis" +) + +func TestIntegrationSourceSpecValidation(t *testing.T) { + tests := []struct { + name string + spec IntegrationSourceSpec + want *apis.FieldError + }{ + { + name: "valid timer source", + spec: IntegrationSourceSpec{ + Timer: &Timer{ + Period: 1000, + Message: "test message", + ContentType: "text/plain", + }, + }, + want: nil, + }, + { + name: "valid AWS S3 source with auth and region", + spec: IntegrationSourceSpec{ + Aws: &Aws{ + S3: &AWSS3{ + AWSCommon: AWSCommon{ + Region: "us-east-1", + }, + BucketNameOrArn: "example-bucket", + }, + Auth: &Auth{ + Secret: &Secret{ + Ref: &SecretReference{ + Name: "aws-secret", + }, + }, + }, + }, + }, + want: nil, + }, + { + name: "valid AWS SQS source with auth and region", + spec: IntegrationSourceSpec{ + Aws: &Aws{ + SQS: &AWSSQS{ + AWSCommon: AWSCommon{ + Region: "us-east-1", + }, + QueueNameOrArn: "example-queue", + }, + Auth: &Auth{ + Secret: &Secret{ + Ref: &SecretReference{ + Name: "aws-secret", + }, + }, + }, + }, + }, + want: nil, + }, + { + name: "valid AWS DDBStreams source with auth and region", + spec: IntegrationSourceSpec{ + Aws: &Aws{ + DDBStreams: &AWSDDBStreams{ + AWSCommon: AWSCommon{ + Region: "us-east-1", + }, + Table: "example-table", + }, + Auth: &Auth{ + Secret: &Secret{ + Ref: &SecretReference{ + Name: "aws-secret", + }, + }, + }, + }, + }, + want: nil, + }, + { + name: "multiple sources set (invalid)", + spec: IntegrationSourceSpec{ + Timer: &Timer{ + Period: 1000, + Message: "test message", + ContentType: "text/plain", + }, + Aws: &Aws{ + S3: &AWSS3{ + AWSCommon: AWSCommon{ + Region: "us-east-1", + }, + BucketNameOrArn: "example-bucket", + }, + }, + }, + want: apis.ErrGeneric("only one source type can be set", "spec"), + }, + { + name: "multiple AWS sources set (invalid)", + spec: IntegrationSourceSpec{ + Aws: &Aws{ + S3: &AWSS3{ + AWSCommon: AWSCommon{ + Region: "us-east-1", + }, + BucketNameOrArn: "example-bucket", + }, + SQS: &AWSSQS{ + AWSCommon: AWSCommon{ + Region: "us-east-1", + }, + QueueNameOrArn: "example-queue", + }, + Auth: &Auth{ + Secret: &Secret{ + Ref: &SecretReference{ + Name: "aws-secret", + }, + }, + }, + }, + }, + want: apis.ErrGeneric("only one source type can be set", "spec"), + }, + { + name: "AWS SQS source without QueueNameOrArn (invalid)", + spec: IntegrationSourceSpec{ + Aws: &Aws{ + SQS: &AWSSQS{ + AWSCommon: AWSCommon{ + Region: "us-east-1", + }, + }, + Auth: &Auth{ + Secret: &Secret{ + Ref: &SecretReference{ + Name: "aws-secret", + }, + }, + }, + }, + }, + want: apis.ErrMissingField("aws.sqs.queueNameOrArn"), + }, + { + name: "AWS DDBStreams source without Table (invalid)", + spec: IntegrationSourceSpec{ + Aws: &Aws{ + DDBStreams: &AWSDDBStreams{ + AWSCommon: AWSCommon{ + Region: "us-east-1", + }, + }, + Auth: &Auth{ + Secret: &Secret{ + Ref: &SecretReference{ + Name: "aws-secret", + }, + }, + }, + }, + }, + want: apis.ErrMissingField("aws.ddb-streams.table"), + }, + { + name: "no source type specified (invalid)", + spec: IntegrationSourceSpec{}, + want: apis.ErrGeneric("at least one source type must be specified", "spec"), + }, + { + name: "AWS source without auth (invalid)", + spec: IntegrationSourceSpec{ + Aws: &Aws{ + S3: &AWSS3{ + AWSCommon: AWSCommon{ + Region: "us-east-1", + }, + BucketNameOrArn: "example-bucket", + }, + }, + }, + want: apis.ErrMissingField("aws.auth.secret.ref.name"), + }, + { + name: "AWS S3 source without region (invalid)", + spec: IntegrationSourceSpec{ + Aws: &Aws{ + S3: &AWSS3{ + BucketNameOrArn: "example-bucket", + }, + Auth: &Auth{ + Secret: &Secret{ + Ref: &SecretReference{ + Name: "aws-secret", + }, + }, + }, + }, + }, + want: apis.ErrMissingField("aws.s3.region"), + }, + } + + 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.Errorf("IntegrationSourceSpec.Validate (-want, +got) = %v", diff) + } + }) + } +} diff --git a/pkg/apis/sources/v1alpha1/register.go b/pkg/apis/sources/v1alpha1/register.go new file mode 100644 index 00000000000..a812c885f5b --- /dev/null +++ b/pkg/apis/sources/v1alpha1/register.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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/eventing/pkg/apis/sources" +) + +// 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, + &IntegrationSource{}, + &IntegrationSourceList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} 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..5fe9cf729af --- /dev/null +++ b/pkg/apis/sources/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,308 @@ +//go:build !ignore_autogenerated +// +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" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AWSCommon) DeepCopyInto(out *AWSCommon) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSCommon. +func (in *AWSCommon) DeepCopy() *AWSCommon { + if in == nil { + return nil + } + out := new(AWSCommon) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AWSDDBStreams) DeepCopyInto(out *AWSDDBStreams) { + *out = *in + out.AWSCommon = in.AWSCommon + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSDDBStreams. +func (in *AWSDDBStreams) DeepCopy() *AWSDDBStreams { + if in == nil { + return nil + } + out := new(AWSDDBStreams) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AWSS3) DeepCopyInto(out *AWSS3) { + *out = *in + out.AWSCommon = in.AWSCommon + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSS3. +func (in *AWSS3) DeepCopy() *AWSS3 { + if in == nil { + return nil + } + out := new(AWSS3) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AWSSQS) DeepCopyInto(out *AWSSQS) { + *out = *in + out.AWSCommon = in.AWSCommon + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSSQS. +func (in *AWSSQS) DeepCopy() *AWSSQS { + if in == nil { + return nil + } + out := new(AWSSQS) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Auth) DeepCopyInto(out *Auth) { + *out = *in + if in.Secret != nil { + in, out := &in.Secret, &out.Secret + *out = new(Secret) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Auth. +func (in *Auth) DeepCopy() *Auth { + if in == nil { + return nil + } + out := new(Auth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Aws) DeepCopyInto(out *Aws) { + *out = *in + if in.S3 != nil { + in, out := &in.S3, &out.S3 + *out = new(AWSS3) + **out = **in + } + if in.SQS != nil { + in, out := &in.SQS, &out.SQS + *out = new(AWSSQS) + **out = **in + } + if in.DDBStreams != nil { + in, out := &in.DDBStreams, &out.DDBStreams + *out = new(AWSDDBStreams) + **out = **in + } + if in.Auth != nil { + in, out := &in.Auth, &out.Auth + *out = new(Auth) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Aws. +func (in *Aws) DeepCopy() *Aws { + if in == nil { + return nil + } + out := new(Aws) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IntegrationSource) DeepCopyInto(out *IntegrationSource) { + *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 IntegrationSource. +func (in *IntegrationSource) DeepCopy() *IntegrationSource { + if in == nil { + return nil + } + out := new(IntegrationSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IntegrationSource) 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 *IntegrationSourceList) DeepCopyInto(out *IntegrationSourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]IntegrationSource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntegrationSourceList. +func (in *IntegrationSourceList) DeepCopy() *IntegrationSourceList { + if in == nil { + return nil + } + out := new(IntegrationSourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IntegrationSourceList) 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 *IntegrationSourceSpec) DeepCopyInto(out *IntegrationSourceSpec) { + *out = *in + in.SourceSpec.DeepCopyInto(&out.SourceSpec) + if in.Aws != nil { + in, out := &in.Aws, &out.Aws + *out = new(Aws) + (*in).DeepCopyInto(*out) + } + if in.Timer != nil { + in, out := &in.Timer, &out.Timer + *out = new(Timer) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntegrationSourceSpec. +func (in *IntegrationSourceSpec) DeepCopy() *IntegrationSourceSpec { + if in == nil { + return nil + } + out := new(IntegrationSourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IntegrationSourceStatus) DeepCopyInto(out *IntegrationSourceStatus) { + *out = *in + in.SourceStatus.DeepCopyInto(&out.SourceStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IntegrationSourceStatus. +func (in *IntegrationSourceStatus) DeepCopy() *IntegrationSourceStatus { + if in == nil { + return nil + } + out := new(IntegrationSourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Secret) DeepCopyInto(out *Secret) { + *out = *in + if in.Ref != nil { + in, out := &in.Ref, &out.Ref + *out = new(SecretReference) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Secret. +func (in *Secret) DeepCopy() *Secret { + if in == nil { + return nil + } + out := new(Secret) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretReference) DeepCopyInto(out *SecretReference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretReference. +func (in *SecretReference) DeepCopy() *SecretReference { + if in == nil { + return nil + } + out := new(SecretReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Timer) DeepCopyInto(out *Timer) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Timer. +func (in *Timer) DeepCopy() *Timer { + if in == nil { + return nil + } + out := new(Timer) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index 72890b65cc8..dcb08498521 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -34,6 +34,7 @@ import ( messagingv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/messaging/v1" sinksv1alpha1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sinks/v1alpha1" sourcesv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1" + sourcesv1alpha1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha1" sourcesv1beta2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1beta2" ) @@ -48,6 +49,7 @@ type Interface interface { MessagingV1() messagingv1.MessagingV1Interface SinksV1alpha1() sinksv1alpha1.SinksV1alpha1Interface SourcesV1() sourcesv1.SourcesV1Interface + SourcesV1alpha1() sourcesv1alpha1.SourcesV1alpha1Interface SourcesV1beta2() sourcesv1beta2.SourcesV1beta2Interface } @@ -63,6 +65,7 @@ type Clientset struct { messagingV1 *messagingv1.MessagingV1Client sinksV1alpha1 *sinksv1alpha1.SinksV1alpha1Client sourcesV1 *sourcesv1.SourcesV1Client + sourcesV1alpha1 *sourcesv1alpha1.SourcesV1alpha1Client sourcesV1beta2 *sourcesv1beta2.SourcesV1beta2Client } @@ -111,6 +114,11 @@ func (c *Clientset) SourcesV1() sourcesv1.SourcesV1Interface { return c.sourcesV1 } +// SourcesV1alpha1 retrieves the SourcesV1alpha1Client +func (c *Clientset) SourcesV1alpha1() sourcesv1alpha1.SourcesV1alpha1Interface { + return c.sourcesV1alpha1 +} + // SourcesV1beta2 retrieves the SourcesV1beta2Client func (c *Clientset) SourcesV1beta2() sourcesv1beta2.SourcesV1beta2Interface { return c.sourcesV1beta2 @@ -196,6 +204,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, if err != nil { return nil, err } + cs.sourcesV1alpha1, err = sourcesv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } cs.sourcesV1beta2, err = sourcesv1beta2.NewForConfigAndClient(&configShallowCopy, httpClient) if err != nil { return nil, err @@ -230,6 +242,7 @@ func New(c rest.Interface) *Clientset { cs.messagingV1 = messagingv1.New(c) cs.sinksV1alpha1 = sinksv1alpha1.New(c) cs.sourcesV1 = sourcesv1.New(c) + cs.sourcesV1alpha1 = sourcesv1alpha1.New(c) cs.sourcesV1beta2 = sourcesv1beta2.New(c) cs.DiscoveryClient = discovery.NewDiscoveryClient(c) diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go index 83462864136..90b59fe63a7 100644 --- a/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -43,6 +43,8 @@ import ( fakesinksv1alpha1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sinks/v1alpha1/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" sourcesv1beta2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1beta2" fakesourcesv1beta2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1beta2/fake" ) @@ -142,6 +144,11 @@ func (c *Clientset) SourcesV1() sourcesv1.SourcesV1Interface { return &fakesourcesv1.FakeSourcesV1{Fake: &c.Fake} } +// SourcesV1alpha1 retrieves the SourcesV1alpha1Client +func (c *Clientset) SourcesV1alpha1() sourcesv1alpha1.SourcesV1alpha1Interface { + return &fakesourcesv1alpha1.FakeSourcesV1alpha1{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 6fc03b4ba58..834b36ce0ae 100644 --- a/pkg/client/clientset/versioned/fake/register.go +++ b/pkg/client/clientset/versioned/fake/register.go @@ -33,6 +33,7 @@ import ( messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" sinksv1alpha1 "knative.dev/eventing/pkg/apis/sinks/v1alpha1" sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + sourcesv1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" ) @@ -49,6 +50,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{ messagingv1.AddToScheme, sinksv1alpha1.AddToScheme, sourcesv1.AddToScheme, + sourcesv1alpha1.AddToScheme, sourcesv1beta2.AddToScheme, } diff --git a/pkg/client/clientset/versioned/scheme/register.go b/pkg/client/clientset/versioned/scheme/register.go index 5d2955e038f..786a698e532 100644 --- a/pkg/client/clientset/versioned/scheme/register.go +++ b/pkg/client/clientset/versioned/scheme/register.go @@ -33,6 +33,7 @@ import ( messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" sinksv1alpha1 "knative.dev/eventing/pkg/apis/sinks/v1alpha1" sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + sourcesv1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" ) @@ -49,6 +50,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{ messagingv1.AddToScheme, sinksv1alpha1.AddToScheme, sourcesv1.AddToScheme, + sourcesv1alpha1.AddToScheme, sourcesv1beta2.AddToScheme, } 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..0b13fd8e001 --- /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..40528db3a52 --- /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_integrationsource.go b/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/fake_integrationsource.go new file mode 100644 index 00000000000..cf59005eb09 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/fake_integrationsource.go @@ -0,0 +1,141 @@ +/* +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" + 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" +) + +// FakeIntegrationSources implements IntegrationSourceInterface +type FakeIntegrationSources struct { + Fake *FakeSourcesV1alpha1 + ns string +} + +var integrationsourcesResource = v1alpha1.SchemeGroupVersion.WithResource("integrationsources") + +var integrationsourcesKind = v1alpha1.SchemeGroupVersion.WithKind("IntegrationSource") + +// Get takes name of the integrationSource, and returns the corresponding integrationSource object, and an error if there is any. +func (c *FakeIntegrationSources) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.IntegrationSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(integrationsourcesResource, c.ns, name), &v1alpha1.IntegrationSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.IntegrationSource), err +} + +// List takes label and field selectors, and returns the list of IntegrationSources that match those selectors. +func (c *FakeIntegrationSources) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.IntegrationSourceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(integrationsourcesResource, integrationsourcesKind, c.ns, opts), &v1alpha1.IntegrationSourceList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.IntegrationSourceList{ListMeta: obj.(*v1alpha1.IntegrationSourceList).ListMeta} + for _, item := range obj.(*v1alpha1.IntegrationSourceList).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 integrationSources. +func (c *FakeIntegrationSources) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(integrationsourcesResource, c.ns, opts)) + +} + +// Create takes the representation of a integrationSource and creates it. Returns the server's representation of the integrationSource, and an error, if there is any. +func (c *FakeIntegrationSources) Create(ctx context.Context, integrationSource *v1alpha1.IntegrationSource, opts v1.CreateOptions) (result *v1alpha1.IntegrationSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(integrationsourcesResource, c.ns, integrationSource), &v1alpha1.IntegrationSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.IntegrationSource), err +} + +// Update takes the representation of a integrationSource and updates it. Returns the server's representation of the integrationSource, and an error, if there is any. +func (c *FakeIntegrationSources) Update(ctx context.Context, integrationSource *v1alpha1.IntegrationSource, opts v1.UpdateOptions) (result *v1alpha1.IntegrationSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(integrationsourcesResource, c.ns, integrationSource), &v1alpha1.IntegrationSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.IntegrationSource), 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 *FakeIntegrationSources) UpdateStatus(ctx context.Context, integrationSource *v1alpha1.IntegrationSource, opts v1.UpdateOptions) (*v1alpha1.IntegrationSource, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(integrationsourcesResource, "status", c.ns, integrationSource), &v1alpha1.IntegrationSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.IntegrationSource), err +} + +// Delete takes name of the integrationSource and deletes it. Returns an error if one occurs. +func (c *FakeIntegrationSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(integrationsourcesResource, c.ns, name, opts), &v1alpha1.IntegrationSource{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeIntegrationSources) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(integrationsourcesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.IntegrationSourceList{}) + return err +} + +// Patch applies the patch and returns the patched integrationSource. +func (c *FakeIntegrationSources) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.IntegrationSource, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(integrationsourcesResource, c.ns, name, pt, data, subresources...), &v1alpha1.IntegrationSource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.IntegrationSource), 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..29164f5c7a6 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha1/fake/fake_sources_client.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 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) IntegrationSources(namespace string) v1alpha1.IntegrationSourceInterface { + return &FakeIntegrationSources{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..93a464c5232 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha1/generated_expansion.go @@ -0,0 +1,21 @@ +/* +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 IntegrationSourceExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/sources/v1alpha1/integrationsource.go b/pkg/client/clientset/versioned/typed/sources/v1alpha1/integrationsource.go new file mode 100644 index 00000000000..aa695a3b878 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha1/integrationsource.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" +) + +// IntegrationSourcesGetter has a method to return a IntegrationSourceInterface. +// A group's client should implement this interface. +type IntegrationSourcesGetter interface { + IntegrationSources(namespace string) IntegrationSourceInterface +} + +// IntegrationSourceInterface has methods to work with IntegrationSource resources. +type IntegrationSourceInterface interface { + Create(ctx context.Context, integrationSource *v1alpha1.IntegrationSource, opts v1.CreateOptions) (*v1alpha1.IntegrationSource, error) + Update(ctx context.Context, integrationSource *v1alpha1.IntegrationSource, opts v1.UpdateOptions) (*v1alpha1.IntegrationSource, error) + UpdateStatus(ctx context.Context, integrationSource *v1alpha1.IntegrationSource, opts v1.UpdateOptions) (*v1alpha1.IntegrationSource, 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.IntegrationSource, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.IntegrationSourceList, 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.IntegrationSource, err error) + IntegrationSourceExpansion +} + +// integrationSources implements IntegrationSourceInterface +type integrationSources struct { + client rest.Interface + ns string +} + +// newIntegrationSources returns a IntegrationSources +func newIntegrationSources(c *SourcesV1alpha1Client, namespace string) *integrationSources { + return &integrationSources{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the integrationSource, and returns the corresponding integrationSource object, and an error if there is any. +func (c *integrationSources) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.IntegrationSource, err error) { + result = &v1alpha1.IntegrationSource{} + err = c.client.Get(). + Namespace(c.ns). + Resource("integrationsources"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of IntegrationSources that match those selectors. +func (c *integrationSources) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.IntegrationSourceList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.IntegrationSourceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("integrationsources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested integrationSources. +func (c *integrationSources) 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("integrationsources"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a integrationSource and creates it. Returns the server's representation of the integrationSource, and an error, if there is any. +func (c *integrationSources) Create(ctx context.Context, integrationSource *v1alpha1.IntegrationSource, opts v1.CreateOptions) (result *v1alpha1.IntegrationSource, err error) { + result = &v1alpha1.IntegrationSource{} + err = c.client.Post(). + Namespace(c.ns). + Resource("integrationsources"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(integrationSource). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a integrationSource and updates it. Returns the server's representation of the integrationSource, and an error, if there is any. +func (c *integrationSources) Update(ctx context.Context, integrationSource *v1alpha1.IntegrationSource, opts v1.UpdateOptions) (result *v1alpha1.IntegrationSource, err error) { + result = &v1alpha1.IntegrationSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("integrationsources"). + Name(integrationSource.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(integrationSource). + 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 *integrationSources) UpdateStatus(ctx context.Context, integrationSource *v1alpha1.IntegrationSource, opts v1.UpdateOptions) (result *v1alpha1.IntegrationSource, err error) { + result = &v1alpha1.IntegrationSource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("integrationsources"). + Name(integrationSource.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(integrationSource). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the integrationSource and deletes it. Returns an error if one occurs. +func (c *integrationSources) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("integrationsources"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *integrationSources) 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("integrationsources"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched integrationSource. +func (c *integrationSources) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.IntegrationSource, err error) { + result = &v1alpha1.IntegrationSource{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("integrationsources"). + 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..838e0042784 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sources/v1alpha1/sources_client.go @@ -0,0 +1,107 @@ +/* +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 ( + "net/http" + + 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 + IntegrationSourcesGetter +} + +// SourcesV1alpha1Client is used to interact with features provided by the sources.knative.dev group. +type SourcesV1alpha1Client struct { + restClient rest.Interface +} + +func (c *SourcesV1alpha1Client) IntegrationSources(namespace string) IntegrationSourceInterface { + return newIntegrationSources(c, namespace) +} + +// NewForConfig creates a new SourcesV1alpha1Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*SourcesV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new SourcesV1alpha1Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*SourcesV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientForConfigAndClient(&config, h) + 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/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 41c490e06ce..cf7e4cf4896 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -32,6 +32,7 @@ import ( messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" sinksv1alpha1 "knative.dev/eventing/pkg/apis/sinks/v1alpha1" sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + sourcesv1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2" ) @@ -111,6 +112,10 @@ 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 sourcesv1alpha1.SchemeGroupVersion.WithResource("integrationsources"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1alpha1().IntegrationSources().Informer()}, nil + // Group=sources.knative.dev, Version=v1beta2 case sourcesv1beta2.SchemeGroupVersion.WithResource("pingsources"): return &genericInformer{resource: resource.GroupResource(), informer: f.Sources().V1beta2().PingSources().Informer()}, nil diff --git a/pkg/client/informers/externalversions/sources/interface.go b/pkg/client/informers/externalversions/sources/interface.go index c8b6385943e..20ad73586e7 100644 --- a/pkg/client/informers/externalversions/sources/interface.go +++ b/pkg/client/informers/externalversions/sources/interface.go @@ -21,6 +21,7 @@ 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" v1beta2 "knative.dev/eventing/pkg/client/informers/externalversions/sources/v1beta2" ) @@ -28,6 +29,8 @@ import ( type Interface interface { // V1 provides access to shared informers for resources in V1. V1() v1.Interface + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() v1alpha1.Interface // V1beta2 provides access to shared informers for resources in V1beta2. V1beta2() v1beta2.Interface } @@ -48,6 +51,11 @@ func (g *group) V1() v1.Interface { return v1.New(g.factory, g.namespace, g.tweakListOptions) } +// V1alpha1 returns a new v1alpha1.Interface. +func (g *group) V1alpha1() v1alpha1.Interface { + return v1alpha1.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/integrationsource.go b/pkg/client/informers/externalversions/sources/v1alpha1/integrationsource.go new file mode 100644 index 00000000000..34e3a1f1b34 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1alpha1/integrationsource.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" +) + +// IntegrationSourceInformer provides access to a shared informer and lister for +// IntegrationSources. +type IntegrationSourceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.IntegrationSourceLister +} + +type integrationSourceInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewIntegrationSourceInformer constructs a new informer for IntegrationSource 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 NewIntegrationSourceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredIntegrationSourceInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredIntegrationSourceInformer constructs a new informer for IntegrationSource 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 NewFilteredIntegrationSourceInformer(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().IntegrationSources(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SourcesV1alpha1().IntegrationSources(namespace).Watch(context.TODO(), options) + }, + }, + &sourcesv1alpha1.IntegrationSource{}, + resyncPeriod, + indexers, + ) +} + +func (f *integrationSourceInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredIntegrationSourceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *integrationSourceInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&sourcesv1alpha1.IntegrationSource{}, f.defaultInformer) +} + +func (f *integrationSourceInformer) Lister() v1alpha1.IntegrationSourceLister { + return v1alpha1.NewIntegrationSourceLister(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..6dce960d202 --- /dev/null +++ b/pkg/client/informers/externalversions/sources/v1alpha1/interface.go @@ -0,0 +1,45 @@ +/* +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 { + // IntegrationSources returns a IntegrationSourceInformer. + IntegrationSources() IntegrationSourceInformer +} + +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} +} + +// IntegrationSources returns a IntegrationSourceInformer. +func (v *version) IntegrationSources() IntegrationSourceInformer { + return &integrationSourceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/injection/informers/sources/v1alpha1/integrationsource/fake/fake.go b/pkg/client/injection/informers/sources/v1alpha1/integrationsource/fake/fake.go new file mode 100644 index 00000000000..6a0fed73290 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha1/integrationsource/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" + integrationsource "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha1/integrationsource" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = integrationsource.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Sources().V1alpha1().IntegrationSources() + return context.WithValue(ctx, integrationsource.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/sources/v1alpha1/integrationsource/filtered/fake/fake.go b/pkg/client/injection/informers/sources/v1alpha1/integrationsource/filtered/fake/fake.go new file mode 100644 index 00000000000..8977c76fbf0 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha1/integrationsource/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/integrationsource/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().IntegrationSources() + 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/integrationsource/filtered/integrationsource.go b/pkg/client/injection/informers/sources/v1alpha1/integrationsource/filtered/integrationsource.go new file mode 100644 index 00000000000..d21cf4ab247 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha1/integrationsource/filtered/integrationsource.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().IntegrationSources() + 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.IntegrationSourceInformer { + 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.IntegrationSourceInformer with selector %s from context.", selector) + } + return untyped.(v1alpha1.IntegrationSourceInformer) +} diff --git a/pkg/client/injection/informers/sources/v1alpha1/integrationsource/integrationsource.go b/pkg/client/injection/informers/sources/v1alpha1/integrationsource/integrationsource.go new file mode 100644 index 00000000000..221f33b8e65 --- /dev/null +++ b/pkg/client/injection/informers/sources/v1alpha1/integrationsource/integrationsource.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 integrationsource + +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().IntegrationSources() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1alpha1.IntegrationSourceInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/eventing/pkg/client/informers/externalversions/sources/v1alpha1.IntegrationSourceInformer from context.") + } + return untyped.(v1alpha1.IntegrationSourceInformer) +} diff --git a/pkg/client/injection/reconciler/sources/v1alpha1/integrationsource/controller.go b/pkg/client/injection/reconciler/sources/v1alpha1/integrationsource/controller.go new file mode 100644 index 00000000000..1338a059205 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1alpha1/integrationsource/controller.go @@ -0,0 +1,170 @@ +/* +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 integrationsource + +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" + integrationsource "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha1/integrationsource" + 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 = "integrationsource-controller" + defaultFinalizerName = "integrationsources.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.ControllerOptions 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)) + } + + integrationsourceInformer := integrationsource.Get(ctx) + + lister := integrationsourceInformer.Lister() + + var promoteFilterFunc func(obj interface{}) bool + var promoteFunc = func(bkt reconciler.Bucket) {} + + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + + // Signal promotion event + promoteFunc(bkt) + + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + if promoteFilterFunc != nil { + if ok := promoteFilterFunc(elt); !ok { + continue + } + } + 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.IntegrationSource"), + ) + + impl := controller.NewContext(ctx, rec, controller.ControllerOptions{WorkQueueName: ctrTypeName, Logger: logger}) + 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 + } + if opts.PromoteFilterFunc != nil { + promoteFilterFunc = opts.PromoteFilterFunc + } + if opts.PromoteFunc != nil { + promoteFunc = opts.PromoteFunc + } + } + + 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/v1alpha1/integrationsource/reconciler.go b/pkg/client/injection/reconciler/sources/v1alpha1/integrationsource/reconciler.go new file mode 100644 index 00000000000..0de10c6cf8c --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1alpha1/integrationsource/reconciler.go @@ -0,0 +1,440 @@ +/* +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 integrationsource + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + zap "go.uber.org/zap" + "go.uber.org/zap/zapcore" + 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" + v1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" + versioned "knative.dev/eventing/pkg/client/clientset/versioned" + sourcesv1alpha1 "knative.dev/eventing/pkg/client/listers/sources/v1alpha1" + 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 v1alpha1.IntegrationSource. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1alpha1.IntegrationSource. 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 *v1alpha1.IntegrationSource) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1alpha1.IntegrationSource. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1alpha1.IntegrationSource. 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 *v1alpha1.IntegrationSource) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1alpha1.IntegrationSource if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1alpha1.IntegrationSource. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1alpha1.IntegrationSource) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1alpha1.IntegrationSource) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1alpha1.IntegrationSource 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 sourcesv1alpha1.IntegrationSourceLister + + // 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 sourcesv1alpha1.IntegrationSourceLister, 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) + } + + 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.IntegrationSources(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: + // 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, logger, 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 + } + + if controller.IsSkipKey(reconcileEvent) { + // This is a wrapped error, don't emit an event. + } else if ok, _ := controller.IsRequeueKey(reconcileEvent); ok { + // This is a wrapped error, don't emit an event. + } else { + 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, logger *zap.SugaredLogger, existing *v1alpha1.IntegrationSource, desired *v1alpha1.IntegrationSource) 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.SourcesV1alpha1().IntegrationSources(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 logger.Desugar().Core().Enabled(zapcore.DebugLevel) { + if diff, err := kmp.SafeDiff(existing.Status, desired.Status); err == nil && diff != "" { + logger.Debug("Updating status with: ", diff) + } + } + + existing.Status = desired.Status + + updater := r.Client.SourcesV1alpha1().IntegrationSources(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 *v1alpha1.IntegrationSource, desiredFinalizers sets.Set[string]) (*v1alpha1.IntegrationSource, error) { + // Don't modify the informers copy. + existing := resource.DeepCopy() + + var finalizers []string + + // If there's nothing to update, just return. + existingFinalizers := sets.New[string](existing.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 = sets.List(existingFinalizers) + } + + 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.SourcesV1alpha1().IntegrationSources(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 *v1alpha1.IntegrationSource) (*v1alpha1.IntegrationSource, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.New[string](resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource, finalizers) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1alpha1.IntegrationSource, reconcileEvent reconciler.Event) (*v1alpha1.IntegrationSource, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + if resource.GetDeletionTimestamp().IsZero() { + return resource, nil + } + + finalizers := sets.New[string](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) + } + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource, finalizers) +} diff --git a/pkg/client/injection/reconciler/sources/v1alpha1/integrationsource/state.go b/pkg/client/injection/reconciler/sources/v1alpha1/integrationsource/state.go new file mode 100644 index 00000000000..501667a6e70 --- /dev/null +++ b/pkg/client/injection/reconciler/sources/v1alpha1/integrationsource/state.go @@ -0,0 +1,97 @@ +/* +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 integrationsource + +import ( + fmt "fmt" + + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + v1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" + 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 + // 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) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + 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 { + // If we are not the leader, and we don't implement the ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1alpha1.IntegrationSource) (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 + } + return "unknown", 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..16600e2ab8b --- /dev/null +++ b/pkg/client/listers/sources/v1alpha1/expansion_generated.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 lister-gen. DO NOT EDIT. + +package v1alpha1 + +// IntegrationSourceListerExpansion allows custom methods to be added to +// IntegrationSourceLister. +type IntegrationSourceListerExpansion interface{} + +// IntegrationSourceNamespaceListerExpansion allows custom methods to be added to +// IntegrationSourceNamespaceLister. +type IntegrationSourceNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/sources/v1alpha1/integrationsource.go b/pkg/client/listers/sources/v1alpha1/integrationsource.go new file mode 100644 index 00000000000..a9d3d1a5292 --- /dev/null +++ b/pkg/client/listers/sources/v1alpha1/integrationsource.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" +) + +// IntegrationSourceLister helps list IntegrationSources. +// All objects returned here must be treated as read-only. +type IntegrationSourceLister interface { + // List lists all IntegrationSources in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.IntegrationSource, err error) + // IntegrationSources returns an object that can list and get IntegrationSources. + IntegrationSources(namespace string) IntegrationSourceNamespaceLister + IntegrationSourceListerExpansion +} + +// integrationSourceLister implements the IntegrationSourceLister interface. +type integrationSourceLister struct { + indexer cache.Indexer +} + +// NewIntegrationSourceLister returns a new IntegrationSourceLister. +func NewIntegrationSourceLister(indexer cache.Indexer) IntegrationSourceLister { + return &integrationSourceLister{indexer: indexer} +} + +// List lists all IntegrationSources in the indexer. +func (s *integrationSourceLister) List(selector labels.Selector) (ret []*v1alpha1.IntegrationSource, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.IntegrationSource)) + }) + return ret, err +} + +// IntegrationSources returns an object that can list and get IntegrationSources. +func (s *integrationSourceLister) IntegrationSources(namespace string) IntegrationSourceNamespaceLister { + return integrationSourceNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// IntegrationSourceNamespaceLister helps list and get IntegrationSources. +// All objects returned here must be treated as read-only. +type IntegrationSourceNamespaceLister interface { + // List lists all IntegrationSources in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.IntegrationSource, err error) + // Get retrieves the IntegrationSource from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.IntegrationSource, error) + IntegrationSourceNamespaceListerExpansion +} + +// integrationSourceNamespaceLister implements the IntegrationSourceNamespaceLister +// interface. +type integrationSourceNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all IntegrationSources in the indexer for a given namespace. +func (s integrationSourceNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.IntegrationSource, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.IntegrationSource)) + }) + return ret, err +} + +// Get retrieves the IntegrationSource from the indexer for a given namespace and name. +func (s integrationSourceNamespaceLister) Get(name string) (*v1alpha1.IntegrationSource, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("integrationsource"), name) + } + return obj.(*v1alpha1.IntegrationSource), nil +} diff --git a/pkg/reconciler/containersource/containersource_test.go b/pkg/reconciler/containersource/containersource_test.go index 6c588a59b87..b5b223c1344 100644 --- a/pkg/reconciler/containersource/containersource_test.go +++ b/pkg/reconciler/containersource/containersource_test.go @@ -216,7 +216,7 @@ func TestAllCases(t *testing.T) { }, Key: testNS + "/" + sourceName, WantEvents: []string{ - Eventf(corev1.EventTypeNormal, sourceReconciled, `ContainerSource reconciled: "%s/%s"`, testNS, sourceName), + Eventf(corev1.EventTypeNormal, "ContainerSourceReconciled", `ContainerSource reconciled: "%s/%s"`, testNS, sourceName), }, WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ Object: NewContainerSource(sourceName, testNS, diff --git a/pkg/reconciler/integrationsource/controller.go b/pkg/reconciler/integrationsource/controller.go new file mode 100644 index 00000000000..a84bec85815 --- /dev/null +++ b/pkg/reconciler/integrationsource/controller.go @@ -0,0 +1,111 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 integrationsource + +import ( + "context" + + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/cache" + containersourceinformer "knative.dev/eventing/pkg/client/injection/informers/sources/v1/containersource" + + configmapinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/configmap/filtered" + "knative.dev/pkg/kmeta" + "knative.dev/pkg/system" + + kubeclient "knative.dev/pkg/client/injection/kube/client" + "knative.dev/pkg/configmap" + "knative.dev/pkg/controller" + "knative.dev/pkg/logging" + + "knative.dev/eventing/pkg/apis/feature" + "knative.dev/eventing/pkg/apis/sources/v1alpha1" + eventingclient "knative.dev/eventing/pkg/client/injection/client" + integrationsourceinformer "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha1/integrationsource" + v1integrationsource "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1alpha1/integrationsource" + "knative.dev/eventing/pkg/eventingtls" +) + +// NewController creates a Reconciler for IntegrationSource and returns the result of NewImpl. +func NewController( + ctx context.Context, + cmw configmap.Watcher, +) *controller.Impl { + + kubeClient := kubeclient.Get(ctx) + eventingClient := eventingclient.Get(ctx) + integrationsourceInformer := integrationsourceinformer.Get(ctx) + containerSourceInformer := containersourceinformer.Get(ctx) + + trustBundleConfigMapInformer := configmapinformer.Get(ctx, eventingtls.TrustBundleLabelSelector) + + var globalResync func(obj interface{}) + featureStore := feature.NewStore(logging.FromContext(ctx).Named("feature-config-store"), + func(name string, value interface{}) { + if globalResync != nil { + globalResync(nil) + } + }) + featureStore.WatchConfigs(cmw) + + r := &Reconciler{ + kubeClientSet: kubeClient, + eventingClientSet: eventingClient, + containerSourceLister: containerSourceInformer.Lister(), + integrationSourceLister: integrationsourceInformer.Lister(), + } + + impl := v1integrationsource.NewImpl(ctx, r, func(impl *controller.Impl) controller.Options { + return controller.Options{ConfigStore: featureStore} + }) + + globalResync = func(_ interface{}) { + impl.GlobalResync(integrationsourceInformer.Informer()) + } + + integrationsourceInformer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue)) + + containerSourceInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ + FilterFunc: controller.FilterController(&v1alpha1.IntegrationSource{}), + Handler: controller.HandleAll(impl.EnqueueControllerOf), + }) + + trustBundleConfigMapInformer.Informer().AddEventHandler(controller.HandleAll(func(i interface{}) { + obj, err := kmeta.DeletionHandlingAccessor(i) + if err != nil { + return + } + if obj.GetNamespace() == system.Namespace() { + globalResync(i) + return + } + + sources, err := integrationsourceInformer.Lister().IntegrationSources(obj.GetNamespace()).List(labels.Everything()) + if err != nil { + return + } + for _, src := range sources { + impl.EnqueueKey(types.NamespacedName{ + Namespace: src.Namespace, + Name: src.Name, + }) + } + })) + + return impl +} diff --git a/pkg/reconciler/integrationsource/controller_test.go b/pkg/reconciler/integrationsource/controller_test.go new file mode 100644 index 00000000000..e4c21f26565 --- /dev/null +++ b/pkg/reconciler/integrationsource/controller_test.go @@ -0,0 +1,60 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 integrationsource + +import ( + "context" + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + filteredFactory "knative.dev/pkg/client/injection/kube/informers/factory/filtered" + "knative.dev/pkg/configmap" + . "knative.dev/pkg/reconciler/testing" + + // Fake injection informers + "knative.dev/eventing/pkg/apis/feature" + _ "knative.dev/eventing/pkg/client/injection/informers/sources/v1/containersource/fake" + _ "knative.dev/eventing/pkg/client/injection/informers/sources/v1alpha1/integrationsource/fake" + "knative.dev/eventing/pkg/eventingtls" + _ "knative.dev/pkg/client/injection/kube/informers/apps/v1/deployment/fake" + _ "knative.dev/pkg/client/injection/kube/informers/core/v1/configmap/filtered/fake" + _ "knative.dev/pkg/client/injection/kube/informers/core/v1/serviceaccount/filtered/fake" + _ "knative.dev/pkg/client/injection/kube/informers/factory/filtered/fake" + _ "knative.dev/pkg/injection/clients/dynamicclient/fake" +) + +func TestNew(t *testing.T) { + ctx, _ := SetupFakeContext(t, SetUpInformerSelector) + + c := NewController(ctx, configmap.NewStaticWatcher( + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: feature.FlagsConfigName, + }, + }, + )) + + if c == nil { + t.Fatal("Expected NewController to return a non-nil value") + } +} + +func SetUpInformerSelector(ctx context.Context) context.Context { + ctx = filteredFactory.WithSelectors(ctx, eventingtls.TrustBundleLabelSelector) + return ctx +} diff --git a/pkg/reconciler/integrationsource/integrationsource.go b/pkg/reconciler/integrationsource/integrationsource.go new file mode 100644 index 00000000000..1022b66172b --- /dev/null +++ b/pkg/reconciler/integrationsource/integrationsource.go @@ -0,0 +1,92 @@ +package integrationsource + +import ( + "context" + "fmt" + + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + v1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/eventing/pkg/apis/sources/v1alpha1" + clientset "knative.dev/eventing/pkg/client/clientset/versioned" + "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1alpha1/integrationsource" + v1listers "knative.dev/eventing/pkg/client/listers/sources/v1" + listers "knative.dev/eventing/pkg/client/listers/sources/v1alpha1" + "knative.dev/eventing/pkg/reconciler/integrationsource/resources" + "knative.dev/pkg/controller" + "knative.dev/pkg/logging" + pkgreconciler "knative.dev/pkg/reconciler" +) + +const ( + // Name of the corev1.Events emitted from the reconciliation process + sourceReconciled = "IntegrationSourceReconciled" + containerSourceCreated = "ContainerSourceCreated" + containerSourceUpdated = "ContainerSourceUpdated" +) + +// Reconciler implements controller.Reconciler for ContainerSource resources. +type Reconciler struct { + kubeClientSet kubernetes.Interface + eventingClientSet clientset.Interface + + containerSourceLister v1listers.ContainerSourceLister + integrationSourceLister listers.IntegrationSourceLister +} + +// Check that our Reconciler implements Interface +var _ integrationsource.Interface = (*Reconciler)(nil) + +// newReconciledNormal makes a new reconciler event with event type Normal, and +// reason ContainerSourceReconciled. +func newReconciledNormal(namespace, name string) pkgreconciler.Event { + return pkgreconciler.NewEvent(corev1.EventTypeNormal, sourceReconciled, "IntegrationSource reconciled: \"%s/%s\"", namespace, name) +} + +func (r *Reconciler) ReconcileKind(ctx context.Context, source *v1alpha1.IntegrationSource) pkgreconciler.Event { + + _, err := r.reconcileContainerSource(ctx, source) + if err != nil { + logging.FromContext(ctx).Errorw("Error reconciling ContainerSource", zap.Error(err)) + return err + } + + return newReconciledNormal(source.Namespace, source.Name) +} + +func (r *Reconciler) reconcileContainerSource(ctx context.Context, source *v1alpha1.IntegrationSource) (*v1.ContainerSource, error) { + expected := resources.NewContainerSource(source) + + cs, err := r.containerSourceLister.ContainerSources(source.Namespace).Get(expected.Name) + if apierrors.IsNotFound(err) { + cs, err = r.eventingClientSet.SourcesV1().ContainerSources(source.Namespace).Create(ctx, expected, metav1.CreateOptions{}) + if err != nil { + return nil, fmt.Errorf("creating new ContainerSource: %v", err) + } + controller.GetEventRecorder(ctx).Eventf(source, corev1.EventTypeNormal, containerSourceCreated, "ContainerSource created %q", cs.Name) + } else if err != nil { + return nil, fmt.Errorf("getting ContainerSource: %v", err) + } else if !metav1.IsControlledBy(cs, source) { + return nil, fmt.Errorf("ContainerSource %q is not owned by IntegrationSource %q", cs.Name, source.Name) + } else if r.containerSourceSpecChanged(&cs.Spec, &expected.Spec) { + cs.Spec = expected.Spec + cs, err = r.eventingClientSet.SourcesV1().ContainerSources(source.Namespace).Update(ctx, cs, metav1.UpdateOptions{}) + if err != nil { + return nil, fmt.Errorf("updating ContainerSource: %v", err) + } + controller.GetEventRecorder(ctx).Eventf(source, corev1.EventTypeNormal, containerSourceUpdated, "ContainerSource updated %q", cs.Name) + } else { + logging.FromContext(ctx).Debugw("Reusing existing ContainerSource", zap.Any("ContainerSource", cs.ObjectMeta)) + } + + source.Status.PropagateContainerSourceStatus(&cs.Status) + return cs, nil +} + +func (r *Reconciler) containerSourceSpecChanged(have *v1.ContainerSourceSpec, want *v1.ContainerSourceSpec) bool { + return !equality.Semantic.DeepDerivative(want, have) +} diff --git a/pkg/reconciler/integrationsource/integrationsource_test.go b/pkg/reconciler/integrationsource/integrationsource_test.go new file mode 100644 index 00000000000..92dc884bfcf --- /dev/null +++ b/pkg/reconciler/integrationsource/integrationsource_test.go @@ -0,0 +1,258 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 integrationsource + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + clientgotesting "k8s.io/client-go/testing" + sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + sourcesv1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" + fakeeventingclient "knative.dev/eventing/pkg/client/injection/client/fake" + "knative.dev/eventing/pkg/client/injection/reconciler/sources/v1alpha1/integrationsource" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + fakekubeclient "knative.dev/pkg/client/injection/kube/client/fake" + "knative.dev/pkg/kmeta" + "knative.dev/pkg/logging" + + "context" + + . "knative.dev/eventing/pkg/reconciler/testing/v1" + . "knative.dev/eventing/pkg/reconciler/testing/v1alpha1" + "knative.dev/pkg/client/injection/ducks/duck/v1/addressable" + "knative.dev/pkg/configmap" + "knative.dev/pkg/controller" + logtesting "knative.dev/pkg/logging/testing" + . "knative.dev/pkg/reconciler/testing" + + "testing" +) + +const ( + sourceName = "test-integration-source" + sourceUID = "1234-5678-90" + testNS = "testnamespace" + sinkName = "testsink" + generation = 1 +) + +var ( + conditionTrue = corev1.ConditionTrue + + containerSourceName = fmt.Sprintf("%s-containersource", sourceName) + + sinkDest = duckv1.Destination{ + Ref: &duckv1.KReference{ + Name: sinkName, + Kind: "Channel", + APIVersion: "messaging.knative.dev/v1", + }, + } +) + +func TestReconcile(t *testing.T) { + + table := TableTest{ + { + Name: "bad work queue key", + Key: "too/many/parts", + }, + { + Name: "key not found", + // Make sure Reconcile handles good keys that don't exist. + Key: "foo/not-found", + }, { + Name: "error creating containersource", + Objects: []runtime.Object{ + NewIntegrationSource(sourceName, testNS, + WithIntegrationSourceUID(sourceUID), + WithIntegrationSourceSpec(makeIntegrationSourceSpec(sinkDest)), + ), + }, + Key: testNS + "/" + sourceName, + WithReactors: []clientgotesting.ReactionFunc{ + InduceFailure("create", "containersources"), + }, + WantEvents: []string{ + Eventf(corev1.EventTypeWarning, "InternalError", "creating new ContainerSource: inducing failure for %s %s", "create", "containersources"), + }, + WantErr: true, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ + Object: NewIntegrationSource(sourceName, testNS, + WithIntegrationSourceUID(sourceUID), + WithIntegrationSourceSpec(makeIntegrationSourceSpec(sinkDest)), + WithInitIntegrationSourceConditions, + ), + }}, + WantCreates: []runtime.Object{ + makeContainerSource(NewIntegrationSource(sourceName, testNS, + WithIntegrationSourceUID(sourceUID), + WithIntegrationSourceSpec(makeIntegrationSourceSpec(sinkDest))), + nil), + }, + }, { + Name: "successfully reconciled and not ready", + Objects: []runtime.Object{ + NewIntegrationSource(sourceName, testNS, + WithIntegrationSourceUID(sourceUID), + WithIntegrationSourceSpec(makeIntegrationSourceSpec(sinkDest)), + ), + }, + Key: testNS + "/" + sourceName, + WantEvents: []string{ + Eventf(corev1.EventTypeNormal, containerSourceCreated, "ContainerSource created %q", containerSourceName), + Eventf(corev1.EventTypeNormal, sourceReconciled, `IntegrationSource reconciled: "%s/%s"`, testNS, sourceName), + }, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ + Object: NewIntegrationSource(sourceName, testNS, + WithIntegrationSourceUID(sourceUID), + WithIntegrationSourceSpec(makeIntegrationSourceSpec(sinkDest)), + WithInitIntegrationSourceConditions, + ), + }}, + WantCreates: []runtime.Object{ + makeContainerSource(NewIntegrationSource(sourceName, testNS, + WithIntegrationSourceUID(sourceUID), + WithIntegrationSourceSpec(makeIntegrationSourceSpec(sinkDest))), + nil), + }, + }, { + Name: "successfully reconciled and ready", + Objects: []runtime.Object{ + NewIntegrationSource(sourceName, testNS, + WithIntegrationSourceUID(sourceUID), + WithIntegrationSourceSpec(makeIntegrationSourceSpec(sinkDest)), + ), + makeContainerSource(NewIntegrationSource(sourceName, testNS, + WithIntegrationSourceUID(sourceUID), + WithIntegrationSourceSpec(makeIntegrationSourceSpec(sinkDest)), + ), &conditionTrue), + }, + Key: testNS + "/" + sourceName, + WantEvents: []string{ + Eventf(corev1.EventTypeNormal, sourceReconciled, `IntegrationSource reconciled: "%s/%s"`, testNS, sourceName), + }, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{{ + Object: NewIntegrationSource(sourceName, testNS, + WithIntegrationSourceUID(sourceUID), + WithIntegrationSourceSpec(makeIntegrationSourceSpec(sinkDest)), + WithInitIntegrationSourceConditions, + WithIntegrationSourceStatusObservedGeneration(generation), + WithIntegrationSourcePropagateContainerSourceStatus(makeContainerSourceStatus(&conditionTrue)), + ), + }}, + }} + + logger := logtesting.TestLogger(t) + + table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler { + ctx = addressable.WithDuck(ctx) + r := &Reconciler{ + kubeClientSet: fakekubeclient.Get(ctx), + eventingClientSet: fakeeventingclient.Get(ctx), + containerSourceLister: listers.GetContainerSourceLister(), + integrationSourceLister: listers.GetIntegrationSourceLister(), + } + + return integrationsource.NewReconciler(ctx, logging.FromContext(ctx), fakeeventingclient.Get(ctx), listers.GetIntegrationSourceLister(), controller.GetEventRecorder(ctx), r) + }, + true, + logger, + )) +} + +func makeContainerSource(source *sourcesv1alpha1.IntegrationSource, ready *corev1.ConditionStatus) runtime.Object { + cs := &sourcesv1.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + *kmeta.NewControllerRef(source), + }, + Name: containerSourceName, + Namespace: source.Namespace, + }, + Spec: sourcesv1.ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "source", + Image: "gcr.io/knative-nightly/timer-source:latest", + ImagePullPolicy: corev1.PullIfNotPresent, + Env: []corev1.EnvVar{ + { + Name: "CAMEL_KNATIVE_CLIENT_SSL_ENABLED", + Value: "true", + }, + { + Name: "CAMEL_KNATIVE_CLIENT_SSL_CERT_PATH", + Value: "/knative-custom-certs/knative-eventing-bundle.pem", + }, + { + Name: "CAMEL_KAMELET_TIMER_SOURCE_PERIOD", + Value: "1000", + }, + { + Name: "CAMEL_KAMELET_TIMER_SOURCE_MESSAGE", + Value: "Hallo", + }, + { + Name: "CAMEL_KAMELET_TIMER_SOURCE_REPEATCOUNT", + Value: "0", + }, + }, + }, + }, + }, + }, + SourceSpec: source.Spec.SourceSpec, + }, + } + + if ready != nil { + cs.Status = *makeContainerSourceStatus(ready) + } + return cs +} + +func makeContainerSourceStatus(ready *corev1.ConditionStatus) *sourcesv1.ContainerSourceStatus { + return &sourcesv1.ContainerSourceStatus{ + SourceStatus: duckv1.SourceStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: apis.ConditionReady, + Status: *ready, + }}, + }, + }, + } +} + +func makeIntegrationSourceSpec(sink duckv1.Destination) sourcesv1alpha1.IntegrationSourceSpec { + return sourcesv1alpha1.IntegrationSourceSpec{ + Timer: &sourcesv1alpha1.Timer{ + Period: 1000, + Message: "Hallo", + }, + SourceSpec: duckv1.SourceSpec{ + Sink: sink, + }, + } +} diff --git a/pkg/reconciler/integrationsource/resources/containersource.go b/pkg/reconciler/integrationsource/resources/containersource.go new file mode 100644 index 00000000000..85e4806c6f5 --- /dev/null +++ b/pkg/reconciler/integrationsource/resources/containersource.go @@ -0,0 +1,231 @@ +/* + Copyright 2024 The Knative Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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 resources + +import ( + "fmt" + "reflect" + "strconv" + "strings" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/eventing/pkg/apis/sources/v1alpha1" + "knative.dev/pkg/kmeta" +) + +func NewContainerSource(source *v1alpha1.IntegrationSource) *sourcesv1.ContainerSource { + return &sourcesv1.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + *kmeta.NewControllerRef(source), + }, + Name: ContainerSourceName(source), + Namespace: source.Namespace, + }, + Spec: sourcesv1.ContainerSourceSpec{ + + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "source", + Image: selectImage(source), + ImagePullPolicy: corev1.PullIfNotPresent, + Env: makeEnv(source), + }, + }, + }, + }, + SourceSpec: source.Spec.SourceSpec, + }, + } +} + +func generateEnvVarsFromStruct(prefix string, s interface{}) []corev1.EnvVar { + var envVars = makeSSLEnvVar() + + // Use reflection to inspect the struct fields + v := reflect.ValueOf(s) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + t := v.Type() + + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + fieldType := t.Field(i) + + // Skip unexported fields + if !field.CanInterface() { + continue + } + + // Handle embedded/anonymous structs recursively + if fieldType.Anonymous && field.Kind() == reflect.Struct { + // Recursively handle embedded structs with the same prefix + envVars = append(envVars, generateEnvVarsFromStruct(prefix, field.Interface())...) + continue + } + + // Extract the JSON tag or fall back to the Go field name + jsonTag := fieldType.Tag.Get("json") + tagName := strings.Split(jsonTag, ",")[0] + + // fallback to Go field name if no JSON tag + if tagName == "" || tagName == "-" { + tagName = fieldType.Name + } + + envVarName := fmt.Sprintf("%s_%s", prefix, strings.ToUpper(tagName)) + + if field.Kind() == reflect.Ptr { + if field.IsNil() { + continue + } + field = field.Elem() + } + + var value string + switch field.Kind() { + case reflect.Int, reflect.Int32, reflect.Int64: + value = strconv.FormatInt(field.Int(), 10) + case reflect.Bool: + value = strconv.FormatBool(field.Bool()) + case reflect.String: + value = field.String() + default: + // Skip unsupported types + continue + } + + // Skip zero/empty values + if value == "" { + continue + } + + envVars = append(envVars, corev1.EnvVar{ + Name: envVarName, + Value: value, + }) + } + + return envVars +} + +// Function to create environment variables for Timer or AWS configurations dynamically +func makeEnv(source *v1alpha1.IntegrationSource) []corev1.EnvVar { + var envVars []corev1.EnvVar + + // Timer environment variables + if source.Spec.Timer != nil { + envVars = append(envVars, generateEnvVarsFromStruct("CAMEL_KAMELET_TIMER_SOURCE", *source.Spec.Timer)...) + return envVars + } + + // Handle secret name only if AWS is configured + var secretName string + if source.Spec.Aws != nil && source.Spec.Aws.Auth != nil && source.Spec.Aws.Auth.Secret != nil && source.Spec.Aws.Auth.Secret.Ref != nil { + secretName = source.Spec.Aws.Auth.Secret.Ref.Name + } + + // AWS S3 environment variables + if source.Spec.Aws != nil && source.Spec.Aws.S3 != nil { + envVars = append(envVars, generateEnvVarsFromStruct("CAMEL_KAMELET_AWS_S3_SOURCE", *source.Spec.Aws.S3)...) + if secretName != "" { + envVars = append(envVars, []corev1.EnvVar{ + makeSecretEnvVar("CAMEL_KAMELET_AWS_S3_SOURCE_ACCESSKEY", "aws.s3.accessKey", secretName), + makeSecretEnvVar("CAMEL_KAMELET_AWS_S3_SOURCE_SECRETKEY", "aws.s3.secretKey", secretName), + }...) + } + return envVars + } + + // AWS SQS environment variables + if source.Spec.Aws != nil && source.Spec.Aws.SQS != nil { + envVars = append(envVars, generateEnvVarsFromStruct("CAMEL_KAMELET_AWS_SQS_SOURCE", *source.Spec.Aws.SQS)...) + if secretName != "" { + envVars = append(envVars, []corev1.EnvVar{ + makeSecretEnvVar("CAMEL_KAMELET_AWS_SQS_SOURCE_ACCESSKEY", "aws.s3.accessKey", secretName), + makeSecretEnvVar("CAMEL_KAMELET_AWS_SQS_SOURCE_SECRETKEY", "aws.s3.secretKey", secretName), + }...) + } + return envVars + } + + // AWS DynamoDB Streams environment variables + if source.Spec.Aws != nil && source.Spec.Aws.DDBStreams != nil { + envVars = append(envVars, generateEnvVarsFromStruct("CAMEL_KAMELET_AWS_DDB_STREAMS_SOURCE", *source.Spec.Aws.DDBStreams)...) + if secretName != "" { + envVars = append(envVars, []corev1.EnvVar{ + makeSecretEnvVar("CAMEL_KAMELET_AWS_DDB_STREAMS_SOURCE_ACCESSKEY", "aws.s3.accessKey", secretName), + makeSecretEnvVar("CAMEL_KAMELET_AWS_DDB_STREAMS_SOURCE_SECRETKEY", "aws.s3.secretKey", secretName), + }...) + } + return envVars + } + + // If no valid configuration is found, return empty envVars + return envVars +} + +func makeSSLEnvVar() []corev1.EnvVar { + return []corev1.EnvVar{ + { + Name: "CAMEL_KNATIVE_CLIENT_SSL_ENABLED", + Value: "true", + }, + { + Name: "CAMEL_KNATIVE_CLIENT_SSL_CERT_PATH", + Value: "/knative-custom-certs/knative-eventing-bundle.pem", + }, + } +} + +func selectImage(source *v1alpha1.IntegrationSource) string { + if source.Spec.Timer != nil { + return "gcr.io/knative-nightly/timer-source:latest" + } + if source.Spec.Aws != nil { + if source.Spec.Aws.S3 != nil { + return "gcr.io/knative-nightly/aws-s3-source:latest" + } + if source.Spec.Aws.SQS != nil { + return "gcr.io/knative-nightly/aws-sqs-source:latest" + } + if source.Spec.Aws.DDBStreams != nil { + return "gcr.io/knative-nightly/aws-ddb-streams-source:latest" + } + } + return "" +} + +func makeSecretEnvVar(name, key, secretName string) corev1.EnvVar { + return corev1.EnvVar{ + Name: name, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + Key: key, + LocalObjectReference: corev1.LocalObjectReference{ + Name: secretName, + }, + }, + }, + } +} diff --git a/pkg/reconciler/integrationsource/resources/containersource_test.go b/pkg/reconciler/integrationsource/resources/containersource_test.go new file mode 100644 index 00000000000..5bac6445343 --- /dev/null +++ b/pkg/reconciler/integrationsource/resources/containersource_test.go @@ -0,0 +1,133 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 resources + +import ( + "fmt" + "testing" + + sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + "knative.dev/pkg/apis" + "knative.dev/pkg/kmeta" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/eventing/pkg/apis/sources/v1alpha1" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +const ( + testName = "test-integrationsource" + testNamespace = "test-namespace" + testUID = "test-uid" +) + +func TestNewContainerSource(t *testing.T) { + source := &v1alpha1.IntegrationSource{ + + ObjectMeta: metav1.ObjectMeta{ + Name: testName, + Namespace: testNamespace, + UID: testUID, + }, + Spec: v1alpha1.IntegrationSourceSpec{ + Timer: &v1alpha1.Timer{ + Period: 1000, + Message: "test-message", + ContentType: "text/plain", + RepeatCount: 0, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + URI: apis.HTTP("http://test-sink"), + }, + }, + }, + } + + want := &sourcesv1.ContainerSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-containersource", testName), + Namespace: testNamespace, + OwnerReferences: []metav1.OwnerReference{ + *kmeta.NewControllerRef(source), + }, + }, + Spec: sourcesv1.ContainerSourceSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "source", + Image: "gcr.io/knative-nightly/timer-source:latest", + ImagePullPolicy: corev1.PullIfNotPresent, + Env: []corev1.EnvVar{ + {Name: "CAMEL_KNATIVE_CLIENT_SSL_ENABLED", Value: "true"}, + {Name: "CAMEL_KNATIVE_CLIENT_SSL_CERT_PATH", Value: "/knative-custom-certs/knative-eventing-bundle.pem"}, + {Name: "CAMEL_KAMELET_TIMER_SOURCE_PERIOD", Value: "1000"}, + {Name: "CAMEL_KAMELET_TIMER_SOURCE_MESSAGE", Value: "test-message"}, + {Name: "CAMEL_KAMELET_TIMER_SOURCE_CONTENTTYPE", Value: "text/plain"}, + {Name: "CAMEL_KAMELET_TIMER_SOURCE_REPEATCOUNT", Value: "0"}, + }, + }, + }, + }, + }, + SourceSpec: duckv1.SourceSpec{ + Sink: duckv1.Destination{ + URI: apis.HTTP("http://test-sink"), + }, + }, + }, + } + + got := NewContainerSource(source) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("NewContainerSource() mismatch (-want +got):\n%s", diff) + } +} + +func TestGenerateEnvVarsFromStruct(t *testing.T) { + type TestStruct struct { + Field1 int `json:"field1"` + Field2 bool `json:"field2"` + Field3 string `json:"field3"` + } + + prefix := "TEST_PREFIX" + input := &TestStruct{ + Field1: 123, + Field2: true, + Field3: "hello", + } + + // Expected environment variables including SSL settings + want := []corev1.EnvVar{ + {Name: "CAMEL_KNATIVE_CLIENT_SSL_ENABLED", Value: "true"}, + {Name: "CAMEL_KNATIVE_CLIENT_SSL_CERT_PATH", Value: "/knative-custom-certs/knative-eventing-bundle.pem"}, + {Name: "TEST_PREFIX_FIELD1", Value: "123"}, + {Name: "TEST_PREFIX_FIELD2", Value: "true"}, + {Name: "TEST_PREFIX_FIELD3", Value: "hello"}, + } + + got := generateEnvVarsFromStruct(prefix, input) + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("generateEnvVarsFromStruct() mismatch (-want +got):\n%s", diff) + } +} diff --git a/pkg/reconciler/integrationsource/resources/names.go b/pkg/reconciler/integrationsource/resources/names.go new file mode 100644 index 00000000000..a69509e41dc --- /dev/null +++ b/pkg/reconciler/integrationsource/resources/names.go @@ -0,0 +1,26 @@ +/* + Copyright 2024 The Knative Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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 resources + +import ( + "knative.dev/eventing/pkg/apis/sources/v1alpha1" + "knative.dev/pkg/kmeta" +) + +func ContainerSourceName(source *v1alpha1.IntegrationSource) string { + return kmeta.ChildName(source.Name, "-containersource") +} diff --git a/pkg/reconciler/testing/v1/listers.go b/pkg/reconciler/testing/v1/listers.go index 2d2c32a9f93..a6d0733136a 100644 --- a/pkg/reconciler/testing/v1/listers.go +++ b/pkg/reconciler/testing/v1/listers.go @@ -39,6 +39,7 @@ import ( messagingv1 "knative.dev/eventing/pkg/apis/messaging/v1" sinksv1alpha1 "knative.dev/eventing/pkg/apis/sinks/v1alpha1" sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + sourcesv1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" fakeeventingclientset "knative.dev/eventing/pkg/client/clientset/versioned/fake" eventinglisters "knative.dev/eventing/pkg/client/listers/eventing/v1" eventingv1alpha1listers "knative.dev/eventing/pkg/client/listers/eventing/v1alpha1" @@ -47,6 +48,7 @@ import ( messaginglisters "knative.dev/eventing/pkg/client/listers/messaging/v1" sinkslisters "knative.dev/eventing/pkg/client/listers/sinks/v1alpha1" sourcelisters "knative.dev/eventing/pkg/client/listers/sources/v1" + sourcev1alpha1listers "knative.dev/eventing/pkg/client/listers/sources/v1alpha1" testscheme "knative.dev/eventing/pkg/reconciler/testing/scheme" duckv1 "knative.dev/pkg/apis/duck/v1" "knative.dev/pkg/reconciler/testing" @@ -126,6 +128,10 @@ func (l *Listers) GetJobSinkLister() sinkslisters.JobSinkLister { return sinkslisters.NewJobSinkLister(l.indexerFor(&sinksv1alpha1.JobSink{})) } +func (l *Listers) GetIntegrationSourceLister() sourcev1alpha1listers.IntegrationSourceLister { + return sourcev1alpha1listers.NewIntegrationSourceLister(l.indexerFor(&sourcesv1alpha1.IntegrationSource{})) +} + func (l *Listers) GetPingSourceLister() sourcelisters.PingSourceLister { return sourcelisters.NewPingSourceLister(l.indexerFor(&sourcesv1.PingSource{})) } diff --git a/pkg/reconciler/testing/v1alpha1/integrationsource.go b/pkg/reconciler/testing/v1alpha1/integrationsource.go new file mode 100644 index 00000000000..ace331455c4 --- /dev/null +++ b/pkg/reconciler/testing/v1alpha1/integrationsource.go @@ -0,0 +1,73 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + v1 "knative.dev/eventing/pkg/apis/sources/v1" + v1alpha1 "knative.dev/eventing/pkg/apis/sources/v1alpha1" +) + +// IntegrationSourceOption enables further configuration of a IntegrationSource. +type IntegrationSourceOption func(source *v1alpha1.IntegrationSource) + +// NewIntegrationSource creates a v1 IntegrationSource with IntegrationSourceOptions +func NewIntegrationSource(name, namespace string, o ...IntegrationSourceOption) *v1alpha1.IntegrationSource { + s := &v1alpha1.IntegrationSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } + for _, opt := range o { + opt(s) + } + s.SetDefaults(context.Background()) + return s +} + +func WithIntegrationSourceUID(uid types.UID) IntegrationSourceOption { + return func(s *v1alpha1.IntegrationSource) { + s.UID = uid + } +} + +// WithInitIntegrationSourceConditions initializes the IntegrationSource's conditions. +func WithInitIntegrationSourceConditions(s *v1alpha1.IntegrationSource) { + s.Status.InitializeConditions() +} + +func WithIntegrationSourceStatusObservedGeneration(generation int64) IntegrationSourceOption { + return func(s *v1alpha1.IntegrationSource) { + s.Status.ObservedGeneration = generation + } +} + +func WithIntegrationSourcePropagateContainerSourceStatus(status *v1.ContainerSourceStatus) IntegrationSourceOption { + return func(s *v1alpha1.IntegrationSource) { + s.Status.PropagateContainerSourceStatus(status) + } +} + +func WithIntegrationSourceSpec(spec v1alpha1.IntegrationSourceSpec) IntegrationSourceOption { + return func(s *v1alpha1.IntegrationSource) { + s.Spec = spec + } +} diff --git a/test/rekt/features/integrationsource/features.go b/test/rekt/features/integrationsource/features.go new file mode 100644 index 00000000000..583ea67c23c --- /dev/null +++ b/test/rekt/features/integrationsource/features.go @@ -0,0 +1,117 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 integrationsource + +import ( + "context" + + "github.com/cloudevents/sdk-go/v2/test" + "knative.dev/eventing/pkg/eventingtls/eventingtlstesting" + "knative.dev/eventing/test/rekt/features/featureflags" + "knative.dev/eventing/test/rekt/features/source" + "knative.dev/eventing/test/rekt/resources/integrationsource" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/network" + "knative.dev/reconciler-test/pkg/environment" + "knative.dev/reconciler-test/pkg/eventshub" + "knative.dev/reconciler-test/pkg/eventshub/assert" + "knative.dev/reconciler-test/pkg/feature" + "knative.dev/reconciler-test/pkg/resources/service" +) + +func SendsEventsWithSinkRef() *feature.Feature { + source := feature.MakeRandomK8sName("integrationsource") + sink := feature.MakeRandomK8sName("sink") + f := feature.NewFeature() + + f.Setup("install sink", eventshub.Install(sink, eventshub.StartReceiver)) + + f.Requirement("install integrationsource", integrationsource.Install(source, integrationsource.WithSink(service.AsDestinationRef(sink)))) + f.Requirement("integrationsource goes ready", integrationsource.IsReady(source)) + + f.Stable("integrationsource as event source"). + Must("delivers events", + assert.OnStore(sink).MatchEvent(test.HasType("dev.knative.connector.event.timer")).AtLeast(1)) + + return f +} + +func SendEventsWithTLSRecieverAsSink() *feature.Feature { + src := feature.MakeRandomK8sName("integrationsource") + sink := feature.MakeRandomK8sName("sink") + f := feature.NewFeature() + + f.Prerequisite("should not run when Istio is enabled", featureflags.IstioDisabled()) + + f.Setup("install sink", eventshub.Install(sink, eventshub.StartReceiverTLS)) + + f.Requirement("install ContainerSource", func(ctx context.Context, t feature.T) { + d := service.AsDestinationRef(sink) + d.CACerts = eventshub.GetCaCerts(ctx) + + integrationsource.Install(src, integrationsource.WithSink(d))(ctx, t) + }) + f.Requirement("integrationsource goes ready", integrationsource.IsReady(src)) + + f.Stable("integrationsource as event source"). + Must("delivers events", + assert.OnStore(sink). + Match(assert.MatchKind(eventshub.EventReceived)). + MatchEvent(test.HasType("dev.knative.connector.event.timer")). + AtLeast(1), + ). + Must("Set sinkURI to HTTPS endpoint", source.ExpectHTTPSSink(integrationsource.Gvr(), src)). + Must("Set sinkCACerts to non empty CA certs", source.ExpectCACerts(integrationsource.Gvr(), src)) + + return f +} + +func SendEventsWithTLSRecieverAsSinkTrustBundle() *feature.Feature { + src := feature.MakeRandomK8sName("integrationsource") + sink := feature.MakeRandomK8sName("sink") + f := feature.NewFeature() + + f.Prerequisite("should not run when Istio is enabled", featureflags.IstioDisabled()) + + f.Setup("install sink", eventshub.Install(sink, + eventshub.IssuerRef(eventingtlstesting.IssuerKind, eventingtlstesting.IssuerName), + eventshub.StartReceiverTLS, + )) + + f.Requirement("install ContainerSource", func(ctx context.Context, t feature.T) { + integrationsource.Install(src, integrationsource.WithSink(&duckv1.Destination{ + URI: &apis.URL{ + Scheme: "https", // Force using https + Host: network.GetServiceHostname(sink, environment.FromContext(ctx).Namespace()), + }, + CACerts: nil, // CA certs are in the trust-bundle + }))(ctx, t) + }) + f.Requirement("integrationsource goes ready", integrationsource.IsReady(src)) + + f.Stable("integrationsource as event source"). + Must("delivers events", + assert.OnStore(sink). + Match(assert.MatchKind(eventshub.EventReceived)). + MatchEvent(test.HasType("dev.knative.connector.event.timer")). + AtLeast(1), + ). + Must("Set sinkURI to HTTPS endpoint", source.ExpectHTTPSSink(integrationsource.Gvr(), src)) + + return f +} diff --git a/test/rekt/features/integrationsource/oidc_feature.go b/test/rekt/features/integrationsource/oidc_feature.go new file mode 100644 index 00000000000..c4f65a7f2dd --- /dev/null +++ b/test/rekt/features/integrationsource/oidc_feature.go @@ -0,0 +1,65 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 integrationsource + +import ( + "context" + + "github.com/cloudevents/sdk-go/v2/test" + "knative.dev/eventing/test/rekt/features/featureflags" + "knative.dev/eventing/test/rekt/features/source" + "knative.dev/eventing/test/rekt/resources/integrationsource" + "knative.dev/reconciler-test/pkg/eventshub" + "knative.dev/reconciler-test/pkg/eventshub/assert" + "knative.dev/reconciler-test/pkg/feature" + "knative.dev/reconciler-test/pkg/resources/service" +) + +func SendsEventsWithSinkRefOIDC() *feature.Feature { + src := feature.MakeRandomK8sName("integrationsource") + sink := feature.MakeRandomK8sName("sink") + sinkAudience := "audience" + f := feature.NewFeature() + + f.Prerequisite("OIDC authentication is enabled", featureflags.AuthenticationOIDCEnabled()) + f.Prerequisite("transport encryption is strict", featureflags.TransportEncryptionStrict()) + f.Prerequisite("should not run when Istio is enabled", featureflags.IstioDisabled()) + + f.Setup("install sink", eventshub.Install(sink, + eventshub.OIDCReceiverAudience(sinkAudience), + eventshub.StartReceiverTLS)) + + f.Requirement("install integrationsource", func(ctx context.Context, t feature.T) { + d := service.AsDestinationRef(sink) + d.CACerts = eventshub.GetCaCerts(ctx) + d.Audience = &sinkAudience + + integrationsource.Install(src, integrationsource.WithSink(d))(ctx, t) + }) + + f.Requirement("integrationsource goes ready", integrationsource.IsReady(src)) + + f.Stable("integrationsource as event source"). + Must("delivers events", + assert.OnStore(sink).MatchEvent(test.HasType("dev.knative.connector.event.timer")).AtLeast(1)). + Must("uses integrationsources identity for OIDC", assert.OnStore(sink).MatchWithContext( + assert.MatchKind(eventshub.EventReceived).WithContext(), + assert.MatchOIDCUserFromResource(integrationsource.Gvr(), src)).AtLeast(1)). + Must("Set sinkURI to HTTPS endpoint", source.ExpectHTTPSSink(integrationsource.Gvr(), src)). + Must("Set sinkCACerts to non empty CA certs", source.ExpectCACerts(integrationsource.Gvr(), src)) + return f +} diff --git a/test/rekt/integrationsource_test.go b/test/rekt/integrationsource_test.go new file mode 100644 index 00000000000..c33a89d8b04 --- /dev/null +++ b/test/rekt/integrationsource_test.go @@ -0,0 +1,63 @@ +//go:build e2e +// +build e2e + +/* +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 rekt + +import ( + "testing" + + "knative.dev/pkg/system" + "knative.dev/reconciler-test/pkg/environment" + "knative.dev/reconciler-test/pkg/eventshub" + "knative.dev/reconciler-test/pkg/k8s" + "knative.dev/reconciler-test/pkg/knative" + + "knative.dev/eventing/test/rekt/features/integrationsource" +) + +func TestIntegrationSourceWithSinkRef(t *testing.T) { + t.Parallel() + + ctx, env := global.Environment( + knative.WithKnativeNamespace(system.Namespace()), + knative.WithLoggingConfig, + knative.WithTracingConfig, + k8s.WithEventListener, + environment.Managed(t), + ) + t.Cleanup(env.Finish) + + env.Test(ctx, t, integrationsource.SendsEventsWithSinkRef()) +} + +func TestIntegrationSourceWithTLS(t *testing.T) { + t.Parallel() + + ctx, env := global.Environment( + knative.WithKnativeNamespace(system.Namespace()), + knative.WithLoggingConfig, + knative.WithTracingConfig, + k8s.WithEventListener, + environment.Managed(t), + eventshub.WithTLS(t), + ) + + env.ParallelTest(ctx, t, integrationsource.SendEventsWithTLSRecieverAsSink()) + env.ParallelTest(ctx, t, integrationsource.SendEventsWithTLSRecieverAsSinkTrustBundle()) +} diff --git a/test/rekt/resources/integrationsource/integrationsource.go b/test/rekt/resources/integrationsource/integrationsource.go new file mode 100644 index 00000000000..0c6821b3ea4 --- /dev/null +++ b/test/rekt/resources/integrationsource/integrationsource.go @@ -0,0 +1,103 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT 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 integrationsource + +import ( + "context" + "embed" + "strings" + "time" + + "k8s.io/apimachinery/pkg/runtime/schema" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/reconciler-test/pkg/environment" + "knative.dev/reconciler-test/pkg/feature" + "knative.dev/reconciler-test/pkg/k8s" + "knative.dev/reconciler-test/pkg/manifest" +) + +//go:embed integrationsource.yaml +var yaml embed.FS + +func Gvr() schema.GroupVersionResource { + return schema.GroupVersionResource{Group: "sources.knative.dev", Version: "v1alpha1", Resource: "integrationsources"} +} + +// IsReady tests to see if a ContainerSource becomes ready within the time given. +func IsReady(name string, timing ...time.Duration) feature.StepFn { + return k8s.IsReady(Gvr(), name, timing...) +} + +// Install will create a ContainerSource resource, augmented with the config fn options. +func Install(name string, opts ...manifest.CfgFn) feature.StepFn { + cfg := map[string]interface{}{ + "name": name, + } + for _, fn := range opts { + fn(cfg) + } + + return func(ctx context.Context, t feature.T) { + if ic := environment.GetIstioConfig(ctx); ic.Enabled { + manifest.WithIstioPodAnnotations(cfg) + } + + //if err := registerImage(ctx); err != nil { + // t.Fatal(err) + //} + if _, err := manifest.InstallYamlFS(ctx, yaml, cfg); err != nil { + t.Fatal(err) + } + } +} + +func WithSink(d *duckv1.Destination) manifest.CfgFn { + return func(cfg map[string]interface{}) { + if _, set := cfg["sink"]; !set { + cfg["sink"] = map[string]interface{}{} + } + sink := cfg["sink"].(map[string]interface{}) + + ref := d.Ref + uri := d.URI + + if d.CACerts != nil { + // This is a multi-line string and should be indented accordingly. + // Replace "new line" with "new line + spaces". + sink["CACerts"] = strings.ReplaceAll(*d.CACerts, "\n", "\n ") + } + + if uri != nil { + sink["uri"] = uri.String() + } + + if d.Audience != nil { + sink["audience"] = *d.Audience + } + + if ref != nil { + if _, set := sink["ref"]; !set { + sink["ref"] = map[string]interface{}{} + } + sref := sink["ref"].(map[string]interface{}) + sref["apiVersion"] = ref.APIVersion + sref["kind"] = ref.Kind + sref["namespace"] = ref.Namespace + sref["name"] = ref.Name + } + } +} diff --git a/test/rekt/resources/integrationsource/integrationsource.yaml b/test/rekt/resources/integrationsource/integrationsource.yaml new file mode 100644 index 00000000000..0da9cd6f231 --- /dev/null +++ b/test/rekt/resources/integrationsource/integrationsource.yaml @@ -0,0 +1,47 @@ +# Copyright 2024 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: sources.knative.dev/v1alpha1 +kind: IntegrationSource +metadata: + name: {{ .name }} + namespace: {{ .namespace }} +spec: + timer: + period: 2000 + message: "Hello, Eventing Core" +# {{if .sink }} + sink: + {{ if .sink.ref }} + ref: + kind: {{ .sink.ref.kind }} + {{ if .sink.ref.namespace }} + namespace: {{ .sink.ref.namespace }} + {{ else }} + namespace: {{ .namespace }} + {{ end }} + name: {{ .sink.ref.name }} + apiVersion: {{ .sink.ref.apiVersion }} + {{ end }} + {{ if .sink.uri }} + uri: {{ .sink.uri }} + {{ end }} + {{ if .sink.CACerts }} + CACerts: |- + {{ .sink.CACerts }} + {{ end }} + {{if .sink.audience }} + audience: {{ .sink.audience}} + {{ end }} + {{ end }}