From 69fddb17eb45faef263d860c6215e57bb47e4832 Mon Sep 17 00:00:00 2001 From: Simon Beck Date: Mon, 14 Oct 2024 13:14:43 +0200 Subject: [PATCH] Add user management to MariaDB It's now possible to do user management with MariaDB. The logic is exactly the same as with PostgreSQL. --- apis/vshn/v1/dbaas_vshn_mariadb.go | 4 +- apis/vshn/v1/dbaas_vshn_postgresql.go | 1 + apis/vshn/v1/zz_generated.deepcopy.go | 9 +- crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml | 1 + crds/vshn.appcat.vshn.io_vshnmariadbs.yaml | 41 +++ crds/vshn.appcat.vshn.io_vshnnextclouds.yaml | 1 + crds/vshn.appcat.vshn.io_vshnpostgresqls.yaml | 1 + crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml | 2 + crds/vshn.appcat.vshn.io_xvshnmariadbs.yaml | 43 +++ crds/vshn.appcat.vshn.io_xvshnnextclouds.yaml | 2 + .../vshn.appcat.vshn.io_xvshnpostgresqls.yaml | 2 + .../functions/vshnmariadb/register.go | 4 + .../functions/vshnmariadb/user_management.go | 265 ++++++++++++++++++ .../vshnmariadb/user_management_test.go | 136 +++++++++ .../functions/vshnpostgres/user_management.go | 14 +- pkg/scheme.go | 2 + .../usermanagement/01-emptyaccess.yaml | 32 +++ 17 files changed, 550 insertions(+), 10 deletions(-) create mode 100644 pkg/comp-functions/functions/vshnmariadb/user_management.go create mode 100644 pkg/comp-functions/functions/vshnmariadb/user_management_test.go create mode 100644 test/functions/vshnmariadb/usermanagement/01-emptyaccess.yaml diff --git a/apis/vshn/v1/dbaas_vshn_mariadb.go b/apis/vshn/v1/dbaas_vshn_mariadb.go index 643af44ed..dfe3d6a84 100644 --- a/apis/vshn/v1/dbaas_vshn_mariadb.go +++ b/apis/vshn/v1/dbaas_vshn_mariadb.go @@ -98,8 +98,8 @@ type VSHNMariaDBServiceSpec struct { // ServiceLevel defines the service level of this service. Either Best Effort or Guaranteed Availability is allowed. ServiceLevel VSHNDBaaSServiceLevel `json:"serviceLevel,omitempty"` - // +kubebuilder:default="standalone" - // +kubebuilder:validation:Enum=replication;standalone + // Access defines additional users and databases for this instance. + Access []VSHNAccess `json:"access,omitempty"` } // VSHNMariaDBTLSSpec contains settings to control tls traffic of a service. diff --git a/apis/vshn/v1/dbaas_vshn_postgresql.go b/apis/vshn/v1/dbaas_vshn_postgresql.go index 4a5ec52a9..285e997c3 100644 --- a/apis/vshn/v1/dbaas_vshn_postgresql.go +++ b/apis/vshn/v1/dbaas_vshn_postgresql.go @@ -153,6 +153,7 @@ type VSHNPostgreSQLServiceSpec struct { // +kubebuilder:default=false VacuumEnabled bool `json:"vacuumEnabled,omitempty"` + // Access defines additional users and databases for this instance. Access []VSHNAccess `json:"access,omitempty"` } diff --git a/apis/vshn/v1/zz_generated.deepcopy.go b/apis/vshn/v1/zz_generated.deepcopy.go index a5f735813..6d3d92c0f 100644 --- a/apis/vshn/v1/zz_generated.deepcopy.go +++ b/apis/vshn/v1/zz_generated.deepcopy.go @@ -510,7 +510,7 @@ func (in *VSHNMariaDBList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VSHNMariaDBParameters) DeepCopyInto(out *VSHNMariaDBParameters) { *out = *in - out.Service = in.Service + in.Service.DeepCopyInto(&out.Service) out.Size = in.Size in.Scheduling.DeepCopyInto(&out.Scheduling) out.TLS = in.TLS @@ -534,6 +534,13 @@ func (in *VSHNMariaDBParameters) DeepCopy() *VSHNMariaDBParameters { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VSHNMariaDBServiceSpec) DeepCopyInto(out *VSHNMariaDBServiceSpec) { *out = *in + if in.Access != nil { + in, out := &in.Access, &out.Access + *out = make([]VSHNAccess, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNMariaDBServiceSpec. diff --git a/crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml b/crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml index dda6eceb2..22345d0c6 100644 --- a/crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml +++ b/crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml @@ -9776,6 +9776,7 @@ spec: description: Service contains PostgreSQL DBaaS specific properties properties: access: + description: Access defines additional users and databases for this instance. items: properties: database: diff --git a/crds/vshn.appcat.vshn.io_vshnmariadbs.yaml b/crds/vshn.appcat.vshn.io_vshnmariadbs.yaml index 30b942c8a..82caf646b 100644 --- a/crds/vshn.appcat.vshn.io_vshnmariadbs.yaml +++ b/crds/vshn.appcat.vshn.io_vshnmariadbs.yaml @@ -4843,6 +4843,47 @@ spec: service: description: Service contains MariaDB DBaaS specific properties properties: + access: + description: Access defines additional users and databases for this instance. + items: + properties: + database: + description: Database is the name of the database to create, defaults to user. + type: string + privileges: + description: |- + Privileges specifies the privileges to grant the user. Please check + the database's docs for available privileges. + items: + type: string + type: array + user: + description: |- + User specifies the username. If all other fields are left empty + then a new database with the same name and all permissions will be created. + type: string + writeConnectionSecretToRef: + description: |- + WriteConnectionSecretToReference specifies the namespace and name of a + Secret to which any connection details for this user should + be written. + If not specified, a secret with the name $claimname-$username will be + created in the namespace where the claim is located. + properties: + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - name + - namespace + type: object + required: + - user + type: object + type: array mariadbSettings: description: MariadbSettings contains additional MariaDB settings. type: string diff --git a/crds/vshn.appcat.vshn.io_vshnnextclouds.yaml b/crds/vshn.appcat.vshn.io_vshnnextclouds.yaml index b1fb79ace..73e9c8e81 100644 --- a/crds/vshn.appcat.vshn.io_vshnnextclouds.yaml +++ b/crds/vshn.appcat.vshn.io_vshnnextclouds.yaml @@ -9742,6 +9742,7 @@ spec: description: Service contains PostgreSQL DBaaS specific properties properties: access: + description: Access defines additional users and databases for this instance. items: properties: database: diff --git a/crds/vshn.appcat.vshn.io_vshnpostgresqls.yaml b/crds/vshn.appcat.vshn.io_vshnpostgresqls.yaml index aee7a52a9..271178a44 100644 --- a/crds/vshn.appcat.vshn.io_vshnpostgresqls.yaml +++ b/crds/vshn.appcat.vshn.io_vshnpostgresqls.yaml @@ -4966,6 +4966,7 @@ spec: description: Service contains PostgreSQL DBaaS specific properties properties: access: + description: Access defines additional users and databases for this instance. items: properties: database: diff --git a/crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml b/crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml index df2117e26..f784d43ba 100644 --- a/crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml +++ b/crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml @@ -11556,6 +11556,8 @@ spec: properties properties: access: + description: Access defines additional users and databases + for this instance. items: properties: database: diff --git a/crds/vshn.appcat.vshn.io_xvshnmariadbs.yaml b/crds/vshn.appcat.vshn.io_xvshnmariadbs.yaml index 7706817bd..6bf286063 100644 --- a/crds/vshn.appcat.vshn.io_xvshnmariadbs.yaml +++ b/crds/vshn.appcat.vshn.io_xvshnmariadbs.yaml @@ -5567,6 +5567,49 @@ spec: service: description: Service contains MariaDB DBaaS specific properties properties: + access: + description: Access defines additional users and databases + for this instance. + items: + properties: + database: + description: Database is the name of the database to + create, defaults to user. + type: string + privileges: + description: |- + Privileges specifies the privileges to grant the user. Please check + the database's docs for available privileges. + items: + type: string + type: array + user: + description: |- + User specifies the username. If all other fields are left empty + then a new database with the same name and all permissions will be created. + type: string + writeConnectionSecretToRef: + description: |- + WriteConnectionSecretToReference specifies the namespace and name of a + Secret to which any connection details for this user should + be written. + If not specified, a secret with the name $claimname-$username will be + created in the namespace where the claim is located. + properties: + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - name + - namespace + type: object + required: + - user + type: object + type: array mariadbSettings: description: MariadbSettings contains additional MariaDB settings. type: string diff --git a/crds/vshn.appcat.vshn.io_xvshnnextclouds.yaml b/crds/vshn.appcat.vshn.io_xvshnnextclouds.yaml index 9f2faa9b2..7cc3825c0 100644 --- a/crds/vshn.appcat.vshn.io_xvshnnextclouds.yaml +++ b/crds/vshn.appcat.vshn.io_xvshnnextclouds.yaml @@ -11520,6 +11520,8 @@ spec: properties properties: access: + description: Access defines additional users and databases + for this instance. items: properties: database: diff --git a/crds/vshn.appcat.vshn.io_xvshnpostgresqls.yaml b/crds/vshn.appcat.vshn.io_xvshnpostgresqls.yaml index 16616f780..9569b3d23 100644 --- a/crds/vshn.appcat.vshn.io_xvshnpostgresqls.yaml +++ b/crds/vshn.appcat.vshn.io_xvshnpostgresqls.yaml @@ -5648,6 +5648,8 @@ spec: description: Service contains PostgreSQL DBaaS specific properties properties: access: + description: Access defines additional users and databases + for this instance. items: properties: database: diff --git a/pkg/comp-functions/functions/vshnmariadb/register.go b/pkg/comp-functions/functions/vshnmariadb/register.go index a1deaeeec..4fe1f57ec 100644 --- a/pkg/comp-functions/functions/vshnmariadb/register.go +++ b/pkg/comp-functions/functions/vshnmariadb/register.go @@ -39,6 +39,10 @@ func init() { Name: "billing", Execute: AddServiceBillingLabel, }, + { + Name: "user-management", + Execute: UserManagement, + }, }, }) } diff --git a/pkg/comp-functions/functions/vshnmariadb/user_management.go b/pkg/comp-functions/functions/vshnmariadb/user_management.go new file mode 100644 index 000000000..d4dc46ada --- /dev/null +++ b/pkg/comp-functions/functions/vshnmariadb/user_management.go @@ -0,0 +1,265 @@ +package vshnmariadb + +import ( + "context" + "fmt" + + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + xfnproto "github.com/crossplane/function-sdk-go/proto/v1beta1" + my1alpha1 "github.com/vshn/appcat/v4/apis/sql/mysql/v1alpha1" + vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" + "github.com/vshn/appcat/v4/pkg/comp-functions/functions/common" + "github.com/vshn/appcat/v4/pkg/comp-functions/runtime" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func UserManagement(ctx context.Context, comp *vshnv1.VSHNMariaDB, svc *runtime.ServiceRuntime) *xfnproto.Result { + + err := svc.GetDesiredComposite(comp) + if err != nil { + return runtime.NewFatalResult(fmt.Errorf("Cannot get composite from function io: %w", err)) + } + + // Nothing defined, let's return early + if len(comp.Spec.Parameters.Service.Access) == 0 { + return nil + } + + addProviderConfig(comp, svc) + + for _, access := range comp.Spec.Parameters.Service.Access { + + userPasswordRef := addUser(comp, svc, *access.User) + + dbname := *access.User + if access.Database != nil { + dbname = *access.Database + } + + addDatabase(comp, svc, dbname) + + addGrants(comp, svc, *access.User, dbname, access.Privileges) + + addConnectionDetail(comp, svc, userPasswordRef, *access.User, dbname, access.WriteConnectionSecretToReference) + } + + return nil +} + +func addUser(comp common.Composite, svc *runtime.ServiceRuntime, username string) string { + secretName, err := common.AddGenericSecret(comp, svc, "userpass-"+username, []string{"userpass"}) + if err != nil { + svc.Log.Error(err, "cannot deploy user password secret") + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot deploy user password secret: %s", err))) + } + + role := &my1alpha1.User{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%s-role", comp.GetName(), username), + Annotations: map[string]string{ + "crossplane.io/external-name": username, + }, + Labels: map[string]string{ + runtime.ProviderConfigIgnoreLabel: "true", + }, + }, + Spec: my1alpha1.UserSpec{ + ForProvider: my1alpha1.UserParameters{}, + ResourceSpec: xpv1.ResourceSpec{ + ProviderConfigReference: &xpv1.Reference{ + Name: comp.GetName(), + }, + }, + }, + } + + err = svc.SetDesiredComposedResource(role, runtime.ComposedOptionProtects(comp.GetName()+"-provider-conf-credentials"), runtime.ComposedOptionProtects(secretName)) + if err != nil { + svc.Log.Error(err, "cannot apply user") + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot apply user: %s", err))) + } + + return secretName +} + +func addConnectionDetail(comp common.Composite, svc *runtime.ServiceRuntime, secretName, username, dbname string, connectionDetailRef *xpv1.SecretReference) { + userpassCD, err := svc.GetObservedComposedResourceConnectionDetails(secretName) + if err != nil { + svc.Log.Error(err, "cannot get userpassword from secret") + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot get userpassword from secret: %s", err))) + } + + compositeCD := svc.GetConnectionDetails() + + url := fmt.Sprintf("mysql://%s:%s@%s:%s", username, userpassCD["userpass"], compositeCD["MARIADB_HOST"], compositeCD["MARIADB_PORT"]) + + om := metav1.ObjectMeta{ + Name: comp.GetLabels()["crossplane.io/claim-name"] + "-" + username, + Namespace: comp.GetClaimNamespace(), + } + if connectionDetailRef != nil { + om.Name = connectionDetailRef.Name + om.Namespace = connectionDetailRef.Namespace + } + + userpassSecret := &corev1.Secret{ + ObjectMeta: om, + Type: corev1.SecretType("connection.crossplane.io/v1alpha1"), + Data: map[string][]byte{ + "MARIADB_USERNAME": []byte(username), + "MARIADB_PASSWORD": userpassCD["userpass"], + "MARIADB_DB": []byte(dbname), + "MARIADB_HOST": compositeCD["MARIADB_HOST"], + "MARIADB_PORT": compositeCD["MARIADB_PORT"], + "MARIADB_URL": []byte(url), + "ca.crt": compositeCD["ca.crt"], + }, + } + + err = svc.SetDesiredKubeObject(userpassSecret, fmt.Sprintf("%s-user-%s", comp.GetName(), username)) + if err != nil { + svc.Log.Error(err, "cannot get userpassword from secret") + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot get userpassword from secret: %s", err))) + } +} + +func addProviderConfig(comp *vshnv1.VSHNMariaDB, svc *runtime.ServiceRuntime) { + cd := svc.GetConnectionDetails() + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "provider-conf-credentials", + Namespace: comp.GetInstanceNamespace(), + }, + Data: map[string][]byte{ + "username": cd["MARIADB_USERNAME"], + "password": cd["MARIADB_PASSWORD"], + "endpoint": cd["MARIADB_HOST"], + "port": cd["MARIADB_PORT"], + }, + } + + err := svc.SetDesiredKubeObject(secret, comp.GetName()+"-provider-conf-credentials", + runtime.KubeOptionProtects("namespace-conditions"), + runtime.KubeOptionProtects("cluster"), + runtime.KubeOptionProtects(comp.GetName()+"-netpol")) + if err != nil { + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot set credential secret for provider-sql: %s", err))) + svc.Log.Error(err, "cannot set credential secret for provider-sql") + } + + tls := "preferred" + if comp.Spec.Parameters.TLS.TLSEnabled { + // We need to skip-verify here as with the postgresql. + // provider-sql does not support custom CA certs. + tls = "skip-verify" + } + + config := &my1alpha1.ProviderConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName(), + }, + Spec: my1alpha1.ProviderConfigSpec{ + TLS: &tls, + Credentials: my1alpha1.ProviderCredentials{ + Source: "MySQLConnectionSecret", + ConnectionSecretRef: &xpv1.SecretReference{ + Name: "provider-conf-credentials", + Namespace: comp.GetInstanceNamespace(), + }, + }, + }, + } + + err = svc.SetDesiredKubeObject(config, comp.GetName()+"-providerconfig") + if err != nil { + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot apply the provider config for provider sql: %s", err))) + svc.Log.Error(err, "cannot apply the provider config for provider sql") + } +} + +// We check if the database is already specified. +// If not it will be added. +// This should handle cases where there are mutliple users pointing to the same +// database, and one is deleted, that the database is not dropped. +func addDatabase(comp common.Composite, svc *runtime.ServiceRuntime, name string) { + resname := fmt.Sprintf("%s-%s-database", comp.GetName(), name) + + xdb := &my1alpha1.Database{} + + // If there's a database with the same name we will just return + err := svc.GetDesiredComposedResourceByName(xdb, resname) + if err == nil { + return + } + if err != runtime.ErrNotFound { + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot check if database exists: %s", err))) + svc.Log.Error(err, "cannot check if database exists") + } + + xdb = &my1alpha1.Database{ + ObjectMeta: metav1.ObjectMeta{ + Name: resname, + Annotations: map[string]string{ + "crossplane.io/external-name": name, + }, + Labels: map[string]string{ + runtime.ProviderConfigIgnoreLabel: "true", + }, + }, + Spec: my1alpha1.DatabaseSpec{ + ForProvider: my1alpha1.DatabaseParameters{}, + ResourceSpec: xpv1.ResourceSpec{ + ProviderConfigReference: &xpv1.Reference{ + Name: comp.GetName(), + }, + }, + }, + } + + err = svc.SetDesiredComposedResource(xdb, runtime.ComposedOptionProtects(comp.GetName()+"-provider-conf-credentials")) + if err != nil { + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot apply database: %s", err))) + svc.Log.Error(err, "cannot apply database") + } +} + +func addGrants(comp common.Composite, svc *runtime.ServiceRuntime, username, dbname string, privileges []string) { + privs := []my1alpha1.GrantPrivilege{} + + if len(privileges) == 0 { + privs = append(privs, "ALL") + } + + for _, priv := range privileges { + privs = append(privs, my1alpha1.GrantPrivilege(priv)) + } + + grant := &my1alpha1.Grant{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%s-%s-grants", comp.GetName(), username, dbname), + Labels: map[string]string{ + runtime.ProviderConfigIgnoreLabel: "true", + }, + }, + Spec: my1alpha1.GrantSpec{ + ForProvider: my1alpha1.GrantParameters{ + Privileges: privs, + User: &username, + Database: &dbname, + }, + ResourceSpec: xpv1.ResourceSpec{ + ProviderConfigReference: &xpv1.Reference{ + Name: comp.GetName(), + }, + }, + }, + } + + err := svc.SetDesiredComposedResource(grant, runtime.ComposedOptionProtects(comp.GetName()+"-provider-conf-credentials")) + if err != nil { + svc.AddResult(runtime.NewWarningResult(fmt.Sprintf("cannot apply database: %s", err))) + svc.Log.Error(err, "cannot apply database") + } +} diff --git a/pkg/comp-functions/functions/vshnmariadb/user_management_test.go b/pkg/comp-functions/functions/vshnmariadb/user_management_test.go new file mode 100644 index 000000000..127189149 --- /dev/null +++ b/pkg/comp-functions/functions/vshnmariadb/user_management_test.go @@ -0,0 +1,136 @@ +package vshnmariadb + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + my1alpha1 "github.com/vshn/appcat/v4/apis/sql/mysql/v1alpha1" + pgv1alpha1 "github.com/vshn/appcat/v4/apis/sql/postgresql/v1alpha1" + vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" + "github.com/vshn/appcat/v4/pkg/comp-functions/functions/commontest" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" +) + +func Test_addProviderConfig(t *testing.T) { + // given + svc := commontest.LoadRuntimeFromFile(t, "vshnmariadb/usermanagement/01-emptyaccess.yaml") + + // when + comp := &vshnv1.VSHNMariaDB{} + assert.NoError(t, svc.GetObservedComposite(comp)) + addProviderConfig(comp, svc) + + // then + secret := &corev1.Secret{} + assert.NoError(t, svc.GetDesiredKubeObject(secret, comp.GetName()+"-provider-conf-credentials")) + + config := &pgv1alpha1.ProviderConfig{} + assert.NoError(t, svc.GetDesiredKubeObject(config, comp.GetName()+"-providerconfig")) + assert.Equal(t, comp.GetInstanceNamespace(), secret.GetNamespace()) + +} + +func Test_addUser(t *testing.T) { + // given + svc := commontest.LoadRuntimeFromFile(t, "vshnmariadb/usermanagement/01-emptyaccess.yaml") + + // when + comp := &vshnv1.VSHNMariaDB{} + assert.NoError(t, svc.GetObservedComposite(comp)) + + addUser(comp, svc, "unit") + + // then + role := &pgv1alpha1.Role{} + assert.NoError(t, svc.GetDesiredComposedResourceByName(role, fmt.Sprintf("%s-%s-role", comp.GetName(), "unit"))) + +} + +func Test_addDatabase(t *testing.T) { + // given + svc := commontest.LoadRuntimeFromFile(t, "vshnmariadb/usermanagement/01-emptyaccess.yaml") + + // when + comp := &vshnv1.VSHNMariaDB{} + assert.NoError(t, svc.GetObservedComposite(comp)) + + addDatabase(comp, svc, "unit") + + // then + db := &my1alpha1.Database{} + assert.NoError(t, svc.GetDesiredComposedResourceByName(db, fmt.Sprintf("%s-%s-database", comp.GetName(), "unit"))) + +} + +func Test_addGrants(t *testing.T) { + // given + svc := commontest.LoadRuntimeFromFile(t, "vshnmariadb/usermanagement/01-emptyaccess.yaml") + + // when + comp := &vshnv1.VSHNMariaDB{} + assert.NoError(t, svc.GetObservedComposite(comp)) + + addGrants(comp, svc, "unit", "unit", []string{"ALL"}) + + // then + grant := &my1alpha1.Grant{} + assert.NoError(t, svc.GetDesiredComposedResourceByName(grant, fmt.Sprintf("%s-%s-%s-grants", comp.GetName(), "unit", "unit"))) + +} + +func TestUserManagement(t *testing.T) { + // given with empty accesss object + svc := commontest.LoadRuntimeFromFile(t, "vshnmariadb/usermanagement/01-emptyaccess.yaml") + + // when applied + assert.Nil(t, UserManagement(context.TODO(), &vshnv1.VSHNMariaDB{}, svc)) + + // then expect no database + comp := &vshnv1.VSHNMariaDB{} + assert.NoError(t, svc.GetObservedComposite(comp)) + + db := &my1alpha1.Database{} + assert.Error(t, svc.GetDesiredComposedResourceByName(db, fmt.Sprintf("%s-%s-database", comp.GetName(), "prod"))) + + // when adding an user + comp.Spec.Parameters.Service.Access = []vshnv1.VSHNAccess{ + { + User: ptr.To("prod"), + }, + } + + assert.NoError(t, svc.SetDesiredCompositeStatus(comp)) + assert.Nil(t, UserManagement(context.TODO(), &vshnv1.VSHNMariaDB{}, svc)) + + // then expect database + db = &my1alpha1.Database{} + assert.NoError(t, svc.GetDesiredComposedResourceByName(db, fmt.Sprintf("%s-%s-database", comp.GetName(), "prod"))) + + // when adding user pointing to same db + comp.Spec.Parameters.Service.Access = append(comp.Spec.Parameters.Service.Access, vshnv1.VSHNAccess{ + User: ptr.To("another"), + Database: ptr.To("prod"), + }) + + assert.NoError(t, svc.SetDesiredCompositeStatus(comp)) + assert.Nil(t, UserManagement(context.TODO(), &vshnv1.VSHNMariaDB{}, svc)) + + // then expect database + db = &my1alpha1.Database{} + assert.NoError(t, svc.GetDesiredComposedResourceByName(db, fmt.Sprintf("%s-%s-database", comp.GetName(), "prod"))) + + // When deleting user pointing to same db + comp.Spec.Parameters.Service.Access = []vshnv1.VSHNAccess{ + { + User: ptr.To("another"), + Database: ptr.To("prod"), + }, + } + + // then expect database + db = &my1alpha1.Database{} + assert.NoError(t, svc.GetDesiredComposedResourceByName(db, fmt.Sprintf("%s-%s-database", comp.GetName(), "prod"))) +} diff --git a/pkg/comp-functions/functions/vshnpostgres/user_management.go b/pkg/comp-functions/functions/vshnpostgres/user_management.go index 532bb85a4..3510cb5da 100644 --- a/pkg/comp-functions/functions/vshnpostgres/user_management.go +++ b/pkg/comp-functions/functions/vshnpostgres/user_management.go @@ -22,7 +22,7 @@ func UserManagement(ctx context.Context, comp *vshnv1.VSHNPostgreSQL, svc *runti } // Nothing defined, let's return early - if comp.Spec.Parameters.Service.Access == nil || len(comp.Spec.Parameters.Service.Access) == 0 { + if len(comp.Spec.Parameters.Service.Access) == 0 { return nil } @@ -47,7 +47,7 @@ func UserManagement(ctx context.Context, comp *vshnv1.VSHNPostgreSQL, svc *runti return nil } -func addUser(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime, username string) string { +func addUser(comp common.Composite, svc *runtime.ServiceRuntime, username string) string { secretName, err := common.AddGenericSecret(comp, svc, "userpass-"+username, []string{"userpass"}) if err != nil { svc.Log.Error(err, "cannot deploy user password secret") @@ -72,7 +72,7 @@ func addUser(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime, username PasswordSecretRef: &xpv1.SecretKeySelector{ SecretReference: xpv1.SecretReference{ Name: secretName, - Namespace: comp.Status.InstanceNamespace, + Namespace: comp.GetInstanceNamespace(), }, Key: "userpass", }, @@ -94,7 +94,7 @@ func addUser(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime, username return secretName } -func addConnectionDetail(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime, secretName, username, dbname string, connectionDetailRef *xpv1.SecretReference) { +func addConnectionDetail(comp common.Composite, svc *runtime.ServiceRuntime, secretName, username, dbname string, connectionDetailRef *xpv1.SecretReference) { userpassCD, err := svc.GetObservedComposedResourceConnectionDetails(secretName) if err != nil { svc.Log.Error(err, "cannot get userpassword from secret") @@ -137,7 +137,7 @@ func addConnectionDetail(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntim } } -func addProviderConfig(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime) { +func addProviderConfig(comp common.Composite, svc *runtime.ServiceRuntime) { cd := svc.GetConnectionDetails() secret := &corev1.Secret{ @@ -191,7 +191,7 @@ func addProviderConfig(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime) // If not it will be added. // This should handle cases where there are mutliple users pointing to the same // database, and one is deleted, that the database is not dropped. -func addDatabase(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime, name string) { +func addDatabase(comp common.Composite, svc *runtime.ServiceRuntime, name string) { resname := fmt.Sprintf("%s-%s-database", comp.GetName(), name) xdb := &pgv1alpha1.Database{} @@ -233,7 +233,7 @@ func addDatabase(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime, name } } -func addGrants(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime, username, dbname string, privileges []string) { +func addGrants(comp common.Composite, svc *runtime.ServiceRuntime, username, dbname string, privileges []string) { privs := []pgv1alpha1.GrantPrivilege{} if len(privileges) == 0 { diff --git a/pkg/scheme.go b/pkg/scheme.go index 8fc92c000..7499ac06b 100644 --- a/pkg/scheme.go +++ b/pkg/scheme.go @@ -11,6 +11,7 @@ import ( promv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" alertmanagerv1alpha1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1alpha1" xkube "github.com/vshn/appcat/v4/apis/kubernetes/v1alpha2" + my1alpha1 "github.com/vshn/appcat/v4/apis/sql/mysql/v1alpha1" pgv1alpha1 "github.com/vshn/appcat/v4/apis/sql/postgresql/v1alpha1" stackgresv1 "github.com/vshn/appcat/v4/apis/stackgres/v1" stackgresv1beta1 "github.com/vshn/appcat/v4/apis/stackgres/v1beta1" @@ -62,4 +63,5 @@ func AddToScheme(s *runtime.Scheme) { _ = cloudscalev1.SchemeBuilder.AddToScheme(s) _ = exoscalev1.SchemeBuilder.AddToScheme(s) _ = spksv1alpha1.SchemeBuilder.AddToScheme(s) + _ = my1alpha1.SchemeBuilder.AddToScheme(s) } diff --git a/test/functions/vshnmariadb/usermanagement/01-emptyaccess.yaml b/test/functions/vshnmariadb/usermanagement/01-emptyaccess.yaml new file mode 100644 index 000000000..fbce0fe64 --- /dev/null +++ b/test/functions/vshnmariadb/usermanagement/01-emptyaccess.yaml @@ -0,0 +1,32 @@ +desired: + composite: + connection_details: + MARIADB_USER: cm9vdA== #root + MARIADB_PASSWORD: cm9vdA== #root + MARIADB_HOST: bG9jYWxob3N0IC1uCg== #localhost + MARIADB_PORT: NTQzMgo= + resource: + apiVersion: vshn.appcat.vshn.io/v1 + kind: XVSHNMariaDB + metadata: + name: my-gc9x4 + spec: + parameters: null + writeConnectionSecretToRef: {} + status: {} +observed: + composite: + connection_details: + MARIADB_USER: cm9vdA== #root + MARIADB_PASSWORD: cm9vdA== #root + MARIADB_HOST: bG9jYWxob3N0IC1uCg== #localhost + MARIADB_PORT: NTQzMgo= + resource: + apiVersion: vshn.appcat.vshn.io/v1 + kind: XVSHNMariaDB + metadata: + name: my-gc9x4 + spec: + parameters: null + writeConnectionSecretToRef: {} + status: {}