deprecate crd validation toggle and sync with manifests (#1781)

* deprecate crd validation toggle and sync with manifests
* fix description in pg crd manifests
* change CRD creation strategy
* affinity matchExpression has values
* lower repair period in e2e tests
This commit is contained in:
Felix Kunde 2022-02-18 15:04:31 +01:00 committed by GitHub
parent 658923d10d
commit 3ce0b1e7fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 262 additions and 153 deletions

View File

@ -61,6 +61,11 @@ spec:
configuration: configuration:
type: object type: object
properties: properties:
crd_categories:
type: array
nullable: true
items:
type: string
docker_image: docker_image:
type: string type: string
default: "registry.opensource.zalan.do/acid/spilo-14:2.1-p3" default: "registry.opensource.zalan.do/acid/spilo-14:2.1-p3"
@ -69,6 +74,7 @@ spec:
default: true default: true
enable_crd_validation: enable_crd_validation:
type: boolean type: boolean
description: deprecated
default: true default: true
enable_lazy_spilo_upgrade: enable_lazy_spilo_upgrade:
type: boolean type: boolean
@ -90,11 +96,13 @@ spec:
default: false default: false
max_instances: max_instances:
type: integer type: integer
minimum: -1 # -1 = disabled description: "-1 = disabled"
minimum: -1
default: -1 default: -1
min_instances: min_instances:
type: integer type: integer
minimum: -1 # -1 = disabled description: "-1 = disabled"
minimum: -1
default: -1 default: -1
resync_period: resync_period:
type: string type: string
@ -184,12 +192,12 @@ spec:
type: array type: array
items: items:
type: string type: string
enable_init_containers:
type: boolean
default: true
enable_cross_namespace_secret: enable_cross_namespace_secret:
type: boolean type: boolean
default: false default: false
enable_init_containers:
type: boolean
default: true
enable_pod_antiaffinity: enable_pod_antiaffinity:
type: boolean type: boolean
default: false default: false
@ -410,12 +418,12 @@ spec:
type: string type: string
log_s3_bucket: log_s3_bucket:
type: string type: string
wal_az_storage_account:
type: string
wal_gs_bucket: wal_gs_bucket:
type: string type: string
wal_s3_bucket: wal_s3_bucket:
type: string type: string
wal_az_storage_account:
type: string
logical_backup: logical_backup:
type: object type: object
properties: properties:

View File

@ -147,7 +147,7 @@ spec:
- "transaction" - "transaction"
numberOfInstances: numberOfInstances:
type: integer type: integer
minimum: 2 minimum: 1
resources: resources:
type: object type: object
required: required:
@ -201,8 +201,9 @@ spec:
type: boolean type: boolean
enableShmVolume: enableShmVolume:
type: boolean type: boolean
init_containers: # deprecated init_containers:
type: array type: array
description: deprecated
nullable: true nullable: true
items: items:
type: object type: object
@ -229,8 +230,8 @@ spec:
items: items:
type: object type: object
required: required:
- weight
- preference - preference
- weight
properties: properties:
preference: preference:
type: object type: object
@ -348,8 +349,9 @@ spec:
type: object type: object
additionalProperties: additionalProperties:
type: string type: string
pod_priority_class_name: # deprecated pod_priority_class_name:
type: string type: string
description: deprecated
podPriorityClassName: podPriorityClassName:
type: string type: string
postgresql: postgresql:
@ -393,8 +395,9 @@ spec:
type: boolean type: boolean
secretNamespace: secretNamespace:
type: string type: string
replicaLoadBalancer: # deprecated replicaLoadBalancer:
type: boolean type: boolean
description: deprecated
resources: resources:
type: object type: object
required: required:
@ -512,14 +515,14 @@ spec:
- PreferNoSchedule - PreferNoSchedule
tolerationSeconds: tolerationSeconds:
type: integer type: integer
useLoadBalancer: # deprecated useLoadBalancer:
type: boolean type: boolean
description: deprecated
users: users:
type: object type: object
additionalProperties: additionalProperties:
type: array type: array
nullable: true nullable: true
description: "Role flags specified here must not contradict each other"
items: items:
type: string type: string
enum: enum:

View File

@ -22,8 +22,9 @@ enableJsonLogging: false
configGeneral: configGeneral:
# the deployment should create/update the CRDs # the deployment should create/update the CRDs
enable_crd_registration: true enable_crd_registration: true
# choose if deployment creates/updates CRDs with OpenAPIV3Validation # specify categories under which crds should be listed
enable_crd_validation: true crd_categories:
- "all"
# update only the statefulsets without immediately doing the rolling update # update only the statefulsets without immediately doing the rolling update
enable_lazy_spilo_upgrade: false enable_lazy_spilo_upgrade: false
# set the PGVERSION env var instead of providing the version via postgresql.bin_dir in SPILO_CONFIGURATION # set the PGVERSION env var instead of providing the version via postgresql.bin_dir in SPILO_CONFIGURATION

View File

@ -3,6 +3,25 @@
Learn how to configure and manage the Postgres Operator in your Kubernetes (K8s) Learn how to configure and manage the Postgres Operator in your Kubernetes (K8s)
environment. environment.
## CRD registration and validation
On startup, the operator will try to register the necessary
[CustomResourceDefinitions](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions)
`Postgresql` and `OperatorConfiguration`. The latter will only get created if
the `POSTGRES_OPERATOR_CONFIGURATION_OBJECT` [environment variable](https://github.com/zalando/postgres-operator/blob/master/manifests/postgres-operator.yaml#L36)
is set in the deployment yaml and is not empty. If the CRDs already exists they
will only be patched. If you do not wish the operator to create or update the
CRDs set `enable_crd_registration` config option to `false`.
CRDs are defined with a `openAPIV3Schema` structural schema against which new
manifests of [`postgresql`](https://github.com/zalando/postgres-operator/blob/master/manifests/postgresql.crd.yaml) or [`OperatorConfiguration`](https://github.com/zalando/postgres-operator/blob/master/manifests/operatorconfiguration.crd.yaml)
resources will be validated. On creation you can bypass the validation with
`kubectl create --validate=false`.
By default, the operator will register the CRDs in the `all` category so
that resources are listed on `kubectl get all` commands. The `crd_categories`
config option allows for customization of categories.
## Upgrading the operator ## Upgrading the operator
The Postgres Operator is upgraded by changing the docker image within the The Postgres Operator is upgraded by changing the docker image within the
@ -63,30 +82,6 @@ upgrade procedure, refer to the [corresponding PR in Spilo](https://github.com/z
When `major_version_upgrade_mode` is set to `manual` the operator will run When `major_version_upgrade_mode` is set to `manual` the operator will run
the upgrade script for you after the manifest is updated and pods are rotated. the upgrade script for you after the manifest is updated and pods are rotated.
## 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](https://github.com/zalando/postgres-operator/blob/master/manifests/postgres-operator.yaml#L36)
in the deployment yaml is set and not empty.
When submitting manifests of [`postgresql`](https://github.com/zalando/postgres-operator/blob/master/manifests/postgresql.crd.yaml) or
[`OperatorConfiguration`](https://github.com/zalando/postgres-operator/blob/master/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
kubectl patch crd postgresqls.acid.zalan.do -p '{"spec":{"validation": null}}'
```
## Non-default cluster domain ## Non-default cluster domain
If your cluster uses a DNS domain other than the default `cluster.local`, this If your cluster uses a DNS domain other than the default `cluster.local`, this

View File

@ -75,9 +75,12 @@ Those are top-level keys, containing both leaf keys and groups.
The default is `true`. The default is `true`.
* **enable_crd_validation** * **enable_crd_validation**
toggles if the operator will create or update CRDs with *deprecated*: 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) [OpenAPI v3 schema validation](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#validation)
The default is `true`. The default is `true`. `false` will be ignored, since `apiextensions.io/v1` requires a structural schema definition.
* **crd_categories**
The operator will register CRDs in the `all` category by default so that they will be returned by a `kubectl get all` call. You are free to change categories or leave them empty.
* **enable_lazy_spilo_upgrade** * **enable_lazy_spilo_upgrade**
Instruct operator to update only the statefulsets with new images (Spilo and InitContainers) without immediately doing the rolling update. The assumption is pods will be re-started later with new images, for example due to the node rotation. Instruct operator to update only the statefulsets with new images (Spilo and InitContainers) without immediately doing the rolling update. The assumption is pods will be re-started later with new images, for example due to the node rotation.

View File

@ -204,7 +204,8 @@ class EndToEndTestCase(unittest.TestCase):
"enable_postgres_team_crd": "true", "enable_postgres_team_crd": "true",
"enable_team_member_deprecation": "true", "enable_team_member_deprecation": "true",
"role_deletion_suffix": "_delete_me", "role_deletion_suffix": "_delete_me",
"resync_period": "15s" "resync_period": "15s",
"repair_period": "10s",
}, },
} }
k8s.update_config(enable_postgres_team_crd) k8s.update_config(enable_postgres_team_crd)
@ -288,6 +289,7 @@ class EndToEndTestCase(unittest.TestCase):
revert_resync = { revert_resync = {
"data": { "data": {
"resync_period": "4m", "resync_period": "4m",
"repair_period": "1m",
}, },
} }
k8s.update_config(revert_resync) k8s.update_config(revert_resync)
@ -1403,6 +1405,7 @@ class EndToEndTestCase(unittest.TestCase):
"data": { "data": {
"pod_label_wait_timeout": "2s", "pod_label_wait_timeout": "2s",
"resync_period": "30s", "resync_period": "30s",
"repair_period": "10s",
} }
} }
@ -1444,6 +1447,7 @@ class EndToEndTestCase(unittest.TestCase):
"data": { "data": {
"pod_label_wait_timeout": "10m", "pod_label_wait_timeout": "10m",
"resync_period": "4m", "resync_period": "4m",
"repair_period": "2m",
} }
} }
k8s.update_config(patch_resync_config, "revert resync interval and pod_label_wait_timeout") k8s.update_config(patch_resync_config, "revert resync interval and pod_label_wait_timeout")

View File

@ -22,6 +22,7 @@ data:
# connection_pooler_number_of_instances: 2 # connection_pooler_number_of_instances: 2
# connection_pooler_schema: "pooler" # connection_pooler_schema: "pooler"
# connection_pooler_user: "pooler" # connection_pooler_user: "pooler"
crd_categories: "all"
# custom_service_annotations: "keyx:valuez,keya:valuea" # custom_service_annotations: "keyx:valuez,keya:valuea"
# custom_pod_annotations: "keya:valuea,keyb:valueb" # custom_pod_annotations: "keya:valuea,keyb:valueb"
db_hosted_zone: db.example.com db_hosted_zone: db.example.com
@ -36,7 +37,6 @@ data:
# downscaler_annotations: "deployment-time,downscaler/*" # downscaler_annotations: "deployment-time,downscaler/*"
# enable_admin_role_for_users: "true" # enable_admin_role_for_users: "true"
# enable_crd_registration: "true" # enable_crd_registration: "true"
# enable_crd_validation: "true"
# enable_cross_namespace_secret: "false" # enable_cross_namespace_secret: "false"
# enable_database_access: "true" # enable_database_access: "true"
enable_ebs_gp3_migration: "false" enable_ebs_gp3_migration: "false"

View File

@ -59,6 +59,11 @@ spec:
configuration: configuration:
type: object type: object
properties: properties:
crd_categories:
type: array
nullable: true
items:
type: string
docker_image: docker_image:
type: string type: string
default: "registry.opensource.zalan.do/acid/spilo-14:2.1-p3" default: "registry.opensource.zalan.do/acid/spilo-14:2.1-p3"
@ -67,6 +72,7 @@ spec:
default: true default: true
enable_crd_validation: enable_crd_validation:
type: boolean type: boolean
description: deprecated
default: true default: true
enable_lazy_spilo_upgrade: enable_lazy_spilo_upgrade:
type: boolean type: boolean
@ -88,11 +94,13 @@ spec:
default: false default: false
max_instances: max_instances:
type: integer type: integer
minimum: -1 # -1 = disabled description: "-1 = disabled"
minimum: -1
default: -1 default: -1
min_instances: min_instances:
type: integer type: integer
minimum: -1 # -1 = disabled description: "-1 = disabled"
minimum: -1
default: -1 default: -1
resync_period: resync_period:
type: string type: string
@ -182,6 +190,9 @@ spec:
type: array type: array
items: items:
type: string type: string
enable_cross_namespace_secret:
type: boolean
default: false
enable_init_containers: enable_init_containers:
type: boolean type: boolean
default: true default: true

View File

@ -5,7 +5,8 @@ metadata:
configuration: configuration:
docker_image: registry.opensource.zalan.do/acid/spilo-14:2.1-p3 docker_image: registry.opensource.zalan.do/acid/spilo-14:2.1-p3
# enable_crd_registration: true # enable_crd_registration: true
# enable_crd_validation: true # crd_categories:
# - all
# enable_lazy_spilo_upgrade: false # enable_lazy_spilo_upgrade: false
enable_pgversion_env_var: true enable_pgversion_env_var: true
# enable_shm_volume: true # enable_shm_volume: true

View File

@ -145,7 +145,7 @@ spec:
- "transaction" - "transaction"
numberOfInstances: numberOfInstances:
type: integer type: integer
minimum: 2 minimum: 1
resources: resources:
type: object type: object
required: required:
@ -199,8 +199,9 @@ spec:
type: boolean type: boolean
enableShmVolume: enableShmVolume:
type: boolean type: boolean
init_containers: # deprecated init_containers:
type: array type: array
description: deprecated
nullable: true nullable: true
items: items:
type: object type: object
@ -227,8 +228,8 @@ spec:
items: items:
type: object type: object
required: required:
- weight
- preference - preference
- weight
properties: properties:
preference: preference:
type: object type: object
@ -346,8 +347,9 @@ spec:
type: object type: object
additionalProperties: additionalProperties:
type: string type: string
pod_priority_class_name: # deprecated pod_priority_class_name:
type: string type: string
description: deprecated
podPriorityClassName: podPriorityClassName:
type: string type: string
postgresql: postgresql:
@ -391,8 +393,9 @@ spec:
type: boolean type: boolean
secretNamespace: secretNamespace:
type: string type: string
replicaLoadBalancer: # deprecated replicaLoadBalancer:
type: boolean type: boolean
description: deprecated
resources: resources:
type: object type: object
required: required:
@ -510,14 +513,14 @@ spec:
- PreferNoSchedule - PreferNoSchedule
tolerationSeconds: tolerationSeconds:
type: integer type: integer
useLoadBalancer: # deprecated useLoadBalancer:
type: boolean type: boolean
description: deprecated
users: users:
type: object type: object
additionalProperties: additionalProperties:
type: array type: array
nullable: true nullable: true
description: "Role flags specified here must not contradict each other"
items: items:
type: string type: string
enum: enum:

View File

@ -1,6 +1,8 @@
package v1 package v1
import ( import (
"fmt"
acidzalando "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do" acidzalando "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do"
"github.com/zalando/postgres-operator/pkg/util" "github.com/zalando/postgres-operator/pkg/util"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@ -11,11 +13,13 @@ import (
const ( const (
PostgresCRDResourceKind = "postgresql" PostgresCRDResourceKind = "postgresql"
PostgresCRDResourcePlural = "postgresqls" PostgresCRDResourcePlural = "postgresqls"
PostgresCRDResourceList = PostgresCRDResourceKind + "List"
PostgresCRDResouceName = PostgresCRDResourcePlural + "." + acidzalando.GroupName PostgresCRDResouceName = PostgresCRDResourcePlural + "." + acidzalando.GroupName
PostgresCRDResourceShort = "pg" PostgresCRDResourceShort = "pg"
OperatorConfigCRDResouceKind = "OperatorConfiguration" OperatorConfigCRDResouceKind = "OperatorConfiguration"
OperatorConfigCRDResourcePlural = "operatorconfigurations" OperatorConfigCRDResourcePlural = "operatorconfigurations"
OperatorConfigCRDResourceList = OperatorConfigCRDResouceKind + "List"
OperatorConfigCRDResourceName = OperatorConfigCRDResourcePlural + "." + acidzalando.GroupName OperatorConfigCRDResourceName = OperatorConfigCRDResourcePlural + "." + acidzalando.GroupName
OperatorConfigCRDResourceShort = "opconfig" OperatorConfigCRDResourceShort = "opconfig"
) )
@ -149,6 +153,7 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
}, },
"targetContainers": { "targetContainers": {
Type: "array", Type: "array",
Nullable: true,
Items: &apiextv1.JSONSchemaPropsOrArray{ Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{ Schema: &apiextv1.JSONSchemaProps{
Type: "string", Type: "string",
@ -200,7 +205,6 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
}, },
"timestamp": { "timestamp": {
Type: "string", 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]+)?(([+-]([01][0-9]|2[0-3]):[0-5][0-9]))$", 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]+)?(([+-]([01][0-9]|2[0-3]):[0-5][0-9]))$",
}, },
"uid": { "uid": {
@ -243,12 +247,10 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
Properties: map[string]apiextv1.JSONSchemaProps{ Properties: map[string]apiextv1.JSONSchemaProps{
"cpu": { "cpu": {
Type: "string", 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})?)$", Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
}, },
"memory": { "memory": {
Type: "string", 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?)$", Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
}, },
}, },
@ -259,12 +261,10 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
Properties: map[string]apiextv1.JSONSchemaProps{ Properties: map[string]apiextv1.JSONSchemaProps{
"cpu": { "cpu": {
Type: "string", 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})?)$", Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
}, },
"memory": { "memory": {
Type: "string", 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?)$", Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
}, },
}, },
@ -284,7 +284,6 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
AdditionalProperties: &apiextv1.JSONSchemaPropsOrBool{ AdditionalProperties: &apiextv1.JSONSchemaPropsOrBool{
Schema: &apiextv1.JSONSchemaProps{ Schema: &apiextv1.JSONSchemaProps{
Type: "string", Type: "string",
Description: "User names specified here as database owners must be declared in the users key of the spec key",
}, },
}, },
}, },
@ -311,7 +310,8 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
}, },
"init_containers": { "init_containers": {
Type: "array", Type: "array",
Description: "Deprecated", Description: "deprecated",
Nullable: true,
Items: &apiextv1.JSONSchemaPropsOrArray{ Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{ Schema: &apiextv1.JSONSchemaProps{
Type: "object", Type: "object",
@ -321,6 +321,7 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
}, },
"initContainers": { "initContainers": {
Type: "array", Type: "array",
Nullable: true,
Items: &apiextv1.JSONSchemaPropsOrArray{ Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{ Schema: &apiextv1.JSONSchemaProps{
Type: "object", Type: "object",
@ -359,8 +360,22 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
Items: &apiextv1.JSONSchemaPropsOrArray{ Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{ Schema: &apiextv1.JSONSchemaProps{
Type: "object", Type: "object",
AdditionalProperties: &apiextv1.JSONSchemaPropsOrBool{ Required: []string{"key", "operator"},
Allows: true, Properties: map[string]apiextv1.JSONSchemaProps{
"key": {
Type: "string",
},
"operator": {
Type: "string",
},
"values": {
Type: "array",
Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{
Type: "string",
},
},
},
}, },
}, },
}, },
@ -370,8 +385,22 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
Items: &apiextv1.JSONSchemaPropsOrArray{ Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{ Schema: &apiextv1.JSONSchemaProps{
Type: "object", Type: "object",
AdditionalProperties: &apiextv1.JSONSchemaPropsOrBool{ Required: []string{"key", "operator"},
Allows: true, Properties: map[string]apiextv1.JSONSchemaProps{
"key": {
Type: "string",
},
"operator": {
Type: "string",
},
"values": {
Type: "array",
Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{
Type: "string",
},
},
},
}, },
}, },
}, },
@ -401,8 +430,22 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
Items: &apiextv1.JSONSchemaPropsOrArray{ Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{ Schema: &apiextv1.JSONSchemaProps{
Type: "object", Type: "object",
AdditionalProperties: &apiextv1.JSONSchemaPropsOrBool{ Required: []string{"key", "operator"},
Allows: true, Properties: map[string]apiextv1.JSONSchemaProps{
"key": {
Type: "string",
},
"operator": {
Type: "string",
},
"values": {
Type: "array",
Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{
Type: "string",
},
},
},
}, },
}, },
}, },
@ -412,8 +455,22 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
Items: &apiextv1.JSONSchemaPropsOrArray{ Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{ Schema: &apiextv1.JSONSchemaProps{
Type: "object", Type: "object",
AdditionalProperties: &apiextv1.JSONSchemaPropsOrBool{ Required: []string{"key", "operator"},
Allows: true, Properties: map[string]apiextv1.JSONSchemaProps{
"key": {
Type: "string",
},
"operator": {
Type: "string",
},
"values": {
Type: "array",
Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{
Type: "string",
},
},
},
}, },
}, },
}, },
@ -492,7 +549,7 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
}, },
"pod_priority_class_name": { "pod_priority_class_name": {
Type: "string", Type: "string",
Description: "Deprecated", Description: "deprecated",
}, },
"podPriorityClassName": { "podPriorityClassName": {
Type: "string", Type: "string",
@ -579,7 +636,7 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
}, },
"replicaLoadBalancer": { "replicaLoadBalancer": {
Type: "boolean", Type: "boolean",
Description: "Deprecated", Description: "deprecated",
}, },
"resources": { "resources": {
Type: "object", Type: "object",
@ -591,12 +648,10 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
Properties: map[string]apiextv1.JSONSchemaProps{ Properties: map[string]apiextv1.JSONSchemaProps{
"cpu": { "cpu": {
Type: "string", 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})?)$", Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
}, },
"memory": { "memory": {
Type: "string", 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?)$", Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
}, },
}, },
@ -607,12 +662,10 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
Properties: map[string]apiextv1.JSONSchemaProps{ Properties: map[string]apiextv1.JSONSchemaProps{
"cpu": { "cpu": {
Type: "string", 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})?)$", Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
}, },
"memory": { "memory": {
Type: "string", 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?)$", Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
}, },
}, },
@ -632,6 +685,7 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
}, },
"sidecars": { "sidecars": {
Type: "array", Type: "array",
Nullable: true,
Items: &apiextv1.JSONSchemaPropsOrArray{ Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{ Schema: &apiextv1.JSONSchemaProps{
Type: "object", Type: "object",
@ -730,14 +784,13 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
}, },
"useLoadBalancer": { "useLoadBalancer": {
Type: "boolean", Type: "boolean",
Description: "Deprecated", Description: "deprecated",
}, },
"users": { "users": {
Type: "object", Type: "object",
AdditionalProperties: &apiextv1.JSONSchemaPropsOrBool{ AdditionalProperties: &apiextv1.JSONSchemaPropsOrBool{
Schema: &apiextv1.JSONSchemaProps{ Schema: &apiextv1.JSONSchemaProps{
Type: "array", Type: "array",
Description: "Role flags specified here must not contradict each other",
Nullable: true, Nullable: true,
Items: &apiextv1.JSONSchemaPropsOrArray{ Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{ Schema: &apiextv1.JSONSchemaProps{
@ -908,7 +961,6 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
}, },
"size": { "size": {
Type: "string", Type: "string",
Description: "Value must not be zero",
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$", Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
}, },
"storageClass": { "storageClass": {
@ -961,6 +1013,15 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{
"configuration": { "configuration": {
Type: "object", Type: "object",
Properties: map[string]apiextv1.JSONSchemaProps{ Properties: map[string]apiextv1.JSONSchemaProps{
"crd_categories": {
Type: "array",
Nullable: true,
Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{
Type: "string",
},
},
},
"docker_image": { "docker_image": {
Type: "string", Type: "string",
}, },
@ -969,6 +1030,7 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{
}, },
"enable_crd_validation": { "enable_crd_validation": {
Type: "boolean", Type: "boolean",
Description: "deprecated",
}, },
"enable_lazy_spilo_upgrade": { "enable_lazy_spilo_upgrade": {
Type: "boolean", Type: "boolean",
@ -1014,6 +1076,7 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{
}, },
"sidecars": { "sidecars": {
Type: "array", Type: "array",
Nullable: true,
Items: &apiextv1.JSONSchemaPropsOrArray{ Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{ Schema: &apiextv1.JSONSchemaProps{
Type: "object", Type: "object",
@ -1125,6 +1188,7 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{
}, },
"infrastructure_roles_secrets": { "infrastructure_roles_secrets": {
Type: "array", Type: "array",
Nullable: true,
Items: &apiextv1.JSONSchemaPropsOrArray{ Items: &apiextv1.JSONSchemaPropsOrArray{
Schema: &apiextv1.JSONSchemaProps{ Schema: &apiextv1.JSONSchemaProps{
Type: "object", Type: "object",
@ -1626,18 +1690,27 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{
}, },
} }
func buildCRD(name, kind, plural, short string, columns []apiextv1.CustomResourceColumnDefinition, validation apiextv1.CustomResourceValidation) *apiextv1.CustomResourceDefinition { func buildCRD(name, kind, plural, list, short string,
categories []string,
columns []apiextv1.CustomResourceColumnDefinition,
validation apiextv1.CustomResourceValidation) *apiextv1.CustomResourceDefinition {
return &apiextv1.CustomResourceDefinition{ return &apiextv1.CustomResourceDefinition{
TypeMeta: metav1.TypeMeta{
APIVersion: fmt.Sprintf("%s/%s", apiextv1.GroupName, apiextv1.SchemeGroupVersion.Version),
Kind: "CustomResourceDefinition",
},
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
}, },
Spec: apiextv1.CustomResourceDefinitionSpec{ Spec: apiextv1.CustomResourceDefinitionSpec{
Group: SchemeGroupVersion.Group, Group: SchemeGroupVersion.Group,
Names: apiextv1.CustomResourceDefinitionNames{ Names: apiextv1.CustomResourceDefinitionNames{
Plural: plural,
ShortNames: []string{short},
Kind: kind, Kind: kind,
Categories: []string{"all"}, ListKind: list,
Plural: plural,
Singular: kind,
ShortNames: []string{short},
Categories: categories,
}, },
Scope: apiextv1.NamespaceScoped, Scope: apiextv1.NamespaceScoped,
Versions: []apiextv1.CustomResourceDefinitionVersion{ Versions: []apiextv1.CustomResourceDefinitionVersion{
@ -1657,33 +1730,25 @@ func buildCRD(name, kind, plural, short string, columns []apiextv1.CustomResourc
} }
// PostgresCRD returns CustomResourceDefinition built from PostgresCRDResource // PostgresCRD returns CustomResourceDefinition built from PostgresCRDResource
func PostgresCRD(enableValidation *bool) *apiextv1.CustomResourceDefinition { func PostgresCRD(crdCategories []string) *apiextv1.CustomResourceDefinition {
postgresCRDvalidation := apiextv1.CustomResourceValidation{}
if enableValidation != nil && *enableValidation {
postgresCRDvalidation = PostgresCRDResourceValidation
}
return buildCRD(PostgresCRDResouceName, return buildCRD(PostgresCRDResouceName,
PostgresCRDResourceKind, PostgresCRDResourceKind,
PostgresCRDResourcePlural, PostgresCRDResourcePlural,
PostgresCRDResourceList,
PostgresCRDResourceShort, PostgresCRDResourceShort,
crdCategories,
PostgresCRDResourceColumns, PostgresCRDResourceColumns,
postgresCRDvalidation) PostgresCRDResourceValidation)
} }
// ConfigurationCRD returns CustomResourceDefinition built from OperatorConfigCRDResource // ConfigurationCRD returns CustomResourceDefinition built from OperatorConfigCRDResource
func ConfigurationCRD(enableValidation *bool) *apiextv1.CustomResourceDefinition { func ConfigurationCRD(crdCategories []string) *apiextv1.CustomResourceDefinition {
opconfigCRDvalidation := apiextv1.CustomResourceValidation{}
if enableValidation != nil && *enableValidation {
opconfigCRDvalidation = OperatorConfigCRDResourceValidation
}
return buildCRD(OperatorConfigCRDResourceName, return buildCRD(OperatorConfigCRDResourceName,
OperatorConfigCRDResouceKind, OperatorConfigCRDResouceKind,
OperatorConfigCRDResourcePlural, OperatorConfigCRDResourcePlural,
OperatorConfigCRDResourceList,
OperatorConfigCRDResourceShort, OperatorConfigCRDResourceShort,
crdCategories,
OperatorConfigCRDResourceColumns, OperatorConfigCRDResourceColumns,
opconfigCRDvalidation) OperatorConfigCRDResourceValidation)
} }

View File

@ -221,6 +221,7 @@ type OperatorLogicalBackupConfiguration struct {
type OperatorConfigurationData struct { type OperatorConfigurationData struct {
EnableCRDRegistration *bool `json:"enable_crd_registration,omitempty"` EnableCRDRegistration *bool `json:"enable_crd_registration,omitempty"`
EnableCRDValidation *bool `json:"enable_crd_validation,omitempty"` EnableCRDValidation *bool `json:"enable_crd_validation,omitempty"`
CRDCategories []string `json:"crd_categories,omitempty"`
EnableLazySpiloUpgrade bool `json:"enable_lazy_spilo_upgrade,omitempty"` EnableLazySpiloUpgrade bool `json:"enable_lazy_spilo_upgrade,omitempty"`
EnablePgVersionEnvVar bool `json:"enable_pgversion_env_var,omitempty"` EnablePgVersionEnvVar bool `json:"enable_pgversion_env_var,omitempty"`
EnableSpiloWalPathCompat bool `json:"enable_spilo_wal_path_compat,omitempty"` EnableSpiloWalPathCompat bool `json:"enable_spilo_wal_path_compat,omitempty"`

View File

@ -377,6 +377,11 @@ func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData
*out = new(bool) *out = new(bool)
**out = **in **out = **in
} }
if in.CRDCategories != nil {
in, out := &in.CRDCategories, &out.CRDCategories
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ShmVolume != nil { if in.ShmVolume != nil {
in, out := &in.ShmVolume, &out.ShmVolume in, out := &in.ShmVolume, &out.ShmVolume
*out = new(bool) *out = new(bool)

View File

@ -310,7 +310,7 @@ func (c *Controller) initController() {
if configObjectName := os.Getenv("POSTGRES_OPERATOR_CONFIGURATION_OBJECT"); configObjectName != "" { if configObjectName := os.Getenv("POSTGRES_OPERATOR_CONFIGURATION_OBJECT"); configObjectName != "" {
if c.opConfig.EnableCRDRegistration != nil && *c.opConfig.EnableCRDRegistration { if c.opConfig.EnableCRDRegistration != nil && *c.opConfig.EnableCRDRegistration {
if err := c.createConfigurationCRD(c.opConfig.EnableCRDValidation); err != nil { if err := c.createConfigurationCRD(); err != nil {
c.logger.Fatalf("could not register Operator Configuration CustomResourceDefinition: %v", err) c.logger.Fatalf("could not register Operator Configuration CustomResourceDefinition: %v", err)
} }
} }
@ -328,7 +328,7 @@ func (c *Controller) initController() {
c.modifyConfigFromEnvironment() c.modifyConfigFromEnvironment()
if c.opConfig.EnableCRDRegistration != nil && *c.opConfig.EnableCRDRegistration { if c.opConfig.EnableCRDRegistration != nil && *c.opConfig.EnableCRDRegistration {
if err := c.createPostgresCRD(c.opConfig.EnableCRDValidation); err != nil { if err := c.createPostgresCRD(); err != nil {
c.logger.Fatalf("could not register Postgres CustomResourceDefinition: %v", err) c.logger.Fatalf("could not register Postgres CustomResourceDefinition: %v", err)
} }
} }

View File

@ -35,6 +35,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
// general config // general config
result.EnableCRDRegistration = util.CoalesceBool(fromCRD.EnableCRDRegistration, util.True()) result.EnableCRDRegistration = util.CoalesceBool(fromCRD.EnableCRDRegistration, util.True())
result.EnableCRDValidation = util.CoalesceBool(fromCRD.EnableCRDValidation, util.True()) result.EnableCRDValidation = util.CoalesceBool(fromCRD.EnableCRDValidation, util.True())
result.CRDCategories = util.CoalesceStrArr(fromCRD.CRDCategories, []string{"all"})
result.EnableLazySpiloUpgrade = fromCRD.EnableLazySpiloUpgrade result.EnableLazySpiloUpgrade = fromCRD.EnableLazySpiloUpgrade
result.EnablePgVersionEnvVar = fromCRD.EnablePgVersionEnvVar result.EnablePgVersionEnvVar = fromCRD.EnablePgVersionEnvVar
result.EnableSpiloWalPathCompat = fromCRD.EnableSpiloWalPathCompat result.EnableSpiloWalPathCompat = fromCRD.EnableSpiloWalPathCompat

View File

@ -53,28 +53,35 @@ func (c *Controller) clusterWorkerID(clusterName spec.NamespacedName) uint32 {
return c.clusterWorkers[clusterName] return c.clusterWorkers[clusterName]
} }
func (c *Controller) createOperatorCRD(crd *apiextv1.CustomResourceDefinition) error { func (c *Controller) createOperatorCRD(desiredCrd *apiextv1.CustomResourceDefinition) error {
if _, err := c.KubeClient.CustomResourceDefinitions().Create(context.TODO(), crd, metav1.CreateOptions{}); err != nil { crd, err := c.KubeClient.CustomResourceDefinitions().Get(context.TODO(), desiredCrd.Name, metav1.GetOptions{})
if k8sutil.ResourceAlreadyExists(err) { if k8sutil.ResourceNotFound(err) {
c.logger.Infof("customResourceDefinition %q is already registered and will only be updated", crd.Name) if _, err := c.KubeClient.CustomResourceDefinitions().Create(context.TODO(), desiredCrd, metav1.CreateOptions{}); err != nil {
return fmt.Errorf("could not create customResourceDefinition %q: %v", desiredCrd.Name, err)
patch, err := json.Marshal(crd) }
}
if err != nil { if err != nil {
return fmt.Errorf("could not marshal new customResourceDefintion: %v", err) c.logger.Errorf("could not get customResourceDefinition %q: %v", desiredCrd.Name, err)
}
if crd != nil {
c.logger.Infof("customResourceDefinition %q is already registered and will only be updated", crd.Name)
// copy annotations and labels from existing CRD since we do not define them
desiredCrd.Annotations = crd.Annotations
desiredCrd.Labels = crd.Labels
patch, err := json.Marshal(desiredCrd)
if err != nil {
return fmt.Errorf("could not marshal new customResourceDefintion %q: %v", desiredCrd.Name, err)
} }
if _, err := c.KubeClient.CustomResourceDefinitions().Patch( if _, err := c.KubeClient.CustomResourceDefinitions().Patch(
context.TODO(), crd.Name, types.MergePatchType, patch, metav1.PatchOptions{}); err != nil { context.TODO(), crd.Name, types.MergePatchType, patch, metav1.PatchOptions{}); err != nil {
return fmt.Errorf("could not update customResourceDefinition: %v", err) return fmt.Errorf("could not update customResourceDefinition %q: %v", crd.Name, err)
}
} else {
c.logger.Errorf("could not create customResourceDefinition %q: %v", crd.Name, err)
} }
} else { } else {
c.logger.Infof("customResourceDefinition %q has been registered", crd.Name) c.logger.Infof("customResourceDefinition %q has been registered", crd.Name)
} }
return wait.Poll(c.config.CRDReadyWaitInterval, c.config.CRDReadyWaitTimeout, func() (bool, error) { return wait.Poll(c.config.CRDReadyWaitInterval, c.config.CRDReadyWaitTimeout, func() (bool, error) {
c, err := c.KubeClient.CustomResourceDefinitions().Get(context.TODO(), crd.Name, metav1.GetOptions{}) c, err := c.KubeClient.CustomResourceDefinitions().Get(context.TODO(), desiredCrd.Name, metav1.GetOptions{})
if err != nil { if err != nil {
return false, err return false, err
} }
@ -96,12 +103,12 @@ func (c *Controller) createOperatorCRD(crd *apiextv1.CustomResourceDefinition) e
}) })
} }
func (c *Controller) createPostgresCRD(enableValidation *bool) error { func (c *Controller) createPostgresCRD() error {
return c.createOperatorCRD(acidv1.PostgresCRD(enableValidation)) return c.createOperatorCRD(acidv1.PostgresCRD(c.opConfig.CRDCategories))
} }
func (c *Controller) createConfigurationCRD(enableValidation *bool) error { func (c *Controller) createConfigurationCRD() error {
return c.createOperatorCRD(acidv1.ConfigurationCRD(enableValidation)) return c.createOperatorCRD(acidv1.ConfigurationCRD(c.opConfig.CRDCategories))
} }
func readDecodedRole(s string) (*spec.PgUser, error) { func readDecodedRole(s string) (*spec.PgUser, error) {

View File

@ -20,6 +20,7 @@ type CRD struct {
RepairPeriod time.Duration `name:"repair_period" default:"5m"` RepairPeriod time.Duration `name:"repair_period" default:"5m"`
EnableCRDRegistration *bool `name:"enable_crd_registration" default:"true"` EnableCRDRegistration *bool `name:"enable_crd_registration" default:"true"`
EnableCRDValidation *bool `name:"enable_crd_validation" default:"true"` EnableCRDValidation *bool `name:"enable_crd_validation" default:"true"`
CRDCategories []string `name:"crd_categories" default:"all"`
} }
// Resources describes kubernetes resource specific configuration parameters // Resources describes kubernetes resource specific configuration parameters