From 5215a12fdb6519a1b816f3435e5650394148e92b Mon Sep 17 00:00:00 2001 From: Veronika Fisarova Date: Tue, 14 Oct 2025 13:52:03 +0200 Subject: [PATCH] Application Credential support Signed-off-by: Veronika Fisarova --- .../cinder.openstack.org_cinderapis.yaml | 8 + api/bases/cinder.openstack.org_cinders.yaml | 8 + api/go.mod | 2 + api/v1beta1/cinder_webhook.go | 6 +- api/v1beta1/cinderapi_types.go | 13 ++ api/v1beta1/zz_generated.deepcopy.go | 16 ++ .../cinder.openstack.org_cinderapis.yaml | 8 + .../bases/cinder.openstack.org_cinders.yaml | 8 + go.mod | 2 + go.sum | 4 +- internal/controller/cinder_controller.go | 57 +++---- internal/controller/cinderapi_controller.go | 22 +++ .../cinder/config/00-global-defaults.conf | 28 +++- test/functional/cinder_controller_test.go | 143 ++++++++++++++++++ 14 files changed, 280 insertions(+), 45 deletions(-) diff --git a/api/bases/cinder.openstack.org_cinderapis.yaml b/api/bases/cinder.openstack.org_cinderapis.yaml index 104c6d12..9c46944d 100644 --- a/api/bases/cinder.openstack.org_cinderapis.yaml +++ b/api/bases/cinder.openstack.org_cinderapis.yaml @@ -52,6 +52,14 @@ spec: spec: description: CinderAPISpec defines the desired state of CinderAPI properties: + auth: + description: Auth - Parameters related to authentication + properties: + applicationCredentialSecret: + description: ApplicationCredentialSecret - Secret containing Application + Credential ID and Secret + type: string + type: object containerImage: description: ContainerImage - Cinder Container Image URL (will be set to environmental default if empty) diff --git a/api/bases/cinder.openstack.org_cinders.yaml b/api/bases/cinder.openstack.org_cinders.yaml index bfa1b22f..75dbe25c 100644 --- a/api/bases/cinder.openstack.org_cinders.yaml +++ b/api/bases/cinder.openstack.org_cinders.yaml @@ -57,6 +57,14 @@ spec: description: CinderAPI - Spec definition for the API service of this Cinder deployment properties: + auth: + description: Auth - Parameters related to authentication + properties: + applicationCredentialSecret: + description: ApplicationCredentialSecret - Secret containing + Application Credential ID and Secret + type: string + type: object containerImage: description: ContainerImage - Cinder Container Image URL (will be set to environmental default if empty) diff --git a/api/go.mod b/api/go.mod index e90ed4f3..d5d55bbf 100644 --- a/api/go.mod +++ b/api/go.mod @@ -94,3 +94,5 @@ replace k8s.io/component-base => k8s.io/component-base v0.31.14 //allow-merging replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging + +replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20251211085602-3e1a3e022c81 diff --git a/api/v1beta1/cinder_webhook.go b/api/v1beta1/cinder_webhook.go index 20e94d09..32c81ee8 100644 --- a/api/v1beta1/cinder_webhook.go +++ b/api/v1beta1/cinder_webhook.go @@ -29,6 +29,7 @@ import ( topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/common/util" + common_webhook "github.com/openstack-k8s-operators/lib-common/modules/common/webhook" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -36,7 +37,6 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - common_webhook "github.com/openstack-k8s-operators/lib-common/modules/common/webhook" ) // CinderDefaults - @@ -198,7 +198,7 @@ func (spec *CinderSpec) ValidateCreate( func (spec *CinderSpecCore) ValidateCreate( basePath *field.Path, namespace string, -) ([]string, field.ErrorList){ +) ([]string, field.ErrorList) { var allErrs field.ErrorList var allWarns admission.Warnings @@ -486,7 +486,7 @@ func (spec *CinderSpec) ValidateCinderBackup(basePath *field.Path) ([]string, fi return allWarns, allErrs } -func (spec *CinderSpecCore) ValidateCinderBackup(basePath *field.Path)([]string, field.ErrorList) { +func (spec *CinderSpecCore) ValidateCinderBackup(basePath *field.Path) ([]string, field.ErrorList) { var allErrs field.ErrorList var allWarns []string diff --git a/api/v1beta1/cinderapi_types.go b/api/v1beta1/cinderapi_types.go index 2ebea369..033d8a64 100644 --- a/api/v1beta1/cinderapi_types.go +++ b/api/v1beta1/cinderapi_types.go @@ -43,6 +43,11 @@ type CinderAPITemplateCore struct { // +operator-sdk:csv:customresourcedefinitions:type=spec // TLS - Parameters related to the TLS TLS tls.API `json:"tls,omitempty"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // Auth - Parameters related to authentication + Auth AuthSpec `json:"auth,omitempty"` } // CinderAPITemplate defines the input parameters for the Cinder API service @@ -61,6 +66,14 @@ type APIOverrideSpec struct { Service map[service.Endpoint]service.RoutedOverrideSpec `json:"service,omitempty"` } +// AuthSpec defines authentication parameters +type AuthSpec struct { + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // ApplicationCredentialSecret - Secret containing Application Credential ID and Secret + ApplicationCredentialSecret string `json:"applicationCredentialSecret,omitempty"` +} + // CinderAPISpec defines the desired state of CinderAPI type CinderAPISpec struct { // Common input parameters for all Cinder services diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 5cb37ef8..426ca395 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -50,6 +50,21 @@ func (in *APIOverrideSpec) DeepCopy() *APIOverrideSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthSpec) DeepCopyInto(out *AuthSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthSpec. +func (in *AuthSpec) DeepCopy() *AuthSpec { + if in == nil { + return nil + } + out := new(AuthSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Cinder) DeepCopyInto(out *Cinder) { *out = *in @@ -267,6 +282,7 @@ func (in *CinderAPITemplateCore) DeepCopyInto(out *CinderAPITemplateCore) { } in.Override.DeepCopyInto(&out.Override) in.TLS.DeepCopyInto(&out.TLS) + out.Auth = in.Auth } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CinderAPITemplateCore. diff --git a/config/crd/bases/cinder.openstack.org_cinderapis.yaml b/config/crd/bases/cinder.openstack.org_cinderapis.yaml index 104c6d12..9c46944d 100644 --- a/config/crd/bases/cinder.openstack.org_cinderapis.yaml +++ b/config/crd/bases/cinder.openstack.org_cinderapis.yaml @@ -52,6 +52,14 @@ spec: spec: description: CinderAPISpec defines the desired state of CinderAPI properties: + auth: + description: Auth - Parameters related to authentication + properties: + applicationCredentialSecret: + description: ApplicationCredentialSecret - Secret containing Application + Credential ID and Secret + type: string + type: object containerImage: description: ContainerImage - Cinder Container Image URL (will be set to environmental default if empty) diff --git a/config/crd/bases/cinder.openstack.org_cinders.yaml b/config/crd/bases/cinder.openstack.org_cinders.yaml index bfa1b22f..75dbe25c 100644 --- a/config/crd/bases/cinder.openstack.org_cinders.yaml +++ b/config/crd/bases/cinder.openstack.org_cinders.yaml @@ -57,6 +57,14 @@ spec: description: CinderAPI - Spec definition for the API service of this Cinder deployment properties: + auth: + description: Auth - Parameters related to authentication + properties: + applicationCredentialSecret: + description: ApplicationCredentialSecret - Secret containing + Application Credential ID and Secret + type: string + type: object containerImage: description: ContainerImage - Cinder Container Image URL (will be set to environmental default if empty) diff --git a/go.mod b/go.mod index 845ab7c1..ad4c991b 100644 --- a/go.mod +++ b/go.mod @@ -143,3 +143,5 @@ replace k8s.io/component-base => k8s.io/component-base v0.31.14 //allow-merging replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging + +replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20251211085602-3e1a3e022c81 diff --git a/go.sum b/go.sum index 8d186b8c..7bc21e88 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Deydra71/keystone-operator/api v0.0.0-20251211085602-3e1a3e022c81 h1:plax+NFgJJL1SrERyXAnf3jOHRhLTtBlJ2oc7d84EoU= +github.com/Deydra71/keystone-operator/api v0.0.0-20251211085602-3e1a3e022c81/go.mod h1:b98Jl8eyUw8V07l9YiuQnoMlnWC748oV8IhXH15NCC4= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= @@ -120,8 +122,6 @@ github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e h1:E1OdwSpqWuDPCedyU github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e/go.mod h1:Shkl4HanLwDiiBzakv+con/aMGnVE2MAGvoKp5oyYUo= github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260108154501-11e5091cddf1 h1:zAbZVtpldi1TU/CO9aU2ZByzcsi+N3aIv6snpSjBVLY= github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260108154501-11e5091cddf1/go.mod h1:ZXwFlspJCdZEUjMbmaf61t5AMB4u2vMyAMMoe/vJroE= -github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260109123729-8c46aa6cb459 h1:5p1EssFXwriFNoxDZsO8a4r0GnruIVD/d7dNBzcglR8= -github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260109123729-8c46aa6cb459/go.mod h1:djqR/hwPpIld/siGG6JTa3YuI6QGMf4e7aphVRBhlAk= github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251230215914-6ba873b49a35 h1:pF3mJ3nwq6r4qwom+rEWZNquZpcQW/iftHlJ1KPIDsk= github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251230215914-6ba873b49a35/go.mod h1:kycZyoe7OZdW1HUghr2nI3N7wSJtNahXf6b/ypD14f4= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20251230215914-6ba873b49a35 h1:IdcI8DFvW8rXtchONSzbDmhhRp1YyO2YaBJDBXr44Gk= diff --git a/internal/controller/cinder_controller.go b/internal/controller/cinder_controller.go index 7d166dfb..0f3fe6a0 100644 --- a/internal/controller/cinder_controller.go +++ b/internal/controller/cinder_controller.go @@ -27,7 +27,6 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -251,6 +250,7 @@ const ( tlsAPIInternalField = ".spec.tls.api.internal.secretName" tlsAPIPublicField = ".spec.tls.api.public.secretName" topologyField = ".spec.topologyRef.Name" + authAppCredSecretField = ".spec.auth.applicationCredentialSecret" // #nosec G101 ) var ( @@ -265,6 +265,7 @@ var ( tlsAPIInternalField, tlsAPIPublicField, topologyField, + authAppCredSecretField, } ) @@ -368,43 +369,9 @@ func (r *CinderReconciler) SetupWithManager(mgr ctrl.Manager) error { handler.EnqueueRequestsFromMapFunc(transportURLSecretFn)). Watches(&memcachedv1.Memcached{}, handler.EnqueueRequestsFromMapFunc(memcachedFn)). - Watches(&keystonev1.KeystoneAPI{}, - handler.EnqueueRequestsFromMapFunc(r.findObjectForSrc), - builder.WithPredicates(keystonev1.KeystoneAPIStatusChangedPredicate)). Complete(r) } -func (r *CinderReconciler) findObjectForSrc(ctx context.Context, src client.Object) []reconcile.Request { - requests := []reconcile.Request{} - - Log := r.GetLogger(ctx) - - crList := &cinderv1beta1.CinderList{} - listOps := &client.ListOptions{ - Namespace: src.GetNamespace(), - } - err := r.List(ctx, crList, listOps) - if err != nil { - Log.Error(err, fmt.Sprintf("listing %s for namespace: %s", crList.GroupVersionKind().Kind, src.GetNamespace())) - return requests - } - - for _, item := range crList.Items { - Log.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) - - requests = append(requests, - reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: item.GetName(), - Namespace: item.GetNamespace(), - }, - }, - ) - } - - return requests -} - func (r *CinderReconciler) reconcileDelete(ctx context.Context, instance *cinderv1beta1.Cinder, helper *helper.Helper) (ctrl.Result, error) { Log := r.GetLogger(ctx) @@ -1125,6 +1092,26 @@ func (r *CinderReconciler) generateServiceConfigs( } templateParameters["VHosts"] = httpdVhostConfig + // Retrieve Application Credential data from CinderAPI Auth section if specified + Log := r.GetLogger(ctx) + if instance.Spec.CinderAPI.Auth.ApplicationCredentialSecret != "" { + acSecret := &corev1.Secret{} + key := types.NamespacedName{Namespace: instance.Namespace, Name: instance.Spec.CinderAPI.Auth.ApplicationCredentialSecret} + if err := h.GetClient().Get(ctx, key, acSecret); err != nil { + if !k8s_errors.IsNotFound(err) { + Log.Error(err, "Failed to get ApplicationCredential secret", "secret", key) + } + } else { + acID, okID := acSecret.Data[keystonev1.ACIDSecretKey] + acSecretData, okSecret := acSecret.Data[keystonev1.ACSecretSecretKey] + if okID && len(acID) > 0 && okSecret && len(acSecretData) > 0 { + templateParameters["ApplicationCredentialID"] = string(acID) + templateParameters["ApplicationCredentialSecret"] = string(acSecretData) + Log.Info("Using ApplicationCredentials auth from CinderAPI spec", "secret", instance.Spec.CinderAPI.Auth.ApplicationCredentialSecret) + } + } + } + var notificationInstanceURLSecret *corev1.Secret if instance.Status.NotificationsURLSecret != nil { // Get a notificationInstanceURLSecret only if rabbitMQ referenced in diff --git a/internal/controller/cinderapi_controller.go b/internal/controller/cinderapi_controller.go index 8a2fd181..8aa6ced8 100644 --- a/internal/controller/cinderapi_controller.go +++ b/internal/controller/cinderapi_controller.go @@ -338,6 +338,18 @@ func (r *CinderAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Man return err } + // index authAppCredSecretField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &cinderv1beta1.CinderAPI{}, authAppCredSecretField, func(rawObj client.Object) []string { + // Extract the application credential secret name from the spec, if one is provided + cr := rawObj.(*cinderv1beta1.CinderAPI) + if cr.Spec.Auth.ApplicationCredentialSecret == "" { + return nil + } + return []string{cr.Spec.Auth.ApplicationCredentialSecret} + }); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). For(&cinderv1beta1.CinderAPI{}). Owns(&keystonev1.KeystoneService{}). @@ -899,6 +911,16 @@ func (r *CinderAPIReconciler) reconcileNormal(ctx context.Context, instance *cin // normal reconcile tasks // + // Verify Application Credential secret if specified + if instance.Spec.Auth.ApplicationCredentialSecret != "" { + acSecret := types.NamespacedName{Namespace: instance.Namespace, Name: instance.Spec.Auth.ApplicationCredentialSecret} + acHash, _, err := secret.VerifySecret(ctx, acSecret, []string{keystonev1.ACIDSecretKey, keystonev1.ACSecretSecretKey}, helper.GetClient(), 0) + if err == nil && acHash != "" { + // AC secret exists and is valid - add to configVars for hash tracking + configVars[instance.Spec.Auth.ApplicationCredentialSecret] = env.SetValue(acHash) + } + } + // // create hash over all the different input resources to identify if any those changed // and a restart/recreate is required. diff --git a/templates/cinder/config/00-global-defaults.conf b/templates/cinder/config/00-global-defaults.conf index af4912a3..75398796 100644 --- a/templates/cinder/config/00-global-defaults.conf +++ b/templates/cinder/config/00-global-defaults.conf @@ -76,12 +76,18 @@ auth_url = {{ .KeystoneInternalURL }} memcached_servers = {{ .MemcachedServers }} memcache_pool_dead_retry = 10 memcache_pool_conn_get_timeout = 2 +{{ if (index . "ApplicationCredentialID") -}} +auth_type = v3applicationcredential +application_credential_id = {{ .ApplicationCredentialID }} +application_credential_secret = {{ .ApplicationCredentialSecret }} +{{ else -}} auth_type = password +username = {{ .ServiceUser }} +password = {{ .ServicePassword }} project_domain_name = Default user_domain_name = Default project_name = service -username = {{ .ServiceUser }} -password = {{ .ServicePassword }} +{{ end -}} service_token_roles_required = true interface = internal {{if (index . "MemcachedAuthCert")}} @@ -93,20 +99,32 @@ memcache_tls_enabled = true [nova] interface = internal -auth_type = password auth_url = {{ .KeystoneInternalURL }} +{{ if (index . "ApplicationCredentialID") -}} +auth_type = v3applicationcredential +application_credential_id = {{ .ApplicationCredentialID }} +application_credential_secret = {{ .ApplicationCredentialSecret }} +{{ else -}} +auth_type = password username = {{ .ServiceUser }} password = {{ .ServicePassword }} user_domain_name = Default project_name = service project_domain_name = Default +{{ end -}} [service_user] send_service_user_token = True auth_url = {{ .KeystoneInternalURL }} +{{ if (index . "ApplicationCredentialID") -}} +auth_type = v3applicationcredential +application_credential_id = {{ .ApplicationCredentialID }} +application_credential_secret = {{ .ApplicationCredentialSecret }} +{{ else -}} auth_type = password +username = {{ .ServiceUser }} +password = {{ .ServicePassword }} project_domain_name = Default user_domain_name = Default project_name = service -username = {{ .ServiceUser }} -password = {{ .ServicePassword }} +{{ end -}} diff --git a/test/functional/cinder_controller_test.go b/test/functional/cinder_controller_test.go index 3c23ac91..8d9b5837 100644 --- a/test/functional/cinder_controller_test.go +++ b/test/functional/cinder_controller_test.go @@ -29,6 +29,7 @@ import ( corev1 "k8s.io/api/core/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" @@ -37,9 +38,11 @@ import ( "github.com/openstack-k8s-operators/cinder-operator/internal/cinder" memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" + keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" util "github.com/openstack-k8s-operators/lib-common/modules/common/util" mariadb_test "github.com/openstack-k8s-operators/mariadb-operator/api/test/helpers" + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" ) var _ = Describe("Cinder controller", func() { @@ -1805,4 +1808,144 @@ var _ = Describe("Cinder Webhook", func() { var statusError *k8s_errors.StatusError Expect(errors.As(err, &statusError)).To(BeFalse()) }) + + When("An ApplicationCredential is created for Cinder", func() { + var ( + acName string + acSecretName string + servicePasswordSecret string + passwordSelector string + ) + BeforeEach(func() { + servicePasswordSecret = "ac-test-osp-secret" //nolint:gosec // G101 + passwordSelector = "CinderPassword" + + DeferCleanup(k8sClient.Delete, ctx, + CreateCinderMessageBusSecret( + cinderTest.Instance.Namespace, + cinderTest.RabbitmqSecretName, + ), + ) + DeferCleanup(k8sClient.Delete, ctx, + CreateCinderSecret( + cinderTest.Instance.Namespace, servicePasswordSecret)) + + acName = fmt.Sprintf("ac-%s", cinder.ServiceName) + acSecretName = acName + "-secret" + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: cinderTest.Instance.Namespace, + Name: acSecretName, + }, + Data: map[string][]byte{ + keystonev1.ACIDSecretKey: []byte("test-ac-id"), + keystonev1.ACSecretSecretKey: []byte("test-ac-secret"), + }, + } + DeferCleanup(k8sClient.Delete, ctx, secret) + Expect(k8sClient.Create(ctx, secret)).To(Succeed()) + + spec := GetDefaultCinderSpec() + spec["secret"] = servicePasswordSecret + spec["cinderAPI"] = map[string]interface{}{ + "auth": map[string]interface{}{ + "applicationCredentialSecret": acSecretName, + }, + } + DeferCleanup(th.DeleteInstance, CreateCinder(cinderTest.Instance, spec)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + cinderTest.Instance.Namespace, + GetCinder(cinderTest.Instance).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}})) + DeferCleanup(keystone.DeleteKeystoneAPI, + keystone.CreateKeystoneAPI(cinderTest.Instance.Namespace), + ) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(cinderTest.Instance.Namespace, MemcachedInstance, memcachedv1.MemcachedSpec{})) + infra.SimulateMemcachedReady(cinderTest.CinderMemcached) + + // Create MariaDB account and database + acc, accSecret := mariadb.CreateMariaDBAccountAndSecret(cinderTest.Database, mariadbv1.MariaDBAccountSpec{}) + DeferCleanup(k8sClient.Delete, ctx, acc) + DeferCleanup(k8sClient.Delete, ctx, accSecret) + mariadb.CreateMariaDBDatabase(cinderTest.Database.Namespace, cinderTest.Database.Name, mariadbv1.MariaDBDatabaseSpec{}) + DeferCleanup(k8sClient.Delete, ctx, mariadb.GetMariaDBDatabase(cinderTest.Database)) + + ac := &keystonev1.KeystoneApplicationCredential{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: cinderTest.Instance.Namespace, + Name: acName, + }, + Spec: keystonev1.KeystoneApplicationCredentialSpec{ + UserName: cinder.ServiceName, + Secret: servicePasswordSecret, + PasswordSelector: passwordSelector, + Roles: []string{"admin", "member"}, + AccessRules: []keystonev1.ACRule{{Service: "identity", Method: "POST", Path: "/auth/tokens"}}, + ExpirationDays: 30, + GracePeriodDays: 5, + }, + } + DeferCleanup(k8sClient.Delete, ctx, ac) + Expect(k8sClient.Create(ctx, ac)).To(Succeed()) + + fetched := &keystonev1.KeystoneApplicationCredential{} + key := types.NamespacedName{Namespace: ac.Namespace, Name: ac.Name} + Expect(k8sClient.Get(ctx, key, fetched)).To(Succeed()) + + fetched.Status.SecretName = acSecretName + now := metav1.Now() + readyCond := condition.Condition{ + Type: condition.ReadyCondition, + Status: corev1.ConditionTrue, + Reason: condition.ReadyReason, + Message: condition.ReadyMessage, + LastTransitionTime: now, + } + fetched.Status.Conditions = condition.Conditions{readyCond} + Expect(k8sClient.Status().Update(ctx, fetched)).To(Succeed()) + + infra.SimulateTransportURLReady(cinderTest.CinderTransportURL) + mariadb.SimulateMariaDBAccountCompleted(cinderTest.Database) + mariadb.SimulateMariaDBDatabaseCompleted(cinderTest.Database) + + th.SimulateJobSuccess(cinderTest.CinderDBSync) + + keystone.SimulateKeystoneEndpointReady(cinderTest.CinderKeystoneEndpoint) + }) + + It("should render ApplicationCredential auth in CinderAPI config", func() { + keystone.SimulateKeystoneEndpointReady(cinderTest.CinderKeystoneEndpoint) + + Eventually(func(g Gomega) { + api := GetCinderAPI(cinderTest.CinderAPI) + g.Expect(api).NotTo(BeNil()) + g.Expect(api.Spec.Auth.ApplicationCredentialSecret).To(Equal(acSecretName)) + }, timeout, interval).Should(Succeed()) + + // Check CinderAPI config secret for AC auth + Eventually(func(g Gomega) { + apiConfigSecretName := types.NamespacedName{ + Namespace: cinderTest.CinderAPI.Namespace, + Name: cinderTest.CinderAPI.Name + "-config-data", + } + cfgSecret := th.GetSecret(apiConfigSecretName) + g.Expect(cfgSecret).NotTo(BeNil()) + + conf := string(cfgSecret.Data["00-global-defaults.conf"]) + + // AC auth is configured + g.Expect(conf).To(ContainSubstring("auth_type = v3applicationcredential")) + g.Expect(conf).To(ContainSubstring("application_credential_id = test-ac-id")) + g.Expect(conf).To(ContainSubstring("application_credential_secret = test-ac-secret")) + + // Password auth fields should not be present + g.Expect(conf).NotTo(ContainSubstring("auth_type = password")) + g.Expect(conf).NotTo(ContainSubstring("username = cinder")) + g.Expect(conf).NotTo(ContainSubstring("project_name = service")) + }, timeout, interval).Should(Succeed()) + }) + }) })