Skip to content

Commit 7d57743

Browse files
committed
Copy pod_environment_secret to the cluster namespaces
1 parent 8b404fd commit 7d57743

File tree

11 files changed

+181
-19
lines changed

11 files changed

+181
-19
lines changed

manifests/postgresql-operator-default-configuration.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ configuration:
7979
pdb_name_format: "postgres-{cluster}-pdb"
8080
pod_antiaffinity_topology_key: "kubernetes.io/hostname"
8181
# pod_environment_configmap: "default/my-custom-config"
82-
# pod_environment_secret: "my-custom-secret"
82+
# pod_environment_secret: "default/my-custom-secret"
8383
pod_management_policy: "ordered_ready"
8484
# pod_priority_class_name: "postgres-pod-priority"
8585
pod_role_label: spilo-role

pkg/apis/acid.zalan.do/v1/operator_configuration_type.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ type KubernetesMetaConfiguration struct {
9090
// TODO: use a proper toleration structure?
9191
PodToleration map[string]string `json:"toleration,omitempty"`
9292
PodEnvironmentConfigMap spec.NamespacedName `json:"pod_environment_configmap,omitempty"`
93-
PodEnvironmentSecret string `json:"pod_environment_secret,omitempty"`
93+
PodEnvironmentSecret spec.NamespacedName `json:"pod_environment_secret,omitempty"`
9494
PodPriorityClassName string `json:"pod_priority_class_name,omitempty"`
9595
MasterPodMoveTimeout Duration `json:"master_pod_move_timeout,omitempty"`
9696
EnablePodAntiAffinity bool `json:"enable_pod_antiaffinity,omitempty"`

pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/cluster/k8sres.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -893,13 +893,13 @@ func (c *Cluster) getPodEnvironmentConfigMapVariables() ([]v1.EnvVar, error) {
893893
func (c *Cluster) getPodEnvironmentSecretVariables() ([]v1.EnvVar, error) {
894894
secretPodEnvVarsList := make([]v1.EnvVar, 0)
895895

896-
if c.OpConfig.PodEnvironmentSecret == "" {
896+
if c.OpConfig.PodEnvironmentSecret.Name == "" {
897897
return secretPodEnvVarsList, nil
898898
}
899899

900900
secret, err := c.KubeClient.Secrets(c.Namespace).Get(
901901
context.TODO(),
902-
c.OpConfig.PodEnvironmentSecret,
902+
c.OpConfig.PodEnvironmentSecret.Name,
903903
metav1.GetOptions{})
904904
if err != nil {
905905
return nil, fmt.Errorf("could not read Secret PodEnvironmentSecretName: %v", err)
@@ -910,7 +910,7 @@ func (c *Cluster) getPodEnvironmentSecretVariables() ([]v1.EnvVar, error) {
910910
v1.EnvVar{Name: k, ValueFrom: &v1.EnvVarSource{
911911
SecretKeyRef: &v1.SecretKeySelector{
912912
LocalObjectReference: v1.LocalObjectReference{
913-
Name: c.OpConfig.PodEnvironmentSecret,
913+
Name: c.OpConfig.PodEnvironmentSecret.Name,
914914
},
915915
Key: k,
916916
},

pkg/cluster/k8sres_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,9 @@ func TestPodEnvironmentSecretVariables(t *testing.T) {
789789
subTest: "Secret referenced by PodEnvironmentSecret does not exist",
790790
opConfig: config.Config{
791791
Resources: config.Resources{
792-
PodEnvironmentSecret: "idonotexist",
792+
PodEnvironmentSecret: spec.NamespacedName{
793+
Name: "idonotexist",
794+
},
793795
},
794796
},
795797
err: fmt.Errorf("could not read Secret PodEnvironmentSecretName: Secret PodEnvironmentSecret not found"),
@@ -798,7 +800,9 @@ func TestPodEnvironmentSecretVariables(t *testing.T) {
798800
subTest: "Pod environment vars reference all keys from secret configured by PodEnvironmentSecret",
799801
opConfig: config.Config{
800802
Resources: config.Resources{
801-
PodEnvironmentSecret: testPodEnvironmentSecretName,
803+
PodEnvironmentSecret: spec.NamespacedName{
804+
Name: testPodEnvironmentSecretName,
805+
},
802806
},
803807
},
804808
envVars: []v1.EnvVar{

pkg/cluster/resources.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,24 @@ func (c *Cluster) deleteSecret(uid types.UID, secret v1.Secret) error {
526526
return nil
527527
}
528528

529+
func (c *Cluster) getSecretWithRetry(name, namespace string) (*v1.Secret, error) {
530+
secret := &v1.Secret{}
531+
err := retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout,
532+
func() (bool, error) {
533+
var err error
534+
secret, err = c.KubeClient.Secrets(namespace).Get(
535+
context.TODO(),
536+
name,
537+
metav1.GetOptions{})
538+
if err != nil {
539+
return false, nil
540+
}
541+
return true, nil
542+
},
543+
)
544+
return secret, err
545+
}
546+
529547
func (c *Cluster) createRoles() (err error) {
530548
// TODO: figure out what to do with duplicate names (humans and robots) among pgUsers
531549
return c.syncRoles()

pkg/cluster/sync.go

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
v1 "k8s.io/api/core/v1"
2020
policybeta1 "k8s.io/api/policy/v1beta1"
2121
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
"k8s.io/apimachinery/pkg/types"
2223
)
2324

2425
var requireMasterRestartWhenDecreased = []string{
@@ -621,14 +622,18 @@ func (c *Cluster) checkAndSetGlobalPostgreSQLConfiguration(pod *v1.Pod, patroniC
621622
}
622623

623624
func (c *Cluster) syncSecrets() error {
624-
625625
c.logger.Info("syncing secrets")
626626
c.setProcessName("syncing secrets")
627627
generatedSecrets := c.generateUserSecrets()
628628
rotationUsers := make(spec.PgUserMap)
629629
retentionUsers := make([]string, 0)
630630
currentTime := time.Now()
631631

632+
c.logger.Debug("coping PodEnvironmentSecretName if needed")
633+
if err := c.copyPodEnvironmentSecret(); err != nil {
634+
return err
635+
}
636+
632637
for secretUsername, generatedSecret := range generatedSecrets {
633638
secret, err := c.KubeClient.Secrets(generatedSecret.Namespace).Create(context.TODO(), generatedSecret, metav1.CreateOptions{})
634639
if err == nil {
@@ -795,6 +800,61 @@ func (c *Cluster) updateSecret(
795800
return nil
796801
}
797802

803+
func (c *Cluster) copyPodEnvironmentSecret() error {
804+
if c.OpConfig.PodEnvironmentSecret.Name == "" {
805+
return nil
806+
}
807+
// Searching for a Secret within a namespace defined by the configuration
808+
originalSecret, err := c.getSecretWithRetry(c.OpConfig.PodEnvironmentSecret.Name, c.OpConfig.PodEnvironmentSecret.Namespace)
809+
if err != nil {
810+
return fmt.Errorf("could not read Secret PodEnvironmentSecretName: %w", err)
811+
}
812+
813+
if c.OpConfig.PodEnvironmentSecret.Namespace == c.Namespace {
814+
return nil
815+
}
816+
// Attempting to find a Secret in the cluster's namespace if that namespace is not equal to the namespace defined by the configuration
817+
secret, err := c.KubeClient.Secrets(c.Namespace).Get(
818+
context.TODO(),
819+
c.OpConfig.PodEnvironmentSecret.Name,
820+
metav1.GetOptions{})
821+
if err != nil {
822+
if !k8sutil.ResourceNotFound(err) {
823+
return fmt.Errorf("could not read Secret PodEnvironmentSecretName in cluster namespace: %w", err)
824+
}
825+
// Secret within cluster namespace not found. Let's create it
826+
createSecret := &v1.Secret{
827+
ObjectMeta: metav1.ObjectMeta{
828+
Name: originalSecret.Name,
829+
Namespace: c.Namespace,
830+
Labels: originalSecret.Labels,
831+
Annotations: originalSecret.Annotations,
832+
},
833+
Data: originalSecret.Data,
834+
}
835+
_, err = c.KubeClient.Secrets(c.Namespace).Create(context.TODO(), createSecret, metav1.CreateOptions{})
836+
return k8sutil.ResourceIgnoreAlreadyExists(err)
837+
}
838+
// The secret exists. We need to check if it needs to be updated or not
839+
if !reflect.DeepEqual(originalSecret.Data, secret.Data) {
840+
patchData, err := secretDataPath(originalSecret.Data)
841+
if err != nil {
842+
return fmt.Errorf("could not form patch for the Secret %q: %w", c.OpConfig.PodEnvironmentSecret.Name, err)
843+
}
844+
_, err = c.KubeClient.Secrets(c.Namespace).Patch(
845+
context.TODO(),
846+
c.OpConfig.PodEnvironmentSecret.Name,
847+
types.MergePatchType,
848+
patchData,
849+
metav1.PatchOptions{},
850+
"")
851+
if err != nil {
852+
return fmt.Errorf("could not patch Secret %q: %w", c.OpConfig.PodEnvironmentSecret.Name, err)
853+
}
854+
}
855+
return nil
856+
}
857+
798858
func (c *Cluster) syncRoles() (err error) {
799859
c.setProcessName("syncing roles")
800860

pkg/cluster/sync_test.go

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,12 @@ package cluster
22

33
import (
44
"bytes"
5+
"context"
56
"io/ioutil"
67
"net/http"
78
"testing"
89
"time"
910

10-
"context"
11-
12-
v1 "k8s.io/api/core/v1"
13-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14-
"k8s.io/apimachinery/pkg/types"
15-
1611
"github.com/golang/mock/gomock"
1712
"github.com/sirupsen/logrus"
1813
"github.com/stretchr/testify/assert"
@@ -23,6 +18,9 @@ import (
2318
"github.com/zalando/postgres-operator/pkg/util/config"
2419
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
2520
"github.com/zalando/postgres-operator/pkg/util/patroni"
21+
v1 "k8s.io/api/core/v1"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"k8s.io/apimachinery/pkg/types"
2624
"k8s.io/client-go/kubernetes/fake"
2725
)
2826

@@ -48,7 +46,8 @@ func newFakeK8sSyncClient() (k8sutil.KubernetesClient, *fake.Clientset) {
4846

4947
func newFakeK8sSyncSecretsClient() (k8sutil.KubernetesClient, *fake.Clientset) {
5048
return k8sutil.KubernetesClient{
51-
SecretsGetter: clientSet.CoreV1(),
49+
SecretsGetter: clientSet.CoreV1(),
50+
NamespacesGetter: clientSet.CoreV1(),
5251
}, clientSet
5352
}
5453

@@ -365,3 +364,61 @@ func TestUpdateSecret(t *testing.T) {
365364
}
366365
}
367366
}
367+
368+
func TestCopyPodEnvironmentSecret(t *testing.T) {
369+
client, _ := newFakeK8sSyncSecretsClient()
370+
371+
const (
372+
clusterNamespace = metav1.NamespaceDefault
373+
clusterName = "acid-test-cluster"
374+
secretEnvVar = "POD_ENV_VARIABLE"
375+
secretNamespace = "secret-ns"
376+
secretName = "pod-environment-secret"
377+
)
378+
379+
podEnvironmentSecret := &v1.Secret{
380+
TypeMeta: metav1.TypeMeta{},
381+
ObjectMeta: metav1.ObjectMeta{
382+
Name: secretName,
383+
Namespace: secretNamespace,
384+
},
385+
StringData: map[string]string{
386+
secretEnvVar: "data",
387+
},
388+
}
389+
390+
var cluster = New(
391+
Config{
392+
OpConfig: config.Config{
393+
Resources: config.Resources{
394+
PodEnvironmentSecret: spec.NamespacedName{
395+
Name: secretName,
396+
Namespace: secretNamespace,
397+
},
398+
ResourceCheckInterval: time.Duration(3),
399+
ResourceCheckTimeout: time.Duration(10),
400+
},
401+
},
402+
}, client, acidv1.Postgresql{}, logger, eventRecorder)
403+
cluster.Name = clusterName
404+
cluster.Namespace = clusterNamespace
405+
406+
// Run copyPodEnvironmentSecret without podEnvironmentSecret presence in the namespace
407+
err := cluster.copyPodEnvironmentSecret()
408+
assert.Error(t, err)
409+
410+
// Create a Secret in the separate namespace and try to run copyPodEnvironmentSecret again
411+
_, err = client.Namespaces().Create(context.TODO(), &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: clusterNamespace}}, metav1.CreateOptions{})
412+
assert.NoError(t, err)
413+
_, err = client.Secrets(secretNamespace).Create(context.TODO(), podEnvironmentSecret, metav1.CreateOptions{})
414+
assert.NoError(t, err)
415+
416+
assert.NoError(t, cluster.copyPodEnvironmentSecret())
417+
// Validate, that original Secret was copied to the cluster namespace
418+
copySecret, err := client.Secrets(clusterNamespace).Get(context.TODO(), secretName, metav1.GetOptions{})
419+
assert.NoError(t, err)
420+
assert.Equal(t, podEnvironmentSecret.Data[secretEnvVar], copySecret.Data[secretEnvVar])
421+
422+
// Run copyPodEnvironmentSecret and validate that no error happens on retry (Secret already copied)
423+
assert.NoError(t, cluster.copyPodEnvironmentSecret())
424+
}

pkg/cluster/util.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,15 +147,21 @@ func specPatch(spec interface{}) ([]byte, error) {
147147
}{spec})
148148
}
149149

150+
func secretDataPath(data interface{}) ([]byte, error) {
151+
return json.Marshal(struct {
152+
Data interface{} `json:"data"`
153+
}{data})
154+
}
155+
150156
// metaAnnotationsPatch produces a JSON of the object metadata that has only the annotation
151157
// field in order to use it in a MergePatch. Note that we don't patch the complete metadata, since
152158
// it contains the current revision of the object that could be outdated at the time we patch.
153159
func metaAnnotationsPatch(annotations map[string]string) ([]byte, error) {
154-
var meta metav1.ObjectMeta
155-
meta.Annotations = annotations
156160
return json.Marshal(struct {
157161
ObjMeta interface{} `json:"metadata"`
158-
}{&meta})
162+
}{&metav1.ObjectMeta{
163+
Annotations: annotations,
164+
}})
159165
}
160166

161167
func (c *Cluster) logPDBChanges(old, new *policybeta1.PodDisruptionBudget, isUpdate bool, reason string) {

pkg/util/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ type Resources struct {
5454
MinCPULimit string `name:"min_cpu_limit" default:"250m"`
5555
MinMemoryLimit string `name:"min_memory_limit" default:"250Mi"`
5656
PodEnvironmentConfigMap spec.NamespacedName `name:"pod_environment_configmap"`
57-
PodEnvironmentSecret string `name:"pod_environment_secret"`
57+
PodEnvironmentSecret spec.NamespacedName `name:"pod_environment_secret"`
5858
NodeReadinessLabel map[string]string `name:"node_readiness_label" default:""`
5959
NodeReadinessLabelMerge string `name:"node_readiness_label_merge" default:"OR"`
6060
MaxInstances int32 `name:"max_instances" default:"-1"`

0 commit comments

Comments
 (0)