Skip to content

Use createOrUpdate pattern for pod environment configmap #191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 3 additions & 119 deletions controllers/postgres_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ package controllers

import (
"context"
"encoding/json"
"fmt"
"net/url"

"github.com/go-logr/logr"
zalando "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
Expand Down Expand Up @@ -144,10 +142,9 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
log.Info("finalizer added")
}

// Check if zalando dependencies are installed. If not, install them.
if err := r.ensureZalandoDependencies(ctx, instance); err != nil {
r.recorder.Eventf(instance, "Warning", "Error", "failed to install operator: %v", err)
return ctrl.Result{}, fmt.Errorf("error while ensuring Zalando dependencies: %w", err)
if err := r.CreateOrUpdateOperator(ctx, instance); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I don't like the fact that we now always update the operator each time anything changes and that it will not be skipped if the operator is already installed.

Before this change, we once called this code when we initially created the namespace, and once on startup of the postgreslet. Now it will be called from the postgres_controller, meaning every time anything changes.

I would like this to stay as it was: Once on creation, once on startup and each time the backup-config changes.

Porposal

  • CreateOrUpdate stays the way it is (no check if it is already installed), CreateOrUpdatePodEnvironmentConfigMap will be exported
  • The postgres_controller checks if the operator is already installed and skips CreateOrUpdate if it is already installed (just like before)
  • The postgres_controller will no longer update the pod environment configmap
  • On startup, CreateOrUpdateOperator will be called (as before)
  • A new BackupConfigController will watch the backup-configs and update the PodEnvironmentConfigMaps if neccessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r.recorder.Eventf(instance, "Warning", "Error", "failed to install or update operator: %v", err)
return ctrl.Result{}, fmt.Errorf("failed to install or update operator: %w", err)
}

