diff --git a/charts/postgres-operator/crds/operatorconfigurations.yaml b/charts/postgres-operator/crds/operatorconfigurations.yaml index 7e1ecbde1..a8025c212 100644 --- a/charts/postgres-operator/crds/operatorconfigurations.yaml +++ b/charts/postgres-operator/crds/operatorconfigurations.yaml @@ -539,6 +539,8 @@ spec: type: string pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$' default: "30 00 * * *" + logical_backup_cronjob_environment_secret: + type: string debug: type: object properties: diff --git a/charts/postgres-operator/values.yaml b/charts/postgres-operator/values.yaml index 854b29b10..85adb2428 100644 --- a/charts/postgres-operator/values.yaml +++ b/charts/postgres-operator/values.yaml @@ -376,6 +376,8 @@ configLogicalBackup: logical_backup_s3_retention_time: "" # backup schedule in the cron format logical_backup_schedule: "30 00 * * *" + # secret to be used as reference for env variables in cronjob + logical_backup_cronjob_environment_secret: "" # automate creation of human users with teams API service configTeamsApi: diff --git a/docs/reference/operator_parameters.md b/docs/reference/operator_parameters.md index b759d37b0..70135ea69 100644 --- a/docs/reference/operator_parameters.md +++ b/docs/reference/operator_parameters.md @@ -825,6 +825,9 @@ grouped under the `logical_backup` key. [reference schedule format](https://kubernetes.io/docs/tasks/job/automated-tasks-with-cron-jobs/#schedule) into account. Default: "30 00 \* \* \*" +* **logical_backup_cronjob_environment_secret** + Reference to a Kubernetes secret, which keys will be added as environment variables to the cronjob. Default: "" + ## Debugging the operator Options to aid debugging of the operator itself. Grouped under the `debug` key. diff --git a/manifests/configmap.yaml b/manifests/configmap.yaml index 3bba4c50e..390cf1046 100644 --- a/manifests/configmap.yaml +++ b/manifests/configmap.yaml @@ -94,6 +94,7 @@ data: logical_backup_s3_sse: "AES256" # logical_backup_s3_retention_time: "" logical_backup_schedule: "30 00 * * *" + # logical_backup_cronjob_environment_secret: "" major_version_upgrade_mode: "manual" # major_version_upgrade_team_allow_list: "" master_dns_name_format: "{cluster}.{namespace}.{hostedzone}" diff --git a/manifests/operatorconfiguration.crd.yaml b/manifests/operatorconfiguration.crd.yaml index e3eff4fca..d0b30ef37 100644 --- a/manifests/operatorconfiguration.crd.yaml +++ b/manifests/operatorconfiguration.crd.yaml @@ -537,6 +537,8 @@ spec: type: string pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$' default: "30 00 * * *" + logical_backup_cronjob_environment_secret: + type: string debug: type: object properties: diff --git a/manifests/postgresql-operator-default-configuration.yaml b/manifests/postgresql-operator-default-configuration.yaml index 3a43a87bd..d2bb99644 100644 --- a/manifests/postgresql-operator-default-configuration.yaml +++ b/manifests/postgresql-operator-default-configuration.yaml @@ -175,6 +175,7 @@ configuration: logical_backup_s3_sse: "AES256" # logical_backup_s3_retention_time: "" logical_backup_schedule: "30 00 * * *" + # logical_backup_cronjob_environment_secret: "" debug: debug_logging: true enable_database_access: true diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go index 3d9f4f08d..354d1933a 100644 --- a/pkg/apis/acid.zalan.do/v1/crds.go +++ b/pkg/apis/acid.zalan.do/v1/crds.go @@ -1746,6 +1746,9 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{ Type: "string", Pattern: "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$", }, + "logical_backup_cronjob_environment_secret": { + Type: "string", + }, }, }, "debug": { diff --git a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go index afc22cb5d..ee44df9a4 100644 --- a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go +++ b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go @@ -233,6 +233,7 @@ type OperatorLogicalBackupConfiguration struct { RetentionTime string `json:"logical_backup_s3_retention_time,omitempty"` GoogleApplicationCredentials string `json:"logical_backup_google_application_credentials,omitempty"` JobPrefix string `json:"logical_backup_job_prefix,omitempty"` + CronjobEnvironmentSecret string `json:"logical_backup_cronjob_environment_secret,omitempty"` CPURequest string `json:"logical_backup_cpu_request,omitempty"` MemoryRequest string `json:"logical_backup_memory_request,omitempty"` CPULimit string `json:"logical_backup_cpu_limit,omitempty"` diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index f0a88c86d..ffe01c4a1 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -1137,6 +1137,37 @@ func (c *Cluster) getPodEnvironmentSecretVariables() ([]v1.EnvVar, error) { return secretPodEnvVarsList, nil } +// Return list of variables the cronjob received from the configured Secret +func (c *Cluster) getCronjobEnvironmentSecretVariables() ([]v1.EnvVar, error) { + secretCronjobEnvVarsList := make([]v1.EnvVar, 0) + + if c.OpConfig.LogicalBackupCronjobEnvironmentSecret == "" { + return secretCronjobEnvVarsList, nil + } + + secret, err := c.KubeClient.Secrets(c.Namespace).Get( + context.TODO(), + c.OpConfig.LogicalBackupCronjobEnvironmentSecret, + metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("could not read Secret CronjobEnvironmentSecretName: %v", err) + } + + for k := range secret.Data { + secretCronjobEnvVarsList = append(secretCronjobEnvVarsList, + v1.EnvVar{Name: k, ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: c.OpConfig.LogicalBackupCronjobEnvironmentSecret, + }, + Key: k, + }, + }}) + } + + return secretCronjobEnvVarsList, nil +} + func getSidecarContainer(sidecar acidv1.Sidecar, index int, resources *v1.ResourceRequirements) *v1.Container { name := sidecar.Name if name == "" { @@ -2172,7 +2203,13 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1.CronJob, error) { return nil, fmt.Errorf("could not generate resource requirements for logical backup pods: %v", err) } + secretEnvVarsList, err := c.getCronjobEnvironmentSecretVariables() + if err != nil { + return nil, err + } + envVars := c.generateLogicalBackupPodEnvVars() + envVars = append(envVars, secretEnvVarsList...) logicalBackupContainer := generateContainer( logicalBackupContainerName, &c.OpConfig.LogicalBackup.LogicalBackupDockerImage, diff --git a/pkg/cluster/k8sres_test.go b/pkg/cluster/k8sres_test.go index 13b89f460..397c25358 100644 --- a/pkg/cluster/k8sres_test.go +++ b/pkg/cluster/k8sres_test.go @@ -192,6 +192,7 @@ func TestExtractPgVersionFromBinPath(t *testing.T) { const ( testPodEnvironmentConfigMapName = "pod_env_cm" testPodEnvironmentSecretName = "pod_env_sc" + testCronjobEnvironmentSecretName = "pod_env_sc" testPodEnvironmentObjectNotExists = "idonotexist" testPodEnvironmentSecretNameAPIError = "pod_env_sc_apierror" testResourceCheckInterval = 3 @@ -448,6 +449,96 @@ func TestPodEnvironmentSecretVariables(t *testing.T) { } +// Test if the keys of an existing secret are properly referenced +func TestCronjobEnvironmentSecretVariables(t *testing.T) { + testName := "TestCronjobEnvironmentSecretVariables" + tests := []struct { + subTest string + opConfig config.Config + envVars []v1.EnvVar + err error + }{ + { + subTest: "No CronjobEnvironmentSecret configured", + envVars: []v1.EnvVar{}, + }, + { + subTest: "Secret referenced by CronjobEnvironmentSecret does not exist", + opConfig: config.Config{ + LogicalBackup: config.LogicalBackup{ + LogicalBackupCronjobEnvironmentSecret: "idonotexist", + }, + }, + err: fmt.Errorf("could not read Secret CronjobEnvironmentSecretName: secret.core \"idonotexist\" not found"), + }, + { + subTest: "Cronjob environment vars reference all keys from secret configured by CronjobEnvironmentSecret", + opConfig: config.Config{ + LogicalBackup: config.LogicalBackup{ + LogicalBackupCronjobEnvironmentSecret: testCronjobEnvironmentSecretName, + }, + }, + envVars: []v1.EnvVar{ + { + Name: "clone_aws_access_key_id", + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: testPodEnvironmentSecretName, + }, + Key: "clone_aws_access_key_id", + }, + }, + }, + { + Name: "custom_variable", + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: testPodEnvironmentSecretName, + }, + Key: "custom_variable", + }, + }, + }, + { + Name: "standby_google_application_credentials", + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: testPodEnvironmentSecretName, + }, + Key: "standby_google_application_credentials", + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + c := newMockCluster(tt.opConfig) + vars, err := c.getCronjobEnvironmentSecretVariables() + sort.Slice(vars, func(i, j int) bool { return vars[i].Name < vars[j].Name }) + if !reflect.DeepEqual(vars, tt.envVars) { + t.Errorf("%s %s: expected `%v` but got `%v`", + testName, tt.subTest, tt.envVars, vars) + } + if tt.err != nil { + if err.Error() != tt.err.Error() { + t.Errorf("%s %s: expected error `%v` but got `%v`", + testName, tt.subTest, tt.err, err) + } + } else { + if err != nil { + t.Errorf("%s %s: expected no error but got error: `%v`", + testName, tt.subTest, err) + } + } + } + +} + func testEnvs(cluster *Cluster, podSpec *v1.PodTemplateSpec, role PostgresRole) error { required := map[string]bool{ "PGHOST": false, diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index 36c30d318..7636b5b5a 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -189,6 +189,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.LogicalBackupS3RetentionTime = fromCRD.LogicalBackup.RetentionTime result.LogicalBackupGoogleApplicationCredentials = fromCRD.LogicalBackup.GoogleApplicationCredentials result.LogicalBackupJobPrefix = util.Coalesce(fromCRD.LogicalBackup.JobPrefix, "logical-backup-") + result.LogicalBackupCronjobEnvironmentSecret = fromCRD.LogicalBackup.CronjobEnvironmentSecret result.LogicalBackupCPURequest = fromCRD.LogicalBackup.CPURequest result.LogicalBackupMemoryRequest = fromCRD.LogicalBackup.MemoryRequest result.LogicalBackupCPULimit = fromCRD.LogicalBackup.CPULimit diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 7553bdbf9..940b1a86b 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -140,6 +140,7 @@ type LogicalBackup struct { LogicalBackupS3RetentionTime string `name:"logical_backup_s3_retention_time" default:""` LogicalBackupGoogleApplicationCredentials string `name:"logical_backup_google_application_credentials" default:""` LogicalBackupJobPrefix string `name:"logical_backup_job_prefix" default:"logical-backup-"` + LogicalBackupCronjobEnvironmentSecret string `name:"logical_backup_cronjob_environment_secret" default:""` LogicalBackupCPURequest string `name:"logical_backup_cpu_request"` LogicalBackupMemoryRequest string `name:"logical_backup_memory_request"` LogicalBackupCPULimit string `name:"logical_backup_cpu_limit"`