From 0ed07f554af51fc04bf54ab7687d795772b921c7 Mon Sep 17 00:00:00 2001 From: Anurag Mittal Date: Tue, 17 Dec 2024 20:15:53 +0100 Subject: [PATCH 1/4] COSI-74: send-error-for-brown-field-use-case If the bucket already exists, users should add them through brownfield workflow. It is not expected in COSI specs to silently pass if bucket already owned by the IAM user or account used for create bucket request. --- pkg/driver/provisioner_server_impl.go | 6 ------ pkg/driver/provisioner_server_impl_test.go | 10 ---------- 2 files changed, 16 deletions(-) diff --git a/pkg/driver/provisioner_server_impl.go b/pkg/driver/provisioner_server_impl.go index 1b8f0ed9..d5cba7cc 100644 --- a/pkg/driver/provisioner_server_impl.go +++ b/pkg/driver/provisioner_server_impl.go @@ -129,16 +129,10 @@ func (s *ProvisionerServer) DriverCreateBucket(ctx context.Context, err = s3Client.CreateBucket(ctx, bucketName, *s3Params) if err != nil { var bucketAlreadyExists *s3types.BucketAlreadyExists - var bucketOwnedByYou *s3types.BucketAlreadyOwnedByYou if errors.As(err, &bucketAlreadyExists) { klog.V(c.LvlInfo).InfoS("Bucket already exists", "bucketName", bucketName) return nil, status.Errorf(codes.AlreadyExists, "Bucket already exists: %s", bucketName) - } else if errors.As(err, &bucketOwnedByYou) { - klog.V(c.LvlInfo).InfoS("Bucket already exists and is owned by you", "bucketName", bucketName) - return &cosiapi.DriverCreateBucketResponse{ - BucketId: bucketName, - }, nil } else { klog.ErrorS(err, "Failed to create bucket", "bucketName", bucketName) return nil, status.Error(codes.Internal, "Failed to create bucket") diff --git a/pkg/driver/provisioner_server_impl_test.go b/pkg/driver/provisioner_server_impl_test.go index 82af5866..6775c5fa 100644 --- a/pkg/driver/provisioner_server_impl_test.go +++ b/pkg/driver/provisioner_server_impl_test.go @@ -271,16 +271,6 @@ var _ = Describe("ProvisionerServer DriverCreateBucket", Ordered, func() { Expect(status.Code(err)).To(Equal(codes.AlreadyExists)) }) - It("should return success if bucket already owned by you", func(ctx SpecContext) { - mockS3.CreateBucketFunc = func(ctx context.Context, input *s3.CreateBucketInput, _ ...func(*s3.Options)) (*s3.CreateBucketOutput, error) { - return nil, &types.BucketAlreadyOwnedByYou{} - } - - resp, err := provisioner.DriverCreateBucket(ctx, request) - Expect(err).To(BeNil()) - Expect(resp.BucketId).To(Equal(testBucketName)) - }) - It("should return Internal error for other S3 errors", func(ctx SpecContext) { mockS3.CreateBucketFunc = func(ctx context.Context, input *s3.CreateBucketInput, _ ...func(*s3.Options)) (*s3.CreateBucketOutput, error) { return nil, errors.New("SomeOtherError") From 0df0e545dcc6aecdb63268e83173b43a46c5610c Mon Sep 17 00:00:00 2001 From: Anurag Mittal Date: Wed, 18 Dec 2024 09:31:44 +0100 Subject: [PATCH 2/4] COSI-75: add-brownfield-custom-resources --- cosi-examples/brownfield/bucket.yaml | 16 ++++++++++++++++ cosi-examples/brownfield/bucketaccess.yaml | 10 ++++++++++ cosi-examples/brownfield/bucketaccessclass.yaml | 10 ++++++++++ cosi-examples/brownfield/bucketclaim.yaml | 10 ++++++++++ cosi-examples/brownfield/bucketclass.yaml | 10 ++++++++++ 5 files changed, 56 insertions(+) create mode 100644 cosi-examples/brownfield/bucket.yaml create mode 100644 cosi-examples/brownfield/bucketaccess.yaml create mode 100644 cosi-examples/brownfield/bucketaccessclass.yaml create mode 100644 cosi-examples/brownfield/bucketclaim.yaml create mode 100644 cosi-examples/brownfield/bucketclass.yaml diff --git a/cosi-examples/brownfield/bucket.yaml b/cosi-examples/brownfield/bucket.yaml new file mode 100644 index 00000000..76969896 --- /dev/null +++ b/cosi-examples/brownfield/bucket.yaml @@ -0,0 +1,16 @@ +apiVersion: objectstorage.k8s.io/v1alpha1 +kind: Bucket +metadata: + name: brownfield-bucket # should be same as bucket name + namespace: scality-object-storage +spec: + bucketClaim: {} + bucketClassName: brownfield-bucket-class + driverName: cosi.scality.com + deletionPolicy: Retain + existingBucketID: brownfield-bucket # name of pre-existing bucket in S3 + parameters: + objectStorageSecretName: s3-secret-for-cosi + objectStorageSecretNamespace: default + protocols: + - S3 diff --git a/cosi-examples/brownfield/bucketaccess.yaml b/cosi-examples/brownfield/bucketaccess.yaml new file mode 100644 index 00000000..e0d7796c --- /dev/null +++ b/cosi-examples/brownfield/bucketaccess.yaml @@ -0,0 +1,10 @@ +apiVersion: objectstorage.k8s.io/v1alpha1 +kind: BucketAccess +metadata: + name: brownfield-bucket-access + namespace: scality-object-storage +spec: + bucketAccessClassName: brownfield-bucket-access-class + bucketClaimName: brownfield-bucket-claim + credentialsSecretName: brownfield-bucket-secret + protocol: s3 diff --git a/cosi-examples/brownfield/bucketaccessclass.yaml b/cosi-examples/brownfield/bucketaccessclass.yaml new file mode 100644 index 00000000..05137647 --- /dev/null +++ b/cosi-examples/brownfield/bucketaccessclass.yaml @@ -0,0 +1,10 @@ +kind: BucketAccessClass +apiVersion: objectstorage.k8s.io/v1alpha1 +metadata: + name: brownfield-bucket-access-class + namespace: scality-object-storage +driverName: cosi.scality.com +authenticationType: KEY +parameters: + objectStorageSecretName: s3-secret-for-cosi + objectStorageSecretNamespace: default diff --git a/cosi-examples/brownfield/bucketclaim.yaml b/cosi-examples/brownfield/bucketclaim.yaml new file mode 100644 index 00000000..482bfdde --- /dev/null +++ b/cosi-examples/brownfield/bucketclaim.yaml @@ -0,0 +1,10 @@ +apiVersion: objectstorage.k8s.io/v1alpha1 +kind: BucketClaim +metadata: + name: brownfield-bucket-claim + namespace: scality-object-storage +spec: + bucketClassName: brownfield-bucket-class + existingBucketName: brownfield-bucket # name of Bucket object + protocols: + - S3 diff --git a/cosi-examples/brownfield/bucketclass.yaml b/cosi-examples/brownfield/bucketclass.yaml new file mode 100644 index 00000000..622863eb --- /dev/null +++ b/cosi-examples/brownfield/bucketclass.yaml @@ -0,0 +1,10 @@ +apiVersion: objectstorage.k8s.io/v1alpha1 +kind: BucketClass +metadata: + name: brownfield-bucket-class + namespace: scality-object-storage +driverName: cosi.scality.com +deletionPolicy: Delete +parameters: + objectStorageSecretName: s3-secret-for-cosi + objectStorageSecretNamespace: default From 921135b76260bb289bb2f9b33e0d6d117cfec58d Mon Sep 17 00:00:00 2001 From: Anurag Mittal Date: Wed, 18 Dec 2024 09:33:29 +0100 Subject: [PATCH 3/4] COSI-75: e2e-test-for-brown-field-scenario --- .../scripts/e2e_tests_brownfield_use_case.sh | 119 ++++++++++++++++++ ...ts.sh => e2e_tests_greenfield_use_case.sh} | 2 +- .github/workflows/ci-e2e-tests.yml | 9 +- 3 files changed, 126 insertions(+), 4 deletions(-) create mode 100755 .github/scripts/e2e_tests_brownfield_use_case.sh rename .github/scripts/{e2e_tests.sh => e2e_tests_greenfield_use_case.sh} (99%) diff --git a/.github/scripts/e2e_tests_brownfield_use_case.sh b/.github/scripts/e2e_tests_brownfield_use_case.sh new file mode 100755 index 00000000..bc4eefca --- /dev/null +++ b/.github/scripts/e2e_tests_brownfield_use_case.sh @@ -0,0 +1,119 @@ +#!/bin/bash +set -e + +LOG_FILE=".github/e2e_tests/artifacts/logs/e2e_tests/brownfield.log" +mkdir -p "$(dirname "$LOG_FILE")" + +HOST_IP=$(hostname -I | awk '{print $1}') +SECRET_NAME="brownfield-bucket-secret" +IAM_ENDPOINT="http://$HOST_IP:8600" +S3_ENDPOINT="http://$HOST_IP:8000" +BUCKET_NAME="brownfield-bucket" +NAMESPACE="scality-object-storage" +REGION="us-west-1" + +# Error handling function +error_handler() { + echo "An error occurred during bucket creation tests. Check the log file for details." | tee -a "$LOG_FILE" + echo "Failed command: $BASH_COMMAND" | tee -a "$LOG_FILE" + exit 1 +} + +# Trap errors and call the error handler +trap 'error_handler' ERR + +# Log command execution to the log file for debugging +log_and_run() { + "$@" 2>&1 | tee -a "$LOG_FILE" +} + + +# Create the bucket fir brownfield scenario +log_and_run echo "Creating bucket: $BUCKET_NAME" +log_and_run aws s3api create-bucket --bucket "$BUCKET_NAME" --region $REGION --endpoint-url "$S3_ENDPOINT" + +# Check if the bucket exists +log_and_run echo "Checking if bucket $BUCKET_NAME exists" +aws --endpoint-url "$S3_ENDPOINT" s3api head-bucket --bucket "$BUCKET_NAME" +log_and_run echo "Bucket $BUCKET_NAME exists!" + +log_and_run echo "Applying Bucket Class to use existing bucket..." +log_and_run kubectl apply -f cosi-examples/brownfield/bucketclass.yaml + +log_and_run echo "Manually creating Bucket object with existing bucket..." +log_and_run kubectl apply -f cosi-examples/brownfield/bucket.yaml + +log_and_run echo "Applying Bucket Claim referencing the Bucket object..." +log_and_run kubectl apply -f cosi-examples/brownfield/bucketclaim.yaml + +log_and_run echo "Applying Bucket Access Class..." +log_and_run kubectl apply -f cosi-examples/brownfield/bucketaccessclass.yaml + +log_and_run echo "Applying Bucket Access..." +log_and_run kubectl apply -f cosi-examples/brownfield/bucketaccess.yaml + +log_and_run echo "Verifying brownfield-bucket-secret in the default namespace..." +SECRET_JSON="$(kubectl get secret "$SECRET_NAME" --namespace "$NAMESPACE" -o json)" + +# Decode the Base64 encoded BucketInfo +BUCKET_INFO_BASE64="$(echo "$SECRET_JSON" | jq -r '.data.BucketInfo')" +BUCKET_INFO_JSON="$(echo "$BUCKET_INFO_BASE64" | base64 --decode)" + +log_and_run echo "Decoded BucketInfo: $BUCKET_INFO_JSON" + +# Extract values to verify +ACTUAL_BUCKET_NAME=$(echo "$BUCKET_INFO_JSON" | jq -r '.spec.bucketName') +ACTUAL_ENDPOINT=$(echo "$BUCKET_INFO_JSON" | jq -r '.spec.secretS3.endpoint') +ACTUAL_REGION=$(echo "$BUCKET_INFO_JSON" | jq -r '.spec.secretS3.region') +ACTUAL_ACCESS_KEY_ID=$(echo "$BUCKET_INFO_JSON" | jq -r '.spec.secretS3.accessKeyID') +ACTUAL_ACCESS_SECRET_KEY=$(echo "$BUCKET_INFO_JSON" | jq -r '.spec.secretS3.accessSecretKey') +ACTUAL_PROTOCOLS=$(echo "$BUCKET_INFO_JSON" | jq -c '.spec.protocols') + +# Verify bucketName +if [[ "$ACTUAL_BUCKET_NAME" != "$BUCKET_NAME" ]]; then + log_and_run echo "Bucket name mismatch! Expected: $BUCKET_NAME, Found: $ACTUAL_BUCKET_NAME" + exit 1 +fi + +# Verify endpoint +EXPECTED_ENDPOINT="$S3_ENDPOINT" +if [[ "$ACTUAL_ENDPOINT" != "$EXPECTED_ENDPOINT" ]]; then + log_and_run echo "Endpoint mismatch! Expected: $EXPECTED_ENDPOINT, Found: $ACTUAL_ENDPOINT" + exit 1 +fi + +# Verify region +if [[ "$ACTUAL_REGION" != "$REGION" ]]; then + log_and_run echo "Region mismatch! Expected: $REGION, Found: $ACTUAL_REGION" + exit 1 +fi + +# Verify accessSecretKey exists +if [[ -z "$ACTUAL_ACCESS_KEY_ID" ]]; then + log_and_run echo "AccessSecretKey is empty!" + exit 1 +fi + +# Verify accessSecretKey exists +if [[ -z "$ACTUAL_ACCESS_SECRET_KEY" ]]; then + log_and_run echo "AccessSecretKey is empty!" + exit 1 +fi + +# Verify protocol +EXPECTED_PROTOCOLS='["s3"]' +if [[ "$ACTUAL_PROTOCOLS" != "$EXPECTED_PROTOCOLS" ]]; then + log_and_run echo "Protocols mismatch! Expected: $EXPECTED_PROTOCOLS, Found: $ACTUAL_PROTOCOLS" + exit 1 +fi + +# cleanup +log_and_run kubectl delete -f cosi-examples/brownfield/bucketaccess.yaml +log_and_run kubectl delete -f cosi-examples/brownfield/bucketaccessclass.yaml +log_and_run kubectl delete -f cosi-examples/brownfield/bucketclaim.yaml +log_and_run kubectl delete -f cosi-examples/brownfield/bucketclass.yaml + +# Check if the bucket is not deleted and Retain policy is respected +log_and_run echo "Checking if bucket $BUCKET_NAME exists" +aws --endpoint-url "$S3_ENDPOINT" s3api head-bucket --bucket "$BUCKET_NAME" +log_and_run echo "Bucket $BUCKET_NAME has been retained!" diff --git a/.github/scripts/e2e_tests.sh b/.github/scripts/e2e_tests_greenfield_use_case.sh similarity index 99% rename from .github/scripts/e2e_tests.sh rename to .github/scripts/e2e_tests_greenfield_use_case.sh index e0e5b89a..2ea0a083 100755 --- a/.github/scripts/e2e_tests.sh +++ b/.github/scripts/e2e_tests_greenfield_use_case.sh @@ -2,7 +2,7 @@ set -e # Define log file for debugging -LOG_FILE=".github/e2e_tests/artifacts/logs/e2e_tests/bucket_creation_test.log" +LOG_FILE=".github/e2e_tests/artifacts/logs/e2e_tests/greenfield.log" mkdir -p "$(dirname "$LOG_FILE")" # Ensure the log directory exists CONTAINER_NAME=s3_and_iam_deployment-iam-1 diff --git a/.github/workflows/ci-e2e-tests.yml b/.github/workflows/ci-e2e-tests.yml index 8ec0e6f3..60d339aa 100644 --- a/.github/workflows/ci-e2e-tests.yml +++ b/.github/workflows/ci-e2e-tests.yml @@ -96,10 +96,13 @@ jobs: docker save "$CLOUDSERVER_IMAGE" -o /tmp/.docker_cache/cloudserver_image.tar shell: bash - - name: E2E tests for COSI driver using kustomize + - name: E2E tests for greenfield use case using kustomize run: | - pwd - .github/scripts/e2e_tests.sh + .github/scripts/e2e_tests_greenfield_use_case.sh + + - name: E2E tests for brownfield use case using kustomize + run: | + .github/scripts/e2e_tests_brownfield_use_case.sh - name: "Delay completion" if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} From a3e73373f003db33df8592ec7c9dab7ae5ae0f86 Mon Sep 17 00:00:00 2001 From: Anurag Mittal Date: Wed, 18 Dec 2024 12:57:49 +0100 Subject: [PATCH 4/4] COSI-75: re-organize-greenfield-custom-resources --- .github/scripts/cleanup_cosi_resources.sh | 8 ++++---- .github/scripts/e2e_tests_greenfield_use_case.sh | 16 ++++++++-------- cosi-examples/{ => greenfield}/bucketaccess.yaml | 0 .../{ => greenfield}/bucketaccessclass.yaml | 0 .../bucketclaim-deletion-policy.yaml | 0 cosi-examples/{ => greenfield}/bucketclaim.yaml | 0 .../bucketclass-deletion-policy.yaml | 0 cosi-examples/{ => greenfield}/bucketclass.yaml | 0 docs/driver-params.md | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) rename cosi-examples/{ => greenfield}/bucketaccess.yaml (100%) rename cosi-examples/{ => greenfield}/bucketaccessclass.yaml (100%) rename cosi-examples/{ => greenfield}/bucketclaim-deletion-policy.yaml (100%) rename cosi-examples/{ => greenfield}/bucketclaim.yaml (100%) rename cosi-examples/{ => greenfield}/bucketclass-deletion-policy.yaml (100%) rename cosi-examples/{ => greenfield}/bucketclass.yaml (100%) diff --git a/.github/scripts/cleanup_cosi_resources.sh b/.github/scripts/cleanup_cosi_resources.sh index c2d6cef1..e83ff468 100755 --- a/.github/scripts/cleanup_cosi_resources.sh +++ b/.github/scripts/cleanup_cosi_resources.sh @@ -52,12 +52,12 @@ for BUCKET_NAME in $BUCKET_NAMES; do done log_and_run echo "Deleting Bucket Access Class..." -log_and_run kubectl delete -f cosi-examples/bucketaccessclass.yaml --all || { echo "No BucketAccessClass resources found." | tee -a "$LOG_FILE"; } +log_and_run kubectl delete -f cosi-examples/greenfield/bucketaccessclass.yaml --all || { echo "No BucketAccessClass resources found." | tee -a "$LOG_FILE"; } log_and_run echo "Deleting Bucket Class and Bucket Claim..." -log_and_run kubectl delete -f cosi-examples/bucketclass.yaml || { echo "Bucket Class not found." | tee -a "$LOG_FILE"; } -log_and_run kubectl delete -f cosi-examples/bucketclaim.yaml || { echo "Bucket Claim not found." | tee -a "$LOG_FILE"; } -log_and_run kubectl delete -f cosi-examples/bucketclass-delete-on-claim-removal.yaml || { echo "Bucket Class not found." | tee -a "$LOG_FILE"; } +log_and_run kubectl delete -f cosi-examples/greenfield/bucketclass.yaml || { echo "Bucket Class not found." | tee -a "$LOG_FILE"; } +log_and_run kubectl delete -f cosi-examples/greenfield/bucketclaim.yaml || { echo "Bucket Claim not found." | tee -a "$LOG_FILE"; } +log_and_run kubectl delete -f cosi-examples/greenfield/bucketclass-deletion-policy.yaml || { echo "Bucket Class not found." | tee -a "$LOG_FILE"; } log_and_run echo "Deleting s3-secret-for-cosi secret..." log_and_run kubectl delete secret s3-secret-for-cosi --namespace=default || { echo "Secret s3-secret-for-cosi not found." | tee -a "$LOG_FILE"; } diff --git a/.github/scripts/e2e_tests_greenfield_use_case.sh b/.github/scripts/e2e_tests_greenfield_use_case.sh index 2ea0a083..9f875805 100755 --- a/.github/scripts/e2e_tests_greenfield_use_case.sh +++ b/.github/scripts/e2e_tests_greenfield_use_case.sh @@ -83,19 +83,19 @@ EOF # Step 4: Apply Bucket Class log_and_run echo "Applying Bucket Class..." -log_and_run kubectl apply -f cosi-examples/bucketclass.yaml +log_and_run kubectl apply -f cosi-examples/greenfield/bucketclass.yaml # Step 5: Apply Bucket Claim log_and_run echo "Applying Bucket Claim..." -log_and_run kubectl apply -f cosi-examples/bucketclaim.yaml +log_and_run kubectl apply -f cosi-examples/greenfield/bucketclaim.yaml # Step 6: Apply Bucket Access Class log_and_run echo "Applying Bucket Access Class..." -log_and_run kubectl apply -f cosi-examples/bucketaccessclass.yaml +log_and_run kubectl apply -f cosi-examples/greenfield/bucketaccessclass.yaml # Step 7: Apply Bucket Access log_and_run echo "Applying Bucket Access..." -log_and_run kubectl apply -f cosi-examples/bucketaccess.yaml +log_and_run kubectl apply -f cosi-examples/greenfield/bucketaccess.yaml # Step 8: Verify Bucket Creation with Retry log_and_run echo "Listing all S3 buckets before verification..." @@ -213,7 +213,7 @@ fi # Step 11: Delete Bucket Access Resource log_and_run echo "Deleting Bucket Access Resource..." -log_and_run kubectl delete -f cosi-examples/bucketaccess.yaml +log_and_run kubectl delete -f cosi-examples/greenfield/bucketaccess.yaml # Step 12: Verify IAM User Deletion log_and_run echo "Verifying IAM user '$IAM_USER_NAME' deletion..." @@ -230,8 +230,8 @@ fi # Step 13: Test deletion bucket with deletion policy set log_and_run echo "Applying Bucket Class with deletion policy and respective Bucket Claim..." -log_and_run kubectl apply -f cosi-examples/bucketclass-deletion-policy.yaml -log_and_run kubectl apply -f cosi-examples/bucketclaim-deletion-policy.yaml +log_and_run kubectl apply -f cosi-examples/greenfield/bucketclass-deletion-policy.yaml +log_and_run kubectl apply -f cosi-examples/greenfield/bucketclaim-deletion-policy.yaml log_and_run echo "Listing all S3 buckets before deletion..." log_and_run aws s3 ls --endpoint-url "$S3_ENDPOINT" @@ -259,7 +259,7 @@ if [ -z "$BUCKET_TO_BE_DELETED" ]; then fi log_and_run echo "Deleting Bucket Claim..." -log_and_run kubectl delete -f cosi-examples/bucketclaim-deletion-policy.yaml +log_and_run kubectl delete -f cosi-examples/greenfield/bucketclaim-deletion-policy.yaml # Check if the bucket with name $BUCKET_TO_BE_DELETED exists by doing a head bucket. # If bucket exists, retry with ATTEMPTS and DELAY. If bucket is not found, test success. diff --git a/cosi-examples/bucketaccess.yaml b/cosi-examples/greenfield/bucketaccess.yaml similarity index 100% rename from cosi-examples/bucketaccess.yaml rename to cosi-examples/greenfield/bucketaccess.yaml diff --git a/cosi-examples/bucketaccessclass.yaml b/cosi-examples/greenfield/bucketaccessclass.yaml similarity index 100% rename from cosi-examples/bucketaccessclass.yaml rename to cosi-examples/greenfield/bucketaccessclass.yaml diff --git a/cosi-examples/bucketclaim-deletion-policy.yaml b/cosi-examples/greenfield/bucketclaim-deletion-policy.yaml similarity index 100% rename from cosi-examples/bucketclaim-deletion-policy.yaml rename to cosi-examples/greenfield/bucketclaim-deletion-policy.yaml diff --git a/cosi-examples/bucketclaim.yaml b/cosi-examples/greenfield/bucketclaim.yaml similarity index 100% rename from cosi-examples/bucketclaim.yaml rename to cosi-examples/greenfield/bucketclaim.yaml diff --git a/cosi-examples/bucketclass-deletion-policy.yaml b/cosi-examples/greenfield/bucketclass-deletion-policy.yaml similarity index 100% rename from cosi-examples/bucketclass-deletion-policy.yaml rename to cosi-examples/greenfield/bucketclass-deletion-policy.yaml diff --git a/cosi-examples/bucketclass.yaml b/cosi-examples/greenfield/bucketclass.yaml similarity index 100% rename from cosi-examples/bucketclass.yaml rename to cosi-examples/greenfield/bucketclass.yaml diff --git a/docs/driver-params.md b/docs/driver-params.md index f9b8b740..c9c56bd8 100644 --- a/docs/driver-params.md +++ b/docs/driver-params.md @@ -9,7 +9,7 @@ The table below details the configuration parameters for BucketClass, which dete | `objectStorageSecretName` | The name of the Kubernetes secret containing S3 credentials and configuration. | `string` | Yes | | `objectStorageSecretNamespace` | The namespace in which the secret is located (e.g., `default`). | `string` (e.g., `default`) | Yes | -[Example](../cosi-examples/bucketclass.yaml) +[Example](../cosi-examples/greenfield/bucketclass.yaml) ## Configuration Parameters for Kubernetes secret containing S3 credentials and configuration