if err := r.createOrUpdateZalandoPostgresql(ctx, instance, log); err != nil {
Expand Down Expand Up @@ -250,119 +247,6 @@ func (r *PostgresReconciler) deleteUserPasswordsSecret(ctx context.Context, inst
return nil
}

// ensureZalandoDependencies makes sure Zalando resources are installed in the service-cluster.
func (r *PostgresReconciler) ensureZalandoDependencies(ctx context.Context, p *pg.Postgres) error {
namespace := p.ToPeripheralResourceNamespace()
isInstalled, err := r.IsOperatorInstalled(ctx, namespace)
if err != nil {
return fmt.Errorf("error while querying if zalando dependencies are installed: %w", err)
}

if !isInstalled {
if err := r.InstallOrUpdateOperator(ctx, namespace); err != nil {
return fmt.Errorf("error while installing zalando dependencies: %w", err)
}
}

if err := r.updatePodEnvironmentConfigMap(ctx, p); err != nil {
return fmt.Errorf("error while updating backup config: %w", err)
}

return nil
}

func (r *PostgresReconciler) updatePodEnvironmentConfigMap(ctx context.Context, p *pg.Postgres) error {
log := r.Log.WithValues("postgres", p.UID)
if p.Spec.BackupSecretRef == "" {
log.Info("No configured backupSecretRef found, skipping configuration of postgres backup")
return nil
}

// fetch secret
backupSecret := &v1.Secret{}
backupNamespace := types.NamespacedName{
Name: p.Spec.BackupSecretRef,
Namespace: p.Namespace,
}
if err := r.CtrlClient.Get(ctx, backupNamespace, backupSecret); err != nil {
return fmt.Errorf("error while getting the backup secret from control plane cluster: %w", err)
}

backupConfigJSON, ok := backupSecret.Data[pg.BackupConfigKey]
if !ok {
return fmt.Errorf("no backupConfig stored in the secret")
}
var backupConfig pg.BackupConfig
err := json.Unmarshal(backupConfigJSON, &backupConfig)
if err != nil {
return fmt.Errorf("unable to unmarshal backupconfig:%w", err)
}

s3url, err := url.Parse(backupConfig.S3Endpoint)
if err != nil {
return fmt.Errorf("error while parsing the s3 endpoint url in the backup secret: %w", err)
}
// use the s3 endpoint as provided
awsEndpoint := s3url.String()
// modify the scheme to 'https+path'
s3url.Scheme = "https+path"
// use the modified s3 endpoint
walES3Endpoint := s3url.String()
// region
region := backupConfig.S3Region

// use the rest as provided in the secret
bucketName := backupConfig.S3BucketName
awsAccessKeyID := backupConfig.S3AccessKey
awsSecretAccessKey := backupConfig.S3SecretKey
backupSchedule := backupConfig.Schedule
backupNumToRetain := backupConfig.Retention

// s3 server side encryption SSE is enabled if the key is given
// TODO our s3 needs a small change to make this work
walgDisableSSE := "true"
walgSSE := ""
if backupConfig.S3EncryptionKey != nil {
walgDisableSSE = "false"
walgSSE = *backupConfig.S3EncryptionKey
}

// create updated content for pod environment configmap
data := map[string]string{
"USE_WALG_BACKUP": "true",
"USE_WALG_RESTORE": "true",
"WALE_S3_PREFIX": "s3://" + bucketName + "/$(SCOPE)",
"WALG_S3_PREFIX": "s3://" + bucketName + "/$(SCOPE)",
"CLONE_WALG_S3_PREFIX": "s3://" + bucketName + "/$(CLONE_SCOPE)",
"WALE_BACKUP_THRESHOLD_PERCENTAGE": "100",
"AWS_ENDPOINT": awsEndpoint,
"WALE_S3_ENDPOINT": walES3Endpoint, // same as above, but slightly modified
"AWS_ACCESS_KEY_ID": awsAccessKeyID,
"AWS_SECRET_ACCESS_KEY": awsSecretAccessKey,
"AWS_S3_FORCE_PATH_STYLE": "true",
"AWS_REGION": region, // now we can use AWS S3
"WALG_DISABLE_S3_SSE": walgDisableSSE, // disable server side encryption if key is nil
"WALG_S3_SSE": walgSSE, // server side encryption key
"BACKUP_SCHEDULE": backupSchedule,
"BACKUP_NUM_TO_RETAIN": backupNumToRetain,
}

cm := &v1.ConfigMap{}
ns := types.NamespacedName{
Name: operatormanager.PodEnvCMName,
Namespace: p.ToPeripheralResourceNamespace(),
}
if err := r.SvcClient.Get(ctx, ns, cm); err != nil {
return fmt.Errorf("error while getting the pod environment configmap from service cluster: %w", err)
}
cm.Data = data
if err := r.SvcClient.Update(ctx, cm); err != nil {
return fmt.Errorf("error while updating the pod environment configmap in service cluster: %w", err)
}

return nil
}

func (r *PostgresReconciler) isManagedByUs(obj *pg.Postgres) bool {
if obj.Spec.PartitionID != r.PartitionID {
return false
Expand Down
19 changes: 14 additions & 5 deletions controllers/postgres_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ package controllers

import (
pg "github.com/fi-ts/postgreslet/api/v1"
"github.com/fi-ts/postgreslet/pkg/operatormanager"
firewall "github.com/metal-stack/firewall-controller/api/v1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
zalando "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
core "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
)

Expand Down Expand Up @@ -45,6 +45,15 @@ var _ = Describe("postgres controller", func() {
}, timeout, interval).Should(BeTrue())
})

It("should create pod-environment-configmap in service-cluster", func() {
Eventually(func() bool {
return svcClusterClient.Get(newCtx(), types.NamespacedName{
Namespace: instance.ToPeripheralResourceNamespace(),
Name: operatormanager.PodEnvCMName,
}, &core.ConfigMap{}) == nil
}, timeout, interval).Should(BeTrue())
})

It("should create zalando postgresql in service-cluster", func() {
z := &zalando.Postgresql{}
Eventually(func() bool {
Expand All @@ -59,7 +68,7 @@ var _ = Describe("postgres controller", func() {
return svcClusterClient.Get(newCtx(), types.NamespacedName{
Namespace: instance.ToPeripheralResourceNamespace(),
Name: instance.ToSvcLBName(),
}, &corev1.Service{}) == nil
}, &core.Service{}) == nil
}, timeout, interval).Should(BeTrue())
})

Expand All @@ -77,7 +86,7 @@ var _ = Describe("postgres controller", func() {
return ctrlClusterClient.Get(newCtx(), types.NamespacedName{
Namespace: instance.Namespace,
Name: instance.ToUserPasswordsSecretName(),
}, &corev1.Secret{}) == nil
}, &core.Secret{}) == nil
}, timeout, interval).Should(BeTrue())
})
})
Expand All @@ -98,7 +107,7 @@ var _ = Describe("postgres controller", func() {
return svcClusterClient.Get(newCtx(), types.NamespacedName{
Namespace: instance.ToPeripheralResourceNamespace(),
Name: instance.ToSvcLBName(),
}, &corev1.Service{}) == nil
}, &core.Service{}) == nil
}, timeout, interval).ShouldNot(BeTrue())
})

