Skip to content
This repository has been archived by the owner on Aug 19, 2024. It is now read-only.

fix: Make sure to use backward-compatible names for resources created by the operator [RHIDP-2432] #369

13 changes: 12 additions & 1 deletion controllers/backstage_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,22 @@ func (r *BackstageReconciler) applyObjects(ctx context.Context, objects []model.
}

if err := r.patchObject(ctx, baseObject, obj); err != nil {
_, isStatefulSet := obj.Object().(*appsv1.StatefulSet)
if isStatefulSet && errors.IsInvalid(err) && errors.HasStatusCause(err, metav1.CauseTypeForbidden) {
// Some resources like StatefulSets allow patching a limited set of fields.
// That's why we are trying to delete them first, taking care of orphaning the dependents so that resources like PVCs can be retained.
// They will be recreated at the next reconciliation.
if err = r.Delete(ctx, baseObject, client.PropagationPolicy(metav1.DeletePropagationOrphan)); err != nil {
return fmt.Errorf("failed to delete object %s so it can be recreated: %w", obj.Object(), err)
}
lg.V(1).Info("delete object ", objDispName(obj), obj.Object().GetName())
continue
}

return fmt.Errorf("failed to patch object %s: %w", obj.Object(), err)
gazarenkov marked this conversation as resolved.
Show resolved Hide resolved
}

lg.V(1).Info("patch object ", objDispName(obj), obj.Object().GetName())

}
return nil
}
Expand Down
8 changes: 4 additions & 4 deletions examples/bs-existing-secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ metadata:
name: existing-postgres-secret
type: Opaque
stringData:
POSTGRES_PASSWORD: admin123
POSTGRES_PASSWORD: "admin123"
POSTGRES_PORT: "5432"
POSTGRES_USER: postgres
POSTGRESQL_ADMIN_PASSWORD: admin123
POSTGRES_HOST: backstage-db-bs-existing-secret
POSTGRES_USER: "postgres"
POSTGRESQL_ADMIN_PASSWORD: "admin123"
POSTGRES_HOST: "backstage-psql-bs-existing-secret"
23 changes: 12 additions & 11 deletions integration_tests/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ package integration_tests

import (
"context"
"fmt"
"time"

"k8s.io/apimachinery/pkg/api/errors"
"redhat-developer/red-hat-developer-hub-operator/pkg/model"

"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/utils/ptr"

corev1 "k8s.io/api/core/v1"
Expand All @@ -28,8 +30,6 @@ import (

"sigs.k8s.io/controller-runtime/pkg/reconcile"

"redhat-developer/red-hat-developer-hub-operator/pkg/model"

bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1"

"k8s.io/apimachinery/pkg/types"
Expand Down Expand Up @@ -60,13 +60,13 @@ var _ = When("create backstage with CR configured", func() {
Eventually(func(g Gomega) {
By("creating Deployment with database.enableLocalDb=true by default")

err := k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: model.DbStatefulSetName(backstageName)}, &appsv1.StatefulSet{})
err := k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: fmt.Sprintf("backstage-psql-%s", backstageName)}, &appsv1.StatefulSet{})
g.Expect(err).To(Not(HaveOccurred()))

err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: model.DbStatefulSetName(backstageName)}, &corev1.Service{})
err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: fmt.Sprintf("backstage-psql-%s", backstageName)}, &corev1.Service{})
g.Expect(err).To(Not(HaveOccurred()))

err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: model.DbStatefulSetName(backstageName)}, &corev1.Secret{})
err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: fmt.Sprintf("backstage-psql-secret-%s", backstageName)}, &corev1.Secret{})
g.Expect(err).To(Not(HaveOccurred()))

}, time.Minute, time.Second).Should(Succeed())
Expand All @@ -86,15 +86,15 @@ var _ = When("create backstage with CR configured", func() {

Eventually(func(g Gomega) {
By("deleting Local Db StatefulSet, Service and Secret")
err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: model.DbStatefulSetName(backstageName)}, &appsv1.StatefulSet{})
err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: fmt.Sprintf("backstage-psql-%s", backstageName)}, &appsv1.StatefulSet{})
g.Expect(err).To(HaveOccurred())
g.Expect(errors.IsNotFound(err))

err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: model.DbServiceName(backstageName)}, &corev1.Service{})
err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: fmt.Sprintf("backstage-psql-%s", backstageName)}, &corev1.Service{})
g.Expect(err).To(HaveOccurred())
g.Expect(errors.IsNotFound(err))

