From 49895a22009448641253c72a5ce08103e74f647b Mon Sep 17 00:00:00 2001 From: Pierre Ozoux Date: Fri, 26 Jun 2026 10:03:56 +0200 Subject: [PATCH 1/4] fix(helm): remove cpu limit (#2893) There is never a good case for cpu limits :) https://home.robusta.dev/blog/stop-using-cpu-limits Co-authored-by: Felix Kunde --- charts/postgres-operator/values.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/charts/postgres-operator/values.yaml b/charts/postgres-operator/values.yaml index bb2831dd3..b880916f5 100644 --- a/charts/postgres-operator/values.yaml +++ b/charts/postgres-operator/values.yaml @@ -518,7 +518,6 @@ podPriorityClassName: resources: limits: - cpu: 500m memory: 500Mi requests: cpu: 100m From 26af5a7900dba58919d323f097eb6c9536eeae2d Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Fri, 26 Jun 2026 10:43:57 +0200 Subject: [PATCH 2/4] update docs about CRD generation and copy paste to chart dir, too (#3117) --- Makefile | 3 + .../crds/operatorconfigurations.yaml | 5 +- .../postgres-operator/crds/postgresqls.yaml | 343 ++++++++++++++++-- .../postgres-operator/crds/postgresteams.yaml | 114 +++--- docs/developer.md | 44 +-- manifests/operatorconfiguration.crd.yaml | 2 + manifests/postgresql.crd.yaml | 2 + manifests/postgresteam.crd.yaml | 2 + .../v1/operator_configuration_type.go | 1 + .../v1/operatorconfiguration.crd.yaml | 2 + .../acid.zalan.do/v1/postgres_team_type.go | 1 + pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml | 2 + pkg/apis/acid.zalan.do/v1/postgresql_type.go | 1 + 13 files changed, 409 insertions(+), 113 deletions(-) diff --git a/Makefile b/Makefile index 3613c1044..d85b74ed3 100644 --- a/Makefile +++ b/Makefile @@ -75,7 +75,10 @@ $(GENERATED_CRDS): $(GENERATED) @mv manifests/acid.zalan.do_operatorconfigurations.yaml manifests/operatorconfiguration.crd.yaml @mv manifests/acid.zalan.do_postgresteams.yaml manifests/postgresteam.crd.yaml @cp manifests/postgresql.crd.yaml pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml + @cp manifests/postgresql.crd.yaml charts/postgres-operator/crds/postgresqls.yaml @cp manifests/operatorconfiguration.crd.yaml pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml + @cp manifests/operatorconfiguration.crd.yaml charts/postgres-operator/crds/operatorconfigurations.yaml + @cp manifests/postgresteam.crd.yaml charts/postgres-operator/crds/postgresteams.yaml local: ${SOURCES} $(GENERATED_CRDS) CGO_ENABLED=${CGO_ENABLED} go build -o build/${BINARY} $(LOCAL_BUILD_FLAGS) -ldflags "$(LDFLAGS)" $(SOURCES) diff --git a/charts/postgres-operator/crds/operatorconfigurations.yaml b/charts/postgres-operator/crds/operatorconfigurations.yaml index 5875b5808..a6d9a97d0 100644 --- a/charts/postgres-operator/crds/operatorconfigurations.yaml +++ b/charts/postgres-operator/crds/operatorconfigurations.yaml @@ -1,9 +1,12 @@ +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: operatorconfigurations.acid.zalan.do + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 labels: app.kubernetes.io/name: postgres-operator + name: operatorconfigurations.acid.zalan.do spec: group: acid.zalan.do names: diff --git a/charts/postgres-operator/crds/postgresqls.yaml b/charts/postgres-operator/crds/postgresqls.yaml index 2bbf5ee49..cea738dec 100644 --- a/charts/postgres-operator/crds/postgresqls.yaml +++ b/charts/postgres-operator/crds/postgresqls.yaml @@ -1,9 +1,12 @@ +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: postgresqls.acid.zalan.do + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 labels: app.kubernetes.io/name: postgres-operator + name: postgresqls.acid.zalan.do spec: group: acid.zalan.do names: @@ -301,7 +304,9 @@ spec: a Container. properties: name: - description: Name of the environment variable. Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -359,6 +364,43 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -455,8 +497,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must be - a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -514,6 +557,43 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -574,14 +654,14 @@ spec: envFrom: description: |- List of sources to populate environment variables in the container. - The keys defined within a source must be a C_IDENTIFIER. All invalid keys - will be reported as an event when the container is starting. When a key exists in multiple + The keys defined within a source may consist of any printable ASCII characters except '='. + When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. items: description: EnvFromSource represents the source of a set - of ConfigMaps + of ConfigMaps or Secrets properties: configMapRef: description: The ConfigMap to select from @@ -602,8 +682,9 @@ spec: type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to each - key in the ConfigMap. Must be a C_IDENTIFIER. + description: |- + Optional text to prepend to the name of each environment variable. + May consist of any printable ASCII characters except '='. type: string secretRef: description: The Secret to select from @@ -866,6 +947,12 @@ spec: - port type: object type: object + stopSignal: + description: |- + StopSignal defines which signal will be sent to a container when it is being stopped. + If not specified, the default is defined by the container runtime in use. + StopSignal can only be set for Pods with a non-empty .spec.os.name + type: string type: object livenessProbe: description: |- @@ -1236,7 +1323,9 @@ spec: type: integer type: object resizePolicy: - description: Resources resize policy for the container. + description: |- + Resources resize policy for the container. + This field cannot be set on ephemeral containers. items: description: ContainerResizePolicy represents resource resize policy for the container. @@ -1268,7 +1357,7 @@ spec: Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. @@ -1322,10 +1411,10 @@ spec: restartPolicy: description: |- RestartPolicy defines the restart behavior of individual containers in a pod. - This field may only be set for init containers, and the only allowed value is "Always". - For non-init containers or when this field is not specified, + This overrides the pod-level restart policy. When this field is not specified, the restart behavior is defined by the Pod's restart policy and the container type. - Setting the RestartPolicy as "Always" for the init container will have the following effect: + Additionally, setting the RestartPolicy as "Always" for the init container will + have the following effect: this init container will be continually restarted on exit until all regular containers have terminated. Once all regular containers have completed, all init containers with restartPolicy "Always" @@ -1337,6 +1426,59 @@ spec: init container is started, or after any startupProbe has successfully completed. type: string + restartPolicyRules: + description: |- + Represents a list of rules to be checked to determine if the + container should be restarted on exit. The rules are evaluated in + order. Once a rule matches a container exit condition, the remaining + rules are ignored. If no rule matches the container exit condition, + the Container-level restart policy determines the whether the container + is restarted or not. Constraints on the rules: + - At most 20 rules are allowed. + - Rules can have the same action. + - Identical rules are not forbidden in validations. + When rules are specified, container MUST set RestartPolicy explicitly + even it if matches the Pod's RestartPolicy. + items: + description: ContainerRestartRule describes how a container + exit is handled. + properties: + action: + description: |- + Specifies the action taken on a container exit if the requirements + are satisfied. The only possible value is "Restart" to restart the + container. + type: string + exitCodes: + description: Represents the exit codes to check on container + exits. + properties: + operator: + description: |- + Represents the relationship between the container exit code(s) and the + specified values. Possible values are: + - In: the requirement is satisfied if the container exit code is in the + set of specified values. + - NotIn: the requirement is satisfied if the container exit code is + not in the set of specified values. + type: string + values: + description: |- + Specifies the set of values to check for container exit codes. + At most 255 elements are allowed. + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic securityContext: description: |- SecurityContext defines the security options the container should be run with. @@ -1412,7 +1554,6 @@ spec: procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. - This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows. type: string readOnlyRootFilesystem: @@ -1877,8 +2018,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must be - a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -1936,6 +2078,43 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -1996,14 +2175,14 @@ spec: envFrom: description: |- List of sources to populate environment variables in the container. - The keys defined within a source must be a C_IDENTIFIER. All invalid keys - will be reported as an event when the container is starting. When a key exists in multiple + The keys defined within a source may consist of any printable ASCII characters except '='. + When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. items: description: EnvFromSource represents the source of a set - of ConfigMaps + of ConfigMaps or Secrets properties: configMapRef: description: The ConfigMap to select from @@ -2024,8 +2203,9 @@ spec: type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to each - key in the ConfigMap. Must be a C_IDENTIFIER. + description: |- + Optional text to prepend to the name of each environment variable. + May consist of any printable ASCII characters except '='. type: string secretRef: description: The Secret to select from @@ -2288,6 +2468,12 @@ spec: - port type: object type: object + stopSignal: + description: |- + StopSignal defines which signal will be sent to a container when it is being stopped. + If not specified, the default is defined by the container runtime in use. + StopSignal can only be set for Pods with a non-empty .spec.os.name + type: string type: object livenessProbe: description: |- @@ -2658,7 +2844,9 @@ spec: type: integer type: object resizePolicy: - description: Resources resize policy for the container. + description: |- + Resources resize policy for the container. + This field cannot be set on ephemeral containers. items: description: ContainerResizePolicy represents resource resize policy for the container. @@ -2690,7 +2878,7 @@ spec: Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. @@ -2744,10 +2932,10 @@ spec: restartPolicy: description: |- RestartPolicy defines the restart behavior of individual containers in a pod. - This field may only be set for init containers, and the only allowed value is "Always". - For non-init containers or when this field is not specified, + This overrides the pod-level restart policy. When this field is not specified, the restart behavior is defined by the Pod's restart policy and the container type. - Setting the RestartPolicy as "Always" for the init container will have the following effect: + Additionally, setting the RestartPolicy as "Always" for the init container will + have the following effect: this init container will be continually restarted on exit until all regular containers have terminated. Once all regular containers have completed, all init containers with restartPolicy "Always" @@ -2759,6 +2947,59 @@ spec: init container is started, or after any startupProbe has successfully completed. type: string + restartPolicyRules: + description: |- + Represents a list of rules to be checked to determine if the + container should be restarted on exit. The rules are evaluated in + order. Once a rule matches a container exit condition, the remaining + rules are ignored. If no rule matches the container exit condition, + the Container-level restart policy determines the whether the container + is restarted or not. Constraints on the rules: + - At most 20 rules are allowed. + - Rules can have the same action. + - Identical rules are not forbidden in validations. + When rules are specified, container MUST set RestartPolicy explicitly + even it if matches the Pod's RestartPolicy. + items: + description: ContainerRestartRule describes how a container + exit is handled. + properties: + action: + description: |- + Specifies the action taken on a container exit if the requirements + are satisfied. The only possible value is "Restart" to restart the + container. + type: string + exitCodes: + description: Represents the exit codes to check on container + exits. + properties: + operator: + description: |- + Represents the relationship between the container exit code(s) and the + specified values. Possible values are: + - In: the requirement is satisfied if the container exit code is in the + set of specified values. + - NotIn: the requirement is satisfied if the container exit code is + not in the set of specified values. + type: string + values: + description: |- + Specifies the set of values to check for container exit codes. + At most 255 elements are allowed. + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic securityContext: description: |- SecurityContext defines the security options the container should be run with. @@ -2834,7 +3075,6 @@ spec: procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. - This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows. type: string readOnlyRootFilesystem: @@ -3848,8 +4088,9 @@ spec: in a Container. properties: name: - description: Name of the environment variable. Must be - a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -3907,6 +4148,43 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount containing + the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -4213,9 +4491,10 @@ spec: operator: description: |- Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). type: string tolerationSeconds: description: |- @@ -4354,7 +4633,6 @@ spec: - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string nodeTaintsPolicy: description: |- @@ -4365,7 +4643,6 @@ spec: - Ignore: node taints are ignored. All nodes are included. If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string topologyKey: description: |- diff --git a/charts/postgres-operator/crds/postgresteams.yaml b/charts/postgres-operator/crds/postgresteams.yaml index b7a36848d..3bc7fcd1d 100644 --- a/charts/postgres-operator/crds/postgresteams.yaml +++ b/charts/postgres-operator/crds/postgresteams.yaml @@ -1,70 +1,84 @@ +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: postgresteams.acid.zalan.do + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 labels: app.kubernetes.io/name: postgres-operator + name: postgresteams.acid.zalan.do spec: group: acid.zalan.do names: + categories: + - all kind: PostgresTeam listKind: PostgresTeamList plural: postgresteams - singular: postgresteam shortNames: - pgteam - categories: - - all + singular: postgresteam scope: Namespaced versions: - name: v1 + schema: + openAPIV3Schema: + description: PostgresTeam defines Custom Resource Definition Object for team + management. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: PostgresTeamSpec defines the specification for the PostgresTeam + TPR. + properties: + additionalMembers: + additionalProperties: + description: List of users who will also be added to the Postgres + cluster. + items: + type: string + type: array + description: Map for teamId and associated additional users + type: object + additionalSuperuserTeams: + additionalProperties: + description: List of teams to become Postgres superusers + items: + type: string + type: array + description: Map for teamId and associated additional superuser teams + type: object + additionalTeams: + additionalProperties: + description: List of teams whose members will also be added to the + Postgres cluster. + items: + type: string + type: array + description: Map for teamId and associated additional teams + type: object + type: object + required: + - metadata + - spec + type: object served: true storage: true subresources: status: {} - schema: - openAPIV3Schema: - type: object - required: - - kind - - apiVersion - - spec - properties: - kind: - type: string - enum: - - PostgresTeam - apiVersion: - type: string - enum: - - acid.zalan.do/v1 - spec: - type: object - properties: - additionalSuperuserTeams: - type: object - description: "Map for teamId and associated additional superuser teams" - additionalProperties: - type: array - nullable: true - description: "List of teams to become Postgres superusers" - items: - type: string - additionalTeams: - type: object - description: "Map for teamId and associated additional teams" - additionalProperties: - type: array - nullable: true - description: "List of teams whose members will also be added to the Postgres cluster" - items: - type: string - additionalMembers: - type: object - description: "Map for teamId and associated additional users" - additionalProperties: - type: array - nullable: true - description: "List of users who will also be added to the Postgres cluster" - items: - type: string diff --git a/docs/developer.md b/docs/developer.md index 141ee63de..eb7cb0e9f 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -27,20 +27,11 @@ git clone https://github.com/zalando/postgres-operator.git ## Building the operator -We use [Go Modules](https://github.com/golang/go/wiki/Modules) for handling -dependencies. When using Go below v1.13 you need to explicitly enable Go modules -by setting the `GO111MODULE` environment variable to `on`. The make targets do -this for you, so simply run +We use [Go Modules](https://github.com/golang/go/wiki/Modules) for handling dependencies. +Run `go mod vendor && go mod tidy` to install them. -```bash -make -``` - -Build the operator with the `make docker` command. You may define the TAG -variable to assign an explicit tag to your Docker image and the IMAGE to set -the image name. By default, the tag is computed with -`git describe --tags --always --dirty` and the image is -`ghcr.io/zalando/postgres-operator` +Build the operator with the `make docker` command. You may define the TAG variable to assign an explicit tag to your Docker image and the IMAGE to set the image name. By default, the tag is computed with +`git describe --tags --always --dirty` and the image is `ghcr.io/zalando/postgres-operator`. On macos search and replace `sed -i` commands with `sed -i ''` for the make commands to work. ```bash export TAG=$(git describe --tags --always --dirty) @@ -296,8 +287,7 @@ Please run flake8 [before submitting a PR](http://flake8.pycqa.org/en/latest/use In the case you want to add functionality to the operator that shall be controlled via the operator configuration there are a few places that need to be updated. As explained [here](reference/operator_parameters.md), it's possible -to configure the operator either with a ConfigMap or CRD, but currently we aim -to synchronize parameters everywhere. +to configure the operator either with a ConfigMap or CRD. When choosing a parameter name for a new option in a Postgres cluster manifest, keep in mind the naming conventions there. We use `camelCase` for manifest @@ -320,32 +310,28 @@ manifest files: Postgres manifest parameters are defined in the [api package](https://github.com/zalando/postgres-operator/blob/master/pkg/apis/acid.zalan.do/v1/postgresql_type.go). The operator behavior has to be implemented at least in [k8sres.go](https://github.com/zalando/postgres-operator/blob/master/pkg/cluster/k8sres.go). -Validation of CRD parameters is controlled in [crds.go](https://github.com/zalando/postgres-operator/blob/master/pkg/apis/acid.zalan.do/v1/crds.go). Please, reflect your changes in tests, for example in: * [config_test.go](https://github.com/zalando/postgres-operator/blob/master/pkg/util/config/config_test.go) * [k8sres_test.go](https://github.com/zalando/postgres-operator/blob/master/pkg/cluster/k8sres_test.go) * [util_test.go](https://github.com/zalando/postgres-operator/blob/master/pkg/apis/acid.zalan.do/v1/util_test.go) -### Updating manifest files +### Generating the CRDs -For the CRD-based configuration, please update the following files: - -* the default [OperatorConfiguration](https://github.com/zalando/postgres-operator/blob/master/manifests/postgresql-operator-default-configuration.yaml) -* the CRD's [validation](https://github.com/zalando/postgres-operator/blob/master/manifests/operatorconfiguration.crd.yaml) -* the CRD's validation in the [Helm chart](https://github.com/zalando/postgres-operator/blob/master/charts/postgres-operator/crds/operatorconfigurations.yaml) - -Add new options also to the Helm chart's [values file](https://github.com/zalando/postgres-operator/blob/master/charts/postgres-operator/values.yaml) file. -It follows the OperatorConfiguration CRD layout. Nested values will be flattened for the ConfigMap. -Last but no least, update the [ConfigMap](https://github.com/zalando/postgres-operator/blob/master/manifests/configmap.yaml) manifest example as well. +The CRDs can be automatically generated from the go structs. Use the correct kubebuilder annotations for defining the validation, constraints or default values etc.. Run `make` to update the CRDs which are stored in three locations: +- In the Go api package +- The example manifests folder +- The helm chart folder ### Updating documentation -Finally, add a section for each new configuration option and/or cluster manifest +Config changes need to be reflected in the Helm chart's [values file](https://github.com/zalando/postgres-operator/blob/master/charts/postgres-operator/values.yaml), too. It follows the OperatorConfiguration CRD layout. Nested values will be flattened for the ConfigMap. + +Add a section for each new configuration option and/or cluster manifest parameter in the reference documents: * [config reference](reference/operator_parameters.md) * [manifest reference](reference/cluster_manifest.md) -It also helps users to explain new features with examples in the -[administrator docs](administrator.md). +It can also help other K8s admins to explain new features with examples in the +[administrator docs](administrator.md) and also update the [OperatorConfiguration CRD](https://github.com/zalando/postgres-operator/blob/master/manifests/postgresql-operator-default-configuration.yaml) and [ConfigMap](https://github.com/zalando/postgres-operator/blob/master/manifests/configmap.yaml) manifest examples. diff --git a/manifests/operatorconfiguration.crd.yaml b/manifests/operatorconfiguration.crd.yaml index 5f347f2ac..a6d9a97d0 100644 --- a/manifests/operatorconfiguration.crd.yaml +++ b/manifests/operatorconfiguration.crd.yaml @@ -4,6 +4,8 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.17.3 + labels: + app.kubernetes.io/name: postgres-operator name: operatorconfigurations.acid.zalan.do spec: group: acid.zalan.do diff --git a/manifests/postgresql.crd.yaml b/manifests/postgresql.crd.yaml index 9b855eb68..cea738dec 100644 --- a/manifests/postgresql.crd.yaml +++ b/manifests/postgresql.crd.yaml @@ -4,6 +4,8 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.17.3 + labels: + app.kubernetes.io/name: postgres-operator name: postgresqls.acid.zalan.do spec: group: acid.zalan.do diff --git a/manifests/postgresteam.crd.yaml b/manifests/postgresteam.crd.yaml index 2245c6253..3bc7fcd1d 100644 --- a/manifests/postgresteam.crd.yaml +++ b/manifests/postgresteam.crd.yaml @@ -4,6 +4,8 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.17.3 + labels: + app.kubernetes.io/name: postgres-operator name: postgresteams.acid.zalan.do spec: group: acid.zalan.do diff --git a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go index 60793c45c..a55fdd7a8 100644 --- a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go +++ b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go @@ -24,6 +24,7 @@ import ( // +kubebuilder:printcolumn:name="Min-Instances",type=integer,JSONPath=`.configuration.min_instances`,description="Minimum number of instances per Postgres cluster" // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,description="Age of the OperatorConfiguration resource" // +kubebuilder:subresource:status +// +kubebuilder:metadata:labels=app.kubernetes.io/name=postgres-operator type OperatorConfiguration struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` diff --git a/pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml b/pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml index 5f347f2ac..a6d9a97d0 100644 --- a/pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml +++ b/pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml @@ -4,6 +4,8 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.17.3 + labels: + app.kubernetes.io/name: postgres-operator name: operatorconfigurations.acid.zalan.do spec: group: acid.zalan.do diff --git a/pkg/apis/acid.zalan.do/v1/postgres_team_type.go b/pkg/apis/acid.zalan.do/v1/postgres_team_type.go index ffedaef57..be39dea07 100644 --- a/pkg/apis/acid.zalan.do/v1/postgres_team_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgres_team_type.go @@ -11,6 +11,7 @@ import ( // +k8s:deepcopy-gen=true // +kubebuilder:resource:shortName=pgteam,categories=all // +kubebuilder:subresource:status +// +kubebuilder:metadata:labels=app.kubernetes.io/name=postgres-operator type PostgresTeam struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` diff --git a/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml b/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml index 9b855eb68..cea738dec 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml +++ b/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml @@ -4,6 +4,8 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.17.3 + labels: + app.kubernetes.io/name: postgres-operator name: postgresqls.acid.zalan.do spec: group: acid.zalan.do diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index 81efa42cc..85a73605c 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -24,6 +24,7 @@ import ( // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,description="Age of the PostgreSQL cluster" // +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.PostgresClusterStatus`,description="Current sync status of postgresql resource" // +kubebuilder:subresource:status +// +kubebuilder:metadata:labels=app.kubernetes.io/name=postgres-operator type Postgresql struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` From 9030520e24006392846fc7725f5da0aa20ed362f Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Fri, 26 Jun 2026 17:02:50 +0200 Subject: [PATCH 3/4] fix array validation for global maintenance window and protected role names (#3120) * change type of maintenance windows in config crd * fix default array type for protected role names --- .../crds/operatorconfigurations.yaml | 8 +++++++- manifests/operatorconfiguration.crd.yaml | 8 +++++++- .../acid.zalan.do/v1/operator_configuration_type.go | 8 ++++---- .../acid.zalan.do/v1/operatorconfiguration.crd.yaml | 8 +++++++- pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go | 6 ++---- pkg/controller/operator_config.go | 11 +---------- 6 files changed, 28 insertions(+), 21 deletions(-) diff --git a/charts/postgres-operator/crds/operatorconfigurations.yaml b/charts/postgres-operator/crds/operatorconfigurations.yaml index a6d9a97d0..19d957431 100644 --- a/charts/postgres-operator/crds/operatorconfigurations.yaml +++ b/charts/postgres-operator/crds/operatorconfigurations.yaml @@ -706,6 +706,10 @@ spec: type: integer type: object maintenance_windows: + items: + pattern: ^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ + *$ + type: string type: array major_version_upgrade: description: MajorVersionUpgradeConfiguration defines how to execute @@ -851,7 +855,9 @@ spec: type: string type: array protected_role_names: - default: '["admin", "cron_admin"]' + default: + - admin + - cron_admin items: type: string type: array diff --git a/manifests/operatorconfiguration.crd.yaml b/manifests/operatorconfiguration.crd.yaml index a6d9a97d0..19d957431 100644 --- a/manifests/operatorconfiguration.crd.yaml +++ b/manifests/operatorconfiguration.crd.yaml @@ -706,6 +706,10 @@ spec: type: integer type: object maintenance_windows: + items: + pattern: ^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ + *$ + type: string type: array major_version_upgrade: description: MajorVersionUpgradeConfiguration defines how to execute @@ -851,7 +855,9 @@ spec: type: string type: array protected_role_names: - default: '["admin", "cron_admin"]' + default: + - admin + - cron_admin items: type: string type: array diff --git a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go index a55fdd7a8..0d98d487a 100644 --- a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go +++ b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go @@ -284,7 +284,7 @@ type TeamsAPIConfiguration struct { PamRoleName string `json:"pam_role_name,omitempty"` // +kubebuilder:default="https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees" PamConfiguration string `json:"pam_configuration,omitempty"` - // +kubebuilder:default="[\"admin\", \"cron_admin\"]" + // +kubebuilder:default={"admin", "cron_admin"} ProtectedRoles []string `json:"protected_role_names,omitempty"` PostgresSuperuserTeams []string `json:"postgres_superuser_teams,omitempty"` // +kubebuilder:default=true @@ -426,10 +426,10 @@ type OperatorConfigurationData struct { RepairPeriod Duration `json:"repair_period,omitempty"` // +kubebuilder:default=true EnableMaintenanceWindows *bool `json:"enable_maintenance_windows,omitempty"` - // +kubebuilder:validation:Schemaless // +kubebuilder:validation:Type=array - MaintenanceWindows []MaintenanceWindow `json:"maintenance_windows,omitempty"` - SetMemoryRequestToLimit bool `json:"set_memory_request_to_limit,omitempty"` + // +kubebuilder:validation:items:Pattern=`^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ *$` + MaintenanceWindows []string `json:"maintenance_windows,omitempty"` + SetMemoryRequestToLimit bool `json:"set_memory_request_to_limit,omitempty"` // +kubebuilder:default=true ShmVolume *bool `json:"enable_shm_volume,omitempty"` SidecarImages map[string]string `json:"sidecar_docker_images,omitempty"` // deprecated in favour of SidecarContainers diff --git a/pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml b/pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml index a6d9a97d0..19d957431 100644 --- a/pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml +++ b/pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml @@ -706,6 +706,10 @@ spec: type: integer type: object maintenance_windows: + items: + pattern: ^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ + *$ + type: string type: array major_version_upgrade: description: MajorVersionUpgradeConfiguration defines how to execute @@ -851,7 +855,9 @@ spec: type: string type: array protected_role_names: - default: '["admin", "cron_admin"]' + default: + - admin + - cron_admin items: type: string type: array diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 7d18c2cf2..4fd9c4a00 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -440,10 +440,8 @@ func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData } if in.MaintenanceWindows != nil { in, out := &in.MaintenanceWindows, &out.MaintenanceWindows - *out = make([]MaintenanceWindow, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } + *out = make([]string, len(*in)) + copy(*out, *in) } if in.ShmVolume != nil { in, out := &in.ShmVolume, &out.ShmVolume diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index 66fc7a731..94ef86d7c 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -51,16 +51,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.SidecarImages = fromCRD.SidecarImages result.SidecarContainers = fromCRD.SidecarContainers result.EnableMaintenanceWindows = util.CoalesceBool(fromCRD.EnableMaintenanceWindows, util.True()) - if len(fromCRD.MaintenanceWindows) > 0 { - result.MaintenanceWindows = make([]string, 0, len(fromCRD.MaintenanceWindows)) - for _, window := range fromCRD.MaintenanceWindows { - w, err := window.MarshalJSON() - if err != nil { - panic(fmt.Errorf("could not marshal configured maintenance window: %v", err)) - } - result.MaintenanceWindows = append(result.MaintenanceWindows, string(w)) - } - } + result.MaintenanceWindows = fromCRD.MaintenanceWindows // user config result.SuperUsername = util.Coalesce(fromCRD.PostgresUsersConfiguration.SuperUsername, "postgres") From 1227fc2e1c6a4b51b83ad9696bf69fd3e29e66bc Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Mon, 29 Jun 2026 10:32:24 +0200 Subject: [PATCH 4/4] switch to metav1.Duration for timeout options (#3121) * switch to metav1.Duration for timeout options * fix custom config parsing and use safe coalesce --- .../crds/operatorconfigurations.yaml | 36 ++++------- manifests/operatorconfiguration.crd.yaml | 36 ++++------- pkg/apis/acid.zalan.do/v1/marshal.go | 27 -------- .../v1/operator_configuration_type.go | 33 +++++----- .../v1/operatorconfiguration.crd.yaml | 36 ++++------- .../acid.zalan.do/v1/zz_generated.deepcopy.go | 62 ++++++++++++++++++- pkg/cluster/cluster_test.go | 7 ++- pkg/cluster/connection_pooler.go | 2 +- pkg/cluster/k8sres.go | 6 +- pkg/cluster/k8sres_test.go | 29 +++++---- pkg/cluster/pod.go | 6 +- pkg/cluster/pod_test.go | 5 +- pkg/cluster/resources.go | 6 +- pkg/cluster/sync_test.go | 24 +++---- pkg/cluster/util.go | 8 +-- pkg/cluster/util_test.go | 8 +-- pkg/controller/node.go | 2 +- pkg/controller/operator_config.go | 26 ++++---- pkg/controller/postgresql.go | 2 +- pkg/util/config/config.go | 32 +++++----- pkg/util/config/config_test.go | 12 ++-- pkg/util/config/util.go | 19 +++--- pkg/util/util.go | 8 +-- 23 files changed, 214 insertions(+), 218 deletions(-) diff --git a/charts/postgres-operator/crds/operatorconfigurations.yaml b/charts/postgres-operator/crds/operatorconfigurations.yaml index 19d957431..9cd6b27b7 100644 --- a/charts/postgres-operator/crds/operatorconfigurations.yaml +++ b/charts/postgres-operator/crds/operatorconfigurations.yaml @@ -447,8 +447,7 @@ spec: default: 20m description: timeout for successful migration of master pods from unschedulable node - format: int64 - type: integer + type: string node_readiness_label: additionalProperties: type: string @@ -521,8 +520,7 @@ spec: default: 5m description: Postgres pods are terminated forcefully after this timeout - format: int64 - type: integer + type: string secret_name_template: default: '{username}.{cluster}.credentials.{tprkind}.{tprgroup}' description: |- @@ -783,13 +781,11 @@ spec: repair_period: default: 5m description: period between consecutive repair requests - format: int64 - type: integer + type: string resync_period: default: 30m description: period between consecutive sync requests - format: int64 - type: integer + type: string scalyr: description: ScalyrConfiguration defines the configuration for ScalyrAPI properties: @@ -885,48 +881,40 @@ spec: default: 1s description: interval between consecutive attempts of operator calling the Patroni API - format: int64 - type: integer + type: string patroni_api_check_timeout: default: 5s description: timeout when waiting for successful response from Patroni API - format: int64 - type: integer + type: string pod_deletion_wait_timeout: default: 10m description: timeout when waiting for the Postgres pods to be deleted - format: int64 - type: integer + type: string pod_label_wait_timeout: default: 10m description: timeout when waiting for pod role and cluster labels - format: int64 - type: integer + type: string ready_wait_interval: default: 4s description: interval between consecutive attempts waiting for postgresql CRD to be created - format: int64 - type: integer + type: string ready_wait_timeout: default: 30s description: timeout for the complete postgres CRD creation - format: int64 - type: integer + type: string resource_check_interval: default: 3s description: interval to wait between consecutive attempts to check for some K8s resources - format: int64 - type: integer + type: string resource_check_timeout: default: 10m description: timeout when waiting for the presence of a certain K8s resource - format: int64 - type: integer + type: string type: object users: description: PostgresUsersConfiguration defines the system users of diff --git a/manifests/operatorconfiguration.crd.yaml b/manifests/operatorconfiguration.crd.yaml index 19d957431..9cd6b27b7 100644 --- a/manifests/operatorconfiguration.crd.yaml +++ b/manifests/operatorconfiguration.crd.yaml @@ -447,8 +447,7 @@ spec: default: 20m description: timeout for successful migration of master pods from unschedulable node - format: int64 - type: integer + type: string node_readiness_label: additionalProperties: type: string @@ -521,8 +520,7 @@ spec: default: 5m description: Postgres pods are terminated forcefully after this timeout - format: int64 - type: integer + type: string secret_name_template: default: '{username}.{cluster}.credentials.{tprkind}.{tprgroup}' description: |- @@ -783,13 +781,11 @@ spec: repair_period: default: 5m description: period between consecutive repair requests - format: int64 - type: integer + type: string resync_period: default: 30m description: period between consecutive sync requests - format: int64 - type: integer + type: string scalyr: description: ScalyrConfiguration defines the configuration for ScalyrAPI properties: @@ -885,48 +881,40 @@ spec: default: 1s description: interval between consecutive attempts of operator calling the Patroni API - format: int64 - type: integer + type: string patroni_api_check_timeout: default: 5s description: timeout when waiting for successful response from Patroni API - format: int64 - type: integer + type: string pod_deletion_wait_timeout: default: 10m description: timeout when waiting for the Postgres pods to be deleted - format: int64 - type: integer + type: string pod_label_wait_timeout: default: 10m description: timeout when waiting for pod role and cluster labels - format: int64 - type: integer + type: string ready_wait_interval: default: 4s description: interval between consecutive attempts waiting for postgresql CRD to be created - format: int64 - type: integer + type: string ready_wait_timeout: default: 30s description: timeout for the complete postgres CRD creation - format: int64 - type: integer + type: string resource_check_interval: default: 3s description: interval to wait between consecutive attempts to check for some K8s resources - format: int64 - type: integer + type: string resource_check_timeout: default: 10m description: timeout when waiting for the presence of a certain K8s resource - format: int64 - type: integer + type: string type: object users: description: PostgresUsersConfiguration defines the system users of diff --git a/pkg/apis/acid.zalan.do/v1/marshal.go b/pkg/apis/acid.zalan.do/v1/marshal.go index 014214fed..ac351bc0d 100644 --- a/pkg/apis/acid.zalan.do/v1/marshal.go +++ b/pkg/apis/acid.zalan.do/v1/marshal.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "strings" - "time" ) type postgresqlCopy Postgresql @@ -120,29 +119,3 @@ func (p *Postgresql) UnmarshalJSON(data []byte) error { return nil } - -// UnmarshalJSON convert to Duration from byte slice of json -func (d *Duration) UnmarshalJSON(b []byte) error { - var ( - v interface{} - err error - ) - if err = json.Unmarshal(b, &v); err != nil { - return err - } - switch val := v.(type) { - case string: - t, err := time.ParseDuration(val) - if err != nil { - return err - } - *d = Duration(t) - return nil - case float64: - t := time.Duration(val) - *d = Duration(t) - return nil - default: - return fmt.Errorf("could not recognize type %T as a valid type to unmarshal to Duration", val) - } -} diff --git a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go index 0d98d487a..27063fd23 100644 --- a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go +++ b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go @@ -5,8 +5,6 @@ package v1 import ( "github.com/zalando/postgres-operator/pkg/util/config" - "time" - "github.com/zalando/postgres-operator/pkg/spec" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -78,7 +76,7 @@ type KubernetesMetaConfiguration struct { PodServiceAccountRoleBindingDefinition string `json:"pod_service_account_role_binding_definition,omitempty"` // +kubebuilder:default="5m" // Postgres pods are terminated forcefully after this timeout - PodTerminateGracePeriod Duration `json:"pod_terminate_grace_period,omitempty"` + PodTerminateGracePeriod *metav1.Duration `json:"pod_terminate_grace_period,omitempty"` // +optional LivenessProbe *v1.Probe `json:"liveness_probe"` SpiloPrivileged bool `json:"spilo_privileged,omitempty"` @@ -143,9 +141,9 @@ type KubernetesMetaConfiguration struct { PodPriorityClassName string `json:"pod_priority_class_name,omitempty"` // +kubebuilder:default="20m" // timeout for successful migration of master pods from unschedulable node - MasterPodMoveTimeout Duration `json:"master_pod_move_timeout,omitempty"` - EnablePodAntiAffinity bool `json:"enable_pod_antiaffinity,omitempty"` - PodAntiAffinityPreferredDuringScheduling bool `json:"pod_antiaffinity_preferred_during_scheduling,omitempty"` + MasterPodMoveTimeout *metav1.Duration `json:"master_pod_move_timeout,omitempty"` + EnablePodAntiAffinity bool `json:"enable_pod_antiaffinity,omitempty"` + PodAntiAffinityPreferredDuringScheduling bool `json:"pod_antiaffinity_preferred_during_scheduling,omitempty"` // +kubebuilder:default="kubernetes.io/hostname" PodAntiAffinityTopologyKey string `json:"pod_antiaffinity_topology_key,omitempty"` // +kubebuilder:validation:Enum=ordered_ready;parallel @@ -186,28 +184,28 @@ type PostgresPodResourcesDefaults struct { type OperatorTimeouts struct { // +kubebuilder:default="3s" // interval to wait between consecutive attempts to check for some K8s resources - ResourceCheckInterval Duration `json:"resource_check_interval,omitempty"` + ResourceCheckInterval *metav1.Duration `json:"resource_check_interval,omitempty"` // +kubebuilder:default="10m" // timeout when waiting for the presence of a certain K8s resource - ResourceCheckTimeout Duration `json:"resource_check_timeout,omitempty"` + ResourceCheckTimeout *metav1.Duration `json:"resource_check_timeout,omitempty"` // +kubebuilder:default="10m" // timeout when waiting for pod role and cluster labels - PodLabelWaitTimeout Duration `json:"pod_label_wait_timeout,omitempty"` + PodLabelWaitTimeout *metav1.Duration `json:"pod_label_wait_timeout,omitempty"` // +kubebuilder:default="10m" // timeout when waiting for the Postgres pods to be deleted - PodDeletionWaitTimeout Duration `json:"pod_deletion_wait_timeout,omitempty"` + PodDeletionWaitTimeout *metav1.Duration `json:"pod_deletion_wait_timeout,omitempty"` // +kubebuilder:default="4s" // interval between consecutive attempts waiting for postgresql CRD to be created - ReadyWaitInterval Duration `json:"ready_wait_interval,omitempty"` + ReadyWaitInterval *metav1.Duration `json:"ready_wait_interval,omitempty"` // +kubebuilder:default="30s" // timeout for the complete postgres CRD creation - ReadyWaitTimeout Duration `json:"ready_wait_timeout,omitempty"` + ReadyWaitTimeout *metav1.Duration `json:"ready_wait_timeout,omitempty"` // +kubebuilder:default="1s" // interval between consecutive attempts of operator calling the Patroni API - PatroniAPICheckInterval Duration `json:"patroni_api_check_interval,omitempty"` + PatroniAPICheckInterval *metav1.Duration `json:"patroni_api_check_interval,omitempty"` // +kubebuilder:default="5s" // timeout when waiting for successful response from Patroni API - PatroniAPICheckTimeout Duration `json:"patroni_api_check_timeout,omitempty"` + PatroniAPICheckTimeout *metav1.Duration `json:"patroni_api_check_timeout,omitempty"` } // LoadBalancerConfiguration defines the LB configuration @@ -420,10 +418,10 @@ type OperatorConfigurationData struct { Workers uint32 `json:"workers,omitempty"` // +kubebuilder:default="30m" // period between consecutive sync requests - ResyncPeriod Duration `json:"resync_period,omitempty"` + ResyncPeriod *metav1.Duration `json:"resync_period,omitempty"` // +kubebuilder:default="5m" // period between consecutive repair requests - RepairPeriod Duration `json:"repair_period,omitempty"` + RepairPeriod *metav1.Duration `json:"repair_period,omitempty"` // +kubebuilder:default=true EnableMaintenanceWindows *bool `json:"enable_maintenance_windows,omitempty"` // +kubebuilder:validation:Type=array @@ -478,6 +476,3 @@ type OperatorConfigurationData struct { IgnoreInstanceLimitsAnnotationKey string `json:"ignore_instance_limits_annotation_key,omitempty"` IgnoreResourcesLimitsAnnotationKey string `json:"ignore_resources_limits_annotation_key,omitempty"` } - -// Duration shortens this frequently used name -type Duration time.Duration diff --git a/pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml b/pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml index 19d957431..9cd6b27b7 100644 --- a/pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml +++ b/pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml @@ -447,8 +447,7 @@ spec: default: 20m description: timeout for successful migration of master pods from unschedulable node - format: int64 - type: integer + type: string node_readiness_label: additionalProperties: type: string @@ -521,8 +520,7 @@ spec: default: 5m description: Postgres pods are terminated forcefully after this timeout - format: int64 - type: integer + type: string secret_name_template: default: '{username}.{cluster}.credentials.{tprkind}.{tprgroup}' description: |- @@ -783,13 +781,11 @@ spec: repair_period: default: 5m description: period between consecutive repair requests - format: int64 - type: integer + type: string resync_period: default: 30m description: period between consecutive sync requests - format: int64 - type: integer + type: string scalyr: description: ScalyrConfiguration defines the configuration for ScalyrAPI properties: @@ -885,48 +881,40 @@ spec: default: 1s description: interval between consecutive attempts of operator calling the Patroni API - format: int64 - type: integer + type: string patroni_api_check_timeout: default: 5s description: timeout when waiting for successful response from Patroni API - format: int64 - type: integer + type: string pod_deletion_wait_timeout: default: 10m description: timeout when waiting for the Postgres pods to be deleted - format: int64 - type: integer + type: string pod_label_wait_timeout: default: 10m description: timeout when waiting for pod role and cluster labels - format: int64 - type: integer + type: string ready_wait_interval: default: 4s description: interval between consecutive attempts waiting for postgresql CRD to be created - format: int64 - type: integer + type: string ready_wait_timeout: default: 30s description: timeout for the complete postgres CRD creation - format: int64 - type: integer + type: string resource_check_interval: default: 3s description: interval to wait between consecutive attempts to check for some K8s resources - format: int64 - type: integer + type: string resource_check_timeout: default: 10m description: timeout when waiting for the presence of a certain K8s resource - format: int64 - type: integer + type: string type: object users: description: PostgresUsersConfiguration defines the system users of diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 4fd9c4a00..ddccccf7f 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -163,6 +163,11 @@ func (in *KubernetesMetaConfiguration) DeepCopyInto(out *KubernetesMetaConfigura *out = new(bool) **out = **in } + if in.PodTerminateGracePeriod != nil { + in, out := &in.PodTerminateGracePeriod, &out.PodTerminateGracePeriod + *out = new(metav1.Duration) + **out = **in + } if in.LivenessProbe != nil { in, out := &in.LivenessProbe, &out.LivenessProbe *out = new(corev1.Probe) @@ -280,6 +285,11 @@ func (in *KubernetesMetaConfiguration) DeepCopyInto(out *KubernetesMetaConfigura } } out.PodEnvironmentConfigMap = in.PodEnvironmentConfigMap + if in.MasterPodMoveTimeout != nil { + in, out := &in.MasterPodMoveTimeout, &out.MasterPodMoveTimeout + *out = new(metav1.Duration) + **out = **in + } if in.PersistentVolumeClaimRetentionPolicy != nil { in, out := &in.PersistentVolumeClaimRetentionPolicy, &out.PersistentVolumeClaimRetentionPolicy *out = make(map[string]string, len(*in)) @@ -433,6 +443,16 @@ func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData *out = make([]string, len(*in)) copy(*out, *in) } + if in.ResyncPeriod != nil { + in, out := &in.ResyncPeriod, &out.ResyncPeriod + *out = new(metav1.Duration) + **out = **in + } + if in.RepairPeriod != nil { + in, out := &in.RepairPeriod, &out.RepairPeriod + *out = new(metav1.Duration) + **out = **in + } if in.EnableMaintenanceWindows != nil { in, out := &in.EnableMaintenanceWindows, &out.EnableMaintenanceWindows *out = new(bool) @@ -466,7 +486,7 @@ func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData in.MajorVersionUpgrade.DeepCopyInto(&out.MajorVersionUpgrade) in.Kubernetes.DeepCopyInto(&out.Kubernetes) out.PostgresPodResources = in.PostgresPodResources - out.Timeouts = in.Timeouts + in.Timeouts.DeepCopyInto(&out.Timeouts) in.LoadBalancer.DeepCopyInto(&out.LoadBalancer) out.AWSGCP = in.AWSGCP in.OperatorDebug.DeepCopyInto(&out.OperatorDebug) @@ -582,6 +602,46 @@ func (in *OperatorLogicalBackupConfiguration) DeepCopy() *OperatorLogicalBackupC // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OperatorTimeouts) DeepCopyInto(out *OperatorTimeouts) { *out = *in + if in.ResourceCheckInterval != nil { + in, out := &in.ResourceCheckInterval, &out.ResourceCheckInterval + *out = new(metav1.Duration) + **out = **in + } + if in.ResourceCheckTimeout != nil { + in, out := &in.ResourceCheckTimeout, &out.ResourceCheckTimeout + *out = new(metav1.Duration) + **out = **in + } + if in.PodLabelWaitTimeout != nil { + in, out := &in.PodLabelWaitTimeout, &out.PodLabelWaitTimeout + *out = new(metav1.Duration) + **out = **in + } + if in.PodDeletionWaitTimeout != nil { + in, out := &in.PodDeletionWaitTimeout, &out.PodDeletionWaitTimeout + *out = new(metav1.Duration) + **out = **in + } + if in.ReadyWaitInterval != nil { + in, out := &in.ReadyWaitInterval, &out.ReadyWaitInterval + *out = new(metav1.Duration) + **out = **in + } + if in.ReadyWaitTimeout != nil { + in, out := &in.ReadyWaitTimeout, &out.ReadyWaitTimeout + *out = new(metav1.Duration) + **out = **in + } + if in.PatroniAPICheckInterval != nil { + in, out := &in.PatroniAPICheckInterval, &out.PatroniAPICheckInterval + *out = new(metav1.Duration) + **out = **in + } + if in.PatroniAPICheckTimeout != nil { + in, out := &in.PatroniAPICheckTimeout, &out.PatroniAPICheckTimeout + *out = new(metav1.Duration) + **out = **in + } return } diff --git a/pkg/cluster/cluster_test.go b/pkg/cluster/cluster_test.go index 95a445ff3..00e69d4d7 100644 --- a/pkg/cluster/cluster_test.go +++ b/pkg/cluster/cluster_test.go @@ -138,7 +138,8 @@ func TestCreate(t *testing.T) { var cluster = New( Config{ OpConfig: config.Config{ - PodManagementPolicy: "ordered_ready", + PodManagementPolicy: "ordered_ready", + PodTerminateGracePeriod: &metav1.Duration{Duration: 600 * time.Second}, Resources: config.Resources{ ClusterLabels: map[string]string{"application": "spilo"}, ClusterNameLabel: "cluster-name", @@ -147,8 +148,8 @@ func TestCreate(t *testing.T) { DefaultMemoryRequest: "300Mi", DefaultMemoryLimit: "300Mi", PodRoleLabel: "spilo-role", - ResourceCheckInterval: time.Duration(3), - ResourceCheckTimeout: time.Duration(10), + ResourceCheckInterval: &metav1.Duration{Duration: 3 * time.Second}, + ResourceCheckTimeout: &metav1.Duration{Duration: 10 * time.Minute}, }, EnableFinalizers: util.True(), }, diff --git a/pkg/cluster/connection_pooler.go b/pkg/cluster/connection_pooler.go index 85685774d..61cd9b041 100644 --- a/pkg/cluster/connection_pooler.go +++ b/pkg/cluster/connection_pooler.go @@ -292,7 +292,7 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) ( if connectionPoolerSpec == nil { connectionPoolerSpec = &acidv1.ConnectionPooler{} } - gracePeriod := int64(c.OpConfig.PodTerminateGracePeriod.Seconds()) + gracePeriod := int64(util.CoalesceDuration(c.OpConfig.PodTerminateGracePeriod, "5m").Seconds()) resources, err := c.generateResourceRequirements( connectionPoolerSpec.Resources, makeDefaultConnectionPoolerResources(&c.OpConfig), diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 306185e90..2b05e2c4a 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -1165,7 +1165,7 @@ func (c *Cluster) getPodEnvironmentSecretVariables() ([]v1.EnvVar, error) { secret := &v1.Secret{} var notFoundErr error - err := retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout, + err := retryutil.Retry(c.OpConfig.ResourceCheckInterval.Duration, c.OpConfig.ResourceCheckTimeout.Duration, func() (bool, error) { var err error secret, err = c.KubeClient.Secrets(c.Namespace).Get( @@ -1498,7 +1498,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef &tolerationSpec, c.nodeAffinity(c.OpConfig.NodeReadinessLabel, spec.NodeAffinity), spec.SchedulerName, - int64(c.OpConfig.PodTerminateGracePeriod.Seconds()), + int64(util.CoalesceDuration(c.OpConfig.PodTerminateGracePeriod, "5m").Seconds()), c.OpConfig.PodServiceAccountName, c.OpConfig.KubeIAMRole, effectivePodPriorityClassName, @@ -2431,7 +2431,7 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1.CronJob, error) { &tolerationsSpec, c.nodeAffinity(c.OpConfig.NodeReadinessLabel, nil), nil, - int64(c.OpConfig.PodTerminateGracePeriod.Seconds()), + int64(util.CoalesceDuration(c.OpConfig.PodTerminateGracePeriod, "5m").Seconds()), c.OpConfig.PodServiceAccountName, c.OpConfig.KubeIAMRole, "", diff --git a/pkg/cluster/k8sres_test.go b/pkg/cluster/k8sres_test.go index 8cbecb2fa..3f385b456 100644 --- a/pkg/cluster/k8sres_test.go +++ b/pkg/cluster/k8sres_test.go @@ -6,7 +6,6 @@ import ( "reflect" "sort" "testing" - "time" "github.com/stretchr/testify/assert" @@ -361,8 +360,8 @@ func TestPodEnvironmentSecretVariables(t *testing.T) { opConfig: config.Config{ Resources: config.Resources{ PodEnvironmentSecret: testPodEnvironmentObjectNotExists, - ResourceCheckInterval: time.Duration(testResourceCheckInterval), - ResourceCheckTimeout: time.Duration(testResourceCheckTimeout), + ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval}, + ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout}, }, }, err: fmt.Errorf("could not read Secret PodEnvironmentSecretName: still failing after %d retries: secret.core %q not found", maxRetries, testPodEnvironmentObjectNotExists), @@ -372,8 +371,8 @@ func TestPodEnvironmentSecretVariables(t *testing.T) { opConfig: config.Config{ Resources: config.Resources{ PodEnvironmentSecret: testPodEnvironmentSecretNameAPIError, - ResourceCheckInterval: time.Duration(testResourceCheckInterval), - ResourceCheckTimeout: time.Duration(testResourceCheckTimeout), + ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval}, + ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout}, }, }, err: fmt.Errorf("could not read Secret PodEnvironmentSecretName: Secret PodEnvironmentSecret API error"), @@ -383,8 +382,8 @@ func TestPodEnvironmentSecretVariables(t *testing.T) { opConfig: config.Config{ Resources: config.Resources{ PodEnvironmentSecret: testPodEnvironmentSecretName, - ResourceCheckInterval: time.Duration(testResourceCheckInterval), - ResourceCheckTimeout: time.Duration(testResourceCheckTimeout), + ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval}, + ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout}, }, }, envVars: []v1.EnvVar{ @@ -848,8 +847,8 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) { Name: testPodEnvironmentConfigMapName, }, PodEnvironmentSecret: testPodEnvironmentSecretName, - ResourceCheckInterval: time.Duration(testResourceCheckInterval), - ResourceCheckTimeout: time.Duration(testResourceCheckTimeout), + ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval}, + ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout}, }, }, cloneDescription: &acidv1.CloneDescription{}, @@ -877,8 +876,8 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) { Name: testPodEnvironmentConfigMapName, }, PodEnvironmentSecret: testPodEnvironmentSecretName, - ResourceCheckInterval: time.Duration(testResourceCheckInterval), - ResourceCheckTimeout: time.Duration(testResourceCheckTimeout), + ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval}, + ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout}, }, }, cloneDescription: &acidv1.CloneDescription{}, @@ -968,8 +967,8 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) { opConfig: config.Config{ Resources: config.Resources{ PodEnvironmentSecret: testPodEnvironmentSecretName, - ResourceCheckInterval: time.Duration(testResourceCheckInterval), - ResourceCheckTimeout: time.Duration(testResourceCheckTimeout), + ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval}, + ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout}, }, WALES3Bucket: "global-s3-bucket", }, @@ -986,8 +985,8 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) { opConfig: config.Config{ Resources: config.Resources{ PodEnvironmentSecret: testPodEnvironmentSecretName, - ResourceCheckInterval: time.Duration(testResourceCheckInterval), - ResourceCheckTimeout: time.Duration(testResourceCheckTimeout), + ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval}, + ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout}, }, WALES3Bucket: "global-s3-bucket", }, diff --git a/pkg/cluster/pod.go b/pkg/cluster/pod.go index 6658ba414..c18054aad 100644 --- a/pkg/cluster/pod.go +++ b/pkg/cluster/pod.go @@ -334,7 +334,7 @@ func (c *Cluster) getPatroniConfig(pod *v1.Pod) (acidv1.Patroni, map[string]stri pgParameters map[string]string ) podName := util.NameFromMeta(pod.ObjectMeta) - err := retryutil.Retry(c.OpConfig.PatroniAPICheckInterval, c.OpConfig.PatroniAPICheckTimeout, + err := retryutil.Retry(c.OpConfig.PatroniAPICheckInterval.Duration, c.OpConfig.PatroniAPICheckTimeout.Duration, func() (bool, error) { var err error patroniConfig, pgParameters, err = c.patroni.GetConfig(pod) @@ -355,7 +355,7 @@ func (c *Cluster) getPatroniConfig(pod *v1.Pod) (acidv1.Patroni, map[string]stri func (c *Cluster) getPatroniMemberData(pod *v1.Pod) (patroni.MemberData, error) { var memberData patroni.MemberData - err := retryutil.Retry(c.OpConfig.PatroniAPICheckInterval, c.OpConfig.PatroniAPICheckTimeout, + err := retryutil.Retry(c.OpConfig.PatroniAPICheckInterval.Duration, c.OpConfig.PatroniAPICheckTimeout.Duration, func() (bool, error) { var err error memberData, err = c.patroni.GetMemberData(pod) @@ -506,7 +506,7 @@ func (c *Cluster) getSwitchoverCandidate(master *v1.Pod) (spec.NamespacedName, e candidates := make([]patroni.ClusterMember, 0) syncCandidates := make([]patroni.ClusterMember, 0) - err := retryutil.Retry(c.OpConfig.PatroniAPICheckInterval, c.OpConfig.PatroniAPICheckTimeout, + err := retryutil.Retry(c.OpConfig.PatroniAPICheckInterval.Duration, c.OpConfig.PatroniAPICheckTimeout.Duration, func() (bool, error) { var err error members, err = c.patroni.GetClusterMembers(master) diff --git a/pkg/cluster/pod_test.go b/pkg/cluster/pod_test.go index 6ab3f9207..0eb1791e2 100644 --- a/pkg/cluster/pod_test.go +++ b/pkg/cluster/pod_test.go @@ -16,6 +16,7 @@ import ( "github.com/zalando/postgres-operator/pkg/util/k8sutil" "github.com/zalando/postgres-operator/pkg/util/patroni" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestGetSwitchoverCandidate(t *testing.T) { @@ -28,8 +29,8 @@ func TestGetSwitchoverCandidate(t *testing.T) { var cluster = New( Config{ OpConfig: config.Config{ - PatroniAPICheckInterval: time.Duration(1), - PatroniAPICheckTimeout: time.Duration(5), + PatroniAPICheckInterval: &metav1.Duration{Duration: 1 * time.Second}, + PatroniAPICheckTimeout: &metav1.Duration{Duration: 5 * time.Second}, }, }, k8sutil.KubernetesClient{}, acidv1.Postgresql{}, logger, eventRecorder) diff --git a/pkg/cluster/resources.go b/pkg/cluster/resources.go index 1fdad3e5e..6053de471 100644 --- a/pkg/cluster/resources.go +++ b/pkg/cluster/resources.go @@ -234,7 +234,7 @@ func (c *Cluster) replaceStatefulSet(newStatefulSet *appsv1.StatefulSet) error { // wait until the statefulset is truly deleted c.logger.Debug("waiting for the statefulset to be deleted") - err = retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout, + err = retryutil.Retry(c.OpConfig.ResourceCheckInterval.Duration, c.OpConfig.ResourceCheckTimeout.Duration, func() (bool, error) { _, err2 := c.KubeClient.StatefulSets(oldStatefulset.Namespace).Get(context.TODO(), oldStatefulset.Name, metav1.GetOptions{}) if err2 == nil { @@ -550,7 +550,7 @@ func (c *Cluster) deletePrimaryPodDisruptionBudget() error { c.logger.Infof("pod disruption budget %q has been deleted", util.NameFromMeta(c.PrimaryPodDisruptionBudget.ObjectMeta)) c.PrimaryPodDisruptionBudget = nil - err = retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout, + err = retryutil.Retry(c.OpConfig.ResourceCheckInterval.Duration, c.OpConfig.ResourceCheckTimeout.Duration, func() (bool, error) { _, err2 := c.KubeClient.PodDisruptionBudgets(pdbName.Namespace).Get(context.TODO(), pdbName.Name, metav1.GetOptions{}) if err2 == nil { @@ -588,7 +588,7 @@ func (c *Cluster) deleteCriticalOpPodDisruptionBudget() error { c.logger.Infof("pod disruption budget %q has been deleted", util.NameFromMeta(c.CriticalOpPodDisruptionBudget.ObjectMeta)) c.CriticalOpPodDisruptionBudget = nil - err = retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout, + err = retryutil.Retry(c.OpConfig.ResourceCheckInterval.Duration, c.OpConfig.ResourceCheckTimeout.Duration, func() (bool, error) { _, err2 := c.KubeClient.PodDisruptionBudgets(pdbName.Namespace).Get(context.TODO(), pdbName.Name, metav1.GetOptions{}) if err2 == nil { diff --git a/pkg/cluster/sync_test.go b/pkg/cluster/sync_test.go index f7d46d427..1d21d1536 100644 --- a/pkg/cluster/sync_test.go +++ b/pkg/cluster/sync_test.go @@ -101,8 +101,8 @@ func TestSyncStatefulSetsAnnotations(t *testing.T) { DefaultMemoryLimit: "300Mi", InheritedAnnotations: []string{inheritedAnnotation}, PodRoleLabel: "spilo-role", - ResourceCheckInterval: time.Duration(3), - ResourceCheckTimeout: time.Duration(10), + ResourceCheckInterval: &metav1.Duration{Duration: 3 * time.Second}, + ResourceCheckTimeout: &metav1.Duration{Duration: 10 * time.Minute}, }, }, }, client, pg, logger, eventRecorder) @@ -187,8 +187,8 @@ func TestPodAnnotationsSync(t *testing.T) { var cluster = New( Config{ OpConfig: config.Config{ - PatroniAPICheckInterval: time.Duration(1), - PatroniAPICheckTimeout: time.Duration(5), + PatroniAPICheckInterval: &metav1.Duration{Duration: 1 * time.Second}, + PatroniAPICheckTimeout: &metav1.Duration{Duration: 5 * time.Second}, PodManagementPolicy: "ordered_ready", CustomPodAnnotations: customPodAnnotations, ConnectionPooler: config.ConnectionPooler{ @@ -207,8 +207,8 @@ func TestPodAnnotationsSync(t *testing.T) { DefaultMemoryLimit: "300Mi", MaxInstances: -1, PodRoleLabel: "spilo-role", - ResourceCheckInterval: time.Duration(3), - ResourceCheckTimeout: time.Duration(10), + ResourceCheckInterval: &metav1.Duration{Duration: 3 * time.Second}, + ResourceCheckTimeout: &metav1.Duration{Duration: 10 * time.Minute}, }, }, }, client, pg, logger, eventRecorder) @@ -381,8 +381,8 @@ func TestCheckAndSetGlobalPostgreSQLConfiguration(t *testing.T) { DefaultMemoryRequest: "300Mi", DefaultMemoryLimit: "300Mi", PodRoleLabel: "spilo-role", - ResourceCheckInterval: time.Duration(3), - ResourceCheckTimeout: time.Duration(10), + ResourceCheckInterval: &metav1.Duration{Duration: 3 * time.Second}, + ResourceCheckTimeout: &metav1.Duration{Duration: 10 * time.Minute}, }, }, }, client, pg, logger, eventRecorder) @@ -694,8 +694,8 @@ func TestSyncStandbyClusterConfiguration(t *testing.T) { var cluster = New( Config{ OpConfig: config.Config{ - PatroniAPICheckInterval: time.Duration(1), - PatroniAPICheckTimeout: time.Duration(5), + PatroniAPICheckInterval: &metav1.Duration{Duration: 1 * time.Second}, + PatroniAPICheckTimeout: &metav1.Duration{Duration: 5 * time.Second}, PodManagementPolicy: "ordered_ready", Resources: config.Resources{ ClusterLabels: map[string]string{"application": applicationLabel}, @@ -707,8 +707,8 @@ func TestSyncStandbyClusterConfiguration(t *testing.T) { MinInstances: int32(-1), MaxInstances: int32(-1), PodRoleLabel: "spilo-role", - ResourceCheckInterval: time.Duration(3), - ResourceCheckTimeout: time.Duration(10), + ResourceCheckInterval: &metav1.Duration{Duration: 3 * time.Second}, + ResourceCheckTimeout: &metav1.Duration{Duration: 10 * time.Minute}, }, }, }, client, pg, logger, eventRecorder) diff --git a/pkg/cluster/util.go b/pkg/cluster/util.go index cbcccd16e..c3a9dda31 100644 --- a/pkg/cluster/util.go +++ b/pkg/cluster/util.go @@ -342,7 +342,7 @@ func (c *Cluster) annotationsSet(annotations map[string]string) map[string]strin } func (c *Cluster) waitForPodLabel(podEvents chan PodEvent, stopCh chan struct{}, role *PostgresRole) (*v1.Pod, error) { - timeout := time.After(c.OpConfig.PodLabelWaitTimeout) + timeout := time.After(c.OpConfig.PodLabelWaitTimeout.Duration) for { select { case podEvent := <-podEvents: @@ -364,7 +364,7 @@ func (c *Cluster) waitForPodLabel(podEvents chan PodEvent, stopCh chan struct{}, } func (c *Cluster) waitForPodDeletion(podEvents chan PodEvent) error { - timeout := time.After(c.OpConfig.PodDeletionWaitTimeout) + timeout := time.After(c.OpConfig.PodDeletionWaitTimeout.Duration) for { select { case podEvent := <-podEvents: @@ -378,7 +378,7 @@ func (c *Cluster) waitForPodDeletion(podEvents chan PodEvent) error { } func (c *Cluster) waitStatefulsetReady() error { - return retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout, + return retryutil.Retry(c.OpConfig.ResourceCheckInterval.Duration, c.OpConfig.ResourceCheckTimeout.Duration, func() (bool, error) { listOptions := metav1.ListOptions{ LabelSelector: c.labelsSet(false).String(), @@ -428,7 +428,7 @@ func (c *Cluster) _waitPodLabelsReady(anyReplica bool) error { c.logger.Debug("Waiting for any replica pod to become ready") } - err := retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout, + err := retryutil.Retry(c.OpConfig.ResourceCheckInterval.Duration, c.OpConfig.ResourceCheckTimeout.Duration, func() (bool, error) { masterCount := 0 if !anyReplica { diff --git a/pkg/cluster/util_test.go b/pkg/cluster/util_test.go index 8413ca396..0239abf45 100644 --- a/pkg/cluster/util_test.go +++ b/pkg/cluster/util_test.go @@ -297,8 +297,8 @@ func newInheritedAnnotationsCluster(client k8sutil.KubernetesClient) (*Cluster, cluster := New( Config{ OpConfig: config.Config{ - PatroniAPICheckInterval: time.Duration(1), - PatroniAPICheckTimeout: time.Duration(5), + PatroniAPICheckInterval: &metav1.Duration{Duration: 1 * time.Second}, + PatroniAPICheckTimeout: &metav1.Duration{Duration: 5 * time.Second}, KubernetesUseConfigMaps: true, ConnectionPooler: config.ConnectionPooler{ ConnectionPoolerDefaultCPURequest: "100m", @@ -318,8 +318,8 @@ func newInheritedAnnotationsCluster(client k8sutil.KubernetesClient) (*Cluster, DefaultMemoryLimit: "300Mi", InheritedAnnotations: []string{"owned-by"}, PodRoleLabel: "spilo-role", - ResourceCheckInterval: time.Duration(testResourceCheckInterval), - ResourceCheckTimeout: time.Duration(testResourceCheckTimeout), + ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval}, + ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout}, MinInstances: -1, MaxInstances: -1, }, diff --git a/pkg/controller/node.go b/pkg/controller/node.go index 730a9133b..978fda130 100644 --- a/pkg/controller/node.go +++ b/pkg/controller/node.go @@ -152,7 +152,7 @@ func (c *Controller) nodeDelete(obj interface{}) { func (c *Controller) moveMasterPodsOffNode(node *v1.Node) { // retry to move master until configured timeout is reached - err := retryutil.Retry(1*time.Minute, c.opConfig.MasterPodMoveTimeout, + err := retryutil.Retry(1*time.Minute, c.opConfig.MasterPodMoveTimeout.Duration, func() (bool, error) { err := c.attemptToMoveMasterPodsOffNode(node) if err != nil { diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index 94ef86d7c..55f1f00d8 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -4,8 +4,6 @@ import ( "context" "fmt" - "time" - acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" "github.com/zalando/postgres-operator/pkg/util" "github.com/zalando/postgres-operator/pkg/util/config" @@ -44,8 +42,8 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.MaxInstances = fromCRD.MaxInstances result.IgnoreInstanceLimitsAnnotationKey = fromCRD.IgnoreInstanceLimitsAnnotationKey result.IgnoreResourcesLimitsAnnotationKey = fromCRD.IgnoreResourcesLimitsAnnotationKey - result.ResyncPeriod = util.CoalesceDuration(time.Duration(fromCRD.ResyncPeriod), "30m") - result.RepairPeriod = util.CoalesceDuration(time.Duration(fromCRD.RepairPeriod), "5m") + result.ResyncPeriod = util.CoalesceDuration(fromCRD.ResyncPeriod, "30m") + result.RepairPeriod = util.CoalesceDuration(fromCRD.RepairPeriod, "5m") result.SetMemoryRequestToLimit = fromCRD.SetMemoryRequestToLimit result.ShmVolume = util.CoalesceBool(fromCRD.ShmVolume, util.True()) result.SidecarImages = fromCRD.SidecarImages @@ -75,7 +73,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.PodServiceAccountRoleBindingDefinition = fromCRD.Kubernetes.PodServiceAccountRoleBindingDefinition result.PodEnvironmentConfigMap = fromCRD.Kubernetes.PodEnvironmentConfigMap result.PodEnvironmentSecret = fromCRD.Kubernetes.PodEnvironmentSecret - result.PodTerminateGracePeriod = util.CoalesceDuration(time.Duration(fromCRD.Kubernetes.PodTerminateGracePeriod), "5m") + result.PodTerminateGracePeriod = util.CoalesceDuration(fromCRD.Kubernetes.PodTerminateGracePeriod, "5m") result.LivenessProbe = fromCRD.Kubernetes.LivenessProbe result.SpiloPrivileged = fromCRD.Kubernetes.SpiloPrivileged result.SpiloAllowPrivilegeEscalation = util.CoalesceBool(fromCRD.Kubernetes.SpiloAllowPrivilegeEscalation, util.True()) @@ -129,7 +127,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.EnableSecretsDeletion = util.CoalesceBool(fromCRD.Kubernetes.EnableSecretsDeletion, util.True()) result.EnablePersistentVolumeClaimDeletion = util.CoalesceBool(fromCRD.Kubernetes.EnablePersistentVolumeClaimDeletion, util.True()) result.EnableReadinessProbe = fromCRD.Kubernetes.EnableReadinessProbe - result.MasterPodMoveTimeout = util.CoalesceDuration(time.Duration(fromCRD.Kubernetes.MasterPodMoveTimeout), "10m") + result.MasterPodMoveTimeout = util.CoalesceDuration(fromCRD.Kubernetes.MasterPodMoveTimeout, "10m") result.EnablePodAntiAffinity = fromCRD.Kubernetes.EnablePodAntiAffinity result.PodAntiAffinityTopologyKey = util.Coalesce(fromCRD.Kubernetes.PodAntiAffinityTopologyKey, "kubernetes.io/hostname") result.PodAntiAffinityPreferredDuringScheduling = fromCRD.Kubernetes.PodAntiAffinityPreferredDuringScheduling @@ -146,14 +144,14 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.MaxMemoryRequest = fromCRD.PostgresPodResources.MaxMemoryRequest // timeout config - result.ResourceCheckInterval = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.ResourceCheckInterval), "3s") - result.ResourceCheckTimeout = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.ResourceCheckTimeout), "10m") - result.PodLabelWaitTimeout = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.PodLabelWaitTimeout), "10m") - result.PodDeletionWaitTimeout = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.PodDeletionWaitTimeout), "10m") - result.ReadyWaitInterval = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.ReadyWaitInterval), "4s") - result.ReadyWaitTimeout = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.ReadyWaitTimeout), "30s") - result.PatroniAPICheckInterval = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.PatroniAPICheckInterval), "1s") - result.PatroniAPICheckTimeout = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.PatroniAPICheckTimeout), "5s") + result.ResourceCheckInterval = util.CoalesceDuration(fromCRD.Timeouts.ResourceCheckInterval, "3s") + result.ResourceCheckTimeout = util.CoalesceDuration(fromCRD.Timeouts.ResourceCheckTimeout, "10m") + result.PodLabelWaitTimeout = util.CoalesceDuration(fromCRD.Timeouts.PodLabelWaitTimeout, "10m") + result.PodDeletionWaitTimeout = util.CoalesceDuration(fromCRD.Timeouts.PodDeletionWaitTimeout, "10m") + result.ReadyWaitInterval = util.CoalesceDuration(fromCRD.Timeouts.ReadyWaitInterval, "4s") + result.ReadyWaitTimeout = util.CoalesceDuration(fromCRD.Timeouts.ReadyWaitTimeout, "30s") + result.PatroniAPICheckInterval = util.CoalesceDuration(fromCRD.Timeouts.PatroniAPICheckInterval, "1s") + result.PatroniAPICheckTimeout = util.CoalesceDuration(fromCRD.Timeouts.PatroniAPICheckTimeout, "5s") // load balancer config result.DbHostedZone = util.Coalesce(fromCRD.LoadBalancer.DbHostedZone, "db.example.com") diff --git a/pkg/controller/postgresql.go b/pkg/controller/postgresql.go index 0933f7823..942ff9951 100644 --- a/pkg/controller/postgresql.go +++ b/pkg/controller/postgresql.go @@ -27,7 +27,7 @@ import ( func (c *Controller) clusterResync(stopCh <-chan struct{}, wg *sync.WaitGroup) { defer wg.Done() - ticker := time.NewTicker(c.opConfig.ResyncPeriod) + ticker := time.NewTicker(c.opConfig.ResyncPeriod.Duration) for { select { diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 43fa37a33..85119b18b 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -3,33 +3,33 @@ package config import ( "encoding/json" "strings" - "time" "fmt" "github.com/zalando/postgres-operator/pkg/spec" "github.com/zalando/postgres-operator/pkg/util/constants" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // CRD describes CustomResourceDefinition specific configuration parameters type CRD struct { - ReadyWaitInterval time.Duration `name:"ready_wait_interval" default:"4s"` - ReadyWaitTimeout time.Duration `name:"ready_wait_timeout" default:"30s"` - ResyncPeriod time.Duration `name:"resync_period" default:"30m"` - RepairPeriod time.Duration `name:"repair_period" default:"5m"` - EnableCRDRegistration *bool `name:"enable_crd_registration" default:"true"` - CRDCategories []string `name:"crd_categories" default:"all"` + ReadyWaitInterval *metav1.Duration `name:"ready_wait_interval" default:"4s"` + ReadyWaitTimeout *metav1.Duration `name:"ready_wait_timeout" default:"30s"` + ResyncPeriod *metav1.Duration `name:"resync_period" default:"30m"` + RepairPeriod *metav1.Duration `name:"repair_period" default:"5m"` + EnableCRDRegistration *bool `name:"enable_crd_registration" default:"true"` + CRDCategories []string `name:"crd_categories" default:"all"` } // Resources describes kubernetes resource specific configuration parameters type Resources struct { EnableOwnerReferences *bool `name:"enable_owner_references" default:"false"` - ResourceCheckInterval time.Duration `name:"resource_check_interval" default:"3s"` - ResourceCheckTimeout time.Duration `name:"resource_check_timeout" default:"10m"` - PodLabelWaitTimeout time.Duration `name:"pod_label_wait_timeout" default:"10m"` - PodDeletionWaitTimeout time.Duration `name:"pod_deletion_wait_timeout" default:"10m"` - PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"` + ResourceCheckInterval *metav1.Duration `name:"resource_check_interval" default:"3s"` + ResourceCheckTimeout *metav1.Duration `name:"resource_check_timeout" default:"10m"` + PodLabelWaitTimeout *metav1.Duration `name:"pod_label_wait_timeout" default:"10m"` + PodDeletionWaitTimeout *metav1.Duration `name:"pod_deletion_wait_timeout" default:"10m"` + PodTerminateGracePeriod *metav1.Duration `name:"pod_terminate_grace_period" default:"5m"` LivenessProbe *v1.Probe `name:"-"` SpiloRunAsUser *int64 `name:"spilo_runasuser"` SpiloRunAsGroup *int64 `name:"spilo_runasgroup"` @@ -188,7 +188,7 @@ type Config struct { // value of this string must be valid JSON or YAML; see initPodServiceAccount PodServiceAccountDefinition string `name:"pod_service_account_definition" default:""` PodServiceAccountRoleBindingDefinition string `name:"pod_service_account_role_binding_definition" default:""` - MasterPodMoveTimeout time.Duration `name:"master_pod_move_timeout" default:"20m"` + MasterPodMoveTimeout *metav1.Duration `name:"master_pod_move_timeout" default:"20m"` DbHostedZone string `name:"db_hosted_zone" default:"db.example.com"` AWSRegion string `name:"aws_region" default:"eu-central-1"` WALES3Bucket string `name:"wal_s3_bucket"` @@ -242,7 +242,7 @@ type Config struct { RingLogLines int `name:"ring_log_lines" default:"100"` ClusterHistoryEntries int `name:"cluster_history_entries" default:"1000"` TeamAPIRoleConfiguration map[string]string `name:"team_api_role_configuration" default:"log_statement:all"` - PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"` + PodTerminateGracePeriod *metav1.Duration `name:"pod_terminate_grace_period" default:"5m"` PodManagementPolicy string `name:"pod_management_policy" default:"ordered_ready"` EnableReadinessProbe bool `name:"enable_readiness_probe" default:"false"` ProtectedRoles []string `name:"protected_role_names" default:"admin,cron_admin"` @@ -258,8 +258,8 @@ type Config struct { MajorVersionUpgradeTeamAllowList []string `name:"major_version_upgrade_team_allow_list" default:""` MinimalMajorVersion string `name:"minimal_major_version" default:"14"` TargetMajorVersion string `name:"target_major_version" default:"18"` - PatroniAPICheckInterval time.Duration `name:"patroni_api_check_interval" default:"1s"` - PatroniAPICheckTimeout time.Duration `name:"patroni_api_check_timeout" default:"5s"` + PatroniAPICheckInterval *metav1.Duration `name:"patroni_api_check_interval" default:"1s"` + PatroniAPICheckTimeout *metav1.Duration `name:"patroni_api_check_timeout" default:"5s"` EnablePatroniFailsafeMode *bool `name:"enable_patroni_failsafe_mode" default:"false"` EnableSecretsDeletion *bool `name:"enable_secrets_deletion" default:"true"` EnablePersistentVolumeClaimDeletion *bool `name:"enable_persistent_volume_claim_deletion" default:"true"` diff --git a/pkg/util/config/config_test.go b/pkg/util/config/config_test.go index c5fd48d93..6373fdc9a 100644 --- a/pkg/util/config/config_test.go +++ b/pkg/util/config/config_test.go @@ -230,16 +230,16 @@ var newFromMapTests = []struct { { description: "duration parsing", input: map[string]string{ - "ready_wait_interval": "10s", - "ready_wait_timeout": "1m", + "patroni_api_check_interval": "1s", + "patroni_api_check_timeout": "5s", }, expectPanic: false, validateFunc: func(t *testing.T, cfg *Config) { - if cfg.ReadyWaitInterval.Seconds() != 10 { - t.Errorf("expected ReadyWaitInterval=10s, got %v", cfg.ReadyWaitInterval) + if cfg.PatroniAPICheckInterval.Seconds() != 1 { + t.Errorf("expected check interval of 1s, got %.0fs", cfg.PatroniAPICheckInterval.Seconds()) } - if cfg.ReadyWaitTimeout.Minutes() != 1 { - t.Errorf("expected ReadyWaitTimeout=1m, got %v", cfg.ReadyWaitTimeout) + if cfg.PatroniAPICheckTimeout.Seconds() != 5 { + t.Errorf("expected check timeout of 5s, got %.0fs", cfg.PatroniAPICheckTimeout.Seconds()) } }, }, diff --git a/pkg/util/config/util.go b/pkg/util/config/util.go index 4c1bdf7e0..ae1deb461 100644 --- a/pkg/util/config/util.go +++ b/pkg/util/config/util.go @@ -7,6 +7,8 @@ import ( "strconv" "strings" "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type decoder interface { @@ -101,13 +103,7 @@ func processField(value string, field reflect.Value) error { val int64 err error ) - if field.Kind() == reflect.Int64 && typ.PkgPath() == "time" && typ.Name() == "Duration" { - var d time.Duration - d, err = time.ParseDuration(value) - val = int64(d) - } else { - val, err = strconv.ParseInt(value, 0, typ.Bits()) - } + val, err = strconv.ParseInt(value, 0, typ.Bits()) if err != nil { return err } @@ -165,6 +161,15 @@ func processField(value string, field reflect.Value) error { mp.SetMapIndex(k, v) } field.Set(mp) + case reflect.Struct: + if typ.Name() == "Duration" { + var d time.Duration + d, err := time.ParseDuration(value) + if err != nil { + return err + } + field.Set(reflect.ValueOf(metav1.Duration{Duration: d})) + } } return nil diff --git a/pkg/util/util.go b/pkg/util/util.go index 993a773c4..79ff52282 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -311,14 +311,14 @@ func CoalesceBool(val, defaultVal *bool) *bool { return val } -// CoalesceDuration works like coalesce but for time.Duration -func CoalesceDuration(val time.Duration, defaultVal string) time.Duration { - if val == 0 { +// CoalesceDuration works like coalesce but for metav1.Duration +func CoalesceDuration(val *metav1.Duration, defaultVal string) *metav1.Duration { + if val == nil || val.Duration == 0 { duration, err := time.ParseDuration(defaultVal) if err != nil { panic(err) } - return duration + return &metav1.Duration{Duration: duration} } return val }