Expand All @@ -114,7 +123,7 @@ var _ = Describe("postgres controller", func() {
return ctrlClusterClient.Get(newCtx(), types.NamespacedName{
Namespace: instance.Namespace,
Name: instance.ToUserPasswordsSecretName(),
}, &corev1.Secret{}) == nil
}, &core.Secret{}) == nil
}, timeout, interval).ShouldNot(BeTrue())
})
})
Expand Down
24 changes: 20 additions & 4 deletions controllers/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ var _ = BeforeSuite(func(done Done) {

// Todo: OperatorManager should be a reconciler
opMgr, err := operatormanager.New(
ctrlClusterMgr.GetClient(),
svcClusterCfg,
filepath.Join(externalYAMLDir, "svc-postgres-operator.yaml"),
scheme,
Expand Down Expand Up @@ -142,8 +143,11 @@ var _ = BeforeSuite(func(done Done) {
svcClusterClient = svcClusterMgr.GetClient()
Expect(svcClusterClient).ToNot(BeNil())

createNamespace(svcClusterClient, "firewall")
createPostgresTestInstance()
// Available in production, but not here
createNamespace(ctrlClusterClient, "metal-extension-cloud")
createNamespace(svcClusterClient, firewall.ClusterwideNetworkPolicyNamespace)

createPostgresTestInstance(createBackupSecret())
createConfigMapSidecarConfig()
createCredentialSecrets()
}, 1000)
Expand All @@ -158,6 +162,17 @@ var _ = AfterSuite(func() {
Expect(err).ToNot(HaveOccurred())
})

func createBackupSecret() *core.Secret {
backupSecret := &core.Secret{}
bytes, err := os.ReadFile(filepath.Join(externalYAMLDirTest, "secret-backup.yaml"))
Expect(err).ToNot(HaveOccurred())
Expect(yaml.Unmarshal(bytes, backupSecret)).Should(Succeed())

Expect(ctrlClusterClient.Create(newCtx(), backupSecret)).Should(Succeed())

return backupSecret
}

func createCredentialSecrets() {
defer GinkgoRecover()

Expand Down Expand Up @@ -201,14 +216,15 @@ func createNamespace(client client.Client, ns string) {
Expect(client.Create(newCtx(), nsObj)).Should(Succeed())
}

func createPostgresTestInstance() {
func createPostgresTestInstance(backupSecret *core.Secret) {
defer GinkgoRecover()

// Parse the test instance
bytes, err := os.ReadFile(filepath.Join("..", "config", "samples", "complete.yaml"))
Expect(err).ToNot(HaveOccurred())
Expect(yaml.Unmarshal(bytes, instance)).Should(Succeed())

createNamespace(ctrlClusterClient, instance.Namespace)
instance.Spec.BackupSecretRef = backupSecret.Name

Expect(ctrlClusterClient.Create(newCtx(), instance)).Should(Succeed())
}
Expand Down
12 changes: 12 additions & 0 deletions external/test/secret-backup.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: v1
data:
config: eyJpZCI6IjgxZWEyMzMxLTliMTQtNDUyMi05MjgzLTdkOGQwYmM2YjExNSIsIm5hbWUiOiJuYmctcHJvZC10ZXN0IiwicHJvamVjdCI6ImI2MjFlYjk5LTQ4ODgtNDkxMS05M2ZjLTk1ODU0ZmMwMzBlOCIsInRlbmFudCI6ImZpdHMiLCJjcmVhdGVkQnkiOiJcdTAwM2NpemUwMDBcdTAwM2VbYWNoaW0uYWRtaW5AZi1pLXRzLmRlXSIsInJldGVudGlvbiI6IjQiLCJzY2hlZHVsZSI6IjMwIDAwICogKiAqIiwiczNlbmRwb2ludCI6Imh0dHBzOi8vczMucHJvZC0wMy1uYmctdzgxMDEuZml0cy5jbG91ZCIsInMzYnVja2V0bmFtZSI6ImJhY2t1cC0xMjM0NTY3OCIsInMzcmVnaW9uIjoiZHVtbXkiLCJzM2FjY2Vzc2tleSI6IjRJMjUyOFA4UkcwOThYU1NDUEJNIiwiczNzZWNyZXRrZXkiOiJJSXBCOHBjU2tLVVF2MVhadmN6RU1XM0VrbERhUmU1N0FpM2FGdllCIn0=
kind: Secret
metadata:
labels:
postgres.database.fits.cloud/is-backup: "true"
postgres.database.fits.cloud/project-id: b621eb99-4888-4911-93fc-95854fc030e8
postgres.database.fits.cloud/tenant: fits
name: 81ea2331-9b14-4522-9283-7d8d0bc6b115
namespace: metal-extension-cloud
type: Opaque
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func main() {
os.Exit(1)
}

opMgr, err := operatormanager.New(svcClusterConf, "external/svc-postgres-operator.yaml", scheme, ctrl.Log.WithName("OperatorManager"), pspName)
opMgr, err := operatormanager.New(ctrlPlaneClusterMgr.GetClient(), svcClusterConf, "external/svc-postgres-operator.yaml", scheme, ctrl.Log.WithName("OperatorManager"), pspName)
if err != nil {
setupLog.Error(err, "unable to create `OperatorManager`")
os.Exit(1)
Expand Down Expand Up @@ -154,7 +154,7 @@ func main() {
ctx := context.Background()

// update all existing operators to the current version
if err := opMgr.UpdateAllOperators(ctx); err != nil {
if err := opMgr.UpdateAllZalandoOperators(ctx); err != nil {
setupLog.Error(err, "error updating the postgres operators")
}

Expand Down
Loading