err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: model.DbSecretDefaultName(backstageName)}, &corev1.Secret{})
err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: fmt.Sprintf("backstage-psql-secret-%s", backstageName)}, &corev1.Secret{})
g.Expect(err).To(HaveOccurred())
g.Expect(errors.IsNotFound(err))
}, time.Minute, time.Second).Should(Succeed())
Expand All @@ -112,7 +112,7 @@ var _ = When("create backstage with CR configured", func() {
Eventually(func(g Gomega) {
By("not creating a StatefulSet for the Database")
err := k8sClient.Get(ctx,
types.NamespacedName{Namespace: ns, Name: model.DbStatefulSetName(backstageName)},
types.NamespacedName{Namespace: ns, Name: fmt.Sprintf("backstage-psql-%s", backstageName)},
&appsv1.StatefulSet{})
g.Expect(err).Should(HaveOccurred())
g.Expect(errors.IsNotFound(err))
Expand All @@ -133,7 +133,7 @@ var _ = When("create backstage with CR configured", func() {
Eventually(func(g Gomega) {
By("not creating a StatefulSet for the Database")
err := k8sClient.Get(ctx,
types.NamespacedName{Namespace: ns, Name: model.DbStatefulSetName(backstageName)},
types.NamespacedName{Namespace: ns, Name: fmt.Sprintf("backstage-psql-%s", backstageName)},
&appsv1.StatefulSet{})
g.Expect(err).Should(HaveOccurred())
g.Expect(errors.IsNotFound(err))
Expand All @@ -143,4 +143,5 @@ var _ = When("create backstage with CR configured", func() {
g.Expect(err).Should(Not(HaveOccurred()))
}, time.Minute, time.Second).Should(Succeed())
})

})
72 changes: 66 additions & 6 deletions integration_tests/default-config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ package integration_tests

import (
"context"
"redhat-developer/red-hat-developer-hub-operator/pkg/utils"
"fmt"
"time"

appsv1 "k8s.io/api/apps/v1"
"redhat-developer/red-hat-developer-hub-operator/pkg/utils"

"redhat-developer/red-hat-developer-hub-operator/pkg/model"

appsv1 "k8s.io/api/apps/v1"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

bsv1alpha1 "redhat-developer/red-hat-developer-hub-operator/api/v1alpha1"

corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -56,23 +59,23 @@ var _ = When("create default backstage", func() {
Eventually(func(g Gomega) {
By("creating a secret for accessing the Database")
secret := &corev1.Secret{}
secretName := model.DbSecretDefaultName(backstageName)
secretName := fmt.Sprintf("backstage-psql-secret-%s", backstageName)
err := k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: secretName}, secret)
g.Expect(err).ShouldNot(HaveOccurred(), controllerMessage())
g.Expect(len(secret.Data)).To(Equal(5))
g.Expect(secret.Data).To(HaveKeyWithValue("POSTGRES_USER", []uint8("postgres")))

By("creating a StatefulSet for the Database")
ss := &appsv1.StatefulSet{}
err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: model.DbStatefulSetName(backstageName)}, ss)
err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: fmt.Sprintf("backstage-psql-%s", backstageName)}, ss)
g.Expect(err).ShouldNot(HaveOccurred())

By("injecting default DB Secret as an env var for Db container")
g.Expect(model.DbSecretDefaultName(backstageName)).To(BeEnvFromForContainer(ss.Spec.Template.Spec.Containers[0]))
g.Expect(ss.GetOwnerReferences()).To(HaveLen(1))

By("creating a Service for the Database")
err = k8sClient.Get(ctx, types.NamespacedName{Name: model.DbServiceName(backstageName), Namespace: ns}, &corev1.Service{})
err = k8sClient.Get(ctx, types.NamespacedName{Name: fmt.Sprintf("backstage-psql-%s", backstageName), Namespace: ns}, &corev1.Service{})
g.Expect(err).To(Not(HaveOccurred()))

By("creating Deployment")
Expand Down Expand Up @@ -147,7 +150,7 @@ var _ = When("create default backstage", func() {

By("creating StatefulSet")
ss := &appsv1.StatefulSet{}
name := model.DbStatefulSetName(backstageName)
name := fmt.Sprintf("backstage-psql-%s", backstageName)
err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: name}, ss)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(ss.Spec.Template.Spec.Containers).To(HaveLen(1))
Expand All @@ -156,4 +159,61 @@ var _ = When("create default backstage", func() {

})

It("creates runtime object using raw configuration then updates StatefulSet to replace some immutable fields", func() {
if !*testEnv.UseExistingCluster {
Skip("Real cluster required to assert actual deletion and replacement of resources")
}

rawStatefulSetYamlContent := readTestYamlFile("raw-statefulset.yaml")
dbConf := map[string]string{"db-statefulset.yaml": rawStatefulSetYamlContent}

dbRaw := generateConfigMap(ctx, k8sClient, "dbraw", ns, dbConf, nil, nil)

backstageName := createAndReconcileBackstage(ctx, ns, bsv1alpha1.BackstageSpec{
RawRuntimeConfig: &bsv1alpha1.RuntimeConfig{
LocalDbConfigName: dbRaw,
},
}, "")

Eventually(func(g Gomega) {
By("creating Deployment")
deploy := &appsv1.Deployment{}
err := k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: model.DeploymentName(backstageName)}, deploy)
g.Expect(err).ShouldNot(HaveOccurred())

By("creating StatefulSet")
dbStatefulSet := &appsv1.StatefulSet{}
name := fmt.Sprintf("backstage-psql-%s", backstageName)
err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: name}, dbStatefulSet)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(dbStatefulSet.Spec.Template.Spec.Containers).To(HaveLen(1))
g.Expect(dbStatefulSet.Spec.Template.Spec.Containers[0].Image).To(Equal("busybox"))
g.Expect(dbStatefulSet.Spec.PodManagementPolicy).To(Equal(appsv1.ParallelPodManagement))
}, time.Minute, time.Second).Should(Succeed())

By("updating CR to default config")
update := &bsv1alpha1.Backstage{}
err := k8sClient.Get(ctx, types.NamespacedName{Name: backstageName, Namespace: ns}, update)
Expect(err).To(Not(HaveOccurred()))
update.Spec.RawRuntimeConfig = nil
err = k8sClient.Update(ctx, update)
Expect(err).To(Not(HaveOccurred()))

// Patching StatefulSets is done by the reconciler in two passes: first deleting the StatefulSet first, then recreating it in the next reconcilation.
for i := 0; i < 2; i++ {
_, err = NewTestBackstageReconciler(ns).ReconcileAny(ctx, reconcile.Request{
NamespacedName: types.NamespacedName{Name: backstageName, Namespace: ns},
})
Expect(err).To(Not(HaveOccurred()))
}

Eventually(func(g Gomega) {
By("replacing StatefulSet")
dbStatefulSet := &appsv1.StatefulSet{}
err = k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: fmt.Sprintf("backstage-psql-%s", backstageName)}, dbStatefulSet)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(dbStatefulSet.Spec.PodManagementPolicy).To(Equal(appsv1.OrderedReadyPodManagement))
}, time.Minute, time.Second).Should(Succeed())
})

})
1 change: 1 addition & 0 deletions integration_tests/testdata/raw-statefulset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ metadata:
name: db-statefulset
spec:
serviceName: ""
podManagementPolicy: Parallel
replicas: 1
selector:
matchLabels:
Expand Down
2 changes: 1 addition & 1 deletion pkg/model/db-secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func init() {
}

