From 4e0692205b0e3221e210cbbf5e7aaa1433866855 Mon Sep 17 00:00:00 2001 From: Mikkel Oscar Lyderik Larsen Date: Thu, 4 Dec 2025 14:30:12 +0100 Subject: [PATCH] Generate postgresql CRD from go structs Signed-off-by: Mikkel Oscar Lyderik Larsen --- Makefile | 11 +- hack/adjust_postgresql_crd.sh | 22 + manifests/postgresql.crd.yaml | 493 +++++++++++++++---- pkg/apis/acid.zalan.do/v1/postgresql_type.go | 224 ++++++--- pkg/cluster/k8sres.go | 2 +- 5 files changed, 589 insertions(+), 163 deletions(-) create mode 100755 hack/adjust_postgresql_crd.sh diff --git a/Makefile b/Makefile index c0e2e8e34..3c0fe8f9c 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ GITSTATUS = $(shell git status --porcelain || echo "no changes") SOURCES = cmd/main.go VERSION ?= $(shell git describe --tags --always --dirty) CRD_SOURCES = $(shell find pkg/apis/zalando.org pkg/apis/acid.zalan.do -name '*.go' -not -name '*.deepcopy.go') -GENERATED_CRDS = manifests/postgresteam.crd.yaml +GENERATED_CRDS = manifests/postgresteam.crd.yaml manifests/postgresql.crd.yaml GENERATED = pkg/apis/zalando.org/v1/zz_generated.deepcopy.go pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go DIRS := cmd pkg PKG := `go list ./... | grep -v /vendor/` @@ -54,6 +54,7 @@ default: local clean: rm -rf build + rm $(GENERATED) verify: hack/verify-codegen.sh @@ -63,9 +64,13 @@ $(GENERATED): go.mod $(CRD_SOURCES) $(GENERATED_CRDS): $(GENERATED) go tool controller-gen crd:crdVersions=v1,allowDangerousTypes=true paths=./pkg/apis/acid.zalan.do/... output:crd:dir=manifests - # only generate postgresteam.crd.yaml for now + # only generate postgresteam.crd.yaml and postgresql.crd.yaml for now @rm manifests/acid.zalan.do_operatorconfigurations.yaml - @rm manifests/acid.zalan.do_postgresqls.yaml + @mv manifests/acid.zalan.do_postgresqls.yaml manifests/postgresql.crd.yaml + @# hack to use lowercase kind and listKind + @sed -i -e 's/kind: Postgresql/kind: postgresql/' manifests/postgresql.crd.yaml + @sed -i -e 's/listKind: PostgresqlList/listKind: postgresqlList/' manifests/postgresql.crd.yaml + @hack/adjust_postgresql_crd.sh @mv manifests/acid.zalan.do_postgresteams.yaml manifests/postgresteam.crd.yaml local: ${SOURCES} $(GENERATED_CRDS) diff --git a/hack/adjust_postgresql_crd.sh b/hack/adjust_postgresql_crd.sh new file mode 100755 index 000000000..cceb33f64 --- /dev/null +++ b/hack/adjust_postgresql_crd.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# Hack to adjust the generated postgresql CRD YAML file and add missing field +# settings which can not be expressed via kubebuilder markers. +# +# Injections: +# +# * oneOf: for the standby field to enforce that only one of s3_wal_path, gs_wal_path or standby_host is set. +# * This can later be done with // +kubebuilder:validation:ExactlyOneOf marker, but this requires latest Kubernetes version. (Currently the operator depends on v1.32.9) +# * type: string and pattern for the maintenanceWindows items. + +file="${1:-"manifests/postgresql.crd.yaml"}" + +sed -i '/^[[:space:]]*standby:$/{ + # Capture the indentation + s/^\([[:space:]]*\)standby:$/\1standby:\n\1 oneOf:\n\1 - required:\n\1 - s3_wal_path\n\1 - required:\n\1 - gs_wal_path\n\1 - required:\n\1 - standby_host/ +}' "$file" + +sed -i '/^[[:space:]]*maintenanceWindows:$/{ + # Capture the indentation + s/^\([[:space:]]*\)maintenanceWindows:$/\1maintenanceWindows:\n\1 items:\n\1 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))\\ *$'\''\n\1 type: string/ +}' "$file" diff --git a/manifests/postgresql.crd.yaml b/manifests/postgresql.crd.yaml index f8108d448..1bf8d1e80 100644 --- a/manifests/postgresql.crd.yaml +++ b/manifests/postgresql.crd.yaml @@ -1,6 +1,9 @@ +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 name: postgresqls.acid.zalan.do spec: group: acid.zalan.do @@ -40,7 +43,8 @@ spec: jsonPath: .spec.resources.requests.memory name: Memory-Request type: string - - jsonPath: .metadata.creationTimestamp + - description: Age of the PostgreSQL cluster + jsonPath: .metadata.creationTimestamp name: Age type: date - description: Current sync status of postgresql resource @@ -50,19 +54,33 @@ spec: name: v1 schema: openAPIV3Schema: + description: Postgresql defines PostgreSQL Custom Resource Definition Object. properties: apiVersion: - enum: - - acid.zalan.do/v1 + 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: - enum: - - postgresql + 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: PostgresSpec defines the specification for the PostgreSQL + TPR. properties: additionalVolumes: items: + description: AdditionalVolume specs additional optional volumes + for statefulset properties: isSubPathExpr: type: boolean @@ -87,12 +105,16 @@ spec: type: object type: array allowedSourceRanges: + description: load balancers' source ranges are the same for master + and replica services items: pattern: ^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])$ type: string nullable: true type: array clone: + description: CloneDescription describes which cluster the new should + clone and up to which point in time properties: cluster: type: string @@ -107,11 +129,11 @@ spec: s3_wal_path: type: string timestamp: - pattern: '^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([+-]([01][0-9]|2[0-3]):[0-5][0-9]))$' - # The regexp matches the date-time format (RFC 3339 Section 5.6) that specifies a timezone as an offset relative to UTC - # Example: 1996-12-19T16:39:57-08:00 - # Note: this field requires a timezone - + description: |- + The regexp matches the date-time format (RFC 3339 Section 5.6) that specifies a timezone as an offset relative to UTC + Example: 1996-12-19T16:39:57-08:00 + Note: this field requires a timezone + pattern: ^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([+-]([01][0-9]|2[0-3]):[0-5][0-9]))$ type: string uid: format: uuid @@ -120,37 +142,112 @@ spec: - cluster type: object connectionPooler: + description: |- + ConnectionPooler Options for connection pooler + + pgbouncer-large (with higher resources) or odyssey-small (with smaller + resources) + Type string `json:"type,omitempty"` + + makes sense to expose. E.g. pool size (min/max boundaries), max client + connections etc. properties: dockerImage: type: string maxDBConnections: + format: int32 type: integer mode: enum: - - "session" - - "transaction" + - session + - transaction type: string numberOfInstances: + format: int32 minimum: 1 type: integer resources: + description: Resources describes requests and limits for the cluster + resouces. properties: limits: + description: ResourceDescription describes CPU and memory + resources defined for a cluster. properties: cpu: - pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + description: |- + Decimal natural followed by m, or decimal natural followed by + dot followed by up to three decimal digits. + + This is because the Kubernetes CPU resource has millis as the + maximum precision. The actual values are checked in code + because the regular expression would be huge and horrible and + not very helpful in validation error messages; this one checks + only the format of the given number. + + https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu + + Note: the value specified here must not be zero or be lower + than the corresponding request. + pattern: ^(\d+m|\d+(\.\d{1,3})?)$ + type: string + hugepages-1Gi: + pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$ + type: string + hugepages-2Mi: + pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$ type: string memory: - pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + description: |- + You can express memory as a plain integer or as a fixed-point + integer using one of these suffixes: E, P, T, G, M, k. You can + also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki + + https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory + + Note: the value specified here must not be zero or be higher + than the corresponding limit. + pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$ type: string type: object requests: + description: ResourceDescription describes CPU and memory + resources defined for a cluster. properties: cpu: - pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + description: |- + Decimal natural followed by m, or decimal natural followed by + dot followed by up to three decimal digits. + + This is because the Kubernetes CPU resource has millis as the + maximum precision. The actual values are checked in code + because the regular expression would be huge and horrible and + not very helpful in validation error messages; this one checks + only the format of the given number. + + https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu + + Note: the value specified here must not be zero or be lower + than the corresponding request. + pattern: ^(\d+m|\d+(\.\d{1,3})?)$ + type: string + hugepages-1Gi: + pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$ + type: string + hugepages-2Mi: + pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$ type: string memory: - pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + description: |- + You can express memory as a plain integer or as a fixed-point + integer using one of these suffixes: E, P, T, G, M, k. You can + also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki + + https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory + + Note: the value specified here must not be zero or be higher + than the corresponding limit. + pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$ type: string type: object type: object @@ -162,7 +259,9 @@ spec: databases: additionalProperties: type: string - # Note: usernames specified here as database owners must be declared in the users key of the spec key. + description: |- + Note: usernames specified here as database owners must be declared + in the users key of the spec key. type: object dockerImage: type: string @@ -171,6 +270,9 @@ spec: enableLogicalBackup: type: boolean enableMasterLoadBalancer: + description: |- + vars that enable load balancers are pointers because it is important to know if any of them is omitted from the Postgres manifest + in that case the var evaluates to nil and the value is taken from the operator config type: boolean enableMasterPoolerLoadBalancer: type: boolean @@ -183,28 +285,19 @@ spec: enableShmVolume: type: boolean env: - items: - type: object - x-kubernetes-preserve-unknown-fields: true - nullable: true - type: array - initContainers: - items: - type: object - x-kubernetes-preserve-unknown-fields: true nullable: true type: array init_containers: - description: deprecated - items: - type: object - x-kubernetes-preserve-unknown-fields: true + description: deprecated json tags + type: object + x-kubernetes-preserve-unknown-fields: true + initContainers: nullable: true type: array logicalBackupRetention: type: string logicalBackupSchedule: - pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$' + pattern: ^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$ type: string maintenanceWindows: items: @@ -214,101 +307,214 @@ spec: masterServiceAnnotations: additionalProperties: type: string + description: MasterServiceAnnotations takes precedence over ServiceAnnotations + for master role if not empty type: object nodeAffinity: + description: Node affinity is a group of node affinity scheduling + rules. properties: preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). properties: preference: + description: A node selector term, associated with the corresponding + weight. properties: matchExpressions: + description: A list of node selector requirements by + node's labels. items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. properties: key: + description: The label key that the selector applies + to. type: string operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. type: string values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchFields: + description: A list of node selector requirements by + node's fields. items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. properties: key: + description: The label key that the selector applies + to. type: string operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. type: string values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic type: object + x-kubernetes-map-type: atomic weight: + description: Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 type: integer required: - preference - weight type: object type: array + x-kubernetes-list-type: atomic requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. properties: nodeSelectorTerms: + description: Required. A list of node selector terms. The + terms are ORed. items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. properties: matchExpressions: + description: A list of node selector requirements by + node's labels. items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. properties: key: + description: The label key that the selector applies + to. type: string operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. type: string values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchFields: + description: A list of node selector requirements by + node's fields. items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. properties: key: + description: The label key that the selector applies + to. type: string operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. type: string values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: atomic required: - nodeSelectorTerms type: object + x-kubernetes-map-type: atomic type: object numberOfInstances: + format: int32 minimum: 0 type: integer patroni: + description: Patroni contains Patroni-specific configuration properties: failsafe_mode: type: boolean @@ -317,14 +523,17 @@ spec: type: string type: object loop_wait: + format: int32 type: integer maximum_lag_on_failover: + format: int64 type: integer pg_hba: items: type: string type: array retry_timeout: + format: int32 type: integer slots: additionalProperties: @@ -337,20 +546,24 @@ spec: synchronous_mode_strict: type: boolean synchronous_node_count: + format: int32 type: integer ttl: + format: int32 type: integer type: object + pod_priority_class_name: + description: deprecated + type: string podAnnotations: additionalProperties: type: string type: object podPriorityClassName: type: string - pod_priority_class_name: - description: deprecated - type: string postgresql: + description: PostgresqlParam describes PostgreSQL version and pairs + of configuration parameter name - values. properties: parameters: additionalProperties: @@ -358,17 +571,18 @@ spec: type: object version: enum: - - "13" - - "14" - - "15" - - "16" - - "17" + - 13 + - 14 + - 15 + - 16 + - 17 type: string required: - version type: object preparedDatabases: additionalProperties: + description: PreparedDatabase describes elements to be bootstrapped properties: defaultUsers: type: boolean @@ -378,6 +592,8 @@ spec: type: object schemas: additionalProperties: + description: PreparedSchema describes elements to be bootstrapped + per schema properties: defaultRoles: type: boolean @@ -395,58 +611,91 @@ spec: replicaServiceAnnotations: additionalProperties: type: string + description: ReplicaServiceAnnotations takes precedence over ServiceAnnotations + for replica role if not empty type: object resources: + description: Resources describes requests and limits for the cluster + resouces. properties: limits: + description: ResourceDescription describes CPU and memory resources + defined for a cluster. properties: cpu: - # Decimal natural followed by m, or decimal natural followed by - # dot followed by up to three decimal digits. - # - # This is because the Kubernetes CPU resource has millis as the - # maximum precision. The actual values are checked in code - # because the regular expression would be huge and horrible and - # not very helpful in validation error messages; this one checks - # only the format of the given number. - # - # https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu - pattern: '^(\d+m|\d+(\.\d{1,3})?)$' - # Note: the value specified here must not be zero or be lower - # than the corresponding request. + description: |- + Decimal natural followed by m, or decimal natural followed by + dot followed by up to three decimal digits. + This is because the Kubernetes CPU resource has millis as the + maximum precision. The actual values are checked in code + because the regular expression would be huge and horrible and + not very helpful in validation error messages; this one checks + only the format of the given number. + + https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu + + Note: the value specified here must not be zero or be lower + than the corresponding request. + pattern: ^(\d+m|\d+(\.\d{1,3})?)$ type: string hugepages-1Gi: - pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$ type: string hugepages-2Mi: - pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$ type: string memory: - # You can express memory as a plain integer or as a fixed-point - # integer using one of these suffixes: E, P, T, G, M, k. You can - # also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki - # - # https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory - pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' - # Note: the value specified here must not be zero or be higher - # than the corresponding limit. + description: |- + You can express memory as a plain integer or as a fixed-point + integer using one of these suffixes: E, P, T, G, M, k. You can + also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki + https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory + + Note: the value specified here must not be zero or be higher + than the corresponding limit. + pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$ type: string type: object requests: + description: ResourceDescription describes CPU and memory resources + defined for a cluster. properties: cpu: - pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + description: |- + Decimal natural followed by m, or decimal natural followed by + dot followed by up to three decimal digits. + + This is because the Kubernetes CPU resource has millis as the + maximum precision. The actual values are checked in code + because the regular expression would be huge and horrible and + not very helpful in validation error messages; this one checks + only the format of the given number. + + https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu + + Note: the value specified here must not be zero or be lower + than the corresponding request. + pattern: ^(\d+m|\d+(\.\d{1,3})?)$ type: string hugepages-1Gi: - pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$ type: string hugepages-2Mi: - pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$ type: string memory: - pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + description: |- + You can express memory as a plain integer or as a fixed-point + integer using one of these suffixes: E, P, T, G, M, k. You can + also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki + + https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory + + Note: the value specified here must not be zero or be higher + than the corresponding limit. + pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$ type: string type: object type: object @@ -457,16 +706,16 @@ spec: type: string type: object sidecars: - items: - type: object - x-kubernetes-preserve-unknown-fields: true nullable: true type: array spiloFSGroup: + format: int64 type: integer spiloRunAsGroup: + format: int64 type: integer spiloRunAsUser: + format: int64 type: integer standby: oneOf: @@ -476,6 +725,8 @@ spec: - gs_wal_path - required: - standby_host + description: StandbyDescription contains remote primary config or + s3/gs wal path properties: gs_wal_path: type: string @@ -488,13 +739,16 @@ spec: type: object streams: items: + description: Stream defines properties for creating FabricEventStream + resources properties: applicationId: type: string batchSize: + format: int32 type: integer cpu: - pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + pattern: ^(\d+m|\d+(\.\d{1,3})?)$ type: string database: type: string @@ -505,10 +759,12 @@ spec: type: string type: object memory: - pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$ type: string tables: additionalProperties: + description: StreamTable defines properties of outbox tables + for FabricEventStreams properties: eventType: type: string @@ -533,6 +789,7 @@ spec: teamId: type: string tls: + description: TLSDescription specs TLS properties properties: caFile: type: string @@ -549,31 +806,51 @@ spec: type: object tolerations: items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . properties: effect: - enum: - - NoExecute - - NoSchedule - - PreferNoSchedule + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. type: string key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. type: string operator: - enum: - - Equal - - Exists + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. type: string tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 type: integer value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. type: string type: object type: array useLoadBalancer: - description: deprecated + description: |- + deprecated load balancer settings maintained for backward compatibility + see "Load balancers" operator docs type: boolean users: additionalProperties: + description: UserFlags defines flags (such as superuser, nologin) + that could be assigned to individual users items: enum: - bypassrls @@ -605,7 +882,6 @@ spec: - nosuperuser - NOSUPERUSER type: string - nullable: true type: array type: object usersIgnoringSecretRotation: @@ -624,65 +900,94 @@ spec: nullable: true type: array volume: + description: Volume describes a single volume in the manifest. properties: iops: + format: int64 type: integer isSubPathExpr: type: boolean selector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. properties: matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: key: + description: key is the label key that the selector + applies to. type: string operator: - enum: - - DoesNotExist - - Exists - - In - - NotIn + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - x-kubernetes-preserve-unknown-fields: true type: object + x-kubernetes-map-type: atomic size: - pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' - # Note: the value specified here must not be zero. - + pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$ type: string storageClass: type: string subPath: type: string throughput: + format: int64 type: integer + type: + type: string required: - size type: object required: - numberOfInstances - - teamId - postgresql + - teamId - volume type: object status: - additionalProperties: - type: string + description: PostgresStatus contains status of the PostgreSQL cluster + (running, creation failed etc.) + properties: + PostgresClusterStatus: + type: string + required: + - PostgresClusterStatus type: object required: - - kind - - apiVersion + - metadata - spec type: object served: true diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index ef6dfe7ff..8b14892e9 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -11,13 +11,25 @@ import ( // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:object:root=true // Postgresql defines PostgreSQL Custom Resource Definition Object. +// +kubebuilder:resource:categories=all,shortName=pg,scope=Namespaced +// +kubebuilder:printcolumn:name="Team",type=string,JSONPath=`.spec.teamId`,description="Team responsible for Postgres cluster" +// +kubebuilder:printcolumn:name="Version",type=string,JSONPath=`.spec.postgresql.version`,description="PostgreSQL version" +// +kubebuilder:printcolumn:name="Pods",type=integer,JSONPath=`.spec.numberOfInstances`,description="Number of Pods per Postgres cluster" +// +kubebuilder:printcolumn:name="Volume",type=string,JSONPath=`.spec.volume.size`,description="Size of the bound volume" +// +kubebuilder:printcolumn:name="CPU-Request",type=string,JSONPath=`.spec.resources.requests.cpu`,description="Requested CPU for Postgres containers" +// +kubebuilder:printcolumn:name="Memory-Request",type=string,JSONPath=`.spec.resources.requests.memory`,description="Requested memory for Postgres containers" +// +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 type Postgresql struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.ObjectMeta `json:"metadata"` - Spec PostgresSpec `json:"spec"` + Spec PostgresSpec `json:"spec"` + // +optional Status PostgresStatus `json:"status"` Error string `json:"-"` } @@ -25,9 +37,10 @@ type Postgresql struct { // PostgresSpec defines the specification for the PostgreSQL TPR. type PostgresSpec struct { PostgresqlParam `json:"postgresql"` - Volume `json:"volume,omitempty"` - Patroni `json:"patroni,omitempty"` - *Resources `json:"resources,omitempty"` + Volume `json:"volume"` + // +optional + Patroni `json:"patroni"` + *Resources `json:"resources,omitempty"` EnableConnectionPooler *bool `json:"enableConnectionPooler,omitempty"` EnableReplicaConnectionPooler *bool `json:"enableReplicaConnectionPooler,omitempty"` @@ -52,35 +65,59 @@ type PostgresSpec struct { // deprecated load balancer settings maintained for backward compatibility // see "Load balancers" operator docs - UseLoadBalancer *bool `json:"useLoadBalancer,omitempty"` + UseLoadBalancer *bool `json:"useLoadBalancer,omitempty"` + // deprecated ReplicaLoadBalancer *bool `json:"replicaLoadBalancer,omitempty"` // load balancers' source ranges are the same for master and replica services + // +nullable + // +kubebuilder:validation:items:Pattern=`^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])$` + // +optional AllowedSourceRanges []string `json:"allowedSourceRanges"` - Users map[string]UserFlags `json:"users,omitempty"` - UsersIgnoringSecretRotation []string `json:"usersIgnoringSecretRotation,omitempty"` - UsersWithSecretRotation []string `json:"usersWithSecretRotation,omitempty"` - UsersWithInPlaceSecretRotation []string `json:"usersWithInPlaceSecretRotation,omitempty"` + Users map[string]UserFlags `json:"users,omitempty"` + // +nullable + UsersIgnoringSecretRotation []string `json:"usersIgnoringSecretRotation,omitempty"` + // +nullable + UsersWithSecretRotation []string `json:"usersWithSecretRotation,omitempty"` + // +nullable + UsersWithInPlaceSecretRotation []string `json:"usersWithInPlaceSecretRotation,omitempty"` - NumberOfInstances int32 `json:"numberOfInstances"` - MaintenanceWindows []MaintenanceWindow `json:"maintenanceWindows,omitempty"` - Clone *CloneDescription `json:"clone,omitempty"` - Databases map[string]string `json:"databases,omitempty"` - PreparedDatabases map[string]PreparedDatabase `json:"preparedDatabases,omitempty"` - SchedulerName *string `json:"schedulerName,omitempty"` - NodeAffinity *v1.NodeAffinity `json:"nodeAffinity,omitempty"` - Tolerations []v1.Toleration `json:"tolerations,omitempty"` - Sidecars []Sidecar `json:"sidecars,omitempty"` - InitContainers []v1.Container `json:"initContainers,omitempty"` - PodPriorityClassName string `json:"podPriorityClassName,omitempty"` - ShmVolume *bool `json:"enableShmVolume,omitempty"` - EnableLogicalBackup bool `json:"enableLogicalBackup,omitempty"` - LogicalBackupRetention string `json:"logicalBackupRetention,omitempty"` - LogicalBackupSchedule string `json:"logicalBackupSchedule,omitempty"` - StandbyCluster *StandbyDescription `json:"standby,omitempty"` - PodAnnotations map[string]string `json:"podAnnotations,omitempty"` - ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"` + // +kubebuilder:validation:Minimum=0 + NumberOfInstances int32 `json:"numberOfInstances"` + // +kubebuilder:validation:Schemaless + // +kubebuilder:validation:Type=array + // +kubebuilde:validation:items:Type=string + MaintenanceWindows []MaintenanceWindow `json:"maintenanceWindows,omitempty"` + Clone *CloneDescription `json:"clone,omitempty"` + // Note: usernames specified here as database owners must be declared + // in the users key of the spec key. + Databases map[string]string `json:"databases,omitempty"` + PreparedDatabases map[string]PreparedDatabase `json:"preparedDatabases,omitempty"` + SchedulerName *string `json:"schedulerName,omitempty"` + NodeAffinity *v1.NodeAffinity `json:"nodeAffinity,omitempty"` + Tolerations []v1.Toleration `json:"tolerations,omitempty"` + // +kubebuilder:validation:Type=array + // +kubebuilder:validation:Items:Type=object + // +kubebuilder:validation:Items:XPreserveUnknownFields + // +kubebuilder:validation:Schemaless + // +nullable + Sidecars []Sidecar `json:"sidecars,omitempty"` + // +kubebuilder:validation:Type=array + // +kubebuilder:validation:Items:Type=object + // +kubebuilder:validation:Items:XPreserveUnknownFields + // +kubebuilder:validation:Schemaless + // +nullable + InitContainers []v1.Container `json:"initContainers,omitempty"` + PodPriorityClassName string `json:"podPriorityClassName,omitempty"` + ShmVolume *bool `json:"enableShmVolume,omitempty"` + EnableLogicalBackup bool `json:"enableLogicalBackup,omitempty"` + LogicalBackupRetention string `json:"logicalBackupRetention,omitempty"` + // +kubebuilder:validation:Pattern=`^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$` + LogicalBackupSchedule string `json:"logicalBackupSchedule,omitempty"` + StandbyCluster *StandbyDescription `json:"standby,omitempty"` + PodAnnotations map[string]string `json:"podAnnotations,omitempty"` + ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"` // MasterServiceAnnotations takes precedence over ServiceAnnotations for master role if not empty MasterServiceAnnotations map[string]string `json:"masterServiceAnnotations,omitempty"` // ReplicaServiceAnnotations takes precedence over ServiceAnnotations for replica role if not empty @@ -88,11 +125,20 @@ type PostgresSpec struct { TLS *TLSDescription `json:"tls,omitempty"` AdditionalVolumes []AdditionalVolume `json:"additionalVolumes,omitempty"` Streams []Stream `json:"streams,omitempty"` - Env []v1.EnvVar `json:"env,omitempty"` + // +kubebuilder:validation:Type=array + // +kubebuilder:validation:Items:Type=object + // +kubebuilder:validation:Items:XPreserveUnknownFields + // +kubebuilder:validation:Schemaless + // +nullable + Env []v1.EnvVar `json:"env,omitempty"` // deprecated json tags - InitContainersOld []v1.Container `json:"init_containers,omitempty"` - PodPriorityClassNameOld string `json:"pod_priority_class_name,omitempty"` + // +kubebuilder:validation:XPreserveUnknownFields + // +kubebuilder:validation:Type=object + // +kubebuilder:validation:Schemaless + InitContainersOld []v1.Container `json:"init_containers,omitempty"` + // deprecated + PodPriorityClassNameOld string `json:"pod_priority_class_name,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -123,50 +169,84 @@ type PreparedSchema struct { type MaintenanceWindow struct { Everyday bool `json:"everyday,omitempty"` Weekday time.Weekday `json:"weekday,omitempty"` - StartTime metav1.Time `json:"startTime,omitempty"` - EndTime metav1.Time `json:"endTime,omitempty"` + StartTime metav1.Time `json:"startTime"` + EndTime metav1.Time `json:"endTime"` } // Volume describes a single volume in the manifest. type Volume struct { - Selector *metav1.LabelSelector `json:"selector,omitempty"` - Size string `json:"size"` - StorageClass string `json:"storageClass,omitempty"` - SubPath string `json:"subPath,omitempty"` - IsSubPathExpr *bool `json:"isSubPathExpr,omitempty"` - Iops *int64 `json:"iops,omitempty"` - Throughput *int64 `json:"throughput,omitempty"` - VolumeType string `json:"type,omitempty"` + Selector *metav1.LabelSelector `json:"selector,omitempty"` + // +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$` + Size string `json:"size"` + StorageClass string `json:"storageClass,omitempty"` + SubPath string `json:"subPath,omitempty"` + IsSubPathExpr *bool `json:"isSubPathExpr,omitempty"` + Iops *int64 `json:"iops,omitempty"` + Throughput *int64 `json:"throughput,omitempty"` + VolumeType string `json:"type,omitempty"` } // AdditionalVolume specs additional optional volumes for statefulset type AdditionalVolume struct { - Name string `json:"name"` - MountPath string `json:"mountPath"` - SubPath string `json:"subPath,omitempty"` - IsSubPathExpr *bool `json:"isSubPathExpr,omitempty"` - TargetContainers []string `json:"targetContainers"` - VolumeSource v1.VolumeSource `json:"volumeSource"` + Name string `json:"name"` + MountPath string `json:"mountPath"` + SubPath string `json:"subPath,omitempty"` + IsSubPathExpr *bool `json:"isSubPathExpr,omitempty"` + // +nullable + // +optional + TargetContainers []string `json:"targetContainers"` + // +kubebuilder:validation:XPreserveUnknownFields + // +kubebuilder:validation:Type=object + // +kubebuilder:validation:Schemaless + VolumeSource v1.VolumeSource `json:"volumeSource"` } // PostgresqlParam describes PostgreSQL version and pairs of configuration parameter name - values. type PostgresqlParam struct { + // +kubebuilder:validation:Enum=13;14;15;16;17 PgVersion string `json:"version"` Parameters map[string]string `json:"parameters,omitempty"` } // ResourceDescription describes CPU and memory resources defined for a cluster. type ResourceDescription struct { - CPU *string `json:"cpu,omitempty"` - Memory *string `json:"memory,omitempty"` + // Decimal natural followed by m, or decimal natural followed by + // dot followed by up to three decimal digits. + // + // This is because the Kubernetes CPU resource has millis as the + // maximum precision. The actual values are checked in code + // because the regular expression would be huge and horrible and + // not very helpful in validation error messages; this one checks + // only the format of the given number. + // + // https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu + // + // Note: the value specified here must not be zero or be lower + // than the corresponding request. + // +kubebuilder:validation:Pattern=`^(\d+m|\d+(\.\d{1,3})?)$` + CPU *string `json:"cpu,omitempty"` + // You can express memory as a plain integer or as a fixed-point + // integer using one of these suffixes: E, P, T, G, M, k. You can + // also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki + // + // https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory + // + // Note: the value specified here must not be zero or be higher + // than the corresponding limit. + // +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$` + Memory *string `json:"memory,omitempty"` + // +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$` HugePages2Mi *string `json:"hugepages-2Mi,omitempty"` + // +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$` HugePages1Gi *string `json:"hugepages-1Gi,omitempty"` } // Resources describes requests and limits for the cluster resouces. type Resources struct { - ResourceRequests ResourceDescription `json:"requests,omitempty"` - ResourceLimits ResourceDescription `json:"limits,omitempty"` + // +optional + ResourceRequests ResourceDescription `json:"requests"` + // +optional + ResourceLimits ResourceDescription `json:"limits"` } // Patroni contains Patroni-specific configuration @@ -176,7 +256,7 @@ type Patroni struct { TTL uint32 `json:"ttl,omitempty"` LoopWait uint32 `json:"loop_wait,omitempty"` RetryTimeout uint32 `json:"retry_timeout,omitempty"` - MaximumLagOnFailover float32 `json:"maximum_lag_on_failover,omitempty"` // float32 because https://github.com/kubernetes/kubernetes/issues/30213 + MaximumLagOnFailover int64 `json:"maximum_lag_on_failover,omitempty"` Slots map[string]map[string]string `json:"slots,omitempty"` SynchronousMode bool `json:"synchronous_mode,omitempty"` SynchronousModeStrict bool `json:"synchronous_mode_strict,omitempty"` @@ -185,6 +265,7 @@ type Patroni struct { } // StandbyDescription contains remote primary config or s3/gs wal path +// +kubebuilder:validation:ExactlyOneOf=s3_wal_path;gs_wal_path;standby_host type StandbyDescription struct { S3WalPath string `json:"s3_wal_path,omitempty"` GSWalPath string `json:"gs_wal_path,omitempty"` @@ -194,6 +275,7 @@ type StandbyDescription struct { // TLSDescription specs TLS properties type TLSDescription struct { + // +required SecretName string `json:"secretName,omitempty"` CertificateFile string `json:"certificateFile,omitempty"` PrivateKeyFile string `json:"privateKeyFile,omitempty"` @@ -203,8 +285,14 @@ type TLSDescription struct { // CloneDescription describes which cluster the new should clone and up to which point in time type CloneDescription struct { - ClusterName string `json:"cluster,omitempty"` - UID string `json:"uid,omitempty"` + // +required + ClusterName string `json:"cluster,omitempty"` + // +kubebuilder:validation:Format=uuid + UID string `json:"uid,omitempty"` + // The regexp matches the date-time format (RFC 3339 Section 5.6) that specifies a timezone as an offset relative to UTC + // Example: 1996-12-19T16:39:57-08:00 + // Note: this field requires a timezone + // +kubebuilder:validation:Pattern=`^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([+-]([01][0-9]|2[0-3]):[0-5][0-9]))$` EndTimestamp string `json:"timestamp,omitempty"` S3WalPath string `json:"s3_wal_path,omitempty"` S3Endpoint string `json:"s3_endpoint,omitempty"` @@ -224,6 +312,8 @@ type Sidecar struct { } // UserFlags defines flags (such as superuser, nologin) that could be assigned to individual users +// +kubebuilder:validation:items:Enum=bypassrls;BYPASSRLS;nobypassrls;NOBYPASSRLS;createdb;CREATEDB;nocreatedb;NOCREATEDB;createrole;CREATEROLE;nocreaterole;NOCREATEROLE;inherit;INHERIT;noinherit;NOINHERIT;login;LOGIN;nologin;NOLOGIN;replication;REPLICATION;noreplication;NOREPLICATION;superuser;SUPERUSER;nosuperuser;NOSUPERUSER +// +nullable type UserFlags []string // PostgresStatus contains status of the PostgreSQL cluster (running, creation failed etc.) @@ -242,26 +332,30 @@ type PostgresStatus struct { // makes sense to expose. E.g. pool size (min/max boundaries), max client // connections etc. type ConnectionPooler struct { + // +kubebuilder:validation:Minimum=1 NumberOfInstances *int32 `json:"numberOfInstances,omitempty"` Schema string `json:"schema,omitempty"` User string `json:"user,omitempty"` - Mode string `json:"mode,omitempty"` - DockerImage string `json:"dockerImage,omitempty"` - MaxDBConnections *int32 `json:"maxDBConnections,omitempty"` + // +kubebuilder:validation:Enum=session;transaction + Mode string `json:"mode,omitempty"` + DockerImage string `json:"dockerImage,omitempty"` + MaxDBConnections *int32 `json:"maxDBConnections,omitempty"` *Resources `json:"resources,omitempty"` } // Stream defines properties for creating FabricEventStream resources type Stream struct { - ApplicationId string `json:"applicationId"` - Database string `json:"database"` - Tables map[string]StreamTable `json:"tables"` - Filter map[string]*string `json:"filter,omitempty"` - BatchSize *uint32 `json:"batchSize,omitempty"` - CPU *string `json:"cpu,omitempty"` - Memory *string `json:"memory,omitempty"` - EnableRecovery *bool `json:"enableRecovery,omitempty"` + ApplicationId string `json:"applicationId"` + Database string `json:"database"` + Tables map[string]StreamTable `json:"tables"` + Filter map[string]*string `json:"filter,omitempty"` + BatchSize *uint32 `json:"batchSize,omitempty"` + // +kubebuilder:validation:Pattern=`^(\d+m|\d+(\.\d{1,3})?)$` + CPU *string `json:"cpu,omitempty"` + // +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$` + Memory *string `json:"memory,omitempty"` + EnableRecovery *bool `json:"enableRecovery,omitempty"` } // StreamTable defines properties of outbox tables for FabricEventStreams diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 9bc39a9db..bb45ba31f 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -412,7 +412,7 @@ PatroniInitDBParams: } if patroni.MaximumLagOnFailover >= 0 { - config.Bootstrap.DCS.MaximumLagOnFailover = patroni.MaximumLagOnFailover + config.Bootstrap.DCS.MaximumLagOnFailover = float32(patroni.MaximumLagOnFailover) } if patroni.LoopWait != 0 { config.Bootstrap.DCS.LoopWait = patroni.LoopWait