Skip to content

K8SPG-570 update custom secret with labels when they are missing, update missing secret data with autogeneration #1218

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 6 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
49 changes: 49 additions & 0 deletions e2e-tests/tests/users/16-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PostgresCluster
metadata:
name: users
ownerReferences:
- apiVersion: pgv2.percona.com/v2
kind: PerconaPGCluster
name: users
controller: true
blockOwnerDeletion: true
finalizers:
- postgres-operator.crunchydata.com/finalizer
status:
instances:
- name: instance1
readyReplicas: 3
replicas: 3
updatedReplicas: 3
pgbackrest:
repoHost:
apiVersion: apps/v1
kind: StatefulSet
ready: true
repos:
- bound: true
name: repo1
replicaCreateBackupComplete: true
stanzaCreated: true
proxy:
pgBouncer:
readyReplicas: 3
replicas: 3
---
apiVersion: pgv2.percona.com/v2
kind: PerconaPGCluster
metadata:
name: users
status:
pgbouncer:
ready: 3
size: 3
postgres:
instances:
- name: instance1
ready: 3
size: 3
ready: 3
size: 3
state: ready
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
timeout: 10
commands:
- script: |-
set -o errexit
set -o xtrace

source ../../functions

kubectl -n ${NAMESPACE} create secret generic eagle-credentials --from-literal=password=eagle-db-password
sleep 5

kubectl -n ${NAMESPACE} patch perconapgcluster/${test_name} --type=json -p '[{"op":"add", "path":"/spec/autoCreateUserSchema","value":true},{"op":"add", "path":"/spec/users","value":[{"name":"eagle","databases":["nest"],"password":{"type":"ASCII"},"secretName":"eagle-credentials"}]}]'
sleep 15
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: |-
set -o errexit
set -o xtrace

source ../../functions

predefinedPassword=eagle-db-password
user='eagle'
db_name='nest'
schema='eagle'
hostname=$(get_pgbouncer_host eagle-credentials)


run_psql \
'CREATE TABLE IF NOT EXISTS customApp (id int PRIMARY KEY);' \
"-h $hostname -U $user -d $db_name" "$predefinedPassword"
run_psql \
"INSERT INTO $schema.customApp (id) VALUES (100500)" \
"-h $hostname -U $user -d $db_name" "$predefinedPassword"

10 changes: 10 additions & 0 deletions e2e-tests/tests/users/18-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: kuttl.dev/v1beta1
kind: TestAssert
timeout: 30
---
kind: ConfigMap
apiVersion: v1
metadata:
name: 18-read-from-user-db-with-predefined-password
data:
data: ' 100500'
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
timeout: 30
commands:
- script: |-
set -o errexit
set -o xtrace

source ../../functions

predefinedPassword=eagle-db-password
user='eagle'
db_name='nest'
schema='eagle'
hostname=$(get_pgbouncer_host eagle-credentials)

data=$(run_psql "SELECT * from $schema.customApp;" "-h $hostname -U $user -d $db_name" "$predefinedPassword")

kubectl create configmap -n "${NAMESPACE}" 18-read-from-user-db-with-predefined-password --from-literal=data="${data}"
114 changes: 112 additions & 2 deletions internal/controller/postgrescluster/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ import (
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/percona/percona-postgresql-operator/internal/feature"
Expand Down Expand Up @@ -494,6 +497,17 @@ func (r *Reconciler) reconcilePostgresUserSecrets(
userSpecs[string(specUsers[i].Name)] = &specUsers[i]
}

// K8SPG-570 for secrets that were created manually, update them
// with the right labels so that the selector called next to track them
// and utilize their data.
for _, user := range specUsers {
if user.SecretName != "" {
if err := r.updateCustomSecretLabels(ctx, cluster, user); err != nil {
return specUsers, nil, err
}
}
}

secrets := &corev1.SecretList{}
selector, err := naming.AsSelector(naming.ClusterPostgresUsers(cluster.Name))
if err == nil {
Expand Down Expand Up @@ -573,15 +587,111 @@ func (r *Reconciler) reconcilePostgresUserSecrets(

if err == nil {
userSecrets[userName], err = r.generatePostgresUserSecret(cluster, user, secret)
}
if err == nil {
err = errors.WithStack(r.apply(ctx, userSecrets[userName]))
}
}

return specUsers, userSecrets, err
}

// K8SPG-570
// updateCustomSecretLabels checks if a custom secret exists - can be created manually through
// kubectl apply - and updates it with required labels if they are missing. This enables the
// naming.AsSelector(naming.ClusterPostgresUsers(cluster.Name)) to identify these secrets.
func (r *Reconciler) updateCustomSecretLabels(
ctx context.Context, cluster *v1beta1.PostgresCluster, user v1beta1.PostgresUserSpec,
) error {
secretName := string(user.SecretName)
userName := string(user.Name)

secret := &corev1.Secret{}
err := r.Client.Get(ctx, types.NamespacedName{
Name: secretName,
Namespace: cluster.Namespace,
}, secret)
if err != nil {
if k8serrors.IsNotFound(err) {
return nil
}
return errors.Wrap(err, fmt.Sprintf("failed to get user %s secret %s", userName, secretName))
}

requiredLabels := map[string]string{
naming.LabelCluster: cluster.Name,
naming.LabelPostgresUser: userName,
naming.LabelRole: naming.RolePostgresUser,
}

needsUpdate := false
if secret.Labels == nil {
secret.Labels = make(map[string]string)
}

for labelKey, labelValue := range requiredLabels {
if existing, exists := secret.Labels[labelKey]; !exists || existing != labelValue {
secret.Labels[labelKey] = labelValue
needsUpdate = true
}
}

if needsUpdate {
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
current := &corev1.Secret{}
if err := r.Client.Get(ctx, types.NamespacedName{
Name: secretName,
Namespace: cluster.Namespace,
}, current); err != nil {
return err
}

currentOrig := current.DeepCopy()
if current.Labels == nil {
current.Labels = make(map[string]string)
}

updateNeeded := false
for labelKey, labelValue := range requiredLabels {
if existing, exists := current.Labels[labelKey]; !exists || existing != labelValue {
current.Labels[labelKey] = labelValue
updateNeeded = true
}
}

if !updateNeeded {
return nil
}

return r.Client.Patch(ctx, current, client.MergeFrom(currentOrig))
})

if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to update secret %s", secretName))
}

verifyErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
verifySecret := &corev1.Secret{}
if err := r.Client.Get(ctx, types.NamespacedName{
Name: secretName,
Namespace: cluster.Namespace,
}, verifySecret); err != nil {
return err
}

for labelKey, labelValue := range requiredLabels {
if existing, exists := verifySecret.Labels[labelKey]; !exists || existing != labelValue {
return errors.Errorf("secret %s label %s not yet propagated", secretName, labelKey)
}
}

return nil
})

return errors.Wrap(verifyErr, "failed to update secret")
}

return nil
}

// reconcilePostgresUsersInPostgreSQL creates users inside of PostgreSQL and
// sets their options and database access as specified.
func (r *Reconciler) reconcilePostgresUsersInPostgreSQL(
Expand Down
Loading