Add CRD validation (#599)

* add CRD manifests with validation
* update documentation
* patroni slots is not an array but a nested hash map
* make deps call tools
* cover validation in docs and export it in crds.go
* add toggle to disable creation of CRD validation and document it
* use templated service account also for CRD-configured helm deployment
This commit is contained in:
Felix Kunde 2019-11-28 12:02:05 +01:00 committed by GitHub
parent 052940862a
commit a3b34f146f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 2270 additions and 108 deletions

View File

@ -88,7 +88,7 @@ vet:
@go vet $(PKG)
@staticcheck $(PKG)
deps:
deps: tools
GO111MODULE=on go mod vendor
test:

View File

@ -39,3 +39,262 @@ spec:
subresources:
status: {}
version: v1
validation:
openAPIV3Schema:
type: object
required:
- kind
- apiVersion
- configuration
properties:
kind:
type: string
enum:
- OperatorConfiguration
apiVersion:
type: string
enum:
- acid.zalan.do/v1
configuration:
type: object
properties:
docker_image:
type: string
enable_crd_validation:
type: boolean
enable_shm_volume:
type: boolean
etcd_host:
type: string
max_instances:
type: integer
minimum: -1 # -1 = disabled
min_instances:
type: integer
minimum: -1 # -1 = disabled
resync_period:
type: string
repair_period:
type: string
set_memory_request_to_limit:
type: boolean
sidecar_docker_images:
type: object
additionalProperties:
type: string
workers:
type: integer
minimum: 1
users:
type: object
properties:
replication_username:
type: string
super_username:
type: string
kubernetes:
type: object
properties:
cluster_domain:
type: string
cluster_labels:
type: object
additionalProperties:
type: string
cluster_name_label:
type: string
custom_pod_annotations:
type: object
additionalProperties:
type: string
enable_pod_antiaffinity:
type: boolean
enable_pod_disruption_budget:
type: boolean
infrastructure_roles_secret_name:
type: string
inherited_labels:
type: array
items:
type: string
node_readiness_label:
type: object
additionalProperties:
type: string
oauth_token_secret_name:
type: string
pdb_name_format:
type: string
pod_antiaffinity_topology_key:
type: string
pod_environment_configmap:
type: string
pod_management_policy:
type: string
enum:
- "ordered_ready"
- "parallel"
pod_role_label:
type: string
pod_service_account_name:
type: string
pod_terminate_grace_period:
type: string
secret_name_template:
type: string
spilo_fsgroup:
type: integer
spilo_privileged:
type: boolean
toleration:
type: object
additionalProperties:
type: string
watched_namespace:
type: string
postgres_pod_resources:
type: object
properties:
default_cpu_limit:
type: string
pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
default_cpu_request:
type: string
pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
default_memory_limit:
type: string
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
default_memory_request:
type: string
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
timeouts:
type: object
properties:
pod_label_wait_timeout:
type: string
pod_deletion_wait_timeout:
type: string
ready_wait_interval:
type: string
ready_wait_timeout:
type: string
resource_check_interval:
type: string
resource_check_timeout:
type: string
load_balancer:
type: object
properties:
db_hosted_zone:
type: string
enable_master_load_balancer:
type: boolean
enable_replica_load_balancer:
type: boolean
custom_service_annotations:
type: object
additionalProperties:
type: string
master_dns_name_format:
type: string
replica_dns_name_format:
type: string
aws_or_gcp:
type: object
properties:
additional_secret_mount:
type: string
additional_secret_mount_path:
type: string
aws_region:
type: string
kube_iam_role:
type: string
log_s3_bucket:
type: string
wal_s3_bucket:
type: string
logical_backup:
type: object
properties:
logical_backup_schedule:
type: string
pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
logical_backup_docker_image:
type: string
logical_backup_s3_bucket:
type: string
logical_backup_s3_endpoint:
type: string
logical_backup_s3_sse:
type: string
logical_backup_s3_access_key_id:
type: string
logical_backup_s3_secret_access_key:
type: string
debug:
type: object
properties:
debug_logging:
type: boolean
enable_database_access:
type: boolean
teams_api:
type: object
properties:
enable_admin_role_for_users:
type: boolean
enable_team_superuser:
type: boolean
enable_teams_api:
type: boolean
pam_configuration:
type: string
pam_role_name:
type: string
postgres_superuser_teams:
type: array
items:
type: string
protected_role_names:
type: array
items:
type: string
team_admin_role:
type: string
team_api_role_configuration:
type: object
additionalProperties:
type: string
teams_api_url:
type: string
logging_rest_api:
type: object
properties:
api_port:
type: integer
cluster_history_entries:
type: integer
ring_log_lines:
type: integer
scalyr:
type: object
properties:
scalyr_api_key:
type: string
scalyr_cpu_limit:
type: string
pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
scalyr_cpu_request:
type: string
pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
scalyr_image:
type: string
scalyr_memory_limit:
type: string
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
scalyr_memory_request:
type: string
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
scalyr_server_url:
type: string

View File

@ -51,3 +51,313 @@ spec:
subresources:
status: {}
version: v1
validation:
openAPIV3Schema:
type: object
required:
- kind
- apiVersion
- spec
properties:
kind:
type: string
enum:
- postgresql
apiVersion:
type: string
enum:
- acid.zalan.do/v1
spec:
type: object
required:
- numberOfInstances
- teamId
- postgresql
properties:
allowedSourceRanges:
type: array
nullable: true
items:
type: string
pattern: '^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])$'
clone:
type: object
required:
- cluster
properties:
cluster:
type: string
s3_endpoint:
type: string
s3_access_key_id:
type: string
s3_secret_access_key:
type: string
s3_force_path_style:
type: string
s3_wal_path:
type: string
timestamp:
type: string
pattern: '^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([+-]([01][0-9]|2[0-3]):[0-5][0-9]))$'
# The regexp matches the date-time format (RFC 3339 Section 5.6) that specifies a timezone as an offset relative to UTC
# Example: 1996-12-19T16:39:57-08:00
# Note: this field requires a timezone
uid:
format: uuid
type: string
databases:
type: object
additionalProperties:
type: string
# Note: usernames specified here as database owners must be declared in the users key of the spec key.
dockerImage:
type: string
enableLogicalBackup:
type: boolean
enableMasterLoadBalancer:
type: boolean
enableReplicaLoadBalancer:
type: boolean
enableShmVolume:
type: boolean
init_containers: # deprecated
type: array
nullable: true
items:
type: object
additionalProperties: true
initContainers:
type: array
nullable: true
items:
type: object
additionalProperties: true
logicalBackupSchedule:
type: string
pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
maintenanceWindows:
type: array
items:
type: string
pattern: '^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ *$'
numberOfInstances:
type: integer
minimum: 0
patroni:
type: object
properties:
initdb:
type: object
additionalProperties:
type: string
pg_hba:
type: array
items:
type: string
slots:
type: object
additionalProperties:
type: object
additionalProperties:
type: string
ttl:
type: integer
loop_wait:
type: integer
retry_timeout:
type: integer
maximum_lag_on_failover:
type: integer
podAnnotations:
type: object
additionalProperties:
type: string
pod_priority_class_name: # deprecated
type: string
podPriorityClassName:
type: string
postgresql:
type: object
required:
- version
properties:
version:
type: string
enum:
- "9.3"
- "9.4"
- "9.5"
- "9.6"
- "10"
- "11"
- "12"
parameters:
type: object
additionalProperties:
type: string
replicaLoadBalancer: # deprecated
type: boolean
resources:
type: object
required:
- requests
- limits
properties:
limits:
type: object
required:
- cpu
- memory
properties:
cpu:
type: string
# Decimal natural followed by m, or decimal natural followed by
# dot followed by up to three decimal digits.
#
# This is because the Kubernetes CPU resource has millis as the
# maximum precision. The actual values are checked in code
# because the regular expression would be huge and horrible and
# not very helpful in validation error messages; this one checks
# only the format of the given number.
#
# https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu
pattern: '^(\d+m|\d+\.\d{1,3})$'
# Note: the value specified here must not be zero or be lower
# than the corresponding request.
memory:
type: string
# You can express memory as a plain integer or as a fixed-point
# integer using one of these suffixes: E, P, T, G, M, k. You can
# also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki
#
# https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
# Note: the value specified here must not be zero or be lower
# than the corresponding request.
requests:
type: object
required:
- cpu
- memory
properties:
cpu:
type: string
# Decimal natural followed by m, or decimal natural followed by
# dot followed by up to three decimal digits.
#
# This is because the Kubernetes CPU resource has millis as the
# maximum precision. The actual values are checked in code
# because the regular expression would be huge and horrible and
# not very helpful in validation error messages; this one checks
# only the format of the given number.
#
# https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu
pattern: '^(\d+m|\d+\.\d{1,3})$'
# Note: the value specified here must not be zero or be higher
# than the corresponding limit.
memory:
type: string
# You can express memory as a plain integer or as a fixed-point
# integer using one of these suffixes: E, P, T, G, M, k. You can
# also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki
#
# https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
# Note: the value specified here must not be zero or be higher
# than the corresponding limit.
sidecars:
type: array
nullable: true
items:
type: object
additionalProperties: true
spiloFSGroup:
type: integer
standby:
type: object
required:
- s3_wal_path
properties:
s3_wal_path:
type: string
teamId:
type: string
tolerations:
type: array
items:
type: object
required:
- key
- operator
- effect
properties:
key:
type: string
operator:
type: string
enum:
- Equal
- Exists
value:
type: string
effect:
type: string
enum:
- NoExecute
- NoSchedule
- PreferNoSchedule
tolerationSeconds:
type: integer
useLoadBalancer: # deprecated
type: boolean
users:
type: object
additionalProperties:
type: array
nullable: true
description: "Role flags specified here must not contradict each other"
items:
type: string
enum:
- bypassrls
- BYPASSRLS
- nobypassrls
- NOBYPASSRLS
- createdb
- CREATEDB
- nocreatedb
- NOCREATEDB
- createrole
- CREATEROLE
- nocreaterole
- NOCREATEROLE
- inherit
- INHERIT
- noinherit
- NOINHERIT
- login
- LOGIN
- nologin
- NOLOGIN
- replication
- REPLICATION
- noreplication
- NOREPLICATION
- superuser
- SUPERUSER
- nosuperuser
- NOSUPERUSER
volume:
type: object
required:
- size
properties:
size:
type: string
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
# Note: the value specified here must not be zero.
storageClass:
type: string
subPath:
type: string

View File

@ -14,7 +14,7 @@ configuration:
{{ toYaml .Values.configUsers | indent 4 }}
kubernetes:
oauth_token_secret_name: {{ template "postgres-operator.fullname" . }}
pod_service_account_name: operator
pod_service_account_name: {{ include "postgres-operator.serviceAccountName" . }}
{{ toYaml .Values.configKubernetes | indent 4 }}
postgres_pod_resources:
{{ toYaml .Values.configPostgresPodResources | indent 4 }}

View File

@ -17,6 +17,8 @@ configTarget: "OperatorConfigurationCRD"
# general top-level configuration parameters
configGeneral:
# choose if deployment creates/updates CRDs with OpenAPIV3Validation
enable_crd_validation: true
# start any new database pod without limitations on shm memory
enable_shm_volume: true
# etcd connection string for Patroni. Empty uses K8s-native DCS.
@ -57,7 +59,10 @@ configKubernetes:
# label assigned to Kubernetes objects created by the operator
cluster_name_label: cluster-name
# additional annotations to add to every database pod
custom_pod_annotations:
# custom_pod_annotations:
# keya: valuea
# keyb: valueb
# toggles pod anti affinity on the Postgres pods
enable_pod_antiaffinity: false
# toggles PDB to set to MinAvailabe 0 or 1
@ -74,7 +79,8 @@ configKubernetes:
# master_pod_move_timeout: 20m
# set of labels that a running and active node should possess to be considered ready
# node_readiness_label: ""
# node_readiness_label:
# status: ready
# name of the secret containing the OAuth2 token to pass to the teams API
# oauth_token_secret_name: postgresql-operator
@ -214,7 +220,8 @@ configTeamsApi:
# operator will add all team member roles to this group and add a pg_hba line
pam_role_name: zalandos
# List of teams which members need the superuser role in each Postgres cluster
# postgres_superuser_teams: "postgres_superusers"
# postgres_superuser_teams:
# - postgres_superusers
# List of roles that cannot be overwritten by an application, team or infrastructure role
protected_role_names:
@ -228,7 +235,7 @@ configTeamsApi:
# teams_api_url: http://fake-teams-api.default.svc.cluster.local
# Scalyr is a log management tool that Zalando uses as a sidecar
scalyr:
configScalyr:
# API key for the Scalyr sidecar
# scalyr_api_key: ""
@ -257,9 +264,7 @@ serviceAccount:
create: true
# The name of the ServiceAccount to use.
# If not set and create is true, a name is generated using the fullname template
# When relying solely on the OperatorConfiguration CRD, this value has to be "operator"
# Otherwise, the operator tries to use the "default" service account which is forbidden
name: operator
name:
priorityClassName: ""

View File

@ -17,6 +17,8 @@ configTarget: "ConfigMap"
# general configuration parameters
configGeneral:
# choose if deployment creates/updates CRDs with OpenAPIV3Validation
enable_crd_validation: "true"
# start any new database pod without limitations on shm memory
enable_shm_volume: "true"
# etcd connection string for Patroni. Empty uses K8s-native DCS.
@ -55,7 +57,8 @@ configKubernetes:
# label assigned to Kubernetes objects created by the operator
cluster_name_label: version
# annotations attached to each database pod
# custom_pod_annotations: keya:valuea
# custom_pod_annotations: keya:valuea,keyb:valueb
# toggles pod anti affinity on the Postgres pods
enable_pod_antiaffinity: "false"
# toggles PDB to set to MinAvailabe 0 or 1
@ -136,9 +139,9 @@ configLoadBalancer:
# toggles service type load balancer pointing to the replica pod of the cluster
enable_replica_load_balancer: "false"
# defines the DNS name string template for the master load balancer cluster
master_dns_name_format: '{cluster}.{team}.staging.{hostedzone}'
master_dns_name_format: '{cluster}.{team}.{hostedzone}'
# defines the DNS name string template for the replica load balancer cluster
replica_dns_name_format: '{cluster}-repl.{team}.staging.{hostedzone}'
replica_dns_name_format: '{cluster}-repl.{team}.{hostedzone}'
# options to aid debugging of the operator itself
configDebug:

View File

@ -28,7 +28,7 @@ pipeline:
IMAGE=registry-write.opensource.zalan.do/acid/postgres-operator-test
fi
export IMAGE
make tools deps docker
make deps docker
- desc: 'Run unit tests'
cmd: |
export PATH=$PATH:$HOME/go/bin

View File

@ -3,6 +3,30 @@
Learn how to configure and manage the Postgres Operator in your Kubernetes (K8s)
environment.
## CRD Validation
[CustomResourceDefinitions](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions)
will be registered with schema validation by default when the operator is
deployed. The `OperatorConfiguration` CRD will only get created if the
`POSTGRES_OPERATOR_CONFIGURATION_OBJECT` [environment variable](../manifests/postgres-operator.yaml#L36)
in the deployment yaml is set and not empty.
When submitting manifests of [`postgresql`](../manifests/postgresql.crd.yaml) or
[`OperatorConfiguration`](../manifests/operatorconfiguration.crd.yaml) custom
resources with kubectl, validation can be bypassed with `--validate=false`. The
operator can also be configured to not register CRDs with validation on `ADD` or
`UPDATE` events. Running instances are not affected when enabling the validation
afterwards unless the manifests is not changed then. Note, that the provided CRD
manifests contain the validation for users to understand what schema is
enforced.
Once the validation is enabled it can only be disabled manually by editing or
patching the CRD manifest:
```bash
zk8 patch crd postgresqls.acid.zalan.do -p '{"spec":{"validation": null}}'
```
## Namespaces
### Select the namespace to deploy to
@ -32,7 +56,7 @@ By default, the operator watches the namespace it is deployed to. You can
change this by setting the `WATCHED_NAMESPACE` var in the `env` section of the
[operator deployment](../manifests/postgres-operator.yaml) manifest or by
altering the `watched_namespace` field in the operator
[ConfigMap](../manifests/configmap.yaml#L79).
[configuration](../manifests/postgresql-operator-default-configuration.yaml#L49).
In the case both are set, the env var takes the precedence. To make the
operator listen to all namespaces, explicitly set the field/env var to "`*`".
@ -115,7 +139,7 @@ that are aggregated into the K8s [default roles](https://kubernetes.io/docs/refe
To ensure Postgres pods are running on nodes without any other application pods,
you can use [taints and tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/)
and configure the required toleration in the operator ConfigMap.
and configure the required toleration in the operator configuration.
As an example you can set following node taint:
@ -136,6 +160,21 @@ data:
...
```
For an OperatorConfiguration resource the toleration should be defined like
this:
```yaml
apiVersion: "acid.zalan.do/v1"
kind: OperatorConfiguration
metadata:
name: postgresql-configuration
configuration:
kubernetes:
toleration:
postgres: "key:postgres,operator:Exists,effect:NoSchedule"
...
```
Note that the K8s version 1.13 brings [taint-based eviction](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/#taint-based-evictions)
to the beta stage and enables it by default. Postgres pods by default receive
tolerations for `unreachable` and `noExecute` taints with the timeout of `5m`.
@ -148,7 +187,7 @@ completely, specify the toleration by leaving out the `tolerationSeconds` value
To ensure Postgres pods are running on different topologies, you can use
[pod anti affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/)
and configure the required topology in the operator ConfigMap.
and configure the required topology in the operator configuration.
Enable pod anti affinity by adding following line to the operator ConfigMap:
@ -161,21 +200,22 @@ data:
enable_pod_antiaffinity: "true"
```
By default the topology key for the pod anti affinity is set to
`kubernetes.io/hostname`, you can set another topology key e.g.
`failure-domain.beta.kubernetes.io/zone` by adding following line to the
operator ConfigMap, see [built-in node labels](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#interlude-built-in-node-labels) for available topology keys:
Likewise, when using an OperatorConfiguration resource add:
```yaml
apiVersion: v1
kind: ConfigMap
apiVersion: "acid.zalan.do/v1"
kind: OperatorConfiguration
metadata:
name: postgres-operator
data:
enable_pod_antiaffinity: "true"
pod_antiaffinity_topology_key: "failure-domain.beta.kubernetes.io/zone"
name: postgresql-configuration
configuration:
kubernetes:
enable_pod_antiaffinity: true
```
By default the topology key for the pod anti affinity is set to
`kubernetes.io/hostname`, you can set another topology key e.g.
`failure-domain.beta.kubernetes.io/zone`. See [built-in node labels](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#interlude-built-in-node-labels) for available topology keys.
## Pod Disruption Budget
By default the operator uses a PodDisruptionBudget (PDB) to protect the cluster
@ -280,6 +320,20 @@ data:
...
```
**OperatorConfiguration**
```yaml
apiVersion: "acid.zalan.do/v1"
kind: OperatorConfiguration
metadata:
name: postgresql-operator-configuration
configuration:
kubernetes:
# referencing config map with custom settings
pod_environment_configmap: postgres-pod-config
...
```
**referenced ConfigMap `postgres-pod-config`**
```yaml
@ -312,7 +366,7 @@ services: one for the master pod and one for replica pods. To expose these
services to an outer network, one can attach load balancers to them by setting
`enableMasterLoadBalancer` and/or `enableReplicaLoadBalancer` to `true` in the
cluster manifest. In the case any of these variables are omitted from the
manifest, the operator configmap's settings `enable_master_load_balancer` and
manifest, the operator configuration settings `enable_master_load_balancer` and
`enable_replica_load_balancer` apply. Note that the operator settings affect
all Postgresql services running in all namespaces watched by the operator.

View File

@ -33,7 +33,7 @@ by setting the `GO111MODULE` environment variable to `on`. The make targets do
this for you, so simply run
```bash
make tools deps
make deps
```
This would take a while to complete. You have to redo `make deps` every time
@ -284,6 +284,7 @@ manifest files:
Postgres manifest parameters are defined in the [api package](../pkg/apis/acid.zalan.do/v1/postgresql_type.go).
The operator behavior has to be implemented at least in [k8sres.go](../pkg/cluster/k8sres.go).
Validation of CRD parameters is controlled in [crd.go](../pkg/apis/acid.zalan.do/v1/crds.go).
Please, reflect your changes in tests, for example in:
* [config_test.go](../pkg/util/config/config_test.go)
* [k8sres_test.go](../pkg/cluster/k8sres_test.go)
@ -294,6 +295,7 @@ Please, reflect your changes in tests, for example in:
For the CRD-based configuration, please update the following files:
* the default [OperatorConfiguration](../manifests/postgresql-operator-default-configuration.yaml)
* the Helm chart's [values-crd file](../charts/postgres-operator/values.yaml)
* the CRD's [validation](../manifests/operatorconfiguration.crd.yaml)
Reflect the changes in the ConfigMap configuration as well (note that numeric
and boolean parameters have to use double quotes here):

View File

@ -55,8 +55,8 @@ kubectl create -f manifests/postgres-operator.yaml # deployment
```
There is a [Kustomization](https://github.com/kubernetes-sigs/kustomize)
manifest that [combines the mentioned resources](../manifests/kustomization.yaml) -
it can be used with kubectl 1.14 or newer as easy as:
manifest that [combines the mentioned resources](../manifests/kustomization.yaml)
(except for the CRD) - it can be used with kubectl 1.14 or newer as easy as:
```bash
kubectl apply -k github.com/zalando/postgres-operator/manifests
@ -86,8 +86,9 @@ To use CRD-based configuration you need to specify the [values-crd yaml file](..
helm install postgres-operator ./charts/postgres-operator -f ./charts/postgres-operator/values-crd.yaml
```
The chart works with both Helm 2 and Helm 3. Documentation for installing
applications with helm2 can be found in the [helm2 docs](https://v2.helm.sh/docs/).
The chart works with both Helm 2 and Helm 3. The `crd-install` hook from v2 will
be skipped with warning when using v3. Documentation for installing applications
with Helm 2 can be found in the [v2 docs](https://v2.helm.sh/docs/).
### Operator Lifecycle Manager (OLM)
@ -119,15 +120,15 @@ kubectl get pod -l app.kubernetes.io/name=postgres-operator
kubectl create -f manifests/minimal-postgres-manifest.yaml
```
After the cluster manifest is submitted the operator will create Service and
Endpoint resources and a StatefulSet which spins up new Pod(s) given the number
of instances specified in the manifest. All resources are named like the
cluster. The database pods can be identified by their number suffix, starting
from `-0`. They run the [Spilo](https://github.com/zalando/spilo) container
image by Zalando. As for the services and endpoints, there will be one for the
master pod and another one for all the replicas (`-repl` suffix). Check if all
components are coming up. Use the label `application=spilo` to filter and list
the label `spilo-role` to see who is currently the master.
After the cluster manifest is submitted and passed the validation the operator
will create Service and Endpoint resources and a StatefulSet which spins up new
Pod(s) given the number of instances specified in the manifest. All resources
are named like the cluster. The database pods can be identified by their number
suffix, starting from `-0`. They run the [Spilo](https://github.com/zalando/spilo)
container image by Zalando. As for the services and endpoints, there will be one
for the master pod and another one for all the replicas (`-repl` suffix). Check
if all components are coming up. Use the label `application=spilo` to filter and
list the label `spilo-role` to see who is currently the master.
```bash
# check the deployed cluster

View File

@ -29,21 +29,20 @@ configuration.
To test the CRD-based configuration locally, use the following
```bash
kubectl create -f manifests/operatorconfiguration.crd.yaml # registers the CRD
kubectl create -f manifests/postgresql-operator-default-configuration.yaml
kubectl create -f manifests/operator-service-account-rbac.yaml
kubectl create -f manifests/postgres-operator.yaml # set the env var as mentioned above
kubectl create -f manifests/postgresql-operator-default-configuration.yaml
kubectl get operatorconfigurations postgresql-operator-default-configuration -o yaml
```
Note that the operator first attempts to register the CRD of the
`OperatorConfiguration` and then waits for an instance to be created. In
between these two event the operator pod may be failing since it cannot fetch
the not-yet-existing `OperatorConfiguration` instance.
The CRD-based configuration is more powerful than the one based on ConfigMaps
and should be used unless there is a compatibility requirement to use an already
existing configuration. Even in that case, it should be rather straightforward
to convert the configmap based configuration into the CRD-based one and restart
the operator. The ConfigMaps-based configuration will be deprecated and
to convert the ConfigMap-based configuration into the CRD-based one and restart
the operator. The ConfigMap-based configuration will be deprecated and
subsequently removed in future releases.
Note that for the CRD-based configuration groups of configuration options below
@ -71,6 +70,11 @@ Variable names are underscore-separated words.
Those are top-level keys, containing both leaf keys and groups.
* **enable_crd_validation**
toggles if the operator will create or update CRDs with
[OpenAPI v3 schema validation](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#validation)
The default is `true`.
* **etcd_host**
Etcd connection string for Patroni defined as `host:port`. Not required when
Patroni native Kubernetes support is used. The default is empty (use

View File

@ -53,9 +53,9 @@ spec:
- hostssl all all 0.0.0.0/0 md5
- host all all 0.0.0.0/0 md5
# slots:
# - permanent_physical_1:
# permanent_physical_1:
# type: physical
# - permanent_logical_1:
# permanent_logical_1:
# type: logical
# database: foo
# plugin: pgoutput

View File

@ -12,7 +12,7 @@ data:
cluster_labels: application:spilo
cluster_name_label: version
# custom_service_annotations: "keyx:valuez,keya:valuea"
# custom_pod_annotations: "keya:valuea"
# custom_pod_annotations: "keya:valuea,keyb:valueb"
db_hosted_zone: db.example.com
debug_logging: "true"
# default_cpu_limit: "3"
@ -21,6 +21,7 @@ data:
# default_memory_request: 100Mi
docker_image: registry.opensource.zalan.do/acid/spilo-11:1.6-p1
# enable_admin_role_for_users: "true"
# enable_crd_validation: "true"
# enable_database_access: "true"
enable_master_load_balancer: "false"
# enable_pod_antiaffinity: "false"
@ -41,7 +42,7 @@ data:
# logical_backup_s3_secret_access_key: ""
# logical_backup_s3_sse: "AES256"
# logical_backup_schedule: "30 00 * * *"
master_dns_name_format: "{cluster}.{team}.staging.{hostedzone}"
master_dns_name_format: "{cluster}.{team}.{hostedzone}"
# master_pod_move_timeout: 10m
# max_instances: "-1"
# min_instances: "-1"
@ -64,11 +65,11 @@ data:
ready_wait_interval: 3s
ready_wait_timeout: 30s
repair_period: 5m
replica_dns_name_format: "{cluster}-repl.{team}.staging.{hostedzone}"
replica_dns_name_format: "{cluster}-repl.{team}.{hostedzone}"
replication_username: standby
resource_check_interval: 3s
resource_check_timeout: 10m
resync_period: 5m
resync_period: 30m
ring_log_lines: "100"
secret_name_template: "{username}.{cluster}.credentials"
# sidecar_docker_images: ""

View File

@ -0,0 +1,276 @@
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: operatorconfigurations.acid.zalan.do
spec:
group: acid.zalan.do
names:
kind: OperatorConfiguration
listKind: OperatorConfigurationList
plural: operatorconfigurations
singular: operatorconfiguration
shortNames:
- opconfig
scope: Namespaced
subresources:
status: {}
version: v1
validation:
openAPIV3Schema:
type: object
required:
- kind
- apiVersion
- configuration
properties:
kind:
type: string
enum:
- OperatorConfiguration
apiVersion:
type: string
enum:
- acid.zalan.do/v1
configuration:
type: object
properties:
docker_image:
type: string
enable_crd_validation:
type: boolean
enable_shm_volume:
type: boolean
etcd_host:
type: string
max_instances:
type: integer
minimum: -1 # -1 = disabled
min_instances:
type: integer
minimum: -1 # -1 = disabled
resync_period:
type: string
repair_period:
type: string
set_memory_request_to_limit:
type: boolean
sidecar_docker_images:
type: object
additionalProperties:
type: string
workers:
type: integer
minimum: 1
users:
type: object
properties:
replication_username:
type: string
super_username:
type: string
kubernetes:
type: object
properties:
cluster_domain:
type: string
cluster_labels:
type: object
additionalProperties:
type: string
cluster_name_label:
type: string
custom_pod_annotations:
type: object
additionalProperties:
type: string
enable_pod_antiaffinity:
type: boolean
enable_pod_disruption_budget:
type: boolean
infrastructure_roles_secret_name:
type: string
inherited_labels:
type: array
items:
type: string
node_readiness_label:
type: object
additionalProperties:
type: string
oauth_token_secret_name:
type: string
pdb_name_format:
type: string
pod_antiaffinity_topology_key:
type: string
pod_environment_configmap:
type: string
pod_management_policy:
type: string
enum:
- "ordered_ready"
- "parallel"
pod_role_label:
type: string
pod_service_account_name:
type: string
pod_terminate_grace_period:
type: string
secret_name_template:
type: string
spilo_fsgroup:
type: integer
spilo_privileged:
type: boolean
toleration:
type: object
additionalProperties:
type: string
watched_namespace:
type: string
postgres_pod_resources:
type: object
properties:
default_cpu_limit:
type: string
pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
default_cpu_request:
type: string
pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
default_memory_limit:
type: string
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
default_memory_request:
type: string
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
timeouts:
type: object
properties:
pod_label_wait_timeout:
type: string
pod_deletion_wait_timeout:
type: string
ready_wait_interval:
type: string
ready_wait_timeout:
type: string
resource_check_interval:
type: string
resource_check_timeout:
type: string
load_balancer:
type: object
properties:
db_hosted_zone:
type: string
enable_master_load_balancer:
type: boolean
enable_replica_load_balancer:
type: boolean
custom_service_annotations:
type: object
additionalProperties:
type: string
master_dns_name_format:
type: string
replica_dns_name_format:
type: string
aws_or_gcp:
type: object
properties:
additional_secret_mount:
type: string
additional_secret_mount_path:
type: string
aws_region:
type: string
kube_iam_role:
type: string
log_s3_bucket:
type: string
wal_s3_bucket:
type: string
logical_backup:
type: object
properties:
logical_backup_schedule:
type: string
pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
logical_backup_docker_image:
type: string
logical_backup_s3_bucket:
type: string
logical_backup_s3_endpoint:
type: string
logical_backup_s3_sse:
type: string
logical_backup_s3_access_key_id:
type: string
logical_backup_s3_secret_access_key:
type: string
debug:
type: object
properties:
debug_logging:
type: boolean
enable_database_access:
type: boolean
teams_api:
type: object
properties:
enable_admin_role_for_users:
type: boolean
enable_team_superuser:
type: boolean
enable_teams_api:
type: boolean
pam_configuration:
type: string
pam_role_name:
type: string
postgres_superuser_teams:
type: array
items:
type: string
protected_role_names:
type: array
items:
type: string
team_admin_role:
type: string
team_api_role_configuration:
type: object
additionalProperties:
type: string
teams_api_url:
type: string
logging_rest_api:
type: object
properties:
api_port:
type: integer
cluster_history_entries:
type: integer
ring_log_lines:
type: integer
scalyr:
type: object
properties:
scalyr_api_key:
type: string
scalyr_cpu_limit:
type: string
pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
scalyr_cpu_request:
type: string
pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
scalyr_image:
type: string
scalyr_memory_limit:
type: string
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
scalyr_memory_request:
type: string
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
scalyr_server_url:
type: string

View File

@ -3,6 +3,7 @@ kind: OperatorConfiguration
metadata:
name: postgresql-operator-default-configuration
configuration:
# enable_crd_validation: true
etcd_host: ""
docker_image: registry.opensource.zalan.do/acid/spilo-11:1.6-p1
# enable_shm_volume: true
@ -27,18 +28,19 @@ configuration:
# keyb: valueb
enable_pod_antiaffinity: false
enable_pod_disruption_budget: true
# infrastructure_roles_secret_name: ""
# infrastructure_roles_secret_name: postgresql-infrastructure-roles
# inherited_labels:
# - application
# - environment
# node_readiness_label: ""
# node_readiness_label:
# status: ready
oauth_token_secret_name: postgresql-operator
pdb_name_format: "postgres-{cluster}-pdb"
pod_antiaffinity_topology_key: "kubernetes.io/hostname"
# pod_environment_configmap: ""
pod_management_policy: "ordered_ready"
pod_role_label: spilo-role
pod_service_account_name: operator
pod_service_account_name: zalando-postgres-operator
pod_terminate_grace_period: 5m
secret_name_template: "{username}.{cluster}.credentials.{tprkind}.{tprgroup}"
# spilo_fsgroup: 103
@ -74,7 +76,6 @@ configuration:
# log_s3_bucket: ""
# wal_s3_bucket: ""
logical_backup:
log_s3_bucket: ""
logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup"
logical_backup_s3_access_key_id: ""
logical_backup_s3_bucket: "my-bucket-url"
@ -91,7 +92,8 @@ configuration:
enable_teams_api: false
# pam_configuration: ""
pam_role_name: zalandos
# postgres_superuser_teams: "postgres_superusers"
# postgres_superuser_teams:
# - postgres_superusers
protected_role_names:
- admin
team_admin_role: admin

View File

@ -0,0 +1,327 @@
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: postgresqls.acid.zalan.do
spec:
group: acid.zalan.do
names:
kind: postgresql
listKind: postgresqlList
plural: postgresqls
singular: postgresql
shortNames:
- pg
scope: Namespaced
subresources:
status: {}
version: v1
validation:
openAPIV3Schema:
type: object
required:
- kind
- apiVersion
- spec
properties:
kind:
type: string
enum:
- postgresql
apiVersion:
type: string
enum:
- acid.zalan.do/v1
spec:
type: object
required:
- numberOfInstances
- teamId
- postgresql
properties:
allowedSourceRanges:
type: array
nullable: true
items:
type: string
pattern: '^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])$'
clone:
type: object
required:
- cluster
properties:
cluster:
type: string
s3_endpoint:
type: string
s3_access_key_id:
type: string
s3_secret_access_key:
type: string
s3_force_path_style:
type: string
s3_wal_path:
type: string
timestamp:
type: string
pattern: '^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([+-]([01][0-9]|2[0-3]):[0-5][0-9]))$'
# The regexp matches the date-time format (RFC 3339 Section 5.6) that specifies a timezone as an offset relative to UTC
# Example: 1996-12-19T16:39:57-08:00
# Note: this field requires a timezone
uid:
format: uuid
type: string
databases:
type: object
additionalProperties:
type: string
# Note: usernames specified here as database owners must be declared in the users key of the spec key.
dockerImage:
type: string
enableLogicalBackup:
type: boolean
enableMasterLoadBalancer:
type: boolean
enableReplicaLoadBalancer:
type: boolean
enableShmVolume:
type: boolean
init_containers: # deprecated
type: array
nullable: true
items:
type: object
additionalProperties: true
initContainers:
type: array
nullable: true
items:
type: object
additionalProperties: true
logicalBackupSchedule:
type: string
pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
maintenanceWindows:
type: array
items:
type: string
pattern: '^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ *$'
numberOfInstances:
type: integer
minimum: 0
patroni:
type: object
properties:
initdb:
type: object
additionalProperties:
type: string
pg_hba:
type: array
items:
type: string
slots:
type: object
additionalProperties:
type: object
additionalProperties:
type: string
ttl:
type: integer
loop_wait:
type: integer
retry_timeout:
type: integer
maximum_lag_on_failover:
type: integer
podAnnotations:
type: object
additionalProperties:
type: string
pod_priority_class_name: # deprecated
type: string
podPriorityClassName:
type: string
postgresql:
type: object
required:
- version
properties:
version:
type: string
enum:
- "9.3"
- "9.4"
- "9.5"
- "9.6"
- "10"
- "11"
- "12"
parameters:
type: object
additionalProperties:
type: string
replicaLoadBalancer: # deprecated
type: boolean
resources:
type: object
required:
- requests
- limits
properties:
limits:
type: object
required:
- cpu
- memory
properties:
cpu:
type: string
# Decimal natural followed by m, or decimal natural followed by
# dot followed by up to three decimal digits.
#
# This is because the Kubernetes CPU resource has millis as the
# maximum precision. The actual values are checked in code
# because the regular expression would be huge and horrible and
# not very helpful in validation error messages; this one checks
# only the format of the given number.
#
# https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu
pattern: '^(\d+m|\d+\.\d{1,3})$'
# Note: the value specified here must not be zero or be lower
# than the corresponding request.
memory:
type: string
# You can express memory as a plain integer or as a fixed-point
# integer using one of these suffixes: E, P, T, G, M, k. You can
# also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki
#
# https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
# Note: the value specified here must not be zero or be lower
# than the corresponding request.
requests:
type: object
required:
- cpu
- memory
properties:
cpu:
type: string
# Decimal natural followed by m, or decimal natural followed by
# dot followed by up to three decimal digits.
#
# This is because the Kubernetes CPU resource has millis as the
# maximum precision. The actual values are checked in code
# because the regular expression would be huge and horrible and
# not very helpful in validation error messages; this one checks
# only the format of the given number.
#
# https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu
pattern: '^(\d+m|\d+\.\d{1,3})$'
# Note: the value specified here must not be zero or be higher
# than the corresponding limit.
memory:
type: string
# You can express memory as a plain integer or as a fixed-point
# integer using one of these suffixes: E, P, T, G, M, k. You can
# also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki
#
# https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
# Note: the value specified here must not be zero or be higher
# than the corresponding limit.
sidecars:
type: array
nullable: true
items:
type: object
additionalProperties: true
spiloFSGroup:
type: integer
standby:
type: object
required:
- s3_wal_path
properties:
s3_wal_path:
type: string
teamId:
type: string
tolerations:
type: array
items:
type: object
required:
- key
- operator
- effect
properties:
key:
type: string
operator:
type: string
enum:
- Equal
- Exists
value:
type: string
effect:
type: string
enum:
- NoExecute
- NoSchedule
- PreferNoSchedule
tolerationSeconds:
type: integer
useLoadBalancer: # deprecated
type: boolean
users:
type: object
additionalProperties:
type: array
nullable: true
description: "Role flags specified here must not contradict each other"
items:
type: string
enum:
- bypassrls
- BYPASSRLS
- nobypassrls
- NOBYPASSRLS
- createdb
- CREATEDB
- nocreatedb
- NOCREATEDB
- createrole
- CREATEROLE
- nocreaterole
- NOCREATEROLE
- inherit
- INHERIT
- noinherit
- NOINHERIT
- login
- LOGIN
- nologin
- NOLOGIN
- replication
- REPLICATION
- noreplication
- NOREPLICATION
- superuser
- SUPERUSER
- nosuperuser
- NOSUPERUSER
volume:
type: object
required:
- size
properties:
size:
type: string
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
# Note: the value specified here must not be zero.
storageClass:
type: string
subPath:
type: string

View File

@ -1,7 +1,7 @@
package v1
import (
"github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do"
acidzalando "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do"
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -103,7 +103,902 @@ var OperatorConfigCRDResourceColumns = []apiextv1beta1.CustomResourceColumnDefin
},
}
func buildCRD(name, kind, plural, short string, columns []apiextv1beta1.CustomResourceColumnDefinition) *apiextv1beta1.CustomResourceDefinition {
var min0 = 0.0
var min1 = 1.0
var minDisable = -1.0
// PostgresCRDResourceValidation to check applied manifest parameters
var PostgresCRDResourceValidation = apiextv1beta1.CustomResourceValidation{
OpenAPIV3Schema: &apiextv1beta1.JSONSchemaProps{
Type: "object",
Required: []string{"kind", "apiVersion", "spec"},
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"kind": {
Type: "string",
Enum: []apiextv1beta1.JSON{
{
Raw: []byte(`"postgresql"`),
},
},
},
"apiVersion": {
Type: "string",
Enum: []apiextv1beta1.JSON{
{
Raw: []byte(`"acid.zalan.do/v1"`),
},
},
},
"spec": {
Type: "object",
Required: []string{"numberOfInstances", "teamId", "postgresql"},
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"allowedSourceRanges": {
Type: "array",
Nullable: true,
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
Pattern: "^(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\/(\\d|[1-2]\\d|3[0-2])$",
},
},
},
"clone": {
Type: "object",
Required: []string{"cluster"},
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"cluster": {
Type: "string",
},
"s3_endpoint": {
Type: "string",
},
"s3_access_key_id": {
Type: "string",
},
"s3_secret_access_key": {
Type: "string",
},
"s3_force_path_style": {
Type: "string",
},
"s3_wal_path": {
Type: "string",
},
"timestamp": {
Type: "string",
Description: "Date-time format that specifies a timezone as an offset relative to UTC e.g. 1996-12-19T16:39:57-08:00",
Pattern: "^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\\.[0-9]+)?(([Zz])|([+-]([01][0-9]|2[0-3]):[0-5][0-9]))$",
},
"uid": {
Type: "string",
Format: "uuid",
},
},
},
"databases": {
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
Description: "User names specified here as database owners must be declared in the users key of the spec key",
},
},
},
"dockerImage": {
Type: "string",
},
"enableLogicalBackup": {
Type: "boolean",
},
"enableMasterLoadBalancer": {
Type: "boolean",
},
"enableReplicaLoadBalancer": {
Type: "boolean",
},
"enableShmVolume": {
Type: "boolean",
},
"init_containers": {
Type: "array",
Description: "Deprecated",
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Allows: true,
},
},
},
},
"initContainers": {
Type: "array",
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Allows: true,
},
},
},
},
"logicalBackupSchedule": {
Type: "string",
Pattern: "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$",
},
"maintenanceWindows": {
Type: "array",
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
Pattern: "^\\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))-((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))\\ *$",
},
},
},
"numberOfInstances": {
Type: "integer",
Minimum: &min0,
},
"patroni": {
Type: "object",
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"initdb": {
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
"pg_hba": {
Type: "array",
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
"slots": {
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
},
},
"ttl": {
Type: "integer",
},
"loop_wait": {
Type: "integer",
},
"retry_timeout": {
Type: "integer",
},
"maximum_lag_on_failover": {
Type: "integer",
},
},
},
"podAnnotations": {
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
"pod_priority_class_name": {
Type: "string",
Description: "Deprecated",
},
"podPriorityClassName": {
Type: "string",
},
"postgresql": {
Type: "object",
Required: []string{"version"},
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"version": {
Type: "string",
Enum: []apiextv1beta1.JSON{
{
Raw: []byte(`"9.3"`),
},
{
Raw: []byte(`"9.4"`),
},
{
Raw: []byte(`"9.5"`),
},
{
Raw: []byte(`"9.6"`),
},
{
Raw: []byte(`"10"`),
},
{
Raw: []byte(`"11"`),
},
{
Raw: []byte(`"12"`),
},
},
},
"parameters": {
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
},
},
"replicaLoadBalancer": {
Type: "boolean",
Description: "Deprecated",
},
"resources": {
Type: "object",
Required: []string{"requests", "limits"},
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"limits": {
Type: "object",
Required: []string{"cpu", "memory"},
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"cpu": {
Type: "string",
Description: "Decimal natural followed by m, or decimal natural followed by dot followed by up to three decimal digits (precision used by Kubernetes). Must be greater than 0",
Pattern: "^(\\d+m|\\d+\\.\\d{1,3})$",
},
"memory": {
Type: "string",
Description: "Plain integer or fixed-point integer using one of these suffixes: E, P, T, G, M, k (with or without a tailing i). Must be greater than 0",
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
},
},
},
"requests": {
Type: "object",
Required: []string{"cpu", "memory"},
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"cpu": {
Type: "string",
Description: "Decimal natural followed by m, or decimal natural followed by dot followed by up to three decimal digits (precision used by Kubernetes). Must be greater than 0",
Pattern: "^(\\d+m|\\d+\\.\\d{1,3})$",
},
"memory": {
Type: "string",
Description: "Plain integer or fixed-point integer using one of these suffixes: E, P, T, G, M, k (with or without a tailing i). Must be greater than 0",
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
},
},
},
},
},
"sidecars": {
Type: "array",
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Allows: true,
},
},
},
},
"spiloFSGroup": {
Type: "integer",
},
"standby": {
Type: "object",
Required: []string{"s3_wal_path"},
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"s3_wal_path": {
Type: "string",
},
},
},
"teamId": {
Type: "string",
},
"tolerations": {
Type: "array",
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "object",
Required: []string{"key", "operator", "effect"},
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"key": {
Type: "string",
},
"operator": {
Type: "string",
Enum: []apiextv1beta1.JSON{
{
Raw: []byte(`"Equal"`),
},
{
Raw: []byte(`"Exists"`),
},
},
},
"value": {
Type: "string",
},
"effect": {
Type: "string",
Enum: []apiextv1beta1.JSON{
{
Raw: []byte(`"NoExecute"`),
},
{
Raw: []byte(`"NoSchedule"`),
},
{
Raw: []byte(`"PreferNoSchedule"`),
},
},
},
"tolerationSeconds": {
Type: "integer",
},
},
},
},
},
"useLoadBalancer": {
Type: "boolean",
Description: "Deprecated",
},
"users": {
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "array",
Description: "Role flags specified here must not contradict each other",
Nullable: true,
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
Enum: []apiextv1beta1.JSON{
{
Raw: []byte(`"bypassrls"`),
},
{
Raw: []byte(`"BYPASSRLS"`),
},
{
Raw: []byte(`"nobypassrls"`),
},
{
Raw: []byte(`"NOBYPASSRLS"`),
},
{
Raw: []byte(`"createdb"`),
},
{
Raw: []byte(`"CREATEDB"`),
},
{
Raw: []byte(`"nocreatedb"`),
},
{
Raw: []byte(`"NOCREATEDB"`),
},
{
Raw: []byte(`"createrole"`),
},
{
Raw: []byte(`"CREATEROLE"`),
},
{
Raw: []byte(`"nocreaterole"`),
},
{
Raw: []byte(`"NOCREATEROLE"`),
},
{
Raw: []byte(`"inherit"`),
},
{
Raw: []byte(`"INHERIT"`),
},
{
Raw: []byte(`"noinherit"`),
},
{
Raw: []byte(`"NOINHERIT"`),
},
{
Raw: []byte(`"login"`),
},
{
Raw: []byte(`"LOGIN"`),
},
{
Raw: []byte(`"nologin"`),
},
{
Raw: []byte(`"NOLOGIN"`),
},
{
Raw: []byte(`"replication"`),
},
{
Raw: []byte(`"REPLICATION"`),
},
{
Raw: []byte(`"noreplication"`),
},
{
Raw: []byte(`"NOREPLICATION"`),
},
{
Raw: []byte(`"superuser"`),
},
{
Raw: []byte(`"SUPERUSER"`),
},
{
Raw: []byte(`"nosuperuser"`),
},
{
Raw: []byte(`"NOSUPERUSER"`),
},
},
},
},
},
},
},
"volume": {
Type: "object",
Required: []string{"size"},
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"size": {
Type: "string",
Description: "Value must not be zero",
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
},
"storageClass": {
Type: "string",
},
"subPath": {
Type: "string",
},
},
},
},
},
},
},
}
// OperatorConfigCRDResourceValidation to check applied manifest parameters
var OperatorConfigCRDResourceValidation = apiextv1beta1.CustomResourceValidation{
OpenAPIV3Schema: &apiextv1beta1.JSONSchemaProps{
Type: "object",
Required: []string{"kind", "apiVersion", "configuration"},
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"kind": {
Type: "string",
Enum: []apiextv1beta1.JSON{
{
Raw: []byte(`"OperatorConfiguration"`),
},
},
},
"apiVersion": {
Type: "string",
Enum: []apiextv1beta1.JSON{
{
Raw: []byte(`"acid.zalan.do/v1"`),
},
},
},
"configuration": {
Type: "object",
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"docker_image": {
Type: "string",
},
"enable_crd_validation": {
Type: "boolean",
},
"enable_shm_volume": {
Type: "boolean",
},
"etcd_host": {
Type: "string",
},
"max_instances": {
Type: "integer",
Description: "-1 = disabled",
Minimum: &minDisable,
},
"min_instances": {
Type: "integer",
Description: "-1 = disabled",
Minimum: &minDisable,
},
"resync_period": {
Type: "string",
},
"repair_period": {
Type: "string",
},
"set_memory_request_to_limit": {
Type: "boolean",
},
"sidecar_docker_images": {
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
"workers": {
Type: "integer",
Minimum: &min1,
},
"users": {
Type: "object",
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"replication_username": {
Type: "string",
},
"super_username": {
Type: "string",
},
},
},
"kubernetes": {
Type: "object",
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"cluster_domain": {
Type: "string",
},
"cluster_labels": {
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
"cluster_name_label": {
Type: "string",
},
"custom_pod_annotations": {
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
"enable_pod_antiaffinity": {
Type: "boolean",
},
"enable_pod_disruption_budget": {
Type: "boolean",
},
"infrastructure_roles_secret_name": {
Type: "string",
},
"inherited_labels": {
Type: "array",
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
"node_readiness_label": {
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
"oauth_token_secret_name": {
Type: "string",
},
"pdb_name_format": {
Type: "string",
},
"pod_antiaffinity_topology_key": {
Type: "string",
},
"pod_environment_configmap": {
Type: "string",
},
"pod_management_policy": {
Type: "string",
Enum: []apiextv1beta1.JSON{
{
Raw: []byte(`"ordered_ready"`),
},
{
Raw: []byte(`"parallel"`),
},
},
},
"pod_role_label": {
Type: "string",
},
"pod_service_account_name": {
Type: "string",
},
"pod_terminate_grace_period": {
Type: "string",
},
"secret_name_template": {
Type: "string",
},
"spilo_fsgroup": {
Type: "integer",
},
"spilo_privileged": {
Type: "boolean",
},
"toleration": {
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
"watched_namespace": {
Type: "string",
},
},
},
"postgres_pod_resources": {
Type: "object",
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"default_cpu_limit": {
Type: "string",
Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
},
"default_cpu_request": {
Type: "string",
Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
},
"default_memory_limit": {
Type: "string",
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
},
"default_memory_request": {
Type: "string",
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
},
},
},
"timeouts": {
Type: "object",
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"pod_label_wait_timeout": {
Type: "string",
},
"pod_deletion_wait_timeout": {
Type: "string",
},
"ready_wait_interval": {
Type: "string",
},
"ready_wait_timeout": {
Type: "string",
},
"resource_check_interval": {
Type: "string",
},
"resource_check_timeout": {
Type: "string",
},
},
},
"load_balancer": {
Type: "object",
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"db_hosted_zone": {
Type: "string",
},
"enable_master_load_balancer": {
Type: "boolean",
},
"enable_replica_load_balancer": {
Type: "boolean",
},
"custom_service_annotations": {
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
"master_dns_name_format": {
Type: "string",
},
"replica_dns_name_format": {
Type: "string",
},
},
},
"aws_or_gcp": {
Type: "object",
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"additional_secret_mount": {
Type: "string",
},
"additional_secret_mount_path": {
Type: "string",
},
"aws_region": {
Type: "string",
},
"kube_iam_role": {
Type: "string",
},
"log_s3_bucket": {
Type: "string",
},
"wal_s3_bucket": {
Type: "string",
},
},
},
"logical_backup": {
Type: "object",
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"logical_backup_schedule": {
Type: "string",
Pattern: "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$",
},
"logical_backup_docker_image": {
Type: "string",
},
"logical_backup_s3_bucket": {
Type: "string",
},
"logical_backup_s3_endpoint": {
Type: "string",
},
"logical_backup_s3_sse": {
Type: "string",
},
"logical_backup_s3_access_key_id": {
Type: "string",
},
"logical_backup_s3_secret_access_key": {
Type: "string",
},
},
},
"debug": {
Type: "object",
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"debug_logging": {
Type: "boolean",
},
"enable_database_access": {
Type: "boolean",
},
},
},
"teams_api": {
Type: "object",
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"enable_admin_role_for_users": {
Type: "boolean",
},
"enable_team_superuser": {
Type: "boolean",
},
"enable_teams_api": {
Type: "boolean",
},
"pam_configuration": {
Type: "string",
},
"pam_role_name": {
Type: "string",
},
"postgres_superuser_teams": {
Type: "array",
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
"protected_role_names": {
Type: "array",
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
"team_admin_role": {
Type: "string",
},
"team_api_role_configuration": {
Type: "object",
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
Schema: &apiextv1beta1.JSONSchemaProps{
Type: "string",
},
},
},
"teams_api_url": {
Type: "string",
},
},
},
"logging_rest_api": {
Type: "object",
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"api_port": {
Type: "integer",
},
"cluster_history_entries": {
Type: "integer",
},
"ring_log_lines": {
Type: "integer",
},
},
},
"scalyr": {
Type: "object",
Properties: map[string]apiextv1beta1.JSONSchemaProps{
"scalyr_api_key": {
Type: "string",
},
"scalyr_cpu_limit": {
Type: "string",
Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
},
"scalyr_cpu_request": {
Type: "string",
Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
},
"scalyr_image": {
Type: "string",
},
"scalyr_memory_limit": {
Type: "string",
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
},
"scalyr_memory_request": {
Type: "string",
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
},
"scalyr_server_url": {
Type: "string",
},
},
},
},
},
},
},
}
func buildCRD(name, kind, plural, short string, columns []apiextv1beta1.CustomResourceColumnDefinition, validation apiextv1beta1.CustomResourceValidation) *apiextv1beta1.CustomResourceDefinition {
return &apiextv1beta1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: name,
@ -121,24 +1016,39 @@ func buildCRD(name, kind, plural, short string, columns []apiextv1beta1.CustomRe
Status: &apiextv1beta1.CustomResourceSubresourceStatus{},
},
AdditionalPrinterColumns: columns,
Validation: &validation,
},
}
}
// PostgresCRD returns CustomResourceDefinition built from PostgresCRDResource
func PostgresCRD() *apiextv1beta1.CustomResourceDefinition {
func PostgresCRD(enableValidation *bool) *apiextv1beta1.CustomResourceDefinition {
postgresCRDvalidation := apiextv1beta1.CustomResourceValidation{}
if enableValidation != nil && *enableValidation {
postgresCRDvalidation = PostgresCRDResourceValidation
}
return buildCRD(PostgresCRDResouceName,
PostgresCRDResourceKind,
PostgresCRDResourcePlural,
PostgresCRDResourceShort,
PostgresCRDResourceColumns)
PostgresCRDResourceColumns,
postgresCRDvalidation)
}
// ConfigurationCRD returns CustomResourceDefinition built from OperatorConfigCRDResource
func ConfigurationCRD() *apiextv1beta1.CustomResourceDefinition {
func ConfigurationCRD(enableValidation *bool) *apiextv1beta1.CustomResourceDefinition {
opconfigCRDvalidation := apiextv1beta1.CustomResourceValidation{}
if enableValidation != nil && *enableValidation {
opconfigCRDvalidation = OperatorConfigCRDResourceValidation
}
return buildCRD(OperatorConfigCRDResourceName,
OperatorConfigCRDResouceKind,
OperatorConfigCRDResourcePlural,
OperatorConfigCRDResourceShort,
OperatorConfigCRDResourceColumns)
OperatorConfigCRDResourceColumns,
opconfigCRDvalidation)
}

View File

@ -150,6 +150,7 @@ type ScalyrConfiguration struct {
// OperatorConfigurationData defines the operation config
type OperatorConfigurationData struct {
EnableCRDValidation *bool `json:"enable_crd_validation,omitempty"`
EtcdHost string `json:"etcd_host,omitempty"`
DockerImage string `json:"docker_image,omitempty"`
Workers uint32 `json:"workers,omitempty"`

View File

@ -216,6 +216,11 @@ func (in *OperatorConfiguration) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData) {
*out = *in
if in.EnableCRDValidation != nil {
in, out := &in.EnableCRDValidation, &out.EnableCRDValidation
*out = new(bool)
**out = **in
}
if in.ShmVolume != nil {
in, out := &in.ShmVolume, &out.ShmVolume
*out = new(bool)

View File

@ -6,7 +6,7 @@ import (
"sync"
"github.com/sirupsen/logrus"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
@ -240,7 +240,7 @@ func (c *Controller) initController() {
c.initClients()
if configObjectName := os.Getenv("POSTGRES_OPERATOR_CONFIGURATION_OBJECT"); configObjectName != "" {
if err := c.createConfigurationCRD(); err != nil {
if err := c.createConfigurationCRD(c.opConfig.EnableCRDValidation); err != nil {
c.logger.Fatalf("could not register Operator Configuration CustomResourceDefinition: %v", err)
}
if cfg, err := c.readOperatorConfigurationFromCRD(spec.GetOperatorNamespace(), configObjectName); err != nil {
@ -256,7 +256,7 @@ func (c *Controller) initController() {
c.modifyConfigFromEnvironment()
if err := c.createPostgresCRD(); err != nil {
if err := c.createPostgresCRD(c.opConfig.EnableCRDValidation); err != nil {
c.logger.Fatalf("could not register Postgres CustomResourceDefinition: %v", err)
}

View File

@ -25,6 +25,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
result := &config.Config{}
// general config
result.EnableCRDValidation = fromCRD.EnableCRDValidation
result.EtcdHost = fromCRD.EtcdHost
result.DockerImage = fromCRD.DockerImage
result.Workers = fromCRD.Workers

View File

@ -4,7 +4,7 @@ import (
"encoding/json"
"fmt"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
@ -91,12 +91,12 @@ func (c *Controller) createOperatorCRD(crd *apiextv1beta1.CustomResourceDefiniti
})
}
func (c *Controller) createPostgresCRD() error {
return c.createOperatorCRD(acidv1.PostgresCRD())
func (c *Controller) createPostgresCRD(enableValidation *bool) error {
return c.createOperatorCRD(acidv1.PostgresCRD(enableValidation))
}
func (c *Controller) createConfigurationCRD() error {
return c.createOperatorCRD(acidv1.ConfigurationCRD())
func (c *Controller) createConfigurationCRD(enableValidation *bool) error {
return c.createOperatorCRD(acidv1.ConfigurationCRD(enableValidation))
}
func readDecodedRole(s string) (*spec.PgUser, error) {

View File

@ -16,6 +16,7 @@ type CRD struct {
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"`
EnableCRDValidation *bool `name:"enable_crd_validation" default:"true"`
}
// Resources describes kubernetes resource specific configuration parameters

View File

@ -98,7 +98,7 @@ function build_operator_binary(){
# redirecting stderr greatly reduces non-informative output during normal builds
echo "Build operator binary (stderr redirected to /dev/null)..."
make clean tools deps local test > /dev/null 2>&1
make clean deps local test > /dev/null 2>&1
}