diff --git a/charts/postgres-operator/crds/postgresqls.yaml b/charts/postgres-operator/crds/postgresqls.yaml index a83f7cc95..35549dc7e 100644 --- a/charts/postgres-operator/crds/postgresqls.yaml +++ b/charts/postgres-operator/crds/postgresqls.yaml @@ -182,6 +182,17 @@ spec: # Note: usernames specified here as database owners must be declared in the users key of the spec key. dockerImage: type: string + imagePullSecrets: + type: array + nullable: true + description: "Optionally specify an array of imagePullSecrets for the spilo pod" + items: + type: object + required: + - name + properties: + name: + type: string enableConnectionPooler: type: boolean enableReplicaConnectionPooler: diff --git a/charts/postgres-operator/values.yaml b/charts/postgres-operator/values.yaml index 472be7443..420de929b 100644 --- a/charts/postgres-operator/values.yaml +++ b/charts/postgres-operator/values.yaml @@ -39,6 +39,9 @@ configGeneral: etcd_host: "" # Spilo docker image docker_image: ghcr.io/zalando/spilo-16:3.3-p1 + # Optionally specify an array of imagePullSecrets for the spilo pod + # image_pull_secrets: + # - myRegistryKeySecretName # key name for annotation to ignore globally configured instance limits # ignore_instance_limits_annotation_key: "" diff --git a/docs/reference/cluster_manifest.md b/docs/reference/cluster_manifest.md index 610982c73..3b8b8c06d 100644 --- a/docs/reference/cluster_manifest.md +++ b/docs/reference/cluster_manifest.md @@ -64,6 +64,10 @@ These parameters are grouped directly under the `spec` key in the manifest. custom Docker image that overrides the **docker_image** operator parameter. It should be a [Spilo](https://github.com/zalando/spilo) image. Optional. +* **imagePullSecrets** + Specify an array of imagePullSecrets to pull the spilo image (if you want + to pull your own spilo image from a private registry). Optional. + * **schedulerName** specifies the scheduling profile for database pods. If no value is provided K8s' `default-scheduler` will be used. Optional. diff --git a/docs/reference/operator_parameters.md b/docs/reference/operator_parameters.md index 4d4d16cdb..530e0089b 100644 --- a/docs/reference/operator_parameters.md +++ b/docs/reference/operator_parameters.md @@ -119,6 +119,10 @@ Those are top-level keys, containing both leaf keys and groups. your own Spilo image from the [github repository](https://github.com/zalando/spilo). +* **image_pull_secrets** + Specify an array of imagePullSecrets to pull the spilo image (if you + want to pull your own spilo image from a private registry). Optional. + * **sidecar_docker_images** *deprecated*: use **sidecars** instead. A map of sidecar names to Docker images to run with Spilo. In case of the name conflict with the definition in diff --git a/manifests/complete-postgres-manifest.yaml b/manifests/complete-postgres-manifest.yaml index 0b3dc4aa7..f6dee908b 100644 --- a/manifests/complete-postgres-manifest.yaml +++ b/manifests/complete-postgres-manifest.yaml @@ -11,6 +11,8 @@ metadata: # "delete-clustername": "acid-test-cluster" # can only be deleted when name matches if "delete-clustername" key is configured spec: dockerImage: ghcr.io/zalando/spilo-16:3.3-p1 + # imagePullSecrets: + # - name: myRegistryKeySecretName teamId: "acid" numberOfInstances: 2 users: # Application/Robot users diff --git a/manifests/configmap.yaml b/manifests/configmap.yaml index 1c8c8fdfd..6d3d3ec81 100644 --- a/manifests/configmap.yaml +++ b/manifests/configmap.yaml @@ -35,6 +35,7 @@ data: # delete_annotation_date_key: delete-date # delete_annotation_name_key: delete-clustername docker_image: ghcr.io/zalando/spilo-16:3.3-p1 + # image_pull_secrets: "myRegistryKeySecretName,myOtherRegistryKeySecretName" # downscaler_annotations: "deployment-time,downscaler/*" enable_admin_role_for_users: "true" enable_crd_registration: "true" diff --git a/manifests/operatorconfiguration.crd.yaml b/manifests/operatorconfiguration.crd.yaml index a7b1a7280..fee5612be 100644 --- a/manifests/operatorconfiguration.crd.yaml +++ b/manifests/operatorconfiguration.crd.yaml @@ -67,6 +67,16 @@ spec: docker_image: type: string default: "ghcr.io/zalando/spilo-16:3.3-p1" + image_pull_secrets: + type: array + nullable: true + items: + type: object + required: + - name + properties: + name: + type: string enable_crd_registration: type: boolean default: true diff --git a/manifests/postgresql-operator-default-configuration.yaml b/manifests/postgresql-operator-default-configuration.yaml index ecb7a03de..3b3187cf4 100644 --- a/manifests/postgresql-operator-default-configuration.yaml +++ b/manifests/postgresql-operator-default-configuration.yaml @@ -4,6 +4,8 @@ metadata: name: postgresql-operator-default-configuration configuration: docker_image: ghcr.io/zalando/spilo-16:3.3-p1 + # image_pull_secrets: + # - name: myRegistryKeySecretName # enable_crd_registration: true # crd_categories: # - all diff --git a/manifests/postgresql.crd.yaml b/manifests/postgresql.crd.yaml index 9f7e3eff8..97372f526 100644 --- a/manifests/postgresql.crd.yaml +++ b/manifests/postgresql.crd.yaml @@ -180,6 +180,17 @@ spec: # Note: usernames specified here as database owners must be declared in the users key of the spec key. dockerImage: type: string + imagePullSecrets: + type: array + nullable: true + description: "Optionally specify an array of imagePullSecrets for the spilo pod" + items: + type: object + required: + - name + properties: + name: + type: string enableConnectionPooler: type: boolean enableReplicaConnectionPooler: diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go index c5c4b2706..d387a0a77 100644 --- a/pkg/apis/acid.zalan.do/v1/crds.go +++ b/pkg/apis/acid.zalan.do/v1/crds.go @@ -291,6 +291,22 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{ "dockerImage": { Type: "string", }, + "imagePullSecrets": { + Type: "array", + Description: "Optionally specify an array of imagePullSecrets for the spilo pod", + Nullable: true, + Items: &apiextv1.JSONSchemaPropsOrArray{ + Schema: &apiextv1.JSONSchemaProps{ + Type: "object", + Required: []string{"name"}, + Properties: map[string]apiextv1.JSONSchemaProps{ + "name": { + Type: "string", + }, + }, + }, + }, + }, "enableConnectionPooler": { Type: "boolean", }, 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 eb01d450c..5aa3d29eb 100644 --- a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go +++ b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go @@ -263,6 +263,7 @@ type OperatorConfigurationData struct { EtcdHost string `json:"etcd_host,omitempty"` KubernetesUseConfigMaps bool `json:"kubernetes_use_configmaps,omitempty"` DockerImage string `json:"docker_image,omitempty"` + ImagePullSecrets []string `json:"image_pull_secrets,omitempty"` Workers uint32 `json:"workers,omitempty"` ResyncPeriod Duration `json:"resync_period,omitempty"` RepairPeriod Duration `json:"repair_period,omitempty"` diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index 1a8a311f5..6d2f52072 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -33,8 +33,9 @@ type PostgresSpec struct { EnableReplicaConnectionPooler *bool `json:"enableReplicaConnectionPooler,omitempty"` ConnectionPooler *ConnectionPooler `json:"connectionPooler,omitempty"` - TeamID string `json:"teamId"` - DockerImage string `json:"dockerImage,omitempty"` + TeamID string `json:"teamId"` + DockerImage string `json:"dockerImage,omitempty"` + ImagePullSecrets []v1.LocalObjectReference `json:"imagePullSecrets,omitempty"` // deprecated field storing cluster name without teamId prefix ClusterName string `json:"-"` diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 7c0b3ee23..405a468f5 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -433,6 +433,11 @@ func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData *out = make([]string, len(*in)) copy(*out, *in) } + if in.ImagePullSecrets != nil { + in, out := &in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.ShmVolume != nil { in, out := &in.ShmVolume, &out.ShmVolume *out = new(bool) @@ -673,6 +678,11 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) { *out = new(ConnectionPooler) (*in).DeepCopyInto(*out) } + if in.ImagePullSecrets != nil { + in, out := &in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]corev1.LocalObjectReference, len(*in)) + copy(*out, *in) + } if in.SpiloRunAsUser != nil { in, out := &in.SpiloRunAsUser, &out.SpiloRunAsUser *out = new(int64) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 4e67dbd94..61b6988a0 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -821,6 +821,7 @@ func (c *Cluster) generatePodTemplate( additionalSecretMount string, additionalSecretMountPath string, additionalVolumes []acidv1.AdditionalVolume, + imagePullSecrets []v1.LocalObjectReference, ) (*v1.PodTemplateSpec, error) { terminateGracePeriodSeconds := terminateGracePeriod @@ -849,6 +850,10 @@ func (c *Cluster) generatePodTemplate( SecurityContext: &securityContext, } + if imagePullSecrets != nil { + podSpec.ImagePullSecrets = imagePullSecrets + } + if schedulerName != nil { podSpec.SchedulerName = *schedulerName } @@ -1332,6 +1337,12 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef // pickup the docker image for the spilo container effectiveDockerImage := util.Coalesce(spec.DockerImage, c.OpConfig.DockerImage) + // get ImagePullSecrets for spilo pod + effectiveImagePullSecrets := c.OpConfig.ImagePullSecrets + if spec.ImagePullSecrets != nil { + effectiveImagePullSecrets = spec.ImagePullSecrets + } + // determine the User, Group and FSGroup for the spilo pod effectiveRunAsUser := c.OpConfig.Resources.SpiloRunAsUser if spec.SpiloRunAsUser != nil { @@ -1476,7 +1487,8 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef c.OpConfig.PodAntiAffinityPreferredDuringScheduling, c.OpConfig.AdditionalSecretMount, c.OpConfig.AdditionalSecretMountPath, - additionalVolumes) + additionalVolumes, + effectiveImagePullSecrets) if err != nil { return nil, fmt.Errorf("could not generate pod template: %v", err) @@ -2334,7 +2346,8 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1.CronJob, error) { false, c.OpConfig.AdditionalSecretMount, c.OpConfig.AdditionalSecretMountPath, - []acidv1.AdditionalVolume{}); err != nil { + []acidv1.AdditionalVolume{}, + nil); err != nil { return nil, fmt.Errorf("could not generate pod template for logical backup pod: %v", err) } diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index 78e752f1d..57ee49f83 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -40,6 +40,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.EtcdHost = fromCRD.EtcdHost result.KubernetesUseConfigMaps = fromCRD.KubernetesUseConfigMaps result.DockerImage = util.Coalesce(fromCRD.DockerImage, "ghcr.io/zalando/spilo-16:3.3-p1") + result.ImagePullSecrets = util.StrArrToLocalObjectReferenceArr(fromCRD.ImagePullSecrets) result.Workers = util.CoalesceUInt32(fromCRD.Workers, 8) result.MinInstances = fromCRD.MinInstances result.MaxInstances = fromCRD.MaxInstances diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 4c7b8db10..dc9ca2194 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -176,6 +176,7 @@ type Config struct { 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.3-p1"` + ImagePullSecrets []v1.LocalObjectReference `name:"image_pull_secrets"` 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"` diff --git a/pkg/util/util.go b/pkg/util/util.go index 4b3aafc63..111ac1002 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -17,6 +17,7 @@ import ( "time" "github.com/motomux/pretty" + corev1 "k8s.io/api/core/v1" resource "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -379,6 +380,15 @@ func IsSmallerQuantity(requestStr, limitStr string) (bool, error) { return request.Cmp(limit) == -1, nil } +// StrArrToLocalObjectReferenceArr : Converts an array of strings into an array of v1.LocalObjectReference +func StrArrToLocalObjectReferenceArr(arr []string) []corev1.LocalObjectReference { + ret := make([]corev1.LocalObjectReference, len(arr)) + for k, v := range arr { + ret[k] = corev1.LocalObjectReference{Name: v} + } + return ret +} + func MinResource(maxRequestStr, requestStr string) (resource.Quantity, error) { isSmaller, err := IsSmallerQuantity(maxRequestStr, requestStr)