diff --git a/api/bases/placement.openstack.org_placementapis.yaml b/api/bases/placement.openstack.org_placementapis.yaml index 73ef0098..8608b73b 100644 --- a/api/bases/placement.openstack.org_placementapis.yaml +++ b/api/bases/placement.openstack.org_placementapis.yaml @@ -412,6 +412,13 @@ spec: status: description: PlacementAPIStatus defines the observed state of PlacementAPI properties: + applicationCredentialSecret: + description: |- + ApplicationCredentialSecret - the AC secret placement is currently + consuming and protecting with the openstack.org/placementapi-ac-consumer + finalizer. Tracked so the controller can remove its finalizer from the + old secret when the openstack-operator rotates the reference. + type: string conditions: description: Conditions items: diff --git a/api/go.mod b/api/go.mod index b0167096..561612f5 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,8 +3,8 @@ module github.com/openstack-k8s-operators/placement-operator/api go 1.24.4 require ( - github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260401053442-e06fc1a33511 - github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260331122750-ecff41ebb61d + github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260416122644-5476763a36b6 + github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981 k8s.io/api v0.31.14 k8s.io/apimachinery v0.31.14 sigs.k8s.io/controller-runtime v0.19.7 @@ -15,7 +15,6 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect - github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect @@ -93,3 +92,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-20260424093804-00a0ccdc9d20 diff --git a/api/go.sum b/api/go.sum index d0a0a629..87236cd0 100644 --- a/api/go.sum +++ b/api/go.sum @@ -78,10 +78,10 @@ github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= -github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260401053442-e06fc1a33511 h1:W7AlsQoccL1UtW9FqF82Owz/8n8hmJ29/X5+2bCRUHQ= -github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260401053442-e06fc1a33511/go.mod h1:EKtKXGi4O7t/oExH1rGylTHAO5/LasNrf40a3ATT6uI= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260331122750-ecff41ebb61d h1:qbH09BzypLy1+N133JVgfkRmDZaQKpDLwi/InqqOzGM= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260331122750-ecff41ebb61d/go.mod h1:XUUV+h1nZC4kra5oF+cXPkviWYJ3ELhccHxnVO7CvQQ= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260416122644-5476763a36b6 h1:117Gu9HCSu2tAp579WnCJ9QtnslH2qnPB8UFvn8ZpqE= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260416122644-5476763a36b6/go.mod h1:i7l8cihvFktd/LSuyvL2z6OcwauarQGoVhDMePL4VyI= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981 h1:v1viH0gmNb+AXMg/0GxDcj8VUTdjVLotfOIGrNyMxHk= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981/go.mod h1:I/VBXZLdjk8DUGsEbB+Ha72JBFYYntP7Pm2FpEto9K8= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/api/v1beta1/placementapi_types.go b/api/v1beta1/placementapi_types.go index 3354e3b5..bdaf922a 100644 --- a/api/v1beta1/placementapi_types.go +++ b/api/v1beta1/placementapi_types.go @@ -182,6 +182,12 @@ type PlacementAPIStatus struct { // LastAppliedTopology - the last applied Topology LastAppliedTopology *topologyv1.TopoRef `json:"lastAppliedTopology,omitempty"` + + // ApplicationCredentialSecret - the AC secret placement is currently + // consuming and protecting with the openstack.org/placementapi-ac-consumer + // finalizer. Tracked so the controller can remove its finalizer from the + // old secret when the openstack-operator rotates the reference. + ApplicationCredentialSecret string `json:"applicationCredentialSecret,omitempty"` } // PlacementAPI is the Schema for the placementapis API diff --git a/config/crd/bases/placement.openstack.org_placementapis.yaml b/config/crd/bases/placement.openstack.org_placementapis.yaml index 73ef0098..8608b73b 100644 --- a/config/crd/bases/placement.openstack.org_placementapis.yaml +++ b/config/crd/bases/placement.openstack.org_placementapis.yaml @@ -412,6 +412,13 @@ spec: status: description: PlacementAPIStatus defines the observed state of PlacementAPI properties: + applicationCredentialSecret: + description: |- + ApplicationCredentialSecret - the AC secret placement is currently + consuming and protecting with the openstack.org/placementapi-ac-consumer + finalizer. Tracked so the controller can remove its finalizer from the + old secret when the openstack-operator rotates the reference. + type: string conditions: description: Conditions items: diff --git a/go.mod b/go.mod index 916e1372..f2822114 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,10 @@ require ( github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 github.com/onsi/ginkgo/v2 v2.28.1 github.com/onsi/gomega v1.39.1 - github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260401053442-e06fc1a33511 + github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260416122644-5476763a36b6 github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260321081256-de45f3b1de4f - github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260331122750-ecff41ebb61d - github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260331122750-ecff41ebb61d + github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981 + github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260417092244-81c71b39e981 github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260403163210-0e5eb2ea6a9c github.com/openstack-k8s-operators/placement-operator/api v0.3.1-0.20240216174613-3d349f26e681 k8s.io/api v0.31.14 @@ -60,14 +60,13 @@ require ( github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/openshift/api v3.9.0+incompatible // indirect - github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260320125710-3a5f82ff0f18 // indirect - github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260320125710-3a5f82ff0f18 // indirect + github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260417092244-81c71b39e981 // indirect + github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260417092244-81c71b39e981 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect - github.com/rabbitmq/cluster-operator/v2 v2.16.0 // indirect github.com/spf13/cobra v1.9.1 // indirect github.com/spf13/pflag v1.0.7 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect @@ -142,3 +141,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-20260424093804-00a0ccdc9d20 diff --git a/go.sum b/go.sum index dc2b9261..492e4d18 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Deydra71/keystone-operator/api v0.0.0-20260424093804-00a0ccdc9d20 h1:iyxfh2SDvQrOrsHItYAE3A3+8Ku9UnzWAq9jnLJDLjg= +github.com/Deydra71/keystone-operator/api v0.0.0-20260424093804-00a0ccdc9d20/go.mod h1:SpO4CL7c5/1HG+61fP6kWhL2+3aqR+5SNatdZueKrz8= 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= @@ -118,22 +120,18 @@ github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e h1:E1OdwSpqWuDPCedyUt0GEdoAE+r5TXy7YS21yNEo+2U= 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.20260401053442-e06fc1a33511 h1:W7AlsQoccL1UtW9FqF82Owz/8n8hmJ29/X5+2bCRUHQ= -github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260401053442-e06fc1a33511/go.mod h1:EKtKXGi4O7t/oExH1rGylTHAO5/LasNrf40a3ATT6uI= -github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260321081256-de45f3b1de4f h1:60I2YLHRznTY2BQXqXWc+ByJ3ipdQgKgW52t9J8C5DY= -github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260321081256-de45f3b1de4f/go.mod h1:8o6LSPt1VAvvB2ngS2QObGS6HEikSdVpHoKIgmb78KI= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260331122750-ecff41ebb61d h1:qbH09BzypLy1+N133JVgfkRmDZaQKpDLwi/InqqOzGM= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260331122750-ecff41ebb61d/go.mod h1:XUUV+h1nZC4kra5oF+cXPkviWYJ3ELhccHxnVO7CvQQ= -github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260320125710-3a5f82ff0f18 h1:eJDwc8LPJg+H4bHMLh/pDJBk+OezQ+wkjUNpExUFhbM= -github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260320125710-3a5f82ff0f18/go.mod h1:7yqbVpg0k0vW+kZks+TMU/cd1ovoejyHfVPWcyGYLHI= -github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260320125710-3a5f82ff0f18 h1:9lbaamHYKiWQq/Vth69GGNxQz67qC+PbeQdkXvUEeNo= -github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260320125710-3a5f82ff0f18/go.mod h1:3loLaPUDQyvbPekylZd9OCLF+EXH2klRI9IeeQhuMcs= -github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260331122750-ecff41ebb61d h1:R+04x9fcBpJqP109FIYoKonaCQOmDeqkjVzL5OCLNLs= -github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260331122750-ecff41ebb61d/go.mod h1:dEjz8zHRIlP3vnMmWdHytlLeSZ6BHcIiSTPM7xTQxFg= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260416122644-5476763a36b6 h1:117Gu9HCSu2tAp579WnCJ9QtnslH2qnPB8UFvn8ZpqE= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260416122644-5476763a36b6/go.mod h1:i7l8cihvFktd/LSuyvL2z6OcwauarQGoVhDMePL4VyI= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981 h1:v1viH0gmNb+AXMg/0GxDcj8VUTdjVLotfOIGrNyMxHk= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260417092244-81c71b39e981/go.mod h1:I/VBXZLdjk8DUGsEbB+Ha72JBFYYntP7Pm2FpEto9K8= +github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260417092244-81c71b39e981 h1:jN3Kvt+RYUTaL9EXeeeIqRXVjqeNF74SuLTDXmi4X2Y= +github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260417092244-81c71b39e981/go.mod h1:7yqbVpg0k0vW+kZks+TMU/cd1ovoejyHfVPWcyGYLHI= +github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260417092244-81c71b39e981 h1:X3/Gc+i0ZxaROExrpLXonz9EPhftlubFnOK4aSkRLvo= +github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260417092244-81c71b39e981/go.mod h1:3loLaPUDQyvbPekylZd9OCLF+EXH2klRI9IeeQhuMcs= +github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260417092244-81c71b39e981 h1:KAQ8T+Ri3JWgsyK1D6QybScMh6fpkYUUA+0ntnOiAl4= +github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260417092244-81c71b39e981/go.mod h1:dEjz8zHRIlP3vnMmWdHytlLeSZ6BHcIiSTPM7xTQxFg= github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260403163210-0e5eb2ea6a9c h1:Ia5Q1cqldbKSO6xZhmYGl3cOhdiL3qvpLAD+qjutRio= github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260403163210-0e5eb2ea6a9c/go.mod h1:gwMbEqWORY6TTW1Dk8OC4xzufRYRUYeXg0kqO1cNosE= -github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec h1:saovr368HPAKHN0aRPh8h8n9s9dn3d8Frmfua0UYRlc= -github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec/go.mod h1:Nh2NEePLjovUQof2krTAg4JaAoLacqtPTZQXK6izNfg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/internal/controller/placementapi_controller.go b/internal/controller/placementapi_controller.go index 26743f19..dfb299ff 100644 --- a/internal/controller/placementapi_controller.go +++ b/internal/controller/placementapi_controller.go @@ -456,6 +456,22 @@ func (r *PlacementAPIReconciler) Reconcile(ctx context.Context, req ctrl.Request return ctrl.Result{}, nil } + if instance.Spec.Auth.ApplicationCredentialSecret != "" || instance.Status.ApplicationCredentialSecret != "" { + if err := keystonev1.ManageACSecretFinalizer(ctx, h, instance.Namespace, + instance.Spec.Auth.ApplicationCredentialSecret, + instance.Status.ApplicationCredentialSecret, + placement.ACConsumerFinalizer); err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.ServiceConfigReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.ServiceConfigReadyErrorMessage, + err.Error())) + return ctrl.Result{}, err + } + } + instance.Status.ApplicationCredentialSecret = instance.Spec.Auth.ApplicationCredentialSecret + instance.Status.Conditions.MarkTrue(condition.ServiceConfigReadyCondition, condition.ServiceConfigReadyMessage) serviceAnnotations, result, err := r.ensureNetworkAttachments(ctx, h, instance) @@ -1095,6 +1111,17 @@ func (r *PlacementAPIReconciler) reconcileDelete(ctx context.Context, instance * } } + // Remove consumer finalizer from AC secrets placement was consuming. + for _, secretName := range []string{ + instance.Status.ApplicationCredentialSecret, + instance.Spec.Auth.ApplicationCredentialSecret, + } { + if err := keystonev1.RemoveACSecretConsumerFinalizer(ctx, helper, instance.Namespace, + secretName, placement.ACConsumerFinalizer); err != nil { + return ctrl.Result{}, err + } + } + // We did all the cleanup on the objects we created so we can remove the // finalizer from ourselves to allow the deletion controllerutil.RemoveFinalizer(instance, helper.GetFinalizer()) diff --git a/internal/placement/const.go b/internal/placement/const.go index f058387e..b678734d 100644 --- a/internal/placement/const.go +++ b/internal/placement/const.go @@ -36,4 +36,7 @@ const ( // PlacementUserID is the linux user ID used by Kolla for the placement // user in the service containers PlacementUserID int64 = 42482 + + // ACConsumerFinalizer is added to AC secrets that placement is actively consuming + ACConsumerFinalizer = "openstack.org/placementapi-ac-consumer" ) diff --git a/test/functional/placementapi_controller_test.go b/test/functional/placementapi_controller_test.go index 93da1e90..710def21 100644 --- a/test/functional/placementapi_controller_test.go +++ b/test/functional/placementapi_controller_test.go @@ -32,6 +32,7 @@ import ( mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" "github.com/openstack-k8s-operators/placement-operator/internal/placement" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ) @@ -1526,4 +1527,259 @@ var _ = Describe("PlacementAPI reconfiguration", func() { }) }) + When("ApplicationCredential consumer finalizer is managed", func() { + var acSecretName string + + BeforeEach(func() { + acSecretName = "ac-placement-a1b2c-secret" //nolint:gosec // G101 + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: names.Namespace, + Name: acSecretName, + }, + Data: map[string][]byte{ + keystonev1.ACIDSecretKey: []byte("a1b2ctest-ac-id"), + keystonev1.ACSecretSecretKey: []byte("test-ac-secret"), + }, + } + DeferCleanup(k8sClient.Delete, ctx, secret) + Expect(k8sClient.Create(ctx, secret)).To(Succeed()) + + spec := GetDefaultPlacementAPISpec() + spec["auth"] = map[string]any{ + "applicationCredentialSecret": acSecretName, + } + + DeferCleanup(th.DeleteInstance, CreatePlacementAPI(names.PlacementAPIName, spec)) + DeferCleanup( + k8sClient.Delete, ctx, CreatePlacementAPISecret(names.Namespace, SecretName)) + keystoneAPIName := keystone.CreateKeystoneAPI(names.Namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + names.Namespace, + GetDefaultPlacementAPISpec()["databaseInstance"].(string), + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + }) + + It("should add the consumer finalizer to the AC secret", func() { + Eventually(func(g Gomega) { + secret := th.GetSecret(types.NamespacedName{ + Namespace: names.Namespace, + Name: acSecretName, + }) + g.Expect(secret.Finalizers).To( + ContainElement(placement.ACConsumerFinalizer)) + }, timeout, interval).Should(Succeed()) + }) + + It("should track the consumed AC secret in status", func() { + Eventually(func(g Gomega) { + p := GetPlacementAPI(names.PlacementAPIName) + g.Expect(p.Status.ApplicationCredentialSecret).To(Equal(acSecretName)) + }, timeout, interval).Should(Succeed()) + }) + + It("should move the finalizer from the old to the new secret on rotation", func() { + Eventually(func(g Gomega) { + secret := th.GetSecret(types.NamespacedName{ + Namespace: names.Namespace, + Name: acSecretName, + }) + g.Expect(secret.Finalizers).To( + ContainElement(placement.ACConsumerFinalizer)) + }, timeout, interval).Should(Succeed()) + + newACSecretName := "ac-placement-x9y8z-secret" //nolint:gosec // G101 + newSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: names.Namespace, + Name: newACSecretName, + }, + Data: map[string][]byte{ + keystonev1.ACIDSecretKey: []byte("x9y8zrotated-ac-id"), + keystonev1.ACSecretSecretKey: []byte("rotated-ac-secret"), + }, + } + DeferCleanup(k8sClient.Delete, ctx, newSecret) + Expect(k8sClient.Create(ctx, newSecret)).To(Succeed()) + + Eventually(func(g Gomega) { + p := GetPlacementAPI(names.PlacementAPIName) + p.Spec.Auth.ApplicationCredentialSecret = newACSecretName + g.Expect(k8sClient.Update(ctx, p)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + secret := th.GetSecret(types.NamespacedName{ + Namespace: names.Namespace, + Name: newACSecretName, + }) + g.Expect(secret.Finalizers).To( + ContainElement(placement.ACConsumerFinalizer)) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + secret := th.GetSecret(types.NamespacedName{ + Namespace: names.Namespace, + Name: acSecretName, + }) + g.Expect(secret.Finalizers).NotTo( + ContainElement(placement.ACConsumerFinalizer)) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + p := GetPlacementAPI(names.PlacementAPIName) + g.Expect(p.Status.ApplicationCredentialSecret).To(Equal(newACSecretName)) + }, timeout, interval).Should(Succeed()) + }) + + It("should remove the consumer finalizer from AC secret on CR deletion", func() { + Eventually(func(g Gomega) { + secret := th.GetSecret(types.NamespacedName{ + Namespace: names.Namespace, + Name: acSecretName, + }) + g.Expect(secret.Finalizers).To( + ContainElement(placement.ACConsumerFinalizer)) + }, timeout, interval).Should(Succeed()) + + th.DeleteInstance(GetPlacementAPI(names.PlacementAPIName)) + + secret := th.GetSecret(types.NamespacedName{ + Namespace: names.Namespace, + Name: acSecretName, + }) + Expect(secret.Finalizers).NotTo( + ContainElement(placement.ACConsumerFinalizer)) + }) + + It("should remove the consumer finalizer when AC auth is cleared from spec", func() { + Eventually(func(g Gomega) { + secret := th.GetSecret(types.NamespacedName{ + Namespace: names.Namespace, + Name: acSecretName, + }) + g.Expect(secret.Finalizers).To( + ContainElement(placement.ACConsumerFinalizer)) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + p := GetPlacementAPI(names.PlacementAPIName) + p.Spec.Auth.ApplicationCredentialSecret = "" + g.Expect(k8sClient.Update(ctx, p)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + secret := th.GetSecret(types.NamespacedName{ + Namespace: names.Namespace, + Name: acSecretName, + }) + g.Expect(secret.Finalizers).NotTo( + ContainElement(placement.ACConsumerFinalizer)) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + p := GetPlacementAPI(names.PlacementAPIName) + g.Expect(p.Status.ApplicationCredentialSecret).To(BeEmpty()) + }, timeout, interval).Should(Succeed()) + }) + }) + + When("ApplicationCredential secret is not found", func() { + BeforeEach(func() { + DeferCleanup( + k8sClient.Delete, ctx, CreatePlacementAPISecret(names.Namespace, SecretName)) + keystoneAPIName := keystone.CreateKeystoneAPI(names.Namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName) + + spec := GetDefaultPlacementAPISpec() + spec["auth"] = map[string]any{ + "applicationCredentialSecret": "nonexistent-ac-secret", + } + + DeferCleanup(th.DeleteInstance, CreatePlacementAPI(names.PlacementAPIName, spec)) + + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + names.Namespace, + GetDefaultPlacementAPISpec()["databaseInstance"].(string), + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + }) + + It("should set ServiceConfigReady to False", func() { + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionFalse, + ) + }) + }) + + When("ApplicationCredential secret is missing required keys", func() { + BeforeEach(func() { + DeferCleanup( + k8sClient.Delete, ctx, CreatePlacementAPISecret(names.Namespace, SecretName)) + keystoneAPIName := keystone.CreateKeystoneAPI(names.Namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName) + + badACSecretName := "ac-placement-bad-secret" //nolint:gosec // G101 + badSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: names.Namespace, + Name: badACSecretName, + }, + Data: map[string][]byte{ + "WRONG_KEY": []byte("some-value"), + }, + } + DeferCleanup(k8sClient.Delete, ctx, badSecret) + Expect(k8sClient.Create(ctx, badSecret)).To(Succeed()) + + spec := GetDefaultPlacementAPISpec() + spec["auth"] = map[string]any{ + "applicationCredentialSecret": badACSecretName, + } + + DeferCleanup(th.DeleteInstance, CreatePlacementAPI(names.PlacementAPIName, spec)) + + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + names.Namespace, + GetDefaultPlacementAPISpec()["databaseInstance"].(string), + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + }) + + It("should set ServiceConfigReady to False", func() { + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionFalse, + ) + }) + }) + })