From ceb63b2f863c81e3db8bd3c2674e60e66077a49f Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Tue, 28 Jul 2026 23:15:52 +0800 Subject: [PATCH 1/2] docs: align docs with codes --- CONTRIBUTING.md | 23 +++-- README.md | 71 ++++++-------- charts/pulsar-resources-operator/Chart.yaml | 2 +- charts/pulsar-resources-operator/README.md | 94 +++++++++++-------- .../README.md.gotmpl | 16 +++- .../values.schema.json | 4 + charts/pulsar-resources-operator/values.yaml | 30 +++--- .../resource_v1alpha1_pulsarnamespace.yaml | 5 +- .../resource_v1alpha1_pulsartopic.yaml | 5 +- docs/apikey.md | 38 +++----- docs/compute_flink_deployment.md | 62 ++++++------ docs/pulsar_connection.md | 18 ++-- docs/pulsar_function.md | 10 +- docs/pulsar_geo_replication.md | 44 +++++---- docs/pulsar_namespace.md | 36 ++++++- docs/pulsar_ns_isolation_policy.md | 2 + docs/pulsar_package.md | 24 ++--- docs/pulsar_permission.md | 6 +- docs/pulsar_resource_lifecycle.md | 41 +++++--- docs/pulsar_tenant.md | 2 +- docs/pulsar_topic.md | 71 +++++++++++--- docs/rolebinding.md | 13 +-- docs/secret.md | 23 ++++- docs/serviceaccount.md | 4 +- docs/serviceaccountbinding.md | 6 ++ docs/streamnative_cloud_connection.md | 12 ++- tests/README.md | 17 ++-- 27 files changed, 405 insertions(+), 274 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d303b6da..5b062b65 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,16 +54,23 @@ Please read through below conventions before contributions. - All filenames should be lowercase. - Go source files and directories use underscores, not dashes. - Package directories should generally avoid using separators as much as possible. When package names are multiple words, they usually should be in nested subdirectories. -- Document directories and filenames should use dashes rather than underscores. +- Documentation filenames are lowercase and currently follow the existing `docs/*.md` underscore convention. Match neighboring files when adding new documents. - All source files should add a license at the beginning. ### How to work locally -1. Clones this repo -2. Create the cluster `minikube start --memory=8192 --cpus=4` -3. [Deploy Apache Pulsar Standalone](https://pulsar.apache.org/docs/4.0.x/getting-started-helm/#step-1-install-pulsar-helm-chart) -4. Open the minikube tunnel in another terminal `minikube tunnel -c` -5. Apply operator's crds `make install` -6. Executes `go run .` in order to run the operator locally rather than inside the cluster -7. Run tests `~/go/bin/ginkgo ./operator` \ No newline at end of file +1. Install the Go version declared by `go.work` and clone this repository. +2. Run unit and envtest coverage with `make test`. +3. For end-to-end tests, create a cluster, for example `minikube start --memory=8192 --cpus=4`. +4. [Deploy Apache Pulsar](https://pulsar.apache.org/docs/4.0.x/getting-started-helm/#step-1-install-pulsar-helm-chart). +5. Apply the operator CRDs with `make install`. +6. Run the operator locally with `make run`. +7. In another terminal, run the integration suite: + + ```shell + cd tests + go run github.com/onsi/ginkgo/v2/ginkgo --trace ./operator + ``` + + Set `ADMIN_SERVICE_URL`, `NAMESPACE`, `BROKER_NAME`, and `PROXY_URL` as described in [`tests/README.md`](tests/README.md) when defaults do not match the test cluster. diff --git a/README.md b/README.md index 17fa1160..efc0c6b0 100644 --- a/README.md +++ b/README.md @@ -2,38 +2,34 @@ Authored by [StreamNative](https://streamnative.io), this Pulsar Resources Operator is a controller that manages the Pulsar resources automatically using the manifest on Kubernetes. Therefore, you can manage the Pulsar resources without the help of `pulsar-admin` or `pulsarctl` CLI tool. It is useful for initializing basic resources when creating a new Pulsar cluster. -Currently, the Pulsar Resources Operator provides full lifecycle management for the following Pulsar resources, including creation, update, and deletion. - -- [Tenants](docs/pulsar_tenant.md) -- [Namespaces](docs/pulsar_namespace.md) -- [Topics](docs/pulsar_topic.md) -- [Permissions](docs/pulsar_permission.md) -- [Packages](docs/pulsar_package.md) -- [Functions](docs/pulsar_function.md) -- [Sinks](docs/pulsar_sink.md) -- [Sources](docs/pulsar_source.md) -- [Geo-Replication](docs/pulsar_geo_replication.md) -- [NS-Isolation-Policy](docs/pulsar_ns_isolation_policy.md) +The operator manages these resource groups: + +- Pulsar connectivity: [PulsarConnection](docs/pulsar_connection.md) +- Pulsar resources: [Tenants](docs/pulsar_tenant.md), [Namespaces](docs/pulsar_namespace.md), [Topics](docs/pulsar_topic.md), [Permissions](docs/pulsar_permission.md), [Packages](docs/pulsar_package.md), [Functions](docs/pulsar_function.md), [Sinks](docs/pulsar_sink.md), [Sources](docs/pulsar_source.md), [Geo-Replication](docs/pulsar_geo_replication.md), and [NS-Isolation-Policy](docs/pulsar_ns_isolation_policy.md) +- StreamNative Cloud connectivity and resources: [StreamNativeCloudConnection](docs/streamnative_cloud_connection.md), [ComputeWorkspace](docs/compute_workspace.md), [ComputeFlinkDeployment](docs/compute_flink_deployment.md), [Secret](docs/secret.md), [ServiceAccount](docs/serviceaccount.md), [ServiceAccountBinding](docs/serviceaccountbinding.md), [APIKey](docs/apikey.md), and [RoleBinding](docs/rolebinding.md) ## Lifecycle Management -The Pulsar Resources Operator provides a flexible approach to managing the lifecycle of Pulsar resources through the `PulsarResourceLifeCyclePolicy`. This policy determines how Pulsar resources are handled when their corresponding Kubernetes custom resources are deleted. For more details on lifecycle management, please refer to the [PulsarResourceLifeCyclePolicy documentation](docs/pulsar_resource_lifecycle.md). +The Pulsar Resources Operator provides a flexible approach to managing remote-resource lifecycle through `PulsarResourceLifeCyclePolicy`. This policy determines how supported Pulsar and StreamNative Cloud resources are handled when their Kubernetes custom resources are deleted. For details and the supported-resource list, see [PulsarResourceLifeCyclePolicy](docs/pulsar_resource_lifecycle.md). There are two available options for the lifecycle policy: -1. `CleanUpAfterDeletion`: When set, the Pulsar resource (such as a tenant, namespace, or topic) will be deleted from the Pulsar cluster when its corresponding Kubernetes custom resource is deleted. This is the default policy. +1. `CleanUpAfterDeletion`: The remote resource is deleted when its Kubernetes custom resource is deleted. This is the default policy. -2. `KeepAfterDeletion`: When set, the Pulsar resource will remain in the Pulsar cluster even after its corresponding Kubernetes custom resource is deleted. +2. `KeepAfterDeletion`: The remote resource remains after its Kubernetes custom resource is deleted. You can specify the lifecycle policy in the custom resource definition: ```yaml -apiVersion: pulsar.streamnative.io/v1beta1 +apiVersion: resource.streamnative.io/v1alpha1 kind: PulsarTenant metadata: name: my-tenant spec: - pulsarResourceLifeCyclePolicy: KeepAfterDeletion + name: my-tenant + connectionRef: + name: my-pulsar-connection + lifecyclePolicy: KeepAfterDeletion ``` # Installation @@ -44,10 +40,11 @@ You can install the Pulsar Resources Operator using the officially supported `pu ## Prerequisites -- Install [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) (v1.16 - v1.24), compatible with your cluster (+/- 1 minor release from your cluster). -- Install [`Helm`](https://helm.sh/docs/intro/install/) (v3.0.2 or higher). -- Prepare a Kubernetes cluster (v1.16 - v1.24). -- Prepare a [Pulsar cluster](https://docs.streamnative.io/operators/pulsar-operator/tutorial/deploy-pulsar) +- Install [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl), compatible with your cluster (+/- 1 minor release from your cluster). +- Install [`Helm`](https://helm.sh/docs/intro/install/) v3. +- Prepare a Kubernetes cluster v1.18 or newer, matching the Helm chart's `kubeVersion` constraint. +- Prepare a [Pulsar cluster](https://docs.streamnative.io/operators/pulsar-operator/tutorial/deploy-pulsar) when managing Pulsar resources. +- Prepare StreamNative Cloud service-account credentials and an organization name when managing StreamNative Cloud resources. ## Install Pulsar Resources Operator @@ -60,21 +57,13 @@ To install the Pulsar Resources Operator, follow these steps. helm repo update ``` -2. Create a Kubernetes namespace. +2. Install the operator using the `pulsar-resources-operator` Helm chart. Helm creates the namespace when needed. ```shell - kubectl create namespace + helm -n install streamnative/pulsar-resources-operator \ + --create-namespace ``` - >**Note** - > - > You can skip this step if you specify a Kubernetes namespace via the `-- create-namespace ` option when you install the operator. - -3. Install the operator using the `pulsar-resources-operator` Helm chart. - - ```shell - helm -n install streamnative/pulsar-resources-operator - ``` -4. Verify that the operator is installed successfully +3. Verify that the operator is installed successfully. ```shell kubectl -n get pods @@ -89,18 +78,17 @@ To install the Pulsar Resources Operator, follow these steps. ## Upgrade Pulsar Resources Operator -To upgrade the operator, execute the following command. +Helm does not upgrade CRDs from a chart's `crds/` directory. Pull the target chart, apply its CRDs, then upgrade the release: ```shell helm repo update -helm -n upgrade streamnative/pulsar-resources-operator +helm pull streamnative/pulsar-resources-operator --version --untar +kubectl apply -f pulsar-resources-operator/crds +helm -n upgrade streamnative/pulsar-resources-operator \ + --version ``` ->**Note** -> -> Don not forget to apply the latest crd files. Because there is no support for upgrading or deleting CRDs using Helm -> https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations -> You can use `helm pull streamnative/pulsar-resources-operator` to download the chart and unpack it, then apply the crds +See [Helm CRD caveats](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations). ## Uninstall Pulsar Resources Operator @@ -110,6 +98,8 @@ To uninstall the operator, execute the following command. helm -n uninstall ``` +Helm leaves CRDs and existing custom resources in place. Remove CRDs separately only after deleting or preserving all managed resources intentionally. + # Tutorial This tutorial guides you through creating Pulsar resources. You can create Pulsar resources automatically by applying resource manifest files to the Kubernetes. @@ -154,4 +144,3 @@ This library is licensed under the terms of the [Apache License 2.0](LICENSE) an ## About StreamNative Founded in 2019 by the original creators of Apache Pulsar, [StreamNative](https://streamnative.io) is one of the leading contributors to the open-source Apache Pulsar project. We have helped engineering teams worldwide make the move to Pulsar with [StreamNative Cloud](https://streamnative.io/product), a fully managed service to help teams accelerate time-to-production. - diff --git a/charts/pulsar-resources-operator/Chart.yaml b/charts/pulsar-resources-operator/Chart.yaml index 7bea24bb..534ae86a 100644 --- a/charts/pulsar-resources-operator/Chart.yaml +++ b/charts/pulsar-resources-operator/Chart.yaml @@ -30,7 +30,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: v0.19.0 +version: 0.19.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/pulsar-resources-operator/README.md b/charts/pulsar-resources-operator/README.md index ef3c8553..521e4f0b 100644 --- a/charts/pulsar-resources-operator/README.md +++ b/charts/pulsar-resources-operator/README.md @@ -1,6 +1,8 @@ -# Pulsar Resources Operator +# pulsar-resources-operator -![Version: v0.19.0](https://img.shields.io/badge/Version-v0.19.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.19.0](https://img.shields.io/badge/AppVersion-v0.19.0-informational?style=flat-square) +Pulsar Resources Operator Helm chart for Pulsar Resources Management on Kubernetes + +![Version: 0.19.0](https://img.shields.io/badge/Version-0.19.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.19.0](https://img.shields.io/badge/AppVersion-v0.19.0-informational?style=flat-square) ## Installing the Chart @@ -8,49 +10,26 @@ To install the chart with the release name `my-release`: ```console $ helm repo add streamnative https://charts.streamnative.io -$ helm -n install my-release streamnative/pulsar-resources-operator +$ helm -n install my-release streamnative/pulsar-resources-operator --create-namespace ``` ## Requirements -Kubernetes: `>= 1.16.0-0` +Kubernetes: `>= 1.18.0-0` Pulsar: `>= 2.9.0.x` ## CRD Upgrade -Helm installs CRDs from `crds/` only on `helm install`. A `helm upgrade` does not update CRDs. -To upgrade CRDs, apply them explicitly before or after upgrading the chart. - -Apply from the local chart directory: +Helm installs files from `crds/` during `helm install`, but does not update them during `helm upgrade`. Pull the target chart and apply its CRDs explicitly: ```console -$ kubectl apply -f charts/pulsar-resources-operator/crds +$ helm pull streamnative/pulsar-resources-operator --version --untar +$ kubectl apply -f pulsar-resources-operator/crds +$ helm -n upgrade my-release streamnative/pulsar-resources-operator --version ``` -Apply from raw URLs for a specific version (v0.19.0 shown below): - -```console -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_pulsarfunctions.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_serviceaccounts.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_pulsarpackages.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_serviceaccountbindings.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_computeworkspaces.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_pulsargeoreplications.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_apikeys.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_computeflinkdeployments.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_pulsarconnections.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_pulsarpermissions.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_pulsarnamespaces.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_pulsarsinks.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_pulsartopics.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_secrets.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_rolebindings.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_pulsarnsisolationpolicies.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_pulsartenants.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_pulsarsources.yaml -kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources-operator/refs/tags/pulsar-resources-operator-v0.19.0/charts/pulsar-resources-operator/crds/resource.streamnative.io_streamnativecloudconnections.yaml -``` +Uninstalling the release does not remove CRDs or existing custom resources. ## Values @@ -58,26 +37,59 @@ kubectl apply -f https://raw.githubusercontent.com/streamnative/pulsar-resources |-----|------|---------|-------------| | affinity | object | `{}` | Add affinity for pod | | annotations | object | `{}` | Add annotations for the deployment | +| cloudStorage | object | `{"azure":{"accountName":"","credentials":{"accountKey":"","create":false,"sasToken":"","secretName":"azure-credentials","useAccountKey":true},"enabled":false},"gcs":{"enabled":false,"serviceAccount":{"key":{"create":false,"json":"","mountPath":"/var/secrets/google","secretName":"gcs-credentials"},"name":"","useWorkloadIdentity":false}},"s3":{"credentials":{"accessKeyId":"","create":false,"secretAccessKey":"","secretName":"aws-credentials"},"enabled":false,"region":""}}` | Cloud storage configuration used when downloading files for PulsarPackage resources. | +| cloudStorage.azure | object | `{"accountName":"","credentials":{"accountKey":"","create":false,"sasToken":"","secretName":"azure-credentials","useAccountKey":true},"enabled":false}` | Azure Blob Storage configuration | +| cloudStorage.azure.accountName | string | `""` | Azure storage account name | +| cloudStorage.azure.credentials | object | `{"accountKey":"","create":false,"sasToken":"","secretName":"azure-credentials","useAccountKey":true}` | Azure credentials configuration | +| cloudStorage.azure.credentials.accountKey | string | `""` | Storage account key (only used if create is true) | +| cloudStorage.azure.credentials.create | bool | `false` | Create a new secret for Azure credentials | +| cloudStorage.azure.credentials.sasToken | string | `""` | SAS token (only used if create is true) | +| cloudStorage.azure.credentials.secretName | string | `"azure-credentials"` | Existing secret name | +| cloudStorage.azure.credentials.useAccountKey | bool | `true` | Use account key for authentication (if false, will use SAS token) | +| cloudStorage.azure.enabled | bool | `false` | Enable Azure Blob Storage support | +| cloudStorage.gcs | object | `{"enabled":false,"serviceAccount":{"key":{"create":false,"json":"","mountPath":"/var/secrets/google","secretName":"gcs-credentials"},"name":"","useWorkloadIdentity":false}}` | Google Cloud Storage configuration | +| cloudStorage.gcs.enabled | bool | `false` | Enable Google Cloud Storage support | +| cloudStorage.gcs.serviceAccount | object | `{"key":{"create":false,"json":"","mountPath":"/var/secrets/google","secretName":"gcs-credentials"},"name":"","useWorkloadIdentity":false}` | Service account configuration | +| cloudStorage.gcs.serviceAccount.key | object | `{"create":false,"json":"","mountPath":"/var/secrets/google","secretName":"gcs-credentials"}` | Service account key configuration (only used if useWorkloadIdentity is false) | +| cloudStorage.gcs.serviceAccount.key.create | bool | `false` | Create a new secret for service account key | +| cloudStorage.gcs.serviceAccount.key.json | string | `""` | Service account key JSON content (only used if create is true) | +| cloudStorage.gcs.serviceAccount.key.mountPath | string | `"/var/secrets/google"` | Mount path of the service account key file | +| cloudStorage.gcs.serviceAccount.key.secretName | string | `"gcs-credentials"` | Existing secret name containing the service account key | +| cloudStorage.gcs.serviceAccount.name | string | `""` | GCP service account email for Workload Identity Format: GSA_NAME@PROJECT_ID.iam.gserviceaccount.com | +| cloudStorage.gcs.serviceAccount.useWorkloadIdentity | bool | `false` | Use GCP Workload Identity (recommended for GKE) | +| cloudStorage.s3 | object | `{"credentials":{"accessKeyId":"","create":false,"secretAccessKey":"","secretName":"aws-credentials"},"enabled":false,"region":""}` | AWS S3 configuration | +| cloudStorage.s3.credentials | object | `{"accessKeyId":"","create":false,"secretAccessKey":"","secretName":"aws-credentials"}` | AWS credentials secret configuration | +| cloudStorage.s3.credentials.accessKeyId | string | `""` | AWS access key ID (only used if create is true) | +| cloudStorage.s3.credentials.create | bool | `false` | Create a new secret for AWS credentials | +| cloudStorage.s3.credentials.secretAccessKey | string | `""` | AWS secret access key (only used if create is true) | +| cloudStorage.s3.credentials.secretName | string | `"aws-credentials"` | Existing secret name | +| cloudStorage.s3.enabled | bool | `false` | Enable AWS S3 support | +| cloudStorage.s3.region | string | `""` | AWS region | | containerName | string | `"manager"` | Name of the operator container | +| extraVolumeMounts | list | `[]` | Additional volume mounts for the operator container. Paths are available to file-based PulsarConnection authentication. | +| extraVolumes | list | `[]` | Additional pod volumes for the operator deployment. | | features.alwaysUpdatePulsarResource | bool | `false` | Re-apply observed managed Pulsar resources even when their Kubernetes resources are already Ready. Prefer temporary use for upgrade remediation because it increases Pulsar admin API load on reconciliations and resyncs. | +| features.maxConcurrentReconciles | int | `1` | Maximum concurrent reconciles for the PulsarConnection and RoleBinding controllers. Values of 0 or 1 leave the flags unset and use the binary default. | +| features.resyncPeriod | int | `10` | Base informer resync period in hours. | +| features.retryCount | int | `5` | Number of retries used by the PulsarConnection-managed resource reconciler. | | fullnameOverride | string | `""` | It will override the name of deployment | -| image.manager.registry | string | `"docker.io"` | Specififies the registry of images, especially when user want to use a different image hub | -| image.manager.repository | string | `"streamnative/pulsar-resources-operator"` | The full repo name for image. | -| image.manager.tag | string | `""` | Image tag, it can override the image tag whose default is the chart appVersion. | +| image.manager.registry | string | `"docker.io"` | Container image registry. | +| image.manager.repository | string | `"streamnative/pulsar-resources-operator"` | Container image repository. | +| image.manager.tag | string | `""` | Container image tag. Defaults to chart appVersion when empty. | | image.pullPolicy | string | `"IfNotPresent"` | Image pull policy for the container. | -| imagePullSecrets | list | `[]` | Specifies image pull secrets for private registry, the format is `- name: gcr` | +| imagePullSecrets | list | `[]` | Image pull secrets for private registries, for example `- name: gcr`. | | labels | object | `{}` | Add labels for the deployment | | nameOverride | string | `""` | It will override the value of label `app.kubernetes.io/name` on pod | -| namespace | string | `""` | Specifies namespace for the release, it will override the `-n` parameter when it's not empty | +| namespace | string | `""` | Namespace for chart resources. When empty, use Helm release namespace. | | nodeSelector | object | `{}` | Add NodeSelector for pod schedule | | podAnnotations | object | `{}` | Add annotations for the deployment pod | | podLabels | object | `{}` | Add labels for the deployment pod | | podSecurityContext | object | `{}` | Add security context for pod | -| replicaCount | int | `1` | The replicas of pod | +| replicaCount | int | `1` | Number of operator replicas. | | resources | object | `{}` | Add resource limits and requests | | securityContext | object | `{}` | Add security context for container | -| serviceAccount.annotations | object | `{}` | | +| serviceAccount.annotations | object | `{}` | Annotations to add to the service account | | serviceAccount.create | bool | `true` | Specifies whether a service account should be created | -| serviceAccount.name | string | `""` | | -| terminationGracePeriodSeconds | int | `10` | The period seconds that pod will be termiated gracefully | +| serviceAccount.name | string | `""` | Name of the service account to use. When empty and create=true, the chart generates a name. | +| terminationGracePeriodSeconds | int | `10` | Graceful termination period in seconds. | | tolerations | list | `[]` | Add tolerations | diff --git a/charts/pulsar-resources-operator/README.md.gotmpl b/charts/pulsar-resources-operator/README.md.gotmpl index 53dc1618..bf39b9de 100644 --- a/charts/pulsar-resources-operator/README.md.gotmpl +++ b/charts/pulsar-resources-operator/README.md.gotmpl @@ -10,11 +10,23 @@ To install the chart with the release name `my-release`: ```console $ helm repo add streamnative https://charts.streamnative.io -$ helm -n install my-release streamnative/{{ template "chart.name" . }} +$ helm -n install my-release streamnative/{{ template "chart.name" . }} --create-namespace ``` {{ template "chart.requirementsSection" . }} Pulsar: `>= 2.9.0.x` -{{ template "chart.valuesSection" . }} \ No newline at end of file +## CRD Upgrade + +Helm installs files from `crds/` during `helm install`, but does not update them during `helm upgrade`. Pull the target chart and apply its CRDs explicitly: + +```console +$ helm pull streamnative/pulsar-resources-operator --version --untar +$ kubectl apply -f pulsar-resources-operator/crds +$ helm -n upgrade my-release streamnative/pulsar-resources-operator --version +``` + +Uninstalling the release does not remove CRDs or existing custom resources. + +{{ template "chart.valuesSection" . }} diff --git a/charts/pulsar-resources-operator/values.schema.json b/charts/pulsar-resources-operator/values.schema.json index da0c8625..c623e94b 100644 --- a/charts/pulsar-resources-operator/values.schema.json +++ b/charts/pulsar-resources-operator/values.schema.json @@ -123,6 +123,10 @@ "alwaysUpdatePulsarResource": { "type": "boolean" }, + "maxConcurrentReconciles": { + "type": "integer", + "minimum": 0 + }, "resyncPeriod": { "type": "integer" }, diff --git a/charts/pulsar-resources-operator/values.yaml b/charts/pulsar-resources-operator/values.yaml index 32c3b4e5..6b465b87 100644 --- a/charts/pulsar-resources-operator/values.yaml +++ b/charts/pulsar-resources-operator/values.yaml @@ -17,10 +17,10 @@ # This is a YAML-formatted file. # Declare variables to be passed into your templates. -# -- Specifies namespace for the release, it will override the `-n` parameter when it's not empty +# -- Namespace for chart resources. When empty, use Helm release namespace. namespace: "" -# -- The replicas of pod +# -- Number of operator replicas. replicaCount: 1 # -- Name of the operator container @@ -31,14 +31,14 @@ image: pullPolicy: IfNotPresent # resource controller container manager: - # -- Specififies the registry of images, especially when user want to use a different image hub + # -- Container image registry. registry: docker.io - # -- The full repo name for image. + # -- Container image repository. repository: streamnative/pulsar-resources-operator - # -- Image tag, it can override the image tag whose default is the chart appVersion. + # -- Container image tag. Defaults to chart appVersion when empty. tag: "" -# -- Specifies image pull secrets for private registry, the format is `- name: gcr` +# -- Image pull secrets for private registries, for example `- name: gcr`. imagePullSecrets: [] # If you want to specify secrets, follow this format # - name: test @@ -46,11 +46,11 @@ imagePullSecrets: [] features: # -- Re-apply observed managed Pulsar resources even when their Kubernetes resources are already Ready. Prefer temporary use for upgrade remediation because it increases Pulsar admin API load on reconciliations and resyncs. alwaysUpdatePulsarResource: false - # resyncPeriod determines the minimum frequency at which watched resources are reconciled. The unit is hour, default value is 10 hours. + # -- Base informer resync period in hours. resyncPeriod: 10 - # The number of retries in case of error. + # -- Number of retries used by the PulsarConnection-managed resource reconciler. retryCount: 5 - # The maximum number of concurrent reconciles per controller. + # -- Maximum concurrent reconciles for the PulsarConnection and RoleBinding controllers. Values of 0 or 1 leave the flags unset and use the binary default. maxConcurrentReconciles: 1 # -- It will override the value of label `app.kubernetes.io/name` on pod @@ -62,10 +62,9 @@ fullnameOverride: "" serviceAccount: # -- Specifies whether a service account should be created create: true - # Annotations to add to the service account + # -- Annotations to add to the service account annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template + # -- Name of the service account to use. When empty and create=true, the chart generates a name. name: "" # -- Add labels for the deployment @@ -115,11 +114,10 @@ tolerations: [] # -- Add affinity for pod affinity: {} -# -- The period seconds that pod will be termiated gracefully +# -- Graceful termination period in seconds. terminationGracePeriodSeconds: 10 -# -- Cloud storage providers configuration -# -- Allow to sync files as Pulsar Packages to Pulsar Packages Management Service +# -- Cloud storage configuration used when downloading files for PulsarPackage resources. cloudStorage: # -- AWS S3 configuration s3: @@ -179,5 +177,7 @@ cloudStorage: # -- Use account key for authentication (if false, will use SAS token) useAccountKey: true +# -- Additional volume mounts for the operator container. Paths are available to file-based PulsarConnection authentication. extraVolumeMounts: [] +# -- Additional pod volumes for the operator deployment. extraVolumes: [] diff --git a/config/samples/resource_v1alpha1_pulsarnamespace.yaml b/config/samples/resource_v1alpha1_pulsarnamespace.yaml index 62e09cf6..3f187c63 100644 --- a/config/samples/resource_v1alpha1_pulsarnamespace.yaml +++ b/config/samples/resource_v1alpha1_pulsarnamespace.yaml @@ -21,11 +21,12 @@ spec: name: tenant-one/testns connectionRef: name: pulsarconnection-sample + backlogQuotaType: destination_storage backlogQuotaLimitSize: 1Gi - backlogQuotaLimitTime: 2h backlogQuotaRetentionPolicy: producer_request_hold - # default is destination_storage + # For an age-based quota, use these fields instead of backlogQuotaLimitSize: # backlogQuotaType: message_age + # backlogQuotaLimitTime: 2h bundles: 16 messageTTL: 1h maxProducersPerTopic: 2 diff --git a/config/samples/resource_v1alpha1_pulsartopic.yaml b/config/samples/resource_v1alpha1_pulsartopic.yaml index d88bd206..7f3ef999 100644 --- a/config/samples/resource_v1alpha1_pulsartopic.yaml +++ b/config/samples/resource_v1alpha1_pulsartopic.yaml @@ -30,8 +30,9 @@ spec: # maxUnAckedMessagesPerSubscription: 4 # retentionTime: 1h # retentionSize: 10Gi - # backlogQuotaLimitTime: 24h + # backlogQuotaType: destination_storage # backlogQuotaLimitSize: 1Gi # backlogQuotaRetentionPolicy: producer_request_hold + # For an age-based quota, set backlogQuotaType: message_age and use backlogQuotaLimitTime instead. # compactionThreshold: 104857600 # 100MB in bytes - lifecyclePolicy: CleanUpAfterDeletion \ No newline at end of file + lifecyclePolicy: CleanUpAfterDeletion diff --git a/docs/apikey.md b/docs/apikey.md index 1a626a12..343502be 100644 --- a/docs/apikey.md +++ b/docs/apikey.md @@ -16,20 +16,20 @@ spec: instanceName: my-pulsar-instance serviceAccountName: my-service-account description: "API Key for automation" - expirationTime: "2025-12-31T23:59:59Z" + expirationTime: "2027-12-31T23:59:59Z" ``` ## Specification | Field | Type | Description | Required | | --- | --- | --- | --- | -| `spec.apiServerRef` | [LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#localobjectreference-v1-core) | Reference to a StreamNativeCloudConnection in the same namespace | Yes | +| `spec.apiServerRef` | `corev1.LocalObjectReference` | Reference to a StreamNativeCloudConnection in the same namespace | Yes | | `spec.lifecyclePolicy` | string | Whether to delete the remote API key or keep it when the Kubernetes resource is deleted. Defaults to cleanup when omitted. | No | | `spec.instanceName` | string | Name of the instance this API key is for | No | -| `spec.serviceAccountName` | string | Name of the service account this API key is for | Yes | +| `spec.serviceAccountName` | string | Name of the service account this API key is for. The CRD and controller treat it as optional; the remote API validates whether the supplied target is sufficient. | No | | `spec.description` | string | User-defined description of the API key | No | -| `spec.expirationTime` | string | Timestamp defining when this API key will expire | No | -| `spec.revoke` | boolean | Indicates whether this API key should be revoked | No | +| `spec.expirationTime` | string | Timestamp defining when this API key will expire. Used during creation; changing this field alone does not trigger a remote update. | No | +| `spec.revoke` | boolean | Present in the CRD, but not propagated by the current cloud-client conversion. Do not rely on it for revocation. | No | | `spec.encryptionKey` | object | Contains the public key used to encrypt the token | No | | `spec.exportPlaintextToken` | boolean | Indicates whether the plaintext token should be exported as Kubernetes secret if the Pulsar Resources Operator managed the private key. (Default: false) | No | @@ -42,7 +42,7 @@ spec: | `status.keyId` | string | Unique identifier for the API key | | `status.issuedAt` | string | Timestamp when the key was issued | | `status.expiresAt` | string | Timestamp when the key expires | -| `status.token` | string | The plaintext security token issued for the key (available only after creation) | +| `status.token` | string | Reserved field; the current controller does not populate plaintext tokens in status. | | `status.encryptedToken` | object | The encrypted security token if an encryption key was provided | | `status.revokedAt` | string | Timestamp when the key was revoked, if applicable | @@ -54,13 +54,15 @@ For each APIKey resource, the operator creates and manages two types of Secrets: - Name format: `-private-key` - Contains key: `private-key` -2. **Token Secret**: Contains the decrypted API token for authentication, only if no `encryptionKey` provided, and `exportPlaintextToken` to `true` +2. **Token Secret**: Contains the decrypted API token for authentication, only when the operator generated the key pair and `exportPlaintextToken` is `true` - Name format: `-token` - Contains key: `token` - Includes labels: - `resources.streamnative.io/apikey`: Name of the APIKey - `resources.streamnative.io/key-id`: Unique identifier of the APIKey +If you supply your own `encryptionKey`, the operator does not have the matching private key and cannot export a plaintext token. + ## Usage API keys provide authentication credentials for service accounts to access the StreamNative Cloud API. They can be used in automated workflows, CI/CD pipelines, or any system that needs to interact with StreamNative Cloud resources. @@ -84,7 +86,7 @@ spec: instanceName: my-pulsar-instance serviceAccountName: my-service-account description: "API Key for CI/CD pipeline" - expirationTime: "2026-01-01T00:00:00Z" + expirationTime: "2027-01-01T00:00:00Z" ``` ### Using API Keys in Applications @@ -114,22 +116,8 @@ The application can then read the token from `/etc/apikey/token`. Set `spec.lifecyclePolicy: KeepAfterDeletion` if you want to remove the Kubernetes `APIKey` resource while preserving the remote key in StreamNative Cloud. -### Revoking an API Key - -To revoke an API key, update the `spec.revoke` field to `true`: +With `KeepAfterDeletion`, Kubernetes owner references still remove the generated private-key and token Secrets together with the custom resource. -```yaml -apiVersion: resource.streamnative.io/v1alpha1 -kind: APIKey -metadata: - name: my-automation-key - namespace: default -spec: - apiServerRef: - name: my-connection - instanceName: my-pulsar-instance - serviceAccountName: my-service-account - revoke: true -``` +### Revoking or Removing an API Key -Once revoked, the API key can no longer be used for authentication. +The current operator does not propagate `spec.revoke` to StreamNative Cloud. To remove the remote key, delete the Kubernetes `APIKey` with the default `CleanUpAfterDeletion` lifecycle policy. Use StreamNative Cloud tooling directly when revocation must preserve the remote APIKey object. diff --git a/docs/compute_flink_deployment.md b/docs/compute_flink_deployment.md index 339eb82d..7433e763 100644 --- a/docs/compute_flink_deployment.md +++ b/docs/compute_flink_deployment.md @@ -2,7 +2,7 @@ ## Overview -The `ComputeFlinkDeployment` resource defines a Flink deployment in StreamNative Cloud. It supports both Ververica Platform (VVP) and Community deployment templates, allowing you to deploy and manage Flink applications. +The `ComputeFlinkDeployment` resource defines a Flink deployment in StreamNative Cloud. The current client conversion implements Ververica Platform (VVP) templates. `communityTemplate` remains in the CRD but is not sent to StreamNative Cloud. ## Specifications @@ -11,15 +11,15 @@ The `ComputeFlinkDeployment` resource defines a Flink deployment in StreamNative | `apiServerRef` | Reference to the StreamNativeCloudConnection resource for API server access. If not specified, the APIServerRef from the referenced ComputeWorkspace will be used. | No | | `lifecyclePolicy` | Whether to delete the remote Flink deployment or keep it when the Kubernetes resource is deleted. Defaults to cleanup when omitted. | No | | `workspaceName` | Name of the ComputeWorkspace where the Flink deployment will run | Yes | -| `labels` | Labels to add to the deployment | No | -| `annotations` | Annotations to add to the deployment | No | -| `template` | VVP deployment template configuration | No* | -| `communityTemplate` | Community deployment template configuration | No* | +| `labels` | Present in the CRD but not propagated by the current client. Use `template.deployment.userMetadata.labels` instead. | No | +| `annotations` | Present in the CRD but not propagated by the current client. Use `template.deployment.userMetadata.annotations` instead. | No | +| `template` | VVP deployment template configuration. This is the only template type currently propagated. | Conditional | +| `communityTemplate` | Present in the CRD but ignored by the current create/update conversion. | No | | `defaultPulsarCluster`| Default Pulsar cluster to use for the deployment | No | -| `configuration` | Additional configuration for the Flink deployment, including environment variables and secrets | No | -| `imagePullSecrets` | List of image pull secrets to use for the deployment | No | +| `configuration` | Environment variables and Secret references. Propagated during remote creation; current update conversion leaves existing remote configuration unchanged. | No | +| `imagePullSecrets` | Image pull secrets injected into VVP JobManager and TaskManager pod templates. | No | -*Note: Either `template` or `communityTemplate` must be specified, but not both. +Use `template` for managed deployments. A resource containing only `communityTemplate` is accepted by Kubernetes but reaches the remote API without a deployment template. ## APIServerRef Inheritance @@ -53,7 +53,7 @@ This inheritance mechanism allows you to: | Field | Description | Required | |-------------|--------------------------------------------------------------------------------------------|----------| | `name` | Name of the ENV variable | Yes | -| `valueFrom` | References a secret in the same namespace | Yes | +| `valueFrom` | Secret key selector sent to the remote deployment. Optional in the CRD, but needed for a useful secret-backed value. | No | ### VVP Deployment Template @@ -74,9 +74,12 @@ This inheritance mechanism allows you to: | Field | Description | Required | |--------------------------------|----------------------------------------------------------------------------------------|----------| | `deploymentTargetName` | Target name for the deployment | No | +| `jobFailureExpirationTime` | Expiration setting for failed jobs | No | | `state` | State of the deployment (RUNNING, SUSPENDED, CANCELLED) | No | | `maxJobCreationAttempts` | Maximum number of job creation attempts (minimum: 1) | No | | `maxSavepointCreationAttempts` | Maximum number of savepoint creation attempts (minimum: 1) | No | +| `restoreStrategy` | Restore strategy containing `kind` and `allowNonRestoredState` | No | +| `sessionClusterName` | Session cluster used by the deployment | No | | `template` | Deployment template configuration | Yes | ##### Template Spec Fields @@ -85,6 +88,8 @@ This inheritance mechanism allows you to: |----------------------|----------------------------------------------------------------------------------------|----------| | `artifact` | Deployment artifact configuration | Yes | | `flinkConfiguration` | Flink configuration key-value pairs | No | +| `kubernetes` | VVP Kubernetes settings. The current local type propagates `labels`; top-level `imagePullSecrets` injects pod template image-pull secrets. | No | +| `latestCheckpointFetchInterval` | Checkpoint status fetch interval | No | | `parallelism` | Parallelism of the Flink job | No | | `numberOfTaskManagers`| Number of task managers | No | | `resources` | Resource requirements for jobmanager and taskmanager | No | @@ -94,42 +99,34 @@ This inheritance mechanism allows you to: | Field | Description | Required | |--------------------------|----------------------------------------------------------------------------------------|----------| -| `kind` | Type of artifact (JAR, PYTHON, sqlscript) | Yes | +| `kind` | Type of artifact (for example `JAR`, `PYTHON`, or `sqlscript`). The current checked-in CRD does not mark it required, but set it for a usable remote deployment. | No* | | `jarUri` | URI of the JAR file | No* | | `pythonArtifactUri` | URI of the Python artifact | No* | | `sqlScript` | SQL script content | No* | +| `additionalDependencies` | Additional artifact dependencies | No | | `flinkVersion` | Flink version to use | No | +| `flinkImageRegistry` | Flink image registry | No | +| `flinkImageRepository` | Flink image repository | No | | `flinkImageTag` | Flink image tag to use | No | | `mainArgs` | Arguments for the main class/method | No | | `entryClass` | Entry class for JAR artifacts | No | +| `uri` | Generic artifact URI | No | +| `artifactImage` | Container image containing the artifact | No | -*Note: One of `jarUri`, `pythonArtifactUri`, or `sqlScript` must be specified based on the `kind`. +*The current CRD does not enforce the artifact kind/URI combination. Supply `kind` and the matching artifact field expected by StreamNative Cloud, such as `jarUri`, `pythonArtifactUri`, or `sqlScript`. -### Community Deployment Template +`additionalPythonArchives`, `additionalPythonLibraries`, `artifactKind`, and `entryModule` exist in the CRD but are not copied by the current converter. -| Field | Description | Required | -|--------------------------|----------------------------------------------------------------------------------------|----------| -| `metadata` | Metadata for the deployment (annotations, labels) | No | -| `spec` | Community deployment specification | Yes | +### Community Deployment Template -#### Community Deployment Spec - -| Field | Description | Required | -|--------------------------|----------------------------------------------------------------------------------------|----------| -| `image` | Flink image to use | Yes | -| `jarUri` | URI of the JAR file | Yes | -| `entryClass` | Entry class of the JAR | No | -| `mainArgs` | Main arguments for the application | No | -| `flinkConfiguration` | Flink configuration key-value pairs | No | -| `jobManagerPodTemplate` | Pod template for the job manager | No | -| `taskManagerPodTemplate` | Pod template for the task manager | No | +`communityTemplate` is defined by the CRD, but `pkg/streamnativecloud/flinkdeployment_client.go` currently copies only `template`. Do not use `communityTemplate` until client conversion support is implemented. ## Status | Field | Description | |----------------------|-------------------------------------------------------------------------------------------------| | `conditions` | List of status conditions for the deployment | -| `observedGeneration` | The last observed generation of the resource | +| `observedGeneration` | Reserved field; the current controller records generation on the `Ready` condition but does not populate this top-level status field. | | `deploymentStatus` | Raw deployment status from the API server | ## Example @@ -343,15 +340,16 @@ spec: ## Update Deployment You can update the deployment by modifying the YAML file and reapplying it. Most fields can be updated, including: -- Flink configuration +- VVP template Flink configuration - Resources - Parallelism - Logging settings - Artifact configuration -- Environment variables and secrets - Image pull secrets -After applying changes, verify the status to ensure the deployment is updated properly. +The current update client replaces the VVP template, workspace name, and default Pulsar cluster. It does not copy top-level `configuration`, `labels`, or `annotations` during update. Environment variables and Secret references supplied at creation therefore remain unchanged until update support is added or the remote deployment is recreated. + +After applying changes, verify the `Ready` condition and `status.deploymentStatus` to ensure the remote deployment accepted the update. ## Delete Deployment @@ -362,3 +360,5 @@ kubectl delete computeflinkdeployment operator-test-v1 ``` This will stop the Flink job and clean up all associated resources in StreamNative Cloud. + +Set `spec.lifecyclePolicy: KeepAfterDeletion` to remove only the Kubernetes custom resource and retain the remote deployment. diff --git a/docs/pulsar_connection.md b/docs/pulsar_connection.md index 2a93844a..bb7ac12e 100644 --- a/docs/pulsar_connection.md +++ b/docs/pulsar_connection.md @@ -8,18 +8,18 @@ The `PulsarConnection` resource defines the connection details for a Pulsar clus | Field | Description | Required | Version | |-------|-------------|----------|---------| -| `adminServiceURL` | Admin service URL (e.g., `http://cluster-broker.test.svc.cluster.local:8080`). | No | All | -| `adminServiceSecureURL` | HTTPS admin service URL. | No | ≥ 0.3.0 | +| `adminServiceURL` | Admin service URL (e.g., `http://cluster-broker.test.svc.cluster.local:8080`). Required when `adminServiceSecureURL` is absent. | Conditional | All | +| `adminServiceSecureURL` | HTTPS admin service URL. Required when `adminServiceURL` is absent and preferred when both are set. | Conditional | ≥ 0.3.0 | | `brokerServiceURL` | Broker service URL (e.g., `pulsar://pulsar-sn-platform-broker.test.svc.cluster.local:6650`). | No | ≥ 0.3.0 | | `brokerServiceSecureURL` | TLS broker service URL (e.g., `pulsar+ssl://pulsar-sn-platform-broker.test.svc.cluster.local:6651`). | No | ≥ 0.3.0 | | `clusterName` | Pulsar cluster name (used for Geo-Replication). | No | ≥ 0.3.0 | | `authentication` | Authentication configuration (`token`, `oauth2`, or `tls`). | No | All | -| `brokerClientTrustCertsFilePath` | Path to trusted TLS cert for broker connections. | No | ≥ 0.3.0 | +| `brokerClientTrustCertsFilePath` | Certificate path stored in Pulsar cluster metadata for broker-to-broker geo-replication connections. The path must be available to the source brokers. | No | ≥ 0.3.0 | | `tlsAllowInsecureConnection` | Allow insecure TLS connection to brokers. | No | ≥ 0.5.0 | | `tlsEnableHostnameVerification` | Enable hostname verification for TLS. | No | ≥ 0.5.0 | -| `tlsTrustCertsFilePath` | CA certificate path for TLS verification. | No | ≥ 0.5.0 | +| `tlsTrustCertsFilePath` | CA certificate path used by the operator when connecting to `adminServiceSecureURL`. | No | ≥ 0.5.0 | -Fields with a listed version are available only from that version onward. +At least one of `adminServiceURL` and `adminServiceSecureURL` must be set; reconciliation fails when both are empty. Fields with a listed version are available only from that version onward. ## Authentication Methods @@ -102,7 +102,7 @@ spec: ```bash kubectl create secret generic pulsar-jwt-secret \ - --from-literal=brokerClientAuthenticationParameters= + --from-literal=brokerClientAuthenticationParameters= ``` ```yaml @@ -134,7 +134,7 @@ spec: clusterName: pulsar-cluster authentication: token: - value: + value: ``` ### OAuth2 with Secret @@ -195,7 +195,7 @@ spec: ### OAuth2 with file-based `ValueOrSecretRef` -When you want the controller to read OAuth2 credentials from a mounted file instead of embedding them in the CR or a Secret reference, mount the secret into the operator pod and point `key.file` at the mounted path. +When you want the controller to read OAuth2 credentials from a mounted file instead of embedding them in the CR or a Secret reference, mount the secret into the operator pod and point `key.file` at the mounted path. The path is resolved inside the operator container. 1) Create a secret from the credentials file: @@ -267,4 +267,4 @@ spec: - Create: `kubectl apply -f connection.yaml` - Check status: `kubectl -n get pulsarconnection.resource.streamnative.io` - Update: edit `connection.yaml` and re-apply (for example, remove `authentication` if the cluster is unauthenticated). -- Delete: `kubectl -n delete pulsarconnection.resource.streamnative.io `; the CR is removed after dependent Pulsar resources are cleaned up. +- Delete: remove dependent resources first, then run `kubectl -n delete pulsarconnection.resource.streamnative.io `. The current deletion guard explicitly waits for referenced tenants, namespaces, topics, and geo-replications; remove other referenced resources as well to avoid leaving them without a connection. diff --git a/docs/pulsar_function.md b/docs/pulsar_function.md index 8a3c43b1..ea7a5354 100644 --- a/docs/pulsar_function.md +++ b/docs/pulsar_function.md @@ -47,6 +47,8 @@ spec: This table lists specifications available for the `PulsarFunction` resource. +Configure exactly one package field: `jar`, `py`, or `go`. The controller requires a non-empty package URL and checks these fields in that order. + | Option | Description | Required or not | |----------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------| | `autoAck` | Whether to automatically acknowledge messages. | Optional | @@ -59,8 +61,8 @@ This table lists specifications available for the `PulsarFunction` resource. | `forwardSourceMessageProperty` | Whether to forward the source message property. | Optional | | `inputs` | The input topics. | Yes | | `jar` | The JAR package URL, can be used by Java runtime. | Optional | -| `py` | The Python package URL, can be used by Java runtime. | Optional | -| `go` | The Go package URL, can be used by Java runtime. | Optional | +| `py` | The Python package URL for the Python runtime. | Optional | +| `go` | The Go package URL for the Go runtime. | Optional | | `lifecyclePolicy` | The resource lifecycle policy. Available options are `CleanUpAfterDeletion` and `KeepAfterDeletion`. By default, it is set to `CleanUpAfterDeletion`. | Optional | | `logTopic` | The log topic. | Optional | | `maxMessageRetries` | The maximum number of message retries. | Optional | @@ -93,8 +95,6 @@ This table lists specifications available for the `PulsarFunction` resource. | `inputSpecs` | The input specs. | Optional | | `inputTypeClassName` | The input type class name of the function. | Optional | | `maxPendingAsyncRequests` | The maximum number of pending async requests. | Optional | -| `exposePulsarAdminClientEnabled` | Whether to expose the Pulsar admin client. | Optional | -| `skipToLatest` | Whether to skip to the latest. | Optional | 2. Apply the YAML file to create the function. @@ -102,7 +102,7 @@ This table lists specifications available for the `PulsarFunction` resource. kubectl apply -f function.yaml ``` -3. Check the resource status. When column Ready is true, it indicates the resource is created successfully in the pulsar cluster +3. Check the resource status. `READY=True` indicates that the function was created successfully in the Pulsar cluster. ```shell kubectl get pulsarfunction diff --git a/docs/pulsar_geo_replication.md b/docs/pulsar_geo_replication.md index 8bf4d2a2..4e5c8dbe 100644 --- a/docs/pulsar_geo_replication.md +++ b/docs/pulsar_geo_replication.md @@ -8,7 +8,7 @@ Key points about PulsarGeoReplication: 1. It's used for configuring replication between separate Pulsar instances. 2. The replication is unidirectional. To set up bidirectional replication, you need to create two PulsarGeoReplication resources, one for each direction. -3. It creates a new cluster in the destination Pulsar instance for each PulsarGeoReplication resource. +3. It creates or updates a cluster entry in the **source** Pulsar instance. That entry uses the destination connection's `clusterName` and service/authentication settings. 4. It's different from configuring geo-replication between clusters within a single Pulsar instance. For that purpose, use the `replicationClusters` field in the `PulsarNamespace` resource instead. PulsarGeoReplication is particularly useful for scenarios where you need to replicate data across different Pulsar deployments, such as disaster recovery, data locality, or compliance with data residency requirements. @@ -30,7 +30,7 @@ The `PulsarGeoReplication` resource has the following specifications: | `lifecyclePolicy` | Determines whether to keep or delete the geo-replication configuration when the Kubernetes resource is deleted. Options: `CleanUpAfterDeletion`, `KeepAfterDeletion`. Default is `CleanUpAfterDeletion`. | No | | `clusterParamsOverride` | Allows overriding specific cluster parameters when setting up geo-replication. This is useful when the destination cluster requires different configuration than what's defined in the `destinationConnectionRef`. See [Cluster Parameters Override](#cluster-parameters-override) for details. | No | -The `PulsarGeoReplication` resource is designed to configure geo-replication between separate Pulsar instances. It creates a new "Cluster" in the destination Pulsar cluster identified by `destinationConnectionRef`. This setup allows configuring the replication of data from the source cluster (identified by `connectionRef`) to the destination cluster. By establishing this connection, the brokers in the source cluster can communicate with and replicate data to the brokers in the destination cluster, enabling geo-replication between the two separate Pulsar instances. +The `PulsarGeoReplication` resource is designed to configure geo-replication between separate Pulsar instances. It creates a cluster entry in the source cluster identified by `connectionRef`, using connection data from `destinationConnectionRef`. Source brokers then use that entry to connect and replicate data to the destination cluster. ### Deletion Behavior @@ -97,8 +97,8 @@ spec: name: us-east-to-west-connection clusterParamsOverride: # Override URLs for cross-cluster communication - serviceURL: "https://geo-replication-admin.us-west.example.com:8443" - brokerServiceURL: "pulsar://geo-replication-broker.us-west.example.com:6650" + serviceSecureURL: "https://geo-replication-admin.us-west.example.com:8443" + brokerServiceSecureURL: "pulsar+ssl://geo-replication-broker.us-west.example.com:6651" # Override authentication for geo-replication authentication: authPlugin: "org.apache.pulsar.client.impl.auth.AuthenticationToken" @@ -115,8 +115,10 @@ spec: The `lifecyclePolicy` field determines what happens to the geo-replication configuration when the Kubernetes PulsarGeoReplication resource is deleted: -- `CleanUpAfterDeletion` (default): The geo-replication configuration will be removed from both Pulsar clusters when the Kubernetes resource is deleted. -- `KeepAfterDeletion`: The geo-replication configuration will remain in both Pulsar clusters even after the Kubernetes resource is deleted. +- `CleanUpAfterDeletion` (default): The destination cluster entry is removed from the source Pulsar cluster when the Kubernetes resource is deleted. +- `KeepAfterDeletion`: The destination cluster entry remains in the source Pulsar cluster after the Kubernetes resource is deleted. + +Remove references from `PulsarTenant`, `PulsarNamespace`, and `PulsarTopic` resources before deleting a geo-replication resource. Pulsar can reject cluster deletion while replication policies still reference it. For more information about lifecycle policies, refer to the [PulsarResourceLifeCyclePolicy documentation](pulsar_resource_lifecycle.md). @@ -164,13 +166,12 @@ pulsar-admin clusters list --url http://:8080 ## Tutorial: How to configure Geo-replication -This section describes how to configure Geo-replication between clusters `us-east-sn-platform` and `us-west-sn-platform` in different namespaces of the same Kubernetes cluster. +This section configures one-way replication from `us-east-sn-platform` to `us-west-sn-platform`, with the two Pulsar instances deployed in different Kubernetes namespaces. Repeat the setup in the opposite direction for bidirectional replication. The relation is shown below. ```mermaid graph TD; us-east-->us-west; - us-west-->us-east; ``` ### Prerequisites @@ -204,8 +205,8 @@ The destination PulsarConnection has the information of the Pulsar cluster`us-we apiVersion: resource.streamnative.io/v1alpha1 kind: PulsarConnection metadata: - name: us-west-dest-connection - namespace: us-west + name: us-east-to-west-connection + namespace: us-east spec: # The destination us-west cluster name clusterName: us-west-sn-platform @@ -216,9 +217,9 @@ spec: #### Use tls connection -When you want to use tls to connect remote cluster, you need to do some extra steps. +When source brokers use TLS to connect to the remote cluster, the trust certificate path stored in cluster metadata must exist on those source brokers. -1. For a selfsigning cert, you need to create a secret to store the cert file of connecting the `us-west` brokers. +1. For a self-signed certificate, create a Secret containing the CA certificate in the source broker namespace. ```yaml apiVersion: v1 @@ -227,11 +228,11 @@ data: kind: Secret metadata: name: us-west-tls-broker - namespace: us-esat + namespace: us-east type: Opaque ``` -2. Mount the secret to `us-west` pulsarbroker by adding these line to the `pulsarbroker.spec.pod.secretRefs`. The mount path will be used in `us-west` pulsar connection. +2. Mount the Secret into the **source** (`us-east`) brokers. The exact pod configuration depends on how Pulsar is deployed; the resulting mount path is referenced by `brokerClientTrustCertsFilePath`. ```yaml spec: pod: @@ -240,7 +241,9 @@ spec: secretName: us-west-tls-broker ``` -3. Add `adminServiceSecureURL` and `brokerServiceSecureURL` to the destination connection +3. If the remote admin endpoint also uses that self-signed CA, make the CA available inside the operator container and set `tlsTrustCertsFilePath`. Kubernetes Secret volumes are namespace-scoped, so create an equivalent Secret in the operator release namespace and mount it with the chart's `extraVolumes` and `extraVolumeMounts` values. + +4. Add the secure URLs and trust paths to the destination connection. ```yaml apiVersion: resource.streamnative.io/v1alpha1 kind: PulsarConnection @@ -256,14 +259,15 @@ spec: authentication: token: value: xxxx - adminServiceSecureURL: https://:8443 # remote Pulsar admin TLS service + brokerServiceSecureURL: pulsar+ssl://:6651 # remote Pulsar broker TLS service + tlsTrustCertsFilePath: /etc/operator-tls/us-west/ca.crt # path inside operator container + brokerClientTrustCertsFilePath: /etc/tls/us-west/ca.crt # path mounted on source brokers ``` ### Create a PulsarGeoReplication -This section enabled Geo-replication on `us-east`, which replicates data from `us-east` to `us-west`. The operator will create a new cluster entry called `us-west-sn-platform` in `us-east` cluster. +This section enables geo-replication on `us-east`, which replicates data from `us-east` to `us-west`. The operator creates a cluster entry named `us-west-sn-platform` in the `us-east` cluster. ```yaml apiVersion: resource.streamnative.io/v1alpha1 @@ -347,4 +351,4 @@ spec: After the resources are ready, you can test Geo-replication by producing and consuming messages. - Open a terminal and run the command `./bin/pulsar-client produce geo-test/geo-namespace/geo-topic -m "hello" -n 10` to produce messages to `us-east`. -- Open another terminal and run the command `./bin/pulsar-client consume geo-test/geo-namespace/geo-topic -s sub -n 0` to consume messages from `us-west`. \ No newline at end of file +- Open another terminal and run the command `./bin/pulsar-client consume geo-test/geo-namespace/geo-topic -s sub -n 0` to consume messages from `us-west`. diff --git a/docs/pulsar_namespace.md b/docs/pulsar_namespace.md index afdb2119..d8d5612b 100644 --- a/docs/pulsar_namespace.md +++ b/docs/pulsar_namespace.md @@ -24,6 +24,7 @@ The `PulsarNamespace` resource defines a namespace in a Pulsar cluster. It allow | `backlogQuotaType` | Controls how the backlog quota is enforced. Options: "destination_storage" (limits backlog by size in bytes), "message_age" (limits by time). | No | | `offloadThresholdTime` | Time limit for message offloading. Messages older than this limit will be offloaded to the tiered storage. | No | | `offloadThresholdSize` | Size limit for message offloading. When the limit is reached, older messages will be offloaded to the tiered storage. | No | +| `offloadPolicies` | Full namespace-level tiered-storage policy, including driver, bucket, endpoint, credentials, thread settings, and thresholds. Uses the same structure as [`PulsarTopic.spec.offloadPolicies`](pulsar_topic.md#offloadpolicies). | No | | `geoReplicationRefs` | List of references to PulsarGeoReplication resources, used to configure geo-replication for this namespace. Use only when using PulsarGeoReplication for setting up geo-replication between two Pulsar instances. | No | | `replicationClusters` | List of clusters to which the namespace is replicated. Use only if replicating clusters within the same Pulsar instance. | No | | `deduplication` | Whether to enable message deduplication for the namespace. | No | @@ -50,6 +51,31 @@ The `PulsarNamespace` resource defines a namespace in a Pulsar cluster. It allow Note: Valid time units are "s" (seconds), "m" (minutes), "h" (hours), "d" (days), "w" (weeks). +## Backlog Quota Selection + +`backlogQuotaRetentionPolicy` is required whenever a backlog quota is configured. `backlogQuotaType` selects which limit the operator sends to Pulsar: + +- `destination_storage` (default) requires `backlogQuotaLimitSize`; `backlogQuotaLimitTime` is ignored for that quota. +- `message_age` requires `backlogQuotaLimitTime`; `backlogQuotaLimitSize` is ignored for that quota. + +Configure one matching limit per quota type. Use `backlogQuotaLimitSize: "-1"` with `destination_storage` for an unlimited size quota. + +## Namespace Offload Policies + +`offloadThresholdTime` and `offloadThresholdSize` call the dedicated namespace threshold APIs. `offloadPolicies` sends the complete Pulsar offload-policy object and supports every field listed in [`PulsarTopic.spec.offloadPolicies`](pulsar_topic.md#offloadpolicies). + +When both forms configure the same threshold, the operator applies `offloadPolicies` after the dedicated threshold fields. Avoid conflicting values. + +```yaml +spec: + offloadPolicies: + managedLedgerOffloadDriver: aws-s3 + s3ManagedLedgerOffloadBucket: pulsar-offload + s3ManagedLedgerOffloadRegion: us-west-2 + managedLedgerOffloadThresholdInBytes: 1073741824 + managedLedgerOffloadDeletionLagInMillis: 300000 +``` + ## topicAutoCreationConfig The `topicAutoCreationConfig` field allows you to control the automatic topic creation behavior at the namespace level: @@ -448,10 +474,9 @@ spec: name: test-tenant/testns connectionRef: name: test-pulsar-connection + backlogQuotaType: destination_storage backlogQuotaLimitSize: 1Gi - backlogQuotaLimitTime: 24h backlogQuotaRetentionPolicy: producer_request_hold - # backlogQuotaType: destination_storage bundles: 16 messageTTL: 1h @@ -544,16 +569,17 @@ If a namespace was already `Ready=True` before an operator upgrade introduced a Please note the following important points: -1. The fields `name` and `bundles` cannot be updated after the namespace is created. These are immutable properties of the namespace. +1. `bundles` is used only when the remote namespace is first created; changing it later does not rebundle an existing namespace. Changing `name` targets a different remote namespace and does not rename or delete the previously managed namespace. Create a new custom resource for migrations instead of changing either field in place. 2. Most fields can be modified after namespace creation, including: - - **Message and Quota Policies**: `backlogQuotaLimitSize`, `backlogQuotaLimitTime`, `messageTTL`, `retentionTime`, `retentionSize` + - **Message and Quota Policies**: `backlogQuotaLimitSize`, `backlogQuotaLimitTime`, `backlogQuotaRetentionPolicy`, `backlogQuotaType`, `messageTTL`, `retentionTime`, `retentionSize` + - **Tiered Storage**: `offloadThresholdTime`, `offloadThresholdSize`, `offloadPolicies` - **Consumer/Producer Limits**: `maxProducersPerTopic`, `maxConsumersPerTopic`, `maxConsumersPerSubscription` - **Rate Limiting**: `dispatchRate`, `subscriptionDispatchRate`, `replicatorDispatchRate`, `publishRate`, `subscribeRate` - **Schema Management**: `schemaCompatibilityStrategy`, `schemaValidationEnforced`, `isAllowAutoUpdateSchema`, `schemaAutoUpdateCompatibilityStrategy` (note: uses different enum values than `schemaCompatibilityStrategy`) - **Topic Management**: `topicAutoCreationConfig`, `compactionThreshold`, `inactiveTopicPolicies`, `subscriptionExpirationTime` - **Security**: `encryptionRequired`, `validateProducerName`, `subscriptionAuthMode` - - **Advanced Settings**: `persistencePolicies`, `antiAffinityGroup`, `properties` + - **Advanced Settings**: `persistencePolicies`, `deduplication`, `bookieAffinityGroup`, `replicationClusters`, `geoReplicationRefs`, `antiAffinityGroup`, `properties` 3. If you want to change the `connectionRef`, ensure that the new PulsarConnection resource exists and is properly configured. Changing the `connectionRef` can have significant implications: diff --git a/docs/pulsar_ns_isolation_policy.md b/docs/pulsar_ns_isolation_policy.md index 113b86c1..68e5a6db 100644 --- a/docs/pulsar_ns_isolation_policy.md +++ b/docs/pulsar_ns_isolation_policy.md @@ -74,6 +74,8 @@ The `OBSERVED_GENERATION` should increment, and `READY` should become `True` whe ## Delete A Pulsar ns-isolation-policy +`PulsarNSIsolationPolicy` does not expose `lifecyclePolicy`. Deleting the custom resource always asks Pulsar to delete the remote namespace-isolation policy, then removes the finalizer. + To delete a PulsarNSIsolationPolicy resource, use the following kubectl command: ```shell diff --git a/docs/pulsar_package.md b/docs/pulsar_package.md index 757c4715..9ecb0246 100644 --- a/docs/pulsar_package.md +++ b/docs/pulsar_package.md @@ -23,8 +23,8 @@ This table lists specifications available for the `PulsarPackage` resource. | Option | Description | Required or not | | ---|---|--- | -| `packageURL` | The package URL. The information you provide creates a URL for a package, in the format ://///. | Yes | -| `fileURL` | The file URL that can be download from. | Yes | +| `packageURL` | Pulsar package URL in the format `:////@`. | Yes | +| `fileURL` | URL from which the package content is downloaded. | Yes | | `connectionRef` | The reference to a PulsarConnection. | Yes | | `description` | The description of the package. | Optional | | `contact` | The contact information of the package. | Optional | @@ -75,8 +75,8 @@ Please note: - With `Always`: The operator will check the file content and update if changed - With `IfNotPresent` or `Never`: The operator will not update existing package content 2. To ensure getting the latest content, either: - - Use `@latest` tag in packageURL - - Set `syncPolicy: Always` + - Set `syncPolicy: Always` explicitly + - Use `@latest` in `packageURL` together with explicit `syncPolicy: Always` - Create a new package with a different version ## Delete PulsarPackage @@ -96,14 +96,16 @@ Please note that when you delete the package: The `syncPolicy` field determines how the operator handles package updates: -- `Always`: The operator will check and update the package content on each reconciliation if the content has changed. +- `Always`: The operator downloads the file and compares the managed checksum whenever this package is actively reconciled, including when its status is already Ready. - `IfNotPresent`: The operator will only upload the package if it doesn't exist in Pulsar. -- `Never`: The operator will never upload the package if it already exists, and will fail if the package doesn't exist. +- `Never`: The operator never uploads package content. The current implementation also marks the custom resource Ready when the remote package is absent; it does not enforce existence. If `syncPolicy` is not specified, the operator will: - Use `Always` if the packageURL contains `@latest` tag (e.g., `function://public/default/api-examples@latest`) - Use `IfNotPresent` for all other cases (e.g., `function://public/default/api-examples@v3.2.3.3`) +The Ready-resource shortcut checks whether `syncPolicy` is explicitly `Always`. For periodic checksum checks after the resource becomes Ready, set `syncPolicy: Always` rather than relying only on the implicit `@latest` default. + ## Managed Properties The operator automatically manages several properties for each package under the prefix `pulsarpackages.resource.streamnative.io`. These properties are used to track package state and ownership: @@ -121,10 +123,7 @@ The operator automatically manages several properties for each package under the These properties are automatically set and managed by the operator. When specifying custom properties in the `properties` field, any property with the prefix `pulsarpackages.resource.streamnative.io` will be ignored to prevent conflicts with the managed properties. -Example of viewing managed properties: -```shell -kubectl get pulsarpackage test-pulsar-package -o jsonpath='{.status.properties}' -``` +Managed properties are stored in Pulsar package metadata, not in `PulsarPackage.status`. Inspect them through the Pulsar package metadata API or CLI for the configured `packageURL`. ## Cloud Storage Support @@ -133,8 +132,9 @@ The operator supports downloading package files from various cloud storage provi - `s3://` - Amazon S3 and S3-compatible storage - `gs://` - Google Cloud Storage - `azblob://` - Azure Blob Storage -- `https://` - HTTPS URLs (default) -- `file://` - Local file system (for testing) +- `http://` - HTTP URLs +- `https://` - HTTPS URLs +- `file://` - Local path inside the operator container. The current reconciler removes the returned path after processing, so do not point it at a persistent source file. ### Amazon S3 diff --git a/docs/pulsar_permission.md b/docs/pulsar_permission.md index 3f51eaf3..61d45345 100644 --- a/docs/pulsar_permission.md +++ b/docs/pulsar_permission.md @@ -50,7 +50,7 @@ For more information about lifecycle policies, refer to the [PulsarResourceLifeC ## Create a Pulsar Permission -1. Define a permission by using the YAML file and save the YAML file `permission.yaml`.t +1. Define a permission by using the YAML file and save the YAML file as `permission.yaml`. This example grants the `ironman` with `consume`, `produce`, `functions`, and `sink` permissions on the namespace `test-tenant/testns`. ```yaml apiVersion: resource.streamnative.io/v1alpha1 @@ -102,7 +102,7 @@ Important notes about side effects of updating a Pulsar permission: 3. Adding new permissions doesn't automatically grant access to existing data. Users may need to reconnect or refresh their sessions to utilize new permissions. -4. Modifying the `resourceType` or `resourceName` effectively creates a new permission set rather than updating the existing one. The old permissions will remain unless explicitly removed. +4. Modifying `resourceType` or `resourceName` changes the permission target. The controller uses its managed-state annotation to revoke roles that this `PulsarPermission` previously managed on the old target before granting them on the new target. Permissions owned by other resources are left untouched. 5. If you want to change the `connectionRef`, ensure that the new PulsarConnection resource exists and is properly configured. Changing the `connectionRef` can have significant implications: @@ -165,4 +165,4 @@ pulsarpermission-sample-topic-error test-tenant/testn1 namespace ["iro kubectl -n test delete pulsarpermission.resource.streamnative.io test-pulsar-permission ``` -Please be noticed, when you delete the permission, the real permission will still exist if the `lifecyclePolicy` is `KeepAfterDeletion`. \ No newline at end of file +When `lifecyclePolicy` is `KeepAfterDeletion`, deleting the custom resource leaves the remote Pulsar permission in place. diff --git a/docs/pulsar_resource_lifecycle.md b/docs/pulsar_resource_lifecycle.md index 08e67a17..45aceb9b 100644 --- a/docs/pulsar_resource_lifecycle.md +++ b/docs/pulsar_resource_lifecycle.md @@ -11,6 +11,10 @@ The `PulsarResourceLifeCyclePolicy` can be set to one of two values: 1. `CleanUpAfterDeletion` 2. `KeepAfterDeletion` +The `lifecyclePolicy` field is implemented by `PulsarTenant`, `PulsarNamespace`, `PulsarTopic`, `PulsarPermission`, `PulsarGeoReplication`, `PulsarPackage`, `PulsarFunction`, `PulsarSink`, `PulsarSource`, `ComputeWorkspace`, `ComputeFlinkDeployment`, `Secret`, `ServiceAccount`, `ServiceAccountBinding`, `APIKey`, and `RoleBinding`. + +`PulsarConnection`, `PulsarNSIsolationPolicy`, and `StreamNativeCloudConnection` use their own deletion behavior and do not expose this field. + ### CleanUpAfterDeletion When set to `CleanUpAfterDeletion`, the Pulsar resource (such as a tenant, namespace, or topic) will be deleted from the Pulsar cluster when its corresponding Kubernetes custom resource is deleted. @@ -18,13 +22,16 @@ When set to `CleanUpAfterDeletion`, the Pulsar resource (such as a tenant, names Example: ```yaml -apiVersion: pulsar.streamnative.io/v1alpha1 +apiVersion: resource.streamnative.io/v1alpha1 kind: PulsarTenant metadata: - name: my-tenant + name: my-tenant-resource + namespace: default spec: + name: my-tenant + connectionRef: + name: my-pulsar-connection lifecyclePolicy: CleanUpAfterDeletion - <...> ``` In this example, when the Kubernetes custom resource for the Pulsar tenant is deleted, the corresponding Pulsar tenant will be deleted from the Pulsar cluster. @@ -36,13 +43,16 @@ When set to `KeepAfterDeletion`, the Pulsar resource will not be deleted from th Example: ```yaml -apiVersion: pulsar.streamnative.io/v1alpha1 +apiVersion: resource.streamnative.io/v1alpha1 kind: PulsarNamespace metadata: - name: my-namespace + name: my-namespace-resource + namespace: default spec: + name: my-tenant/my-namespace + connectionRef: + name: my-pulsar-connection lifecyclePolicy: KeepAfterDeletion - <...> ``` In this example, when the Kubernetes custom resource for the Pulsar namespace is deleted, the corresponding Pulsar namespace will not be deleted from the Pulsar cluster. The namespace will remain in the Pulsar cluster after the Kubernetes custom resource is deleted. @@ -59,20 +69,23 @@ When you need to delete the actual Pulsar resource (tenant, namespace, or topic) Simply delete the Kubernetes custom resource, and the corresponding Pulsar resource will be automatically deleted from the Pulsar cluster. ```shell - kubectl delete pulsartenant my-tenant + kubectl -n default delete pulsartenant my-tenant-resource ``` 2. **For resources with `KeepAfterDeletion` policy:** a. First, update the custom resource to change the policy to `CleanUpAfterDeletion`: ```yaml - apiVersion: pulsar.streamnative.io/v1alpha1 + apiVersion: resource.streamnative.io/v1alpha1 kind: PulsarTenant metadata: - name: my-tenant + name: my-tenant-resource + namespace: default spec: + name: my-tenant + connectionRef: + name: my-pulsar-connection lifecyclePolicy: CleanUpAfterDeletion - # ... other fields ... ``` Apply the updated resource: @@ -84,7 +97,7 @@ When you need to delete the actual Pulsar resource (tenant, namespace, or topic) b. Then, delete the Kubernetes custom resource: ```shell - kubectl delete pulsartenant my-tenant + kubectl -n default delete pulsartenant my-tenant-resource ``` This two-step process ensures that the Pulsar resource is deleted from both Kubernetes and the Pulsar cluster. @@ -113,11 +126,11 @@ For normal steady-state operation, the operator skips applying Pulsar API change After upgrading the operator, a new spec field may be introduced while existing custom resources remain `Ready=True` at the same generation. In that case, the new field is not applied to Pulsar until the resource is reconciled again. Recovery options are: -1. Update the custom resource spec or metadata so Kubernetes increments the resource generation, then wait for `Ready=True` again. +1. Update the custom resource **spec** so Kubernetes increments `metadata.generation`, then wait for `Ready=True` again. Metadata-only changes do not increment generation and do not bypass the ready-resource shortcut. 2. Temporarily enable `ALWAYS_UPDATE_PULSAR_RESOURCE=true` (Helm: `features.alwaysUpdatePulsarResource=true`) so the operator re-applies observed managed child resources even when they are already Ready. 3. Disable `ALWAYS_UPDATE_PULSAR_RESOURCE` after remediation unless continuous re-application is intentionally required. -Use the always-update option carefully. It can apply all observed managed resources on every reconciliation or resync and may increase Pulsar broker/admin API load. The `PulsarConnection` deletion guard is still preserved: a deleting connection is kept until its remaining managed child resources are removed. +The always-update feature applies to Pulsar resources reconciled through `PulsarConnection`; it does not control the independent StreamNative Cloud controllers. Use it carefully: it can apply all observed managed Pulsar resources on every reconciliation or resync and may increase Pulsar broker/admin API load. The `PulsarConnection` deletion guard is still preserved. ## Changing the Policy @@ -139,4 +152,4 @@ You can change the policy of a Pulsar resource by updating the `lifecyclePolicy` 6. **Documentation**: It's advisable to document any policy changes, especially in production environments, to maintain clarity on resource management strategies. -Always test policy changes in a non-production environment first to understand their full implications. \ No newline at end of file +Always test policy changes in a non-production environment first to understand their full implications. diff --git a/docs/pulsar_tenant.md b/docs/pulsar_tenant.md index 3d4971e9..8873aa5f 100644 --- a/docs/pulsar_tenant.md +++ b/docs/pulsar_tenant.md @@ -122,7 +122,7 @@ The `OBSERVED_GENERATION` should increment, and `READY` should become `True` whe Please note the following important points when updating a Pulsar tenant: -1. The `name` field is immutable and cannot be changed after the tenant is created. If you need to rename a tenant, you'll need to create a new one and migrate the resources. +1. Changing `name` targets a different remote tenant; the operator does not rename or delete the tenant previously referenced by this custom resource. Create a new custom resource and migrate explicitly instead of changing `name` in place. 2. Changes to `adminRoles` will affect who has administrative access to the tenant. Be cautious when modifying this field to avoid accidentally revoking necessary permissions. diff --git a/docs/pulsar_topic.md b/docs/pulsar_topic.md index dec462b4..e7008a30 100644 --- a/docs/pulsar_topic.md +++ b/docs/pulsar_topic.md @@ -22,6 +22,7 @@ The `PulsarTopic` resource defines a topic in a Pulsar cluster. It allows you to | `backlogQuotaLimitTime` | Time limit for message backlog. Messages older than this limit will be removed or handled according to the retention policy. | No | | `backlogQuotaLimitSize` | Size limit for message backlog. When the limit is reached, older messages will be removed or handled according to the retention policy. Use `"-1"` to allow an unlimited backlog (no producer throttling). | No | | `backlogQuotaRetentionPolicy` | Retention policy for messages when backlog quota is exceeded. Options: "producer_request_hold", "producer_exception", or "consumer_backlog_eviction". **Required whenever backlogQuotaLimitTime or backlogQuotaLimitSize is set.** | Conditional | +| `backlogQuotaType` | Selects the quota dimension: `destination_storage` (default, requires `backlogQuotaLimitSize`) or `message_age` (requires `backlogQuotaLimitTime`). | No | | `lifecyclePolicy` | Determines whether to keep or delete the Pulsar topic when the Kubernetes resource is deleted. Options: `CleanUpAfterDeletion`, `KeepAfterDeletion`. Default is `CleanUpAfterDeletion`. | No | | `schemaInfo` | Schema information for the topic. See [schemaInfo](#schemainfo) for more details. | No | | `geoReplicationRefs` | List of references to PulsarGeoReplication resources, used to enable geo-replication at the topic level. | No | @@ -106,6 +107,28 @@ spec: - Retention quota must exceed configured backlog quota for the topic - Consider the storage and cost implications before enabling infinite retention +## Backlog Quota Selection + +`backlogQuotaRetentionPolicy` is required whenever a backlog quota is configured. The selected quota type determines which limit is used: + +```yaml +# Size-based quota (default type) +spec: + backlogQuotaType: destination_storage + backlogQuotaLimitSize: 1Gi + backlogQuotaRetentionPolicy: producer_request_hold +``` + +```yaml +# Age-based quota +spec: + backlogQuotaType: message_age + backlogQuotaLimitTime: 24h + backlogQuotaRetentionPolicy: consumer_backlog_eviction +``` + +When `backlogQuotaType` is omitted, the operator uses `destination_storage`. Do not set only `backlogQuotaLimitTime` without also setting `backlogQuotaType: message_age`. + ## Create A Pulsar Topic 1. Define a topic named `persistent://test-tenant/testns/topic123` by using the YAML file and save the YAML file `topic.yaml`. @@ -128,7 +151,7 @@ spec: # maxUnAckedMessagesPerSubscription: # retentionTime: 20h # or "-1" for infinite retention time # retentionSize: 2Gi # or "-1" for infinite retention size -# backlogQuotaLimitTime: 24h +# backlogQuotaType: destination_storage # backlogQuotaLimitSize: 1Gi # backlogQuotaRetentionPolicy: producer_request_hold # lifecyclePolicy: CleanUpAfterDeletion @@ -177,11 +200,13 @@ You can update the topic policies by editing the `topic.yaml` file and then appl Important notes when updating a Pulsar topic: -1. The fields `name` and `persistent` are immutable and cannot be updated after the topic is created. +1. Changing `name` or changing the effective topic domain (`persistent://` versus `non-persistent://`) targets a different remote topic; the operator does not rename or delete the old topic. The domain embedded in `name` takes precedence over `persistent`. Create a new custom resource for topic migrations instead of changing identity fields in place. -2. Other fields such as `partitions`, `maxProducers`, `maxConsumers`, `messageTTL`, `retentionTime`, `retentionSize`, `backlogQuotaLimitTime`, `backlogQuotaLimitSize`, `backlogQuotaRetentionPolicy`, `backlogQuotaType`, `compactionThreshold`, `persistencePolicies`, `delayedDelivery`, `dispatchRate`, `publishRate`, `inactiveTopicPolicies`, `subscribeRate`, `subscriptionDispatchRate`, `replicatorDispatchRate`, `maxMessageSize`, `maxConsumersPerSubscription`, `maxSubscriptionsPerTopic`, `maxUnAckedMessagesPerConsumer`, `maxUnAckedMessagesPerSubscription`, `deduplication`, `deduplicationSnapshotInterval`, `offloadPolicies`, `autoSubscriptionCreation`, `schemaValidationEnforced`, `schemaCompatibilityStrategy`, and `properties` can be modified. Clearing an optional field (removing it from the spec or setting it to null) removes the corresponding topic-level policy and lets the namespace-level default take effect again. +2. `partitions` can increase the partition count of an existing partitioned topic. Pulsar does not support decreasing partitions or converting between partitioned and non-partitioned topics. -3. If you want to change the `connectionRef`, ensure that the new PulsarConnection resource exists and is properly configured. Changing the `connectionRef` can have significant implications: +3. Other fields such as `maxProducers`, `maxConsumers`, `messageTTL`, `retentionTime`, `retentionSize`, `backlogQuotaLimitTime`, `backlogQuotaLimitSize`, `backlogQuotaRetentionPolicy`, `backlogQuotaType`, `compactionThreshold`, `persistencePolicies`, `delayedDelivery`, `dispatchRate`, `publishRate`, `inactiveTopicPolicies`, `subscribeRate`, `subscriptionDispatchRate`, `replicatorDispatchRate`, `maxMessageSize`, `maxConsumersPerSubscription`, `maxSubscriptionsPerTopic`, `maxUnAckedMessagesPerConsumer`, `maxUnAckedMessagesPerSubscription`, `deduplication`, `deduplicationSnapshotInterval`, `offloadPolicies`, `autoSubscriptionCreation`, `schemaValidationEnforced`, `schemaCompatibilityStrategy`, `replicationClusters`, `geoReplicationRefs`, and `properties` can be modified. Clearing a managed optional policy removes the topic-level override and lets the namespace-level default take effect again. + +4. If you want to change the `connectionRef`, ensure that the new PulsarConnection resource exists and is properly configured. Changing the `connectionRef` can have significant implications: - If the new PulsarConnection refers to the same Pulsar cluster (i.e., the admin and broker URLs are the same), the topic will remain in its original location. The operator will simply use the new connection details to manage the existing topic. @@ -189,17 +214,17 @@ Important notes when updating a Pulsar topic: Be cautious when changing the `connectionRef`, especially if it points to a new cluster, as this can lead to topic duplication across clusters. Always verify the intended behavior and manage any cleanup of the old topic if necessary. -4. Changes to `lifecyclePolicy` will only affect what happens when the PulsarTopic resource is deleted, not the current state of the topic. +5. Changes to `lifecyclePolicy` will only affect what happens when the PulsarTopic resource is deleted, not the current state of the topic. -5. Be cautious when updating topic policies, as changes may affect existing producers and consumers. It's recommended to test changes in a non-production environment first. +6. Be cautious when updating topic policies, as changes may affect existing producers and consumers. It's recommended to test changes in a non-production environment first. -6. After applying changes, you can check the status of the update using: +7. After applying changes, you can check the status of the update using: ```shell kubectl -n test get pulsartopic.resource.streamnative.io test-pulsar-topic123 ``` The `OBSERVED_GENERATION` should increment, and `READY` should become `True` when the update is complete. -7. Updating the `schemaInfo` field may have implications for existing producers and consumers. Ensure that any schema changes adhere to Pulsar's schema compatibility strategies. For more information on schema evolution and compatibility, refer to the [Pulsar Schema Evolution and Compatibility](https://pulsar.apache.org/docs/schema-understand#schema-evolution) documentation. +8. Updating the `schemaInfo` field may have implications for existing producers and consumers. Ensure that any schema changes adhere to Pulsar's schema compatibility strategies. For more information on schema evolution and compatibility, refer to the [Pulsar Schema Evolution and Compatibility](https://pulsar.apache.org/docs/schema-understand#schema-evolution) documentation. ## Delete A PulsarTopic @@ -372,22 +397,41 @@ The `offloadPolicies` field configures topic-level tiered storage offload, overr | Field | Description | Type | |-------|-------------|------| -| `managedLedgerOffloadDriver` | Offload driver name. Supported values include `aws-s3`, `gcs`, `azureblob`, `filesystem`. | string | -| `managedLedgerOffloadMaxThreads` | Maximum number of threads used by the offloader for this topic. | int | +| `offloadersDirectory` | Directory in the broker container from which offloader implementations are loaded. | string | +| `managedLedgerOffloadDriver` | Offload driver name understood by the Pulsar broker. | string | +| `managedLedgerOffloadMaxThreads` | Maximum offload worker threads. | int | +| `managedLedgerOffloadReadThreads` | Maximum threads used for reading offloaded data. | int | +| `managedLedgerOffloadPrefetchRounds` | Number of read-ahead/prefetch rounds for offloaded data. | int | +| `managedLedgerOffloadThresholdInSeconds` | Age threshold in seconds after which closed ledgers are eligible for offload. | int64 | | `managedLedgerOffloadThresholdInBytes` | Size threshold in bytes after which closed ledgers are offloaded to tiered storage. Use `0` to trigger offload as soon as a ledger is closed (i.e. immediately). Use `-1` to disable size-based auto-offload. | int64 | | `managedLedgerOffloadDeletionLagInMillis` | Time in milliseconds to wait after a ledger is successfully offloaded before deleting it from BookKeeper. For example, `60000` keeps the BookKeeper copy for 1 minute after offload completes. | int64 | +| `managedLedgerOffloadedReadPriority` | Read priority between BookKeeper and tiered storage, interpreted by the broker. | string | +| `managedLedgerExtraConfigurations` | Free-form managed-ledger/offloader configuration. | map[string]string | | `managedLedgerOffloadAutoTriggerSizeThresholdBytes` | Legacy alias for the auto-trigger size threshold. Prefer `managedLedgerOffloadThresholdInBytes`. | int64 | -| `s3ManagedLedgerOffloadBucket` | S3 bucket name for the `aws-s3` driver. | string | | `s3ManagedLedgerOffloadRegion` | S3 region for the `aws-s3` driver. | string | +| `s3ManagedLedgerOffloadBucket` | S3 bucket name for the `aws-s3` driver. | string | | `s3ManagedLedgerOffloadServiceEndpoint` | S3 service endpoint URL (optional, e.g. for S3-compatible storage). | string | +| `s3ManagedLedgerOffloadMaxBlockSizeInBytes` | Maximum S3 multipart-upload block size. | int | +| `s3ManagedLedgerOffloadReadBufferSizeInBytes` | S3 offloaded-read buffer size. | int | | `s3ManagedLedgerOffloadCredentialId` | Access key ID for the `aws-s3` driver. Prefer IAM roles in production. | string | | `s3ManagedLedgerOffloadCredentialSecret` | Secret access key for the `aws-s3` driver. Prefer IAM roles in production. | string | | `s3ManagedLedgerOffloadRole` | IAM role ARN to assume when offloading. | string | | `s3ManagedLedgerOffloadRoleSessionName` | Session name used when assuming the IAM role. | string | -| `offloadersDirectory` | Directory inside the broker container where offloader jars are loaded from. | string | +| `gcsManagedLedgerOffloadRegion` | GCS region. | string | +| `gcsManagedLedgerOffloadBucket` | GCS bucket name. | string | +| `gcsManagedLedgerOffloadMaxBlockSizeInBytes` | Maximum GCS write block size. | int | +| `gcsManagedLedgerOffloadReadBufferSizeInBytes` | GCS offloaded-read buffer size. | int | +| `gcsManagedLedgerOffloadServiceAccountKeyFile` | Service-account key file path available to brokers. | string | +| `fileSystemProfilePath` | Filesystem offloader profile path. | string | +| `fileSystemURI` | Filesystem offload destination URI. | string | +| `managedLedgerOffloadBucket` | Provider-neutral offload bucket/container. | string | +| `managedLedgerOffloadRegion` | Provider-neutral offload region. | string | +| `managedLedgerOffloadServiceEndpoint` | Provider-neutral service endpoint. | string | +| `managedLedgerOffloadMaxBlockSizeInBytes` | Provider-neutral maximum write block size. | int | +| `managedLedgerOffloadReadBufferSizeInBytes` | Provider-neutral offloaded-read buffer size. | int | | `managedLedgerOffloadDriverMetadata` | Free-form driver-specific metadata as key/value pairs. | map[string]string | -> **Note:** Bucket, region, credentials and driver configuration are normally provided at the broker / cluster level (for example via the `sn-platform` chart's `broker.offload` settings). At the topic level you usually only need the policy fields that change behavior — `managedLedgerOffloadThresholdInBytes` and `managedLedgerOffloadDeletionLagInMillis` — and can leave the driver/bucket fields empty so the broker defaults are inherited. +> **Note:** Bucket, region, credentials and driver configuration are normally provided at the broker or cluster level. At topic level you usually only need fields that change policy behavior, such as `managedLedgerOffloadThresholdInBytes` and `managedLedgerOffloadDeletionLagInMillis`. Credential fields are stored directly in the custom resource; prefer broker-side workload identity, roles, or mounted credentials. **Example — aggressive immediate offload for a single topic:** @@ -410,6 +454,7 @@ spec: managedLedgerOffloadDeletionLagInMillis: 60000 # equivalent to offloadDeletionLagSeconds=60 # Allow unlimited backlog without throttling producers. + backlogQuotaType: destination_storage backlogQuotaLimitSize: "-1" # equivalent to backlogQuotaLimitBytes=-1 backlogQuotaRetentionPolicy: producer_request_hold ``` diff --git a/docs/rolebinding.md b/docs/rolebinding.md index 0d74a90c..12a6336e 100644 --- a/docs/rolebinding.md +++ b/docs/rolebinding.md @@ -30,14 +30,14 @@ spec: | Field | Type | Description | Required | | --- | --- | --- | --- | -| `spec.apiServerRef` | [LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#localobjectreference-v1-core) | Reference to a StreamNativeCloudConnection in the same namespace. | Yes | +| `spec.apiServerRef` | `corev1.LocalObjectReference` | Reference to a StreamNativeCloudConnection in the same namespace. | Yes | | `spec.lifecyclePolicy` | string | Whether to delete the remote role binding or keep it when the Kubernetes resource is deleted. Defaults to cleanup when omitted. | No | | `spec.clusterRole` | string | The name of the `ClusterRole` to be granted. See [Predefined RBAC Roles](https://docs.streamnative.io/cloud/security/access/rbac/predefined-rbac-roles). | Yes | | `spec.users` | []string | A list of user emails that will be granted the role. | No | | `spec.identityPools` | []string | A list of identity pools that will be granted the role. | No | | `spec.serviceAccounts` | []string | A list of service accounts that will be granted the role. | No | | `spec.cel` | string | An optional CEL (Common Expression Language) expression for conditional role binding. | No | -| `spec.srnOrganization` | []string | The organization scope for the SRN. | No | +| `spec.srnOrganization` | []string | Present in the CRD, but ignored by the current converter. Resource names always use `StreamNativeCloudConnection.spec.organization`. | No | | `spec.srnInstance` | []string | The Pulsar instance scope for the SRN. | No | | `spec.srnCluster` | []string | The cluster scope for the SRN. | No | | `spec.srnTenant` | []string | The tenant scope for the SRN. | No | @@ -55,8 +55,8 @@ spec: | --- | --- | --- | | `status.conditions` | []Condition | Represents the latest available observations of the `RoleBinding`'s state. | | `status.observedGeneration`| int64 | The last generation of the resource that was observed by the controller. | -| `status.failedClusters` | []string | A list of clusters where applying the role binding failed. | -| `status.syncedClusters` | map[string]string | A map of clusters where the role binding has been successfully synced. The key is the cluster name and the value is the sync status. | +| `status.failedClusters` | []string | Reserved field; the current controller does not populate it. | +| `status.syncedClusters` | map[string]string | Reserved field; the current controller does not populate it. | ## Conditional Role Bindings @@ -67,7 +67,7 @@ While basic role bindings associate a role with a subject, conditional role bind You can scope permissions by specifying one or more `spec.srn*` fields. This is the simplest way to limit a role to specific resources like tenants, namespaces, or topics. -The SRN fields are provided as arrays to allow granting the same role across multiple resources of the same type in a single `RoleBinding`. +SRN arrays are combined **by index**, not as a Cartesian product. The controller creates one resource-name entry for each index up to the longest array; missing values at an index remain empty. Repeat shared scope values so every intended entry is complete. For example, to grant the `tenant-admin` role to a user for two specific tenants (`finance` and `marketing`) within an instance: @@ -86,11 +86,12 @@ spec: # Define the scope of this binding srnInstance: - "my-cloud-instance" + - "my-cloud-instance" srnTenant: - "finance" - "marketing" ``` -The controller will create a separate binding in the cloud for each combination of SRN values provided. +This produces two resource-name entries: `(my-cloud-instance, finance)` and `(my-cloud-instance, marketing)`. `srnOrganization` does not participate; the connection's organization is used for both. ### Using CEL Expressions diff --git a/docs/secret.md b/docs/secret.md index c394a4d6..577923b0 100644 --- a/docs/secret.md +++ b/docs/secret.md @@ -12,13 +12,13 @@ The `Secret` resource defines a secret in StreamNative Cloud. It allows you to c | `lifecyclePolicy` | Whether to delete the remote secret or keep it when the Kubernetes resource is deleted. Defaults to cleanup when omitted. | No | | `instanceName` | Name of the instance this secret is for (e.g. pulsar-instance) | No | | `location` | Location of the secret | No | -| `data` | Secret data, values should be base64 encoded | No* | -| `secretRef` | Reference to a Kubernetes secret. When secretRef is set, it will be used to fetch the secret data, and data field will be ignored | No* | +| `data` | Secret data as plain string values passed to the StreamNative Cloud API. | No* | +| `secretRef` | Reference to a Kubernetes Secret whose decoded `data` values are copied into this resource's `spec.data`. | No* | | `poolMemberName` | Pool member to deploy the secret | No | | `tolerations` | Tolerations for the secret | No | | `type` | Used to facilitate programmatic handling of secret data | No | -*Note: Either `data` or `secretRef` must be specified. +*Note: Either `data` or `secretRef` must be specified. When both are present, `data` takes precedence. ### KubernetesSecretReference Structure @@ -34,7 +34,20 @@ The `Secret` resource defines a secret in StreamNative Cloud. It allows you to c | `key` | Taint key that the toleration applies to. Empty means match all taint keys | No | | `operator` | Represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal | No | | `value` | Taint value the toleration matches to | No | -| `effect` | Indicates the taint effect to match. Empty means match all taint effects | No | +| `effect` | Taint effect to match. Supported controller values include `NoSchedule`, `PreferNoSchedule`, `NoCleanup`, and `NoConnect`; empty matches all effects. | No | + +### Kubernetes Secret Reference Behavior + +On the first reconciliation with an empty `spec.data`, the controller reads the referenced Kubernetes Secret, decodes each byte value to a string, copies the result and Secret type into the custom resource spec, then sends that copied data to StreamNative Cloud. + +This is a snapshot, not a live reference. After `spec.data` has been populated, later changes to the referenced Kubernetes Secret are not copied automatically because direct data takes precedence. Remove `spec.data` explicitly to import the reference again, for example with a JSON Patch: + +```shell +kubectl -n default patch secret.resource.streamnative.io test-secret \ + --type=json -p='[{"op":"remove","path":"/spec/data"}]' +``` + +Because copied values are stored in the custom resource, protect access to both the source Kubernetes Secret and the `Secret.resource.streamnative.io` object. ## Status @@ -106,6 +119,8 @@ You can update the secret by modifying the YAML file and reapplying it. Most fie - Kubernetes secret reference - Tolerations +Changing `secretRef` alone does not refresh copied data. Remove `spec.data` as shown above so the next reconciliation reads the new reference. + After applying changes, verify the status to ensure the secret is configured properly. ## Delete Secret diff --git a/docs/serviceaccount.md b/docs/serviceaccount.md index c8698b21..bb04eb40 100644 --- a/docs/serviceaccount.md +++ b/docs/serviceaccount.md @@ -19,7 +19,7 @@ spec: | Field | Type | Description | Required | | --- | --- | --- | --- | -| `spec.apiServerRef` | [LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#localobjectreference-v1-core) | Reference to a StreamNativeCloudConnection in the same namespace | Yes | +| `spec.apiServerRef` | `corev1.LocalObjectReference` | Reference to a StreamNativeCloudConnection in the same namespace | Yes | | `spec.lifecyclePolicy` | string | Whether to delete the remote service account or keep it when the Kubernetes resource is deleted. Defaults to cleanup when omitted. | No | ## Status @@ -54,7 +54,7 @@ Service accounts provide a way to manage access to StreamNative Cloud resources To create a service account, you need: 1. A StreamNativeCloudConnection resource configured with valid credentials -2. An instance in which to create the service account +2. `spec.organization` set on that connection; the remote service account is created in that organization ```yaml apiVersion: resource.streamnative.io/v1alpha1 diff --git a/docs/serviceaccountbinding.md b/docs/serviceaccountbinding.md index 1b899149..5193feb8 100644 --- a/docs/serviceaccountbinding.md +++ b/docs/serviceaccountbinding.md @@ -104,4 +104,10 @@ For more detailed status information: kubectl describe serviceaccountbinding app-service-binding -n default ``` +### Update Limitation + +The current reconciler creates one remote binding per `poolMemberRefs` entry, named `..`. Existing remote bindings are treated as already correct: updates are not sent, and removing an entry from `poolMemberRefs` does not delete its previously created remote binding. + +Deletion cleans up only entries still present in the custom resource at deletion time. To change or remove pool-member bindings safely, delete the original resource before applying a replacement, or clean up stale remote bindings directly. + Set `spec.lifecyclePolicy: KeepAfterDeletion` if you want the operator to stop managing the remote bindings without deleting them from StreamNative Cloud. diff --git a/docs/streamnative_cloud_connection.md b/docs/streamnative_cloud_connection.md index 92aaa77c..d544e9c0 100644 --- a/docs/streamnative_cloud_connection.md +++ b/docs/streamnative_cloud_connection.md @@ -8,12 +8,12 @@ The `StreamNativeCloudConnection` resource defines a connection to the StreamNat | Field | Description | Required | |--------------------------------|-----------------------------------------------------------------------------------------------------------------|----------| -| `server` | The URL of the API server | Yes | +| `server` | URL of the API server. Defaults to `https://api.streamnative.cloud`. | No | | `auth.credentialsRef` | Reference to the service account credentials secret | Yes | -| `logs.serviceUrl` | URL of the logging service. Required if logs configuration is specified. | No* | -| `organization` | The organization to use in the API server. If not specified, the connection name will be used | No | +| `logs.serviceUrl` | Logging service URL. Required by the CRD when `logs` is present, but not consumed by the current connection or resource clients. | Conditional | +| `organization` | Organization namespace used by StreamNative Cloud resource clients. Required before reconciling any dependent cloud resource. | Conditional | -*Note: If `logs` configuration is specified, `serviceUrl` becomes required. +The connection health check itself does not require `organization`, but `ComputeWorkspace`, `ComputeFlinkDeployment`, `Secret`, `ServiceAccount`, `ServiceAccountBinding`, `APIKey`, and `RoleBinding` controllers reject an empty value. There is no fallback to the Kubernetes resource name in the current implementation. ## Status @@ -102,6 +102,8 @@ You can update the connection by modifying the YAML file and reapplying it. Most - Organization - Credentials reference +`logs` is currently stored by Kubernetes but does not affect controller behavior. + After applying changes, verify the status to ensure the connection is working properly. ## Delete Connection @@ -112,4 +114,4 @@ To delete a StreamNativeCloudConnection resource: kubectl delete streamnativecloudconnection test-connection ``` -Note that deleting the connection will affect any resources that depend on it, such as ComputeWorkspaces or ComputeFlinkDeployments. +The controller keeps its finalizer while dependent cloud resources in the same namespace still reference this connection. Delete or repoint those resources first. This includes direct references and `ComputeFlinkDeployment` references inherited through `ComputeWorkspace`. diff --git a/tests/README.md b/tests/README.md index ccab3809..061458ed 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,8 +1,8 @@ -# tests +# Integration tests -tests is an individul module beside pulsar resources operator +`tests` is a separate Go module included by the repository's `go.work` file. -`go mod tidy` to download modules for tests +Run `go mod download` from this directory to prefetch its dependencies without rewriting module files. ## Requirements @@ -12,7 +12,9 @@ tests is an individul module beside pulsar resources operator ## Run tests -`ginkgo --trace --progress ./operator` +```bash +go run github.com/onsi/ginkgo/v2/ginkgo --trace ./operator +``` Optionally, if you have an external pulsar cluster (e.g. deployed on minikube) and you want to test the operator without deploying it in kubernetes: @@ -20,7 +22,7 @@ Optionally, if you have an external pulsar cluster (e.g. deployed on minikube) a ```bash make install -go run . +make run ``` 2. In another terminal run @@ -33,7 +35,8 @@ export NAMESPACE=pulsar # your pulsar broker name export BROKER_NAME=pulsar-mini # your pulsar proxy url -export PROXY_URL=http://localhost:80 +export PROXY_URL=pulsar://localhost:6650 -ginkgo --trace --progress ./operator +cd tests +go run github.com/onsi/ginkgo/v2/ginkgo --trace ./operator ``` From 81c4fa040b5fcdd2e0e21ca5d2e65003eafe4a5d Mon Sep 17 00:00:00 2001 From: Rui Fu Date: Wed, 29 Jul 2026 11:15:43 +0800 Subject: [PATCH 2/2] fix: address review comments --- charts/pulsar-resources-operator/Chart.yaml | 2 +- charts/pulsar-resources-operator/README.md | 2 +- .../templates/deployment.yaml | 29 +++++++++-------- .../tests/deployment_test.yaml | 32 +++++++++++++++++++ docs/rolebinding.md | 4 +-- 5 files changed, 51 insertions(+), 18 deletions(-) diff --git a/charts/pulsar-resources-operator/Chart.yaml b/charts/pulsar-resources-operator/Chart.yaml index 534ae86a..7bea24bb 100644 --- a/charts/pulsar-resources-operator/Chart.yaml +++ b/charts/pulsar-resources-operator/Chart.yaml @@ -30,7 +30,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.19.0 +version: v0.19.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/pulsar-resources-operator/README.md b/charts/pulsar-resources-operator/README.md index 521e4f0b..2bfe1410 100644 --- a/charts/pulsar-resources-operator/README.md +++ b/charts/pulsar-resources-operator/README.md @@ -2,7 +2,7 @@ Pulsar Resources Operator Helm chart for Pulsar Resources Management on Kubernetes -![Version: 0.19.0](https://img.shields.io/badge/Version-0.19.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.19.0](https://img.shields.io/badge/AppVersion-v0.19.0-informational?style=flat-square) +![Version: v0.19.0](https://img.shields.io/badge/Version-v0.19.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.19.0](https://img.shields.io/badge/AppVersion-v0.19.0-informational?style=flat-square) ## Installing the Chart diff --git a/charts/pulsar-resources-operator/templates/deployment.yaml b/charts/pulsar-resources-operator/templates/deployment.yaml index 0c82a8f4..287edee6 100644 --- a/charts/pulsar-resources-operator/templates/deployment.yaml +++ b/charts/pulsar-resources-operator/templates/deployment.yaml @@ -13,6 +13,7 @@ # limitations under the License. # +{{- $useGCSKey := and .Values.cloudStorage.gcs.enabled (not .Values.cloudStorage.gcs.serviceAccount.useWorkloadIdentity) }} apiVersion: apps/v1 kind: Deployment metadata: @@ -42,13 +43,16 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} spec: - {{- if or .Values.cloudStorage.gcs.enabled .Values.cloudStorage.s3.enabled .Values.cloudStorage.azure.enabled }} + {{- if or $useGCSKey .Values.extraVolumes }} volumes: - {{- if and .Values.cloudStorage.gcs.enabled (not .Values.cloudStorage.gcs.serviceAccount.useWorkloadIdentity) }} + {{- if $useGCSKey }} - name: gcs-credentials secret: secretName: {{ .Values.cloudStorage.gcs.serviceAccount.key.secretName }} {{- end }} + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- end }} imagePullSecrets: {{- toYaml .Values.imagePullSecrets | nindent 8 }} @@ -116,11 +120,16 @@ spec: - containerPort: 8443 name: metrics protocol: TCP - {{- if and .Values.cloudStorage.gcs.enabled (not .Values.cloudStorage.gcs.serviceAccount.useWorkloadIdentity) }} + {{- if or $useGCSKey .Values.extraVolumeMounts }} volumeMounts: - - name: gcs-credentials - mountPath: {{ .Values.cloudStorage.gcs.serviceAccount.key.mountPath }} - readOnly: true + {{- if $useGCSKey }} + - name: gcs-credentials + mountPath: {{ .Values.cloudStorage.gcs.serviceAccount.key.mountPath }} + readOnly: true + {{- end }} + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 10 }} + {{- end }} {{- end }} livenessProbe: httpGet: @@ -130,10 +139,6 @@ spec: periodSeconds: 20 resources: {{- toYaml .Values.resources | nindent 10 }} - {{- if .Values.extraVolumeMounts }} - volumeMounts: - {{- toYaml .Values.extraVolumeMounts | nindent 10 }} - {{- end }} nodeSelector: {{- toYaml .Values.nodeSelector | nindent 8 }} affinity: @@ -141,7 +146,3 @@ spec: tolerations: {{- toYaml .Values.tolerations | nindent 8 }} terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} - {{- if .Values.extraVolumes }} - volumes: - {{- toYaml .Values.extraVolumes | nindent 8 }} - {{- end }} diff --git a/charts/pulsar-resources-operator/tests/deployment_test.yaml b/charts/pulsar-resources-operator/tests/deployment_test.yaml index 4e2f6f07..e2100845 100644 --- a/charts/pulsar-resources-operator/tests/deployment_test.yaml +++ b/charts/pulsar-resources-operator/tests/deployment_test.yaml @@ -87,3 +87,35 @@ tests: - equal: path: spec.template.spec.containers[0].resources.requests.memory value: 128Mi + + - it: should merge GCS credentials and extra volumes + set: + cloudStorage.gcs.enabled: true + cloudStorage.gcs.serviceAccount.useWorkloadIdentity: false + extraVolumeMounts: + - name: connection-credentials + mountPath: /var/secrets/connection + readOnly: true + extraVolumes: + - name: connection-credentials + secret: + secretName: connection-credentials + asserts: + - equal: + path: spec.template.spec.volumes + value: + - name: gcs-credentials + secret: + secretName: gcs-credentials + - name: connection-credentials + secret: + secretName: connection-credentials + - equal: + path: spec.template.spec.containers[0].volumeMounts + value: + - name: gcs-credentials + mountPath: /var/secrets/google + readOnly: true + - name: connection-credentials + mountPath: /var/secrets/connection + readOnly: true diff --git a/docs/rolebinding.md b/docs/rolebinding.md index 12a6336e..58ead1d6 100644 --- a/docs/rolebinding.md +++ b/docs/rolebinding.md @@ -37,7 +37,7 @@ spec: | `spec.identityPools` | []string | A list of identity pools that will be granted the role. | No | | `spec.serviceAccounts` | []string | A list of service accounts that will be granted the role. | No | | `spec.cel` | string | An optional CEL (Common Expression Language) expression for conditional role binding. | No | -| `spec.srnOrganization` | []string | Present in the CRD, but ignored by the current converter. Resource names always use `StreamNativeCloudConnection.spec.organization`. | No | +| `spec.srnOrganization` | []string | Values are not copied into resource names; organization always comes from `StreamNativeCloudConnection.spec.organization`. Its array length still controls the number of entries, so unmatched indexes emit organization-only entries. | No | | `spec.srnInstance` | []string | The Pulsar instance scope for the SRN. | No | | `spec.srnCluster` | []string | The cluster scope for the SRN. | No | | `spec.srnTenant` | []string | The tenant scope for the SRN. | No | @@ -91,7 +91,7 @@ spec: - "finance" - "marketing" ``` -This produces two resource-name entries: `(my-cloud-instance, finance)` and `(my-cloud-instance, marketing)`. `srnOrganization` does not participate; the connection's organization is used for both. +This produces two resource-name entries: `(my-cloud-instance, finance)` and `(my-cloud-instance, marketing)`. `srnOrganization` values are not copied; the connection's organization is used for both. Its array length still participates in the entry count. Do not make it longer than the narrower SRN arrays: unmatched indexes become organization-only entries, and setting it alone creates only organization-wide entries. ### Using CEL Expressions