diff --git a/Documentation/CRDs/Object-Storage/ceph-object-store-user-crd.md b/Documentation/CRDs/Object-Storage/ceph-object-store-user-crd.md index 4dde0d8bf1ac..19d3cafe8ad5 100644 --- a/Documentation/CRDs/Object-Storage/ceph-object-store-user-crd.md +++ b/Documentation/CRDs/Object-Storage/ceph-object-store-user-crd.md @@ -60,3 +60,78 @@ spec: * `user-policy` * `odic-provider` * `ratelimit` + +### CephObjectStoreUser Reference Secret + +A Kubernetes secret is created by Rook with the name, `rook-ceph-object-user--` in the same namespace of cephobjectUser, where: + +* `store-name`: The object store name in which the user will be created. This matches the name of the objectstore CRD. +* `user-name`: The metadata name of the cephObjectStoreUser + +Application pods can use the secret to configure access to the object store. + +```yaml +apiVersion: v1 +data: + AccessKey: *** # [1] + Credentials: *** # [2] + Endpoint: *** # [3] + SecretKey: *** # [4] +kind: Secret +... +... +type: kubernetes.io/rook + +``` + +1. `AccessKey`: User access key for ceph S3. +2. `SecretKey`: User secret key for ceph S3. +3. `Credentials`: Comma separated values of all access and secret keys. +4. `Endpoint`: Endpoint for ceph S3. + +#### User-Specified Reference Secret + +AccessKey, SecretKey, and Credentials can be specified before CephObjectStore creation or modified afterwards to support user-specified keys and key rotation. Endpoint cannot be overridden by users and will always be updated by Rook to match the latest CephObjectStore information. + +Create or update the Kubernetes secret with name, `rook-ceph-object-user--` in the same namespace of cephobjectUser, + +Steps: + +i) Specify the annotations as `rook.io/source-of-truth: secret` and type as `type: "kubernetes.io/rook"`. + +ii) Specify the user defined `AccessKey` and `SecretKey`. + +iii) (optional) The array of `Credentials` which contains all the trusted access and secret keys. Make sure to also add the user defined `AccessKey` and `SecretKey` of step ii). + +`Credentials` +```console +[{"access_key":"IE58RNT71Y2F1EQE80RA","secret_key":"cULyMz5dCpX18dPsJhpIKay7vcDNRNJWJPu8VqUA"}, {"access_key":"IE58RNT71Y2F1EQE80RC","secret_key":"cULyMz5dCpX18dPsJhpIKay7vcDNRNJWJPu8VqUA"}] +``` + +If any key is present in the ceph user and not present in the `Credentials`, it will be removed from the ceph user too. +If `Credentials` is left empty, it will be auto updated by the latest AccessKey and SecretKey and will remove all the other keys from the ceph user. + +!!! note + Secret data usually needs to be converted to base64 format. + +Example Secret: + +```console +kubectl create -f +apiVersion: v1 +kind: Secret +metadata: + name: rook-ceph-object-user-my-store-my-user + namespace: rook-ceph + annotations: + rook.io/source-of-truth: secret +data: + AccessKey: *** + SecretKey: *** + Endpoint: *** + Credentials: *** +type: "kubernetes.io/rook" +``` + +!!! note + Be careful when updating Kubernetes secret values. Mistakes entering info can cause errors reconciling the CephObjectStore. diff --git a/go.mod b/go.mod index d6e72f3f3acf..a2d967c5a7a6 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,7 @@ require ( ) require ( - emperror.dev/errors v0.8.1 // indirect + emperror.dev/errors v0.8.1 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/ansel1/merry v1.8.0 // indirect github.com/ansel1/merry/v2 v2.2.0 // indirect diff --git a/pkg/operator/ceph/object/secret/secret.go b/pkg/operator/ceph/object/secret/secret.go new file mode 100644 index 000000000000..7b23a0dc2308 --- /dev/null +++ b/pkg/operator/ceph/object/secret/secret.go @@ -0,0 +1,83 @@ +/* +Copyright 2016 The Rook Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package secret + +import ( + "encoding/json" + "strings" + + "github.com/ceph/go-ceph/rgw/admin" + "github.com/pkg/errors" +) + +const ( + //#nosec G101 -- This is the name of an annotation key, not a secret + UpdateObjectUserSecretAnnotation = "rook.io/source-of-truth" + SourceOfTruthSecret = "secret" +) + +// Credential is the user credential values +type Credential struct { + AccessKey string `json:"access_key" url:"access-key"` + SecretKey string `json:"secret_key" url:"secret-key"` +} + +func ValidateCredentials(accessKey, secretKey string, credentials []Credential) ([]Credential, error) { + // if len is 0 just update the credentials array with the accessKey and secretKey key + if len(credentials) == 0 { + credentials = []Credential{ + {AccessKey: accessKey, + SecretKey: secretKey}} + return credentials, nil + } + // match if the credentials array contains the accessKey and secretKey key + for i := range credentials { + if credentials[i].AccessKey == accessKey && credentials[0].SecretKey == secretKey { + return credentials, nil + } + } + + return credentials, errors.Errorf("secret keys data is invalid, please update the secret with cvalid format") +} + +func UnmarshalKeys(credentials []byte) ([]Credential, error) { + var userKeys []Credential + err := json.Unmarshal(credentials, &userKeys) + if err != nil { + return userKeys, errors.Wrapf(err, "unable to unmarshal credentials from the object secret") + } + return userKeys, nil +} + +func GetUserDefinedSecretAnnotations(annotations map[string]string) bool { + if value, found := annotations[UpdateObjectUserSecretAnnotation]; found { + if strings.EqualFold(value, SourceOfTruthSecret) { + return true + } + } + return false +} + +func UpdatecredentialsUserId(uid, displayName string, credentials []Credential) []admin.UserKeySpec { + var updatedCredentials []admin.UserKeySpec + for i := range credentials { + userSpec := admin.UserKeySpec{User: displayName, UID: uid, AccessKey: credentials[i].AccessKey, SecretKey: credentials[i].SecretKey} + updatedCredentials = append(updatedCredentials, userSpec) + } + + return updatedCredentials +} diff --git a/pkg/operator/ceph/object/user.go b/pkg/operator/ceph/object/user.go index 7cb2b670b682..c1a6c5c85b6f 100644 --- a/pkg/operator/ceph/object/user.go +++ b/pkg/operator/ceph/object/user.go @@ -241,17 +241,23 @@ func GenerateCephUserSecretName(store, username string) string { return fmt.Sprintf("rook-ceph-object-user-%s-%s", store, username) } -func generateCephUserSecret(userConfig *admin.User, endpoint, namespace, storeName, tlsSecretName string) *corev1.Secret { +func generateCephUserSecret(userConfig *admin.User, endpoint, namespace, storeName, tlsSecretName string) (*corev1.Secret, error) { secretName := GenerateCephUserSecretName(storeName, userConfig.ID) + secretKeys, err := json.Marshal(userConfig.Keys) + if err != nil { + return &corev1.Secret{}, err + } // Store the keys in a secret secrets := map[string]string{ - "AccessKey": userConfig.Keys[0].AccessKey, - "SecretKey": userConfig.Keys[0].SecretKey, - "Endpoint": endpoint, + "AccessKey": userConfig.Keys[0].AccessKey, + "SecretKey": userConfig.Keys[0].SecretKey, + "SecretKeys": string(secretKeys), + "Endpoint": endpoint, } if tlsSecretName != "" { secrets["SSLCertSecretName"] = tlsSecretName } + secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: secretName, @@ -266,15 +272,18 @@ func generateCephUserSecret(userConfig *admin.User, endpoint, namespace, storeNa StringData: secrets, Type: k8sutil.RookType, } - return secret + + return secret, nil } func ReconcileCephUserSecret(ctx context.Context, k8sclient client.Client, scheme *runtime.Scheme, ownerRef metav1.Object, userConfig *admin.User, endpoint, namespace, storeName, tlsSecretName string) (reconcile.Result, error) { // Generate Kubernetes Secret - secret := generateCephUserSecret(userConfig, endpoint, namespace, storeName, tlsSecretName) - + secret, err := generateCephUserSecret(userConfig, endpoint, namespace, storeName, tlsSecretName) + if err != nil { + return reconcile.Result{}, errors.Wrapf(err, "failed to get the ceph object user secret %q", GenerateCephUserSecretName(storeName, userConfig.ID)) + } // Set owner ref to the object store user object - err := controllerutil.SetControllerReference(ownerRef, secret, scheme) + err = controllerutil.SetControllerReference(ownerRef, secret, scheme) if err != nil { return reconcile.Result{}, errors.Wrapf(err, "failed to set owner reference of ceph object user secret %q", secret.Name) } diff --git a/pkg/operator/ceph/object/user/controller.go b/pkg/operator/ceph/object/user/controller.go index 1c7624e6c7c4..4b97a0b9c67b 100644 --- a/pkg/operator/ceph/object/user/controller.go +++ b/pkg/operator/ceph/object/user/controller.go @@ -23,28 +23,28 @@ import ( "reflect" "github.com/ceph/go-ceph/rgw/admin" - opcontroller "github.com/rook/rook/pkg/operator/ceph/controller" - "github.com/rook/rook/pkg/operator/ceph/reporting" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/record" - "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/controller-runtime/pkg/source" - "github.com/coreos/pkg/capnslog" "github.com/pkg/errors" cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1" "github.com/rook/rook/pkg/clusterd" cephclient "github.com/rook/rook/pkg/daemon/ceph/client" + opcontroller "github.com/rook/rook/pkg/operator/ceph/controller" "github.com/rook/rook/pkg/operator/ceph/object" + secret "github.com/rook/rook/pkg/operator/ceph/object/secret" + "github.com/rook/rook/pkg/operator/ceph/reporting" "github.com/rook/rook/pkg/operator/k8sutil" corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" ) const ( @@ -250,7 +250,7 @@ func (r *ReconcileObjectStoreUser) reconcile(request reconcile.Request) (reconci } // CREATE/UPDATE CEPH USER - reconcileResponse, err = r.reconcileCephUser(cephObjectStoreUser) + reconcileResponse, overwriteExistingCreds, err := r.reconcileCephUser(cephObjectStoreUser) if err != nil { r.updateStatus(k8sutil.ObservedGenerationNotAvailable, request.NamespacedName, k8sutil.ReconcileFailedStatus) return reconcileResponse, *cephObjectStoreUser, err @@ -263,12 +263,13 @@ func (r *ReconcileObjectStoreUser) reconcile(request reconcile.Request) (reconci } tlsSecretName := store.Spec.Gateway.SSLCertificateRef - reconcileResponse, err = object.ReconcileCephUserSecret(r.opManagerContext, r.client, r.scheme, cephObjectStoreUser, r.userConfig, r.objContext.Endpoint, cephObjectStoreUser.Namespace, cephObjectStoreUser.Spec.Store, tlsSecretName) - if err != nil { - r.updateStatus(k8sutil.ObservedGenerationNotAvailable, request.NamespacedName, k8sutil.ReconcileFailedStatus) - return reconcileResponse, *cephObjectStoreUser, err + if !overwriteExistingCreds { + reconcileResponse, err = object.ReconcileCephUserSecret(r.opManagerContext, r.client, r.scheme, cephObjectStoreUser, r.userConfig, r.objContext.Endpoint, cephObjectStoreUser.Namespace, cephObjectStoreUser.Spec.Store, tlsSecretName) + if err != nil { + r.updateStatus(k8sutil.ObservedGenerationNotAvailable, request.NamespacedName, k8sutil.ReconcileFailedStatus) + return reconcileResponse, *cephObjectStoreUser, err + } } - // update ObservedGeneration in status at the end of reconcile // Set Ready status, we are done reconciling r.updateStatus(observedGeneration, request.NamespacedName, k8sutil.ReadyStatus) @@ -278,21 +279,97 @@ func (r *ReconcileObjectStoreUser) reconcile(request reconcile.Request) (reconci return reconcile.Result{}, *cephObjectStoreUser, nil } -func (r *ReconcileObjectStoreUser) reconcileCephUser(cephObjectStoreUser *cephv1.CephObjectStoreUser) (reconcile.Result, error) { - err := r.createOrUpdateCephUser(cephObjectStoreUser) +func (r *ReconcileObjectStoreUser) getUserDefinedSecret(u *cephv1.CephObjectStoreUser) ([]secret.Credential, bool, error) { + var credentialsStruct []secret.Credential + overwriteExistingCreds := false + secretName := object.GenerateCephUserSecretName(u.Spec.Store, u.Name) + namspacedName := types.NamespacedName{Namespace: u.Namespace, Name: secretName} + + cephObjectStoreUserSecret := &corev1.Secret{} + err := r.client.Get(r.clusterInfo.Context, namspacedName, cephObjectStoreUserSecret) + if err != nil { + if !kerrors.IsNotFound(err) { + return credentialsStruct, overwriteExistingCreds, errors.Wrapf(err, "failed to get user cephobjectstoreuser secret %q", secretName) + } + logger.Debugf("no user secret %q provided for cephobjectstoreuser", secretName) + + } else { + if secret.GetUserDefinedSecretAnnotations(cephObjectStoreUserSecret.GetAnnotations()) { + overwriteExistingCreds = true + credentialsStruct, err = secret.UnmarshalKeys(cephObjectStoreUserSecret.Data["Credentials"]) + if err != nil { + return credentialsStruct, overwriteExistingCreds, errors.Wrapf(err, "invalid cephobjectstoreuser secret %q format", secretName) + } + credentialsStruct, err = secret.ValidateCredentials(string(cephObjectStoreUserSecret.Data["AccessKey"]), string(cephObjectStoreUserSecret.Data["SecretKey"]), credentialsStruct) + if err != nil { + return credentialsStruct, overwriteExistingCreds, errors.Wrapf(err, "invalid cephobjectstoreuser secret %q values", secretName) + } + } + } + + return credentialsStruct, overwriteExistingCreds, nil +} + +func (r *ReconcileObjectStoreUser) reconcileCephUser(cephObjectStoreUser *cephv1.CephObjectStoreUser) (reconcile.Result, bool, error) { + // get the user defined k8s s3 keys if exists + creds, overwriteExistingCreds, err := r.getUserDefinedSecret(cephObjectStoreUser) if err != nil { - return reconcile.Result{}, errors.Wrapf(err, "failed to create/update object store user %q", cephObjectStoreUser.Name) + return reconcile.Result{}, overwriteExistingCreds, errors.Wrapf(err, "failed to user defined k8s s3 keys of object user %q", r.userConfig.ID) } - return reconcile.Result{}, nil + err = r.createOrUpdateCephUser(cephObjectStoreUser, creds, overwriteExistingCreds) + if err != nil { + return reconcile.Result{}, overwriteExistingCreds, errors.Wrapf(err, "failed to create/update object store user %q", cephObjectStoreUser.Name) + } + + return reconcile.Result{}, overwriteExistingCreds, nil } -func (r *ReconcileObjectStoreUser) createOrUpdateCephUser(u *cephv1.CephObjectStoreUser) error { +func (r *ReconcileObjectStoreUser) modifyObjectUserSecret(credentialsStruct []secret.Credential, user admin.User) error { + // update the userid on the credentials + usercredentialConfig := secret.UpdatecredentialsUserId(user.ID, user.DisplayName, credentialsStruct) + // if secret exists and source of truth is secret then use the user specified keys + r.userConfig.Keys = usercredentialConfig + // get and map all the secrets of the ceph user + credentialsExists := make(map[string]string) + for i := range user.Keys { + credentialsExists[user.Keys[i].AccessKey] = user.Keys[i].SecretKey + } + // add the new keys from the credentials array that are not already present in the ceph user + // addition and removal of keys can be done only one at a time + for _, secretKey := range usercredentialConfig { + // remove the key from the credentialsExists map if it already exists + if credentialsExists[secretKey.AccessKey] == secretKey.SecretKey { + credentialsExists[secretKey.AccessKey] = "" + continue + } + // add the key to the ceph user + _, err := r.objContext.AdminOpsClient.CreateKey(r.opManagerContext, secretKey) + if err != nil { + return errors.Wrapf(err, "failed to update ceph object user %q key", r.userConfig.ID) + } + } + // remove the keys from the ceph user that are not in the usercredentialConfig + for accessKey, secretKey := range credentialsExists { + if secretKey == "" { + break + } + // remove the key to the ceph user + err := r.objContext.AdminOpsClient.RemoveKey(r.opManagerContext, admin.UserKeySpec{AccessKey: accessKey, SecretKey: secretKey, UID: user.ID, User: user.DisplayName}) + if err != nil { + return errors.Wrapf(err, "failed to remove the ceph object user %q key", r.userConfig.ID) + } + } + return nil +} + +func (r *ReconcileObjectStoreUser) createOrUpdateCephUser(u *cephv1.CephObjectStoreUser, creds []secret.Credential, overwriteExistingCreds bool) error { logger.Infof("creating ceph object user %q in namespace %q", u.Name, u.Namespace) logCreateOrUpdate := fmt.Sprintf("retrieved existing ceph object user %q", u.Name) var user admin.User var err error + user, err = r.objContext.AdminOpsClient.GetUser(r.opManagerContext, *r.userConfig) if err != nil { if errors.Is(err, admin.ErrNoSuchUser) { @@ -362,12 +439,20 @@ func (r *ReconcileObjectStoreUser) createOrUpdateCephUser(u *cephv1.CephObjectSt return errors.Wrapf(err, "failed to set quotas for user %q", u.Name) } + if overwriteExistingCreds { + err = r.modifyObjectUserSecret(creds, user) + if err != nil { + return errors.Wrapf(err, "failed to modify objectstoreuser %q secret, when the source-of-truth was secret", u.Name) + } + logger.Infof("updated objectstoreuser %q secret, when the source-of-truth was secret", u.Name) + } + // Set access and secret key if r.userConfig.Keys == nil { r.userConfig.Keys = make([]admin.UserKeySpec, 1) + r.userConfig.Keys[0].AccessKey = user.Keys[0].AccessKey + r.userConfig.Keys[0].SecretKey = user.Keys[0].SecretKey } - r.userConfig.Keys[0].AccessKey = user.Keys[0].AccessKey - r.userConfig.Keys[0].SecretKey = user.Keys[0].SecretKey logger.Info(logCreateOrUpdate) return nil diff --git a/pkg/operator/ceph/object/user/controller_test.go b/pkg/operator/ceph/object/user/controller_test.go index b443ebca2136..5f5544151b89 100644 --- a/pkg/operator/ceph/object/user/controller_test.go +++ b/pkg/operator/ceph/object/user/controller_test.go @@ -30,11 +30,13 @@ import ( "github.com/coreos/pkg/capnslog" cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1" rookclient "github.com/rook/rook/pkg/client/clientset/versioned/fake" + cephclient "github.com/rook/rook/pkg/daemon/ceph/client" "github.com/rook/rook/pkg/operator/k8sutil" "github.com/rook/rook/pkg/operator/test" "github.com/rook/rook/pkg/clusterd" cephobject "github.com/rook/rook/pkg/operator/ceph/object" + secret "github.com/rook/rook/pkg/operator/ceph/object/secret" exectest "github.com/rook/rook/pkg/util/exec/test" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" @@ -491,21 +493,29 @@ func TestCreateOrUpdateCephUser(t *testing.T) { return nil, fmt.Errorf("unexpected request: %q. method %q. path %q", req.URL.RawQuery, req.Method, req.URL.Path) }, } + s := scheme.Scheme + cl := fake.NewClientBuilder().WithScheme(s).Build() adminClient, err := admin.New("rook-ceph-rgw-my-store.mycluster.svc", "53S6B9S809NUP19IJ2K3", "1bXPegzsGClvoGAiJdHQD1uOW2sQBLAZM9j9VtXR", mockClient) assert.NoError(t, err) userConfig := generateUserConfig(objectUser) + clusterInfo := &cephclient.ClusterInfo{ + Context: context.TODO(), + } r := &ReconcileObjectStoreUser{ objContext: &cephobject.AdminOpsContext{ AdminOpsClient: adminClient, }, userConfig: &userConfig, opManagerContext: context.TODO(), + client: cl, + clusterInfo: clusterInfo, } maxsize, err := resource.ParseQuantity(maxsizestr) assert.NoError(t, err) + var cred []secret.Credential t.Run("user with empty name", func(t *testing.T) { - err = r.createOrUpdateCephUser(objectUser) + err = r.createOrUpdateCephUser(objectUser, cred, false) assert.Error(t, err) }) @@ -513,7 +523,7 @@ func TestCreateOrUpdateCephUser(t *testing.T) { objectUser.Name = name userConfig = generateUserConfig(objectUser) r.userConfig = &userConfig - err = r.createOrUpdateCephUser(objectUser) + err = r.createOrUpdateCephUser(objectUser, cred, false) assert.NoError(t, err) }) @@ -521,7 +531,7 @@ func TestCreateOrUpdateCephUser(t *testing.T) { objectUser.Spec.Quotas = &cephv1.ObjectUserQuotaSpec{MaxBuckets: &maxbucket} userConfig = generateUserConfig(objectUser) r.userConfig = &userConfig - err = r.createOrUpdateCephUser(objectUser) + err = r.createOrUpdateCephUser(objectUser, cred, false) assert.NoError(t, err) }) @@ -535,7 +545,7 @@ func TestCreateOrUpdateCephUser(t *testing.T) { } userConfig = generateUserConfig(objectUser) r.userConfig = &userConfig - err = r.createOrUpdateCephUser(objectUser) + err = r.createOrUpdateCephUser(objectUser, cred, false) assert.NoError(t, err) }) @@ -545,35 +555,35 @@ func TestCreateOrUpdateCephUser(t *testing.T) { objectUser.Spec.Quotas = &cephv1.ObjectUserQuotaSpec{MaxObjects: &maxobject} userConfig = generateUserConfig(objectUser) r.userConfig = &userConfig - err = r.createOrUpdateCephUser(objectUser) + err = r.createOrUpdateCephUser(objectUser, cred, false) assert.NoError(t, err) }) t.Run("setting MaxSize for the user", func(t *testing.T) { objectUser.Spec.Quotas = &cephv1.ObjectUserQuotaSpec{MaxSize: &maxsize} userConfig = generateUserConfig(objectUser) r.userConfig = &userConfig - err = r.createOrUpdateCephUser(objectUser) + err = r.createOrUpdateCephUser(objectUser, cred, false) assert.NoError(t, err) }) t.Run("resetting MaxSize and MaxObjects for the user", func(t *testing.T) { objectUser.Spec.Quotas = nil userConfig = generateUserConfig(objectUser) r.userConfig = &userConfig - err = r.createOrUpdateCephUser(objectUser) + err = r.createOrUpdateCephUser(objectUser, cred, false) assert.NoError(t, err) }) t.Run("setting both MaxSize and MaxObjects for the user", func(t *testing.T) { objectUser.Spec.Quotas = &cephv1.ObjectUserQuotaSpec{MaxObjects: &maxobject, MaxSize: &maxsize} userConfig = generateUserConfig(objectUser) r.userConfig = &userConfig - err = r.createOrUpdateCephUser(objectUser) + err = r.createOrUpdateCephUser(objectUser, cred, false) assert.NoError(t, err) }) t.Run("resetting MaxSize and MaxObjects again for the user", func(t *testing.T) { objectUser.Spec.Quotas = nil userConfig = generateUserConfig(objectUser) r.userConfig = &userConfig - err = r.createOrUpdateCephUser(objectUser) + err = r.createOrUpdateCephUser(objectUser, cred, false) assert.NoError(t, err) }) @@ -587,7 +597,7 @@ func TestCreateOrUpdateCephUser(t *testing.T) { objectUser.Spec.Quotas = &cephv1.ObjectUserQuotaSpec{MaxBuckets: &maxbucket, MaxObjects: &maxobject, MaxSize: &maxsize} userConfig = generateUserConfig(objectUser) r.userConfig = &userConfig - err = r.createOrUpdateCephUser(objectUser) + err = r.createOrUpdateCephUser(objectUser, cred, false) assert.NoError(t, err) }) }