func DbSecretDefaultName(backstageName string) string {
return utils.GenerateRuntimeObjectName(backstageName, "backstage-db")
return utils.GenerateRuntimeObjectName(backstageName, "backstage-psql-secret")
}

// implementation of RuntimeObject interface
Expand Down
5 changes: 3 additions & 2 deletions pkg/model/db-secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package model

import (
"context"
"fmt"
"testing"

"k8s.io/utils/ptr"
Expand Down Expand Up @@ -50,7 +51,7 @@ func TestEmptyDbSecret(t *testing.T) {

assert.NoError(t, err)
assert.NotNil(t, model.LocalDbSecret)
assert.Equal(t, DbSecretDefaultName(bs.Name), model.LocalDbSecret.secret.Name)
assert.Equal(t, fmt.Sprintf("backstage-psql-secret-%s", bs.Name), model.LocalDbSecret.secret.Name)

dbss := model.localDbStatefulSet
assert.NotNil(t, dbss)
Expand All @@ -68,7 +69,7 @@ func TestDefaultWithGeneratedSecrets(t *testing.T) {
model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, true, false, testObj.scheme)

assert.NoError(t, err)
assert.Equal(t, DbSecretDefaultName(bs.Name), model.LocalDbSecret.secret.Name)
assert.Equal(t, fmt.Sprintf("backstage-psql-secret-%s", bs.Name), model.LocalDbSecret.secret.Name)
//should be generated
// assert.NotEmpty(t, model.LocalDbSecret.secret.StringData["POSTGRES_USER"])
// assert.NotEmpty(t, model.LocalDbSecret.secret.StringData["POSTGRES_PASSWORD"])
Expand Down
2 changes: 1 addition & 1 deletion pkg/model/db-service.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func init() {
}

func DbServiceName(backstageName string) string {
return utils.GenerateRuntimeObjectName(backstageName, "backstage-db")
return utils.GenerateRuntimeObjectName(backstageName, "backstage-psql")
}

// implementation of RuntimeObject interface
Expand Down
2 changes: 1 addition & 1 deletion pkg/model/db-statefulset.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func init() {
}

func DbStatefulSetName(backstageName string) string {
return utils.GenerateRuntimeObjectName(backstageName, "backstage-db")
return utils.GenerateRuntimeObjectName(backstageName, "backstage-psql")
}

// implementation of RuntimeObject interface
Expand Down
2 changes: 1 addition & 1 deletion pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func BackstageAppLabelValue(backstageName string) string {
}

func BackstageDbAppLabelValue(backstageName string) string {
return fmt.Sprintf("backstage-db-%s", backstageName)
return fmt.Sprintf("backstage-psql-%s", backstageName)
}

func ReadYaml(manifest []byte, object interface{}) error {
Expand Down