package config import ( "encoding/json" "strings" "time" "fmt" "github.com/zalando/postgres-operator/pkg/spec" "github.com/zalando/postgres-operator/pkg/util/constants" v1 "k8s.io/api/core/v1" ) // CRD describes CustomResourceDefinition specific configuration parameters type CRD struct { ReadyWaitInterval time.Duration `name:"ready_wait_interval" default:"4s"` ReadyWaitTimeout time.Duration `name:"ready_wait_timeout" default:"30s"` ResyncPeriod time.Duration `name:"resync_period" default:"30m"` RepairPeriod time.Duration `name:"repair_period" default:"5m"` EnableCRDRegistration *bool `name:"enable_crd_registration" default:"true"` EnableCRDValidation *bool `name:"enable_crd_validation" default:"true"` CRDCategories []string `name:"crd_categories" default:"all"` } // Resources describes kubernetes resource specific configuration parameters type Resources struct { ResourceCheckInterval time.Duration `name:"resource_check_interval" default:"3s"` ResourceCheckTimeout time.Duration `name:"resource_check_timeout" default:"10m"` PodLabelWaitTimeout time.Duration `name:"pod_label_wait_timeout" default:"10m"` PodDeletionWaitTimeout time.Duration `name:"pod_deletion_wait_timeout" default:"10m"` PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"` SpiloRunAsUser *int64 `name:"spilo_runasuser"` SpiloRunAsGroup *int64 `name:"spilo_runasgroup"` SpiloFSGroup *int64 `name:"spilo_fsgroup"` PodPriorityClassName string `name:"pod_priority_class_name"` ClusterDomain string `name:"cluster_domain" default:"cluster.local"` SpiloPrivileged bool `name:"spilo_privileged" default:"false"` SpiloAllowPrivilegeEscalation *bool `name:"spilo_allow_privilege_escalation" default:"true"` AdditionalPodCapabilities []string `name:"additional_pod_capabilities" default:""` ClusterLabels map[string]string `name:"cluster_labels" default:"application:spilo"` InheritedLabels []string `name:"inherited_labels" default:""` InheritedAnnotations []string `name:"inherited_annotations" default:""` DownscalerAnnotations []string `name:"downscaler_annotations"` IgnoredAnnotations []string `name:"ignored_annotations"` ClusterNameLabel string `name:"cluster_name_label" default:"cluster-name"` DeleteAnnotationDateKey string `name:"delete_annotation_date_key"` DeleteAnnotationNameKey string `name:"delete_annotation_name_key"` PodRoleLabel string `name:"pod_role_label" default:"spilo-role"` PodToleration map[string]string `name:"toleration" default:""` DefaultCPURequest string `name:"default_cpu_request"` DefaultMemoryRequest string `name:"default_memory_request"` DefaultCPULimit string `name:"default_cpu_limit"` DefaultMemoryLimit string `name:"default_memory_limit"` MinCPULimit string `name:"min_cpu_limit"` MinMemoryLimit string `name:"min_memory_limit"` MaxCPURequest string `name:"max_cpu_request"` MaxMemoryRequest string `name:"max_memory_request"` PodEnvironmentConfigMap spec.NamespacedName `name:"pod_environment_configmap"` PodEnvironmentSecret string `name:"pod_environment_secret"` NodeReadinessLabel map[string]string `name:"node_readiness_label" default:""` NodeReadinessLabelMerge string `name:"node_readiness_label_merge" default:"OR"` ShmVolume *bool `name:"enable_shm_volume" default:"true"` MaxInstances int32 `name:"max_instances" default:"-1"` MinInstances int32 `name:"min_instances" default:"-1"` IgnoreInstanceLimitsAnnotationKey string `name:"ignore_instance_limits_annotation_key"` } type InfrastructureRole struct { // Name of a secret which describes the role, and optionally name of a // configmap with an extra information SecretName spec.NamespacedName UserKey string PasswordKey string RoleKey string DefaultUserValue string DefaultRoleValue string // This field point out the detailed yaml definition of the role, if exists Details string // Specify if a secret contains multiple fields in the following format: // // %(userkey)idx: ... // %(passwordkey)idx: ... // %(rolekey)idx: ... // // If it does, Name/Password/Role are interpreted not as unique field // names, but as a template. Template bool } // Auth describes authentication specific configuration parameters type Auth struct { SecretNameTemplate StringTemplate `name:"secret_name_template" default:"{username}.{cluster}.credentials.{tprkind}.{tprgroup}"` PamRoleName string `name:"pam_role_name" default:"zalandos"` PamConfiguration string `name:"pam_configuration" default:"https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"` TeamsAPIUrl string `name:"teams_api_url" default:"https://teams.example.com/api/"` OAuthTokenSecretName spec.NamespacedName `name:"oauth_token_secret_name" default:"postgresql-operator"` InfrastructureRolesSecretName spec.NamespacedName `name:"infrastructure_roles_secret_name"` InfrastructureRoles []*InfrastructureRole `name:"-"` InfrastructureRolesDefs string `name:"infrastructure_roles_secrets"` SuperUsername string `name:"super_username" default:"postgres"` ReplicationUsername string `name:"replication_username" default:"standby"` AdditionalOwnerRoles []string `name:"additional_owner_roles" default:""` EnablePasswordRotation bool `name:"enable_password_rotation" default:"false"` PasswordRotationInterval uint32 `name:"password_rotation_interval" default:"90"` PasswordRotationUserRetention uint32 `name:"password_rotation_user_retention" default:"180"` } // Scalyr holds the configuration for the Scalyr Agent sidecar for log shipping: type Scalyr struct { ScalyrAPIKey string `name:"scalyr_api_key" default:""` ScalyrImage string `name:"scalyr_image" default:""` ScalyrServerURL string `name:"scalyr_server_url" default:"https://upload.eu.scalyr.com"` ScalyrCPURequest string `name:"scalyr_cpu_request" default:"100m"` ScalyrMemoryRequest string `name:"scalyr_memory_request" default:"50Mi"` ScalyrCPULimit string `name:"scalyr_cpu_limit" default:"1"` ScalyrMemoryLimit string `name:"scalyr_memory_limit" default:"500Mi"` } // LogicalBackup defines configuration for logical backup type LogicalBackup struct { LogicalBackupSchedule string `name:"logical_backup_schedule" default:"30 00 * * *"` LogicalBackupDockerImage string `name:"logical_backup_docker_image" default:"registry.opensource.zalan.do/acid/logical-backup:v1.10.1"` LogicalBackupProvider string `name:"logical_backup_provider" default:"s3"` LogicalBackupAzureStorageAccountName string `name:"logical_backup_azure_storage_account_name" default:""` LogicalBackupAzureStorageContainer string `name:"logical_backup_azure_storage_container" default:""` LogicalBackupAzureStorageAccountKey string `name:"logical_backup_azure_storage_account_key" default:""` LogicalBackupS3Bucket string `name:"logical_backup_s3_bucket" default:""` LogicalBackupS3Region string `name:"logical_backup_s3_region" default:""` LogicalBackupS3Endpoint string `name:"logical_backup_s3_endpoint" default:""` LogicalBackupS3AccessKeyID string `name:"logical_backup_s3_access_key_id" default:""` LogicalBackupS3SecretAccessKey string `name:"logical_backup_s3_secret_access_key" default:""` LogicalBackupS3SSE string `name:"logical_backup_s3_sse" default:""` 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"` LogicalBackupMemoryLimit string `name:"logical_backup_memory_limit"` } // Operator options for connection pooler type ConnectionPooler struct { NumberOfInstances *int32 `name:"connection_pooler_number_of_instances" default:"2"` Schema string `name:"connection_pooler_schema" default:"pooler"` User string `name:"connection_pooler_user" default:"pooler"` Image string `name:"connection_pooler_image" default:"registry.opensource.zalan.do/acid/pgbouncer"` Mode string `name:"connection_pooler_mode" default:"transaction"` MaxDBConnections *int32 `name:"connection_pooler_max_db_connections" default:"60"` ConnectionPoolerDefaultCPURequest string `name:"connection_pooler_default_cpu_request"` ConnectionPoolerDefaultMemoryRequest string `name:"connection_pooler_default_memory_request"` ConnectionPoolerDefaultCPULimit string `name:"connection_pooler_default_cpu_limit"` ConnectionPoolerDefaultMemoryLimit string `name:"connection_pooler_default_memory_limit"` } // Config describes operator config type Config struct { CRD Resources Auth Scalyr LogicalBackup ConnectionPooler WatchedNamespace string `name:"watched_namespace"` // special values: "*" means 'watch all namespaces', the empty string "" means 'watch a namespace where operator is deployed to' KubernetesUseConfigMaps bool `name:"kubernetes_use_configmaps" default:"false"` EtcdHost string `name:"etcd_host" default:""` // special values: the empty string "" means Patroni will use K8s as a DCS DockerImage string `name:"docker_image" default:"ghcr.io/zalando/spilo-16:3.2-p2"` SidecarImages map[string]string `name:"sidecar_docker_images"` // deprecated in favour of SidecarContainers SidecarContainers []v1.Container `name:"sidecars"` PodServiceAccountName string `name:"pod_service_account_name" default:"postgres-pod"` // value of this string must be valid JSON or YAML; see initPodServiceAccount PodServiceAccountDefinition string `name:"pod_service_account_definition" default:""` PodServiceAccountRoleBindingDefinition string `name:"pod_service_account_role_binding_definition" default:""` MasterPodMoveTimeout time.Duration `name:"master_pod_move_timeout" default:"20m"` DbHostedZone string `name:"db_hosted_zone" default:"db.example.com"` AWSRegion string `name:"aws_region" default:"eu-central-1"` WALES3Bucket string `name:"wal_s3_bucket"` LogS3Bucket string `name:"log_s3_bucket"` KubeIAMRole string `name:"kube_iam_role"` WALGSBucket string `name:"wal_gs_bucket"` GCPCredentials string `name:"gcp_credentials"` WALAZStorageAccount string `name:"wal_az_storage_account"` AdditionalSecretMount string `name:"additional_secret_mount"` AdditionalSecretMountPath string `name:"additional_secret_mount_path" default:"/meta/credentials"` EnableEBSGp3Migration bool `name:"enable_ebs_gp3_migration" default:"false"` EnableEBSGp3MigrationMaxSize int64 `name:"enable_ebs_gp3_migration_max_size" default:"1000"` DebugLogging bool `name:"debug_logging" default:"true"` EnableDBAccess bool `name:"enable_database_access" default:"true"` EnableTeamsAPI bool `name:"enable_teams_api" default:"true"` EnableTeamSuperuser bool `name:"enable_team_superuser" default:"false"` TeamAdminRole string `name:"team_admin_role" default:"admin"` RoleDeletionSuffix string `name:"role_deletion_suffix" default:"_deleted"` EnableTeamMemberDeprecation bool `name:"enable_team_member_deprecation" default:"false"` EnableAdminRoleForUsers bool `name:"enable_admin_role_for_users" default:"true"` EnablePostgresTeamCRD bool `name:"enable_postgres_team_crd" default:"false"` EnablePostgresTeamCRDSuperusers bool `name:"enable_postgres_team_crd_superusers" default:"false"` EnableMasterLoadBalancer bool `name:"enable_master_load_balancer" default:"true"` EnableMasterPoolerLoadBalancer bool `name:"enable_master_pooler_load_balancer" default:"false"` EnableReplicaLoadBalancer bool `name:"enable_replica_load_balancer" default:"false"` EnableReplicaPoolerLoadBalancer bool `name:"enable_replica_pooler_load_balancer" default:"false"` CustomServiceAnnotations map[string]string `name:"custom_service_annotations"` CustomPodAnnotations map[string]string `name:"custom_pod_annotations"` EnablePodAntiAffinity bool `name:"enable_pod_antiaffinity" default:"false"` PodAntiAffinityPreferredDuringScheduling bool `name:"pod_antiaffinity_preferred_during_scheduling" default:"false"` PodAntiAffinityTopologyKey string `name:"pod_antiaffinity_topology_key" default:"kubernetes.io/hostname"` StorageResizeMode string `name:"storage_resize_mode" default:"pvc"` EnableLoadBalancer *bool `name:"enable_load_balancer"` // deprecated and kept for backward compatibility ExternalTrafficPolicy string `name:"external_traffic_policy" default:"Cluster"` MasterDNSNameFormat StringTemplate `name:"master_dns_name_format" default:"{cluster}.{namespace}.{hostedzone}"` MasterLegacyDNSNameFormat StringTemplate `name:"master_legacy_dns_name_format" default:"{cluster}.{team}.{hostedzone}"` ReplicaDNSNameFormat StringTemplate `name:"replica_dns_name_format" default:"{cluster}-repl.{namespace}.{hostedzone}"` ReplicaLegacyDNSNameFormat StringTemplate `name:"replica_legacy_dns_name_format" default:"{cluster}-repl.{team}.{hostedzone}"` PDBNameFormat StringTemplate `name:"pdb_name_format" default:"postgres-{cluster}-pdb"` PDBMasterLabelSelector *bool `name:"pdb_master_label_selector" default:"true"` EnablePodDisruptionBudget *bool `name:"enable_pod_disruption_budget" default:"true"` EnableInitContainers *bool `name:"enable_init_containers" default:"true"` EnableSidecars *bool `name:"enable_sidecars" default:"true"` SharePgSocketWithSidecars *bool `name:"share_pgsocket_with_sidecars" default:"false"` Workers uint32 `name:"workers" default:"8"` APIPort int `name:"api_port" default:"8080"` RingLogLines int `name:"ring_log_lines" default:"100"` ClusterHistoryEntries int `name:"cluster_history_entries" default:"1000"` TeamAPIRoleConfiguration map[string]string `name:"team_api_role_configuration" default:"log_statement:all"` PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"` PodManagementPolicy string `name:"pod_management_policy" default:"ordered_ready"` EnableReadinessProbe bool `name:"enable_readiness_probe" default:"false"` ProtectedRoles []string `name:"protected_role_names" default:"admin,cron_admin"` PostgresSuperuserTeams []string `name:"postgres_superuser_teams" default:""` SetMemoryRequestToLimit bool `name:"set_memory_request_to_limit" default:"false"` EnableLazySpiloUpgrade bool `name:"enable_lazy_spilo_upgrade" default:"false"` EnableCrossNamespaceSecret bool `name:"enable_cross_namespace_secret" default:"false"` EnableFinalizers *bool `name:"enable_finalizers" default:"false"` EnablePgVersionEnvVar bool `name:"enable_pgversion_env_var" default:"true"` EnableSpiloWalPathCompat bool `name:"enable_spilo_wal_path_compat" default:"false"` EnableTeamIdClusternamePrefix bool `name:"enable_team_id_clustername_prefix" default:"false"` MajorVersionUpgradeMode string `name:"major_version_upgrade_mode" default:"off"` MajorVersionUpgradeTeamAllowList []string `name:"major_version_upgrade_team_allow_list" default:""` MinimalMajorVersion string `name:"minimal_major_version" default:"12"` TargetMajorVersion string `name:"target_major_version" default:"16"` PatroniAPICheckInterval time.Duration `name:"patroni_api_check_interval" default:"1s"` PatroniAPICheckTimeout time.Duration `name:"patroni_api_check_timeout" default:"5s"` EnablePatroniFailsafeMode *bool `name:"enable_patroni_failsafe_mode" default:"false"` PersistentVolumeClaimRetentionPolicy map[string]string `name:"persistent_volume_claim_retention_policy" default:"when_deleted:retain,when_scaled:retain"` } // MustMarshal marshals the config or panics func (c Config) MustMarshal() string { b, err := json.MarshalIndent(c, "", " ") if err != nil { panic(err) } return string(b) } // NewFromMap creates Config from the map func NewFromMap(m map[string]string) *Config { cfg := Config{} fields, _ := structFields(&cfg) for _, structField := range fields { key := strings.ToLower(structField.Name) value, ok := m[key] if !ok && structField.Default != "" { value = structField.Default } if value == "" { continue } err := processField(value, structField.Field) if err != nil { panic(err) } } if err := validate(&cfg); err != nil { panic(err) } return &cfg } // Copy creates a copy of the config func Copy(c *Config) Config { cfg := *c cfg.ClusterLabels = make(map[string]string, len(c.ClusterLabels)) for k, v := range c.ClusterLabels { cfg.ClusterLabels[k] = v } return cfg } func validate(cfg *Config) (err error) { if cfg.MinInstances > 0 && cfg.MaxInstances > 0 && cfg.MinInstances > cfg.MaxInstances { err = fmt.Errorf("minimum number of instances %d is set higher than the maximum number %d", cfg.MinInstances, cfg.MaxInstances) } if cfg.Workers == 0 { err = fmt.Errorf("number of workers should be higher than 0") } if *cfg.ConnectionPooler.NumberOfInstances < constants.ConnectionPoolerMinInstances { msg := "number of connection pooler instances should be higher than %d" err = fmt.Errorf(msg, constants.ConnectionPoolerMinInstances) } if cfg.ConnectionPooler.User == cfg.SuperUsername { msg := "connection pool user is not allowed to be the same as super user, username: %s" err = fmt.Errorf(msg, cfg.ConnectionPooler.User) } return }