diff --git a/config/crd/bases/mongodbcommunity.mongodb.com_mongodbcommunity.yaml b/config/crd/bases/mongodbcommunity.mongodb.com_mongodbcommunity.yaml index 12207a6bd..b100966b7 100644 --- a/config/crd/bases/mongodbcommunity.mongodb.com_mongodbcommunity.yaml +++ b/config/crd/bases/mongodbcommunity.mongodb.com_mongodbcommunity.yaml @@ -577,6 +577,13 @@ spec: nullable: true type: object x-kubernetes-preserve-unknown-fields: true + connectionStringSecretAnnotations: + additionalProperties: + type: string + description: ConnectionStringSecretAnnotations is the annotations + of the secret object created by the operator which exposes + the connection strings for the user. + type: object connectionStringSecretName: description: |- ConnectionStringSecretName is the name of the secret object created by the operator which exposes the connection strings for the user. diff --git a/docs/mongodbcommunity/users.md b/docs/mongodbcommunity/users.md index 96a44570a..8dca7569b 100644 --- a/docs/mongodbcommunity/users.md +++ b/docs/mongodbcommunity/users.md @@ -42,6 +42,8 @@ You cannot disable SCRAM authentication. | `spec.users.roles` | array of objects | Configures roles assigned to the user. | Yes | | `spec.users.roles.role.name` | string | Name of the role. Valid values are [built-in roles](https://www.mongodb.com/docs/manual/reference/built-in-roles/#built-in-roles) and [custom roles](deploy-configure.md#define-a-custom-database-role) that you have defined. | Yes | | `spec.users.roles.role.db` | string | Database that the role applies to. | Yes | + | `spec.users.connectionStringSecretAnnotations` | object | Annotations of the secret object created by the operator which exposes the connection strings for the user. | No | + ```yaml --- diff --git a/helm_chart/crds/mongodbcommunity.mongodb.com_mongodbcommunity.yaml b/helm_chart/crds/mongodbcommunity.mongodb.com_mongodbcommunity.yaml index 12207a6bd..b100966b7 100644 --- a/helm_chart/crds/mongodbcommunity.mongodb.com_mongodbcommunity.yaml +++ b/helm_chart/crds/mongodbcommunity.mongodb.com_mongodbcommunity.yaml @@ -577,6 +577,13 @@ spec: nullable: true type: object x-kubernetes-preserve-unknown-fields: true + connectionStringSecretAnnotations: + additionalProperties: + type: string + description: ConnectionStringSecretAnnotations is the annotations + of the secret object created by the operator which exposes + the connection strings for the user. + type: object connectionStringSecretName: description: |- ConnectionStringSecretName is the name of the secret object created by the operator which exposes the connection strings for the user. diff --git a/mongodb-community-operator/api/v1/mongodbcommunity_types.go b/mongodb-community-operator/api/v1/mongodbcommunity_types.go index c8e857041..6357324eb 100644 --- a/mongodb-community-operator/api/v1/mongodbcommunity_types.go +++ b/mongodb-community-operator/api/v1/mongodbcommunity_types.go @@ -449,6 +449,10 @@ type MongoDBUser struct { // +optional ConnectionStringSecretNamespace string `json:"connectionStringSecretNamespace,omitempty"` + // ConnectionStringSecretAnnotations is the annotations of the secret object created by the operator which exposes the connection strings for the user. + // +optional + ConnectionStringSecretAnnotations map[string]string `json:"connectionStringSecretAnnotations,omitempty"` + // Additional options to be appended to the connection string. // These options apply only to this user and will override any existing options in the resource. // +kubebuilder:validation:Type=object @@ -748,12 +752,13 @@ func (m *MongoDBCommunity) GetAuthUsers() []authtypes.User { } users[i] = authtypes.User{ - Username: u.Name, - Database: u.DB, - Roles: roles, - ConnectionStringSecretName: u.GetConnectionStringSecretName(m.Name), - ConnectionStringSecretNamespace: u.GetConnectionStringSecretNamespace(m.Namespace), - ConnectionStringOptions: u.AdditionalConnectionStringConfig.Object, + Username: u.Name, + Database: u.DB, + Roles: roles, + ConnectionStringSecretName: u.GetConnectionStringSecretName(m.Name), + ConnectionStringSecretNamespace: u.GetConnectionStringSecretNamespace(m.Namespace), + ConnectionStringSecretAnnotations: u.ConnectionStringSecretAnnotations, + ConnectionStringOptions: u.AdditionalConnectionStringConfig.Object, } if u.DB != constants.ExternalDB { diff --git a/mongodb-community-operator/api/v1/zz_generated.deepcopy.go b/mongodb-community-operator/api/v1/zz_generated.deepcopy.go index 973c5cd39..b558c8d67 100644 --- a/mongodb-community-operator/api/v1/zz_generated.deepcopy.go +++ b/mongodb-community-operator/api/v1/zz_generated.deepcopy.go @@ -314,6 +314,13 @@ func (in *MongoDBUser) DeepCopyInto(out *MongoDBUser) { *out = make([]Role, len(*in)) copy(*out, *in) } + if in.ConnectionStringSecretAnnotations != nil { + in, out := &in.ConnectionStringSecretAnnotations, &out.ConnectionStringSecretAnnotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } in.AdditionalConnectionStringConfig.DeepCopyInto(&out.AdditionalConnectionStringConfig) } diff --git a/mongodb-community-operator/controllers/mongodb_users.go b/mongodb-community-operator/controllers/mongodb_users.go index 329c237d6..d7df0618e 100644 --- a/mongodb-community-operator/controllers/mongodb_users.go +++ b/mongodb-community-operator/controllers/mongodb_users.go @@ -74,6 +74,7 @@ func (r ReplicaSetReconciler) updateConnectionStringSecrets(ctx context.Context, connectionStringSecret := secret.Builder(). SetName(secretName). SetNamespace(secretNamespace). + SetAnnotations(user.ConnectionStringSecretAnnotations). SetField("connectionString.standard", mdb.MongoAuthUserURI(user, pwd, clusterDomain)). SetField("connectionString.standardSrv", mdb.MongoAuthUserSRVURI(user, pwd, clusterDomain)). SetField("username", user.Username). diff --git a/mongodb-community-operator/controllers/replicaset_controller_test.go b/mongodb-community-operator/controllers/replicaset_controller_test.go index 47a2bba03..7559db794 100644 --- a/mongodb-community-operator/controllers/replicaset_controller_test.go +++ b/mongodb-community-operator/controllers/replicaset_controller_test.go @@ -687,6 +687,43 @@ func assertStatefulsetReady(ctx context.Context, t *testing.T, mgr manager.Manag assert.True(t, statefulset.IsReady(sts, expectedReplicas)) } +func TestService_connectionStringSecretAnnotationsAreApplied(t *testing.T) { + ctx := context.Background() + secretAnnotations := map[string]string{ + "tests.first-annotation": "some-value", + "tests.second-annotation": "other-value", + } + + mdb := newScramReplicaSet(mdbv1.MongoDBUser{ + Name: "testuser", + PasswordSecretRef: mdbv1.SecretKeyReference{ + Name: "password-secret-name", + }, + ScramCredentialsSecretName: "scram-credentials", + ConnectionStringSecretAnnotations: secretAnnotations, + }) + + mgr := client.NewManager(ctx, &mdb) + + err := createUserPasswordSecret(ctx, mgr.Client, mdb, "password-secret-name", "pass") + assert.NoError(t, err) + + r := NewReconciler(mgr, "fake-mongodbRepoUrl", "fake-mongodbImage", "ubi8", AgentImage, "fake-versionUpgradeHookImage", "fake-readinessProbeImage") + res, err := r.Reconcile(ctx, reconcile.Request{NamespacedName: types.NamespacedName{Namespace: mdb.Namespace, Name: mdb.Name}}) + assertReconciliationSuccessful(t, res, err) + assertConnectionStringSecretAnnotations(ctx, t, mgr.Client, mdb, secretAnnotations) +} + +func assertConnectionStringSecretAnnotations(ctx context.Context, t *testing.T, c k8sClient.Client, mdb mdbv1.MongoDBCommunity, expectedAnnotations map[string]string) { + connectionStringSecret := corev1.Secret{} + scramUsers := mdb.GetAuthUsers() + require.Len(t, scramUsers, 1) + secretNamespacedName := types.NamespacedName{Name: scramUsers[0].ConnectionStringSecretName, Namespace: scramUsers[0].ConnectionStringSecretNamespace} + err := c.Get(ctx, secretNamespacedName, &connectionStringSecret) + require.NoError(t, err) + assert.Subset(t, connectionStringSecret.Annotations, expectedAnnotations) +} + func TestService_configuresPrometheusCustomPorts(t *testing.T) { ctx := context.Background() mdb := newTestReplicaSet() diff --git a/mongodb-community-operator/pkg/authentication/authtypes/authtypes.go b/mongodb-community-operator/pkg/authentication/authtypes/authtypes.go index 6a11097ea..8e29e6009 100644 --- a/mongodb-community-operator/pkg/authentication/authtypes/authtypes.go +++ b/mongodb-community-operator/pkg/authentication/authtypes/authtypes.go @@ -74,6 +74,9 @@ type User struct { // ConnectionStringSecretNamespace is the namespace of the secret object created by the operator which exposes the connection strings for the user. ConnectionStringSecretNamespace string `json:"connectionStringSecretNamespace,omitempty"` + // ConnectionStringSecretAnnotations is the annotations of the secret object created by the operator which exposes the connection strings for the user. + ConnectionStringSecretAnnotations map[string]string + // ConnectionStringOptions contains connection string options for this user // These options will be appended at the end of the connection string and // will override any existing options from the resources. diff --git a/mongodb-community-operator/pkg/kube/secret/secret_builder.go b/mongodb-community-operator/pkg/kube/secret/secret_builder.go index 6700ee9b4..8983b17be 100644 --- a/mongodb-community-operator/pkg/kube/secret/secret_builder.go +++ b/mongodb-community-operator/pkg/kube/secret/secret_builder.go @@ -11,6 +11,7 @@ type builder struct { labels map[string]string name string namespace string + annotations map[string]string ownerReferences []metav1.OwnerReference } @@ -24,6 +25,11 @@ func (b *builder) SetNamespace(namespace string) *builder { return b } +func (b *builder) SetAnnotations(annotations map[string]string) *builder { + b.annotations = annotations + return b +} + func (b *builder) SetField(key, value string) *builder { b.data[key] = []byte(value) return b @@ -73,6 +79,7 @@ func (b builder) Build() corev1.Secret { Namespace: b.namespace, OwnerReferences: b.ownerReferences, Labels: b.labels, + Annotations: b.annotations, }, Data: b.data, Type: b.dataType, diff --git a/mongodb-community-operator/test/e2e/mongodbtests/mongodbtests.go b/mongodb-community-operator/test/e2e/mongodbtests/mongodbtests.go index 529fe069e..2735eb40a 100644 --- a/mongodb-community-operator/test/e2e/mongodbtests/mongodbtests.go +++ b/mongodb-community-operator/test/e2e/mongodbtests/mongodbtests.go @@ -203,6 +203,7 @@ func ConnectionStringSecretsAreConfigured(ctx context.Context, mdb *mdbv1.MongoD assert.NoError(t, err) assertEqualOwnerReference(t, "Secret", secretNamespacedName, secret.GetOwnerReferences(), expectedOwnerReference) + containsMetadata(t, secret.ObjectMeta, map[string]string{}, user.ConnectionStringSecretAnnotations, "secret "+secretNamespacedName.Name) } } } @@ -683,6 +684,18 @@ func AddConnectionStringOptionToUser(ctx context.Context, mdb *mdbv1.MongoDBComm } } +func AddConnectionStringAnnotationsToUser(ctx context.Context, mdb *mdbv1.MongoDBCommunity, annotations map[string]string) func(t *testing.T) { + return func(t *testing.T) { + t.Logf("Adding %v to connection string annotations", annotations) + err := e2eutil.UpdateMongoDBResource(ctx, mdb, func(db *mdbv1.MongoDBCommunity) { + db.Spec.Users[0].ConnectionStringSecretAnnotations = annotations + }) + if err != nil { + t.Fatal(err) + } + } +} + func StatefulSetContainerConditionIsTrue(ctx context.Context, mdb *mdbv1.MongoDBCommunity, containerName string, condition func(c corev1.Container) bool) func(*testing.T) { return func(t *testing.T) { sts := appsv1.StatefulSet{} diff --git a/mongodb-community-operator/test/e2e/replica_set_connection_string_options/replica_set_connection_string_options_test.go b/mongodb-community-operator/test/e2e/replica_set_connection_string_options/replica_set_connection_string_options_test.go index 016389a9f..bc895adef 100644 --- a/mongodb-community-operator/test/e2e/replica_set_connection_string_options/replica_set_connection_string_options_test.go +++ b/mongodb-community-operator/test/e2e/replica_set_connection_string_options/replica_set_connection_string_options_test.go @@ -106,4 +106,24 @@ func TestReplicaSetWithConnectionString(t *testing.T) { t.Run("Test SRV Connectivity with generated connection string secret", tester.ConnectivityRejected(ctx, WithURI(mongodbtests.GetSrvConnectionStringForUser(ctx, mdb, scramUser)))) }) + + /** + Connection String Annotations options. + */ + t.Run("Connection String With Annotations", func(t *testing.T) { + t.Run("Resetting Connection String Options", mongodbtests.ResetConnectionStringOptions(ctx, &mdb)) + t.Run("Test Add New Connection String Annotations to Resource", mongodbtests.AddConnectionStringAnnotationsToUser(ctx, &mdb, map[string]string{"mongodbcommunity.mongodb.com/test-annotation": "test-value"})) + t.Run("Test Secrets Are Updated", mongodbtests.MongoDBReachesRunningPhase(ctx, &mdb)) + + scramUser = mdb.GetAuthUsers()[0] + t.Run("Test Basic Connectivity", tester.ConnectivitySucceeds()) + t.Run("Test SRV Connectivity", tester.ConnectivitySucceeds(WithURI(mdb.MongoSRVURI("")), WithoutTls(), WithReplicaSet(mdb.Name))) + t.Run("Test Basic Connectivity with generated connection string secret", + tester.ConnectivitySucceeds(WithURI(mongodbtests.GetConnectionStringForUser(ctx, mdb, scramUser)))) + t.Run("Test SRV Connectivity with generated connection string secret", + tester.ConnectivitySucceeds(WithURI(mongodbtests.GetSrvConnectionStringForUser(ctx, mdb, scramUser)))) + + ownerRef := mdb.GetOwnerReferences()[0] + t.Run("Test Connection String Annotations are as expected", mongodbtests.ConnectionStringSecretsAreConfigured(ctx, &mdb, ownerRef)) + }) } diff --git a/public/crds.yaml b/public/crds.yaml index 00adc4cbd..4fe3425c3 100644 --- a/public/crds.yaml +++ b/public/crds.yaml @@ -7569,6 +7569,13 @@ spec: nullable: true type: object x-kubernetes-preserve-unknown-fields: true + connectionStringSecretAnnotations: + additionalProperties: + type: string + description: ConnectionStringSecretAnnotations is the annotations + of the secret object created by the operator which exposes + the connection strings for the user. + type: object connectionStringSecretName: description: |- ConnectionStringSecretName is the name of the secret object created by the operator which exposes the connection strings for the user.