diff --git a/Makefile b/Makefile index d21f770da5..05198b07ac 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,9 @@ PGMONITOR_DIR ?= hack/tools/pgmonitor PGMONITOR_VERSION ?= v4.11.0 QUERIES_CONFIG_DIR ?= hack/tools/queries +EXTERNAL_SNAPSHOTTER_DIR ?= hack/tools/external-snapshotter +EXTERNAL_SNAPSHOTTER_VERSION ?= v8.0.1 + # Buildah's "build" used to be "bud". Use the alias to be compatible for a while. BUILDAH_BUILD ?= buildah bud @@ -52,6 +55,12 @@ get-pgmonitor: cp -r '$(PGMONITOR_DIR)/postgres_exporter/common/.' '${QUERIES_CONFIG_DIR}' cp '$(PGMONITOR_DIR)/postgres_exporter/linux/queries_backrest.yml' '${QUERIES_CONFIG_DIR}' +.PHONY: get-external-snapshotter +get-external-snapshotter: + git -C '$(dir $(EXTERNAL_SNAPSHOTTER_DIR))' clone https://github.com/kubernetes-csi/external-snapshotter.git || git -C '$(EXTERNAL_SNAPSHOTTER_DIR)' fetch origin + @git -C '$(EXTERNAL_SNAPSHOTTER_DIR)' checkout '$(EXTERNAL_SNAPSHOTTER_VERSION)' + @git -C '$(EXTERNAL_SNAPSHOTTER_DIR)' config pull.ff only + .PHONY: clean clean: ## Clean resources clean: clean-deprecated @@ -64,6 +73,7 @@ clean: clean-deprecated [ ! -f hack/tools/setup-envtest ] || rm hack/tools/setup-envtest [ ! -d hack/tools/envtest ] || { chmod -R u+w hack/tools/envtest && rm -r hack/tools/envtest; } [ ! -d hack/tools/pgmonitor ] || rm -rf hack/tools/pgmonitor + [ ! -d hack/tools/external-snapshotter ] || rm -rf hack/tools/external-snapshotter [ ! -n "$$(ls hack/tools)" ] || rm -r hack/tools/* [ ! -d hack/.kube ] || rm -r hack/.kube @@ -130,7 +140,10 @@ deploy-dev: createnamespaces CHECK_FOR_UPGRADES='$(if $(CHECK_FOR_UPGRADES),$(CHECK_FOR_UPGRADES),false)' \ KUBECONFIG=hack/.kube/postgres-operator/pgo \ PGO_NAMESPACE='postgres-operator' \ - $(shell $(KUSTOMIZE) build ./config/dev | \ + PGO_INSTALLER='deploy-dev' \ + PGO_INSTALLER_ORIGIN='postgres-operator-repo' \ + BUILD_SOURCE='build-postgres-operator' \ + $(shell kubectl kustomize ./config/dev | \ sed -ne '/^kind: Deployment/,/^---/ { \ /RELATED_IMAGE_/ { N; s,.*\(RELATED_[^[:space:]]*\).*value:[[:space:]]*\([^[:space:]]*\),\1="\2",; p; }; \ }') \ @@ -140,8 +153,9 @@ deploy-dev: createnamespaces ##@ Build - Binary .PHONY: build-postgres-operator build-postgres-operator: ## Build the postgres-operator binary - $(GO_BUILD) -ldflags '-X "main.versionString=$(PGO_VERSION)"' \ - -o bin/postgres-operator ./cmd/postgres-operator + $(GO_BUILD) $(\ + ) --ldflags '-X "main.versionString=$(PGO_VERSION)"' $(\ + ) --trimpath -o bin/postgres-operator ./cmd/postgres-operator ##@ Build - Images .PHONY: build-postgres-operator-image @@ -193,7 +207,7 @@ check: get-pgmonitor check-envtest: ## Run check using envtest and a mock kube api check-envtest: ENVTEST_USE = hack/tools/setup-envtest --bin-dir=$(CURDIR)/hack/tools/envtest use $(ENVTEST_K8S_VERSION) check-envtest: SHELL = bash -check-envtest: get-pgmonitor +check-envtest: get-pgmonitor get-external-snapshotter GOBIN='$(CURDIR)/hack/tools' $(GO) install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest @$(ENVTEST_USE) --print=overview && echo source <($(ENVTEST_USE) --print=env) && PGO_NAMESPACE="postgres-operator" QUERIES_CONFIG_DIR="$(CURDIR)/${QUERIES_CONFIG_DIR}" \ @@ -205,7 +219,7 @@ check-envtest: get-pgmonitor # make check-envtest-existing PGO_TEST_TIMEOUT_SCALE=1.2 .PHONY: check-envtest-existing check-envtest-existing: ## Run check using envtest and an existing kube api -check-envtest-existing: get-pgmonitor +check-envtest-existing: get-pgmonitor get-external-snapshotter check-envtest-existing: createnamespaces kubectl apply --server-side -k ./config/dev USE_EXISTING_CLUSTER=true PGO_NAMESPACE="postgres-operator" QUERIES_CONFIG_DIR="$(CURDIR)/${QUERIES_CONFIG_DIR}" \ diff --git a/build/crd/.gitignore b/build/crd/.gitignore index d56fdba014..2c43d9b253 100644 --- a/build/crd/.gitignore +++ b/build/crd/.gitignore @@ -1,6 +1,6 @@ /generated/ +/crunchy/generated/ /crunchybridgeclusters/generated/ /postgresclusters/generated/ /pgupgrades/generated/ /pgadmins/generated/ -/crunchy/generated/ diff --git a/build/crd/crunchy/generated/postgres-operator.crunchydata.com_pgupgrades.yaml b/build/crd/crunchy/generated/postgres-operator.crunchydata.com_pgupgrades.yaml index df891f7c43..fc164b987b 100644 --- a/build/crd/crunchy/generated/postgres-operator.crunchydata.com_pgupgrades.yaml +++ b/build/crd/crunchy/generated/postgres-operator.crunchydata.com_pgupgrades.yaml @@ -963,7 +963,7 @@ spec: type: object fromPostgresVersion: description: The major version of PostgreSQL before the upgrade. - maximum: 16 + maximum: 17 minimum: 10 type: integer image: @@ -2508,7 +2508,7 @@ spec: type: string toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. - maximum: 16 + maximum: 17 minimum: 10 type: integer tolerations: diff --git a/build/crd/crunchy/generated/postgres-operator.crunchydata.com_postgresclusters.yaml b/build/crd/crunchy/generated/postgres-operator.crunchydata.com_postgresclusters.yaml index a9a5ce4831..6b5bc5f4d7 100644 --- a/build/crd/crunchy/generated/postgres-operator.crunchydata.com_postgresclusters.yaml +++ b/build/crd/crunchy/generated/postgres-operator.crunchydata.com_postgresclusters.yaml @@ -4766,8 +4766,17 @@ spec: required: - repos type: object - required: - - pgbackrest + snapshots: + description: VolumeSnapshot configuration + properties: + volumeSnapshotClassName: + description: Name of the VolumeSnapshotClass that should be + used by VolumeSnapshots + minLength: 1 + type: string + required: + - volumeSnapshotClassName + type: object type: object config: properties: @@ -14004,8 +14013,8 @@ spec: postgresVersion: description: The major version of PostgreSQL installed in the PostgreSQL image - maximum: 16 - minimum: 12 + maximum: 17 + minimum: 10 type: integer proxy: description: The specification of a proxy that connects to PostgreSQL. diff --git a/cmd/postgres-operator/main.go b/cmd/postgres-operator/main.go index 0a7c48b6ae..eba40e69a7 100644 --- a/cmd/postgres-operator/main.go +++ b/cmd/postgres-operator/main.go @@ -1,19 +1,8 @@ -package main - -/* -Copyright 2017 - 2024 Crunchy Data Solutions, Inc. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +// Copyright 2017 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +package main import ( "context" @@ -135,7 +124,7 @@ func main() { log.Info("upgrade checking enabled") // get the URL for the check for upgrades endpoint if set in the env assertNoError(upgradecheck.ManagedScheduler(mgr, - isOpenshift(ctx, mgr.GetConfig()), os.Getenv("CHECK_FOR_UPGRADES_URL"), versionString)) + isOpenshift(ctx, mgr.GetConfig()), os.Getenv("CHECK_FOR_UPGRADES_URL"), versionString, nil)) } assertNoError(mgr.Start(ctx)) diff --git a/cmd/postgres-operator/main_test.go b/cmd/postgres-operator/main_test.go index da23e1a3e6..f369ce6bd3 100644 --- a/cmd/postgres-operator/main_test.go +++ b/cmd/postgres-operator/main_test.go @@ -1,17 +1,6 @@ -/* -Copyright 2017 - 2024 Crunchy Data Solutions, Inc. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2017 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package main diff --git a/cmd/postgres-operator/open_telemetry.go b/cmd/postgres-operator/open_telemetry.go index 94050b987e..2c9eedc135 100644 --- a/cmd/postgres-operator/open_telemetry.go +++ b/cmd/postgres-operator/open_telemetry.go @@ -1,19 +1,8 @@ -package main - -/* -Copyright 2021 - 2024 Crunchy Data Solutions, Inc. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +package main import ( "context" diff --git a/config/README.md b/config/README.md index d1ecf9d1f8..00ebaf8833 100644 --- a/config/README.md +++ b/config/README.md @@ -1,16 +1,7 @@ diff --git a/config/crd/bases/postgres-operator.crunchydata.com_crunchybridgeclusters.yaml b/config/crd/bases/postgres-operator.crunchydata.com_crunchybridgeclusters.yaml index 75564f2483..3798c3cf7f 100644 --- a/config/crd/bases/postgres-operator.crunchydata.com_crunchybridgeclusters.yaml +++ b/config/crd/bases/postgres-operator.crunchydata.com_crunchybridgeclusters.yaml @@ -64,8 +64,8 @@ spec: majorVersion: description: |- The ID of the cluster's major Postgres version. - Currently Bridge offers 13-16 - maximum: 16 + Currently Bridge offers 13-17 + maximum: 17 minimum: 13 type: integer metadata: diff --git a/config/crd/bases/postgres-operator.crunchydata.com_pgupgrades.yaml b/config/crd/bases/postgres-operator.crunchydata.com_pgupgrades.yaml index bacf8fa72c..09b82d3276 100644 --- a/config/crd/bases/postgres-operator.crunchydata.com_pgupgrades.yaml +++ b/config/crd/bases/postgres-operator.crunchydata.com_pgupgrades.yaml @@ -965,7 +965,7 @@ spec: type: object fromPostgresVersion: description: The major version of PostgreSQL before the upgrade. - maximum: 16 + maximum: 17 minimum: 10 type: integer image: @@ -2505,7 +2505,7 @@ spec: type: string toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. - maximum: 16 + maximum: 17 minimum: 10 type: integer tolerations: diff --git a/config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml b/config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml index 664e1fb8ad..71a7bcf680 100644 --- a/config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml +++ b/config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml @@ -4756,8 +4756,17 @@ spec: required: - repos type: object - required: - - pgbackrest + snapshots: + description: VolumeSnapshot configuration + properties: + volumeSnapshotClassName: + description: Name of the VolumeSnapshotClass that should be + used by VolumeSnapshots + minLength: 1 + type: string + required: + - volumeSnapshotClassName + type: object type: object config: properties: @@ -13946,8 +13955,8 @@ spec: postgresVersion: description: The major version of PostgreSQL installed in the PostgreSQL image - maximum: 16 - minimum: 12 + maximum: 17 + minimum: 10 type: integer proxy: description: The specification of a proxy that connects to PostgreSQL. diff --git a/config/rbac/cluster/role.yaml b/config/rbac/cluster/role.yaml index 5e6691ac09..6eabd3cb6a 100644 --- a/config/rbac/cluster/role.yaml +++ b/config/rbac/cluster/role.yaml @@ -10,6 +10,7 @@ rules: - configmaps - persistentvolumeclaims - secrets + - serviceaccounts - services verbs: - create @@ -55,16 +56,6 @@ rules: - list - patch - watch -- apiGroups: - - '' - resources: - - serviceaccounts - verbs: - - create - - get - - list - - patch - - watch - apiGroups: - apps resources: @@ -247,6 +238,18 @@ rules: - roles verbs: - create + - delete + - get + - list + - patch + - watch +- apiGroups: + - snapshot.storage.k8s.io + resources: + - volumesnapshots + verbs: + - create + - delete - get - list - patch diff --git a/config/rbac/namespace/role.yaml b/config/rbac/namespace/role.yaml index c68a6c949e..ef2e8c5047 100644 --- a/config/rbac/namespace/role.yaml +++ b/config/rbac/namespace/role.yaml @@ -10,6 +10,7 @@ rules: - configmaps - persistentvolumeclaims - secrets + - serviceaccounts - services verbs: - create @@ -55,16 +56,6 @@ rules: - list - patch - watch -- apiGroups: - - '' - resources: - - serviceaccounts - verbs: - - create - - get - - list - - patch - - watch - apiGroups: - apps resources: @@ -247,6 +238,18 @@ rules: - roles verbs: - create + - delete + - get + - list + - patch + - watch +- apiGroups: + - snapshot.storage.k8s.io + resources: + - volumesnapshots + verbs: + - create + - delete - get - list - patch diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index 8d75cd6227..b5004710e8 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -64,8 +64,8 @@ spec: majorVersion: description: |- The ID of the cluster's major Postgres version. - Currently Bridge offers 13-16 - maximum: 16 + Currently Bridge offers 13-17 + maximum: 17 minimum: 13 type: integer metadata: @@ -23818,7 +23818,7 @@ spec: type: object fromPostgresVersion: description: The major version of PostgreSQL before the upgrade. - maximum: 16 + maximum: 17 minimum: 10 type: integer image: @@ -25358,7 +25358,7 @@ spec: type: string toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. - maximum: 16 + maximum: 17 minimum: 10 type: integer tolerations: @@ -30306,8 +30306,17 @@ spec: required: - repos type: object - required: - - pgbackrest + snapshots: + description: VolumeSnapshot configuration + properties: + volumeSnapshotClassName: + description: Name of the VolumeSnapshotClass that should be + used by VolumeSnapshots + minLength: 1 + type: string + required: + - volumeSnapshotClassName + type: object type: object config: properties: @@ -39496,8 +39505,8 @@ spec: postgresVersion: description: The major version of PostgreSQL installed in the PostgreSQL image - maximum: 16 - minimum: 12 + maximum: 17 + minimum: 10 type: integer proxy: description: The specification of a proxy that connects to PostgreSQL. @@ -45439,6 +45448,7 @@ rules: - configmaps - persistentvolumeclaims - secrets + - serviceaccounts - services verbs: - create @@ -45484,16 +45494,6 @@ rules: - list - patch - watch -- apiGroups: - - "" - resources: - - serviceaccounts - verbs: - - create - - get - - list - - patch - - watch - apiGroups: - apps resources: @@ -45676,6 +45676,18 @@ rules: - roles verbs: - create + - delete + - get + - list + - patch + - watch +- apiGroups: + - snapshot.storage.k8s.io + resources: + - volumesnapshots + verbs: + - create + - delete - get - list - patch diff --git a/deploy/crd.yaml b/deploy/crd.yaml index ab09163642..581367b96d 100644 --- a/deploy/crd.yaml +++ b/deploy/crd.yaml @@ -64,8 +64,8 @@ spec: majorVersion: description: |- The ID of the cluster's major Postgres version. - Currently Bridge offers 13-16 - maximum: 16 + Currently Bridge offers 13-17 + maximum: 17 minimum: 13 type: integer metadata: @@ -23818,7 +23818,7 @@ spec: type: object fromPostgresVersion: description: The major version of PostgreSQL before the upgrade. - maximum: 16 + maximum: 17 minimum: 10 type: integer image: @@ -25358,7 +25358,7 @@ spec: type: string toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. - maximum: 16 + maximum: 17 minimum: 10 type: integer tolerations: @@ -30306,8 +30306,17 @@ spec: required: - repos type: object - required: - - pgbackrest + snapshots: + description: VolumeSnapshot configuration + properties: + volumeSnapshotClassName: + description: Name of the VolumeSnapshotClass that should be + used by VolumeSnapshots + minLength: 1 + type: string + required: + - volumeSnapshotClassName + type: object type: object config: properties: @@ -39496,8 +39505,8 @@ spec: postgresVersion: description: The major version of PostgreSQL installed in the PostgreSQL image - maximum: 16 - minimum: 12 + maximum: 17 + minimum: 10 type: integer proxy: description: The specification of a proxy that connects to PostgreSQL. diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml index 873b4c22d3..b80c54851d 100644 --- a/deploy/cw-bundle.yaml +++ b/deploy/cw-bundle.yaml @@ -64,8 +64,8 @@ spec: majorVersion: description: |- The ID of the cluster's major Postgres version. - Currently Bridge offers 13-16 - maximum: 16 + Currently Bridge offers 13-17 + maximum: 17 minimum: 13 type: integer metadata: @@ -23818,7 +23818,7 @@ spec: type: object fromPostgresVersion: description: The major version of PostgreSQL before the upgrade. - maximum: 16 + maximum: 17 minimum: 10 type: integer image: @@ -25358,7 +25358,7 @@ spec: type: string toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. - maximum: 16 + maximum: 17 minimum: 10 type: integer tolerations: @@ -30306,8 +30306,17 @@ spec: required: - repos type: object - required: - - pgbackrest + snapshots: + description: VolumeSnapshot configuration + properties: + volumeSnapshotClassName: + description: Name of the VolumeSnapshotClass that should be + used by VolumeSnapshots + minLength: 1 + type: string + required: + - volumeSnapshotClassName + type: object type: object config: properties: @@ -39496,8 +39505,8 @@ spec: postgresVersion: description: The major version of PostgreSQL installed in the PostgreSQL image - maximum: 16 - minimum: 12 + maximum: 17 + minimum: 10 type: integer proxy: description: The specification of a proxy that connects to PostgreSQL. @@ -45439,6 +45448,7 @@ rules: - configmaps - persistentvolumeclaims - secrets + - serviceaccounts - services verbs: - create @@ -45484,16 +45494,6 @@ rules: - list - patch - watch -- apiGroups: - - "" - resources: - - serviceaccounts - verbs: - - create - - get - - list - - patch - - watch - apiGroups: - apps resources: @@ -45676,6 +45676,18 @@ rules: - roles verbs: - create + - delete + - get + - list + - patch + - watch +- apiGroups: + - snapshot.storage.k8s.io + resources: + - volumesnapshots + verbs: + - create + - delete - get - list - patch diff --git a/deploy/cw-rbac.yaml b/deploy/cw-rbac.yaml index e5e1a46ad9..37081c9a0b 100644 --- a/deploy/cw-rbac.yaml +++ b/deploy/cw-rbac.yaml @@ -14,6 +14,7 @@ rules: - configmaps - persistentvolumeclaims - secrets + - serviceaccounts - services verbs: - create @@ -59,16 +60,6 @@ rules: - list - patch - watch -- apiGroups: - - "" - resources: - - serviceaccounts - verbs: - - create - - get - - list - - patch - - watch - apiGroups: - apps resources: @@ -251,6 +242,18 @@ rules: - roles verbs: - create + - delete + - get + - list + - patch + - watch +- apiGroups: + - snapshot.storage.k8s.io + resources: + - volumesnapshots + verbs: + - create + - delete - get - list - patch diff --git a/deploy/rbac.yaml b/deploy/rbac.yaml index 2c7ae82873..4d630e43c4 100644 --- a/deploy/rbac.yaml +++ b/deploy/rbac.yaml @@ -14,6 +14,7 @@ rules: - configmaps - persistentvolumeclaims - secrets + - serviceaccounts - services verbs: - create @@ -59,16 +60,6 @@ rules: - list - patch - watch -- apiGroups: - - "" - resources: - - serviceaccounts - verbs: - - create - - get - - list - - patch - - watch - apiGroups: - apps resources: @@ -251,6 +242,18 @@ rules: - roles verbs: - create + - delete + - get + - list + - patch + - watch +- apiGroups: + - snapshot.storage.k8s.io + resources: + - volumesnapshots + verbs: + - create + - delete - get - list - patch diff --git a/go.mod b/go.mod index f6197641e1..a5f5e55e43 100644 --- a/go.mod +++ b/go.mod @@ -41,10 +41,11 @@ require ( ) require ( - github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect + github.com/kubernetes-csi/external-snapshotter/client/v8 v8.0.0 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/x448/float16 v0.8.4 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect diff --git a/go.sum b/go.sum index 10c76332ae..368b02e1a2 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -134,6 +136,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kubernetes-csi/external-snapshotter/client/v8 v8.0.0 h1:mjQG0Vakr2h246kEDR85U8y8ZhPgT3bguTCajRa/jaw= +github.com/kubernetes-csi/external-snapshotter/client/v8 v8.0.0/go.mod h1:E3vdYxHj2C2q6qo8/Da4g7P+IcwqRZyy3gJBzYybV9Y= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 2c973beb91..7fc3d63c10 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,15 +1,3 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 diff --git a/installers/olm/validate-image.sh b/installers/olm/validate-image.sh index 473ee7e65a..46ec8d703b 100755 --- a/installers/olm/validate-image.sh +++ b/installers/olm/validate-image.sh @@ -1,7 +1,6 @@ #!/usr/bin/env bash # vim: set noexpandtab : set -eu -set -o xtrace push_trap_exit() { local -a array diff --git a/internal/bridge/client.go b/internal/bridge/client.go index a42be5fca3..81d066124f 100644 --- a/internal/bridge/client.go +++ b/internal/bridge/client.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package bridge diff --git a/internal/bridge/client_test.go b/internal/bridge/client_test.go index b17d4f75e3..6b4455f764 100644 --- a/internal/bridge/client_test.go +++ b/internal/bridge/client_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package bridge @@ -913,6 +902,7 @@ func TestGetClusterStatus(t *testing.T) { client := NewClient(server.URL, "") assert.Equal(t, client.BaseURL.String(), server.URL) + _, err = client.GetClusterStatus(context.Background(), testApiKey, clusterId) assert.NilError(t, err) }) diff --git a/internal/bridge/crunchybridgecluster/apply.go b/internal/bridge/crunchybridgecluster/apply.go index 5276678fa5..d77d719d6a 100644 --- a/internal/bridge/crunchybridgecluster/apply.go +++ b/internal/bridge/crunchybridgecluster/apply.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package crunchybridgecluster diff --git a/internal/bridge/crunchybridgecluster/crunchybridgecluster_controller.go b/internal/bridge/crunchybridgecluster/crunchybridgecluster_controller.go index abb13b3cd3..fbbdab045f 100644 --- a/internal/bridge/crunchybridgecluster/crunchybridgecluster_controller.go +++ b/internal/bridge/crunchybridgecluster/crunchybridgecluster_controller.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package crunchybridgecluster diff --git a/internal/bridge/crunchybridgecluster/crunchybridgecluster_controller_test.go b/internal/bridge/crunchybridgecluster/crunchybridgecluster_controller_test.go index 0335816602..b1a6791799 100644 --- a/internal/bridge/crunchybridgecluster/crunchybridgecluster_controller_test.go +++ b/internal/bridge/crunchybridgecluster/crunchybridgecluster_controller_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package crunchybridgecluster diff --git a/internal/bridge/crunchybridgecluster/delete.go b/internal/bridge/crunchybridgecluster/delete.go index b58e988d8c..0749e38654 100644 --- a/internal/bridge/crunchybridgecluster/delete.go +++ b/internal/bridge/crunchybridgecluster/delete.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package crunchybridgecluster diff --git a/internal/bridge/crunchybridgecluster/delete_test.go b/internal/bridge/crunchybridgecluster/delete_test.go index 78cca5f1b0..543881c7bc 100644 --- a/internal/bridge/crunchybridgecluster/delete_test.go +++ b/internal/bridge/crunchybridgecluster/delete_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package crunchybridgecluster diff --git a/internal/bridge/crunchybridgecluster/helpers_test.go b/internal/bridge/crunchybridgecluster/helpers_test.go index a78539d794..ad14b32751 100644 --- a/internal/bridge/crunchybridgecluster/helpers_test.go +++ b/internal/bridge/crunchybridgecluster/helpers_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package crunchybridgecluster diff --git a/internal/bridge/crunchybridgecluster/mock_bridge_api.go b/internal/bridge/crunchybridgecluster/mock_bridge_api.go index d2e332725f..081bb60a47 100644 --- a/internal/bridge/crunchybridgecluster/mock_bridge_api.go +++ b/internal/bridge/crunchybridgecluster/mock_bridge_api.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package crunchybridgecluster diff --git a/internal/bridge/crunchybridgecluster/postgres.go b/internal/bridge/crunchybridgecluster/postgres.go index c63aa7a200..36e88c17e5 100644 --- a/internal/bridge/crunchybridgecluster/postgres.go +++ b/internal/bridge/crunchybridgecluster/postgres.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package crunchybridgecluster @@ -27,7 +16,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/percona/percona-postgresql-operator/internal/bridge" - "github.com/percona/percona-postgresql-operator/internal/initialize" "github.com/percona/percona-postgresql-operator/internal/naming" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -45,11 +33,11 @@ func (r *CrunchyBridgeClusterReconciler) generatePostgresRoleSecret( Name: secretName, }} intent.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Secret")) - initialize.StringMap(&intent.StringData) - - intent.StringData["name"] = clusterRole.Name - intent.StringData["password"] = clusterRole.Password - intent.StringData["uri"] = clusterRole.URI + intent.StringData = map[string]string{ + "name": clusterRole.Name, + "password": clusterRole.Password, + "uri": clusterRole.URI, + } intent.Annotations = cluster.Spec.Metadata.GetAnnotationsOrNil() intent.Labels = naming.Merge( diff --git a/internal/bridge/crunchybridgecluster/postgres_test.go b/internal/bridge/crunchybridgecluster/postgres_test.go index 25f51d75c9..be462e997b 100644 --- a/internal/bridge/crunchybridgecluster/postgres_test.go +++ b/internal/bridge/crunchybridgecluster/postgres_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package crunchybridgecluster diff --git a/internal/bridge/crunchybridgecluster/watches.go b/internal/bridge/crunchybridgecluster/watches.go index 49761bd3fd..3774a0a211 100644 --- a/internal/bridge/crunchybridgecluster/watches.go +++ b/internal/bridge/crunchybridgecluster/watches.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package crunchybridgecluster diff --git a/internal/bridge/crunchybridgecluster/watches_test.go b/internal/bridge/crunchybridgecluster/watches_test.go index 27beabf8a5..02f5215fdf 100644 --- a/internal/bridge/crunchybridgecluster/watches_test.go +++ b/internal/bridge/crunchybridgecluster/watches_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package crunchybridgecluster diff --git a/internal/bridge/installation.go b/internal/bridge/installation.go index e24ce5edb3..3e8fdf4e17 100644 --- a/internal/bridge/installation.go +++ b/internal/bridge/installation.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package bridge diff --git a/internal/bridge/installation_test.go b/internal/bridge/installation_test.go index 2c29c71794..73511d6017 100644 --- a/internal/bridge/installation_test.go +++ b/internal/bridge/installation_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package bridge diff --git a/internal/bridge/naming.go b/internal/bridge/naming.go index 7a0124ae7a..cabe8e9cf6 100644 --- a/internal/bridge/naming.go +++ b/internal/bridge/naming.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package bridge diff --git a/internal/bridge/quantity.go b/internal/bridge/quantity.go index 1c1915b716..a948c6b4cf 100644 --- a/internal/bridge/quantity.go +++ b/internal/bridge/quantity.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package bridge diff --git a/internal/bridge/quantity_test.go b/internal/bridge/quantity_test.go index e9d2cce100..7cfebb4a86 100644 --- a/internal/bridge/quantity_test.go +++ b/internal/bridge/quantity_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package bridge diff --git a/internal/config/config.go b/internal/config/config.go index 4af9684584..9b02d0d6ee 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package config diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 08b8db938d..ab9cf02818 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package config @@ -25,30 +14,6 @@ import ( "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) -func saveEnv(t testing.TB, key string) { - t.Helper() - previous, ok := os.LookupEnv(key) - t.Cleanup(func() { - if ok { - os.Setenv(key, previous) - } else { - os.Unsetenv(key) - } - }) -} - -func setEnv(t testing.TB, key, value string) { - t.Helper() - saveEnv(t, key) - assert.NilError(t, os.Setenv(key, value)) -} - -func unsetEnv(t testing.TB, key string) { - t.Helper() - saveEnv(t, key) - assert.NilError(t, os.Unsetenv(key)) -} - func TestFetchKeyCommand(t *testing.T) { spec1 := v1beta1.PostgresClusterSpec{} @@ -117,13 +82,14 @@ func TestFetchKeyCommand(t *testing.T) { func TestPGAdminContainerImage(t *testing.T) { cluster := &v1beta1.PostgresCluster{} - unsetEnv(t, "RELATED_IMAGE_PGADMIN") + t.Setenv("RELATED_IMAGE_PGADMIN", "") + os.Unsetenv("RELATED_IMAGE_PGADMIN") assert.Equal(t, PGAdminContainerImage(cluster), "") - setEnv(t, "RELATED_IMAGE_PGADMIN", "") + t.Setenv("RELATED_IMAGE_PGADMIN", "") assert.Equal(t, PGAdminContainerImage(cluster), "") - setEnv(t, "RELATED_IMAGE_PGADMIN", "env-var-pgadmin") + t.Setenv("RELATED_IMAGE_PGADMIN", "env-var-pgadmin") assert.Equal(t, PGAdminContainerImage(cluster), "env-var-pgadmin") assert.NilError(t, yaml.Unmarshal([]byte(`{ @@ -135,13 +101,14 @@ func TestPGAdminContainerImage(t *testing.T) { func TestPGBackRestContainerImage(t *testing.T) { cluster := &v1beta1.PostgresCluster{} - unsetEnv(t, "RELATED_IMAGE_PGBACKREST") + t.Setenv("RELATED_IMAGE_PGBACKREST", "") + os.Unsetenv("RELATED_IMAGE_PGBACKREST") assert.Equal(t, PGBackRestContainerImage(cluster), "") - setEnv(t, "RELATED_IMAGE_PGBACKREST", "") + t.Setenv("RELATED_IMAGE_PGBACKREST", "") assert.Equal(t, PGBackRestContainerImage(cluster), "") - setEnv(t, "RELATED_IMAGE_PGBACKREST", "env-var-pgbackrest") + t.Setenv("RELATED_IMAGE_PGBACKREST", "env-var-pgbackrest") assert.Equal(t, PGBackRestContainerImage(cluster), "env-var-pgbackrest") assert.NilError(t, yaml.Unmarshal([]byte(`{ @@ -153,13 +120,14 @@ func TestPGBackRestContainerImage(t *testing.T) { func TestPGBouncerContainerImage(t *testing.T) { cluster := &v1beta1.PostgresCluster{} - unsetEnv(t, "RELATED_IMAGE_PGBOUNCER") + t.Setenv("RELATED_IMAGE_PGBOUNCER", "") + os.Unsetenv("RELATED_IMAGE_PGBOUNCER") assert.Equal(t, PGBouncerContainerImage(cluster), "") - setEnv(t, "RELATED_IMAGE_PGBOUNCER", "") + t.Setenv("RELATED_IMAGE_PGBOUNCER", "") assert.Equal(t, PGBouncerContainerImage(cluster), "") - setEnv(t, "RELATED_IMAGE_PGBOUNCER", "env-var-pgbouncer") + t.Setenv("RELATED_IMAGE_PGBOUNCER", "env-var-pgbouncer") assert.Equal(t, PGBouncerContainerImage(cluster), "env-var-pgbouncer") assert.NilError(t, yaml.Unmarshal([]byte(`{ @@ -171,13 +139,14 @@ func TestPGBouncerContainerImage(t *testing.T) { func TestPGExporterContainerImage(t *testing.T) { cluster := &v1beta1.PostgresCluster{} - unsetEnv(t, "RELATED_IMAGE_PGEXPORTER") + t.Setenv("RELATED_IMAGE_PGEXPORTER", "") + os.Unsetenv("RELATED_IMAGE_PGEXPORTER") assert.Equal(t, PGExporterContainerImage(cluster), "") - setEnv(t, "RELATED_IMAGE_PGEXPORTER", "") + t.Setenv("RELATED_IMAGE_PGEXPORTER", "") assert.Equal(t, PGExporterContainerImage(cluster), "") - setEnv(t, "RELATED_IMAGE_PGEXPORTER", "env-var-pgexporter") + t.Setenv("RELATED_IMAGE_PGEXPORTER", "env-var-pgexporter") assert.Equal(t, PGExporterContainerImage(cluster), "env-var-pgexporter") assert.NilError(t, yaml.Unmarshal([]byte(`{ @@ -189,13 +158,14 @@ func TestPGExporterContainerImage(t *testing.T) { func TestStandalonePGAdminContainerImage(t *testing.T) { pgadmin := &v1beta1.PGAdmin{} - unsetEnv(t, "RELATED_IMAGE_STANDALONE_PGADMIN") + t.Setenv("RELATED_IMAGE_STANDALONE_PGADMIN", "") + os.Unsetenv("RELATED_IMAGE_STANDALONE_PGADMIN") assert.Equal(t, StandalonePGAdminContainerImage(pgadmin), "") - setEnv(t, "RELATED_IMAGE_STANDALONE_PGADMIN", "") + t.Setenv("RELATED_IMAGE_STANDALONE_PGADMIN", "") assert.Equal(t, StandalonePGAdminContainerImage(pgadmin), "") - setEnv(t, "RELATED_IMAGE_STANDALONE_PGADMIN", "env-var-pgadmin") + t.Setenv("RELATED_IMAGE_STANDALONE_PGADMIN", "env-var-pgadmin") assert.Equal(t, StandalonePGAdminContainerImage(pgadmin), "env-var-pgadmin") assert.NilError(t, yaml.Unmarshal([]byte(`{ @@ -208,13 +178,14 @@ func TestPostgresContainerImage(t *testing.T) { cluster := &v1beta1.PostgresCluster{} cluster.Spec.PostgresVersion = 12 - unsetEnv(t, "RELATED_IMAGE_POSTGRES_12") + t.Setenv("RELATED_IMAGE_POSTGRES_12", "") + os.Unsetenv("RELATED_IMAGE_POSTGRES_12") assert.Equal(t, PostgresContainerImage(cluster), "") - setEnv(t, "RELATED_IMAGE_POSTGRES_12", "") + t.Setenv("RELATED_IMAGE_POSTGRES_12", "") assert.Equal(t, PostgresContainerImage(cluster), "") - setEnv(t, "RELATED_IMAGE_POSTGRES_12", "env-var-postgres") + t.Setenv("RELATED_IMAGE_POSTGRES_12", "env-var-postgres") assert.Equal(t, PostgresContainerImage(cluster), "env-var-postgres") cluster.Spec.Image = "spec-image" @@ -222,7 +193,7 @@ func TestPostgresContainerImage(t *testing.T) { cluster.Spec.Image = "" cluster.Spec.PostGISVersion = "3.0" - setEnv(t, "RELATED_IMAGE_POSTGRES_12_GIS_3.0", "env-var-postgis") + t.Setenv("RELATED_IMAGE_POSTGRES_12_GIS_3.0", "env-var-postgis") assert.Equal(t, PostgresContainerImage(cluster), "env-var-postgis") cluster.Spec.Image = "spec-image" @@ -233,7 +204,8 @@ func TestVerifyImageValues(t *testing.T) { cluster := &v1beta1.PostgresCluster{} verifyImageCheck := func(t *testing.T, envVar, errString string, cluster *v1beta1.PostgresCluster) { - unsetEnv(t, envVar) + t.Setenv(envVar, "") + os.Unsetenv(envVar) err := VerifyImageValues(cluster) assert.ErrorContains(t, err, errString) } diff --git a/internal/controller/pgupgrade/apply.go b/internal/controller/pgupgrade/apply.go index 5e3719cb19..71cf65cd4f 100644 --- a/internal/controller/pgupgrade/apply.go +++ b/internal/controller/pgupgrade/apply.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package pgupgrade diff --git a/internal/controller/pgupgrade/jobs.go b/internal/controller/pgupgrade/jobs.go index 61f037a843..3194fbfc1c 100644 --- a/internal/controller/pgupgrade/jobs.go +++ b/internal/controller/pgupgrade/jobs.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package pgupgrade @@ -175,6 +165,7 @@ func (r *PGUpgradeReconciler) generateUpgradeJob( // Replace all containers with one that does the upgrade. job.Spec.Template.Spec.EphemeralContainers = nil + // K8SPG-254: Major upgrade support job.Spec.Template.Spec.InitContainers = upgrade.Spec.InitContainers volumeMounts := database.VolumeMounts @@ -186,7 +177,7 @@ func (r *PGUpgradeReconciler) generateUpgradeJob( // refers back to the container by name, so use that same name here. Name: database.Name, SecurityContext: database.SecurityContext, - VolumeMounts: volumeMounts, + VolumeMounts: volumeMounts, // K8SPG-254 // Use our upgrade command and the specified image and resources. Command: upgradeCommand(upgrade, fetchKeyCommand), @@ -197,8 +188,8 @@ func (r *PGUpgradeReconciler) generateUpgradeJob( // The following will set these fields to null if not set in the spec job.Spec.Template.Spec.Affinity = upgrade.Spec.Affinity - job.Spec.Template.Spec.PriorityClassName = initialize.FromPointer( - upgrade.Spec.PriorityClassName) + job.Spec.Template.Spec.PriorityClassName = + initialize.FromPointer(upgrade.Spec.PriorityClassName) job.Spec.Template.Spec.Tolerations = upgrade.Spec.Tolerations r.setControllerReference(upgrade, job) @@ -307,8 +298,8 @@ func (r *PGUpgradeReconciler) generateRemoveDataJob( // The following will set these fields to null if not set in the spec job.Spec.Template.Spec.Affinity = upgrade.Spec.Affinity - job.Spec.Template.Spec.PriorityClassName = initialize.FromPointer( - upgrade.Spec.PriorityClassName) + job.Spec.Template.Spec.PriorityClassName = + initialize.FromPointer(upgrade.Spec.PriorityClassName) job.Spec.Template.Spec.Tolerations = upgrade.Spec.Tolerations r.setControllerReference(upgrade, job) diff --git a/internal/controller/pgupgrade/jobs_test.go b/internal/controller/pgupgrade/jobs_test.go index 0b98d381e5..077bf55963 100644 --- a/internal/controller/pgupgrade/jobs_test.go +++ b/internal/controller/pgupgrade/jobs_test.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package pgupgrade @@ -21,25 +11,16 @@ import ( "testing" "gotest.tools/v3/assert" - "gotest.tools/v3/assert/cmp" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "sigs.k8s.io/yaml" "github.com/percona/percona-postgresql-operator/internal/initialize" + "github.com/percona/percona-postgresql-operator/internal/testing/cmp" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) -// marshalMatches converts actual to YAML and compares that to expected. -func marshalMatches(actual interface{}, expected string) cmp.Comparison { - b, err := yaml.Marshal(actual) - if err != nil { - return func() cmp.Result { return cmp.ResultFromError(err) } - } - return cmp.DeepEqual(string(b), strings.Trim(expected, "\t\n")+"\n") -} - func TestGenerateUpgradeJob(t *testing.T) { ctx := context.Background() reconciler := &PGUpgradeReconciler{} @@ -77,7 +58,7 @@ func TestGenerateUpgradeJob(t *testing.T) { } job := reconciler.generateUpgradeJob(ctx, upgrade, startup, "") - assert.Assert(t, marshalMatches(job, ` + assert.Assert(t, cmp.MarshalMatches(job, ` apiVersion: batch/v1 kind: Job metadata: @@ -208,7 +189,7 @@ func TestGenerateRemoveDataJob(t *testing.T) { } job := reconciler.generateRemoveDataJob(ctx, upgrade, sts) - assert.Assert(t, marshalMatches(job, ` + assert.Assert(t, cmp.MarshalMatches(job, ` apiVersion: batch/v1 kind: Job metadata: @@ -271,42 +252,17 @@ status: {} `)) } -// saveEnv preserves environment variables so that any modifications needed for -// the tests can be undone once completed. -func saveEnv(t testing.TB, key string) { - t.Helper() - previous, ok := os.LookupEnv(key) - t.Cleanup(func() { - if ok { - os.Setenv(key, previous) - } else { - os.Unsetenv(key) - } - }) -} - -func setEnv(t testing.TB, key, value string) { - t.Helper() - saveEnv(t, key) - assert.NilError(t, os.Setenv(key, value)) -} - -func unsetEnv(t testing.TB, key string) { - t.Helper() - saveEnv(t, key) - assert.NilError(t, os.Unsetenv(key)) -} - func TestPGUpgradeContainerImage(t *testing.T) { upgrade := &v1beta1.PGUpgrade{} - unsetEnv(t, "RELATED_IMAGE_PGUPGRADE") + t.Setenv("RELATED_IMAGE_PGUPGRADE", "") + os.Unsetenv("RELATED_IMAGE_PGUPGRADE") assert.Equal(t, pgUpgradeContainerImage(upgrade), "") - setEnv(t, "RELATED_IMAGE_PGUPGRADE", "") + t.Setenv("RELATED_IMAGE_PGUPGRADE", "") assert.Equal(t, pgUpgradeContainerImage(upgrade), "") - setEnv(t, "RELATED_IMAGE_PGUPGRADE", "env-var-pgbackrest") + t.Setenv("RELATED_IMAGE_PGUPGRADE", "env-var-pgbackrest") assert.Equal(t, pgUpgradeContainerImage(upgrade), "env-var-pgbackrest") assert.NilError(t, yaml.Unmarshal( @@ -318,7 +274,8 @@ func TestVerifyUpgradeImageValue(t *testing.T) { upgrade := &v1beta1.PGUpgrade{} t.Run("crunchy-postgres", func(t *testing.T) { - unsetEnv(t, "RELATED_IMAGE_PGUPGRADE") + t.Setenv("RELATED_IMAGE_PGUPGRADE", "") + os.Unsetenv("RELATED_IMAGE_PGUPGRADE") err := verifyUpgradeImageValue(upgrade) assert.ErrorContains(t, err, "crunchy-upgrade") }) diff --git a/internal/controller/pgupgrade/labels.go b/internal/controller/pgupgrade/labels.go index fb7ac21deb..a5eda21d7a 100644 --- a/internal/controller/pgupgrade/labels.go +++ b/internal/controller/pgupgrade/labels.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package pgupgrade diff --git a/internal/controller/pgupgrade/pgupgrade_controller.go b/internal/controller/pgupgrade/pgupgrade_controller.go index f7d7facfce..4c2ba30724 100644 --- a/internal/controller/pgupgrade/pgupgrade_controller.go +++ b/internal/controller/pgupgrade/pgupgrade_controller.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package pgupgrade @@ -176,7 +166,7 @@ func (r *PGUpgradeReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return } - // PERCONA: Don't require registration for major upgrades + // K8SPG-254: Don't require registration for major upgrades // if !r.UpgradeAuthorized(upgrade) { // return ctrl.Result{}, nil // } @@ -328,6 +318,7 @@ func (r *PGUpgradeReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, nil } + // K8SPG-254: Major upgrade support if version != int64(upgrade.Spec.FromPostgresVersion) && statusVersion != int64(upgrade.Spec.ToPostgresVersion) { meta.SetStatusCondition(&upgrade.Status.Conditions, metav1.Condition{ diff --git a/internal/controller/pgupgrade/registration.go b/internal/controller/pgupgrade/registration.go index f27426c9b6..4d24e1debb 100644 --- a/internal/controller/pgupgrade/registration.go +++ b/internal/controller/pgupgrade/registration.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package pgupgrade diff --git a/internal/controller/pgupgrade/registration_test.go b/internal/controller/pgupgrade/registration_test.go index 5c1218d2bf..82b583f69e 100644 --- a/internal/controller/pgupgrade/registration_test.go +++ b/internal/controller/pgupgrade/registration_test.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package pgupgrade diff --git a/internal/controller/pgupgrade/utils.go b/internal/controller/pgupgrade/utils.go index f45935ce77..fddd12a76d 100644 --- a/internal/controller/pgupgrade/utils.go +++ b/internal/controller/pgupgrade/utils.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package pgupgrade diff --git a/internal/controller/pgupgrade/world.go b/internal/controller/pgupgrade/world.go index 70f113d3b7..e38c8bea82 100644 --- a/internal/controller/pgupgrade/world.go +++ b/internal/controller/pgupgrade/world.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package pgupgrade diff --git a/internal/controller/pgupgrade/world_test.go b/internal/controller/pgupgrade/world_test.go index 3b3d36b83b..f73e8b319f 100644 --- a/internal/controller/pgupgrade/world_test.go +++ b/internal/controller/pgupgrade/world_test.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package pgupgrade diff --git a/internal/controller/postgrescluster/apply.go b/internal/controller/postgrescluster/apply.go index 6d2256e5ef..33bf0cd9ca 100644 --- a/internal/controller/postgrescluster/apply.go +++ b/internal/controller/postgrescluster/apply.go @@ -1,33 +1,15 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster import ( "context" - "encoding/json" - "fmt" "reflect" - jsonpatch "github.com/evanphx/json-patch/v5" - "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/validation/field" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/percona/percona-postgresql-operator/internal/kubeapi" @@ -57,11 +39,6 @@ func (r *Reconciler) apply(ctx context.Context, object client.Object) error { // does not match the intent, send a json-patch to get really specific. switch actual := object.(type) { case *corev1.Service: - // Changing Service.Spec.Type requires a special apply-patch sometimes. - if err != nil { - err = r.handleServiceError(ctx, object.(*corev1.Service), data, err) - } - applyServiceSpec(patch, actual.Spec, intent.(*corev1.Service).Spec, "spec") } @@ -72,53 +49,6 @@ func (r *Reconciler) apply(ctx context.Context, object client.Object) error { return err } -// handleServiceError inspects err for expected Kubernetes API responses to -// writing a Service. It returns err when it cannot resolve the issue, otherwise -// it returns nil. -func (r *Reconciler) handleServiceError( - ctx context.Context, service *corev1.Service, apply []byte, err error, -) error { - var status metav1.Status - if api := apierrors.APIStatus(nil); errors.As(err, &api) { - status = api.Status() - } - - // Service.Spec.Ports.NodePort must be cleared for ClusterIP prior to - // Kubernetes 1.20. When all the errors are about disallowed "nodePort", - // run a json-patch on the apply-patch to set them all to null. - // - https://issue.k8s.io/33766 - if service.Spec.Type == corev1.ServiceTypeClusterIP { - add := json.RawMessage(`"add"`) - null := json.RawMessage(`null`) - patch := make(jsonpatch.Patch, 0, len(service.Spec.Ports)) - - if apierrors.IsInvalid(err) && status.Details != nil { - for i, cause := range status.Details.Causes { - path := json.RawMessage(fmt.Sprintf(`"/spec/ports/%d/nodePort"`, i)) - - if cause.Type == metav1.CauseType(field.ErrorTypeForbidden) && - cause.Field == fmt.Sprintf("spec.ports[%d].nodePort", i) { - patch = append(patch, - jsonpatch.Operation{"op": &add, "value": &null, "path": &path}) - } - } - } - - // Amend the apply-patch when all the errors can be fixed. - if len(patch) == len(service.Spec.Ports) { - apply, err = patch.Apply(apply) - } - - // Send the apply-patch with force=true. - if err == nil { - patch := client.RawPatch(client.Apply.Type(), apply) - err = r.patch(ctx, service, patch, client.ForceOwnership) - } - } - - return err -} - // applyServiceSpec is called by Reconciler.apply to work around issues // with server-side apply. func applyServiceSpec( diff --git a/internal/controller/postgrescluster/apply_test.go b/internal/controller/postgrescluster/apply_test.go index d6e407b51b..639458bd9d 100644 --- a/internal/controller/postgrescluster/apply_test.go +++ b/internal/controller/postgrescluster/apply_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -310,55 +299,4 @@ func TestServerSideApply(t *testing.T) { }) } }) - - t.Run("ServiceType", func(t *testing.T) { - constructor := func(name string) *corev1.Service { - var service corev1.Service - service.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Service")) - service.Namespace, service.Name = ns.Name, name - service.Spec.Ports = []corev1.ServicePort{ - {Name: "one", Port: 9999, Protocol: corev1.ProtocolTCP}, - {Name: "two", Port: 1234, Protocol: corev1.ProtocolTCP}, - } - return &service - } - - reconciler := Reconciler{Client: cc, Owner: client.FieldOwner(t.Name())} - - // Start as NodePort. - intent := constructor("node-port") - intent.Spec.Type = corev1.ServiceTypeNodePort - - // Create the Service. - before := intent.DeepCopy() - assert.NilError(t, - cc.Patch(ctx, before, client.Apply, client.ForceOwnership, reconciler.Owner)) - - // Change to ClusterIP. - intent.Spec.Type = corev1.ServiceTypeClusterIP - - // client.Apply cannot change it in old versions of Kubernetes. - after := intent.DeepCopy() - err := cc.Patch(ctx, after, client.Apply, client.ForceOwnership, reconciler.Owner) - - switch { - case serverVersion.LessThan(version.MustParseGeneric("1.20")): - - assert.ErrorContains(t, err, "nodePort: Forbidden", - "expected https://issue.k8s.io/33766") - - default: - assert.NilError(t, err) - assert.Equal(t, after.Spec.Type, intent.Spec.Type) - assert.Equal(t, after.Spec.ClusterIP, before.Spec.ClusterIP, - "expected to keep the same ClusterIP") - } - - // Our apply method changes it. - again := intent.DeepCopy() - assert.NilError(t, reconciler.apply(ctx, again)) - assert.Equal(t, again.Spec.Type, intent.Spec.Type) - assert.Equal(t, again.Spec.ClusterIP, before.Spec.ClusterIP, - "expected to keep the same ClusterIP") - }) } diff --git a/internal/controller/postgrescluster/cluster.go b/internal/controller/postgrescluster/cluster.go index 07a4e827b5..7bacd68ed0 100644 --- a/internal/controller/postgrescluster/cluster.go +++ b/internal/controller/postgrescluster/cluster.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -26,6 +15,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + "github.com/percona/percona-postgresql-operator/internal/initialize" "github.com/percona/percona-postgresql-operator/internal/naming" "github.com/percona/percona-postgresql-operator/internal/patroni" "github.com/percona/percona-postgresql-operator/internal/pki" @@ -119,6 +109,7 @@ func (r *Reconciler) generateClusterPrimaryService( cluster.Spec.Metadata.GetAnnotationsOrNil()) service.Labels = naming.Merge( cluster.Spec.Metadata.GetLabelsOrNil(), + // K8SPG-430 naming.WithPerconaLabels(map[string]string{ naming.LabelCluster: cluster.Name, naming.LabelRole: naming.RolePrimary, @@ -213,6 +204,7 @@ func (r *Reconciler) generateClusterReplicaService( // add our labels last so they aren't overwritten service.Labels = naming.Merge( service.Labels, + // K8SPG-430 naming.WithPerconaLabels(map[string]string{ naming.LabelCluster: cluster.Name, naming.LabelRole: naming.RoleReplica, @@ -248,6 +240,8 @@ func (r *Reconciler) generateClusterReplicaService( } servicePort.NodePort = *spec.NodePort } + service.Spec.ExternalTrafficPolicy = initialize.FromPointer(spec.ExternalTrafficPolicy) + service.Spec.InternalTrafficPolicy = spec.InternalTrafficPolicy } service.Spec.Ports = []corev1.ServicePort{servicePort} @@ -290,7 +284,9 @@ func (r *Reconciler) reconcileClusterReplicaService( func (r *Reconciler) reconcileDataSource(ctx context.Context, cluster *v1beta1.PostgresCluster, observed *observedInstances, clusterVolumes []corev1.PersistentVolumeClaim, - rootCA *pki.RootCertificateAuthority) (bool, error) { + rootCA *pki.RootCertificateAuthority, + backupsSpecFound bool, +) (bool, error) { // a hash func to hash the pgBackRest restore options hashFunc := func(jobConfigs []string) (string, error) { @@ -413,7 +409,8 @@ func (r *Reconciler) reconcileDataSource(ctx context.Context, switch { case dataSource != nil: if err := r.reconcilePostgresClusterDataSource(ctx, cluster, dataSource, - configHash, clusterVolumes, rootCA); err != nil { + configHash, clusterVolumes, rootCA, + backupsSpecFound); err != nil { return true, err } case cloudDataSource != nil: diff --git a/internal/controller/postgrescluster/cluster_test.go b/internal/controller/postgrescluster/cluster_test.go index a3c790e0b6..eb905e28b7 100644 --- a/internal/controller/postgrescluster/cluster_test.go +++ b/internal/controller/postgrescluster/cluster_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -36,6 +25,7 @@ import ( "github.com/percona/percona-postgresql-operator/internal/initialize" "github.com/percona/percona-postgresql-operator/internal/naming" + "github.com/percona/percona-postgresql-operator/internal/testing/cmp" "github.com/percona/percona-postgresql-operator/internal/testing/require" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -271,11 +261,9 @@ func TestCustomLabels(t *testing.T) { MatchLabels: map[string]string{ naming.LabelCluster: cluster.Name, }, - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: naming.LabelPGBackRest, - Operator: metav1.LabelSelectorOpExists, - }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: naming.LabelPGBackRest, + Operator: metav1.LabelSelectorOpExists}, }, }) assert.NilError(t, err) @@ -526,11 +514,9 @@ func TestCustomAnnotations(t *testing.T) { MatchLabels: map[string]string{ naming.LabelCluster: cluster.Name, }, - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: naming.LabelPGBackRest, - Operator: metav1.LabelSelectorOpExists, - }, + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: naming.LabelPGBackRest, + Operator: metav1.LabelSelectorOpExists}, }, }) assert.NilError(t, err) @@ -606,6 +592,7 @@ func TestGenerateClusterPrimaryService(t *testing.T) { cluster.Name = "pg5" cluster.Spec.Port = initialize.Int32(2600) + // K8SPG-430 cluster.Labels = map[string]string{ naming.LabelVersion: "2.3.0", } @@ -617,11 +604,11 @@ func TestGenerateClusterPrimaryService(t *testing.T) { assert.ErrorContains(t, err, "not implemented") alwaysExpect := func(t testing.TB, service *corev1.Service, endpoints *corev1.Endpoints) { - assert.Assert(t, marshalMatches(service.TypeMeta, ` + assert.Assert(t, cmp.MarshalMatches(service.TypeMeta, ` apiVersion: v1 kind: Service `)) - assert.Assert(t, marshalMatches(service.ObjectMeta, ` + assert.Assert(t, cmp.MarshalMatches(service.ObjectMeta, ` creationTimestamp: null labels: app.kubernetes.io/component: pg @@ -641,7 +628,7 @@ ownerReferences: name: pg5 uid: "" `)) - assert.Assert(t, marshalMatches(service.Spec.Ports, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Ports, ` - name: postgres port: 2600 protocol: TCP @@ -652,7 +639,7 @@ ownerReferences: assert.Assert(t, service.Spec.Selector == nil, "got %v", service.Spec.Selector) - assert.Assert(t, marshalMatches(endpoints, ` + assert.Assert(t, cmp.MarshalMatches(endpoints, ` apiVersion: v1 kind: Endpoints metadata: @@ -742,6 +729,7 @@ func TestGenerateClusterReplicaServiceIntent(t *testing.T) { cluster.Name = "pg2" cluster.Spec.Port = initialize.Int32(9876) + // K8SPG-430 cluster.Labels = map[string]string{ naming.LabelVersion: "2.3.0", } @@ -750,11 +738,11 @@ func TestGenerateClusterReplicaServiceIntent(t *testing.T) { assert.NilError(t, err) alwaysExpect := func(t testing.TB, service *corev1.Service) { - assert.Assert(t, marshalMatches(service.TypeMeta, ` + assert.Assert(t, cmp.MarshalMatches(service.TypeMeta, ` apiVersion: v1 kind: Service `)) - assert.Assert(t, marshalMatches(service.ObjectMeta, ` + assert.Assert(t, cmp.MarshalMatches(service.ObjectMeta, ` creationTimestamp: null labels: app.kubernetes.io/component: pg @@ -777,7 +765,7 @@ ownerReferences: } alwaysExpect(t, service) - assert.Assert(t, marshalMatches(service.Spec, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec, ` ports: - name: postgres port: 9876 @@ -813,7 +801,7 @@ type: ClusterIP assert.NilError(t, err) alwaysExpect(t, service) test.Expect(t, service) - assert.Assert(t, marshalMatches(service.Spec.Ports, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Ports, ` - name: postgres port: 9876 protocol: TCP @@ -833,12 +821,12 @@ type: ClusterIP assert.NilError(t, err) // Annotations present in the metadata. - assert.Assert(t, marshalMatches(service.ObjectMeta.Annotations, ` + assert.Assert(t, cmp.MarshalMatches(service.ObjectMeta.Annotations, ` some: note `)) // Labels present in the metadata. - assert.Assert(t, marshalMatches(service.ObjectMeta.Labels, ` + assert.Assert(t, cmp.MarshalMatches(service.ObjectMeta.Labels, ` app.kubernetes.io/component: pg app.kubernetes.io/instance: pg2 app.kubernetes.io/managed-by: percona-postgresql-operator @@ -850,7 +838,7 @@ postgres-operator.crunchydata.com/role: replica `)) // Labels not in the selector. - assert.Assert(t, marshalMatches(service.Spec.Selector, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Selector, ` postgres-operator.crunchydata.com/cluster: pg2 postgres-operator.crunchydata.com/role: replica `)) diff --git a/internal/controller/postgrescluster/controller.go b/internal/controller/postgrescluster/controller.go index 28686ed74d..85828236e3 100644 --- a/internal/controller/postgrescluster/controller.go +++ b/internal/controller/postgrescluster/controller.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -29,9 +18,11 @@ import ( policyv1 "k8s.io/api/policy/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/client-go/discovery" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -63,10 +54,11 @@ const ( // Reconciler holds resources for the PostgresCluster reconciler type Reconciler struct { - Client client.Client - IsOpenShift bool - Owner client.FieldOwner - PodExec func( + Client client.Client + DiscoveryClient *discovery.DiscoveryClient + IsOpenShift bool + Owner client.FieldOwner + PodExec func( ctx context.Context, namespace, pod, container string, stdin io.Reader, stdout, stderr io.Writer, command ...string, ) error @@ -162,21 +154,24 @@ func (r *Reconciler) Reconcile( } var ( - clusterConfigMap *corev1.ConfigMap - clusterReplicationSecret *corev1.Secret - clusterPodService *corev1.Service - clusterVolumes []corev1.PersistentVolumeClaim - instanceServiceAccount *corev1.ServiceAccount - instances *observedInstances - patroniLeaderService *corev1.Service - primaryCertificate *corev1.SecretProjection - primaryService *corev1.Service - replicaService *corev1.Service - rootCA *pki.RootCertificateAuthority - monitoringSecret *corev1.Secret - exporterQueriesConfig *corev1.ConfigMap - exporterWebConfig *corev1.ConfigMap - err error + clusterConfigMap *corev1.ConfigMap + clusterReplicationSecret *corev1.Secret + clusterPodService *corev1.Service + clusterVolumes []corev1.PersistentVolumeClaim + instanceServiceAccount *corev1.ServiceAccount + instances *observedInstances + patroniLeaderService *corev1.Service + primaryCertificate *corev1.SecretProjection + primaryService *corev1.Service + replicaService *corev1.Service + rootCA *pki.RootCertificateAuthority + monitoringSecret *corev1.Secret + exporterQueriesConfig *corev1.ConfigMap + exporterWebConfig *corev1.ConfigMap + err error + backupsSpecFound bool + backupsReconciliationAllowed bool + dedicatedSnapshotPVC *corev1.PersistentVolumeClaim ) patchClusterStatus := func() error { @@ -214,22 +209,45 @@ func (r *Reconciler) Reconcile( meta.RemoveStatusCondition(&cluster.Status.Conditions, v1beta1.PostgresClusterProgressing) } + if err == nil { + backupsSpecFound, backupsReconciliationAllowed, err = r.BackupsEnabled(ctx, cluster) + + // If we cannot reconcile because the backup reconciliation is paused, set a condition and exit + if !backupsReconciliationAllowed { + meta.SetStatusCondition(&cluster.Status.Conditions, metav1.Condition{ + Type: v1beta1.PostgresClusterProgressing, + Status: metav1.ConditionFalse, + Reason: "Paused", + Message: "Reconciliation is paused: please fill in spec.backups " + + "or add the postgres-operator.crunchydata.com/authorizeBackupRemoval " + + "annotation to authorize backup removal.", + + ObservedGeneration: cluster.GetGeneration(), + }) + return runtime.ErrorWithBackoff(patchClusterStatus()) + } else { + meta.RemoveStatusCondition(&cluster.Status.Conditions, v1beta1.PostgresClusterProgressing) + } + } + pgHBAs := postgres.NewHBAs() pmm.PostgreSQLHBAs(cluster, &pgHBAs) pgmonitor.PostgreSQLHBAs(cluster, &pgHBAs) pgbouncer.PostgreSQL(cluster, &pgHBAs) pgParameters := postgres.NewParameters() + // K8SPG-375 if cluster.Spec.Extensions.PGStatMonitor { pgstatmonitor.PostgreSQLParameters(&pgParameters) } if cluster.Spec.Extensions.PGAudit { pgaudit.PostgreSQLParameters(&pgParameters) } + // K8SPG-577 if cluster.Spec.Extensions.PGStatStatements { pgstatstatements.PostgreSQLParameters(&pgParameters) } - pgbackrest.PostgreSQL(cluster, &pgParameters) + pgbackrest.PostgreSQL(cluster, &pgParameters, backupsSpecFound) pgmonitor.PostgreSQLParameters(cluster, &pgParameters) // Set huge_pages = try if a hugepages resource limit > 0, otherwise set "off" @@ -296,7 +314,7 @@ func (r *Reconciler) Reconcile( // the controller should return early while data initialization is in progress, after // which it will indicate that an early return is no longer needed, and reconciliation // can proceed normally. - returnEarly, err := r.reconcileDataSource(ctx, cluster, instances, clusterVolumes, rootCA) + returnEarly, err := r.reconcileDataSource(ctx, cluster, instances, clusterVolumes, rootCA, backupsSpecFound) if err != nil || returnEarly { return runtime.ErrorWithBackoff(errors.Join(err, patchClusterStatus())) } @@ -338,7 +356,9 @@ func (r *Reconciler) Reconcile( err = r.reconcileInstanceSets( ctx, cluster, clusterConfigMap, clusterReplicationSecret, rootCA, clusterPodService, instanceServiceAccount, instances, patroniLeaderService, - primaryCertificate, clusterVolumes, exporterQueriesConfig, exporterWebConfig) + primaryCertificate, clusterVolumes, exporterQueriesConfig, exporterWebConfig, + backupsSpecFound, + ) } if err == nil { @@ -350,13 +370,20 @@ func (r *Reconciler) Reconcile( if err == nil { var next reconcile.Result - if next, err = r.reconcilePGBackRest(ctx, cluster, instances, rootCA); err == nil && !next.IsZero() { + if next, err = r.reconcilePGBackRest(ctx, cluster, + instances, rootCA, backupsSpecFound); err == nil && !next.IsZero() { result.Requeue = result.Requeue || next.Requeue if next.RequeueAfter > 0 { result.RequeueAfter = next.RequeueAfter } } } + if err == nil { + dedicatedSnapshotPVC, err = r.reconcileDedicatedSnapshotVolume(ctx, cluster, clusterVolumes) + } + if err == nil { + err = r.reconcileVolumeSnapshots(ctx, cluster, dedicatedSnapshotPVC) + } if err == nil { err = r.reconcilePGBouncer(ctx, cluster, instances, primaryCertificate, rootCA) } @@ -459,6 +486,14 @@ func (r *Reconciler) SetupWithManager(mgr manager.Manager) error { } } + if r.DiscoveryClient == nil { + var err error + r.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(mgr.GetConfig()) + if err != nil { + return err + } + } + return builder.ControllerManagedBy(mgr). For(&v1beta1.PostgresCluster{}). Owns(&corev1.ConfigMap{}). @@ -479,3 +514,28 @@ func (r *Reconciler) SetupWithManager(mgr manager.Manager) error { r.controllerRefHandlerFuncs()). // watch all StatefulSets Complete(r) } + +// GroupVersionKindExists checks to see whether a given Kind for a given +// GroupVersion exists in the Kubernetes API Server. +func (r *Reconciler) GroupVersionKindExists(groupVersion, kind string) (*bool, error) { + if r.DiscoveryClient == nil { + return initialize.Bool(false), nil + } + + resourceList, err := r.DiscoveryClient.ServerResourcesForGroupVersion(groupVersion) + if err != nil { + if apierrors.IsNotFound(err) { + return initialize.Bool(false), nil + } + + return nil, err + } + + for _, resource := range resourceList.APIResources { + if resource.Kind == kind { + return initialize.Bool(true), nil + } + } + + return initialize.Bool(false), nil +} diff --git a/internal/controller/postgrescluster/controller_ref_manager.go b/internal/controller/postgrescluster/controller_ref_manager.go index 2fe3d77d54..ec15ec364a 100644 --- a/internal/controller/postgrescluster/controller_ref_manager.go +++ b/internal/controller/postgrescluster/controller_ref_manager.go @@ -1,17 +1,6 @@ -/* -Copyright 2021 - 2024 Crunchy Data Solutions, Inc. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -37,8 +26,8 @@ import ( // adoptObject adopts the provided Object by adding controller owner refs for the provided // PostgresCluster. func (r *Reconciler) adoptObject(ctx context.Context, postgresCluster *v1beta1.PostgresCluster, - obj client.Object, -) error { + obj client.Object) error { + if err := controllerutil.SetControllerReference(postgresCluster, obj, r.Client.Scheme()); err != nil { return err @@ -66,8 +55,8 @@ func (r *Reconciler) adoptObject(ctx context.Context, postgresCluster *v1beta1.P // race conditions with the garbage collector (see // https://github.com/kubernetes/kubernetes/issues/42639) func (r *Reconciler) claimObject(ctx context.Context, postgresCluster *v1beta1.PostgresCluster, - obj client.Object, -) error { + obj client.Object) error { + controllerRef := metav1.GetControllerOfNoCopy(obj) if controllerRef != nil { // if not owned by this postgrescluster then ignore @@ -123,8 +112,8 @@ func (r *Reconciler) claimObject(ctx context.Context, postgresCluster *v1beta1.P // getPostgresClusterForObject is responsible for obtaining the PostgresCluster associated // with an Object. func (r *Reconciler) getPostgresClusterForObject(ctx context.Context, - obj client.Object, -) (bool, *v1beta1.PostgresCluster, error) { + obj client.Object) (bool, *v1beta1.PostgresCluster, error) { + clusterName := "" // first see if it has a PostgresCluster ownership ref or a PostgresCluster label @@ -157,8 +146,8 @@ func (r *Reconciler) getPostgresClusterForObject(ctx context.Context, // to adopt or release/orphan an Object. This includes obtaining the PostgresCluster for // the Object and then calling the logic needed to either adopt or release it. func (r *Reconciler) manageControllerRefs(ctx context.Context, - obj client.Object, -) error { + obj client.Object) error { + found, postgresCluster, err := r.getPostgresClusterForObject(ctx, obj) if err != nil { return err @@ -174,8 +163,8 @@ func (r *Reconciler) manageControllerRefs(ctx context.Context, // PostgresCluster. This is done by removing the PostgresCluster's controller owner // refs from the Object. func (r *Reconciler) releaseObject(ctx context.Context, - postgresCluster *v1beta1.PostgresCluster, obj client.Object, -) error { + postgresCluster *v1beta1.PostgresCluster, obj client.Object) error { + // TODO create a strategic merge type in kubeapi instead of using Merge7386 patch, err := kubeapi.NewMergePatch(). Add("metadata", "ownerReferences")([]map[string]string{{ @@ -192,6 +181,7 @@ func (r *Reconciler) releaseObject(ctx context.Context, // controllerRefHandlerFuncs returns the handler funcs that should be utilized to watch // StatefulSets within the cluster as needed to manage controller ownership refs. func (r *Reconciler) controllerRefHandlerFuncs() *handler.Funcs { + log := logging.FromContext(context.Background()) errMsg := "managing StatefulSet controller refs" diff --git a/internal/controller/postgrescluster/controller_ref_manager_test.go b/internal/controller/postgrescluster/controller_ref_manager_test.go index 5cb157da59..3355936718 100644 --- a/internal/controller/postgrescluster/controller_ref_manager_test.go +++ b/internal/controller/postgrescluster/controller_ref_manager_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster diff --git a/internal/controller/postgrescluster/controller_test.go b/internal/controller/postgrescluster/controller_test.go index df4301ea16..b36340b4df 100644 --- a/internal/controller/postgrescluster/controller_test.go +++ b/internal/controller/postgrescluster/controller_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster diff --git a/internal/controller/postgrescluster/delete.go b/internal/controller/postgrescluster/delete.go index 7236d24f5c..39fd4fd6ba 100644 --- a/internal/controller/postgrescluster/delete.go +++ b/internal/controller/postgrescluster/delete.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster diff --git a/internal/controller/postgrescluster/helpers_test.go b/internal/controller/postgrescluster/helpers_test.go index 3ae98c53be..e507dfb87c 100644 --- a/internal/controller/postgrescluster/helpers_test.go +++ b/internal/controller/postgrescluster/helpers_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -22,6 +11,7 @@ import ( "testing" "time" + batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -32,7 +22,7 @@ import ( "github.com/percona/percona-postgresql-operator/internal/controller/runtime" "github.com/percona/percona-postgresql-operator/internal/initialize" - "github.com/percona/percona-postgresql-operator/internal/testing/cmp" + "github.com/percona/percona-postgresql-operator/internal/naming" "github.com/percona/percona-postgresql-operator/internal/testing/require" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -63,11 +53,6 @@ func init() { } } -// marshalMatches converts actual to YAML and compares that to expected. -func marshalMatches(actual interface{}, expected string) cmp.Comparison { - return cmp.MarshalMatches(actual, expected) -} - // setupKubernetes starts or connects to a Kubernetes API and returns a client // that uses it. See [require.Kubernetes] for more details. func setupKubernetes(t testing.TB) (*rest.Config, client.Client) { @@ -116,6 +101,7 @@ func testVolumeClaimSpec() corev1.PersistentVolumeClaimSpec { }, } } + func testCluster() *v1beta1.PostgresCluster { // Defines a base cluster spec that can be used by tests to generate a // cluster with an expected number of instances @@ -155,6 +141,58 @@ func testCluster() *v1beta1.PostgresCluster { return cluster.DeepCopy() } +func testBackupJob(cluster *v1beta1.PostgresCluster) *batchv1.Job { + job := batchv1.Job{ + TypeMeta: metav1.TypeMeta{ + APIVersion: batchv1.SchemeGroupVersion.String(), + Kind: "Job", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "backup-job-1", + Namespace: cluster.Namespace, + Labels: map[string]string{ + naming.LabelCluster: cluster.Name, + naming.LabelPGBackRestBackup: "", + naming.LabelPGBackRestRepo: "repo1", + }, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "test", Image: "test"}}, + RestartPolicy: corev1.RestartPolicyNever, + }, + }, + }, + } + + return job.DeepCopy() +} + +func testRestoreJob(cluster *v1beta1.PostgresCluster) *batchv1.Job { + job := batchv1.Job{ + TypeMeta: metav1.TypeMeta{ + APIVersion: batchv1.SchemeGroupVersion.String(), + Kind: "Job", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "restore-job-1", + Namespace: cluster.Namespace, + Labels: naming.PGBackRestRestoreJobLabels(cluster.Name), + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "test", Image: "test"}}, + RestartPolicy: corev1.RestartPolicyNever, + }, + }, + }, + } + + return job.DeepCopy() +} + // setupManager creates the runtime manager used during controller testing func setupManager(t *testing.T, cfg *rest.Config, controllerSetup func(mgr manager.Manager)) (context.Context, context.CancelFunc) { diff --git a/internal/controller/postgrescluster/instance.go b/internal/controller/postgrescluster/instance.go index ae5eda0f44..0d913c25b8 100644 --- a/internal/controller/postgrescluster/instance.go +++ b/internal/controller/postgrescluster/instance.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -346,7 +335,7 @@ func (r *Reconciler) observeInstances( status.DesiredPGDataVolume = make(map[string]string) for _, instance := range observed.bySet[name] { - status.Replicas += int32(len(instance.Pods)) // nolint:gosec + status.Replicas += int32(len(instance.Pods)) //nolint:gosec if ready, known := instance.IsReady(); known && ready { status.ReadyReplicas++ @@ -604,6 +593,7 @@ func (r *Reconciler) reconcileInstanceSets( primaryCertificate *corev1.SecretProjection, clusterVolumes []corev1.PersistentVolumeClaim, exporterQueriesConfig, exporterWebConfig *corev1.ConfigMap, + backupsSpecFound bool, ) error { // Go through the observed instances and check if a primary has been determined. @@ -640,7 +630,9 @@ func (r *Reconciler) reconcileInstanceSets( rootCA, clusterPodService, instanceServiceAccount, patroniLeaderService, primaryCertificate, findAvailableInstanceNames(*set, instances, clusterVolumes), - numInstancePods, clusterVolumes, exporterQueriesConfig, exporterWebConfig) + numInstancePods, clusterVolumes, exporterQueriesConfig, exporterWebConfig, + backupsSpecFound, + ) if err == nil { err = r.reconcileInstanceSetPodDisruptionBudget(ctx, cluster, set) @@ -1079,6 +1071,7 @@ func (r *Reconciler) scaleUpInstances( numInstancePods int, clusterVolumes []corev1.PersistentVolumeClaim, exporterQueriesConfig, exporterWebConfig *corev1.ConfigMap, + backupsSpecFound bool, ) ([]*appsv1.StatefulSet, error) { log := logging.FromContext(ctx) @@ -1123,6 +1116,7 @@ func (r *Reconciler) scaleUpInstances( rootCA, clusterPodService, instanceServiceAccount, patroniLeaderService, primaryCertificate, instances[i], numInstancePods, clusterVolumes, exporterQueriesConfig, exporterWebConfig, + backupsSpecFound, ) } if err == nil { @@ -1152,6 +1146,7 @@ func (r *Reconciler) reconcileInstance( numInstancePods int, clusterVolumes []corev1.PersistentVolumeClaim, exporterQueriesConfig, exporterWebConfig *corev1.ConfigMap, + backupsSpecFound bool, ) error { log := logging.FromContext(ctx).WithValues("instance", instance.Name) ctx = logging.NewContext(ctx, log) @@ -1183,7 +1178,7 @@ func (r *Reconciler) reconcileInstance( ctx, cluster, spec, instance, rootCA) } if err == nil { - postgresDataVolume, err = r.reconcilePostgresDataVolume(ctx, cluster, spec, instance, clusterVolumes) + postgresDataVolume, err = r.reconcilePostgresDataVolume(ctx, cluster, spec, instance, clusterVolumes, nil) } if err == nil { postgresWALVolume, err = r.reconcilePostgresWALVolume(ctx, cluster, spec, instance, observed, clusterVolumes) @@ -1198,8 +1193,10 @@ func (r *Reconciler) reconcileInstance( postgresDataVolume, postgresWALVolume, tablespaceVolumes, &instance.Spec.Template.Spec) - addPGBackRestToInstancePodSpec( - ctx, cluster, instanceCertificates, &instance.Spec.Template.Spec) + if backupsSpecFound { + addPGBackRestToInstancePodSpec( + ctx, cluster, instanceCertificates, &instance.Spec.Template.Spec) + } err = patroni.InstancePod( ctx, cluster, clusterConfigMap, clusterPodService, patroniLeaderService, @@ -1215,12 +1212,13 @@ func (r *Reconciler) reconcileInstance( // containers if err == nil { addNSSWrapper( - cluster, + cluster, // K8SPG-260 config.PostgresContainerImage(cluster), cluster.Spec.ImagePullPolicy, &instance.Spec.Template) } + // K8SPG-435 sizeLimit := getTMPSizeLimit(instance.Labels[naming.LabelVersion], spec.Resources) // add an emptyDir volume to the PodTemplateSpec and an associated '/tmp' volume mount to @@ -1258,7 +1256,7 @@ func generateInstanceStatefulSetIntent(_ context.Context, sts.Labels = naming.Merge( cluster.Spec.Metadata.GetLabelsOrNil(), spec.Metadata.GetLabelsOrNil(), - naming.WithPerconaLabels(map[string]string{ + naming.WithPerconaLabels(map[string]string{ // K8SPG-430 naming.LabelCluster: cluster.Name, naming.LabelInstanceSet: spec.Name, naming.LabelInstance: sts.Name, @@ -1278,7 +1276,7 @@ func generateInstanceStatefulSetIntent(_ context.Context, sts.Spec.Template.Labels = naming.Merge( cluster.Spec.Metadata.GetLabelsOrNil(), spec.Metadata.GetLabelsOrNil(), - naming.WithPerconaLabels(map[string]string{ + naming.WithPerconaLabels(map[string]string{ // K8SPG-430 naming.LabelCluster: cluster.Name, naming.LabelInstanceSet: spec.Name, naming.LabelInstance: sts.Name, @@ -1304,15 +1302,11 @@ func generateInstanceStatefulSetIntent(_ context.Context, sts.Spec.Template.Spec.Affinity = spec.Affinity sts.Spec.Template.Spec.Tolerations = spec.Tolerations sts.Spec.Template.Spec.TopologySpreadConstraints = spec.TopologySpreadConstraints - if spec.PriorityClassName != nil { - sts.Spec.Template.Spec.PriorityClassName = *spec.PriorityClassName - } + sts.Spec.Template.Spec.PriorityClassName = initialize.FromPointer(spec.PriorityClassName) // if default pod scheduling is not explicitly disabled, add the default // pod topology spread constraints - if cluster.Spec.DisableDefaultPodScheduling == nil || - (cluster.Spec.DisableDefaultPodScheduling != nil && - !*cluster.Spec.DisableDefaultPodScheduling) { + if !initialize.FromPointer(cluster.Spec.DisableDefaultPodScheduling) { sts.Spec.Template.Spec.TopologySpreadConstraints = append( sts.Spec.Template.Spec.TopologySpreadConstraints, defaultTopologySpreadConstraints( @@ -1364,6 +1358,7 @@ func generateInstanceStatefulSetIntent(_ context.Context, // - https://releases.k8s.io/v1.23.0/pkg/kubelet/kubelet_pods.go#L553-L563 sts.Spec.Template.Spec.EnableServiceLinks = initialize.Bool(false) + // K8SPG-514 if spec.SecurityContext != nil { sts.Spec.Template.Spec.SecurityContext = spec.SecurityContext } else { @@ -1409,7 +1404,7 @@ func (r *Reconciler) reconcileInstanceConfigMap( instanceConfigMap.Labels = naming.Merge( cluster.Spec.Metadata.GetLabelsOrNil(), spec.Metadata.GetLabelsOrNil(), - naming.WithPerconaLabels(map[string]string{ + naming.WithPerconaLabels(map[string]string{ // K8SPG-430 naming.LabelCluster: cluster.Name, naming.LabelInstanceSet: spec.Name, naming.LabelInstance: instance.Name, @@ -1455,7 +1450,7 @@ func (r *Reconciler) reconcileInstanceCertificates( instanceCerts.Labels = naming.Merge( cluster.Spec.Metadata.GetLabelsOrNil(), spec.Metadata.GetLabelsOrNil(), - naming.WithPerconaLabels(map[string]string{ + naming.WithPerconaLabels(map[string]string{ // K8SPG-430 naming.LabelCluster: cluster.Name, naming.LabelInstanceSet: spec.Name, naming.LabelInstance: instance.Name, diff --git a/internal/controller/postgrescluster/instance.md b/internal/controller/postgrescluster/instance.md index 933ca9bbe3..f0de4c5d7a 100644 --- a/internal/controller/postgrescluster/instance.md +++ b/internal/controller/postgrescluster/instance.md @@ -1,16 +1,7 @@ ## Shutdown and Startup Logic Detail diff --git a/internal/controller/postgrescluster/instance_rollout_test.go b/internal/controller/postgrescluster/instance_rollout_test.go index fcb9bc2ab3..c6b3f0cbcc 100644 --- a/internal/controller/postgrescluster/instance_rollout_test.go +++ b/internal/controller/postgrescluster/instance_rollout_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster diff --git a/internal/controller/postgrescluster/instance_test.go b/internal/controller/postgrescluster/instance_test.go index eb63b7bafb..74f6593384 100644 --- a/internal/controller/postgrescluster/instance_test.go +++ b/internal/controller/postgrescluster/instance_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -572,7 +561,7 @@ func TestAddPGBackRestToInstancePodSpec(t *testing.T) { // Only database container has mounts. // Other containers are ignored. - assert.Assert(t, marshalMatches(out.Containers, ` + assert.Assert(t, cmp.MarshalMatches(out.Containers, ` - name: database resources: {} volumeMounts: @@ -664,7 +653,7 @@ func TestAddPGBackRestToInstancePodSpec(t *testing.T) { // Instance configuration files with certificates. // Other volumes are ignored. - assert.Assert(t, marshalMatches(out.Volumes, ` + assert.Assert(t, cmp.MarshalMatches(out.Volumes, ` - name: other - name: postgres-data - name: postgres-wal @@ -712,7 +701,7 @@ func TestAddPGBackRestToInstancePodSpec(t *testing.T) { // Instance configuration files plus client and server certificates. // The server certificate comes from the instance Secret. // Other volumes are untouched. - assert.Assert(t, marshalMatches(result.Volumes, ` + assert.Assert(t, cmp.MarshalMatches(result.Volumes, ` - name: other - name: postgres-data - name: postgres-wal @@ -766,7 +755,7 @@ func TestAddPGBackRestToInstancePodSpec(t *testing.T) { // The TLS server is added and configuration mounted. // It has PostgreSQL volumes mounted while other volumes are ignored. - assert.Assert(t, marshalMatches(out.Containers, ` + assert.Assert(t, cmp.MarshalMatches(out.Containers, ` - name: database resources: {} volumeMounts: @@ -882,7 +871,7 @@ func TestAddPGBackRestToInstancePodSpec(t *testing.T) { assert.DeepEqual(t, before.Containers[:2], out.Containers[:2]) // It has the custom resources. - assert.Assert(t, marshalMatches(out.Containers[2:], ` + assert.Assert(t, cmp.MarshalMatches(out.Containers[2:], ` - command: - pgbackrest - server @@ -1583,7 +1572,7 @@ func TestGenerateInstanceStatefulSetIntent(t *testing.T) { name: "check default scheduling constraints are added", run: func(t *testing.T, ss *appsv1.StatefulSet) { assert.Equal(t, len(ss.Spec.Template.Spec.TopologySpreadConstraints), 2) - assert.Assert(t, marshalMatches(ss.Spec.Template.Spec.TopologySpreadConstraints, ` + assert.Assert(t, cmp.MarshalMatches(ss.Spec.Template.Spec.TopologySpreadConstraints, ` - labelSelector: matchExpressions: - key: postgres-operator.crunchydata.com/data @@ -1630,7 +1619,7 @@ func TestGenerateInstanceStatefulSetIntent(t *testing.T) { }, run: func(t *testing.T, ss *appsv1.StatefulSet) { assert.Equal(t, len(ss.Spec.Template.Spec.TopologySpreadConstraints), 3) - assert.Assert(t, marshalMatches(ss.Spec.Template.Spec.TopologySpreadConstraints, ` + assert.Assert(t, cmp.MarshalMatches(ss.Spec.Template.Spec.TopologySpreadConstraints, ` - labelSelector: matchExpressions: - key: postgres-operator.crunchydata.com/cluster @@ -1713,7 +1702,7 @@ func TestGenerateInstanceStatefulSetIntent(t *testing.T) { }, run: func(t *testing.T, ss *appsv1.StatefulSet) { assert.Equal(t, len(ss.Spec.Template.Spec.TopologySpreadConstraints), 1) - assert.Assert(t, marshalMatches(ss.Spec.Template.Spec.TopologySpreadConstraints, + assert.Assert(t, cmp.MarshalMatches(ss.Spec.Template.Spec.TopologySpreadConstraints, `- labelSelector: matchExpressions: - key: postgres-operator.crunchydata.com/cluster @@ -1990,7 +1979,7 @@ func TestReconcileInstanceSetPodDisruptionBudget(t *testing.T) { cluster := testCluster() cluster.Namespace = ns.Name spec := &cluster.Spec.InstanceSets[0] - spec.MinAvailable = initialize.IntOrStringInt32(0) + spec.MinAvailable = initialize.Pointer(intstr.FromInt32(0)) assert.NilError(t, r.reconcileInstanceSetPodDisruptionBudget(ctx, cluster, spec)) assert.Assert(t, !foundPDB(cluster, spec)) }) @@ -1999,7 +1988,7 @@ func TestReconcileInstanceSetPodDisruptionBudget(t *testing.T) { cluster := testCluster() cluster.Namespace = ns.Name spec := &cluster.Spec.InstanceSets[0] - spec.MinAvailable = initialize.IntOrStringInt32(1) + spec.MinAvailable = initialize.Pointer(intstr.FromInt32(1)) assert.NilError(t, r.Client.Create(ctx, cluster)) t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) }) @@ -2008,7 +1997,7 @@ func TestReconcileInstanceSetPodDisruptionBudget(t *testing.T) { assert.Assert(t, foundPDB(cluster, spec)) t.Run("deleted", func(t *testing.T) { - spec.MinAvailable = initialize.IntOrStringInt32(0) + spec.MinAvailable = initialize.Pointer(intstr.FromInt32(0)) err := r.reconcileInstanceSetPodDisruptionBudget(ctx, cluster, spec) if apierrors.IsConflict(err) { // When running in an existing environment another controller will sometimes update @@ -2026,7 +2015,7 @@ func TestReconcileInstanceSetPodDisruptionBudget(t *testing.T) { cluster := testCluster() cluster.Namespace = ns.Name spec := &cluster.Spec.InstanceSets[0] - spec.MinAvailable = initialize.IntOrStringString("50%") + spec.MinAvailable = initialize.Pointer(intstr.FromString("50%")) assert.NilError(t, r.Client.Create(ctx, cluster)) t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) }) @@ -2035,7 +2024,7 @@ func TestReconcileInstanceSetPodDisruptionBudget(t *testing.T) { assert.Assert(t, foundPDB(cluster, spec)) t.Run("deleted", func(t *testing.T) { - spec.MinAvailable = initialize.IntOrStringString("0%") + spec.MinAvailable = initialize.Pointer(intstr.FromString("0%")) err := r.reconcileInstanceSetPodDisruptionBudget(ctx, cluster, spec) if apierrors.IsConflict(err) { // When running in an existing environment another controller will sometimes update @@ -2049,13 +2038,13 @@ func TestReconcileInstanceSetPodDisruptionBudget(t *testing.T) { }) t.Run("delete with 00%", func(t *testing.T) { - spec.MinAvailable = initialize.IntOrStringString("50%") + spec.MinAvailable = initialize.Pointer(intstr.FromString("50%")) assert.NilError(t, r.reconcileInstanceSetPodDisruptionBudget(ctx, cluster, spec)) assert.Assert(t, foundPDB(cluster, spec)) t.Run("deleted", func(t *testing.T) { - spec.MinAvailable = initialize.IntOrStringString("00%") + spec.MinAvailable = initialize.Pointer(intstr.FromString("00%")) err := r.reconcileInstanceSetPodDisruptionBudget(ctx, cluster, spec) if apierrors.IsConflict(err) { // When running in an existing environment another controller will sometimes update @@ -2128,13 +2117,13 @@ func TestCleanupDisruptionBudgets(t *testing.T) { cluster := testCluster() cluster.Namespace = ns.Name spec := &cluster.Spec.InstanceSets[0] - spec.MinAvailable = initialize.IntOrStringInt32(1) + spec.MinAvailable = initialize.Pointer(intstr.FromInt32(1)) assert.NilError(t, r.Client.Create(ctx, cluster)) t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) }) expectedPDB := generatePDB(t, cluster, spec, - initialize.IntOrStringInt32(1)) + initialize.Pointer(intstr.FromInt32(1))) assert.NilError(t, createPDB(expectedPDB)) t.Run("no instances were removed", func(t *testing.T) { @@ -2147,7 +2136,7 @@ func TestCleanupDisruptionBudgets(t *testing.T) { leftoverPDB := generatePDB(t, cluster, &v1beta1.PostgresInstanceSetSpec{ Name: "old-instance", Replicas: initialize.Int32(1), - }, initialize.IntOrStringInt32(1)) + }, initialize.Pointer(intstr.FromInt32(1))) assert.NilError(t, createPDB(leftoverPDB)) assert.Assert(t, foundPDB(expectedPDB)) diff --git a/internal/controller/postgrescluster/patroni.go b/internal/controller/postgrescluster/patroni.go index 528a3936cd..09f74ca876 100644 --- a/internal/controller/postgrescluster/patroni.go +++ b/internal/controller/postgrescluster/patroni.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -160,7 +149,7 @@ func (r *Reconciler) reconcilePatroniDistributedConfiguration( cluster.Spec.Metadata.GetAnnotationsOrNil()) dcsService.Labels = naming.Merge( cluster.Spec.Metadata.GetLabelsOrNil(), - naming.WithPerconaLabels(map[string]string{ + naming.WithPerconaLabels(map[string]string{ // K8SPG-430 naming.LabelCluster: cluster.Name, naming.LabelPatroni: naming.PatroniScope(cluster), }, cluster.Name, "", cluster.Labels[naming.LabelVersion])) @@ -247,7 +236,7 @@ func (r *Reconciler) generatePatroniLeaderLeaseService( // add our labels last so they aren't overwritten service.Labels = naming.Merge(service.Labels, - naming.WithPerconaLabels(map[string]string{ + naming.WithPerconaLabels(map[string]string{ // K8SPG-430 naming.LabelCluster: cluster.Name, naming.LabelPatroni: naming.PatroniScope(cluster), }, cluster.Name, "", cluster.Labels[naming.LabelVersion])) @@ -271,6 +260,7 @@ func (r *Reconciler) generatePatroniLeaderLeaseService( service.Spec.Type = corev1.ServiceTypeClusterIP } else { service.Spec.Type = corev1.ServiceType(spec.Type) + // K8SPG-389 service.Spec.LoadBalancerSourceRanges = spec.LoadBalancerSourceRanges if spec.NodePort != nil { @@ -287,6 +277,8 @@ func (r *Reconciler) generatePatroniLeaderLeaseService( } servicePort.NodePort = *spec.NodePort } + service.Spec.ExternalTrafficPolicy = initialize.FromPointer(spec.ExternalTrafficPolicy) + service.Spec.InternalTrafficPolicy = spec.InternalTrafficPolicy } service.Spec.Ports = []corev1.ServicePort{servicePort} diff --git a/internal/controller/postgrescluster/patroni_test.go b/internal/controller/postgrescluster/patroni_test.go index 2113fc91dd..3351b199b3 100644 --- a/internal/controller/postgrescluster/patroni_test.go +++ b/internal/controller/postgrescluster/patroni_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -37,6 +26,7 @@ import ( "github.com/percona/percona-postgresql-operator/internal/initialize" "github.com/percona/percona-postgresql-operator/internal/naming" + "github.com/percona/percona-postgresql-operator/internal/testing/cmp" "github.com/percona/percona-postgresql-operator/internal/testing/require" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -60,11 +50,11 @@ func TestGeneratePatroniLeaderLeaseService(t *testing.T) { } alwaysExpect := func(t testing.TB, service *corev1.Service) { - assert.Assert(t, marshalMatches(service.TypeMeta, ` + assert.Assert(t, cmp.MarshalMatches(service.TypeMeta, ` apiVersion: v1 kind: Service `)) - assert.Assert(t, marshalMatches(service.ObjectMeta, ` + assert.Assert(t, cmp.MarshalMatches(service.ObjectMeta, ` creationTimestamp: null labels: app.kubernetes.io/instance: pg2 @@ -96,7 +86,7 @@ ownerReferences: alwaysExpect(t, service) // Defaults to ClusterIP. assert.Equal(t, service.Spec.Type, corev1.ServiceTypeClusterIP) - assert.Assert(t, marshalMatches(service.Spec.Ports, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Ports, ` - name: postgres port: 9876 protocol: TCP @@ -188,7 +178,7 @@ ownerReferences: assert.NilError(t, err) alwaysExpect(t, service) test.Expect(t, service) - assert.Assert(t, marshalMatches(service.Spec.Ports, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Ports, ` - name: postgres port: 9876 protocol: TCP @@ -213,7 +203,7 @@ ownerReferences: assert.NilError(t, err) alwaysExpect(t, service) assert.Equal(t, service.Spec.Type, corev1.ServiceTypeNodePort) - assert.Assert(t, marshalMatches(service.Spec.Ports, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Ports, ` - name: postgres nodePort: 32001 port: 9876 @@ -226,7 +216,7 @@ ownerReferences: assert.Equal(t, service.Spec.Type, corev1.ServiceTypeLoadBalancer) assert.NilError(t, err) alwaysExpect(t, service) - assert.Assert(t, marshalMatches(service.Spec.Ports, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Ports, ` - name: postgres nodePort: 32002 port: 9876 diff --git a/internal/controller/postgrescluster/pgadmin.go b/internal/controller/postgrescluster/pgadmin.go index db40d670fc..d4e16767f4 100644 --- a/internal/controller/postgrescluster/pgadmin.go +++ b/internal/controller/postgrescluster/pgadmin.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -179,6 +168,7 @@ func (r *Reconciler) generatePGAdminService( service.Spec.Type = corev1.ServiceTypeClusterIP } else { service.Spec.Type = corev1.ServiceType(spec.Type) + // K8SPG-389 service.Spec.LoadBalancerSourceRanges = spec.LoadBalancerSourceRanges if spec.NodePort != nil { if service.Spec.Type == corev1.ServiceTypeClusterIP { @@ -194,6 +184,8 @@ func (r *Reconciler) generatePGAdminService( } servicePort.NodePort = *spec.NodePort } + service.Spec.ExternalTrafficPolicy = initialize.FromPointer(spec.ExternalTrafficPolicy) + service.Spec.InternalTrafficPolicy = spec.InternalTrafficPolicy } service.Spec.Ports = []corev1.ServicePort{servicePort} @@ -305,11 +297,8 @@ func (r *Reconciler) reconcilePGAdminStatefulSet( // Use scheduling constraints from the cluster spec. sts.Spec.Template.Spec.Affinity = cluster.Spec.UserInterface.PGAdmin.Affinity sts.Spec.Template.Spec.Tolerations = cluster.Spec.UserInterface.PGAdmin.Tolerations - - if cluster.Spec.UserInterface.PGAdmin.PriorityClassName != nil { - sts.Spec.Template.Spec.PriorityClassName = *cluster.Spec.UserInterface.PGAdmin.PriorityClassName - } - + sts.Spec.Template.Spec.PriorityClassName = + initialize.FromPointer(cluster.Spec.UserInterface.PGAdmin.PriorityClassName) sts.Spec.Template.Spec.TopologySpreadConstraints = cluster.Spec.UserInterface.PGAdmin.TopologySpreadConstraints @@ -361,7 +350,7 @@ func (r *Reconciler) reconcilePGAdminStatefulSet( // add nss_wrapper init container and add nss_wrapper env vars to the pgAdmin // container addNSSWrapper( - cluster, + cluster, // K8SPG-260 config.PGAdminContainerImage(cluster), cluster.Spec.ImagePullPolicy, &sts.Spec.Template) diff --git a/internal/controller/postgrescluster/pgadmin_test.go b/internal/controller/postgrescluster/pgadmin_test.go index 2fad4f9e04..a52752c5c3 100644 --- a/internal/controller/postgrescluster/pgadmin_test.go +++ b/internal/controller/postgrescluster/pgadmin_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -164,7 +153,7 @@ func TestGeneratePGAdminService(t *testing.T) { assert.NilError(t, err) assert.Assert(t, !specified) - assert.Assert(t, marshalMatches(service.ObjectMeta, ` + assert.Assert(t, cmp.MarshalMatches(service.ObjectMeta, ` creationTimestamp: null name: my-cluster-pgadmin namespace: my-ns @@ -177,11 +166,11 @@ namespace: my-ns } alwaysExpect := func(t testing.TB, service *corev1.Service) { - assert.Assert(t, marshalMatches(service.TypeMeta, ` + assert.Assert(t, cmp.MarshalMatches(service.TypeMeta, ` apiVersion: v1 kind: Service `)) - assert.Assert(t, marshalMatches(service.ObjectMeta, ` + assert.Assert(t, cmp.MarshalMatches(service.ObjectMeta, ` creationTimestamp: null labels: app.kubernetes.io/instance: my-cluster @@ -279,7 +268,7 @@ ownerReferences: alwaysExpect(t, service) // Defaults to ClusterIP. assert.Equal(t, service.Spec.Type, corev1.ServiceTypeClusterIP) - assert.Assert(t, marshalMatches(service.Spec.Ports, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Ports, ` - name: pgadmin port: 5050 protocol: TCP @@ -312,7 +301,7 @@ ownerReferences: assert.Assert(t, specified) alwaysExpect(t, service) test.Expect(t, service) - assert.Assert(t, marshalMatches(service.Spec.Ports, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Ports, ` - name: pgadmin port: 5050 protocol: TCP @@ -337,7 +326,7 @@ ownerReferences: assert.NilError(t, err) assert.Equal(t, service.Spec.Type, corev1.ServiceTypeNodePort) alwaysExpect(t, service) - assert.Assert(t, marshalMatches(service.Spec.Ports, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Ports, ` - name: pgadmin nodePort: 32001 port: 5050 @@ -350,7 +339,7 @@ ownerReferences: assert.NilError(t, err) assert.Equal(t, service.Spec.Type, corev1.ServiceTypeLoadBalancer) alwaysExpect(t, service) - assert.Assert(t, marshalMatches(service.Spec.Ports, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Ports, ` - name: pgadmin nodePort: 32002 port: 5050 @@ -729,7 +718,7 @@ func TestReconcilePGAdminDataVolume(t *testing.T) { assert.Equal(t, pvc.Labels[naming.LabelRole], naming.RolePGAdmin) assert.Equal(t, pvc.Labels[naming.LabelData], naming.DataPGAdmin) - assert.Assert(t, marshalMatches(pvc.Spec, ` + assert.Assert(t, cmp.MarshalMatches(pvc.Spec, ` accessModes: - ReadWriteOnce resources: diff --git a/internal/controller/postgrescluster/pgbackrest.go b/internal/controller/postgrescluster/pgbackrest.go index 5cf4b72e26..61587e0943 100644 --- a/internal/controller/postgrescluster/pgbackrest.go +++ b/internal/controller/postgrescluster/pgbackrest.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -19,6 +8,7 @@ import ( "context" "fmt" "io" + "reflect" "regexp" "sort" "strings" @@ -43,6 +33,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/percona/percona-postgresql-operator/internal/config" + "github.com/percona/percona-postgresql-operator/internal/feature" "github.com/percona/percona-postgresql-operator/internal/initialize" "github.com/percona/percona-postgresql-operator/internal/logging" "github.com/percona/percona-postgresql-operator/internal/naming" @@ -119,11 +110,14 @@ var regexRepoIndex = regexp.MustCompile(`\d+`) // RepoResources is used to store various resources for pgBackRest repositories and // repository hosts type RepoResources struct { + hosts []*appsv1.StatefulSet cronjobs []*batchv1.CronJob manualBackupJobs []*batchv1.Job replicaCreateBackupJobs []*batchv1.Job - hosts []*appsv1.StatefulSet pvcs []*corev1.PersistentVolumeClaim + sas []*corev1.ServiceAccount + roles []*rbacv1.Role + rolebindings []*rbacv1.RoleBinding } // applyRepoHostIntent ensures the pgBackRest repository host StatefulSet is synchronized with the @@ -194,24 +188,44 @@ func (r *Reconciler) applyRepoVolumeIntent(ctx context.Context, return repo, nil } +// +kubebuilder:rbac:groups="apps",resources="statefulsets",verbs={list} +// +kubebuilder:rbac:groups="batch",resources="cronjobs",verbs={list} +// +kubebuilder:rbac:groups="batch",resources="jobs",verbs={list} +// +kubebuilder:rbac:groups="",resources="configmaps",verbs={list} +// +kubebuilder:rbac:groups="",resources="persistentvolumeclaims",verbs={list} +// +kubebuilder:rbac:groups="",resources="secrets",verbs={list} +// +kubebuilder:rbac:groups="",resources="serviceaccounts",verbs={list} +// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources="roles",verbs={list} +// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources="rolebindings",verbs={list} + // getPGBackRestResources returns the existing pgBackRest resources that should utilized by the // PostgresCluster controller during reconciliation. Any items returned are verified to be owned // by the PostgresCluster controller and still applicable per the current PostgresCluster spec. -// Additionally, and resources identified that no longer correspond to any current configuration +// Additionally, any resources identified that no longer correspond to any current configuration // are deleted. func (r *Reconciler) getPGBackRestResources(ctx context.Context, - postgresCluster *v1beta1.PostgresCluster) (*RepoResources, error) { + postgresCluster *v1beta1.PostgresCluster, + backupsSpecFound bool, +) (*RepoResources, error) { repoResources := &RepoResources{} gvks := []schema.GroupVersionKind{{ - Group: corev1.SchemeGroupVersion.Group, - Version: corev1.SchemeGroupVersion.Version, - Kind: "ConfigMapList", + Group: appsv1.SchemeGroupVersion.Group, + Version: appsv1.SchemeGroupVersion.Version, + Kind: "StatefulSetList", + }, { + Group: batchv1.SchemeGroupVersion.Group, + Version: batchv1.SchemeGroupVersion.Version, + Kind: "CronJobList", }, { Group: batchv1.SchemeGroupVersion.Group, Version: batchv1.SchemeGroupVersion.Version, Kind: "JobList", + }, { + Group: corev1.SchemeGroupVersion.Group, + Version: corev1.SchemeGroupVersion.Version, + Kind: "ConfigMapList", }, { Group: corev1.SchemeGroupVersion.Group, Version: corev1.SchemeGroupVersion.Version, @@ -221,13 +235,17 @@ func (r *Reconciler) getPGBackRestResources(ctx context.Context, Version: corev1.SchemeGroupVersion.Version, Kind: "SecretList", }, { - Group: appsv1.SchemeGroupVersion.Group, - Version: appsv1.SchemeGroupVersion.Version, - Kind: "StatefulSetList", + Group: corev1.SchemeGroupVersion.Group, + Version: corev1.SchemeGroupVersion.Version, + Kind: "ServiceAccountList", }, { - Group: batchv1.SchemeGroupVersion.Group, - Version: batchv1.SchemeGroupVersion.Version, - Kind: "CronJobList", + Group: rbacv1.SchemeGroupVersion.Group, + Version: rbacv1.SchemeGroupVersion.Version, + Kind: "RoleList", + }, { + Group: rbacv1.SchemeGroupVersion.Group, + Version: rbacv1.SchemeGroupVersion.Version, + Kind: "RoleBindingList", }} selector := naming.PGBackRestSelector(postgresCluster.GetName()) @@ -243,7 +261,7 @@ func (r *Reconciler) getPGBackRestResources(ctx context.Context, continue } - owned, err := r.cleanupRepoResources(ctx, postgresCluster, uList.Items) + owned, err := r.cleanupRepoResources(ctx, postgresCluster, uList.Items, backupsSpecFound) if err != nil { return nil, errors.WithStack(err) } @@ -265,8 +283,11 @@ func (r *Reconciler) getPGBackRestResources(ctx context.Context, } // +kubebuilder:rbac:groups="",resources="persistentvolumeclaims",verbs={delete} +// +kubebuilder:rbac:groups="",resources="serviceaccounts",verbs={delete} // +kubebuilder:rbac:groups="apps",resources="statefulsets",verbs={delete} // +kubebuilder:rbac:groups="batch",resources="cronjobs",verbs={delete} +// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources="roles",verbs={delete} +// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources="rolebindings",verbs={delete} // cleanupRepoResources cleans up pgBackRest repository resources that should no longer be // reconciled by deleting them. This includes deleting repos (i.e. PersistentVolumeClaims) that @@ -274,7 +295,9 @@ func (r *Reconciler) getPGBackRestResources(ctx context.Context, // pgBackRest repository host resources if a repository host is no longer configured. func (r *Reconciler) cleanupRepoResources(ctx context.Context, postgresCluster *v1beta1.PostgresCluster, - ownedResources []unstructured.Unstructured) ([]unstructured.Unstructured, error) { + ownedResources []unstructured.Unstructured, + backupsSpecFound bool, +) ([]unstructured.Unstructured, error) { // stores the resources that should not be deleted ownedNoDelete := []unstructured.Unstructured{} @@ -289,11 +312,17 @@ func (r *Reconciler) cleanupRepoResources(ctx context.Context, // spec switch { case hasLabel(naming.LabelPGBackRestConfig): + if !backupsSpecFound { + break + } // Simply add the things we never want to delete (e.g. the pgBackRest configuration) // to the slice and do not delete ownedNoDelete = append(ownedNoDelete, owned) delete = false case hasLabel(naming.LabelPGBackRestDedicated): + if !backupsSpecFound { + break + } // Any resources from before 5.1 that relate to the previously required // SSH configuration should be deleted. // TODO(tjmoore4): This can be removed once 5.0 is EOL. @@ -305,6 +334,9 @@ func (r *Reconciler) cleanupRepoResources(ctx context.Context, delete = false } case hasLabel(naming.LabelPGBackRestRepoVolume): + if !backupsSpecFound { + break + } // If a volume (PVC) is identified for a repo that no longer exists in the // spec then delete it. Otherwise add it to the slice and continue. for _, repo := range postgresCluster.Spec.Backups.PGBackRest.Repos { @@ -317,6 +349,9 @@ func (r *Reconciler) cleanupRepoResources(ctx context.Context, } } case hasLabel(naming.LabelPGBackRestBackup): + if !backupsSpecFound { + break + } // If a Job is identified for a repo that no longer exists in the spec then // delete it. Otherwise add it to the slice and continue. for _, repo := range postgresCluster.Spec.Backups.PGBackRest.Repos { @@ -326,6 +361,9 @@ func (r *Reconciler) cleanupRepoResources(ctx context.Context, } } case hasLabel(naming.LabelPGBackRestCronJob): + if !backupsSpecFound { + break + } for _, repo := range postgresCluster.Spec.Backups.PGBackRest.Repos { if repo.Name == owned.GetLabels()[naming.LabelPGBackRestRepo] { if backupScheduleFound(repo, @@ -337,6 +375,18 @@ func (r *Reconciler) cleanupRepoResources(ctx context.Context, } } case hasLabel(naming.LabelPGBackRestRestore): + if !backupsSpecFound { + break + } + + // If the restore job has the PGBackRestBackupJobCompletion annotation, it is + // used for volume snapshots and should not be deleted (volume snapshots code + // will clean it up when appropriate). + if _, ok := owned.GetAnnotations()[naming.PGBackRestBackupJobCompletion]; ok { + ownedNoDelete = append(ownedNoDelete, owned) + delete = false + } + // When a cluster is prepared for restore, the system identifier is removed from status // and the cluster is therefore no longer bootstrapped. Only once the restore Job is // complete will the cluster then be bootstrapped again, which means by the time we @@ -346,6 +396,12 @@ func (r *Reconciler) cleanupRepoResources(ctx context.Context, ownedNoDelete = append(ownedNoDelete, owned) delete = false } + case hasLabel(naming.LabelPGBackRest): + if !backupsSpecFound { + break + } + ownedNoDelete = append(ownedNoDelete, owned) + delete = false } // If nothing has specified that the resource should not be deleted, then delete @@ -385,6 +441,24 @@ func unstructuredToRepoResources(kind string, repoResources *RepoResources, uList *unstructured.UnstructuredList) error { switch kind { + case "StatefulSetList": + var stsList appsv1.StatefulSetList + if err := runtime.DefaultUnstructuredConverter. + FromUnstructured(uList.UnstructuredContent(), &stsList); err != nil { + return errors.WithStack(err) + } + for i := range stsList.Items { + repoResources.hosts = append(repoResources.hosts, &stsList.Items[i]) + } + case "CronJobList": + var cronList batchv1.CronJobList + if err := runtime.DefaultUnstructuredConverter. + FromUnstructured(uList.UnstructuredContent(), &cronList); err != nil { + return errors.WithStack(err) + } + for i := range cronList.Items { + repoResources.cronjobs = append(repoResources.cronjobs, &cronList.Items[i]) + } case "JobList": var jobList batchv1.JobList if err := runtime.DefaultUnstructuredConverter. @@ -402,6 +476,9 @@ func unstructuredToRepoResources(kind string, repoResources *RepoResources, append(repoResources.manualBackupJobs, &jobList.Items[i]) } } + case "ConfigMapList": + // Repository host now uses mTLS for encryption, authentication, and authorization. + // Configmaps for SSHD are no longer managed here. case "PersistentVolumeClaimList": var pvcList corev1.PersistentVolumeClaimList if err := runtime.DefaultUnstructuredConverter. @@ -411,32 +488,38 @@ func unstructuredToRepoResources(kind string, repoResources *RepoResources, for i := range pvcList.Items { repoResources.pvcs = append(repoResources.pvcs, &pvcList.Items[i]) } - case "StatefulSetList": - var stsList appsv1.StatefulSetList + case "SecretList": + // Repository host now uses mTLS for encryption, authentication, and authorization. + // Secrets for SSHD are no longer managed here. + // TODO(tjmoore4): Consider adding all pgBackRest secrets to RepoResources to + // observe all pgBackRest secrets in one place. + case "ServiceAccountList": + var saList corev1.ServiceAccountList if err := runtime.DefaultUnstructuredConverter. - FromUnstructured(uList.UnstructuredContent(), &stsList); err != nil { + FromUnstructured(uList.UnstructuredContent(), &saList); err != nil { return errors.WithStack(err) } - for i := range stsList.Items { - repoResources.hosts = append(repoResources.hosts, &stsList.Items[i]) + for i := range saList.Items { + repoResources.sas = append(repoResources.sas, &saList.Items[i]) } - case "CronJobList": - var cronList batchv1.CronJobList + case "RoleList": + var roleList rbacv1.RoleList if err := runtime.DefaultUnstructuredConverter. - FromUnstructured(uList.UnstructuredContent(), &cronList); err != nil { + FromUnstructured(uList.UnstructuredContent(), &roleList); err != nil { return errors.WithStack(err) } - for i := range cronList.Items { - repoResources.cronjobs = append(repoResources.cronjobs, &cronList.Items[i]) + for i := range roleList.Items { + repoResources.roles = append(repoResources.roles, &roleList.Items[i]) + } + case "RoleBindingList": + var rb rbacv1.RoleBindingList + if err := runtime.DefaultUnstructuredConverter. + FromUnstructured(uList.UnstructuredContent(), &rb); err != nil { + return errors.WithStack(err) + } + for i := range rb.Items { + repoResources.rolebindings = append(repoResources.rolebindings, &rb.Items[i]) } - case "ConfigMapList": - // Repository host now uses mTLS for encryption, authentication, and authorization. - // Configmaps for SSHD are no longer managed here. - case "SecretList": - // Repository host now uses mTLS for encryption, authentication, and authorization. - // Secrets for SSHD are no longer managed here. - // TODO(tjmoore4): Consider adding all pgBackRest secrets to RepoResources to - // observe all pgBackRest secrets in one place. default: return fmt.Errorf("unexpected kind %q", kind) } @@ -541,16 +624,12 @@ func (r *Reconciler) generateRepoHostIntent(ctx context.Context, postgresCluster repo.Spec.Template.Spec.Affinity = repoHost.Affinity repo.Spec.Template.Spec.Tolerations = repoHost.Tolerations repo.Spec.Template.Spec.TopologySpreadConstraints = repoHost.TopologySpreadConstraints - if repoHost.PriorityClassName != nil { - repo.Spec.Template.Spec.PriorityClassName = *repoHost.PriorityClassName - } + repo.Spec.Template.Spec.PriorityClassName = initialize.FromPointer(repoHost.PriorityClassName) } // if default pod scheduling is not explicitly disabled, add the default // pod topology spread constraints - if postgresCluster.Spec.DisableDefaultPodScheduling == nil || - (postgresCluster.Spec.DisableDefaultPodScheduling != nil && - !*postgresCluster.Spec.DisableDefaultPodScheduling) { + if !initialize.FromPointer(postgresCluster.Spec.DisableDefaultPodScheduling) { repo.Spec.Template.Spec.TopologySpreadConstraints = append( repo.Spec.Template.Spec.TopologySpreadConstraints, defaultTopologySpreadConstraints( @@ -607,6 +686,7 @@ func (r *Reconciler) generateRepoHostIntent(ctx context.Context, postgresCluster // ServiceAccount and do not mount its credentials. repo.Spec.Template.Spec.AutomountServiceAccountToken = initialize.Bool(false) + // K8SPG-138 currVersion, err := gover.NewVersion(postgresCluster.Labels[naming.LabelVersion]) if err == nil && currVersion.GreaterThanOrEqual(gover.Must(gover.NewVersion("2.4.0"))) { repo.Spec.Template.Spec.ServiceAccountName = naming.PGBackRestRBAC(postgresCluster).Name @@ -640,11 +720,12 @@ func (r *Reconciler) generateRepoHostIntent(ctx context.Context, postgresCluster // add nss_wrapper init container and add nss_wrapper env vars to the pgbackrest // container addNSSWrapper( - postgresCluster, + postgresCluster, // K8SPG-260 config.PGBackRestContainerImage(postgresCluster), postgresCluster.Spec.ImagePullPolicy, &repo.Spec.Template) + // K8SPG-435 resources := corev1.ResourceRequirements{} if postgresCluster.Spec.Backups.PGBackRest.RepoHost != nil { resources = postgresCluster.Spec.Backups.PGBackRest.RepoHost.Resources @@ -662,7 +743,6 @@ func (r *Reconciler) generateRepoHostIntent(ctx context.Context, postgresCluster return repo, nil } -// nolint:unparam func (r *Reconciler) generateRepoVolumeIntent(postgresCluster *v1beta1.PostgresCluster, spec corev1.PersistentVolumeClaimSpec, repoName string, repoResources *RepoResources) (*corev1.PersistentVolumeClaim, error) { @@ -713,7 +793,7 @@ func (r *Reconciler) generateRepoVolumeIntent(postgresCluster *v1beta1.PostgresC } // generateBackupJobSpecIntent generates a JobSpec for a pgBackRest backup job -func generateBackupJobSpecIntent(postgresCluster *v1beta1.PostgresCluster, +func generateBackupJobSpecIntent(ctx context.Context, postgresCluster *v1beta1.PostgresCluster, repo v1beta1.PGBackRestRepo, serviceAccountName string, labels, annotations map[string]string, opts ...string) *batchv1.JobSpec { @@ -722,6 +802,11 @@ func generateBackupJobSpecIntent(postgresCluster *v1beta1.PostgresCluster, "--stanza=" + pgbackrest.DefaultStanzaName, "--repo=" + repoIndex, } + // If VolumeSnapshots are enabled, use archive-copy and archive-check options + if postgresCluster.Spec.Backups.Snapshots != nil && feature.Enabled(ctx, feature.VolumeSnapshots) { + cmdOpts = append(cmdOpts, "--archive-copy=y", "--archive-check=y") + } + cmdOpts = append(cmdOpts, opts...) container := corev1.Container{ @@ -737,7 +822,7 @@ func generateBackupJobSpecIntent(postgresCluster *v1beta1.PostgresCluster, Image: config.PGBackRestContainerImage(postgresCluster), ImagePullPolicy: postgresCluster.Spec.ImagePullPolicy, Name: naming.PGBackRestRepoContainerName, - SecurityContext: initialize.RestrictedSecurityContext(postgresCluster.CompareVersion("2.5.0") >= 0), + SecurityContext: initialize.RestrictedSecurityContext(postgresCluster.CompareVersion("2.5.0") >= 0), // K8SPG-260 } if postgresCluster.Spec.Backups.PGBackRest.Jobs != nil { @@ -772,12 +857,10 @@ func generateBackupJobSpecIntent(postgresCluster *v1beta1.PostgresCluster, // set the priority class name, tolerations, and affinity, if they exist if postgresCluster.Spec.Backups.PGBackRest.Jobs != nil { - if postgresCluster.Spec.Backups.PGBackRest.Jobs.PriorityClassName != nil { - jobSpec.Template.Spec.PriorityClassName = - *postgresCluster.Spec.Backups.PGBackRest.Jobs.PriorityClassName - } jobSpec.Template.Spec.Tolerations = postgresCluster.Spec.Backups.PGBackRest.Jobs.Tolerations jobSpec.Template.Spec.Affinity = postgresCluster.Spec.Backups.PGBackRest.Jobs.Affinity + jobSpec.Template.Spec.PriorityClassName = + initialize.FromPointer(postgresCluster.Spec.Backups.PGBackRest.Jobs.PriorityClassName) } // Set the image pull secrets, if any exist. @@ -1194,11 +1277,12 @@ func (r *Reconciler) reconcileRestoreJob(ctx context.Context, // add nss_wrapper init container and add nss_wrapper env vars to the pgbackrest restore // container addNSSWrapper( - cluster, + cluster, // K8SPG-260 config.PGBackRestContainerImage(cluster), cluster.Spec.ImagePullPolicy, &restoreJob.Spec.Template) + // K8SPG-435 resources := corev1.ResourceRequirements{} if cluster.Spec.Backups.PGBackRest.Restore != nil { resources = cluster.Spec.Backups.PGBackRest.Restore.Resources @@ -1247,7 +1331,7 @@ func (r *Reconciler) generateRestoreJobIntent(cluster *v1beta1.PostgresCluster, Name: naming.PGBackRestRestoreContainerName, VolumeMounts: volumeMounts, Env: []corev1.EnvVar{{Name: "PGHOST", Value: "/tmp"}}, - SecurityContext: initialize.RestrictedSecurityContext(cluster.CompareVersion("2.5.0") >= 0), + SecurityContext: initialize.RestrictedSecurityContext(cluster.CompareVersion("2.5.0") >= 0), // K8SPG-260 Resources: dataSource.Resources, }}, RestartPolicy: corev1.RestartPolicyNever, @@ -1275,6 +1359,7 @@ func (r *Reconciler) generateRestoreJobIntent(cluster *v1beta1.PostgresCluster, // Do not add environment variables describing services in this namespace. job.Spec.Template.Spec.EnableServiceLinks = initialize.Bool(false) + // K8SPG-514 if pgbackrest := cluster.Spec.Backups.PGBackRest; pgbackrest.Jobs != nil && pgbackrest.Jobs.SecurityContext != nil { job.Spec.Template.Spec.SecurityContext = cluster.Spec.Backups.PGBackRest.Jobs.SecurityContext } else { @@ -1302,13 +1387,15 @@ func (r *Reconciler) generateRestoreJobIntent(cluster *v1beta1.PostgresCluster, func (r *Reconciler) reconcilePGBackRest(ctx context.Context, postgresCluster *v1beta1.PostgresCluster, instances *observedInstances, - rootCA *pki.RootCertificateAuthority) (reconcile.Result, error) { + rootCA *pki.RootCertificateAuthority, + backupsSpecFound bool, +) (reconcile.Result, error) { // add some additional context about what component is being reconciled log := logging.FromContext(ctx).WithValues("reconciler", "pgBackRest") - // if nil, create the pgBackRest status that will be updated when reconciling various - // pgBackRest resources + // if nil, create the pgBackRest status that will be updated when + // reconciling various pgBackRest resources if postgresCluster.Status.PGBackRest == nil { postgresCluster.Status.PGBackRest = &v1beta1.PGBackRestStatus{} } @@ -1319,12 +1406,19 @@ func (r *Reconciler) reconcilePGBackRest(ctx context.Context, // Get all currently owned pgBackRest resources in the environment as needed for // reconciliation. This includes deleting resources that should no longer exist per the // current spec (e.g. if repos, repo hosts, etc. have been removed). - repoResources, err := r.getPGBackRestResources(ctx, postgresCluster) + repoResources, err := r.getPGBackRestResources(ctx, postgresCluster, backupsSpecFound) if err != nil { // exit early if can't get and clean existing resources as needed to reconcile return reconcile.Result{}, errors.WithStack(err) } + // At this point, reconciliation is allowed, so if no backups spec is found + // clear the status and exit + if !backupsSpecFound { + postgresCluster.Status.PGBackRest = &v1beta1.PGBackRestStatus{} + return result, nil + } + var repoHost *appsv1.StatefulSet var repoHostName string // reconcile the pgbackrest repository host @@ -1445,7 +1539,9 @@ func (r *Reconciler) reconcilePGBackRest(ctx context.Context, func (r *Reconciler) reconcilePostgresClusterDataSource(ctx context.Context, cluster *v1beta1.PostgresCluster, dataSource *v1beta1.PostgresClusterDataSource, configHash string, clusterVolumes []corev1.PersistentVolumeClaim, - rootCA *pki.RootCertificateAuthority) error { + rootCA *pki.RootCertificateAuthority, + backupsSpecFound bool, +) error { // grab cluster, namespaces and repo name information from the data source sourceClusterName := dataSource.ClusterName @@ -1527,7 +1623,7 @@ func (r *Reconciler) reconcilePostgresClusterDataSource(ctx context.Context, // Note that function reconcilePGBackRest only uses forCluster in observedInstances. result, err := r.reconcilePGBackRest(ctx, cluster, &observedInstances{ forCluster: []*Instance{instance}, - }, rootCA) + }, rootCA, backupsSpecFound) if err != nil || result != (reconcile.Result{}) { return fmt.Errorf("unable to reconcile pgBackRest as needed to initialize "+ "PostgreSQL data for the cluster: %w", err) @@ -1573,7 +1669,7 @@ func (r *Reconciler) reconcilePostgresClusterDataSource(ctx context.Context, Namespace: cluster.GetNamespace(), }} // Reconcile the PGDATA and WAL volumes for the restore - pgdata, err := r.reconcilePostgresDataVolume(ctx, cluster, instanceSet, fakeSTS, clusterVolumes) + pgdata, err := r.reconcilePostgresDataVolume(ctx, cluster, instanceSet, fakeSTS, clusterVolumes, sourceCluster) if err != nil { return errors.WithStack(err) } @@ -1587,6 +1683,9 @@ func (r *Reconciler) reconcilePostgresClusterDataSource(ctx context.Context, return errors.WithStack(err) } + // TODO(snapshots): If pgdata is being sourced by a VolumeSnapshot then don't perform a typical restore job; + // we only want to replay the WAL. + // reconcile the pgBackRest restore Job to populate the cluster's data directory if err := r.reconcileRestoreJob(ctx, cluster, sourceCluster, pgdata, pgwal, pgtablespaces, dataSource, instanceName, instanceSetName, configHash, pgbackrest.DefaultStanzaName); err != nil { @@ -1668,7 +1767,7 @@ func (r *Reconciler) reconcileCloudBasedDataSource(ctx context.Context, Namespace: cluster.GetNamespace(), }} // Reconcile the PGDATA and WAL volumes for the restore - pgdata, err := r.reconcilePostgresDataVolume(ctx, cluster, instanceSet, fakeSTS, clusterVolumes) + pgdata, err := r.reconcilePostgresDataVolume(ctx, cluster, instanceSet, fakeSTS, clusterVolumes, nil) if err != nil { return errors.WithStack(err) } @@ -2324,7 +2423,7 @@ func (r *Reconciler) reconcileManualBackup(ctx context.Context, backupJob.ObjectMeta.Labels = labels backupJob.ObjectMeta.Annotations = annotations - spec := generateBackupJobSpecIntent(postgresCluster, repo, + spec := generateBackupJobSpecIntent(ctx, postgresCluster, repo, serviceAccount.GetName(), labels, annotations, backupOpts...) backupJob.Spec = *spec @@ -2491,7 +2590,7 @@ func (r *Reconciler) reconcileReplicaCreateBackup(ctx context.Context, backupJob.ObjectMeta.Labels = labels backupJob.ObjectMeta.Annotations = annotations - spec := generateBackupJobSpecIntent(postgresCluster, replicaCreateRepo, + spec := generateBackupJobSpecIntent(ctx, postgresCluster, replicaCreateRepo, serviceAccount.GetName(), labels, annotations, // K8SPG-506: adding label to get info about this backup in `pgbackrest info` fmt.Sprintf(`--annotation="%s"="%s"`, v2.PGBackrestAnnotationJobType, naming.BackupReplicaCreate), @@ -2923,7 +3022,7 @@ func (r *Reconciler) reconcilePGBackRestCronJob( // set backup type (i.e. "full", "diff", "incr") backupOpts := []string{"--type=" + backupType} - jobSpec := generateBackupJobSpecIntent(cluster, repo, + jobSpec := generateBackupJobSpecIntent(ctx, cluster, repo, serviceAccount.GetName(), labels, annotations, backupOpts...) // Suspend cronjobs when shutdown or read-only. Any jobs that have already @@ -2970,3 +3069,94 @@ func (r *Reconciler) reconcilePGBackRestCronJob( } return err } + +// BackupsEnabled checks the state of the backups (i.e., if backups are in the spec, +// if a repo-host StatefulSet exists, if the annotation permitting backup deletion exists) +// and determines whether reconciliation is allowed. +// Reconciliation of backup-related Kubernetes objects is paused if +// - a user created a cluster with backups; +// - the cluster is updated to remove backups; +// - the annotation authorizing that removal is missing. +// +// This function also returns whether the spec has a defined backups or not. +func (r *Reconciler) BackupsEnabled( + ctx context.Context, + postgresCluster *v1beta1.PostgresCluster, +) ( + backupsSpecFound bool, + backupsReconciliationAllowed bool, + err error, +) { + specFound, stsNotFound, annotationFound, err := r.ObserveBackupUniverse(ctx, postgresCluster) + + switch { + case err != nil: + case specFound: + backupsSpecFound = true + backupsReconciliationAllowed = true + case annotationFound || stsNotFound: + backupsReconciliationAllowed = true + case !annotationFound && !stsNotFound: + // Destroying backups is a two key operation: + // 1. You must remove the backups section of the spec. + // 2. You must apply an annotation to the cluster. + // The existence of a StatefulSet without the backups spec is + // evidence of key 1 being turned without key 2 being turned + // -- block reconciliation until the annotation is added. + backupsReconciliationAllowed = false + default: + backupsReconciliationAllowed = false + } + return backupsSpecFound, backupsReconciliationAllowed, err +} + +// ObserveBackupUniverse returns +// - whether the spec has backups defined; +// - whether the repo-host statefulset exists; +// - whether the cluster has the annotation authorizing backup removal. +func (r *Reconciler) ObserveBackupUniverse(ctx context.Context, + postgresCluster *v1beta1.PostgresCluster, +) ( + backupsSpecFound bool, + repoHostStatefulSetNotFound bool, + backupsRemovalAnnotationFound bool, + err error, +) { + + // Does the cluster have a blank Backups section + backupsSpecFound = !reflect.DeepEqual(postgresCluster.Spec.Backups, v1beta1.Backups{PGBackRest: v1beta1.PGBackRestArchive{}}) + + // Does the repo-host StatefulSet exist? + name := fmt.Sprintf("%s-%s", postgresCluster.GetName(), "repo-host") + existing := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: postgresCluster.Namespace, + Name: name, + }, + } + err = errors.WithStack( + r.Client.Get(ctx, client.ObjectKeyFromObject(existing), existing)) + repoHostStatefulSetNotFound = apierrors.IsNotFound(err) + + // If we have an error that is not related to a missing repo-host StatefulSet, + // we return an error and expect the calling function to correctly stop processing. + if err != nil && !repoHostStatefulSetNotFound { + return true, false, false, err + } + + backupsRemovalAnnotationFound = authorizeBackupRemovalAnnotationPresent(postgresCluster) + + // If we have reached this point, the err is either nil or an IsNotFound error + // which we do not care about; hence, pass nil rather than the err + return backupsSpecFound, repoHostStatefulSetNotFound, backupsRemovalAnnotationFound, nil +} + +func authorizeBackupRemovalAnnotationPresent(postgresCluster *v1beta1.PostgresCluster) bool { + annotations := postgresCluster.GetAnnotations() + for annotation := range annotations { + if annotation == naming.AuthorizeBackupRemovalAnnotation { + return annotations[naming.AuthorizeBackupRemovalAnnotation] == "true" + } + } + return false +} diff --git a/internal/controller/postgrescluster/pgbackrest_test.go b/internal/controller/postgrescluster/pgbackrest_test.go index 1ab3f8c5c5..72ba3b99d6 100644 --- a/internal/controller/postgrescluster/pgbackrest_test.go +++ b/internal/controller/postgrescluster/pgbackrest_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -52,6 +41,7 @@ import ( "github.com/percona/percona-postgresql-operator/internal/naming" "github.com/percona/percona-postgresql-operator/internal/pgbackrest" "github.com/percona/percona-postgresql-operator/internal/pki" + "github.com/percona/percona-postgresql-operator/internal/testing/cmp" "github.com/percona/percona-postgresql-operator/internal/testing/require" v2 "github.com/percona/percona-postgresql-operator/pkg/apis/pgv2.percona.com/v2" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" @@ -198,137 +188,137 @@ func TestReconcilePGBackRest(t *testing.T) { }) t.Cleanup(func() { teardownManager(cancel, t) }) - clusterName := "hippocluster" - clusterUID := "hippouid" + t.Run("run reconcile with backups defined", func(t *testing.T) { + clusterName := "hippocluster" + clusterUID := "hippouid" - ns := setupNamespace(t, tClient) - - // create a PostgresCluster to test with - postgresCluster := fakePostgresCluster(clusterName, ns.GetName(), clusterUID, true) + ns := setupNamespace(t, tClient) + // create a PostgresCluster to test with + postgresCluster := fakePostgresCluster(clusterName, ns.GetName(), clusterUID, true) - // create a service account to test with - serviceAccount, err := r.reconcilePGBackRestRBAC(ctx, postgresCluster) - assert.NilError(t, err) - assert.Assert(t, serviceAccount != nil) + // create a service account to test with + serviceAccount, err := r.reconcilePGBackRestRBAC(ctx, postgresCluster) + assert.NilError(t, err) + assert.Assert(t, serviceAccount != nil) - // create the 'observed' instances and set the leader - instances := &observedInstances{ - forCluster: []*Instance{{Name: "instance1", - Pods: []*corev1.Pod{{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{naming.LabelRole: naming.RolePatroniLeader}, - }, - Spec: corev1.PodSpec{}, - }}, - }, {Name: "instance2"}, {Name: "instance3"}}, - } + // create the 'observed' instances and set the leader + instances := &observedInstances{ + forCluster: []*Instance{{Name: "instance1", + Pods: []*corev1.Pod{{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{naming.LabelRole: naming.RolePatroniLeader}, + }, + Spec: corev1.PodSpec{}, + }}, + }, {Name: "instance2"}, {Name: "instance3"}}, + } - // set status - postgresCluster.Status = v1beta1.PostgresClusterStatus{ - Patroni: v1beta1.PatroniStatus{SystemIdentifier: "12345abcde"}, - PGBackRest: &v1beta1.PGBackRestStatus{ - RepoHost: &v1beta1.RepoHostStatus{Ready: true}, - Repos: []v1beta1.RepoStatus{{Name: "repo1", StanzaCreated: true}}}, - } + // set status + postgresCluster.Status = v1beta1.PostgresClusterStatus{ + Patroni: v1beta1.PatroniStatus{SystemIdentifier: "12345abcde"}, + PGBackRest: &v1beta1.PGBackRestStatus{ + RepoHost: &v1beta1.RepoHostStatus{Ready: true}, + Repos: []v1beta1.RepoStatus{{Name: "repo1", StanzaCreated: true}}}, + } - // set conditions - clusterConditions := map[string]metav1.ConditionStatus{ - ConditionRepoHostReady: metav1.ConditionTrue, - ConditionReplicaCreate: metav1.ConditionTrue, - } - for condition, status := range clusterConditions { - meta.SetStatusCondition(&postgresCluster.Status.Conditions, metav1.Condition{ - Type: condition, Reason: "testing", Status: status}) - } + // set conditions + clusterConditions := map[string]metav1.ConditionStatus{ + ConditionRepoHostReady: metav1.ConditionTrue, + ConditionReplicaCreate: metav1.ConditionTrue, + } + for condition, status := range clusterConditions { + meta.SetStatusCondition(&postgresCluster.Status.Conditions, metav1.Condition{ + Type: condition, Reason: "testing", Status: status}) + } - rootCA, err := pki.NewRootCertificateAuthority() - assert.NilError(t, err) + rootCA, err := pki.NewRootCertificateAuthority() + assert.NilError(t, err) - result, err := r.reconcilePGBackRest(ctx, postgresCluster, instances, rootCA) - if err != nil || result != (reconcile.Result{}) { - t.Errorf("unable to reconcile pgBackRest: %v", err) - } + result, err := r.reconcilePGBackRest(ctx, postgresCluster, instances, rootCA, true) + if err != nil || result != (reconcile.Result{}) { + t.Errorf("unable to reconcile pgBackRest: %v", err) + } - // repo is the first defined repo - repo := postgresCluster.Spec.Backups.PGBackRest.Repos[0] + // repo is the first defined repo + repo := postgresCluster.Spec.Backups.PGBackRest.Repos[0] - // test that the repo was created properly - t.Run("verify pgbackrest dedicated repo StatefulSet", func(t *testing.T) { + // test that the repo was created properly + t.Run("verify pgbackrest dedicated repo StatefulSet", func(t *testing.T) { - // get the pgBackRest repo sts using the labels we expect it to have - dedicatedRepos := &appsv1.StatefulSetList{} - if err := tClient.List(ctx, dedicatedRepos, client.InNamespace(ns.Name), - client.MatchingLabels{ - naming.LabelCluster: clusterName, - naming.LabelPGBackRest: "", - naming.LabelPGBackRestDedicated: "", - }); err != nil { - t.Fatal(err) - } + // get the pgBackRest repo sts using the labels we expect it to have + dedicatedRepos := &appsv1.StatefulSetList{} + if err := tClient.List(ctx, dedicatedRepos, client.InNamespace(ns.Name), + client.MatchingLabels{ + naming.LabelCluster: clusterName, + naming.LabelPGBackRest: "", + naming.LabelPGBackRestDedicated: "", + }); err != nil { + t.Fatal(err) + } - repo := appsv1.StatefulSet{} - // verify that we found a repo sts as expected - if len(dedicatedRepos.Items) == 0 { - t.Fatal("Did not find a dedicated repo sts") - } else if len(dedicatedRepos.Items) > 1 { - t.Fatal("Too many dedicated repo sts's found") - } else { - repo = dedicatedRepos.Items[0] - } + repo := appsv1.StatefulSet{} + // verify that we found a repo sts as expected + if len(dedicatedRepos.Items) == 0 { + t.Fatal("Did not find a dedicated repo sts") + } else if len(dedicatedRepos.Items) > 1 { + t.Fatal("Too many dedicated repo sts's found") + } else { + repo = dedicatedRepos.Items[0] + } - // verify proper number of replicas - if *repo.Spec.Replicas != 1 { - t.Errorf("%v replicas found for dedicated repo sts, expected %v", - repo.Spec.Replicas, 1) - } + // verify proper number of replicas + if *repo.Spec.Replicas != 1 { + t.Errorf("%v replicas found for dedicated repo sts, expected %v", + repo.Spec.Replicas, 1) + } - // verify proper ownership - var foundOwnershipRef bool - for _, r := range repo.GetOwnerReferences() { - if r.Kind == "PostgresCluster" && r.Name == clusterName && - r.UID == types.UID(clusterUID) { + // verify proper ownership + var foundOwnershipRef bool + for _, r := range repo.GetOwnerReferences() { + if r.Kind == "PostgresCluster" && r.Name == clusterName && + r.UID == types.UID(clusterUID) { - foundOwnershipRef = true - break + foundOwnershipRef = true + break + } } - } - if !foundOwnershipRef { - t.Errorf("did not find expected ownership references") - } + if !foundOwnershipRef { + t.Errorf("did not find expected ownership references") + } - // verify proper matching labels - expectedLabels := map[string]string{ - naming.LabelCluster: clusterName, - naming.LabelPGBackRest: "", - naming.LabelPGBackRestDedicated: "", - } - expectedLabelsSelector, err := metav1.LabelSelectorAsSelector( - metav1.SetAsLabelSelector(expectedLabels)) - if err != nil { - t.Error(err) - } - if !expectedLabelsSelector.Matches(labels.Set(repo.GetLabels())) { - t.Errorf("dedicated repo host is missing an expected label: found=%v, expected=%v", - repo.GetLabels(), expectedLabels) - } + // verify proper matching labels + expectedLabels := map[string]string{ + naming.LabelCluster: clusterName, + naming.LabelPGBackRest: "", + naming.LabelPGBackRestDedicated: "", + } + expectedLabelsSelector, err := metav1.LabelSelectorAsSelector( + metav1.SetAsLabelSelector(expectedLabels)) + if err != nil { + t.Error(err) + } + if !expectedLabelsSelector.Matches(labels.Set(repo.GetLabels())) { + t.Errorf("dedicated repo host is missing an expected label: found=%v, expected=%v", + repo.GetLabels(), expectedLabels) + } - template := repo.Spec.Template.DeepCopy() + template := repo.Spec.Template.DeepCopy() - // Containers and Volumes should be populated. - assert.Assert(t, len(template.Spec.Containers) != 0) - assert.Assert(t, len(template.Spec.InitContainers) != 0) - assert.Assert(t, len(template.Spec.Volumes) != 0) + // Containers and Volumes should be populated. + assert.Assert(t, len(template.Spec.Containers) != 0) + assert.Assert(t, len(template.Spec.InitContainers) != 0) + assert.Assert(t, len(template.Spec.Volumes) != 0) - // Ignore Containers and Volumes in the comparison below. - template.Spec.Containers = nil - template.Spec.InitContainers = nil - template.Spec.Volumes = nil + // Ignore Containers and Volumes in the comparison below. + template.Spec.Containers = nil + template.Spec.InitContainers = nil + template.Spec.Volumes = nil - // TODO(tjmoore4): Add additional tests to test appending existing - // topology spread constraints and spec.disableDefaultPodScheduling being - // set to true (as done in instance StatefulSet tests). - assert.Assert(t, marshalMatches(template.Spec, ` + // TODO(tjmoore4): Add additional tests to test appending existing + // topology spread constraints and spec.disableDefaultPodScheduling being + // set to true (as done in instance StatefulSet tests). + assert.Assert(t, cmp.MarshalMatches(template.Spec, ` affinity: {} automountServiceAccountToken: false containers: null @@ -382,224 +372,298 @@ topologySpreadConstraints: maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: ScheduleAnyway - `)) + `)) - // verify that the repohost container exists and contains the proper env vars - var repoHostContExists bool - for _, c := range repo.Spec.Template.Spec.Containers { - if c.Name == naming.PGBackRestRepoContainerName { - repoHostContExists = true + // verify that the repohost container exists and contains the proper env vars + var repoHostContExists bool + for _, c := range repo.Spec.Template.Spec.Containers { + if c.Name == naming.PGBackRestRepoContainerName { + repoHostContExists = true + } + } + // now verify the proper env within the container + if !repoHostContExists { + t.Errorf("dedicated repo host is missing a container with name %s", + naming.PGBackRestRepoContainerName) } - } - // now verify the proper env within the container - if !repoHostContExists { - t.Errorf("dedicated repo host is missing a container with name %s", - naming.PGBackRestRepoContainerName) - } - repoHostStatus := postgresCluster.Status.PGBackRest.RepoHost - if repoHostStatus != nil { - if repoHostStatus.APIVersion != "apps/v1" || repoHostStatus.Kind != "StatefulSet" { - t.Errorf("invalid version/kind for dedicated repo host status") + repoHostStatus := postgresCluster.Status.PGBackRest.RepoHost + if repoHostStatus != nil { + if repoHostStatus.APIVersion != "apps/v1" || repoHostStatus.Kind != "StatefulSet" { + t.Errorf("invalid version/kind for dedicated repo host status") + } + } else { + t.Errorf("dedicated repo host status is missing") } - } else { - t.Errorf("dedicated repo host status is missing") - } - var foundConditionRepoHostsReady bool - for _, c := range postgresCluster.Status.Conditions { - if c.Type == "PGBackRestRepoHostReady" { - foundConditionRepoHostsReady = true - break + var foundConditionRepoHostsReady bool + for _, c := range postgresCluster.Status.Conditions { + if c.Type == "PGBackRestRepoHostReady" { + foundConditionRepoHostsReady = true + break + } + } + if !foundConditionRepoHostsReady { + t.Errorf("status condition PGBackRestRepoHostsReady is missing") } - } - if !foundConditionRepoHostsReady { - t.Errorf("status condition PGBackRestRepoHostsReady is missing") - } - assert.Check(t, wait.PollUntilContextTimeout(ctx, time.Second/2, Scale(time.Second*2), false, - func(ctx context.Context) (bool, error) { - events := &corev1.EventList{} - err := tClient.List(ctx, events, &client.MatchingFields{ - "involvedObject.kind": "PostgresCluster", - "involvedObject.name": clusterName, - "involvedObject.namespace": ns.Name, - "involvedObject.uid": clusterUID, - "reason": "RepoHostCreated", - }) - return len(events.Items) == 1, err - })) - }) + assert.Check(t, wait.PollUntilContextTimeout(ctx, time.Second/2, Scale(time.Second*2), false, + func(ctx context.Context) (bool, error) { + events := &corev1.EventList{} + err := tClient.List(ctx, events, &client.MatchingFields{ + "involvedObject.kind": "PostgresCluster", + "involvedObject.name": clusterName, + "involvedObject.namespace": ns.Name, + "involvedObject.uid": clusterUID, + "reason": "RepoHostCreated", + }) + return len(events.Items) == 1, err + })) + }) - t.Run("verify pgbackrest repo volumes", func(t *testing.T) { + t.Run("verify pgbackrest repo volumes", func(t *testing.T) { + + // get the pgBackRest repo sts using the labels we expect it to have + repoVols := &corev1.PersistentVolumeClaimList{} + if err := tClient.List(ctx, repoVols, client.InNamespace(ns.Name), + client.MatchingLabels{ + naming.LabelCluster: clusterName, + naming.LabelPGBackRest: "", + naming.LabelPGBackRestRepoVolume: "", + }); err != nil { + t.Fatal(err) + } + assert.Assert(t, len(repoVols.Items) > 0) - // get the pgBackRest repo sts using the labels we expect it to have - repoVols := &corev1.PersistentVolumeClaimList{} - if err := tClient.List(ctx, repoVols, client.InNamespace(ns.Name), - client.MatchingLabels{ - naming.LabelCluster: clusterName, - naming.LabelPGBackRest: "", - naming.LabelPGBackRestRepoVolume: "", - }); err != nil { - t.Fatal(err) - } - assert.Assert(t, len(repoVols.Items) > 0) + for _, r := range postgresCluster.Spec.Backups.PGBackRest.Repos { + if r.Volume == nil { + continue + } + var foundRepoVol bool + for _, v := range repoVols.Items { + if v.GetName() == + naming.PGBackRestRepoVolume(postgresCluster, r.Name).Name { + foundRepoVol = true + break + } + } + assert.Assert(t, foundRepoVol) + } + }) + + t.Run("verify pgbackrest configuration", func(t *testing.T) { - for _, r := range postgresCluster.Spec.Backups.PGBackRest.Repos { - if r.Volume == nil { - continue + config := &corev1.ConfigMap{} + if err := tClient.Get(ctx, types.NamespacedName{ + Name: naming.PGBackRestConfig(postgresCluster).Name, + Namespace: postgresCluster.GetNamespace(), + }, config); err != nil { + assert.NilError(t, err) } - var foundRepoVol bool - for _, v := range repoVols.Items { - if v.GetName() == - naming.PGBackRestRepoVolume(postgresCluster, r.Name).Name { - foundRepoVol = true - break + assert.Assert(t, len(config.Data) > 0) + + var instanceConfFound, dedicatedRepoConfFound bool + for k, v := range config.Data { + if v != "" { + if k == pgbackrest.CMInstanceKey { + instanceConfFound = true + } else if k == pgbackrest.CMRepoKey { + dedicatedRepoConfFound = true + } } } - assert.Assert(t, foundRepoVol) - } - }) + assert.Check(t, instanceConfFound) + assert.Check(t, dedicatedRepoConfFound) + }) - t.Run("verify pgbackrest configuration", func(t *testing.T) { + t.Run("verify pgbackrest schedule cronjob", func(t *testing.T) { - config := &corev1.ConfigMap{} - if err := tClient.Get(ctx, types.NamespacedName{ - Name: naming.PGBackRestConfig(postgresCluster).Name, - Namespace: postgresCluster.GetNamespace(), - }, config); err != nil { - assert.NilError(t, err) - } - assert.Assert(t, len(config.Data) > 0) - - var instanceConfFound, dedicatedRepoConfFound bool - for k, v := range config.Data { - if v != "" { - if k == pgbackrest.CMInstanceKey { - instanceConfFound = true - } else if k == pgbackrest.CMRepoKey { - dedicatedRepoConfFound = true - } + // set status + postgresCluster.Status = v1beta1.PostgresClusterStatus{ + Patroni: v1beta1.PatroniStatus{SystemIdentifier: "12345abcde"}, + PGBackRest: &v1beta1.PGBackRestStatus{ + Repos: []v1beta1.RepoStatus{{Name: "repo1", StanzaCreated: true}}}, } - } - assert.Check(t, instanceConfFound) - assert.Check(t, dedicatedRepoConfFound) - }) - t.Run("verify pgbackrest schedule cronjob", func(t *testing.T) { + // set conditions + clusterConditions := map[string]metav1.ConditionStatus{ + ConditionRepoHostReady: metav1.ConditionTrue, + ConditionReplicaCreate: metav1.ConditionTrue, + } - // set status - postgresCluster.Status = v1beta1.PostgresClusterStatus{ - Patroni: v1beta1.PatroniStatus{SystemIdentifier: "12345abcde"}, - PGBackRest: &v1beta1.PGBackRestStatus{ - Repos: []v1beta1.RepoStatus{{Name: "repo1", StanzaCreated: true}}}, - } + for condition, status := range clusterConditions { + meta.SetStatusCondition(&postgresCluster.Status.Conditions, metav1.Condition{ + Type: condition, Reason: "testing", Status: status}) + } - // set conditions - clusterConditions := map[string]metav1.ConditionStatus{ - ConditionRepoHostReady: metav1.ConditionTrue, - ConditionReplicaCreate: metav1.ConditionTrue, - } + requeue := r.reconcileScheduledBackups(ctx, postgresCluster, serviceAccount, fakeObservedCronJobs()) + assert.Assert(t, !requeue) - for condition, status := range clusterConditions { - meta.SetStatusCondition(&postgresCluster.Status.Conditions, metav1.Condition{ - Type: condition, Reason: "testing", Status: status}) - } + returnedCronJob := &batchv1.CronJob{} + if err := tClient.Get(ctx, types.NamespacedName{ + Name: postgresCluster.Name + "-repo1-full", + Namespace: postgresCluster.GetNamespace(), + }, returnedCronJob); err != nil { + assert.NilError(t, err) + } - requeue := r.reconcileScheduledBackups(ctx, postgresCluster, serviceAccount, fakeObservedCronJobs()) - assert.Assert(t, !requeue) + // check returned cronjob matches set spec + assert.Equal(t, returnedCronJob.Name, "hippocluster-repo1-full") + assert.Equal(t, returnedCronJob.Spec.Schedule, testCronSchedule) + assert.Equal(t, returnedCronJob.Spec.ConcurrencyPolicy, batchv1.ForbidConcurrent) + assert.Equal(t, returnedCronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Name, + "pgbackrest") + assert.Assert(t, returnedCronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].SecurityContext != &corev1.SecurityContext{}) - returnedCronJob := &batchv1.CronJob{} - if err := tClient.Get(ctx, types.NamespacedName{ - Name: postgresCluster.Name + "-repo1-full", - Namespace: postgresCluster.GetNamespace(), - }, returnedCronJob); err != nil { - assert.NilError(t, err) - } + }) - // check returned cronjob matches set spec - assert.Equal(t, returnedCronJob.Name, "hippocluster-repo1-full") - assert.Equal(t, returnedCronJob.Spec.Schedule, testCronSchedule) - assert.Equal(t, returnedCronJob.Spec.ConcurrencyPolicy, batchv1.ForbidConcurrent) - assert.Equal(t, returnedCronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Name, - "pgbackrest") - assert.Assert(t, returnedCronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].SecurityContext != &corev1.SecurityContext{}) + t.Run("verify pgbackrest schedule found", func(t *testing.T) { - }) + assert.Assert(t, backupScheduleFound(repo, "full")) - t.Run("verify pgbackrest schedule found", func(t *testing.T) { + testrepo := v1beta1.PGBackRestRepo{ + Name: "repo1", + BackupSchedules: &v1beta1.PGBackRestBackupSchedules{ + Full: &testCronSchedule, + Differential: &testCronSchedule, + Incremental: &testCronSchedule, + }} - assert.Assert(t, backupScheduleFound(repo, "full")) + assert.Assert(t, backupScheduleFound(testrepo, "full")) + assert.Assert(t, backupScheduleFound(testrepo, "diff")) + assert.Assert(t, backupScheduleFound(testrepo, "incr")) - testrepo := v1beta1.PGBackRestRepo{ - Name: "repo1", - BackupSchedules: &v1beta1.PGBackRestBackupSchedules{ - Full: &testCronSchedule, - Differential: &testCronSchedule, - Incremental: &testCronSchedule, - }} + }) - assert.Assert(t, backupScheduleFound(testrepo, "full")) - assert.Assert(t, backupScheduleFound(testrepo, "diff")) - assert.Assert(t, backupScheduleFound(testrepo, "incr")) + t.Run("verify pgbackrest schedule not found", func(t *testing.T) { - }) + assert.Assert(t, !backupScheduleFound(repo, "notabackuptype")) + + noscheduletestrepo := v1beta1.PGBackRestRepo{Name: "repo1"} + assert.Assert(t, !backupScheduleFound(noscheduletestrepo, "full")) + + }) + + t.Run("pgbackrest schedule suspended status", func(t *testing.T) { + + returnedCronJob := &batchv1.CronJob{} + if err := tClient.Get(ctx, types.NamespacedName{ + Name: postgresCluster.Name + "-repo1-full", + Namespace: postgresCluster.GetNamespace(), + }, returnedCronJob); err != nil { + assert.NilError(t, err) + } + + t.Run("pgbackrest schedule suspended false", func(t *testing.T) { + assert.Assert(t, !*returnedCronJob.Spec.Suspend) + }) + + t.Run("shutdown", func(t *testing.T) { + *postgresCluster.Spec.Shutdown = true + postgresCluster.Spec.Standby = nil + + requeue := r.reconcileScheduledBackups(ctx, + postgresCluster, serviceAccount, fakeObservedCronJobs()) + assert.Assert(t, !requeue) + + assert.NilError(t, tClient.Get(ctx, types.NamespacedName{ + Name: postgresCluster.Name + "-repo1-full", + Namespace: postgresCluster.GetNamespace(), + }, returnedCronJob)) + + assert.Assert(t, *returnedCronJob.Spec.Suspend) + }) - t.Run("verify pgbackrest schedule not found", func(t *testing.T) { + t.Run("standby", func(t *testing.T) { + *postgresCluster.Spec.Shutdown = false + postgresCluster.Spec.Standby = &v1beta1.PostgresStandbySpec{ + Enabled: true, + } - assert.Assert(t, !backupScheduleFound(repo, "notabackuptype")) + requeue := r.reconcileScheduledBackups(ctx, + postgresCluster, serviceAccount, fakeObservedCronJobs()) + assert.Assert(t, !requeue) - noscheduletestrepo := v1beta1.PGBackRestRepo{Name: "repo1"} - assert.Assert(t, !backupScheduleFound(noscheduletestrepo, "full")) + assert.NilError(t, tClient.Get(ctx, types.NamespacedName{ + Name: postgresCluster.Name + "-repo1-full", + Namespace: postgresCluster.GetNamespace(), + }, returnedCronJob)) + assert.Assert(t, *returnedCronJob.Spec.Suspend) + }) + }) }) - t.Run("pgbackrest schedule suspended status", func(t *testing.T) { + t.Run("run reconcile with backups not defined", func(t *testing.T) { + clusterName := "hippocluster2" + clusterUID := "hippouid2" - returnedCronJob := &batchv1.CronJob{} - if err := tClient.Get(ctx, types.NamespacedName{ - Name: postgresCluster.Name + "-repo1-full", - Namespace: postgresCluster.GetNamespace(), - }, returnedCronJob); err != nil { - assert.NilError(t, err) + ns := setupNamespace(t, tClient) + // create a PostgresCluster without backups to test with + postgresCluster := fakePostgresCluster(clusterName, ns.GetName(), clusterUID, true) + postgresCluster.Spec.Backups = v1beta1.Backups{} + + // create the 'observed' instances and set the leader + instances := &observedInstances{ + forCluster: []*Instance{{Name: "instance1", + Pods: []*corev1.Pod{{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{naming.LabelRole: naming.RolePatroniLeader}, + }, + Spec: corev1.PodSpec{}, + }}, + }, {Name: "instance2"}, {Name: "instance3"}}, } - t.Run("pgbackrest schedule suspended false", func(t *testing.T) { - assert.Assert(t, !*returnedCronJob.Spec.Suspend) - }) + rootCA, err := pki.NewRootCertificateAuthority() + assert.NilError(t, err) - t.Run("shutdown", func(t *testing.T) { - *postgresCluster.Spec.Shutdown = true - postgresCluster.Spec.Standby = nil + result, err := r.reconcilePGBackRest(ctx, postgresCluster, instances, rootCA, false) + if err != nil { + t.Errorf("unable to reconcile pgBackRest: %v", err) + } + assert.Equal(t, result, reconcile.Result{}) - requeue := r.reconcileScheduledBackups(ctx, - postgresCluster, serviceAccount, fakeObservedCronJobs()) - assert.Assert(t, !requeue) + t.Run("verify pgbackrest dedicated repo StatefulSet", func(t *testing.T) { - assert.NilError(t, tClient.Get(ctx, types.NamespacedName{ - Name: postgresCluster.Name + "-repo1-full", - Namespace: postgresCluster.GetNamespace(), - }, returnedCronJob)) + // Verify the sts doesn't exist + dedicatedRepos := &appsv1.StatefulSetList{} + if err := tClient.List(ctx, dedicatedRepos, client.InNamespace(ns.Name), + client.MatchingLabels{ + naming.LabelCluster: clusterName, + naming.LabelPGBackRest: "", + naming.LabelPGBackRestDedicated: "", + }); err != nil { + t.Fatal(err) + } - assert.Assert(t, *returnedCronJob.Spec.Suspend) + assert.Equal(t, len(dedicatedRepos.Items), 0) }) - t.Run("standby", func(t *testing.T) { - *postgresCluster.Spec.Shutdown = false - postgresCluster.Spec.Standby = &v1beta1.PostgresStandbySpec{ - Enabled: true, + t.Run("verify pgbackrest repo volumes", func(t *testing.T) { + + // get the pgBackRest repo sts using the labels we expect it to have + repoVols := &corev1.PersistentVolumeClaimList{} + if err := tClient.List(ctx, repoVols, client.InNamespace(ns.Name), + client.MatchingLabels{ + naming.LabelCluster: clusterName, + naming.LabelPGBackRest: "", + naming.LabelPGBackRestRepoVolume: "", + }); err != nil { + t.Fatal(err) } - requeue := r.reconcileScheduledBackups(ctx, - postgresCluster, serviceAccount, fakeObservedCronJobs()) - assert.Assert(t, !requeue) + assert.Equal(t, len(repoVols.Items), 0) + }) - assert.NilError(t, tClient.Get(ctx, types.NamespacedName{ - Name: postgresCluster.Name + "-repo1-full", - Namespace: postgresCluster.GetNamespace(), - }, returnedCronJob)) + t.Run("verify pgbackrest configuration", func(t *testing.T) { - assert.Assert(t, *returnedCronJob.Spec.Suspend) + config := &corev1.ConfigMap{} + err := tClient.Get(ctx, types.NamespacedName{ + Name: naming.PGBackRestConfig(postgresCluster).Name, + Namespace: postgresCluster.GetNamespace(), + }, config) + assert.Equal(t, apierrors.IsNotFound(err), true) }) }) } @@ -1648,7 +1712,7 @@ func TestGetPGBackRestResources(t *testing.T) { assert.NilError(t, err) assert.NilError(t, tClient.Create(ctx, resource)) - resources, err := r.getPGBackRestResources(ctx, tc.cluster) + resources, err := r.getPGBackRestResources(ctx, tc.cluster, true) assert.NilError(t, err) assert.Assert(t, tc.result.jobCount == len(resources.replicaCreateBackupJobs)) @@ -1885,7 +1949,7 @@ func TestReconcilePostgresClusterDataSource(t *testing.T) { pgclusterDataSource = tc.dataSource.PostgresCluster } err := r.reconcilePostgresClusterDataSource(ctx, cluster, pgclusterDataSource, - "testhash", nil, rootCA) + "testhash", nil, rootCA, true) assert.NilError(t, err) restoreConfig := &corev1.ConfigMap{} @@ -2090,7 +2154,7 @@ func TestReconcileCloudBasedDataSource(t *testing.T) { assert.Assert(t, apierrors.IsNotFound(err), "expected NotFound, got %#v", err) } else { assert.NilError(t, err) - assert.Assert(t, marshalMatches(restoreConfig.Data["pgbackrest_instance.conf"], tc.result.conf)) + assert.Assert(t, cmp.MarshalMatches(restoreConfig.Data["pgbackrest_instance.conf"], tc.result.conf)) } restoreJobs := &batchv1.JobList{} @@ -2381,8 +2445,10 @@ func TestCopyConfigurationResources(t *testing.T) { } func TestGenerateBackupJobIntent(t *testing.T) { + ctx := context.TODO() t.Run("empty", func(t *testing.T) { spec := generateBackupJobSpecIntent( + ctx, &v1beta1.PostgresCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "", @@ -2396,7 +2462,7 @@ func TestGenerateBackupJobIntent(t *testing.T) { "", nil, nil, ) - assert.Assert(t, marshalMatches(spec.Template.Spec, ` + assert.Assert(t, cmp.MarshalMatches(spec.Template.Spec, ` containers: - command: - /opt/crunchy/bin/pgbackrest @@ -2464,7 +2530,7 @@ volumes: ImagePullPolicy: corev1.PullAlways, }, } - job := generateBackupJobSpecIntent( + job := generateBackupJobSpecIntent(ctx, cluster, v1beta1.PGBackRestRepo{}, "", nil, nil, @@ -2479,7 +2545,7 @@ volumes: cluster.Spec.Backups = v1beta1.Backups{ PGBackRest: v1beta1.PGBackRestArchive{}, } - job := generateBackupJobSpecIntent( + job := generateBackupJobSpecIntent(ctx, cluster, v1beta1.PGBackRestRepo{}, "", nil, nil, @@ -2496,7 +2562,7 @@ volumes: }, }, } - job := generateBackupJobSpecIntent( + job := generateBackupJobSpecIntent(ctx, cluster, v1beta1.PGBackRestRepo{}, "", nil, nil, @@ -2535,7 +2601,7 @@ volumes: }, }, } - job := generateBackupJobSpecIntent( + job := generateBackupJobSpecIntent(ctx, cluster, v1beta1.PGBackRestRepo{}, "", nil, nil, @@ -2548,7 +2614,7 @@ volumes: cluster.Spec.Backups.PGBackRest.Jobs = &v1beta1.BackupJobs{ PriorityClassName: initialize.String("some-priority-class"), } - job := generateBackupJobSpecIntent( + job := generateBackupJobSpecIntent(ctx, cluster, v1beta1.PGBackRestRepo{}, "", nil, nil, @@ -2566,7 +2632,7 @@ volumes: cluster.Spec.Backups.PGBackRest.Jobs = &v1beta1.BackupJobs{ Tolerations: tolerations, } - job := generateBackupJobSpecIntent( + job := generateBackupJobSpecIntent(ctx, cluster, v1beta1.PGBackRestRepo{}, "", nil, nil, @@ -2580,14 +2646,14 @@ volumes: t.Run("Undefined", func(t *testing.T) { cluster.Spec.Backups.PGBackRest.Jobs = nil - spec := generateBackupJobSpecIntent( + spec := generateBackupJobSpecIntent(ctx, cluster, v1beta1.PGBackRestRepo{}, "", nil, nil, ) assert.Assert(t, spec.TTLSecondsAfterFinished == nil) cluster.Spec.Backups.PGBackRest.Jobs = &v1beta1.BackupJobs{} - spec = generateBackupJobSpecIntent( + spec = generateBackupJobSpecIntent(ctx, cluster, v1beta1.PGBackRestRepo{}, "", nil, nil, ) assert.Assert(t, spec.TTLSecondsAfterFinished == nil) @@ -2598,7 +2664,7 @@ volumes: TTLSecondsAfterFinished: initialize.Int32(0), } - spec := generateBackupJobSpecIntent( + spec := generateBackupJobSpecIntent(ctx, cluster, v1beta1.PGBackRestRepo{}, "", nil, nil, ) if assert.Check(t, spec.TTLSecondsAfterFinished != nil) { @@ -2611,7 +2677,7 @@ volumes: TTLSecondsAfterFinished: initialize.Int32(100), } - spec := generateBackupJobSpecIntent( + spec := generateBackupJobSpecIntent(ctx, cluster, v1beta1.PGBackRestRepo{}, "", nil, nil, ) if assert.Check(t, spec.TTLSecondsAfterFinished != nil) { @@ -2832,7 +2898,7 @@ func TestGenerateRestoreJobIntent(t *testing.T) { }) t.Run("SecurityContext", func(t *testing.T) { assert.DeepEqual(t, job.Spec.Template.Spec.Containers[0].SecurityContext, - initialize.RestrictedSecurityContext(true)) + initialize.RestrictedSecurityContext(true)) // K8SPG-260 }) t.Run("Resources", func(t *testing.T) { assert.DeepEqual(t, job.Spec.Template.Spec.Containers[0].Resources, @@ -3702,3 +3768,167 @@ func TestSetScheduledJobStatus(t *testing.T) { assert.Assert(t, len(postgresCluster.Status.PGBackRest.ScheduledBackups) == 0) }) } + +func TestBackupsEnabled(t *testing.T) { + // Garbage collector cleans up test resources before the test completes + if strings.EqualFold(os.Getenv("USE_EXISTING_CLUSTER"), "true") { + t.Skip("USE_EXISTING_CLUSTER: Test fails due to garbage collection") + } + + cfg, tClient := setupKubernetes(t) + require.ParallelCapacity(t, 2) + + r := &Reconciler{} + ctx, cancel := setupManager(t, cfg, func(mgr manager.Manager) { + r = &Reconciler{ + Client: mgr.GetClient(), + Recorder: mgr.GetEventRecorderFor(ControllerName), + Tracer: otel.Tracer(ControllerName), + Owner: ControllerName, + } + }) + t.Cleanup(func() { teardownManager(cancel, t) }) + + t.Run("Cluster with backups, no sts can be reconciled", func(t *testing.T) { + clusterName := "hippocluster1" + clusterUID := "hippouid1" + + ns := setupNamespace(t, tClient) + + // create a PostgresCluster to test with + postgresCluster := fakePostgresCluster(clusterName, ns.GetName(), clusterUID, true) + + backupsSpecFound, backupsReconciliationAllowed, err := r.BackupsEnabled(ctx, postgresCluster) + + assert.NilError(t, err) + assert.Assert(t, backupsSpecFound) + assert.Assert(t, backupsReconciliationAllowed) + }) + + t.Run("Cluster with backups, sts can be reconciled", func(t *testing.T) { + clusterName := "hippocluster2" + clusterUID := "hippouid2" + + ns := setupNamespace(t, tClient) + + // create a PostgresCluster to test with + postgresCluster := fakePostgresCluster(clusterName, ns.GetName(), clusterUID, true) + + // create the 'observed' instances and set the leader + instances := &observedInstances{ + forCluster: []*Instance{{Name: "instance1", + Pods: []*corev1.Pod{{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{naming.LabelRole: naming.RolePatroniLeader}, + }, + Spec: corev1.PodSpec{}, + }}, + }, {Name: "instance2"}, {Name: "instance3"}}, + } + + rootCA, err := pki.NewRootCertificateAuthority() + assert.NilError(t, err) + + _, err = r.reconcilePGBackRest(ctx, postgresCluster, instances, rootCA, true) + assert.NilError(t, err) + + backupsSpecFound, backupsReconciliationAllowed, err := r.BackupsEnabled(ctx, postgresCluster) + + assert.NilError(t, err) + assert.Assert(t, backupsSpecFound) + assert.Assert(t, backupsReconciliationAllowed) + }) + + t.Run("Cluster with no backups, no sts can reconcile", func(t *testing.T) { + // create a PostgresCluster to test with + clusterName := "hippocluster3" + clusterUID := "hippouid3" + + ns := setupNamespace(t, tClient) + + postgresCluster := fakePostgresCluster(clusterName, ns.GetName(), clusterUID, true) + postgresCluster.Spec.Backups = v1beta1.Backups{} + + backupsSpecFound, backupsReconciliationAllowed, err := r.BackupsEnabled(ctx, postgresCluster) + + assert.NilError(t, err) + assert.Assert(t, !backupsSpecFound) + assert.Assert(t, backupsReconciliationAllowed) + }) + + t.Run("Cluster with no backups, sts cannot be reconciled", func(t *testing.T) { + clusterName := "hippocluster4" + clusterUID := "hippouid4" + + ns := setupNamespace(t, tClient) + + // create a PostgresCluster to test with + postgresCluster := fakePostgresCluster(clusterName, ns.GetName(), clusterUID, true) + + // create the 'observed' instances and set the leader + instances := &observedInstances{ + forCluster: []*Instance{{Name: "instance1", + Pods: []*corev1.Pod{{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{naming.LabelRole: naming.RolePatroniLeader}, + }, + Spec: corev1.PodSpec{}, + }}, + }, {Name: "instance2"}, {Name: "instance3"}}, + } + + rootCA, err := pki.NewRootCertificateAuthority() + assert.NilError(t, err) + + _, err = r.reconcilePGBackRest(ctx, postgresCluster, instances, rootCA, true) + assert.NilError(t, err) + + postgresCluster.Spec.Backups = v1beta1.Backups{} + + backupsSpecFound, backupsReconciliationAllowed, err := r.BackupsEnabled(ctx, postgresCluster) + + assert.NilError(t, err) + assert.Assert(t, !backupsSpecFound) + assert.Assert(t, !backupsReconciliationAllowed) + }) + + t.Run("Cluster with no backups, sts, annotation can be reconciled", func(t *testing.T) { + clusterName := "hippocluster5" + clusterUID := "hippouid5" + + ns := setupNamespace(t, tClient) + + // create a PostgresCluster to test with + postgresCluster := fakePostgresCluster(clusterName, ns.GetName(), clusterUID, true) + + // create the 'observed' instances and set the leader + instances := &observedInstances{ + forCluster: []*Instance{{Name: "instance1", + Pods: []*corev1.Pod{{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{naming.LabelRole: naming.RolePatroniLeader}, + }, + Spec: corev1.PodSpec{}, + }}, + }, {Name: "instance2"}, {Name: "instance3"}}, + } + + rootCA, err := pki.NewRootCertificateAuthority() + assert.NilError(t, err) + + _, err = r.reconcilePGBackRest(ctx, postgresCluster, instances, rootCA, true) + assert.NilError(t, err) + + postgresCluster.Spec.Backups = v1beta1.Backups{} + annotations := map[string]string{ + naming.AuthorizeBackupRemovalAnnotation: "true", + } + postgresCluster.Annotations = annotations + + backupsSpecFound, backupsReconciliationAllowed, err := r.BackupsEnabled(ctx, postgresCluster) + + assert.NilError(t, err) + assert.Assert(t, !backupsSpecFound) + assert.Assert(t, backupsReconciliationAllowed) + }) +} diff --git a/internal/controller/postgrescluster/pgbouncer.go b/internal/controller/postgrescluster/pgbouncer.go index c162931438..2fcd2802ff 100644 --- a/internal/controller/postgrescluster/pgbouncer.go +++ b/internal/controller/postgrescluster/pgbouncer.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -141,6 +130,7 @@ func (r *Reconciler) reconcilePGBouncerInPostgreSQL( // PostgreSQL is available for writes. Prepare to either add or remove // PgBouncer objects. + // K8SPG-345 var exposeSuperusers bool if cluster.Spec.Proxy != nil && cluster.Spec.Proxy.PGBouncer != nil { exposeSuperusers = cluster.Spec.Proxy.PGBouncer.ExposeSuperusers @@ -313,7 +303,7 @@ func (r *Reconciler) generatePGBouncerService( service.Spec.Type = corev1.ServiceTypeClusterIP } else { service.Spec.Type = corev1.ServiceType(spec.Type) - service.Spec.LoadBalancerSourceRanges = spec.LoadBalancerSourceRanges + service.Spec.LoadBalancerSourceRanges = spec.LoadBalancerSourceRanges // K8SPG-389 if spec.NodePort != nil { if service.Spec.Type == corev1.ServiceTypeClusterIP { // The NodePort can only be set when the Service type is NodePort or @@ -328,12 +318,8 @@ func (r *Reconciler) generatePGBouncerService( } servicePort.NodePort = *spec.NodePort } - if spec.ExternalTrafficPolicy != nil { - service.Spec.ExternalTrafficPolicy = *spec.ExternalTrafficPolicy - } - if spec.InternalTrafficPolicy != nil { - service.Spec.InternalTrafficPolicy = spec.InternalTrafficPolicy - } + service.Spec.ExternalTrafficPolicy = initialize.FromPointer(spec.ExternalTrafficPolicy) + service.Spec.InternalTrafficPolicy = spec.InternalTrafficPolicy } service.Spec.Ports = []corev1.ServicePort{servicePort} @@ -429,19 +415,14 @@ func (r *Reconciler) generatePGBouncerDeployment( // Use scheduling constraints from the cluster spec. deploy.Spec.Template.Spec.Affinity = cluster.Spec.Proxy.PGBouncer.Affinity deploy.Spec.Template.Spec.Tolerations = cluster.Spec.Proxy.PGBouncer.Tolerations - - if cluster.Spec.Proxy.PGBouncer.PriorityClassName != nil { - deploy.Spec.Template.Spec.PriorityClassName = *cluster.Spec.Proxy.PGBouncer.PriorityClassName - } - + deploy.Spec.Template.Spec.PriorityClassName = + initialize.FromPointer(cluster.Spec.Proxy.PGBouncer.PriorityClassName) deploy.Spec.Template.Spec.TopologySpreadConstraints = cluster.Spec.Proxy.PGBouncer.TopologySpreadConstraints // if default pod scheduling is not explicitly disabled, add the default // pod topology spread constraints - if cluster.Spec.DisableDefaultPodScheduling == nil || - (cluster.Spec.DisableDefaultPodScheduling != nil && - !*cluster.Spec.DisableDefaultPodScheduling) { + if !initialize.FromPointer(cluster.Spec.DisableDefaultPodScheduling) { deploy.Spec.Template.Spec.TopologySpreadConstraints = append( deploy.Spec.Template.Spec.TopologySpreadConstraints, defaultTopologySpreadConstraints(*deploy.Spec.Selector)...) @@ -466,6 +447,7 @@ func (r *Reconciler) generatePGBouncerDeployment( // Do not add environment variables describing services in this namespace. deploy.Spec.Template.Spec.EnableServiceLinks = initialize.Bool(false) + // K8SPG-514 if cluster.Spec.Proxy.PGBouncer.SecurityContext != nil { deploy.Spec.Template.Spec.SecurityContext = cluster.Spec.Proxy.PGBouncer.SecurityContext } else { diff --git a/internal/controller/postgrescluster/pgbouncer_test.go b/internal/controller/postgrescluster/pgbouncer_test.go index 7b73ed85b4..83df0662b3 100644 --- a/internal/controller/postgrescluster/pgbouncer_test.go +++ b/internal/controller/postgrescluster/pgbouncer_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -26,11 +15,13 @@ import ( policyv1 "k8s.io/api/policy/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/percona/percona-postgresql-operator/internal/initialize" "github.com/percona/percona-postgresql-operator/internal/naming" + "github.com/percona/percona-postgresql-operator/internal/testing/cmp" "github.com/percona/percona-postgresql-operator/internal/testing/require" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -63,7 +54,7 @@ func TestGeneratePGBouncerService(t *testing.T) { assert.NilError(t, err) assert.Assert(t, !specified) - assert.Assert(t, marshalMatches(service.ObjectMeta, ` + assert.Assert(t, cmp.MarshalMatches(service.ObjectMeta, ` creationTimestamp: null name: pg7-pgbouncer namespace: ns5 @@ -78,11 +69,11 @@ namespace: ns5 } alwaysExpect := func(t testing.TB, service *corev1.Service) { - assert.Assert(t, marshalMatches(service.TypeMeta, ` + assert.Assert(t, cmp.MarshalMatches(service.TypeMeta, ` apiVersion: v1 kind: Service `)) - assert.Assert(t, marshalMatches(service.ObjectMeta, ` + assert.Assert(t, cmp.MarshalMatches(service.ObjectMeta, ` creationTimestamp: null labels: app.kubernetes.io/component: pgbouncer @@ -181,7 +172,7 @@ ownerReferences: alwaysExpect(t, service) // Defaults to ClusterIP. assert.Equal(t, service.Spec.Type, corev1.ServiceTypeClusterIP) - assert.Assert(t, marshalMatches(service.Spec.Ports, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Ports, ` - name: pgbouncer port: 9651 protocol: TCP @@ -214,7 +205,7 @@ ownerReferences: assert.Assert(t, specified) alwaysExpect(t, service) test.Expect(t, service) - assert.Assert(t, marshalMatches(service.Spec.Ports, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Ports, ` - name: pgbouncer port: 9651 protocol: TCP @@ -239,7 +230,7 @@ ownerReferences: assert.NilError(t, err) assert.Equal(t, service.Spec.Type, corev1.ServiceTypeNodePort) alwaysExpect(t, service) - assert.Assert(t, marshalMatches(service.Spec.Ports, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Ports, ` - name: pgbouncer nodePort: 32001 port: 9651 @@ -252,7 +243,7 @@ ownerReferences: assert.NilError(t, err) assert.Equal(t, service.Spec.Type, corev1.ServiceTypeLoadBalancer) alwaysExpect(t, service) - assert.Assert(t, marshalMatches(service.Spec.Ports, ` + assert.Assert(t, cmp.MarshalMatches(service.Spec.Ports, ` - name: pgbouncer nodePort: 32002 port: 9651 @@ -407,7 +398,7 @@ func TestGeneratePGBouncerDeployment(t *testing.T) { assert.NilError(t, err) assert.Assert(t, !specified) - assert.Assert(t, marshalMatches(deploy.ObjectMeta, ` + assert.Assert(t, cmp.MarshalMatches(deploy.ObjectMeta, ` creationTimestamp: null name: test-cluster-pgbouncer namespace: ns3 @@ -492,7 +483,7 @@ namespace: ns3 // topology spread constraints and spec.disableDefaultPodScheduling being // set to true (as done in instance StatefulSet tests). - assert.Assert(t, marshalMatches(deploy.Spec.Template.Spec, ` + assert.Assert(t, cmp.MarshalMatches(deploy.Spec.Template.Spec, ` automountServiceAccountToken: false containers: null enableServiceLinks: false @@ -573,7 +564,7 @@ func TestReconcilePGBouncerDisruptionBudget(t *testing.T) { cluster := testCluster() cluster.Namespace = ns.Name cluster.Spec.Proxy.PGBouncer.Replicas = initialize.Int32(1) - cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.IntOrStringInt32(0) + cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.Pointer(intstr.FromInt32(0)) assert.NilError(t, r.reconcilePGBouncerPodDisruptionBudget(ctx, cluster)) assert.Assert(t, !foundPDB(cluster)) }) @@ -582,7 +573,7 @@ func TestReconcilePGBouncerDisruptionBudget(t *testing.T) { cluster := testCluster() cluster.Namespace = ns.Name cluster.Spec.Proxy.PGBouncer.Replicas = initialize.Int32(1) - cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.IntOrStringInt32(1) + cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.Pointer(intstr.FromInt32(1)) assert.NilError(t, r.Client.Create(ctx, cluster)) t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) }) @@ -591,7 +582,7 @@ func TestReconcilePGBouncerDisruptionBudget(t *testing.T) { assert.Assert(t, foundPDB(cluster)) t.Run("deleted", func(t *testing.T) { - cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.IntOrStringInt32(0) + cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.Pointer(intstr.FromInt32(0)) err := r.reconcilePGBouncerPodDisruptionBudget(ctx, cluster) if apierrors.IsConflict(err) { // When running in an existing environment another controller will sometimes update @@ -609,7 +600,7 @@ func TestReconcilePGBouncerDisruptionBudget(t *testing.T) { cluster := testCluster() cluster.Namespace = ns.Name cluster.Spec.Proxy.PGBouncer.Replicas = initialize.Int32(1) - cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.IntOrStringString("50%") + cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.Pointer(intstr.FromString("50%")) assert.NilError(t, r.Client.Create(ctx, cluster)) t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) }) @@ -618,7 +609,7 @@ func TestReconcilePGBouncerDisruptionBudget(t *testing.T) { assert.Assert(t, foundPDB(cluster)) t.Run("deleted", func(t *testing.T) { - cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.IntOrStringString("0%") + cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.Pointer(intstr.FromString("0%")) err := r.reconcilePGBouncerPodDisruptionBudget(ctx, cluster) if apierrors.IsConflict(err) { // When running in an existing environment another controller will sometimes update @@ -632,13 +623,13 @@ func TestReconcilePGBouncerDisruptionBudget(t *testing.T) { }) t.Run("delete with 00%", func(t *testing.T) { - cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.IntOrStringString("50%") + cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.Pointer(intstr.FromString("50%")) assert.NilError(t, r.reconcilePGBouncerPodDisruptionBudget(ctx, cluster)) assert.Assert(t, foundPDB(cluster)) t.Run("deleted", func(t *testing.T) { - cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.IntOrStringString("00%") + cluster.Spec.Proxy.PGBouncer.MinAvailable = initialize.Pointer(intstr.FromString("00%")) err := r.reconcilePGBouncerPodDisruptionBudget(ctx, cluster) if apierrors.IsConflict(err) { // When running in an existing environment another controller will sometimes update diff --git a/internal/controller/postgrescluster/pgmonitor.go b/internal/controller/postgrescluster/pgmonitor.go index 280ce05c50..c8b3d400ba 100644 --- a/internal/controller/postgrescluster/pgmonitor.go +++ b/internal/controller/postgrescluster/pgmonitor.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -270,13 +259,34 @@ func addPGMonitorExporterToInstancePodSpec( withBuiltInCollectors := !strings.EqualFold(cluster.Annotations[naming.PostgresExporterCollectorsAnnotation], "None") + var cmd []string + // PG 17 does not include some of the columns found in stat_bgwriter with older PGs. + // Selectively turn off the collector for stat_bgwriter in PG 17, unless the user + // requests all collectors to be turned off. + switch { + case cluster.Spec.PostgresVersion == 17 && withBuiltInCollectors && certSecret == nil: + cmd = pgmonitor.ExporterStartCommand(withBuiltInCollectors, + pgmonitor.ExporterDeactivateStatBGWriterFlag) + case cluster.Spec.PostgresVersion == 17 && withBuiltInCollectors && certSecret != nil: + cmd = pgmonitor.ExporterStartCommand(withBuiltInCollectors, + pgmonitor.ExporterWebConfigFileFlag, + pgmonitor.ExporterDeactivateStatBGWriterFlag) + // If you're turning off all built-in collectors, we don't care which + // version of PG you're using. + case certSecret != nil: + cmd = pgmonitor.ExporterStartCommand(withBuiltInCollectors, + pgmonitor.ExporterWebConfigFileFlag) + default: + cmd = pgmonitor.ExporterStartCommand(withBuiltInCollectors) + } + securityContext := initialize.RestrictedSecurityContext(cluster.CompareVersion("2.5.0") >= 0) exporterContainer := corev1.Container{ Name: naming.ContainerPGMonitorExporter, Image: config.PGExporterContainerImage(cluster), ImagePullPolicy: cluster.Spec.ImagePullPolicy, Resources: cluster.Spec.Monitoring.PGMonitor.Exporter.Resources, - Command: pgmonitor.ExporterStartCommand(withBuiltInCollectors), + Command: cmd, Env: []corev1.EnvVar{ {Name: "DATA_SOURCE_URI", Value: fmt.Sprintf("%s:%d/%s", pgmonitor.ExporterHost, *cluster.Spec.Port, pgmonitor.ExporterDB)}, {Name: "DATA_SOURCE_USER", Value: pgmonitor.MonitoringUser}, @@ -368,8 +378,6 @@ func addPGMonitorExporterToInstancePodSpec( }} exporterContainer.VolumeMounts = append(exporterContainer.VolumeMounts, mounts...) - exporterContainer.Command = pgmonitor.ExporterStartCommand( - withBuiltInCollectors, pgmonitor.ExporterWebConfigFileFlag) } template.Spec.Containers = append(template.Spec.Containers, exporterContainer) diff --git a/internal/controller/postgrescluster/pgmonitor_test.go b/internal/controller/postgrescluster/pgmonitor_test.go index 7dc7645f9a..f8376c0cc1 100644 --- a/internal/controller/postgrescluster/pgmonitor_test.go +++ b/internal/controller/postgrescluster/pgmonitor_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -353,6 +342,10 @@ name: exporter-config // reacts when the kubernetes resources are in different states (e.g., checks // what happens when the database pod is terminating) func TestReconcilePGMonitorExporterSetupErrors(t *testing.T) { + if os.Getenv("QUERIES_CONFIG_DIR") == "" { + t.Skip("QUERIES_CONFIG_DIR must be set") + } + for _, test := range []struct { name string podExecCalled bool @@ -577,6 +570,10 @@ func TestReconcilePGMonitorExporter(t *testing.T) { // when it should be. Because the status updated when we update the setup sql from // pgmonitor (by using podExec), we check if podExec is called when a change is needed. func TestReconcilePGMonitorExporterStatus(t *testing.T) { + if os.Getenv("QUERIES_CONFIG_DIR") == "" { + t.Skip("QUERIES_CONFIG_DIR must be set") + } + for _, test := range []struct { name string exporterEnabled bool diff --git a/internal/controller/postgrescluster/pki.go b/internal/controller/postgrescluster/pki.go index 40e06b1255..68876ddbf9 100644 --- a/internal/controller/postgrescluster/pki.go +++ b/internal/controller/postgrescluster/pki.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -52,6 +41,7 @@ func (r *Reconciler) reconcileRootCertificate( ) { const keyCertificate, keyPrivateKey = "root.crt", "root.key" + // K8SPG-553 existing := &corev1.Secret{ ObjectMeta: naming.PostgresRootCASecret(cluster), } @@ -94,6 +84,7 @@ func (r *Reconciler) reconcileRootCertificate( // Unmarshal and validate the stored root. These first errors can // be ignored because they result in an invalid root which is then // correctly regenerated. + // K8SPG-553 _ = root.Certificate.UnmarshalText(existing.Data[certificateKey]) _ = root.PrivateKey.UnmarshalText(existing.Data[privateKey]) @@ -107,6 +98,7 @@ func (r *Reconciler) reconcileRootCertificate( } } + // K8SPG-555 intent := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: existing.Name, diff --git a/internal/controller/postgrescluster/pki_test.go b/internal/controller/postgrescluster/pki_test.go index fb38ed9e27..3b907f9133 100644 --- a/internal/controller/postgrescluster/pki_test.go +++ b/internal/controller/postgrescluster/pki_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -91,6 +80,7 @@ func TestReconcileCerts(t *testing.T) { initialRoot, err := r.reconcileRootCertificate(ctx, cluster1) assert.NilError(t, err) + // K8SPG-553 cluster1CASecret := &corev1.Secret{ ObjectMeta: naming.PostgresRootCASecret(cluster1), } @@ -114,6 +104,7 @@ func TestReconcileCerts(t *testing.T) { UID: cluster1.UID, } + // K8SPG-553 if len(cluster1CASecret.ObjectMeta.OwnerReferences) > 0 { assert.Equal(t, cluster1CASecret.ObjectMeta.OwnerReferences[0], expectedOR) } @@ -242,7 +233,9 @@ func TestReconcileCerts(t *testing.T) { // check that the leaf cert did not change after another reconciliation assert.DeepEqual(t, newLeaf2, newLeaf) + }) + }) t.Run("check cluster certificate secret reconciliation", func(t *testing.T) { diff --git a/internal/controller/postgrescluster/pod_disruption_budget.go b/internal/controller/postgrescluster/pod_disruption_budget.go index e340dcacfd..8823998ab0 100644 --- a/internal/controller/postgrescluster/pod_disruption_budget.go +++ b/internal/controller/postgrescluster/pod_disruption_budget.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -75,5 +64,5 @@ func getMinAvailable( } // If more than one replica is not defined, we will default to '0' - return initialize.IntOrStringInt32(expect) + return initialize.Pointer(intstr.FromInt32(expect)) } diff --git a/internal/controller/postgrescluster/pod_disruption_budget_test.go b/internal/controller/postgrescluster/pod_disruption_budget_test.go index 4cff1a56db..523e4b7da2 100644 --- a/internal/controller/postgrescluster/pod_disruption_budget_test.go +++ b/internal/controller/postgrescluster/pod_disruption_budget_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -61,7 +50,7 @@ func TestGeneratePodDisruptionBudget(t *testing.T) { "anno-key": "anno-value", }, } - minAvailable = initialize.IntOrStringInt32(1) + minAvailable = initialize.Pointer(intstr.FromInt32(1)) selector := metav1.LabelSelector{ MatchLabels: map[string]string{ "key": "value", @@ -89,19 +78,19 @@ func TestGeneratePodDisruptionBudget(t *testing.T) { func TestGetMinAvailable(t *testing.T) { t.Run("minAvailable provided", func(t *testing.T) { // minAvailable is defined so use that value - ma := initialize.IntOrStringInt32(0) + ma := initialize.Pointer(intstr.FromInt32(0)) expect := getMinAvailable(ma, 1) assert.Equal(t, *expect, intstr.FromInt(0)) - ma = initialize.IntOrStringInt32(1) + ma = initialize.Pointer(intstr.FromInt32(1)) expect = getMinAvailable(ma, 2) assert.Equal(t, *expect, intstr.FromInt(1)) - ma = initialize.IntOrStringString("50%") + ma = initialize.Pointer(intstr.FromString("50%")) expect = getMinAvailable(ma, 3) assert.Equal(t, *expect, intstr.FromString("50%")) - ma = initialize.IntOrStringString("200%") + ma = initialize.Pointer(intstr.FromString("200%")) expect = getMinAvailable(ma, 2147483647) assert.Equal(t, *expect, intstr.FromString("200%")) }) diff --git a/internal/controller/postgrescluster/postgres.go b/internal/controller/postgrescluster/postgres.go index 6275e0cb5b..01e2c15fdb 100644 --- a/internal/controller/postgrescluster/postgres.go +++ b/internal/controller/postgrescluster/postgres.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -56,6 +45,7 @@ func (r *Reconciler) generatePostgresUserSecret( cluster *v1beta1.PostgresCluster, spec *v1beta1.PostgresUserSpec, existing *corev1.Secret, ) (*corev1.Secret, error) { username := string(spec.Name) + // K8SPG-359 intent := &corev1.Secret{} if spec.SecretName != "" { intent = &corev1.Secret{ObjectMeta: naming.PostgresCustomUserSecretName(cluster, string(spec.SecretName))} @@ -63,7 +53,7 @@ func (r *Reconciler) generatePostgresUserSecret( intent = &corev1.Secret{ObjectMeta: naming.PostgresUserSecret(cluster, username)} } intent.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Secret")) - initialize.ByteMap(&intent.Data) + initialize.Map(&intent.Data) // Populate the Secret with libpq keywords for connecting through // the primary Service. @@ -243,6 +233,7 @@ func (r *Reconciler) reconcilePostgresDatabases( // Calculate a hash of the SQL that should be executed in PostgreSQL. + // K8SPG-375, K8SPG-577 var pgAuditOK, pgStatMonitorOK, pgStatStatementsOK, postgisInstallOK bool create := func(ctx context.Context, exec postgres.Executor) error { if cluster.Spec.Extensions.PGStatMonitor { @@ -333,6 +324,7 @@ func (r *Reconciler) reconcilePostgresDatabases( log := logging.FromContext(ctx).WithValues("revision", revision) err = errors.WithStack(create(logging.NewContext(ctx, log), podExecutor)) } + // K8SPG-472 if err == nil && pgStatMonitorOK && pgAuditOK && postgisInstallOK { cluster.Status.DatabaseRevision = revision } @@ -626,7 +618,7 @@ func (r *Reconciler) reconcilePostgresUsersInPostgreSQL( func (r *Reconciler) reconcilePostgresDataVolume( ctx context.Context, cluster *v1beta1.PostgresCluster, instanceSpec *v1beta1.PostgresInstanceSetSpec, instance *appsv1.StatefulSet, - clusterVolumes []corev1.PersistentVolumeClaim, + clusterVolumes []corev1.PersistentVolumeClaim, sourceCluster *v1beta1.PostgresCluster, ) (*corev1.PersistentVolumeClaim, error) { labelMap := map[string]string{ @@ -669,6 +661,32 @@ func (r *Reconciler) reconcilePostgresDataVolume( pvc.Spec = instanceSpec.DataVolumeClaimSpec + // If a source cluster was provided and VolumeSnapshots are turned on in the source cluster and + // there is a VolumeSnapshot available for the source cluster that is ReadyToUse, use it as the + // source for the PVC. If there is an error when retrieving VolumeSnapshots, or no ReadyToUse + // snapshots were found, create a warning event, but continue creating PVC in the usual fashion. + if sourceCluster != nil && sourceCluster.Spec.Backups.Snapshots != nil && feature.Enabled(ctx, feature.VolumeSnapshots) { + snapshots, err := r.getSnapshotsForCluster(ctx, sourceCluster) + if err == nil { + snapshot := getLatestReadySnapshot(snapshots) + if snapshot != nil { + r.Recorder.Eventf(cluster, corev1.EventTypeNormal, "BootstrappingWithSnapshot", + "Snapshot found for %v; bootstrapping cluster with snapshot.", sourceCluster.Name) + pvc.Spec.DataSource = &corev1.TypedLocalObjectReference{ + APIGroup: initialize.String("snapshot.storage.k8s.io"), + Kind: snapshot.Kind, + Name: snapshot.Name, + } + } else { + r.Recorder.Eventf(cluster, corev1.EventTypeWarning, "SnapshotNotFound", + "No ReadyToUse snapshots were found for %v; proceeding with typical restore process.", sourceCluster.Name) + } + } else { + r.Recorder.Eventf(cluster, corev1.EventTypeWarning, "SnapshotNotFound", + "Could not get snapshots for %v, proceeding with typical restore process.", sourceCluster.Name) + } + } + r.setVolumeSize(ctx, cluster, pvc, instanceSpec.Name) // Clear any set limit before applying PVC. This is needed to allow the limit diff --git a/internal/controller/postgrescluster/postgres_test.go b/internal/controller/postgrescluster/postgres_test.go index fceafa1986..de39a3c0c0 100644 --- a/internal/controller/postgrescluster/postgres_test.go +++ b/internal/controller/postgrescluster/postgres_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -23,6 +12,7 @@ import ( "github.com/go-logr/logr/funcr" "github.com/google/go-cmp/cmp/cmpopts" + volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "github.com/pkg/errors" "gotest.tools/v3/assert" appsv1 "k8s.io/api/apps/v1" @@ -257,38 +247,193 @@ func TestReconcilePostgresVolumes(t *testing.T) { Owner: client.FieldOwner(t.Name()), } - cluster := testCluster() - cluster.Namespace = setupNamespace(t, tClient).Name + t.Run("DataVolumeNoSourceCluster", func(t *testing.T) { + cluster := testCluster() + ns := setupNamespace(t, tClient) + cluster.Namespace = ns.Name - assert.NilError(t, tClient.Create(ctx, cluster)) - t.Cleanup(func() { assert.Check(t, tClient.Delete(ctx, cluster)) }) + assert.NilError(t, tClient.Create(ctx, cluster)) + t.Cleanup(func() { assert.Check(t, tClient.Delete(ctx, cluster)) }) - spec := &v1beta1.PostgresInstanceSetSpec{} - assert.NilError(t, yaml.Unmarshal([]byte(`{ - name: "some-instance", - dataVolumeClaimSpec: { - accessModes: [ReadWriteOnce], - resources: { requests: { storage: 1Gi } }, - storageClassName: "storage-class-for-data", - }, - }`), spec)) + spec := &v1beta1.PostgresInstanceSetSpec{} + assert.NilError(t, yaml.Unmarshal([]byte(`{ + name: "some-instance", + dataVolumeClaimSpec: { + accessModes: [ReadWriteOnce], + resources: { requests: { storage: 1Gi } }, + storageClassName: "storage-class-for-data", + }, + }`), spec)) + instance := &appsv1.StatefulSet{ObjectMeta: naming.GenerateInstance(cluster, spec)} + + pvc, err := reconciler.reconcilePostgresDataVolume(ctx, cluster, spec, instance, nil, nil) + assert.NilError(t, err) + + assert.Equal(t, pvc.Labels[naming.LabelCluster], cluster.Name) + assert.Equal(t, pvc.Labels[naming.LabelInstance], instance.Name) + assert.Equal(t, pvc.Labels[naming.LabelInstanceSet], spec.Name) + assert.Equal(t, pvc.Labels[naming.LabelRole], "pgdata") + + assert.Assert(t, cmp.MarshalMatches(pvc.Spec, ` +accessModes: +- ReadWriteOnce +resources: + requests: + storage: 1Gi +storageClassName: storage-class-for-data +volumeMode: Filesystem + `)) + }) + + t.Run("DataVolumeSourceClusterWithGoodSnapshot", func(t *testing.T) { + cluster := testCluster() + ns := setupNamespace(t, tClient) + cluster.Namespace = ns.Name + + assert.NilError(t, tClient.Create(ctx, cluster)) + t.Cleanup(func() { assert.Check(t, tClient.Delete(ctx, cluster)) }) + + spec := &v1beta1.PostgresInstanceSetSpec{} + assert.NilError(t, yaml.Unmarshal([]byte(`{ + name: "some-instance", + dataVolumeClaimSpec: { + accessModes: [ReadWriteOnce], + resources: { requests: { storage: 1Gi } }, + storageClassName: "storage-class-for-data", + }, + }`), spec)) + instance := &appsv1.StatefulSet{ObjectMeta: naming.GenerateInstance(cluster, spec)} + + recorder := events.NewRecorder(t, runtime.Scheme) + reconciler.Recorder = recorder + + // Turn on VolumeSnapshots feature gate + gate := feature.NewGate() + assert.NilError(t, gate.SetFromMap(map[string]bool{ + feature.VolumeSnapshots: true, + })) + ctx := feature.NewContext(ctx, gate) + + // Create source cluster and enable snapshots + sourceCluster := testCluster() + sourceCluster.Namespace = ns.Name + sourceCluster.Name = "rhino" + sourceCluster.Spec.Backups.Snapshots = &v1beta1.VolumeSnapshots{ + VolumeSnapshotClassName: "some-class-name", + } + + // Create a snapshot + snapshot := &volumesnapshotv1.VolumeSnapshot{ + TypeMeta: metav1.TypeMeta{ + APIVersion: volumesnapshotv1.SchemeGroupVersion.String(), + Kind: "VolumeSnapshot", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-snapshot", + Namespace: ns.Name, + Labels: map[string]string{ + naming.LabelCluster: "rhino", + }, + }, + } + snapshot.Spec.Source.PersistentVolumeClaimName = initialize.String("some-pvc-name") + snapshot.Spec.VolumeSnapshotClassName = initialize.String("some-class-name") + err := reconciler.apply(ctx, snapshot) + assert.NilError(t, err) - instance := &appsv1.StatefulSet{ObjectMeta: naming.GenerateInstance(cluster, spec)} + // Get snapshot and update Status.ReadyToUse and CreationTime + err = reconciler.Client.Get(ctx, client.ObjectKeyFromObject(snapshot), snapshot) + assert.NilError(t, err) + + currentTime := metav1.Now() + snapshot.Status = &volumesnapshotv1.VolumeSnapshotStatus{ + ReadyToUse: initialize.Bool(true), + CreationTime: ¤tTime, + } + err = reconciler.Client.Status().Update(ctx, snapshot) + assert.NilError(t, err) - t.Run("DataVolume", func(t *testing.T) { - pvc, err := reconciler.reconcilePostgresDataVolume(ctx, cluster, spec, instance, nil) + // Reconcile volume + pvc, err := reconciler.reconcilePostgresDataVolume(ctx, cluster, spec, instance, nil, sourceCluster) assert.NilError(t, err) - // K8SPG-328: Keep this commented in case of conflicts. - // We don't want to delete PVCs if custom resource is deleted. - //assert.Assert(t, metav1.IsControlledBy(pvc, cluster)) + // assert.Assert(t, metav1.IsControlledBy(pvc, cluster)) + + assert.Equal(t, pvc.Labels[naming.LabelCluster], cluster.Name) + assert.Equal(t, pvc.Labels[naming.LabelInstance], instance.Name) + assert.Equal(t, pvc.Labels[naming.LabelInstanceSet], spec.Name) + assert.Equal(t, pvc.Labels[naming.LabelRole], "pgdata") + + assert.Assert(t, cmp.MarshalMatches(pvc.Spec, ` +accessModes: +- ReadWriteOnce +dataSource: + apiGroup: snapshot.storage.k8s.io + kind: VolumeSnapshot + name: some-snapshot +dataSourceRef: + apiGroup: snapshot.storage.k8s.io + kind: VolumeSnapshot + name: some-snapshot +resources: + requests: + storage: 1Gi +storageClassName: storage-class-for-data +volumeMode: Filesystem + `)) + assert.Equal(t, len(recorder.Events), 1) + assert.Equal(t, recorder.Events[0].Regarding.Name, cluster.Name) + assert.Equal(t, recorder.Events[0].Reason, "BootstrappingWithSnapshot") + assert.Equal(t, recorder.Events[0].Note, "Snapshot found for rhino; bootstrapping cluster with snapshot.") + }) + + t.Run("DataVolumeSourceClusterSnapshotsEnabledNoSnapshots", func(t *testing.T) { + cluster := testCluster() + ns := setupNamespace(t, tClient) + cluster.Namespace = ns.Name + + assert.NilError(t, tClient.Create(ctx, cluster)) + t.Cleanup(func() { assert.Check(t, tClient.Delete(ctx, cluster)) }) + + spec := &v1beta1.PostgresInstanceSetSpec{} + assert.NilError(t, yaml.Unmarshal([]byte(`{ + name: "some-instance", + dataVolumeClaimSpec: { + accessModes: [ReadWriteOnce], + resources: { requests: { storage: 1Gi } }, + storageClassName: "storage-class-for-data", + }, + }`), spec)) + instance := &appsv1.StatefulSet{ObjectMeta: naming.GenerateInstance(cluster, spec)} + + recorder := events.NewRecorder(t, runtime.Scheme) + reconciler.Recorder = recorder + + // Turn on VolumeSnapshots feature gate + gate := feature.NewGate() + assert.NilError(t, gate.SetFromMap(map[string]bool{ + feature.VolumeSnapshots: true, + })) + ctx := feature.NewContext(ctx, gate) + + // Create source cluster and enable snapshots + sourceCluster := testCluster() + sourceCluster.Namespace = ns.Name + sourceCluster.Name = "rhino" + sourceCluster.Spec.Backups.Snapshots = &v1beta1.VolumeSnapshots{ + VolumeSnapshotClassName: "some-class-name", + } + + // Reconcile volume + pvc, err := reconciler.reconcilePostgresDataVolume(ctx, cluster, spec, instance, nil, sourceCluster) + assert.NilError(t, err) assert.Equal(t, pvc.Labels[naming.LabelCluster], cluster.Name) assert.Equal(t, pvc.Labels[naming.LabelInstance], instance.Name) assert.Equal(t, pvc.Labels[naming.LabelInstanceSet], spec.Name) assert.Equal(t, pvc.Labels[naming.LabelRole], "pgdata") - assert.Assert(t, marshalMatches(pvc.Spec, ` + assert.Assert(t, cmp.MarshalMatches(pvc.Spec, ` accessModes: - ReadWriteOnce resources: @@ -297,9 +442,31 @@ resources: storageClassName: storage-class-for-data volumeMode: Filesystem `)) + assert.Equal(t, len(recorder.Events), 1) + assert.Equal(t, recorder.Events[0].Regarding.Name, cluster.Name) + assert.Equal(t, recorder.Events[0].Reason, "SnapshotNotFound") + assert.Equal(t, recorder.Events[0].Note, "No ReadyToUse snapshots were found for rhino; proceeding with typical restore process.") }) t.Run("WALVolume", func(t *testing.T) { + cluster := testCluster() + ns := setupNamespace(t, tClient) + cluster.Namespace = ns.Name + + assert.NilError(t, tClient.Create(ctx, cluster)) + t.Cleanup(func() { assert.Check(t, tClient.Delete(ctx, cluster)) }) + + spec := &v1beta1.PostgresInstanceSetSpec{} + assert.NilError(t, yaml.Unmarshal([]byte(`{ + name: "some-instance", + dataVolumeClaimSpec: { + accessModes: [ReadWriteOnce], + resources: { requests: { storage: 1Gi } }, + storageClassName: "storage-class-for-data", + }, + }`), spec)) + instance := &appsv1.StatefulSet{ObjectMeta: naming.GenerateInstance(cluster, spec)} + observed := &Instance{} t.Run("None", func(t *testing.T) { @@ -330,7 +497,7 @@ volumeMode: Filesystem assert.Equal(t, pvc.Labels[naming.LabelInstanceSet], spec.Name) assert.Equal(t, pvc.Labels[naming.LabelRole], "pgwal") - assert.Assert(t, marshalMatches(pvc.Spec, ` + assert.Assert(t, cmp.MarshalMatches(pvc.Spec, ` accessModes: - ReadWriteMany resources: @@ -510,7 +677,7 @@ func TestSetVolumeSize(t *testing.T) { reconciler.setVolumeSize(ctx, &cluster, pvc, spec.Name) - assert.Assert(t, marshalMatches(pvc.Spec, ` + assert.Assert(t, cmp.MarshalMatches(pvc.Spec, ` accessModes: - ReadWriteOnce resources: @@ -547,7 +714,7 @@ resources: reconciler.setVolumeSize(ctx, &cluster, pvc, spec.Name) - assert.Assert(t, marshalMatches(pvc.Spec, ` + assert.Assert(t, cmp.MarshalMatches(pvc.Spec, ` accessModes: - ReadWriteOnce resources: @@ -590,7 +757,7 @@ resources: reconciler.setVolumeSize(ctx, &cluster, pvc, spec.Name) - assert.Assert(t, marshalMatches(pvc.Spec, ` + assert.Assert(t, cmp.MarshalMatches(pvc.Spec, ` accessModes: - ReadWriteOnce resources: @@ -615,7 +782,7 @@ resources: reconciler.setVolumeSize(ctx, &cluster, pvc, spec.Name) - assert.Assert(t, marshalMatches(pvc.Spec, ` + assert.Assert(t, cmp.MarshalMatches(pvc.Spec, ` accessModes: - ReadWriteOnce resources: @@ -640,7 +807,7 @@ resources: reconciler.setVolumeSize(ctx, &cluster, pvc, spec.Name) - assert.Assert(t, marshalMatches(pvc.Spec, ` + assert.Assert(t, cmp.MarshalMatches(pvc.Spec, ` accessModes: - ReadWriteOnce resources: @@ -667,7 +834,7 @@ resources: reconciler.setVolumeSize(ctx, &cluster, pvc, spec.Name) - assert.Assert(t, marshalMatches(pvc.Spec, ` + assert.Assert(t, cmp.MarshalMatches(pvc.Spec, ` accessModes: - ReadWriteOnce resources: @@ -692,7 +859,7 @@ resources: reconciler.setVolumeSize(ctx, &cluster, pvc, spec.Name) - assert.Assert(t, marshalMatches(pvc.Spec, ` + assert.Assert(t, cmp.MarshalMatches(pvc.Spec, ` accessModes: - ReadWriteOnce resources: @@ -721,7 +888,7 @@ resources: reconciler.setVolumeSize(ctx, &cluster, pvc, spec.Name) - assert.Assert(t, marshalMatches(pvc.Spec, ` + assert.Assert(t, cmp.MarshalMatches(pvc.Spec, ` accessModes: - ReadWriteOnce resources: diff --git a/internal/controller/postgrescluster/rbac.go b/internal/controller/postgrescluster/rbac.go index db6707a9b0..df5dfd0bbe 100644 --- a/internal/controller/postgrescluster/rbac.go +++ b/internal/controller/postgrescluster/rbac.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster diff --git a/internal/controller/postgrescluster/snapshots.go b/internal/controller/postgrescluster/snapshots.go new file mode 100644 index 0000000000..6c9ada6518 --- /dev/null +++ b/internal/controller/postgrescluster/snapshots.go @@ -0,0 +1,616 @@ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package postgrescluster + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/pkg/errors" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" + + "github.com/percona/percona-postgresql-operator/internal/config" + "github.com/percona/percona-postgresql-operator/internal/feature" + "github.com/percona/percona-postgresql-operator/internal/initialize" + "github.com/percona/percona-postgresql-operator/internal/naming" + "github.com/percona/percona-postgresql-operator/internal/pgbackrest" + "github.com/percona/percona-postgresql-operator/internal/postgres" + "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" +) + +// +kubebuilder:rbac:groups="snapshot.storage.k8s.io",resources="volumesnapshots",verbs={get,list,create,patch,delete} + +// reconcileVolumeSnapshots creates and manages VolumeSnapshots if the proper VolumeSnapshot CRDs +// are installed and VolumeSnapshots are enabled for the PostgresCluster. A VolumeSnapshot of the +// primary instance's pgdata volume will be created whenever a backup is completed. The steps to +// create snapshots include the following sequence: +// 1. We find the latest completed backup job and check the timestamp. +// 2. If the timestamp is later than what's on the dedicated snapshot PVC, a restore job runs in +// the dedicated snapshot volume. +// 3. When the restore job completes, an annotation is updated on the PVC. If the restore job +// fails, we don't run it again. +// 4. When the PVC annotation is updated, we see if there's a volume snapshot with an earlier +// timestamp. +// 5. If there are no snapshots at all, we take a snapshot and put the backup job's completion +// timestamp on the snapshot annotation. +// 6. If an earlier snapshot is found, we take a new snapshot, annotate it and delete the old +// snapshot. +// 7. When the snapshot job completes, we delete the restore job. +func (r *Reconciler) reconcileVolumeSnapshots(ctx context.Context, + postgrescluster *v1beta1.PostgresCluster, pvc *corev1.PersistentVolumeClaim) error { + + // If VolumeSnapshots feature gate is disabled. Do nothing and return early. + if !feature.Enabled(ctx, feature.VolumeSnapshots) { + return nil + } + + // Check if the Kube cluster has VolumeSnapshots installed. If VolumeSnapshots + // are not installed, we need to return early. If user is attempting to use + // VolumeSnapshots, return an error, otherwise return nil. + volumeSnapshotKindExists, err := r.GroupVersionKindExists("snapshot.storage.k8s.io/v1", "VolumeSnapshot") + if err != nil { + return err + } + if !*volumeSnapshotKindExists { + if postgrescluster.Spec.Backups.Snapshots != nil { + return errors.New("VolumeSnapshots are not installed/enabled in this Kubernetes cluster; cannot create snapshot.") + } else { + return nil + } + } + + // If user is attempting to use snapshots and has tablespaces enabled, we + // need to create a warning event indicating that the two features are not + // currently compatible and return early. + if postgrescluster.Spec.Backups.Snapshots != nil && + clusterUsingTablespaces(ctx, postgrescluster) { + r.Recorder.Event(postgrescluster, corev1.EventTypeWarning, "IncompatibleFeatures", + "VolumeSnapshots not currently compatible with TablespaceVolumes; cannot create snapshot.") + return nil + } + + // Get all snapshots for the cluster. + snapshots, err := r.getSnapshotsForCluster(ctx, postgrescluster) + if err != nil { + return err + } + + // If snapshots are disabled, delete any existing snapshots and return early. + if postgrescluster.Spec.Backups.Snapshots == nil { + return r.deleteSnapshots(ctx, postgrescluster, snapshots) + } + + // If we got here, then the snapshots are enabled (feature gate is enabled and the + // cluster has a Spec.Backups.Snapshots section defined). + + // Check snapshots for errors; if present, create an event. If there are + // multiple snapshots with errors, create event for the latest error and + // delete any older snapshots with error. + snapshotWithLatestError := getSnapshotWithLatestError(snapshots) + if snapshotWithLatestError != nil { + r.Recorder.Event(postgrescluster, corev1.EventTypeWarning, "VolumeSnapshotError", + *snapshotWithLatestError.Status.Error.Message) + for _, snapshot := range snapshots.Items { + if snapshot.Status.Error != nil && + snapshot.Status.Error.Time.Before(snapshotWithLatestError.Status.Error.Time) { + err = r.deleteControlled(ctx, postgrescluster, &snapshot) + if err != nil { + return err + } + } + } + } + + // Get pvc backup job completion annotation. If it does not exist, there has not been + // a successful restore yet, so return early. + pvcUpdateTimeStamp, pvcAnnotationExists := pvc.GetAnnotations()[naming.PGBackRestBackupJobCompletion] + if !pvcAnnotationExists { + return err + } + + // Check to see if snapshot exists for the latest backup that has been restored into + // the dedicated pvc. + var snapshotForPvcUpdateIdx int + snapshotFoundForPvcUpdate := false + for idx, snapshot := range snapshots.Items { + if snapshot.GetAnnotations()[naming.PGBackRestBackupJobCompletion] == pvcUpdateTimeStamp { + snapshotForPvcUpdateIdx = idx + snapshotFoundForPvcUpdate = true + } + } + + // If a snapshot exists for the latest backup that has been restored into the dedicated pvc + // and the snapshot is Ready, delete all other snapshots. + if snapshotFoundForPvcUpdate && snapshots.Items[snapshotForPvcUpdateIdx].Status.ReadyToUse != nil && + *snapshots.Items[snapshotForPvcUpdateIdx].Status.ReadyToUse { + for idx, snapshot := range snapshots.Items { + if idx != snapshotForPvcUpdateIdx { + err = r.deleteControlled(ctx, postgrescluster, &snapshot) + if err != nil { + return err + } + } + } + } + + // If a snapshot for the latest backup/restore does not exist, create a snapshot. + if !snapshotFoundForPvcUpdate { + var snapshot *volumesnapshotv1.VolumeSnapshot + snapshot, err = r.generateSnapshotOfDedicatedSnapshotVolume(postgrescluster, pvc) + if err == nil { + err = errors.WithStack(r.apply(ctx, snapshot)) + } + } + + return err +} + +// +kubebuilder:rbac:groups="",resources="persistentvolumeclaims",verbs={get} +// +kubebuilder:rbac:groups="",resources="persistentvolumeclaims",verbs={create,delete,patch} + +// reconcileDedicatedSnapshotVolume reconciles the PersistentVolumeClaim that holds a +// copy of the pgdata and is dedicated for clean snapshots of the database. It creates +// and manages the volume as well as the restore jobs that bring the volume data forward +// after a successful backup. +func (r *Reconciler) reconcileDedicatedSnapshotVolume( + ctx context.Context, cluster *v1beta1.PostgresCluster, + clusterVolumes []corev1.PersistentVolumeClaim, +) (*corev1.PersistentVolumeClaim, error) { + + // If VolumeSnapshots feature gate is disabled, do nothing and return early. + if !feature.Enabled(ctx, feature.VolumeSnapshots) { + return nil, nil + } + + // Set appropriate labels for dedicated snapshot volume + labelMap := map[string]string{ + naming.LabelCluster: cluster.Name, + naming.LabelRole: naming.RoleSnapshot, + naming.LabelData: naming.DataPostgres, + } + + // If volume already exists, use existing name. Otherwise, generate a name. + var pvc *corev1.PersistentVolumeClaim + existingPVCName, err := getPGPVCName(labelMap, clusterVolumes) + if err != nil { + return nil, errors.WithStack(err) + } + if existingPVCName != "" { + pvc = &corev1.PersistentVolumeClaim{ObjectMeta: metav1.ObjectMeta{ + Namespace: cluster.GetNamespace(), + Name: existingPVCName, + }} + } else { + pvc = &corev1.PersistentVolumeClaim{ObjectMeta: naming.ClusterDedicatedSnapshotVolume(cluster)} + } + pvc.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("PersistentVolumeClaim")) + + // If snapshots are disabled, delete the PVC if it exists and return early. + // Check the client cache first using Get. + if cluster.Spec.Backups.Snapshots == nil { + key := client.ObjectKeyFromObject(pvc) + err := errors.WithStack(r.Client.Get(ctx, key, pvc)) + if err == nil { + err = errors.WithStack(r.deleteControlled(ctx, cluster, pvc)) + } + return nil, client.IgnoreNotFound(err) + } + + // If we've got this far, snapshots are enabled so we should create/update/get + // the dedicated snapshot volume + pvc, err = r.createDedicatedSnapshotVolume(ctx, cluster, labelMap, pvc) + if err != nil { + return pvc, err + } + + // Determine if we need to run a restore job, based on the most recent backup + // and an annotation on the PVC. + + // Find the most recently completed backup job. + backupJob, err := r.getLatestCompleteBackupJob(ctx, cluster) + if err != nil { + return pvc, err + } + + // Return early if no complete backup job is found. + if backupJob == nil { + return pvc, nil + } + + // Return early if the pvc is annotated with a timestamp newer or equal to the latest backup job. + // If the annotation value cannot be parsed, we want to proceed with a restore. + pvcAnnotationTimestampString := pvc.GetAnnotations()[naming.PGBackRestBackupJobCompletion] + if pvcAnnotationTime, err := time.Parse(time.RFC3339, pvcAnnotationTimestampString); err == nil { + if backupJob.Status.CompletionTime.Compare(pvcAnnotationTime) <= 0 { + return pvc, nil + } + } + + // If we've made it here, the pvc has not been restored with latest backup. + // Find the dedicated snapshot volume restore job if it exists. Since we delete + // successful restores after we annotate the PVC and stop making restore jobs + // if a failed DSV restore job exists, there should only ever be one DSV restore + // job in existence at a time. + // TODO(snapshots): Should this function throw an error or something if multiple + // DSV restores somehow exist? + restoreJob, err := r.getDedicatedSnapshotVolumeRestoreJob(ctx, cluster) + if err != nil { + return pvc, err + } + + // If we don't find a restore job, we run one. + if restoreJob == nil { + err = r.dedicatedSnapshotVolumeRestore(ctx, cluster, pvc, backupJob) + return pvc, err + } + + // If we've made it here, we have found a restore job. If the restore job was + // successful, set/update the annotation on the PVC and delete the restore job. + if restoreJob.Status.Succeeded == 1 { + if pvc.GetAnnotations() == nil { + pvc.Annotations = map[string]string{} + } + pvc.Annotations[naming.PGBackRestBackupJobCompletion] = restoreJob.GetAnnotations()[naming.PGBackRestBackupJobCompletion] + annotations := fmt.Sprintf(`{"metadata":{"annotations":{"%s": "%s"}}}`, + naming.PGBackRestBackupJobCompletion, pvc.Annotations[naming.PGBackRestBackupJobCompletion]) + + patch := client.RawPatch(client.Merge.Type(), []byte(annotations)) + err = r.handlePersistentVolumeClaimError(cluster, + errors.WithStack(r.patch(ctx, pvc, patch))) + + if err != nil { + return pvc, err + } + + err = r.Client.Delete(ctx, restoreJob, client.PropagationPolicy(metav1.DeletePropagationBackground)) + return pvc, errors.WithStack(err) + } + + // If the restore job failed, create a warning event. + if restoreJob.Status.Failed == 1 { + r.Recorder.Event(cluster, corev1.EventTypeWarning, + "DedicatedSnapshotVolumeRestoreJobError", "restore job failed, check the logs") + return pvc, nil + } + + // If we made it here, the restore job is still running and we should do nothing. + return pvc, err +} + +// createDedicatedSnapshotVolume creates/updates/gets the dedicated snapshot volume. +// It expects that the volume name and GVK has already been set on the pvc that is passed in. +func (r *Reconciler) createDedicatedSnapshotVolume(ctx context.Context, + cluster *v1beta1.PostgresCluster, labelMap map[string]string, + pvc *corev1.PersistentVolumeClaim, +) (*corev1.PersistentVolumeClaim, error) { + var err error + + // An InstanceSet must be chosen to scale resources for the dedicated snapshot volume. + // TODO: We've chosen the first InstanceSet for the time being, but might want to consider + // making the choice configurable. + instanceSpec := cluster.Spec.InstanceSets[0] + + pvc.Annotations = naming.Merge( + cluster.Spec.Metadata.GetAnnotationsOrNil(), + instanceSpec.Metadata.GetAnnotationsOrNil()) + + pvc.Labels = naming.Merge( + cluster.Spec.Metadata.GetLabelsOrNil(), + instanceSpec.Metadata.GetLabelsOrNil(), + labelMap, + ) + + err = errors.WithStack(r.setControllerReference(cluster, pvc)) + if err != nil { + return pvc, err + } + + pvc.Spec = instanceSpec.DataVolumeClaimSpec + + // Set the snapshot volume to the same size as the pgdata volume. The size should scale with auto-grow. + r.setVolumeSize(ctx, cluster, pvc, instanceSpec.Name) + + // Clear any set limit before applying PVC. This is needed to allow the limit + // value to change later. + pvc.Spec.Resources.Limits = nil + + err = r.handlePersistentVolumeClaimError(cluster, + errors.WithStack(r.apply(ctx, pvc))) + if err != nil { + return pvc, err + } + + return pvc, err +} + +// dedicatedSnapshotVolumeRestore creates a Job that performs a restore into the dedicated +// snapshot volume. +// This function is very similar to reconcileRestoreJob, but specifically tailored to the +// dedicated snapshot volume. +func (r *Reconciler) dedicatedSnapshotVolumeRestore(ctx context.Context, + cluster *v1beta1.PostgresCluster, dedicatedSnapshotVolume *corev1.PersistentVolumeClaim, + backupJob *batchv1.Job, +) error { + + pgdata := postgres.DataDirectory(cluster) + repoName := backupJob.GetLabels()[naming.LabelPGBackRestRepo] + + opts := []string{ + "--stanza=" + pgbackrest.DefaultStanzaName, + "--pg1-path=" + pgdata, + "--repo=" + regexRepoIndex.FindString(repoName), + "--delta", + } + + cmd := pgbackrest.DedicatedSnapshotVolumeRestoreCommand(pgdata, strings.Join(opts, " ")) + + // Create the volume resources required for the Postgres data directory. + dataVolumeMount := postgres.DataVolumeMount() + dataVolume := corev1.Volume{ + Name: dataVolumeMount.Name, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: dedicatedSnapshotVolume.GetName(), + }, + }, + } + volumes := []corev1.Volume{dataVolume} + volumeMounts := []corev1.VolumeMount{dataVolumeMount} + + _, configHash, err := pgbackrest.CalculateConfigHashes(cluster) + if err != nil { + return err + } + + // A DataSource is required to avoid a nil pointer exception. + fakeDataSource := &v1beta1.PostgresClusterDataSource{RepoName: ""} + + restoreJob := &batchv1.Job{} + instanceName := cluster.Status.StartupInstance + + if err := r.generateRestoreJobIntent(cluster, configHash, instanceName, cmd, + volumeMounts, volumes, fakeDataSource, restoreJob); err != nil { + return errors.WithStack(err) + } + + // Attempt the restore exactly once. If the restore job fails, we prompt the user to investigate. + restoreJob.Spec.BackoffLimit = initialize.Int32(0) + restoreJob.Spec.Template.Spec.RestartPolicy = corev1.RestartPolicyNever + + // Add pgBackRest configs to template. + pgbackrest.AddConfigToRestorePod(cluster, cluster, &restoreJob.Spec.Template.Spec) + + // Add nss_wrapper init container and add nss_wrapper env vars to the pgbackrest restore container. + addNSSWrapper( + cluster, + config.PGBackRestContainerImage(cluster), + cluster.Spec.ImagePullPolicy, + &restoreJob.Spec.Template) + + sizeLimit := getTMPSizeLimit(restoreJob.Labels[naming.LabelVersion], fakeDataSource.Resources) + addTMPEmptyDir(&restoreJob.Spec.Template, sizeLimit) + + restoreJob.Annotations[naming.PGBackRestBackupJobCompletion] = backupJob.Status.CompletionTime.Format(time.RFC3339) + return errors.WithStack(r.apply(ctx, restoreJob)) +} + +// generateSnapshotOfDedicatedSnapshotVolume will generate a VolumeSnapshot of +// the dedicated snapshot PersistentVolumeClaim and annotate it with the +// provided backup job's UID. +func (r *Reconciler) generateSnapshotOfDedicatedSnapshotVolume( + postgrescluster *v1beta1.PostgresCluster, + dedicatedSnapshotVolume *corev1.PersistentVolumeClaim, +) (*volumesnapshotv1.VolumeSnapshot, error) { + + snapshot, err := r.generateVolumeSnapshot(postgrescluster, *dedicatedSnapshotVolume, + postgrescluster.Spec.Backups.Snapshots.VolumeSnapshotClassName) + if err == nil { + if snapshot.Annotations == nil { + snapshot.Annotations = map[string]string{} + } + snapshot.Annotations[naming.PGBackRestBackupJobCompletion] = dedicatedSnapshotVolume.GetAnnotations()[naming.PGBackRestBackupJobCompletion] + } + + return snapshot, err +} + +// generateVolumeSnapshot generates a VolumeSnapshot that will use the supplied +// PersistentVolumeClaim and VolumeSnapshotClassName and will set the provided +// PostgresCluster as the owner. +func (r *Reconciler) generateVolumeSnapshot(postgrescluster *v1beta1.PostgresCluster, + pvc corev1.PersistentVolumeClaim, volumeSnapshotClassName string, +) (*volumesnapshotv1.VolumeSnapshot, error) { + + snapshot := &volumesnapshotv1.VolumeSnapshot{ + TypeMeta: metav1.TypeMeta{ + APIVersion: volumesnapshotv1.SchemeGroupVersion.String(), + Kind: "VolumeSnapshot", + }, + ObjectMeta: naming.ClusterVolumeSnapshot(postgrescluster), + } + snapshot.Spec.Source.PersistentVolumeClaimName = &pvc.Name + snapshot.Spec.VolumeSnapshotClassName = &volumeSnapshotClassName + + snapshot.Annotations = postgrescluster.Spec.Metadata.GetAnnotationsOrNil() + snapshot.Labels = naming.Merge(postgrescluster.Spec.Metadata.GetLabelsOrNil(), + map[string]string{ + naming.LabelCluster: postgrescluster.Name, + }) + + err := errors.WithStack(r.setControllerReference(postgrescluster, snapshot)) + + return snapshot, err +} + +// getDedicatedSnapshotVolumeRestoreJob finds a dedicated snapshot volume (DSV) +// restore job if one exists. Since we delete successful restore jobs and stop +// creating new restore jobs when one fails, there should only ever be one DSV +// restore job present at a time. If a DSV restore cannot be found, we return nil. +func (r *Reconciler) getDedicatedSnapshotVolumeRestoreJob(ctx context.Context, + postgrescluster *v1beta1.PostgresCluster) (*batchv1.Job, error) { + + // Get all restore jobs for this cluster + jobs := &batchv1.JobList{} + selectJobs, err := naming.AsSelector(naming.ClusterRestoreJobs(postgrescluster.Name)) + if err == nil { + err = errors.WithStack( + r.Client.List(ctx, jobs, + client.InNamespace(postgrescluster.Namespace), + client.MatchingLabelsSelector{Selector: selectJobs}, + )) + } + if err != nil { + return nil, err + } + + // Get restore job that has PGBackRestBackupJobCompletion annotation + for _, job := range jobs.Items { + _, annotationExists := job.GetAnnotations()[naming.PGBackRestBackupJobCompletion] + if annotationExists { + return &job, nil + } + } + + return nil, nil +} + +// getLatestCompleteBackupJob finds the most recently completed +// backup job for a cluster +func (r *Reconciler) getLatestCompleteBackupJob(ctx context.Context, + postgrescluster *v1beta1.PostgresCluster) (*batchv1.Job, error) { + + // Get all backup jobs for this cluster + jobs := &batchv1.JobList{} + selectJobs, err := naming.AsSelector(naming.ClusterBackupJobs(postgrescluster.Name)) + if err == nil { + err = errors.WithStack( + r.Client.List(ctx, jobs, + client.InNamespace(postgrescluster.Namespace), + client.MatchingLabelsSelector{Selector: selectJobs}, + )) + } + if err != nil { + return nil, err + } + + zeroTime := metav1.NewTime(time.Time{}) + latestCompleteBackupJob := batchv1.Job{ + Status: batchv1.JobStatus{ + Succeeded: 1, + CompletionTime: &zeroTime, + }, + } + for _, job := range jobs.Items { + if job.Status.Succeeded > 0 && + latestCompleteBackupJob.Status.CompletionTime.Before(job.Status.CompletionTime) { + latestCompleteBackupJob = job + } + } + + if latestCompleteBackupJob.Status.CompletionTime.Equal(&zeroTime) { + return nil, nil + } + + return &latestCompleteBackupJob, nil +} + +// getSnapshotWithLatestError takes a VolumeSnapshotList and returns a pointer to the +// snapshot that has most recently had an error. If no snapshot errors exist +// then it returns nil. +func getSnapshotWithLatestError(snapshots *volumesnapshotv1.VolumeSnapshotList) *volumesnapshotv1.VolumeSnapshot { + zeroTime := metav1.NewTime(time.Time{}) + snapshotWithLatestError := volumesnapshotv1.VolumeSnapshot{ + Status: &volumesnapshotv1.VolumeSnapshotStatus{ + Error: &volumesnapshotv1.VolumeSnapshotError{ + Time: &zeroTime, + }, + }, + } + for _, snapshot := range snapshots.Items { + if snapshot.Status.Error != nil && + snapshotWithLatestError.Status.Error.Time.Before(snapshot.Status.Error.Time) { + snapshotWithLatestError = snapshot + } + } + + if snapshotWithLatestError.Status.Error.Time.Equal(&zeroTime) { + return nil + } + + return &snapshotWithLatestError +} + +// getSnapshotsForCluster gets all the VolumeSnapshots for a given postgrescluster. +func (r *Reconciler) getSnapshotsForCluster(ctx context.Context, cluster *v1beta1.PostgresCluster) ( + *volumesnapshotv1.VolumeSnapshotList, error) { + + selectSnapshots, err := naming.AsSelector(naming.Cluster(cluster.Name)) + if err != nil { + return nil, err + } + snapshots := &volumesnapshotv1.VolumeSnapshotList{} + err = errors.WithStack( + r.Client.List(ctx, snapshots, + client.InNamespace(cluster.Namespace), + client.MatchingLabelsSelector{Selector: selectSnapshots}, + )) + + return snapshots, err +} + +// getLatestReadySnapshot takes a VolumeSnapshotList and returns the latest ready VolumeSnapshot. +func getLatestReadySnapshot(snapshots *volumesnapshotv1.VolumeSnapshotList) *volumesnapshotv1.VolumeSnapshot { + zeroTime := metav1.NewTime(time.Time{}) + latestReadySnapshot := volumesnapshotv1.VolumeSnapshot{ + Status: &volumesnapshotv1.VolumeSnapshotStatus{ + CreationTime: &zeroTime, + }, + } + for _, snapshot := range snapshots.Items { + if snapshot.Status.ReadyToUse != nil && *snapshot.Status.ReadyToUse && + latestReadySnapshot.Status.CreationTime.Before(snapshot.Status.CreationTime) { + latestReadySnapshot = snapshot + } + } + + if latestReadySnapshot.Status.CreationTime.Equal(&zeroTime) { + return nil + } + + return &latestReadySnapshot +} + +// deleteSnapshots takes a postgrescluster and a snapshot list and deletes all snapshots +// in the list that are controlled by the provided postgrescluster. +func (r *Reconciler) deleteSnapshots(ctx context.Context, + postgrescluster *v1beta1.PostgresCluster, snapshots *volumesnapshotv1.VolumeSnapshotList) error { + + for i := range snapshots.Items { + err := errors.WithStack(client.IgnoreNotFound( + r.deleteControlled(ctx, postgrescluster, &snapshots.Items[i]))) + if err != nil { + return err + } + } + return nil +} + +// tablespaceVolumesInUse determines if the TablespaceVolumes feature is enabled and the given +// cluster has tablespace volumes in place. +func clusterUsingTablespaces(ctx context.Context, postgrescluster *v1beta1.PostgresCluster) bool { + for _, instanceSet := range postgrescluster.Spec.InstanceSets { + if len(instanceSet.TablespaceVolumes) > 0 { + return feature.Enabled(ctx, feature.TablespaceVolumes) + } + } + return false +} diff --git a/internal/controller/postgrescluster/snapshots_test.go b/internal/controller/postgrescluster/snapshots_test.go new file mode 100644 index 0000000000..e148997c15 --- /dev/null +++ b/internal/controller/postgrescluster/snapshots_test.go @@ -0,0 +1,1453 @@ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package postgrescluster + +import ( + "context" + "testing" + "time" + + volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" + "github.com/pkg/errors" + "gotest.tools/v3/assert" + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/discovery" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/percona/percona-postgresql-operator/internal/controller/runtime" + "github.com/percona/percona-postgresql-operator/internal/feature" + "github.com/percona/percona-postgresql-operator/internal/initialize" + "github.com/percona/percona-postgresql-operator/internal/naming" + "github.com/percona/percona-postgresql-operator/internal/testing/cmp" + "github.com/percona/percona-postgresql-operator/internal/testing/events" + "github.com/percona/percona-postgresql-operator/internal/testing/require" + "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" +) + +func TestReconcileVolumeSnapshots(t *testing.T) { + ctx := context.Background() + cfg, cc := setupKubernetes(t) + require.ParallelCapacity(t, 1) + discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg) + assert.NilError(t, err) + + recorder := events.NewRecorder(t, runtime.Scheme) + r := &Reconciler{ + Client: cc, + Owner: client.FieldOwner(t.Name()), + DiscoveryClient: discoveryClient, + Recorder: recorder, + } + ns := setupNamespace(t, cc) + + // Enable snapshots feature gate + gate := feature.NewGate() + assert.NilError(t, gate.SetFromMap(map[string]bool{ + feature.VolumeSnapshots: true, + })) + ctx = feature.NewContext(ctx, gate) + + t.Run("SnapshotsDisabledDeleteSnapshots", func(t *testing.T) { + // Create cluster (without snapshots spec) + cluster := testCluster() + cluster.Namespace = ns.Name + cluster.ObjectMeta.UID = "the-uid-123" + assert.NilError(t, r.Client.Create(ctx, cluster)) + t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) }) + + // Create a snapshot + pvc := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dedicated-snapshot-volume", + }, + } + volumeSnapshotClassName := "my-snapshotclass" + snapshot, err := r.generateVolumeSnapshot(cluster, *pvc, volumeSnapshotClassName) + assert.NilError(t, err) + err = errors.WithStack(r.apply(ctx, snapshot)) + assert.NilError(t, err) + + // Get all snapshots for this cluster and assert 1 exists + selectSnapshots, err := naming.AsSelector(naming.Cluster(cluster.Name)) + assert.NilError(t, err) + snapshots := &volumesnapshotv1.VolumeSnapshotList{} + err = errors.WithStack( + r.Client.List(ctx, snapshots, + client.InNamespace(cluster.Namespace), + client.MatchingLabelsSelector{Selector: selectSnapshots}, + )) + assert.NilError(t, err) + assert.Equal(t, len(snapshots.Items), 1) + + // Reconcile snapshots + err = r.reconcileVolumeSnapshots(ctx, cluster, pvc) + assert.NilError(t, err) + + // Get all snapshots for this cluster and assert 0 exist + assert.NilError(t, err) + snapshots = &volumesnapshotv1.VolumeSnapshotList{} + err = errors.WithStack( + r.Client.List(ctx, snapshots, + client.InNamespace(cluster.Namespace), + client.MatchingLabelsSelector{Selector: selectSnapshots}, + )) + assert.NilError(t, err) + assert.Equal(t, len(snapshots.Items), 0) + }) + + t.Run("SnapshotsEnabledTablespacesEnabled", func(t *testing.T) { + // Enable both tablespaces and snapshots feature gates + gate := feature.NewGate() + assert.NilError(t, gate.SetFromMap(map[string]bool{ + feature.TablespaceVolumes: true, + feature.VolumeSnapshots: true, + })) + ctx := feature.NewContext(ctx, gate) + + // Create a cluster with snapshots and tablespaces enabled + volumeSnapshotClassName := "my-snapshotclass" + cluster := testCluster() + cluster.Namespace = ns.Name + cluster.Spec.Backups.Snapshots = &v1beta1.VolumeSnapshots{ + VolumeSnapshotClassName: volumeSnapshotClassName, + } + cluster.Spec.InstanceSets[0].TablespaceVolumes = []v1beta1.TablespaceVolume{{ + Name: "volume-1", + }} + + // Create pvc for reconcile + pvc := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dedicated-snapshot-volume", + }, + } + + // Reconcile + err = r.reconcileVolumeSnapshots(ctx, cluster, pvc) + assert.NilError(t, err) + + // Assert warning event was created and has expected attributes + if assert.Check(t, len(recorder.Events) > 0) { + assert.Equal(t, recorder.Events[0].Type, "Warning") + assert.Equal(t, recorder.Events[0].Regarding.Kind, "PostgresCluster") + assert.Equal(t, recorder.Events[0].Regarding.Name, "hippo") + assert.Equal(t, recorder.Events[0].Reason, "IncompatibleFeatures") + assert.Assert(t, cmp.Contains(recorder.Events[0].Note, "VolumeSnapshots not currently compatible with TablespaceVolumes")) + } + }) + + t.Run("SnapshotsEnabledNoPvcAnnotation", func(t *testing.T) { + // Create a volume snapshot class + volumeSnapshotClassName := "my-snapshotclass" + volumeSnapshotClass := &volumesnapshotv1.VolumeSnapshotClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: volumeSnapshotClassName, + }, + DeletionPolicy: "Delete", + } + assert.NilError(t, r.Client.Create(ctx, volumeSnapshotClass)) + t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, volumeSnapshotClass)) }) + + // Create a cluster with snapshots enabled + cluster := testCluster() + cluster.Namespace = ns.Name + cluster.Spec.Backups.Snapshots = &v1beta1.VolumeSnapshots{ + VolumeSnapshotClassName: volumeSnapshotClassName, + } + assert.NilError(t, r.Client.Create(ctx, cluster)) + t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) }) + + // Create pvc for reconcile + pvc := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dedicated-snapshot-volume", + }, + } + + // Reconcile + err = r.reconcileVolumeSnapshots(ctx, cluster, pvc) + assert.NilError(t, err) + + // Assert no snapshots exist + selectSnapshots, err := naming.AsSelector(naming.Cluster(cluster.Name)) + assert.NilError(t, err) + snapshots := &volumesnapshotv1.VolumeSnapshotList{} + err = errors.WithStack( + r.Client.List(ctx, snapshots, + client.InNamespace(cluster.Namespace), + client.MatchingLabelsSelector{Selector: selectSnapshots}, + )) + assert.NilError(t, err) + assert.Equal(t, len(snapshots.Items), 0) + }) + + t.Run("SnapshotsEnabledReadySnapshotsExist", func(t *testing.T) { + // Create a volume snapshot class + volumeSnapshotClassName := "my-snapshotclass" + volumeSnapshotClass := &volumesnapshotv1.VolumeSnapshotClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: volumeSnapshotClassName, + }, + DeletionPolicy: "Delete", + } + assert.NilError(t, r.Client.Create(ctx, volumeSnapshotClass)) + t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, volumeSnapshotClass)) }) + + // Create a cluster with snapshots enabled + cluster := testCluster() + cluster.Namespace = ns.Name + cluster.ObjectMeta.UID = "the-uid-123" + cluster.Spec.Backups.Snapshots = &v1beta1.VolumeSnapshots{ + VolumeSnapshotClassName: volumeSnapshotClassName, + } + assert.NilError(t, r.Client.Create(ctx, cluster)) + t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) }) + + // Create pvc with annotation + pvcName := initialize.String("dedicated-snapshot-volume") + pvc := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: *pvcName, + Annotations: map[string]string{ + naming.PGBackRestBackupJobCompletion: "backup-timestamp", + }, + }, + } + + // Create snapshot with annotation matching the pvc annotation + snapshot1 := &volumesnapshotv1.VolumeSnapshot{ + TypeMeta: metav1.TypeMeta{ + APIVersion: volumesnapshotv1.SchemeGroupVersion.String(), + Kind: "VolumeSnapshot", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "first-snapshot", + Namespace: ns.Name, + Annotations: map[string]string{ + naming.PGBackRestBackupJobCompletion: "backup-timestamp", + }, + Labels: map[string]string{ + naming.LabelCluster: "hippo", + }, + }, + Spec: volumesnapshotv1.VolumeSnapshotSpec{ + Source: volumesnapshotv1.VolumeSnapshotSource{ + PersistentVolumeClaimName: pvcName, + }, + }, + } + err := errors.WithStack(r.setControllerReference(cluster, snapshot1)) + assert.NilError(t, err) + err = r.apply(ctx, snapshot1) + assert.NilError(t, err) + + // Update snapshot status + truePtr := initialize.Bool(true) + snapshot1.Status = &volumesnapshotv1.VolumeSnapshotStatus{ + ReadyToUse: truePtr, + } + err = r.Client.Status().Update(ctx, snapshot1) + assert.NilError(t, err) + + // Create second snapshot with different annotation value + snapshot2 := &volumesnapshotv1.VolumeSnapshot{ + TypeMeta: metav1.TypeMeta{ + APIVersion: volumesnapshotv1.SchemeGroupVersion.String(), + Kind: "VolumeSnapshot", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "second-snapshot", + Namespace: ns.Name, + Annotations: map[string]string{ + naming.PGBackRestBackupJobCompletion: "older-backup-timestamp", + }, + Labels: map[string]string{ + naming.LabelCluster: "hippo", + }, + }, + Spec: volumesnapshotv1.VolumeSnapshotSpec{ + Source: volumesnapshotv1.VolumeSnapshotSource{ + PersistentVolumeClaimName: pvcName, + }, + }, + } + err = errors.WithStack(r.setControllerReference(cluster, snapshot2)) + assert.NilError(t, err) + err = r.apply(ctx, snapshot2) + assert.NilError(t, err) + + // Update second snapshot's status + snapshot2.Status = &volumesnapshotv1.VolumeSnapshotStatus{ + ReadyToUse: truePtr, + } + err = r.Client.Status().Update(ctx, snapshot2) + assert.NilError(t, err) + + // Reconcile + err = r.reconcileVolumeSnapshots(ctx, cluster, pvc) + assert.NilError(t, err) + + // Assert first snapshot exists and second snapshot was deleted + selectSnapshots, err := naming.AsSelector(naming.Cluster(cluster.Name)) + assert.NilError(t, err) + snapshots := &volumesnapshotv1.VolumeSnapshotList{} + err = errors.WithStack( + r.Client.List(ctx, snapshots, + client.InNamespace(cluster.Namespace), + client.MatchingLabelsSelector{Selector: selectSnapshots}, + )) + assert.NilError(t, err) + assert.Equal(t, len(snapshots.Items), 1) + assert.Equal(t, snapshots.Items[0].Name, "first-snapshot") + + // Cleanup + err = r.deleteControlled(ctx, cluster, snapshot1) + assert.NilError(t, err) + }) + + t.Run("SnapshotsEnabledCreateSnapshot", func(t *testing.T) { + // Create a volume snapshot class + volumeSnapshotClassName := "my-snapshotclass" + volumeSnapshotClass := &volumesnapshotv1.VolumeSnapshotClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: volumeSnapshotClassName, + }, + DeletionPolicy: "Delete", + } + assert.NilError(t, r.Client.Create(ctx, volumeSnapshotClass)) + t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, volumeSnapshotClass)) }) + + // Create a cluster with snapshots enabled + cluster := testCluster() + cluster.Namespace = ns.Name + cluster.ObjectMeta.UID = "the-uid-123" + cluster.Spec.Backups.Snapshots = &v1beta1.VolumeSnapshots{ + VolumeSnapshotClassName: volumeSnapshotClassName, + } + assert.NilError(t, r.Client.Create(ctx, cluster)) + t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) }) + + // Create pvc with annotation + pvcName := initialize.String("dedicated-snapshot-volume") + pvc := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: *pvcName, + Annotations: map[string]string{ + naming.PGBackRestBackupJobCompletion: "another-backup-timestamp", + }, + }, + } + + // Reconcile + err = r.reconcileVolumeSnapshots(ctx, cluster, pvc) + assert.NilError(t, err) + + // Assert that a snapshot was created + selectSnapshots, err := naming.AsSelector(naming.Cluster(cluster.Name)) + assert.NilError(t, err) + snapshots := &volumesnapshotv1.VolumeSnapshotList{} + err = errors.WithStack( + r.Client.List(ctx, snapshots, + client.InNamespace(cluster.Namespace), + client.MatchingLabelsSelector{Selector: selectSnapshots}, + )) + assert.NilError(t, err) + assert.Equal(t, len(snapshots.Items), 1) + assert.Equal(t, snapshots.Items[0].Annotations[naming.PGBackRestBackupJobCompletion], + "another-backup-timestamp") + }) +} + +func TestReconcileDedicatedSnapshotVolume(t *testing.T) { + ctx := context.Background() + cfg, cc := setupKubernetes(t) + discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg) + assert.NilError(t, err) + + recorder := events.NewRecorder(t, runtime.Scheme) + r := &Reconciler{ + Client: cc, + Owner: client.FieldOwner(t.Name()), + DiscoveryClient: discoveryClient, + Recorder: recorder, + } + + // Enable snapshots feature gate + gate := feature.NewGate() + assert.NilError(t, gate.SetFromMap(map[string]bool{ + feature.VolumeSnapshots: true, + })) + ctx = feature.NewContext(ctx, gate) + + t.Run("SnapshotsDisabledDeletePvc", func(t *testing.T) { + // Create cluster without snapshots spec + ns := setupNamespace(t, cc) + cluster := testCluster() + cluster.Namespace = ns.Name + cluster.ObjectMeta.UID = "the-uid-123" + assert.NilError(t, r.Client.Create(ctx, cluster)) + t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) }) + + // Create a dedicated snapshot volume + pvc := &corev1.PersistentVolumeClaim{ + TypeMeta: metav1.TypeMeta{ + Kind: "PersistentVolumeClaim", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "dedicated-snapshot-volume", + Namespace: ns.Name, + Labels: map[string]string{ + naming.LabelCluster: cluster.Name, + naming.LabelRole: naming.RoleSnapshot, + naming.LabelData: naming.DataPostgres, + }, + }, + Spec: testVolumeClaimSpec(), + } + err = errors.WithStack(r.setControllerReference(cluster, pvc)) + assert.NilError(t, err) + err = r.apply(ctx, pvc) + assert.NilError(t, err) + + // Assert that the pvc was created + selectPvcs, err := naming.AsSelector(naming.Cluster(cluster.Name)) + assert.NilError(t, err) + pvcs := &corev1.PersistentVolumeClaimList{} + err = errors.WithStack( + r.Client.List(ctx, pvcs, + client.InNamespace(cluster.Namespace), + client.MatchingLabelsSelector{Selector: selectPvcs}, + )) + assert.NilError(t, err) + assert.Equal(t, len(pvcs.Items), 1) + + // Create volumes for reconcile + clusterVolumes := []corev1.PersistentVolumeClaim{*pvc} + + // Reconcile + returned, err := r.reconcileDedicatedSnapshotVolume(ctx, cluster, clusterVolumes) + assert.NilError(t, err) + assert.Check(t, returned == nil) + + // Assert that the pvc has been deleted or marked for deletion + key, fetched := client.ObjectKeyFromObject(pvc), &corev1.PersistentVolumeClaim{} + if err := r.Client.Get(ctx, key, fetched); err == nil { + assert.Assert(t, fetched.DeletionTimestamp != nil, "expected deleted") + } else { + assert.Assert(t, apierrors.IsNotFound(err), "expected NotFound, got %v", err) + } + }) + + t.Run("SnapshotsEnabledCreatePvcNoBackupNoRestore", func(t *testing.T) { + // Create cluster with snapshots enabled + ns := setupNamespace(t, cc) + cluster := testCluster() + cluster.Namespace = ns.Name + cluster.ObjectMeta.UID = "the-uid-123" + cluster.Spec.Backups.Snapshots = &v1beta1.VolumeSnapshots{ + VolumeSnapshotClassName: "my-snapshotclass", + } + assert.NilError(t, r.Client.Create(ctx, cluster)) + t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) }) + + // Create volumes for reconcile + clusterVolumes := []corev1.PersistentVolumeClaim{} + + // Reconcile + pvc, err := r.reconcileDedicatedSnapshotVolume(ctx, cluster, clusterVolumes) + assert.NilError(t, err) + assert.Assert(t, pvc != nil) + + // Assert pvc was created + selectPvcs, err := naming.AsSelector(naming.Cluster(cluster.Name)) + assert.NilError(t, err) + pvcs := &corev1.PersistentVolumeClaimList{} + err = errors.WithStack( + r.Client.List(ctx, pvcs, + client.InNamespace(cluster.Namespace), + client.MatchingLabelsSelector{Selector: selectPvcs}, + )) + assert.NilError(t, err) + assert.Equal(t, len(pvcs.Items), 1) + }) + + t.Run("SnapshotsEnabledBackupExistsCreateRestore", func(t *testing.T) { + // Create cluster with snapshots enabled + ns := setupNamespace(t, cc) + cluster := testCluster() + cluster.Namespace = ns.Name + cluster.ObjectMeta.UID = "the-uid-123" + cluster.Spec.Backups.Snapshots = &v1beta1.VolumeSnapshots{ + VolumeSnapshotClassName: "my-snapshotclass", + } + assert.NilError(t, r.Client.Create(ctx, cluster)) + t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) }) + + // Create successful backup job + backupJob := testBackupJob(cluster) + err = errors.WithStack(r.setControllerReference(cluster, backupJob)) + assert.NilError(t, err) + err = r.apply(ctx, backupJob) + assert.NilError(t, err) + + currentTime := metav1.Now() + backupJob.Status = batchv1.JobStatus{ + Succeeded: 1, + CompletionTime: ¤tTime, + } + err = r.Client.Status().Update(ctx, backupJob) + assert.NilError(t, err) + + // Create instance set and volumes for reconcile + sts := &appsv1.StatefulSet{} + generateInstanceStatefulSetIntent(ctx, cluster, &cluster.Spec.InstanceSets[0], "pod-service", "service-account", sts, 1) + clusterVolumes := []corev1.PersistentVolumeClaim{} + + // Reconcile + pvc, err := r.reconcileDedicatedSnapshotVolume(ctx, cluster, clusterVolumes) + assert.NilError(t, err) + assert.Assert(t, pvc != nil) + + // Assert restore job with annotation was created + restoreJobs := &batchv1.JobList{} + selectJobs, err := naming.AsSelector(naming.ClusterRestoreJobs(cluster.Name)) + assert.NilError(t, err) + err = errors.WithStack( + r.Client.List(ctx, restoreJobs, + client.InNamespace(cluster.Namespace), + client.MatchingLabelsSelector{Selector: selectJobs}, + )) + assert.NilError(t, err) + assert.Equal(t, len(restoreJobs.Items), 1) + assert.Assert(t, restoreJobs.Items[0].Annotations[naming.PGBackRestBackupJobCompletion] != "") + }) + + t.Run("SnapshotsEnabledSuccessfulRestoreExists", func(t *testing.T) { + // Create cluster with snapshots enabled + ns := setupNamespace(t, cc) + cluster := testCluster() + cluster.Namespace = ns.Name + cluster.ObjectMeta.UID = "the-uid-123" + cluster.Spec.Backups.Snapshots = &v1beta1.VolumeSnapshots{ + VolumeSnapshotClassName: "my-snapshotclass", + } + assert.NilError(t, r.Client.Create(ctx, cluster)) + t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) }) + + // Create times for jobs + currentTime := metav1.Now() + earlierTime := metav1.NewTime(currentTime.AddDate(-1, 0, 0)) + + // Create successful backup job + backupJob := testBackupJob(cluster) + err = errors.WithStack(r.setControllerReference(cluster, backupJob)) + assert.NilError(t, err) + err = r.apply(ctx, backupJob) + assert.NilError(t, err) + + backupJob.Status = batchv1.JobStatus{ + Succeeded: 1, + CompletionTime: &earlierTime, + } + err = r.Client.Status().Update(ctx, backupJob) + assert.NilError(t, err) + + // Create successful restore job + restoreJob := testRestoreJob(cluster) + restoreJob.Annotations = map[string]string{ + naming.PGBackRestBackupJobCompletion: backupJob.Status.CompletionTime.Format(time.RFC3339), + } + err = errors.WithStack(r.setControllerReference(cluster, restoreJob)) + assert.NilError(t, err) + err = r.apply(ctx, restoreJob) + assert.NilError(t, err) + + restoreJob.Status = batchv1.JobStatus{ + Succeeded: 1, + CompletionTime: ¤tTime, + } + err = r.Client.Status().Update(ctx, restoreJob) + assert.NilError(t, err) + + // Create instance set and volumes for reconcile + sts := &appsv1.StatefulSet{} + generateInstanceStatefulSetIntent(ctx, cluster, &cluster.Spec.InstanceSets[0], "pod-service", "service-account", sts, 1) + clusterVolumes := []corev1.PersistentVolumeClaim{} + + // Reconcile + pvc, err := r.reconcileDedicatedSnapshotVolume(ctx, cluster, clusterVolumes) + assert.NilError(t, err) + assert.Assert(t, pvc != nil) + + // Assert restore job was deleted + restoreJobs := &batchv1.JobList{} + selectJobs, err := naming.AsSelector(naming.ClusterRestoreJobs(cluster.Name)) + assert.NilError(t, err) + err = errors.WithStack( + r.Client.List(ctx, restoreJobs, + client.InNamespace(cluster.Namespace), + client.MatchingLabelsSelector{Selector: selectJobs}, + )) + assert.NilError(t, err) + assert.Equal(t, len(restoreJobs.Items), 0) + + // Assert pvc was annotated + assert.Equal(t, pvc.GetAnnotations()[naming.PGBackRestBackupJobCompletion], backupJob.Status.CompletionTime.Format(time.RFC3339)) + }) + + t.Run("SnapshotsEnabledFailedRestoreExists", func(t *testing.T) { + // Create cluster with snapshots enabled + ns := setupNamespace(t, cc) + cluster := testCluster() + cluster.Namespace = ns.Name + cluster.ObjectMeta.UID = "the-uid-123" + cluster.Spec.Backups.Snapshots = &v1beta1.VolumeSnapshots{ + VolumeSnapshotClassName: "my-snapshotclass", + } + assert.NilError(t, r.Client.Create(ctx, cluster)) + t.Cleanup(func() { assert.Check(t, r.Client.Delete(ctx, cluster)) }) + + // Create times for jobs + currentTime := metav1.Now() + earlierTime := metav1.NewTime(currentTime.AddDate(-1, 0, 0)) + + // Create successful backup job + backupJob := testBackupJob(cluster) + err = errors.WithStack(r.setControllerReference(cluster, backupJob)) + assert.NilError(t, err) + err = r.apply(ctx, backupJob) + assert.NilError(t, err) + + backupJob.Status = batchv1.JobStatus{ + Succeeded: 1, + CompletionTime: &earlierTime, + } + err = r.Client.Status().Update(ctx, backupJob) + assert.NilError(t, err) + + // Create failed restore job + restoreJob := testRestoreJob(cluster) + restoreJob.Annotations = map[string]string{ + naming.PGBackRestBackupJobCompletion: backupJob.Status.CompletionTime.Format(time.RFC3339), + } + err = errors.WithStack(r.setControllerReference(cluster, restoreJob)) + assert.NilError(t, err) + err = r.apply(ctx, restoreJob) + assert.NilError(t, err) + + restoreJob.Status = batchv1.JobStatus{ + Succeeded: 0, + Failed: 1, + CompletionTime: ¤tTime, + } + err = r.Client.Status().Update(ctx, restoreJob) + assert.NilError(t, err) + + // Setup instances and volumes for reconcile + sts := &appsv1.StatefulSet{} + generateInstanceStatefulSetIntent(ctx, cluster, &cluster.Spec.InstanceSets[0], "pod-service", "service-account", sts, 1) + clusterVolumes := []corev1.PersistentVolumeClaim{} + + // Reconcile + pvc, err := r.reconcileDedicatedSnapshotVolume(ctx, cluster, clusterVolumes) + assert.NilError(t, err) + assert.Assert(t, pvc != nil) + + // Assert warning event was created and has expected attributes + if assert.Check(t, len(recorder.Events) > 0) { + assert.Equal(t, recorder.Events[0].Type, "Warning") + assert.Equal(t, recorder.Events[0].Regarding.Kind, "PostgresCluster") + assert.Equal(t, recorder.Events[0].Regarding.Name, "hippo") + assert.Equal(t, recorder.Events[0].Reason, "DedicatedSnapshotVolumeRestoreJobError") + assert.Assert(t, cmp.Contains(recorder.Events[0].Note, "restore job failed, check the logs")) + } + }) +} + +func TestCreateDedicatedSnapshotVolume(t *testing.T) { + ctx := context.Background() + _, cc := setupKubernetes(t) + + r := &Reconciler{ + Client: cc, + Owner: client.FieldOwner(t.Name()), + } + + ns := setupNamespace(t, cc) + cluster := testCluster() + cluster.Namespace = ns.Name + cluster.ObjectMeta.UID = "the-uid-123" + + labelMap := map[string]string{ + naming.LabelCluster: cluster.Name, + naming.LabelRole: naming.RoleSnapshot, + naming.LabelData: naming.DataPostgres, + } + pvc := &corev1.PersistentVolumeClaim{ObjectMeta: naming.ClusterDedicatedSnapshotVolume(cluster)} + pvc.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("PersistentVolumeClaim")) + + pvc, err := r.createDedicatedSnapshotVolume(ctx, cluster, labelMap, pvc) + assert.NilError(t, err) + assert.Assert(t, metav1.IsControlledBy(pvc, cluster)) + assert.Equal(t, pvc.Spec.Resources.Requests[corev1.ResourceStorage], resource.MustParse("1Gi")) +} + +func TestDedicatedSnapshotVolumeRestore(t *testing.T) { + ctx := context.Background() + _, cc := setupKubernetes(t) + + r := &Reconciler{ + Client: cc, + Owner: client.FieldOwner(t.Name()), + } + + ns := setupNamespace(t, cc) + cluster := testCluster() + cluster.Namespace = ns.Name + cluster.ObjectMeta.UID = "the-uid-123" + + pvc := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dedicated-snapshot-volume", + }, + } + + sts := &appsv1.StatefulSet{} + generateInstanceStatefulSetIntent(ctx, cluster, &cluster.Spec.InstanceSets[0], "pod-service", "service-account", sts, 1) + currentTime := metav1.Now() + backupJob := testBackupJob(cluster) + backupJob.Status.CompletionTime = ¤tTime + + err := r.dedicatedSnapshotVolumeRestore(ctx, cluster, pvc, backupJob) + assert.NilError(t, err) + + // Assert a restore job was created that has the correct annotation + jobs := &batchv1.JobList{} + selectJobs, err := naming.AsSelector(naming.ClusterRestoreJobs(cluster.Name)) + assert.NilError(t, err) + err = errors.WithStack( + r.Client.List(ctx, jobs, + client.InNamespace(cluster.Namespace), + client.MatchingLabelsSelector{Selector: selectJobs}, + )) + assert.NilError(t, err) + assert.Equal(t, len(jobs.Items), 1) + assert.Equal(t, jobs.Items[0].Annotations[naming.PGBackRestBackupJobCompletion], + backupJob.Status.CompletionTime.Format(time.RFC3339)) +} + +func TestGenerateSnapshotOfDedicatedSnapshotVolume(t *testing.T) { + _, cc := setupKubernetes(t) + require.ParallelCapacity(t, 1) + + r := &Reconciler{ + Client: cc, + Owner: client.FieldOwner(t.Name()), + } + ns := setupNamespace(t, cc) + + cluster := testCluster() + cluster.Namespace = ns.Name + cluster.Spec.Backups.Snapshots = &v1beta1.VolumeSnapshots{ + VolumeSnapshotClassName: "my-snapshot", + } + + pvc := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + naming.PGBackRestBackupJobCompletion: "backup-completion-timestamp", + }, + Name: "dedicated-snapshot-volume", + }, + } + + snapshot, err := r.generateSnapshotOfDedicatedSnapshotVolume(cluster, pvc) + assert.NilError(t, err) + assert.Equal(t, snapshot.GetAnnotations()[naming.PGBackRestBackupJobCompletion], + "backup-completion-timestamp") +} + +func TestGenerateVolumeSnapshot(t *testing.T) { + _, cc := setupKubernetes(t) + require.ParallelCapacity(t, 1) + + r := &Reconciler{ + Client: cc, + Owner: client.FieldOwner(t.Name()), + } + ns := setupNamespace(t, cc) + + cluster := testCluster() + cluster.Namespace = ns.Name + + pvc := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dedicated-snapshot-volume", + }, + } + volumeSnapshotClassName := "my-snapshot" + + snapshot, err := r.generateVolumeSnapshot(cluster, *pvc, volumeSnapshotClassName) + assert.NilError(t, err) + assert.Equal(t, *snapshot.Spec.VolumeSnapshotClassName, "my-snapshot") + assert.Equal(t, *snapshot.Spec.Source.PersistentVolumeClaimName, "dedicated-snapshot-volume") + assert.Equal(t, snapshot.Labels[naming.LabelCluster], "hippo") + assert.Equal(t, snapshot.ObjectMeta.OwnerReferences[0].Name, "hippo") +} + +func TestGetDedicatedSnapshotVolumeRestoreJob(t *testing.T) { + ctx := context.Background() + _, cc := setupKubernetes(t) + require.ParallelCapacity(t, 1) + + r := &Reconciler{ + Client: cc, + Owner: client.FieldOwner(t.Name()), + } + ns := setupNamespace(t, cc) + + cluster := testCluster() + cluster.Namespace = ns.Name + + t.Run("NoRestoreJobs", func(t *testing.T) { + dsvRestoreJob, err := r.getDedicatedSnapshotVolumeRestoreJob(ctx, cluster) + assert.NilError(t, err) + assert.Check(t, dsvRestoreJob == nil) + }) + + t.Run("NoDsvRestoreJobs", func(t *testing.T) { + job1 := testRestoreJob(cluster) + job1.Namespace = ns.Name + + err := r.apply(ctx, job1) + assert.NilError(t, err) + + dsvRestoreJob, err := r.getDedicatedSnapshotVolumeRestoreJob(ctx, cluster) + assert.NilError(t, err) + assert.Check(t, dsvRestoreJob == nil) + }) + + t.Run("DsvRestoreJobExists", func(t *testing.T) { + job2 := testRestoreJob(cluster) + job2.Name = "restore-job-2" + job2.Namespace = ns.Name + job2.Annotations = map[string]string{ + naming.PGBackRestBackupJobCompletion: "backup-timestamp", + } + + err := r.apply(ctx, job2) + assert.NilError(t, err) + + job3 := testRestoreJob(cluster) + job3.Name = "restore-job-3" + job3.Namespace = ns.Name + + err = r.apply(ctx, job3) + assert.NilError(t, err) + + dsvRestoreJob, err := r.getDedicatedSnapshotVolumeRestoreJob(ctx, cluster) + assert.NilError(t, err) + assert.Assert(t, dsvRestoreJob != nil) + assert.Equal(t, dsvRestoreJob.Name, "restore-job-2") + }) +} + +func TestGetLatestCompleteBackupJob(t *testing.T) { + ctx := context.Background() + _, cc := setupKubernetes(t) + // require.ParallelCapacity(t, 1) + + r := &Reconciler{ + Client: cc, + Owner: client.FieldOwner(t.Name()), + } + ns := setupNamespace(t, cc) + + cluster := testCluster() + cluster.Namespace = ns.Name + + t.Run("NoJobs", func(t *testing.T) { + latestCompleteBackupJob, err := r.getLatestCompleteBackupJob(ctx, cluster) + assert.NilError(t, err) + assert.Check(t, latestCompleteBackupJob == nil) + }) + + t.Run("NoCompleteJobs", func(t *testing.T) { + job1 := testBackupJob(cluster) + job1.Namespace = ns.Name + + err := r.apply(ctx, job1) + assert.NilError(t, err) + + latestCompleteBackupJob, err := r.getLatestCompleteBackupJob(ctx, cluster) + assert.NilError(t, err) + assert.Check(t, latestCompleteBackupJob == nil) + }) + + t.Run("OneCompleteBackupJob", func(t *testing.T) { + currentTime := metav1.Now() + + job1 := testBackupJob(cluster) + job1.Namespace = ns.Name + + err := r.apply(ctx, job1) + assert.NilError(t, err) + + job2 := testBackupJob(cluster) + job2.Namespace = ns.Name + job2.Name = "backup-job-2" + + err = r.apply(ctx, job2) + assert.NilError(t, err) + + // Get job1 and update Status. + err = r.Client.Get(ctx, client.ObjectKeyFromObject(job1), job1) + assert.NilError(t, err) + + job1.Status = batchv1.JobStatus{ + Succeeded: 1, + CompletionTime: ¤tTime, + } + err = r.Client.Status().Update(ctx, job1) + assert.NilError(t, err) + + latestCompleteBackupJob, err := r.getLatestCompleteBackupJob(ctx, cluster) + assert.NilError(t, err) + assert.Check(t, latestCompleteBackupJob.Name == "backup-job-1") + }) + + t.Run("TwoCompleteBackupJobs", func(t *testing.T) { + currentTime := metav1.Now() + earlierTime := metav1.NewTime(currentTime.AddDate(-1, 0, 0)) + assert.Check(t, earlierTime.Before(¤tTime)) + + job1 := testBackupJob(cluster) + job1.Namespace = ns.Name + + err := r.apply(ctx, job1) + assert.NilError(t, err) + + job2 := testBackupJob(cluster) + job2.Namespace = ns.Name + job2.Name = "backup-job-2" + + err = r.apply(ctx, job2) + assert.NilError(t, err) + + // Get job1 and update Status. + err = r.Client.Get(ctx, client.ObjectKeyFromObject(job1), job1) + assert.NilError(t, err) + + job1.Status = batchv1.JobStatus{ + Succeeded: 1, + CompletionTime: ¤tTime, + } + err = r.Client.Status().Update(ctx, job1) + assert.NilError(t, err) + + // Get job2 and update Status. + err = r.Client.Get(ctx, client.ObjectKeyFromObject(job2), job2) + assert.NilError(t, err) + + job2.Status = batchv1.JobStatus{ + Succeeded: 1, + CompletionTime: &earlierTime, + } + err = r.Client.Status().Update(ctx, job2) + assert.NilError(t, err) + + latestCompleteBackupJob, err := r.getLatestCompleteBackupJob(ctx, cluster) + assert.NilError(t, err) + assert.Check(t, latestCompleteBackupJob.Name == "backup-job-1") + }) +} + +func TestGetSnapshotWithLatestError(t *testing.T) { + t.Run("NoSnapshots", func(t *testing.T) { + snapshotList := &volumesnapshotv1.VolumeSnapshotList{} + snapshotWithLatestError := getSnapshotWithLatestError(snapshotList) + assert.Check(t, snapshotWithLatestError == nil) + }) + + t.Run("NoSnapshotsWithErrors", func(t *testing.T) { + snapshotList := &volumesnapshotv1.VolumeSnapshotList{ + Items: []volumesnapshotv1.VolumeSnapshot{ + { + Status: &volumesnapshotv1.VolumeSnapshotStatus{ + ReadyToUse: initialize.Bool(true), + }, + }, + { + Status: &volumesnapshotv1.VolumeSnapshotStatus{ + ReadyToUse: initialize.Bool(false), + }, + }, + }, + } + snapshotWithLatestError := getSnapshotWithLatestError(snapshotList) + assert.Check(t, snapshotWithLatestError == nil) + }) + + t.Run("OneSnapshotWithError", func(t *testing.T) { + currentTime := metav1.Now() + earlierTime := metav1.NewTime(currentTime.AddDate(-1, 0, 0)) + snapshotList := &volumesnapshotv1.VolumeSnapshotList{ + Items: []volumesnapshotv1.VolumeSnapshot{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "good-snapshot", + UID: "the-uid-123", + }, + Status: &volumesnapshotv1.VolumeSnapshotStatus{ + CreationTime: ¤tTime, + ReadyToUse: initialize.Bool(true), + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "bad-snapshot", + UID: "the-uid-456", + }, + Status: &volumesnapshotv1.VolumeSnapshotStatus{ + ReadyToUse: initialize.Bool(false), + Error: &volumesnapshotv1.VolumeSnapshotError{ + Time: &earlierTime, + }, + }, + }, + }, + } + snapshotWithLatestError := getSnapshotWithLatestError(snapshotList) + assert.Equal(t, snapshotWithLatestError.ObjectMeta.Name, "bad-snapshot") + }) + + t.Run("TwoSnapshotsWithErrors", func(t *testing.T) { + currentTime := metav1.Now() + earlierTime := metav1.NewTime(currentTime.AddDate(-1, 0, 0)) + snapshotList := &volumesnapshotv1.VolumeSnapshotList{ + Items: []volumesnapshotv1.VolumeSnapshot{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "first-bad-snapshot", + UID: "the-uid-123", + }, + Status: &volumesnapshotv1.VolumeSnapshotStatus{ + ReadyToUse: initialize.Bool(false), + Error: &volumesnapshotv1.VolumeSnapshotError{ + Time: &earlierTime, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "second-bad-snapshot", + UID: "the-uid-456", + }, + Status: &volumesnapshotv1.VolumeSnapshotStatus{ + ReadyToUse: initialize.Bool(false), + Error: &volumesnapshotv1.VolumeSnapshotError{ + Time: ¤tTime, + }, + }, + }, + }, + } + snapshotWithLatestError := getSnapshotWithLatestError(snapshotList) + assert.Equal(t, snapshotWithLatestError.ObjectMeta.Name, "second-bad-snapshot") + }) +} + +func TestGetSnapshotsForCluster(t *testing.T) { + ctx := context.Background() + _, cc := setupKubernetes(t) + require.ParallelCapacity(t, 1) + + r := &Reconciler{ + Client: cc, + Owner: client.FieldOwner(t.Name()), + } + ns := setupNamespace(t, cc) + + cluster := testCluster() + cluster.Namespace = ns.Name + + t.Run("NoSnapshots", func(t *testing.T) { + snapshots, err := r.getSnapshotsForCluster(ctx, cluster) + assert.NilError(t, err) + assert.Equal(t, len(snapshots.Items), 0) + }) + + t.Run("NoSnapshotsForCluster", func(t *testing.T) { + snapshot := &volumesnapshotv1.VolumeSnapshot{ + TypeMeta: metav1.TypeMeta{ + APIVersion: volumesnapshotv1.SchemeGroupVersion.String(), + Kind: "VolumeSnapshot", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-snapshot", + Namespace: ns.Name, + Labels: map[string]string{ + naming.LabelCluster: "rhino", + }, + }, + } + snapshot.Spec.Source.PersistentVolumeClaimName = initialize.String("some-pvc-name") + snapshot.Spec.VolumeSnapshotClassName = initialize.String("some-class-name") + err := r.apply(ctx, snapshot) + assert.NilError(t, err) + + snapshots, err := r.getSnapshotsForCluster(ctx, cluster) + assert.NilError(t, err) + assert.Equal(t, len(snapshots.Items), 0) + }) + + t.Run("OneSnapshotForCluster", func(t *testing.T) { + snapshot1 := &volumesnapshotv1.VolumeSnapshot{ + TypeMeta: metav1.TypeMeta{ + APIVersion: volumesnapshotv1.SchemeGroupVersion.String(), + Kind: "VolumeSnapshot", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-snapshot", + Namespace: ns.Name, + Labels: map[string]string{ + naming.LabelCluster: "rhino", + }, + }, + } + snapshot1.Spec.Source.PersistentVolumeClaimName = initialize.String("some-pvc-name") + snapshot1.Spec.VolumeSnapshotClassName = initialize.String("some-class-name") + err := r.apply(ctx, snapshot1) + assert.NilError(t, err) + + snapshot2 := &volumesnapshotv1.VolumeSnapshot{ + TypeMeta: metav1.TypeMeta{ + APIVersion: volumesnapshotv1.SchemeGroupVersion.String(), + Kind: "VolumeSnapshot", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "another-snapshot", + Namespace: ns.Name, + Labels: map[string]string{ + naming.LabelCluster: "hippo", + }, + }, + } + snapshot2.Spec.Source.PersistentVolumeClaimName = initialize.String("another-pvc-name") + snapshot2.Spec.VolumeSnapshotClassName = initialize.String("another-class-name") + err = r.apply(ctx, snapshot2) + assert.NilError(t, err) + + snapshots, err := r.getSnapshotsForCluster(ctx, cluster) + assert.NilError(t, err) + assert.Equal(t, len(snapshots.Items), 1) + assert.Equal(t, snapshots.Items[0].Name, "another-snapshot") + }) + + t.Run("TwoSnapshotsForCluster", func(t *testing.T) { + snapshot1 := &volumesnapshotv1.VolumeSnapshot{ + TypeMeta: metav1.TypeMeta{ + APIVersion: volumesnapshotv1.SchemeGroupVersion.String(), + Kind: "VolumeSnapshot", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-snapshot", + Namespace: ns.Name, + Labels: map[string]string{ + naming.LabelCluster: "hippo", + }, + }, + } + snapshot1.Spec.Source.PersistentVolumeClaimName = initialize.String("some-pvc-name") + snapshot1.Spec.VolumeSnapshotClassName = initialize.String("some-class-name") + err := r.apply(ctx, snapshot1) + assert.NilError(t, err) + + snapshot2 := &volumesnapshotv1.VolumeSnapshot{ + TypeMeta: metav1.TypeMeta{ + APIVersion: volumesnapshotv1.SchemeGroupVersion.String(), + Kind: "VolumeSnapshot", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "another-snapshot", + Namespace: ns.Name, + Labels: map[string]string{ + naming.LabelCluster: "hippo", + }, + }, + } + snapshot2.Spec.Source.PersistentVolumeClaimName = initialize.String("another-pvc-name") + snapshot2.Spec.VolumeSnapshotClassName = initialize.String("another-class-name") + err = r.apply(ctx, snapshot2) + assert.NilError(t, err) + + snapshots, err := r.getSnapshotsForCluster(ctx, cluster) + assert.NilError(t, err) + assert.Equal(t, len(snapshots.Items), 2) + }) +} + +func TestGetLatestReadySnapshot(t *testing.T) { + t.Run("NoSnapshots", func(t *testing.T) { + snapshotList := &volumesnapshotv1.VolumeSnapshotList{} + latestReadySnapshot := getLatestReadySnapshot(snapshotList) + assert.Assert(t, latestReadySnapshot == nil) + }) + + t.Run("NoReadySnapshots", func(t *testing.T) { + snapshotList := &volumesnapshotv1.VolumeSnapshotList{ + Items: []volumesnapshotv1.VolumeSnapshot{ + { + Status: &volumesnapshotv1.VolumeSnapshotStatus{ + ReadyToUse: initialize.Bool(false), + }, + }, + { + Status: &volumesnapshotv1.VolumeSnapshotStatus{ + ReadyToUse: initialize.Bool(false), + }, + }, + }, + } + latestReadySnapshot := getLatestReadySnapshot(snapshotList) + assert.Assert(t, latestReadySnapshot == nil) + }) + + t.Run("OneReadySnapshot", func(t *testing.T) { + currentTime := metav1.Now() + earlierTime := metav1.NewTime(currentTime.AddDate(-1, 0, 0)) + snapshotList := &volumesnapshotv1.VolumeSnapshotList{ + Items: []volumesnapshotv1.VolumeSnapshot{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "good-snapshot", + UID: "the-uid-123", + }, + Status: &volumesnapshotv1.VolumeSnapshotStatus{ + CreationTime: &earlierTime, + ReadyToUse: initialize.Bool(true), + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "bad-snapshot", + UID: "the-uid-456", + }, + Status: &volumesnapshotv1.VolumeSnapshotStatus{ + CreationTime: ¤tTime, + ReadyToUse: initialize.Bool(false), + }, + }, + }, + } + latestReadySnapshot := getLatestReadySnapshot(snapshotList) + assert.Equal(t, latestReadySnapshot.ObjectMeta.Name, "good-snapshot") + }) + + t.Run("TwoReadySnapshots", func(t *testing.T) { + currentTime := metav1.Now() + earlierTime := metav1.NewTime(currentTime.AddDate(-1, 0, 0)) + snapshotList := &volumesnapshotv1.VolumeSnapshotList{ + Items: []volumesnapshotv1.VolumeSnapshot{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "first-good-snapshot", + UID: "the-uid-123", + }, + Status: &volumesnapshotv1.VolumeSnapshotStatus{ + CreationTime: &earlierTime, + ReadyToUse: initialize.Bool(true), + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "second-good-snapshot", + UID: "the-uid-456", + }, + Status: &volumesnapshotv1.VolumeSnapshotStatus{ + CreationTime: ¤tTime, + ReadyToUse: initialize.Bool(true), + }, + }, + }, + } + latestReadySnapshot := getLatestReadySnapshot(snapshotList) + assert.Equal(t, latestReadySnapshot.ObjectMeta.Name, "second-good-snapshot") + }) +} + +func TestDeleteSnapshots(t *testing.T) { + ctx := context.Background() + cfg, cc := setupKubernetes(t) + discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg) + assert.NilError(t, err) + + r := &Reconciler{ + Client: cc, + Owner: client.FieldOwner(t.Name()), + DiscoveryClient: discoveryClient, + } + ns := setupNamespace(t, cc) + + cluster := testCluster() + cluster.Namespace = ns.Name + cluster.ObjectMeta.UID = "the-uid-123" + assert.NilError(t, r.Client.Create(ctx, cluster)) + + rhinoCluster := testCluster() + rhinoCluster.Name = "rhino" + rhinoCluster.Namespace = ns.Name + rhinoCluster.ObjectMeta.UID = "the-uid-456" + assert.NilError(t, r.Client.Create(ctx, rhinoCluster)) + + t.Cleanup(func() { + assert.Check(t, r.Client.Delete(ctx, cluster)) + assert.Check(t, r.Client.Delete(ctx, rhinoCluster)) + }) + + t.Run("NoSnapshots", func(t *testing.T) { + snapshotList := &volumesnapshotv1.VolumeSnapshotList{} + err := r.deleteSnapshots(ctx, cluster, snapshotList) + assert.NilError(t, err) + }) + + t.Run("NoSnapshotsControlledByHippo", func(t *testing.T) { + pvcName := initialize.String("dedicated-snapshot-volume") + snapshot1 := &volumesnapshotv1.VolumeSnapshot{ + TypeMeta: metav1.TypeMeta{ + APIVersion: volumesnapshotv1.SchemeGroupVersion.String(), + Kind: "VolumeSnapshot", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "first-snapshot", + Namespace: ns.Name, + }, + Spec: volumesnapshotv1.VolumeSnapshotSpec{ + Source: volumesnapshotv1.VolumeSnapshotSource{ + PersistentVolumeClaimName: pvcName, + }, + }, + } + err := errors.WithStack(r.setControllerReference(rhinoCluster, snapshot1)) + assert.NilError(t, err) + err = r.apply(ctx, snapshot1) + assert.NilError(t, err) + + snapshotList := &volumesnapshotv1.VolumeSnapshotList{ + Items: []volumesnapshotv1.VolumeSnapshot{ + *snapshot1, + }, + } + err = r.deleteSnapshots(ctx, cluster, snapshotList) + assert.NilError(t, err) + existingSnapshots := &volumesnapshotv1.VolumeSnapshotList{} + err = errors.WithStack( + r.Client.List(ctx, existingSnapshots, + client.InNamespace(ns.Namespace), + )) + assert.NilError(t, err) + assert.Equal(t, len(existingSnapshots.Items), 1) + }) + + t.Run("OneSnapshotControlledByHippo", func(t *testing.T) { + pvcName := initialize.String("dedicated-snapshot-volume") + snapshot1 := &volumesnapshotv1.VolumeSnapshot{ + TypeMeta: metav1.TypeMeta{ + APIVersion: volumesnapshotv1.SchemeGroupVersion.String(), + Kind: "VolumeSnapshot", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "first-snapshot", + Namespace: ns.Name, + }, + Spec: volumesnapshotv1.VolumeSnapshotSpec{ + Source: volumesnapshotv1.VolumeSnapshotSource{ + PersistentVolumeClaimName: pvcName, + }, + }, + } + err := errors.WithStack(r.setControllerReference(rhinoCluster, snapshot1)) + assert.NilError(t, err) + err = r.apply(ctx, snapshot1) + assert.NilError(t, err) + + snapshot2 := &volumesnapshotv1.VolumeSnapshot{ + TypeMeta: metav1.TypeMeta{ + APIVersion: volumesnapshotv1.SchemeGroupVersion.String(), + Kind: "VolumeSnapshot", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "second-snapshot", + Namespace: ns.Name, + }, + Spec: volumesnapshotv1.VolumeSnapshotSpec{ + Source: volumesnapshotv1.VolumeSnapshotSource{ + PersistentVolumeClaimName: pvcName, + }, + }, + } + err = errors.WithStack(r.setControllerReference(cluster, snapshot2)) + assert.NilError(t, err) + err = r.apply(ctx, snapshot2) + assert.NilError(t, err) + + snapshotList := &volumesnapshotv1.VolumeSnapshotList{ + Items: []volumesnapshotv1.VolumeSnapshot{ + *snapshot1, *snapshot2, + }, + } + err = r.deleteSnapshots(ctx, cluster, snapshotList) + assert.NilError(t, err) + existingSnapshots := &volumesnapshotv1.VolumeSnapshotList{} + err = errors.WithStack( + r.Client.List(ctx, existingSnapshots, + client.InNamespace(ns.Namespace), + )) + assert.NilError(t, err) + assert.Equal(t, len(existingSnapshots.Items), 1) + assert.Equal(t, existingSnapshots.Items[0].Name, "first-snapshot") + }) +} + +func TestClusterUsingTablespaces(t *testing.T) { + ctx := context.Background() + cluster := testCluster() + + t.Run("NoVolumesFeatureEnabled", func(t *testing.T) { + // Enable Tablespaces feature gate + gate := feature.NewGate() + assert.NilError(t, gate.SetFromMap(map[string]bool{ + feature.TablespaceVolumes: true, + })) + ctx := feature.NewContext(ctx, gate) + + assert.Assert(t, !clusterUsingTablespaces(ctx, cluster)) + }) + + t.Run("VolumesInPlaceFeatureDisabled", func(t *testing.T) { + cluster.Spec.InstanceSets[0].TablespaceVolumes = []v1beta1.TablespaceVolume{{ + Name: "volume-1", + }} + + assert.Assert(t, !clusterUsingTablespaces(ctx, cluster)) + }) + + t.Run("VolumesInPlaceAndFeatureEnabled", func(t *testing.T) { + // Enable Tablespaces feature gate + gate := feature.NewGate() + assert.NilError(t, gate.SetFromMap(map[string]bool{ + feature.TablespaceVolumes: true, + })) + ctx := feature.NewContext(ctx, gate) + + assert.Assert(t, clusterUsingTablespaces(ctx, cluster)) + }) +} diff --git a/internal/controller/postgrescluster/suite_test.go b/internal/controller/postgrescluster/suite_test.go index b35249a3e4..1dbd36bd1e 100644 --- a/internal/controller/postgrescluster/suite_test.go +++ b/internal/controller/postgrescluster/suite_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -65,7 +54,10 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") suite.Environment = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "..", "config", "crd", "bases"), + filepath.Join("..", "..", "..", "hack", "tools", "external-snapshotter", "client", "config", "crd"), + }, } _, err := suite.Environment.Start() diff --git a/internal/controller/postgrescluster/topology.go b/internal/controller/postgrescluster/topology.go index a1a73d8581..58778be907 100644 --- a/internal/controller/postgrescluster/topology.go +++ b/internal/controller/postgrescluster/topology.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster diff --git a/internal/controller/postgrescluster/topology_test.go b/internal/controller/postgrescluster/topology_test.go index 1fa7640fc2..5da8c0e7ef 100644 --- a/internal/controller/postgrescluster/topology_test.go +++ b/internal/controller/postgrescluster/topology_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -20,6 +9,8 @@ import ( "gotest.tools/v3/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/percona/percona-postgresql-operator/internal/testing/cmp" ) func TestDefaultTopologySpreadConstraints(t *testing.T) { @@ -31,7 +22,7 @@ func TestDefaultTopologySpreadConstraints(t *testing.T) { }) // Entire selector, hostname, zone, and ScheduleAnyway. - assert.Assert(t, marshalMatches(constraints, ` + assert.Assert(t, cmp.MarshalMatches(constraints, ` - labelSelector: matchExpressions: - key: k1 diff --git a/internal/controller/postgrescluster/util.go b/internal/controller/postgrescluster/util.go index 33799a35a0..3631a74857 100644 --- a/internal/controller/postgrescluster/util.go +++ b/internal/controller/postgrescluster/util.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -31,6 +20,8 @@ import ( "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) +var tmpDirSizeLimit = resource.MustParse("16Mi") + const ( // devSHMDir is the directory used for allocating shared memory segments, // which are needed by Postgres diff --git a/internal/controller/postgrescluster/util_test.go b/internal/controller/postgrescluster/util_test.go index 65d8321f2e..3e673c02d6 100644 --- a/internal/controller/postgrescluster/util_test.go +++ b/internal/controller/postgrescluster/util_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster diff --git a/internal/controller/postgrescluster/volumes.go b/internal/controller/postgrescluster/volumes.go index ced685e730..ae7ac39dd9 100644 --- a/internal/controller/postgrescluster/volumes.go +++ b/internal/controller/postgrescluster/volumes.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -445,7 +434,8 @@ func (r *Reconciler) reconcileMovePGDataDir(ctx context.Context, // at this point, the Job either wasn't found or it has failed, so the it // should be created - moveDirJob.ObjectMeta.Annotations = naming.Merge(cluster.Spec.DataSource.Volumes.PGDataVolume.Annotations, cluster.Spec.Metadata.GetAnnotationsOrNil()) + moveDirJob.ObjectMeta.Annotations = naming.Merge(cluster.Spec.Metadata. + GetAnnotationsOrNil()) labels := naming.Merge(cluster.Spec.DataSource.Volumes.PGDataVolume.Labels, cluster.Spec.Metadata.GetLabelsOrNil(), naming.DirectoryMoveJobLabels(cluster.Name), map[string]string{ @@ -488,7 +478,7 @@ func (r *Reconciler) reconcileMovePGDataDir(ctx context.Context, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{Labels: labels}, Spec: corev1.PodSpec{ - Tolerations: cluster.Spec.DataSource.Volumes.PGDataVolume.Tolerations, + Tolerations: cluster.Spec.DataSource.Volumes.PGDataVolume.Tolerations, // K8SPG-479 // Set the image pull secrets, if any exist. // This is set here rather than using the service account due to the lack // of propagation to existing pods when the CRD is updated: @@ -517,10 +507,9 @@ func (r *Reconciler) reconcileMovePGDataDir(ctx context.Context, }, } // set the priority class name, if it exists - if len(cluster.Spec.InstanceSets) > 0 && - cluster.Spec.InstanceSets[0].PriorityClassName != nil { + if len(cluster.Spec.InstanceSets) > 0 { jobSpec.Template.Spec.PriorityClassName = - *cluster.Spec.InstanceSets[0].PriorityClassName + initialize.FromPointer(cluster.Spec.InstanceSets[0].PriorityClassName) } moveDirJob.Spec = *jobSpec @@ -567,7 +556,7 @@ func (r *Reconciler) reconcileMoveWALDir(ctx context.Context, } } - moveDirJob.ObjectMeta.Annotations = naming.Merge(cluster.Spec.DataSource.Volumes.PGWALVolume.Annotations, cluster.Spec.Metadata. + moveDirJob.ObjectMeta.Annotations = naming.Merge(cluster.Spec.Metadata. GetAnnotationsOrNil()) labels := naming.Merge(cluster.Spec.DataSource.Volumes.PGWALVolume.Labels, cluster.Spec.Metadata.GetLabelsOrNil(), naming.DirectoryMoveJobLabels(cluster.Name), @@ -607,7 +596,7 @@ func (r *Reconciler) reconcileMoveWALDir(ctx context.Context, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{Labels: labels}, Spec: corev1.PodSpec{ - Tolerations: cluster.Spec.DataSource.Volumes.PGWALVolume.Tolerations, + Tolerations: cluster.Spec.DataSource.Volumes.PGWALVolume.Tolerations, // K8SPG-479 // Set the image pull secrets, if any exist. // This is set here rather than using the service account due to the lack // of propagation to existing pods when the CRD is updated: @@ -636,10 +625,9 @@ func (r *Reconciler) reconcileMoveWALDir(ctx context.Context, }, } // set the priority class name, if it exists - if len(cluster.Spec.InstanceSets) > 0 && - cluster.Spec.InstanceSets[0].PriorityClassName != nil { + if len(cluster.Spec.InstanceSets) > 0 { jobSpec.Template.Spec.PriorityClassName = - *cluster.Spec.InstanceSets[0].PriorityClassName + initialize.FromPointer(cluster.Spec.InstanceSets[0].PriorityClassName) } moveDirJob.Spec = *jobSpec @@ -731,7 +719,7 @@ func (r *Reconciler) reconcileMoveRepoDir(ctx context.Context, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{Labels: labels}, Spec: corev1.PodSpec{ - Tolerations: cluster.Spec.DataSource.Volumes.PGBackRestVolume.Tolerations, + Tolerations: cluster.Spec.DataSource.Volumes.PGBackRestVolume.Tolerations, // K8SPG-479 // Set the image pull secrets, if any exist. // This is set here rather than using the service account due to the lack // of propagation to existing pods when the CRD is updated: @@ -760,9 +748,7 @@ func (r *Reconciler) reconcileMoveRepoDir(ctx context.Context, } // set the priority class name, if it exists if repoHost := cluster.Spec.Backups.PGBackRest.RepoHost; repoHost != nil { - if repoHost.PriorityClassName != nil { - jobSpec.Template.Spec.PriorityClassName = *repoHost.PriorityClassName - } + jobSpec.Template.Spec.PriorityClassName = initialize.FromPointer(repoHost.PriorityClassName) } moveDirJob.Spec = *jobSpec diff --git a/internal/controller/postgrescluster/volumes_test.go b/internal/controller/postgrescluster/volumes_test.go index f5437c9485..4de564c810 100644 --- a/internal/controller/postgrescluster/volumes_test.go +++ b/internal/controller/postgrescluster/volumes_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster @@ -809,7 +798,7 @@ volumes: claimName: testpgdata ` - assert.Assert(t, marshalMatches(moveJobs.Items[i].Spec.Template.Spec, compare+"\n")) + assert.Assert(t, cmp.MarshalMatches(moveJobs.Items[i].Spec.Template.Spec, compare+"\n")) } } @@ -869,7 +858,7 @@ volumes: claimName: testwal ` - assert.Assert(t, marshalMatches(moveJobs.Items[i].Spec.Template.Spec, compare+"\n")) + assert.Assert(t, cmp.MarshalMatches(moveJobs.Items[i].Spec.Template.Spec, compare+"\n")) } } @@ -930,7 +919,7 @@ volumes: persistentVolumeClaim: claimName: testrepo ` - assert.Assert(t, marshalMatches(moveJobs.Items[i].Spec.Template.Spec, compare+"\n")) + assert.Assert(t, cmp.MarshalMatches(moveJobs.Items[i].Spec.Template.Spec, compare+"\n")) } } diff --git a/internal/controller/postgrescluster/watches.go b/internal/controller/postgrescluster/watches.go index 01c91bbb3a..047903c2ff 100644 --- a/internal/controller/postgrescluster/watches.go +++ b/internal/controller/postgrescluster/watches.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster diff --git a/internal/controller/postgrescluster/watches_test.go b/internal/controller/postgrescluster/watches_test.go index 81d587f8ca..ad40c9edae 100644 --- a/internal/controller/postgrescluster/watches_test.go +++ b/internal/controller/postgrescluster/watches_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgrescluster diff --git a/internal/controller/runtime/client.go b/internal/controller/runtime/client.go index ae57c08472..4cc05c9835 100644 --- a/internal/controller/runtime/client.go +++ b/internal/controller/runtime/client.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package runtime diff --git a/internal/controller/runtime/pod_client.go b/internal/controller/runtime/pod_client.go index 15485b0cbf..e842601aa7 100644 --- a/internal/controller/runtime/pod_client.go +++ b/internal/controller/runtime/pod_client.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package runtime diff --git a/internal/controller/runtime/reconcile.go b/internal/controller/runtime/reconcile.go index bb278f0f46..a2196d1626 100644 --- a/internal/controller/runtime/reconcile.go +++ b/internal/controller/runtime/reconcile.go @@ -1,17 +1,6 @@ -/* -Copyright 2021 - 2024 Crunchy Data Solutions, Inc. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package runtime diff --git a/internal/controller/runtime/reconcile_test.go b/internal/controller/runtime/reconcile_test.go index 4dd10e1700..925b3cf47d 100644 --- a/internal/controller/runtime/reconcile_test.go +++ b/internal/controller/runtime/reconcile_test.go @@ -1,17 +1,6 @@ -/* -Copyright 2021 - 2024 Crunchy Data Solutions, Inc. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package runtime diff --git a/internal/controller/runtime/runtime.go b/internal/controller/runtime/runtime.go index 6f8d370204..9764a6c7fc 100644 --- a/internal/controller/runtime/runtime.go +++ b/internal/controller/runtime/runtime.go @@ -1,29 +1,18 @@ -/* -Copyright 2021 - 2024 Crunchy Data Solutions, Inc. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package runtime import ( "context" + volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client/config" - "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/manager/signals" @@ -48,6 +37,9 @@ func init() { if err := v1beta1.AddToScheme(Scheme); err != nil { panic(err) } + if err := volumesnapshotv1.AddToScheme(Scheme); err != nil { + panic(err) + } } // GetConfig returns a Kubernetes client configuration from KUBECONFIG or the diff --git a/internal/controller/runtime/ticker.go b/internal/controller/runtime/ticker.go index a670e7812e..5964fd7017 100644 --- a/internal/controller/runtime/ticker.go +++ b/internal/controller/runtime/ticker.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package runtime @@ -35,15 +24,13 @@ type ticker struct { // NewTicker returns a Source that emits e every d. func NewTicker(d time.Duration, e event.GenericEvent, - h handler.EventHandler, -) source.Source { + h handler.EventHandler) source.Source { return &ticker{Duration: d, GenericEvent: e, Handler: h} } // NewTickerImmediate returns a Source that emits e at start and every d. func NewTickerImmediate(d time.Duration, e event.GenericEvent, - h handler.EventHandler, -) source.Source { + h handler.EventHandler) source.Source { return &ticker{Duration: d, GenericEvent: e, Handler: h, Immediate: true} } diff --git a/internal/controller/runtime/ticker_test.go b/internal/controller/runtime/ticker_test.go index 906fa9ab24..d5d30ef7f1 100644 --- a/internal/controller/runtime/ticker_test.go +++ b/internal/controller/runtime/ticker_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package runtime diff --git a/internal/controller/standalone_pgadmin/apply.go b/internal/controller/standalone_pgadmin/apply.go index cad148c768..0eaa613df8 100644 --- a/internal/controller/standalone_pgadmin/apply.go +++ b/internal/controller/standalone_pgadmin/apply.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/controller/standalone_pgadmin/config.go b/internal/controller/standalone_pgadmin/config.go index a842a296ab..ddd080985b 100644 --- a/internal/controller/standalone_pgadmin/config.go +++ b/internal/controller/standalone_pgadmin/config.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/controller/standalone_pgadmin/configmap.go b/internal/controller/standalone_pgadmin/configmap.go index a5fe79258c..2e1390a666 100644 --- a/internal/controller/standalone_pgadmin/configmap.go +++ b/internal/controller/standalone_pgadmin/configmap.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin @@ -63,7 +53,7 @@ func configmap(pgadmin *v1beta1.PGAdmin, naming.StandalonePGAdminLabels(pgadmin.Name)) // TODO(tjmoore4): Populate configuration details. - initialize.StringMap(&configmap.Data) + initialize.Map(&configmap.Data) configSettings, err := generateConfig(pgadmin) if err == nil { configmap.Data[settingsConfigMapKey] = configSettings diff --git a/internal/controller/standalone_pgadmin/configmap_test.go b/internal/controller/standalone_pgadmin/configmap_test.go index 17f2fb4e38..f895d56362 100644 --- a/internal/controller/standalone_pgadmin/configmap_test.go +++ b/internal/controller/standalone_pgadmin/configmap_test.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/controller/standalone_pgadmin/controller.go b/internal/controller/standalone_pgadmin/controller.go index f6d72dbfd1..b4403ab7a3 100644 --- a/internal/controller/standalone_pgadmin/controller.go +++ b/internal/controller/standalone_pgadmin/controller.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/controller/standalone_pgadmin/controller_test.go b/internal/controller/standalone_pgadmin/controller_test.go index 85fbb15f56..1c4924dc7a 100644 --- a/internal/controller/standalone_pgadmin/controller_test.go +++ b/internal/controller/standalone_pgadmin/controller_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/controller/standalone_pgadmin/helpers_test.go b/internal/controller/standalone_pgadmin/helpers_test.go index ab37ffc495..cea3facd87 100644 --- a/internal/controller/standalone_pgadmin/helpers_test.go +++ b/internal/controller/standalone_pgadmin/helpers_test.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/controller/standalone_pgadmin/helpers_unit_test.go b/internal/controller/standalone_pgadmin/helpers_unit_test.go index f25e7a02b8..a57674e00a 100644 --- a/internal/controller/standalone_pgadmin/helpers_unit_test.go +++ b/internal/controller/standalone_pgadmin/helpers_unit_test.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/controller/standalone_pgadmin/pod.go b/internal/controller/standalone_pgadmin/pod.go index 8278264b5d..7c04c72b8d 100644 --- a/internal/controller/standalone_pgadmin/pod.go +++ b/internal/controller/standalone_pgadmin/pod.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin @@ -20,6 +10,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/intstr" "github.com/percona/percona-postgresql-operator/internal/config" "github.com/percona/percona-postgresql-operator/internal/initialize" @@ -124,9 +115,18 @@ func pod( Name: "PGADMIN_SETUP_EMAIL", Value: fmt.Sprintf("admin@%s.%s.svc", inPGAdmin.Name, inPGAdmin.Namespace), }, + // Setting the KRB5_CONFIG for kerberos + // - https://web.mit.edu/kerberos/krb5-current/doc/admin/conf_files/krb5_conf.html + { + Name: "KRB5_CONFIG", + Value: configMountPath + "/krb5.conf", + }, + // In testing it was determined that we need to set this env var for the replay cache + // otherwise it defaults to the read-only location `/var/tmp/` + // - https://web.mit.edu/kerberos/krb5-current/doc/basic/rcache_def.html#replay-cache-types { - Name: "PGADMIN_LISTEN_PORT", - Value: fmt.Sprintf("%d", pgAdminPort), + Name: "KRB5RCACHEDIR", + Value: "/tmp", }, }, VolumeMounts: []corev1.VolumeMount{ @@ -160,7 +160,7 @@ func pod( readinessProbe := &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ - Port: *initialize.IntOrStringInt32(pgAdminPort), + Port: intstr.FromInt32(pgAdminPort), Path: "/login", Scheme: corev1.URISchemeHTTP, }, @@ -431,12 +431,11 @@ with open('` + configMountPath + `/` + gunicornConfigFilePath + `') as _f: script := strings.Join([]string{ // Use the initContainer to create this path to avoid the error noted here: - // - https://github.com/kubernetes/kubernetes/issues/121294 - `mkdir -p /etc/pgadmin/conf.d`, - // Write the system configuration into a read-only file. - `(umask a-w && echo "$1" > ` + scriptMountPath + `/config_system.py` + `)`, - // Write the server configuration into a read-only file. - `(umask a-w && echo "$2" > ` + scriptMountPath + `/gunicorn_config.py` + `)`, + // - https://issue.k8s.io/121294 + `mkdir -p ` + configMountPath, + // Write the system and server configurations. + `echo "$1" > ` + scriptMountPath + `/config_system.py`, + `echo "$2" > ` + scriptMountPath + `/gunicorn_config.py`, }, "\n") return append([]string{"bash", "-ceu", "--", script, "startup"}, args...) diff --git a/internal/controller/standalone_pgadmin/pod_test.go b/internal/controller/standalone_pgadmin/pod_test.go index 4009e1fcc1..f19a08dd56 100644 --- a/internal/controller/standalone_pgadmin/pod_test.go +++ b/internal/controller/standalone_pgadmin/pod_test.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin @@ -104,8 +94,10 @@ containers: env: - name: PGADMIN_SETUP_EMAIL value: admin@pgadmin.postgres-operator.svc - - name: PGADMIN_LISTEN_PORT - value: "5050" + - name: KRB5_CONFIG + value: /etc/pgadmin/conf.d/krb5.conf + - name: KRB5RCACHEDIR + value: /tmp name: pgadmin ports: - containerPort: 5050 @@ -147,8 +139,8 @@ initContainers: - -- - |- mkdir -p /etc/pgadmin/conf.d - (umask a-w && echo "$1" > /etc/pgadmin/config_system.py) - (umask a-w && echo "$2" > /etc/pgadmin/gunicorn_config.py) + echo "$1" > /etc/pgadmin/config_system.py + echo "$2" > /etc/pgadmin/gunicorn_config.py - startup - | import glob, json, re, os @@ -287,8 +279,10 @@ containers: env: - name: PGADMIN_SETUP_EMAIL value: admin@pgadmin.postgres-operator.svc - - name: PGADMIN_LISTEN_PORT - value: "5050" + - name: KRB5_CONFIG + value: /etc/pgadmin/conf.d/krb5.conf + - name: KRB5RCACHEDIR + value: /tmp image: new-image imagePullPolicy: Always name: pgadmin @@ -334,8 +328,8 @@ initContainers: - -- - |- mkdir -p /etc/pgadmin/conf.d - (umask a-w && echo "$1" > /etc/pgadmin/config_system.py) - (umask a-w && echo "$2" > /etc/pgadmin/gunicorn_config.py) + echo "$1" > /etc/pgadmin/config_system.py + echo "$2" > /etc/pgadmin/gunicorn_config.py - startup - | import glob, json, re, os diff --git a/internal/controller/standalone_pgadmin/postgrescluster.go b/internal/controller/standalone_pgadmin/postgrescluster.go index 801570561b..b777140faf 100644 --- a/internal/controller/standalone_pgadmin/postgrescluster.go +++ b/internal/controller/standalone_pgadmin/postgrescluster.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/controller/standalone_pgadmin/service.go b/internal/controller/standalone_pgadmin/service.go index a9ff403173..74a21deffd 100644 --- a/internal/controller/standalone_pgadmin/service.go +++ b/internal/controller/standalone_pgadmin/service.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/controller/standalone_pgadmin/service_test.go b/internal/controller/standalone_pgadmin/service_test.go index 7bab351ac8..a5fed9791a 100644 --- a/internal/controller/standalone_pgadmin/service_test.go +++ b/internal/controller/standalone_pgadmin/service_test.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/controller/standalone_pgadmin/statefulset.go b/internal/controller/standalone_pgadmin/statefulset.go index 255d67bdb3..b10818ee51 100644 --- a/internal/controller/standalone_pgadmin/statefulset.go +++ b/internal/controller/standalone_pgadmin/statefulset.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin @@ -104,10 +94,7 @@ func statefulset( // Use scheduling constraints from the cluster spec. sts.Spec.Template.Spec.Affinity = pgadmin.Spec.Affinity sts.Spec.Template.Spec.Tolerations = pgadmin.Spec.Tolerations - - if pgadmin.Spec.PriorityClassName != nil { - sts.Spec.Template.Spec.PriorityClassName = *pgadmin.Spec.PriorityClassName - } + sts.Spec.Template.Spec.PriorityClassName = initialize.FromPointer(pgadmin.Spec.PriorityClassName) // Restart containers any time they stop, die, are killed, etc. // - https://docs.k8s.io/concepts/workloads/pods/pod-lifecycle/#restart-policy diff --git a/internal/controller/standalone_pgadmin/statefulset_test.go b/internal/controller/standalone_pgadmin/statefulset_test.go index 6f673fc47f..da7ac2c8e7 100644 --- a/internal/controller/standalone_pgadmin/statefulset_test.go +++ b/internal/controller/standalone_pgadmin/statefulset_test.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/controller/standalone_pgadmin/users.go b/internal/controller/standalone_pgadmin/users.go index 67c7e574bf..dd24bbd9ae 100644 --- a/internal/controller/standalone_pgadmin/users.go +++ b/internal/controller/standalone_pgadmin/users.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/controller/standalone_pgadmin/users_test.go b/internal/controller/standalone_pgadmin/users_test.go index f3f1ad49eb..0f75923e12 100644 --- a/internal/controller/standalone_pgadmin/users_test.go +++ b/internal/controller/standalone_pgadmin/users_test.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/controller/standalone_pgadmin/volume.go b/internal/controller/standalone_pgadmin/volume.go index d6aecd2096..631efce9b0 100644 --- a/internal/controller/standalone_pgadmin/volume.go +++ b/internal/controller/standalone_pgadmin/volume.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/controller/standalone_pgadmin/volume_test.go b/internal/controller/standalone_pgadmin/volume_test.go index 5fe823e330..2c61e4f3ba 100644 --- a/internal/controller/standalone_pgadmin/volume_test.go +++ b/internal/controller/standalone_pgadmin/volume_test.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin @@ -18,6 +8,7 @@ import ( "context" "testing" + "github.com/pkg/errors" "gotest.tools/v3/assert" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -26,8 +17,6 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/pkg/errors" - "github.com/percona/percona-postgresql-operator/internal/controller/runtime" "github.com/percona/percona-postgresql-operator/internal/initialize" "github.com/percona/percona-postgresql-operator/internal/naming" diff --git a/internal/controller/standalone_pgadmin/watches.go b/internal/controller/standalone_pgadmin/watches.go index a7ad8d1b76..0632961abe 100644 --- a/internal/controller/standalone_pgadmin/watches.go +++ b/internal/controller/standalone_pgadmin/watches.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin @@ -32,6 +21,7 @@ import ( func (r *PGAdminReconciler) watchPostgresClusters() handler.Funcs { handle := func(ctx context.Context, cluster client.Object, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { for _, pgadmin := range r.findPGAdminsForPostgresCluster(ctx, cluster) { + q.Add(ctrl.Request{ NamespacedName: client.ObjectKeyFromObject(pgadmin), }) diff --git a/internal/controller/standalone_pgadmin/watches_test.go b/internal/controller/standalone_pgadmin/watches_test.go index 7f25ad809c..88a21e07b5 100644 --- a/internal/controller/standalone_pgadmin/watches_test.go +++ b/internal/controller/standalone_pgadmin/watches_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package standalone_pgadmin diff --git a/internal/feature/features.go b/internal/feature/features.go index 16807c6f80..db424ead42 100644 --- a/internal/feature/features.go +++ b/internal/feature/features.go @@ -1,17 +1,6 @@ -/* - Copyright 2017 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2017 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 /* Package feature provides types and functions to enable and disable features @@ -94,6 +83,9 @@ const ( // Support tablespace volumes TablespaceVolumes = "TablespaceVolumes" + + // Support VolumeSnapshots + VolumeSnapshots = "VolumeSnapshots" ) // NewGate returns a MutableGate with the Features defined in this package. @@ -102,12 +94,13 @@ func NewGate() MutableGate { if err := gate.Add(map[Feature]featuregate.FeatureSpec{ AppendCustomQueries: {Default: false, PreRelease: featuregate.Alpha}, - AutoCreateUserSchema: {Default: false, PreRelease: featuregate.Alpha}, + AutoCreateUserSchema: {Default: true, PreRelease: featuregate.Beta}, AutoGrowVolumes: {Default: false, PreRelease: featuregate.Alpha}, BridgeIdentifiers: {Default: false, PreRelease: featuregate.Alpha}, InstanceSidecars: {Default: false, PreRelease: featuregate.Alpha}, PGBouncerSidecars: {Default: false, PreRelease: featuregate.Alpha}, TablespaceVolumes: {Default: false, PreRelease: featuregate.Alpha}, + VolumeSnapshots: {Default: false, PreRelease: featuregate.Alpha}, }); err != nil { panic(err) } @@ -128,3 +121,12 @@ func Enabled(ctx context.Context, f Feature) bool { func NewContext(ctx context.Context, gate Gate) context.Context { return context.WithValue(ctx, contextKey{}, gate) } + +func ShowGates(ctx context.Context) string { + featuresEnabled := "" + gate, ok := ctx.Value(contextKey{}).(Gate) + if ok { + featuresEnabled = gate.String() + } + return featuresEnabled +} diff --git a/internal/feature/features_test.go b/internal/feature/features_test.go index b671bc2517..f76dd216e6 100644 --- a/internal/feature/features_test.go +++ b/internal/feature/features_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2017 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2017 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package feature @@ -27,12 +16,13 @@ func TestDefaults(t *testing.T) { gate := NewGate() assert.Assert(t, false == gate.Enabled(AppendCustomQueries)) - assert.Assert(t, false == gate.Enabled(AutoCreateUserSchema)) + assert.Assert(t, true == gate.Enabled(AutoCreateUserSchema)) assert.Assert(t, false == gate.Enabled(AutoGrowVolumes)) assert.Assert(t, false == gate.Enabled(BridgeIdentifiers)) assert.Assert(t, false == gate.Enabled(InstanceSidecars)) assert.Assert(t, false == gate.Enabled(PGBouncerSidecars)) assert.Assert(t, false == gate.Enabled(TablespaceVolumes)) + assert.Assert(t, false == gate.Enabled(VolumeSnapshots)) assert.Equal(t, gate.String(), "") } @@ -63,10 +53,13 @@ func TestContext(t *testing.T) { t.Parallel() gate := NewGate() ctx := NewContext(context.Background(), gate) + assert.Equal(t, ShowGates(ctx), "") assert.NilError(t, gate.Set("TablespaceVolumes=true")) assert.Assert(t, true == Enabled(ctx, TablespaceVolumes)) + assert.Equal(t, ShowGates(ctx), "TablespaceVolumes=true") assert.NilError(t, gate.SetFromMap(map[string]bool{TablespaceVolumes: false})) assert.Assert(t, false == Enabled(ctx, TablespaceVolumes)) + assert.Equal(t, ShowGates(ctx), "TablespaceVolumes=false") } diff --git a/internal/initialize/doc.go b/internal/initialize/doc.go index 34e34e5cb9..aedd85846f 100644 --- a/internal/initialize/doc.go +++ b/internal/initialize/doc.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 // Package initialize provides functions to initialize some common fields and types. package initialize diff --git a/internal/initialize/metadata.go b/internal/initialize/metadata.go index f27d4c6751..d62530736a 100644 --- a/internal/initialize/metadata.go +++ b/internal/initialize/metadata.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package initialize diff --git a/internal/initialize/metadata_test.go b/internal/initialize/metadata_test.go index b68e728513..6704e4c11d 100644 --- a/internal/initialize/metadata_test.go +++ b/internal/initialize/metadata_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package initialize_test diff --git a/internal/initialize/primitives.go b/internal/initialize/primitives.go index e3954ba436..9bc264f88c 100644 --- a/internal/initialize/primitives.go +++ b/internal/initialize/primitives.go @@ -1,30 +1,12 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package initialize // Bool returns a pointer to v. func Bool(v bool) *bool { return &v } -// ByteMap initializes m when it points to nil. -func ByteMap(m *map[string][]byte) { - if m != nil && *m == nil { - *m = make(map[string][]byte) - } -} - // FromPointer returns the value that p points to. // When p is nil, it returns the zero value of T. func FromPointer[T any](p *T) T { @@ -41,15 +23,17 @@ func Int32(v int32) *int32 { return &v } // Int64 returns a pointer to v. func Int64(v int64) *int64 { return &v } +// Map initializes m when it points to nil. +func Map[M ~map[K]V, K comparable, V any](m *M) { + // See https://pkg.go.dev/maps for similar type constraints. + + if m != nil && *m == nil { + *m = make(M) + } +} + // Pointer returns a pointer to v. func Pointer[T any](v T) *T { return &v } // String returns a pointer to v. func String(v string) *string { return &v } - -// StringMap initializes m when it points to nil. -func StringMap(m *map[string]string) { - if m != nil && *m == nil { - *m = make(map[string]string) - } -} diff --git a/internal/initialize/primitives_test.go b/internal/initialize/primitives_test.go index 4efdf3bb7e..ed3aaa504a 100644 --- a/internal/initialize/primitives_test.go +++ b/internal/initialize/primitives_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package initialize_test @@ -35,27 +24,6 @@ func TestBool(t *testing.T) { } } -func TestByteMap(t *testing.T) { - // Ignores nil pointer. - initialize.ByteMap(nil) - - var m map[string][]byte - - // Starts nil. - assert.Assert(t, m == nil) - - // Gets initialized. - initialize.ByteMap(&m) - assert.DeepEqual(t, m, map[string][]byte{}) - - // Now writable. - m["x"] = []byte("y") - - // Doesn't overwrite. - initialize.ByteMap(&m) - assert.DeepEqual(t, m, map[string][]byte{"x": []byte("y")}) -} - func TestFromPointer(t *testing.T) { t.Run("bool", func(t *testing.T) { assert.Equal(t, initialize.FromPointer((*bool)(nil)), false) @@ -118,6 +86,50 @@ func TestInt64(t *testing.T) { } } +func TestMap(t *testing.T) { + t.Run("map[string][]byte", func(t *testing.T) { + // Ignores nil pointer. + initialize.Map((*map[string][]byte)(nil)) + + var m map[string][]byte + + // Starts nil. + assert.Assert(t, m == nil) + + // Gets initialized. + initialize.Map(&m) + assert.DeepEqual(t, m, map[string][]byte{}) + + // Now writable. + m["x"] = []byte("y") + + // Doesn't overwrite. + initialize.Map(&m) + assert.DeepEqual(t, m, map[string][]byte{"x": []byte("y")}) + }) + + t.Run("map[string]string", func(t *testing.T) { + // Ignores nil pointer. + initialize.Map((*map[string]string)(nil)) + + var m map[string]string + + // Starts nil. + assert.Assert(t, m == nil) + + // Gets initialized. + initialize.Map(&m) + assert.DeepEqual(t, m, map[string]string{}) + + // Now writable. + m["x"] = "y" + + // Doesn't overwrite. + initialize.Map(&m) + assert.DeepEqual(t, m, map[string]string{"x": "y"}) + }) +} + func TestPointer(t *testing.T) { t.Run("bool", func(t *testing.T) { n := initialize.Pointer(false) @@ -189,24 +201,3 @@ func TestString(t *testing.T) { assert.Equal(t, *n, "sup") } } - -func TestStringMap(t *testing.T) { - // Ignores nil pointer. - initialize.StringMap(nil) - - var m map[string]string - - // Starts nil. - assert.Assert(t, m == nil) - - // Gets initialized. - initialize.StringMap(&m) - assert.DeepEqual(t, m, map[string]string{}) - - // Now writable. - m["x"] = "y" - - // Doesn't overwrite. - initialize.StringMap(&m) - assert.DeepEqual(t, m, map[string]string{"x": "y"}) -} diff --git a/internal/initialize/security.go b/internal/initialize/security.go index 7eb865d308..f351a5cefe 100644 --- a/internal/initialize/security.go +++ b/internal/initialize/security.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package initialize diff --git a/internal/initialize/security_test.go b/internal/initialize/security_test.go index 7a3a42b997..909b8d3443 100644 --- a/internal/initialize/security_test.go +++ b/internal/initialize/security_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package initialize_test diff --git a/internal/kubeapi/patch.go b/internal/kubeapi/patch.go index 992040a8d3..973852c17a 100644 --- a/internal/kubeapi/patch.go +++ b/internal/kubeapi/patch.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package kubeapi diff --git a/internal/kubeapi/patch_test.go b/internal/kubeapi/patch_test.go index 5307531228..52f5787b8f 100644 --- a/internal/kubeapi/patch_test.go +++ b/internal/kubeapi/patch_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package kubeapi diff --git a/internal/logging/logr.go b/internal/logging/logr.go index fe29175f7e..c907997d40 100644 --- a/internal/logging/logr.go +++ b/internal/logging/logr.go @@ -1,17 +1,6 @@ -/* -Copyright 2021 - 2024 Crunchy Data Solutions, Inc. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package logging diff --git a/internal/logging/logr_test.go b/internal/logging/logr_test.go index 2d9002650a..1cbc818ad9 100644 --- a/internal/logging/logr_test.go +++ b/internal/logging/logr_test.go @@ -1,17 +1,6 @@ -/* -Copyright 2021 - 2024 Crunchy Data Solutions, Inc. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package logging diff --git a/internal/logging/logrus.go b/internal/logging/logrus.go index 0f3d441d20..9683a104d1 100644 --- a/internal/logging/logrus.go +++ b/internal/logging/logrus.go @@ -1,17 +1,6 @@ -/* -Copyright 2021 - 2024 Crunchy Data Solutions, Inc. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package logging diff --git a/internal/logging/logrus_test.go b/internal/logging/logrus_test.go index ee5777e6a0..3e73193d1a 100644 --- a/internal/logging/logrus_test.go +++ b/internal/logging/logrus_test.go @@ -1,17 +1,6 @@ -/* -Copyright 2021 - 2024 Crunchy Data Solutions, Inc. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package logging diff --git a/internal/naming/annotations.go b/internal/naming/annotations.go index ba8c4e853f..2179a5f084 100644 --- a/internal/naming/annotations.go +++ b/internal/naming/annotations.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package naming @@ -32,6 +21,12 @@ const ( // ID associated with a specific manual backup Job. PGBackRestBackup = annotationPrefix + "pgbackrest-backup" + // PGBackRestBackupJobCompletion is the annotation that is added to restore jobs, pvcs, and + // VolumeSnapshots that are involved in the volume snapshot creation process. The annotation + // holds a RFC3339 formatted timestamp that corresponds to the completion time of the associated + // backup job. + PGBackRestBackupJobCompletion = annotationPrefix + "pgbackrest-backup-job-completion" + // PGBackRestConfigHash is an annotation used to specify the hash value associated with a // repo configuration as needed to detect configuration changes that invalidate running Jobs // (and therefore must be recreated) @@ -67,4 +62,10 @@ const ( // has schemas automatically created for the users defined in `spec.users` for all of the databases // listed for that user. AutoCreateUserSchemaAnnotation = annotationPrefix + "autoCreateUserSchema" + + // AuthorizeBackupRemovalAnnotation is an annotation used to allow users + // to delete PVC-based backups when changing from a cluster with backups + // to a cluster without backups. As usual with the operator, we do not + // touch cloud-based backups. + AuthorizeBackupRemovalAnnotation = annotationPrefix + "authorizeBackupRemoval" ) diff --git a/internal/naming/annotations_test.go b/internal/naming/annotations_test.go index a426a766dd..318dd5ab5c 100644 --- a/internal/naming/annotations_test.go +++ b/internal/naming/annotations_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package naming @@ -23,12 +12,15 @@ import ( ) func TestAnnotationsValid(t *testing.T) { + assert.Assert(t, nil == validation.IsQualifiedName(AuthorizeBackupRemovalAnnotation)) + assert.Assert(t, nil == validation.IsQualifiedName(AutoCreateUserSchemaAnnotation)) + assert.Assert(t, nil == validation.IsQualifiedName(CrunchyBridgeClusterAdoptionAnnotation)) assert.Assert(t, nil == validation.IsQualifiedName(Finalizer)) assert.Assert(t, nil == validation.IsQualifiedName(PatroniSwitchover)) assert.Assert(t, nil == validation.IsQualifiedName(PGBackRestBackup)) + assert.Assert(t, nil == validation.IsQualifiedName(PGBackRestBackupJobCompletion)) assert.Assert(t, nil == validation.IsQualifiedName(PGBackRestConfigHash)) - assert.Assert(t, nil == validation.IsQualifiedName(PGBackRestRestore)) assert.Assert(t, nil == validation.IsQualifiedName(PGBackRestIPVersion)) + assert.Assert(t, nil == validation.IsQualifiedName(PGBackRestRestore)) assert.Assert(t, nil == validation.IsQualifiedName(PostgresExporterCollectorsAnnotation)) - assert.Assert(t, nil == validation.IsQualifiedName(CrunchyBridgeClusterAdoptionAnnotation)) } diff --git a/internal/naming/controllers.go b/internal/naming/controllers.go index 35a1f8dd48..3d492e8a3a 100644 --- a/internal/naming/controllers.go +++ b/internal/naming/controllers.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package naming diff --git a/internal/naming/dns.go b/internal/naming/dns.go index b013cd69c7..d3351a5d70 100644 --- a/internal/naming/dns.go +++ b/internal/naming/dns.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package naming diff --git a/internal/naming/dns_test.go b/internal/naming/dns_test.go index 70c38f71ca..e7e2ea9dc6 100644 --- a/internal/naming/dns_test.go +++ b/internal/naming/dns_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package naming diff --git a/internal/naming/doc.go b/internal/naming/doc.go index 336193e5b6..72cab8b0b0 100644 --- a/internal/naming/doc.go +++ b/internal/naming/doc.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 // Package naming provides functions and constants for the postgres-operator // naming and labeling scheme. diff --git a/internal/naming/labels.go b/internal/naming/labels.go index b3f26b06a5..f51897749c 100644 --- a/internal/naming/labels.go +++ b/internal/naming/labels.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package naming @@ -130,6 +119,9 @@ const ( // RoleMonitoring is the LabelRole applied to Monitoring resources RoleMonitoring = "monitoring" + + // RoleSnapshot is the LabelRole applied to Snapshot resources. + RoleSnapshot = "snapshot" ) const ( @@ -162,6 +154,9 @@ const ( // BackupReplicaCreate is the backup type for the backup taken to enable pgBackRest replica // creation BackupReplicaCreate BackupJobType = "replica-create" + + // BackupScheduled is the backup type utilized for scheduled backups + BackupScheduled BackupJobType = "scheduled" ) const ( @@ -281,6 +276,7 @@ func PGBackRestCronJobLabels(clusterName, repoName, backupType string) labels.Se cronJobLabels := map[string]string{ LabelPGBackRestRepo: repoName, LabelPGBackRestCronJob: backupType, + LabelPGBackRestBackup: string(BackupScheduled), } return labels.Merge(commonLabels, cronJobLabels) } diff --git a/internal/naming/labels_test.go b/internal/naming/labels_test.go index ebd82fc11e..b8a7779858 100644 --- a/internal/naming/labels_test.go +++ b/internal/naming/labels_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package naming @@ -62,7 +51,9 @@ func TestLabelValuesValid(t *testing.T) { assert.Assert(t, nil == validation.IsValidLabelValue(RolePostgresWAL)) assert.Assert(t, nil == validation.IsValidLabelValue(RolePrimary)) assert.Assert(t, nil == validation.IsValidLabelValue(RoleReplica)) + assert.Assert(t, nil == validation.IsValidLabelValue(string(BackupManual))) assert.Assert(t, nil == validation.IsValidLabelValue(string(BackupReplicaCreate))) + assert.Assert(t, nil == validation.IsValidLabelValue(string(BackupScheduled))) assert.Assert(t, nil == validation.IsValidLabelValue(RoleMonitoring)) assert.Assert(t, nil == validation.IsValidLabelValue(RoleCrunchyBridgeClusterPostgresRole)) } @@ -193,6 +184,7 @@ func TestPGBackRestLabelFuncs(t *testing.T) { assert.Equal(t, pgBackRestCronJobLabels.Get(LabelCluster), clusterName) assert.Check(t, pgBackRestCronJobLabels.Has(LabelPGBackRest)) assert.Equal(t, pgBackRestCronJobLabels.Get(LabelPGBackRestRepo), repoName) + assert.Equal(t, pgBackRestCronJobLabels.Get(LabelPGBackRestBackup), string(BackupScheduled)) // verify the labels that identify pgBackRest dedicated repository host resources pgBackRestDedicatedLabels := PGBackRestDedicatedLabels(clusterName) diff --git a/internal/naming/limitations.md b/internal/naming/limitations.md index 78d3721088..ba607215f7 100644 --- a/internal/naming/limitations.md +++ b/internal/naming/limitations.md @@ -1,16 +1,7 @@ # Definitions diff --git a/internal/naming/names.go b/internal/naming/names.go index 7f45306895..d5da45b024 100644 --- a/internal/naming/names.go +++ b/internal/naming/names.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package naming @@ -264,6 +253,24 @@ func ClusterReplicaService(cluster *v1beta1.PostgresCluster) metav1.ObjectMeta { } } +// ClusterDedicatedSnapshotVolume returns the ObjectMeta for the dedicated Snapshot +// volume for a cluster. +func ClusterDedicatedSnapshotVolume(cluster *v1beta1.PostgresCluster) metav1.ObjectMeta { + return metav1.ObjectMeta{ + Namespace: cluster.GetNamespace(), + Name: cluster.GetName() + "-snapshot", + } +} + +// ClusterVolumeSnapshot returns the ObjectMeta, including a random name, for a +// new pgdata VolumeSnapshot. +func ClusterVolumeSnapshot(cluster *v1beta1.PostgresCluster) metav1.ObjectMeta { + return metav1.ObjectMeta{ + Namespace: cluster.Namespace, + Name: cluster.Name + "-pgdata-snapshot-" + rand.String(4), + } +} + // GenerateInstance returns a random name for a member of cluster and set. func GenerateInstance( cluster *v1beta1.PostgresCluster, set *v1beta1.PostgresInstanceSetSpec, @@ -313,8 +320,7 @@ func InstanceCertificates(instance metav1.Object) metav1.ObjectMeta { // InstanceSet returns the ObjectMeta necessary to lookup the objects // associated with a single instance set. Includes PodDisruptionBudgets func InstanceSet(cluster *v1beta1.PostgresCluster, - set *v1beta1.PostgresInstanceSetSpec, -) metav1.ObjectMeta { + set *v1beta1.PostgresInstanceSetSpec) metav1.ObjectMeta { return metav1.ObjectMeta{ Name: cluster.Name + "-set-" + set.Name, Namespace: cluster.Namespace, @@ -486,8 +492,7 @@ func PGBackRestRBAC(cluster *v1beta1.PostgresCluster) metav1.ObjectMeta { // PGBackRestRepoVolume returns the ObjectMeta for a pgBackRest repository volume func PGBackRestRepoVolume(cluster *v1beta1.PostgresCluster, - repoName string, -) metav1.ObjectMeta { + repoName string) metav1.ObjectMeta { return metav1.ObjectMeta{ Name: fmt.Sprintf("%s-%s", cluster.GetName(), repoName), Namespace: cluster.GetNamespace(), diff --git a/internal/naming/names_test.go b/internal/naming/names_test.go index d7d94af2cf..a1b1c7df48 100644 --- a/internal/naming/names_test.go +++ b/internal/naming/names_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package naming @@ -216,6 +205,12 @@ func TestClusterNamesUniqueAndValid(t *testing.T) { {"PGBackRestRepoVolume", PGBackRestRepoVolume(cluster, repoName)}, }) }) + + t.Run("VolumeSnapshots", func(t *testing.T) { + testUniqueAndValid(t, []test{ + {"ClusterVolumeSnapshot", ClusterVolumeSnapshot(cluster)}, + }) + }) } func TestInstanceNamesUniqueAndValid(t *testing.T) { diff --git a/internal/naming/selectors.go b/internal/naming/selectors.go index a847761be5..5f662a39e9 100644 --- a/internal/naming/selectors.go +++ b/internal/naming/selectors.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package naming @@ -46,6 +35,30 @@ func Cluster(cluster string) metav1.LabelSelector { } } +// ClusterRestoreJobs selects all existing restore jobs in a cluster. +func ClusterRestoreJobs(cluster string) metav1.LabelSelector { + return metav1.LabelSelector{ + MatchLabels: map[string]string{ + LabelCluster: cluster, + }, + MatchExpressions: []metav1.LabelSelectorRequirement{ + {Key: LabelPGBackRestRestore, Operator: metav1.LabelSelectorOpExists}, + }, + } +} + +// ClusterBackupJobs selects things for all existing backup jobs in cluster. +func ClusterBackupJobs(cluster string) metav1.LabelSelector { + return metav1.LabelSelector{ + MatchLabels: map[string]string{ + LabelCluster: cluster, + }, + MatchExpressions: []metav1.LabelSelectorRequirement{ + {Key: LabelPGBackRestBackup, Operator: metav1.LabelSelectorOpExists}, + }, + } +} + // ClusterDataForPostgresAndPGBackRest selects things for PostgreSQL data and // things for pgBackRest data. func ClusterDataForPostgresAndPGBackRest(cluster string) metav1.LabelSelector { diff --git a/internal/naming/selectors_test.go b/internal/naming/selectors_test.go index ce23aa507f..e4651d9778 100644 --- a/internal/naming/selectors_test.go +++ b/internal/naming/selectors_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package naming @@ -43,6 +32,18 @@ func TestCluster(t *testing.T) { assert.ErrorContains(t, err, "Invalid") } +func TestClusterBackupJobs(t *testing.T) { + s, err := AsSelector(ClusterBackupJobs("something")) + assert.NilError(t, err) + assert.DeepEqual(t, s.String(), strings.Join([]string{ + "postgres-operator.crunchydata.com/cluster=something", + "postgres-operator.crunchydata.com/pgbackrest-backup", + }, ",")) + + _, err = AsSelector(Cluster("--whoa/yikes")) + assert.ErrorContains(t, err, "Invalid") +} + func TestClusterDataForPostgresAndPGBackRest(t *testing.T) { s, err := AsSelector(ClusterDataForPostgresAndPGBackRest("something")) assert.NilError(t, err) diff --git a/internal/naming/telemetry.go b/internal/naming/telemetry.go index 4f55f831a7..351f52f4ec 100644 --- a/internal/naming/telemetry.go +++ b/internal/naming/telemetry.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package naming diff --git a/internal/patroni/api.go b/internal/patroni/api.go index 2035b3c574..c779f8b3af 100644 --- a/internal/patroni/api.go +++ b/internal/patroni/api.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package patroni diff --git a/internal/patroni/api_test.go b/internal/patroni/api_test.go index 2df86ce1aa..1603d2fc75 100644 --- a/internal/patroni/api_test.go +++ b/internal/patroni/api_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package patroni diff --git a/internal/patroni/certificates.go b/internal/patroni/certificates.go index f7e80c33e1..9aa1525769 100644 --- a/internal/patroni/certificates.go +++ b/internal/patroni/certificates.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package patroni diff --git a/internal/patroni/certificates.md b/internal/patroni/certificates.md index 633466d31c..f58786ce20 100644 --- a/internal/patroni/certificates.md +++ b/internal/patroni/certificates.md @@ -1,16 +1,7 @@ Server diff --git a/internal/patroni/certificates_test.go b/internal/patroni/certificates_test.go index 3ea1f440f4..3432db41e8 100644 --- a/internal/patroni/certificates_test.go +++ b/internal/patroni/certificates_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package patroni diff --git a/internal/patroni/config.go b/internal/patroni/config.go index ee4755ae45..57a2c9c9e2 100644 --- a/internal/patroni/config.go +++ b/internal/patroni/config.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package patroni @@ -243,7 +232,7 @@ func DynamicConfiguration( // Some libraries belong at specific positions in the list, so figure // that out as well. if k == "shared_preload_libraries" { - // Load user-defined libraries ahead of mandatory libraries. + // K8SPG-442: Load user-defined libraries ahead of mandatory libraries. if s, ok := parameters[k].(string); ok && len(s) > 0 { v = s + "," + v } @@ -604,6 +593,7 @@ func instanceYAML( }, } } else { + initdb := []string{ // Enable checksums on data pages to help detect corruption of // storage that would otherwise be silent. This also enables diff --git a/internal/patroni/config.md b/internal/patroni/config.md index 4c261c40ab..18d28d8a4e 100644 --- a/internal/patroni/config.md +++ b/internal/patroni/config.md @@ -1,16 +1,7 @@ Patroni configuration is complicated. The daemon `patroni` and the client diff --git a/internal/patroni/config_test.go b/internal/patroni/config_test.go index f735cd0326..7071cc974a 100644 --- a/internal/patroni/config_test.go +++ b/internal/patroni/config_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package patroni @@ -976,6 +965,7 @@ postgresql: restapi: {} tags: {} `, "\t\n")+"\n") + } func TestPGBackRestCreateReplicaCommand(t *testing.T) { diff --git a/internal/patroni/doc.go b/internal/patroni/doc.go index 8962a0af23..500305406d 100644 --- a/internal/patroni/doc.go +++ b/internal/patroni/doc.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 // Package patroni provides clients, utilities and resources for configuring and // interacting with Patroni inside of a PostgreSQL cluster diff --git a/internal/patroni/rbac.go b/internal/patroni/rbac.go index 5380f2967b..617323acb8 100644 --- a/internal/patroni/rbac.go +++ b/internal/patroni/rbac.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package patroni diff --git a/internal/patroni/rbac_test.go b/internal/patroni/rbac_test.go index 3fee72815f..74f223cd97 100644 --- a/internal/patroni/rbac_test.go +++ b/internal/patroni/rbac_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package patroni diff --git a/internal/patroni/reconcile.go b/internal/patroni/reconcile.go index 2494701e14..ed69ddd8bf 100644 --- a/internal/patroni/reconcile.go +++ b/internal/patroni/reconcile.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package patroni @@ -46,7 +35,7 @@ func ClusterConfigMap(ctx context.Context, ) error { var err error - initialize.StringMap(&outClusterConfigMap.Data) + initialize.Map(&outClusterConfigMap.Data) outClusterConfigMap.Data[configMapFileKey], err = clusterYAML(inCluster, inHBAs, inParameters) @@ -62,7 +51,7 @@ func InstanceConfigMap(ctx context.Context, ) error { var err error - initialize.StringMap(&outInstanceConfigMap.Data) + initialize.Map(&outInstanceConfigMap.Data) command := pgbackrest.ReplicaCreateCommand(inCluster, inInstanceSpec) @@ -77,7 +66,7 @@ func InstanceCertificates(ctx context.Context, inRoot pki.Certificate, inDNS pki.Certificate, inDNSKey pki.PrivateKey, outInstanceCertificates *corev1.Secret, ) error { - initialize.ByteMap(&outInstanceCertificates.Data) + initialize.Map(&outInstanceCertificates.Data) var err error outInstanceCertificates.Data[certAuthorityFileKey], err = certFile(inRoot) diff --git a/internal/patroni/reconcile_test.go b/internal/patroni/reconcile_test.go index 324231ddd3..ec469a00d4 100644 --- a/internal/patroni/reconcile_test.go +++ b/internal/patroni/reconcile_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package patroni diff --git a/internal/pgadmin/config.go b/internal/pgadmin/config.go index 261b2869e7..1d1fbf62de 100644 --- a/internal/pgadmin/config.go +++ b/internal/pgadmin/config.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgadmin diff --git a/internal/pgadmin/config_test.go b/internal/pgadmin/config_test.go index c91e760998..f25ddbcaff 100644 --- a/internal/pgadmin/config_test.go +++ b/internal/pgadmin/config_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgadmin diff --git a/internal/pgadmin/reconcile.go b/internal/pgadmin/reconcile.go index f07788c42e..5a46258551 100644 --- a/internal/pgadmin/reconcile.go +++ b/internal/pgadmin/reconcile.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgadmin @@ -144,7 +133,7 @@ func ConfigMap( return nil } - initialize.StringMap(&outConfigMap.Data) + initialize.Map(&outConfigMap.Data) // To avoid spurious reconciles, the following value must not change when // the spec does not change. [json.Encoder] and [json.Marshal] do this by diff --git a/internal/pgadmin/reconcile_test.go b/internal/pgadmin/reconcile_test.go index c60f77e638..3332fc8760 100644 --- a/internal/pgadmin/reconcile_test.go +++ b/internal/pgadmin/reconcile_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgadmin diff --git a/internal/pgadmin/users.go b/internal/pgadmin/users.go index b888b32706..659bb92b85 100644 --- a/internal/pgadmin/users.go +++ b/internal/pgadmin/users.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgadmin diff --git a/internal/pgadmin/users_test.go b/internal/pgadmin/users_test.go index 497afaca34..89904ec8ba 100644 --- a/internal/pgadmin/users_test.go +++ b/internal/pgadmin/users_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgadmin diff --git a/internal/pgaudit/postgres.go b/internal/pgaudit/postgres.go index fa844a632c..5964cf017c 100644 --- a/internal/pgaudit/postgres.go +++ b/internal/pgaudit/postgres.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgaudit diff --git a/internal/pgaudit/postgres_test.go b/internal/pgaudit/postgres_test.go index acb3e7a90d..07f0dedbaa 100644 --- a/internal/pgaudit/postgres_test.go +++ b/internal/pgaudit/postgres_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgaudit diff --git a/internal/pgbackrest/certificates.go b/internal/pgbackrest/certificates.go index a00a23327c..c734b475fc 100644 --- a/internal/pgbackrest/certificates.go +++ b/internal/pgbackrest/certificates.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest diff --git a/internal/pgbackrest/certificates.md b/internal/pgbackrest/certificates.md index ef6a1dd7b0..344616486b 100644 --- a/internal/pgbackrest/certificates.md +++ b/internal/pgbackrest/certificates.md @@ -1,16 +1,7 @@ Server diff --git a/internal/pgbackrest/certificates_test.go b/internal/pgbackrest/certificates_test.go index 8b4671342f..a6f6d8c7c5 100644 --- a/internal/pgbackrest/certificates_test.go +++ b/internal/pgbackrest/certificates_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest diff --git a/internal/pgbackrest/config.go b/internal/pgbackrest/config.go index dbd1b071c0..64beccc236 100644 --- a/internal/pgbackrest/config.go +++ b/internal/pgbackrest/config.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest @@ -100,7 +89,7 @@ func CreatePGBackRestConfigMapIntent(postgresCluster *v1beta1.PostgresCluster, } // create an empty map for the config data - initialize.StringMap(&cm.Data) + initialize.Map(&cm.Data) pgdataDir := postgres.DataDirectory(postgresCluster) // Port will always be populated, since the API will set a default of 5432 if not provided @@ -275,6 +264,42 @@ mv "${pgdata}" "${pgdata}_bootstrap"` return append([]string{"bash", "-ceu", "--", restoreScript, "-", pgdata}, args...) } +// DedicatedSnapshotVolumeRestoreCommand returns the command for performing a pgBackRest delta restore +// into a dedicated snapshot volume. In addition to calling the pgBackRest restore command with any +// pgBackRest options provided, the script also removes the patroni.dynamic.json file if present. This +// ensures the configuration from the cluster being restored from is not utilized when bootstrapping a +// new cluster, and the configuration for the new cluster is utilized instead. +func DedicatedSnapshotVolumeRestoreCommand(pgdata string, args ...string) []string { + + // The postmaster.pid file is removed, if it exists, before attempting a restore. + // This allows the restore to be tried more than once without the causing an + // error due to the presence of the file in subsequent attempts. + + // Wrap pgbackrest restore command in backup_label checks. If pre/post + // backup_labels are different, restore moved database forward, so return 0 + // so that the Job is successful and we know to proceed with snapshot. + // Otherwise return 1, Job will fail, and we will not proceed with snapshot. + restoreScript := `declare -r pgdata="$1" opts="$2" +BACKUP_LABEL=$([[ ! -e "${pgdata}/backup_label" ]] || md5sum "${pgdata}/backup_label") +echo "Starting pgBackRest delta restore" + +install --directory --mode=0700 "${pgdata}" +rm -f "${pgdata}/postmaster.pid" +bash -xc "pgbackrest restore ${opts}" +rm -f "${pgdata}/patroni.dynamic.json" + +BACKUP_LABEL_POST=$([[ ! -e "${pgdata}/backup_label" ]] || md5sum "${pgdata}/backup_label") +if [[ "${BACKUP_LABEL}" != "${BACKUP_LABEL_POST}" ]] +then + exit 0 +fi +echo Database was not advanced by restore. No snapshot will be taken. +echo Check that your last backup was successful. +exit 1` + + return append([]string{"bash", "-ceu", "--", restoreScript, "-", pgdata}, args...) +} + // populatePGInstanceConfigurationMap returns options representing the pgBackRest configuration for // a PostgreSQL instance func populatePGInstanceConfigurationMap( diff --git a/internal/pgbackrest/config.md b/internal/pgbackrest/config.md index 498348eb90..2101535b3a 100644 --- a/internal/pgbackrest/config.md +++ b/internal/pgbackrest/config.md @@ -1,16 +1,7 @@ # pgBackRest Configuration Overview diff --git a/internal/pgbackrest/config_test.go b/internal/pgbackrest/config_test.go index e7bdfb75a5..06f564e25e 100644 --- a/internal/pgbackrest/config_test.go +++ b/internal/pgbackrest/config_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest @@ -380,6 +369,36 @@ func TestRestoreCommandTDE(t *testing.T) { assert.Assert(t, strings.Contains(string(b), "encryption_key_command = 'echo testValue'"), "expected encryption_key_command setting, got:\n%s", b) } + +func TestDedicatedSnapshotVolumeRestoreCommand(t *testing.T) { + shellcheck := require.ShellCheck(t) + + pgdata := "/pgdata/pg13" + opts := []string{ + "--stanza=" + DefaultStanzaName, "--pg1-path=" + pgdata, + "--repo=1"} + command := DedicatedSnapshotVolumeRestoreCommand(pgdata, strings.Join(opts, " ")) + + assert.DeepEqual(t, command[:3], []string{"bash", "-ceu", "--"}) + assert.Assert(t, len(command) > 3) + + dir := t.TempDir() + file := filepath.Join(dir, "script.bash") + assert.NilError(t, os.WriteFile(file, []byte(command[3]), 0o600)) + + cmd := exec.Command(shellcheck, "--enable=all", file) + output, err := cmd.CombinedOutput() + assert.NilError(t, err, "%q\n%s", cmd.Args, output) +} + +func TestDedicatedSnapshotVolumeRestoreCommandPrettyYAML(t *testing.T) { + b, err := yaml.Marshal(DedicatedSnapshotVolumeRestoreCommand("/dir", "--options")) + + assert.NilError(t, err) + assert.Assert(t, strings.Contains(string(b), "\n- |"), + "expected literal block scalar, got:\n%s", b) +} + func TestServerConfig(t *testing.T) { cluster := &v1beta1.PostgresCluster{} cluster.UID = "shoe" diff --git a/internal/pgbackrest/iana.go b/internal/pgbackrest/iana.go index 9d36385ed6..c6e2f71e6c 100644 --- a/internal/pgbackrest/iana.go +++ b/internal/pgbackrest/iana.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest diff --git a/internal/pgbackrest/options.go b/internal/pgbackrest/options.go index 54cb7dac37..2439901e47 100644 --- a/internal/pgbackrest/options.go +++ b/internal/pgbackrest/options.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest diff --git a/internal/pgbackrest/options_test.go b/internal/pgbackrest/options_test.go index f31853781c..374737ec7f 100644 --- a/internal/pgbackrest/options_test.go +++ b/internal/pgbackrest/options_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest diff --git a/internal/pgbackrest/pgbackrest.go b/internal/pgbackrest/pgbackrest.go index 6dfbad7eb8..618fa850d3 100644 --- a/internal/pgbackrest/pgbackrest.go +++ b/internal/pgbackrest/pgbackrest.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest diff --git a/internal/pgbackrest/pgbackrest_test.go b/internal/pgbackrest/pgbackrest_test.go index f86b423d68..4c5c901120 100644 --- a/internal/pgbackrest/pgbackrest_test.go +++ b/internal/pgbackrest/pgbackrest_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest diff --git a/internal/pgbackrest/postgres.go b/internal/pgbackrest/postgres.go index ad3b0ad372..2afcd508ff 100644 --- a/internal/pgbackrest/postgres.go +++ b/internal/pgbackrest/postgres.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest @@ -26,6 +15,7 @@ import ( func PostgreSQL( inCluster *v1beta1.PostgresCluster, outParameters *postgres.Parameters, + backupsEnabled bool, ) { if outParameters.Mandatory == nil { outParameters.Mandatory = postgres.NewParameterSet() diff --git a/internal/pgbackrest/postgres_test.go b/internal/pgbackrest/postgres_test.go index 5e3151815d..618b8bff4e 100644 --- a/internal/pgbackrest/postgres_test.go +++ b/internal/pgbackrest/postgres_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest @@ -29,7 +18,7 @@ func TestPostgreSQLParameters(t *testing.T) { cluster := new(v1beta1.PostgresCluster) parameters := new(postgres.Parameters) - PostgreSQL(cluster, parameters) + PostgreSQL(cluster, parameters, true) assert.DeepEqual(t, parameters.Mandatory.AsMap(), map[string]string{ "archive_mode": "on", "archive_command": strings.Join([]string{ @@ -55,7 +44,7 @@ func TestPostgreSQLParameters(t *testing.T) { RepoName: "repo99", } - PostgreSQL(cluster, parameters) + PostgreSQL(cluster, parameters, true) assert.DeepEqual(t, parameters.Mandatory.AsMap(), map[string]string{ "archive_mode": "on", "archive_command": strings.Join([]string{ diff --git a/internal/pgbackrest/rbac.go b/internal/pgbackrest/rbac.go index 9a4ad86d91..dfc842dc9f 100644 --- a/internal/pgbackrest/rbac.go +++ b/internal/pgbackrest/rbac.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest diff --git a/internal/pgbackrest/rbac_test.go b/internal/pgbackrest/rbac_test.go index be951eb355..7326a58a80 100644 --- a/internal/pgbackrest/rbac_test.go +++ b/internal/pgbackrest/rbac_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest diff --git a/internal/pgbackrest/reconcile.go b/internal/pgbackrest/reconcile.go index 5e57865f85..40e2faf408 100644 --- a/internal/pgbackrest/reconcile.go +++ b/internal/pgbackrest/reconcile.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest @@ -421,7 +410,7 @@ func InstanceCertificates(ctx context.Context, ) error { var err error - initialize.ByteMap(&outInstanceCertificates.Data) + initialize.Map(&outInstanceCertificates.Data) if err == nil { outInstanceCertificates.Data[certInstanceSecretKey], err = certFile(inDNS) @@ -488,7 +477,7 @@ func RestoreConfig( sourceConfigMap, targetConfigMap *corev1.ConfigMap, sourceSecret, targetSecret *corev1.Secret, ) { - initialize.StringMap(&targetConfigMap.Data) + initialize.Map(&targetConfigMap.Data) // Use the repository definitions from the source cluster. // @@ -500,7 +489,7 @@ func RestoreConfig( targetConfigMap.Data[CMInstanceKey] = sourceConfigMap.Data[CMInstanceKey] if sourceSecret != nil && targetSecret != nil { - initialize.ByteMap(&targetSecret.Data) + initialize.Map(&targetSecret.Data) // - https://golang.org/issue/45038 bytesClone := func(b []byte) []byte { return append([]byte(nil), b...) } @@ -524,7 +513,7 @@ func Secret(ctx context.Context, // Save the CA and generate a TLS client certificate for the entire cluster. if inRepoHost != nil { - initialize.ByteMap(&outSecret.Data) + initialize.Map(&outSecret.Data) // The server verifies its "tls-server-auth" option contains the common // name (CN) of the certificate presented by a client. The entire diff --git a/internal/pgbackrest/reconcile_test.go b/internal/pgbackrest/reconcile_test.go index da8a4631b0..7d5bd2e380 100644 --- a/internal/pgbackrest/reconcile_test.go +++ b/internal/pgbackrest/reconcile_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest @@ -31,6 +20,7 @@ import ( "github.com/percona/percona-postgresql-operator/internal/feature" "github.com/percona/percona-postgresql-operator/internal/naming" "github.com/percona/percona-postgresql-operator/internal/pki" + "github.com/percona/percona-postgresql-operator/internal/testing/cmp" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -203,7 +193,7 @@ func TestAddConfigToInstancePod(t *testing.T) { assert.DeepEqual(t, pod, *result, cmpopts.IgnoreFields(pod, "Containers", "Volumes")) // Only database and pgBackRest containers have mounts. - assert.Assert(t, marshalMatches(result.Containers, ` + assert.Assert(t, cmp.MarshalMatches(result.Containers, ` - name: database resources: {} volumeMounts: @@ -235,7 +225,7 @@ func TestAddConfigToInstancePod(t *testing.T) { alwaysExpect(t, out) // Instance configuration files after custom projections. - assert.Assert(t, marshalMatches(out.Volumes, ` + assert.Assert(t, cmp.MarshalMatches(out.Volumes, ` - name: pgbackrest-config projected: sources: @@ -272,7 +262,7 @@ func TestAddConfigToInstancePod(t *testing.T) { alwaysExpect(t, out) // Instance configuration and certificates. - assert.Assert(t, marshalMatches(out.Volumes, ` + assert.Assert(t, cmp.MarshalMatches(out.Volumes, ` - name: pgbackrest-config projected: sources: @@ -312,7 +302,7 @@ func TestAddConfigToInstancePod(t *testing.T) { alwaysExpect(t, out) // Instance configuration files, server config, and optional client certificates. - assert.Assert(t, marshalMatches(out.Volumes, ` + assert.Assert(t, cmp.MarshalMatches(out.Volumes, ` - name: pgbackrest-config projected: sources: @@ -356,7 +346,7 @@ func TestAddConfigToRepoPod(t *testing.T) { assert.DeepEqual(t, pod, *result, cmpopts.IgnoreFields(pod, "Containers", "Volumes")) // Only pgBackRest containers have mounts. - assert.Assert(t, marshalMatches(result.Containers, ` + assert.Assert(t, cmp.MarshalMatches(result.Containers, ` - name: other resources: {} - name: pgbackrest @@ -383,7 +373,7 @@ func TestAddConfigToRepoPod(t *testing.T) { // Repository configuration files, server config, and client certificates // after custom projections. - assert.Assert(t, marshalMatches(out.Volumes, ` + assert.Assert(t, cmp.MarshalMatches(out.Volumes, ` - name: pgbackrest-config projected: sources: @@ -429,7 +419,7 @@ func TestAddConfigToRestorePod(t *testing.T) { assert.DeepEqual(t, pod, *result, cmpopts.IgnoreFields(pod, "Containers", "Volumes")) // Only pgBackRest containers have mounts. - assert.Assert(t, marshalMatches(result.Containers, ` + assert.Assert(t, cmp.MarshalMatches(result.Containers, ` - name: other resources: {} - name: pgbackrest @@ -464,7 +454,7 @@ func TestAddConfigToRestorePod(t *testing.T) { // Instance configuration files and optional client certificates // after custom projections. - assert.Assert(t, marshalMatches(out.Volumes, ` + assert.Assert(t, cmp.MarshalMatches(out.Volumes, ` - name: pgbackrest-config projected: sources: @@ -508,7 +498,7 @@ func TestAddConfigToRestorePod(t *testing.T) { // Instance configuration files and optional client certificates // after custom projections. - assert.Assert(t, marshalMatches(out.Volumes, ` + assert.Assert(t, cmp.MarshalMatches(out.Volumes, ` - name: pgbackrest-config projected: sources: @@ -550,7 +540,7 @@ func TestAddConfigToRestorePod(t *testing.T) { // Instance configuration files and optional configuration files // after custom projections. - assert.Assert(t, marshalMatches(out.Volumes, ` + assert.Assert(t, cmp.MarshalMatches(out.Volumes, ` - name: postgres-config projected: sources: @@ -629,7 +619,7 @@ func TestAddServerToInstancePod(t *testing.T) { // The TLS server is added while other containers are untouched. // It has PostgreSQL volumes mounted while other volumes are ignored. - assert.Assert(t, marshalMatches(out.Containers, ` + assert.Assert(t, cmp.MarshalMatches(out.Containers, ` - name: database resources: {} - name: other @@ -715,7 +705,7 @@ func TestAddServerToInstancePod(t *testing.T) { // The server certificate comes from the instance Secret. // Other volumes are untouched. - assert.Assert(t, marshalMatches(out.Volumes, ` + assert.Assert(t, cmp.MarshalMatches(out.Volumes, ` - name: other - name: postgres-data - name: postgres-wal @@ -756,7 +746,7 @@ func TestAddServerToInstancePod(t *testing.T) { // Only Containers and Volumes fields have changed. assert.DeepEqual(t, pod, *out, cmpopts.IgnoreFields(pod, "Containers", "Volumes")) - assert.Assert(t, marshalMatches(out.Containers, ` + assert.Assert(t, cmp.MarshalMatches(out.Containers, ` - name: database resources: {} - name: other @@ -885,7 +875,7 @@ func TestAddServerToRepoPod(t *testing.T) { assert.DeepEqual(t, pod, *out, cmpopts.IgnoreFields(pod, "Containers", "Volumes")) // The TLS server is added while other containers are untouched. - assert.Assert(t, marshalMatches(out.Containers, ` + assert.Assert(t, cmp.MarshalMatches(out.Containers, ` - name: other resources: {} - command: @@ -964,7 +954,7 @@ func TestAddServerToRepoPod(t *testing.T) { `)) // The server certificate comes from the pgBackRest Secret. - assert.Assert(t, marshalMatches(out.Volumes, ` + assert.Assert(t, cmp.MarshalMatches(out.Volumes, ` - name: pgbackrest-server projected: sources: diff --git a/internal/pgbackrest/restore.md b/internal/pgbackrest/restore.md index dc1b500811..8828576921 100644 --- a/internal/pgbackrest/restore.md +++ b/internal/pgbackrest/restore.md @@ -1,16 +1,7 @@ ## Target Action diff --git a/internal/pgbackrest/tls-server.md b/internal/pgbackrest/tls-server.md index 2020eb40cd..b572cc1ea4 100644 --- a/internal/pgbackrest/tls-server.md +++ b/internal/pgbackrest/tls-server.md @@ -1,16 +1,7 @@ # pgBackRest TLS Server diff --git a/internal/pgbackrest/util.go b/internal/pgbackrest/util.go index 4b271bc7a9..8f4725e648 100644 --- a/internal/pgbackrest/util.go +++ b/internal/pgbackrest/util.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest diff --git a/internal/pgbackrest/util_test.go b/internal/pgbackrest/util_test.go index ba1ecb84f1..fb18e2b676 100644 --- a/internal/pgbackrest/util_test.go +++ b/internal/pgbackrest/util_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbackrest diff --git a/internal/pgbouncer/certificates.go b/internal/pgbouncer/certificates.go index 4fb0c4926e..31f91c503a 100644 --- a/internal/pgbouncer/certificates.go +++ b/internal/pgbouncer/certificates.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbouncer diff --git a/internal/pgbouncer/certificates_test.go b/internal/pgbouncer/certificates_test.go index 04a8f9708e..5c4a461205 100644 --- a/internal/pgbouncer/certificates_test.go +++ b/internal/pgbouncer/certificates_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbouncer @@ -20,6 +9,8 @@ import ( "gotest.tools/v3/assert" corev1 "k8s.io/api/core/v1" + + "github.com/percona/percona-postgresql-operator/internal/testing/cmp" ) func TestBackendAuthority(t *testing.T) { @@ -27,7 +18,7 @@ func TestBackendAuthority(t *testing.T) { projection := &corev1.SecretProjection{ LocalObjectReference: corev1.LocalObjectReference{Name: "some-name"}, } - assert.Assert(t, marshalMatches(backendAuthority(projection), ` + assert.Assert(t, cmp.MarshalMatches(backendAuthority(projection), ` secret: items: - key: ca.crt @@ -40,7 +31,7 @@ secret: {Key: "some-crt-key", Path: "tls.crt"}, {Key: "some-ca-key", Path: "ca.crt"}, } - assert.Assert(t, marshalMatches(backendAuthority(projection), ` + assert.Assert(t, cmp.MarshalMatches(backendAuthority(projection), ` secret: items: - key: some-ca-key @@ -54,7 +45,7 @@ func TestFrontendCertificate(t *testing.T) { secret.Name = "op-secret" t.Run("Generated", func(t *testing.T) { - assert.Assert(t, marshalMatches(frontendCertificate(nil, secret), ` + assert.Assert(t, cmp.MarshalMatches(frontendCertificate(nil, secret), ` secret: items: - key: pgbouncer-frontend.ca-roots @@ -72,7 +63,7 @@ secret: custom.Name = "some-other" // No items; assume Key matches Path. - assert.Assert(t, marshalMatches(frontendCertificate(custom, secret), ` + assert.Assert(t, cmp.MarshalMatches(frontendCertificate(custom, secret), ` secret: items: - key: ca.crt @@ -91,7 +82,7 @@ secret: {Key: "some-cert-key", Path: "tls.crt"}, {Key: "some-key-key", Path: "tls.key"}, } - assert.Assert(t, marshalMatches(frontendCertificate(custom, secret), ` + assert.Assert(t, cmp.MarshalMatches(frontendCertificate(custom, secret), ` secret: items: - key: some-ca-key diff --git a/internal/pgbouncer/config.go b/internal/pgbouncer/config.go index 399f5745ae..3ac90b5bfe 100644 --- a/internal/pgbouncer/config.go +++ b/internal/pgbouncer/config.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbouncer diff --git a/internal/pgbouncer/config.md b/internal/pgbouncer/config.md index 8c6ee87012..abfec12518 100644 --- a/internal/pgbouncer/config.md +++ b/internal/pgbouncer/config.md @@ -1,16 +1,7 @@ PgBouncer is configured through INI files. It will reload these files when diff --git a/internal/pgbouncer/config_test.go b/internal/pgbouncer/config_test.go index 3c5dddf5f6..51dc76b288 100644 --- a/internal/pgbouncer/config_test.go +++ b/internal/pgbouncer/config_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbouncer @@ -27,6 +16,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" + "github.com/percona/percona-postgresql-operator/internal/testing/cmp" "github.com/percona/percona-postgresql-operator/internal/testing/require" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -150,7 +140,7 @@ func TestPodConfigFiles(t *testing.T) { t.Run("Default", func(t *testing.T) { projections := podConfigFiles(config, configmap, secret) - assert.Assert(t, marshalMatches(projections, ` + assert.Assert(t, cmp.MarshalMatches(projections, ` - configMap: items: - key: pgbouncer-empty @@ -183,7 +173,7 @@ func TestPodConfigFiles(t *testing.T) { } projections := podConfigFiles(config, configmap, secret) - assert.Assert(t, marshalMatches(projections, ` + assert.Assert(t, cmp.MarshalMatches(projections, ` - configMap: items: - key: pgbouncer-empty diff --git a/internal/pgbouncer/postgres.go b/internal/pgbouncer/postgres.go index b31555ea36..adf0b9b885 100644 --- a/internal/pgbouncer/postgres.go +++ b/internal/pgbouncer/postgres.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbouncer @@ -42,7 +31,6 @@ const ( func sqlAuthenticationQuery(sqlFunctionName string, exposeSuperusers bool) string { // Only a subset of authorization identifiers should be accessible to // PgBouncer. - // - https://www.postgresql.org/docs/current/catalog-pg-authid.html conditions := []string{ // Only those with permission to login. `pg_authid.rolcanlogin`, diff --git a/internal/pgbouncer/postgres_test.go b/internal/pgbouncer/postgres_test.go index e324f4ccaf..e3b62dde4e 100644 --- a/internal/pgbouncer/postgres_test.go +++ b/internal/pgbouncer/postgres_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbouncer diff --git a/internal/pgbouncer/reconcile.go b/internal/pgbouncer/reconcile.go index 37439756fb..25d5bca9d0 100644 --- a/internal/pgbouncer/reconcile.go +++ b/internal/pgbouncer/reconcile.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbouncer @@ -41,7 +30,7 @@ func ConfigMap( return } - initialize.StringMap(&outConfigMap.Data) + initialize.Map(&outConfigMap.Data) outConfigMap.Data[emptyConfigMapKey] = "" outConfigMap.Data[iniFileConfigMapKey] = clusterINI(inCluster) @@ -61,7 +50,7 @@ func Secret(ctx context.Context, } var err error - initialize.ByteMap(&outSecret.Data) + initialize.Map(&outSecret.Data) // Use the existing password and verifier. Generate both when either is missing. // NOTE(cbandy): We don't have a function to compare a plaintext password diff --git a/internal/pgbouncer/reconcile_test.go b/internal/pgbouncer/reconcile_test.go index ebd97432c8..cc0fad9bfa 100644 --- a/internal/pgbouncer/reconcile_test.go +++ b/internal/pgbouncer/reconcile_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgbouncer @@ -19,7 +8,7 @@ import ( "context" "testing" - "github.com/google/go-cmp/cmp" + gocmp "github.com/google/go-cmp/cmp" "gotest.tools/v3/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -28,6 +17,7 @@ import ( "github.com/percona/percona-postgresql-operator/internal/naming" "github.com/percona/percona-postgresql-operator/internal/pki" "github.com/percona/percona-postgresql-operator/internal/postgres" + "github.com/percona/percona-postgresql-operator/internal/testing/cmp" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -134,7 +124,7 @@ func TestPod(t *testing.T) { call() - assert.Assert(t, marshalMatches(pod, ` + assert.Assert(t, cmp.MarshalMatches(pod, ` containers: - command: - pgbouncer @@ -244,7 +234,7 @@ volumes: call() - assert.Assert(t, marshalMatches(pod, ` + assert.Assert(t, cmp.MarshalMatches(pod, ` containers: - command: - pgbouncer @@ -354,7 +344,7 @@ volumes: call() - assert.Assert(t, marshalMatches(pod, ` + assert.Assert(t, cmp.MarshalMatches(pod, ` containers: - command: - pgbouncer @@ -506,6 +496,6 @@ func TestPostgreSQL(t *testing.T) { Mandatory: postgresqlHBAs(), }, // postgres.HostBasedAuthentication has unexported fields. Call String() to compare. - cmp.Transformer("", postgres.HostBasedAuthentication.String)) + gocmp.Transformer("", postgres.HostBasedAuthentication.String)) }) } diff --git a/internal/pgmonitor/exporter.go b/internal/pgmonitor/exporter.go index e117c1ab0b..fd873523bd 100644 --- a/internal/pgmonitor/exporter.go +++ b/internal/pgmonitor/exporter.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgmonitor @@ -43,7 +32,8 @@ const ( // postgres_exporter command flags var ( - ExporterWebConfigFileFlag = "--web.config.file=/web-config/web-config.yml" + ExporterWebConfigFileFlag = "--web.config.file=/web-config/web-config.yml" + ExporterDeactivateStatBGWriterFlag = "--no-collector.stat_bgwriter" ) // Defaults for certain values used in queries.yml diff --git a/internal/pgmonitor/exporter_test.go b/internal/pgmonitor/exporter_test.go index fa21e2683f..de43451c88 100644 --- a/internal/pgmonitor/exporter_test.go +++ b/internal/pgmonitor/exporter_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgmonitor diff --git a/internal/pgmonitor/postgres.go b/internal/pgmonitor/postgres.go index a7268544fb..10f0618d73 100644 --- a/internal/pgmonitor/postgres.go +++ b/internal/pgmonitor/postgres.go @@ -1,17 +1,5 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// +// SPDX-License-Identifier: Apache-2.0 package pgmonitor diff --git a/internal/pgmonitor/postgres_test.go b/internal/pgmonitor/postgres_test.go index e29da7ed5c..f27a6e04a8 100644 --- a/internal/pgmonitor/postgres_test.go +++ b/internal/pgmonitor/postgres_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgmonitor diff --git a/internal/pgmonitor/util.go b/internal/pgmonitor/util.go index 82be8449bf..a31efeb58e 100644 --- a/internal/pgmonitor/util.go +++ b/internal/pgmonitor/util.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgmonitor diff --git a/internal/pgmonitor/util_test.go b/internal/pgmonitor/util_test.go index c2547fa4df..2d1957aa54 100644 --- a/internal/pgmonitor/util_test.go +++ b/internal/pgmonitor/util_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pgmonitor diff --git a/internal/pki/common.go b/internal/pki/common.go index 13c573cd2b..fbe9421f8b 100644 --- a/internal/pki/common.go +++ b/internal/pki/common.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pki diff --git a/internal/pki/doc.go b/internal/pki/doc.go index bfbe34e3c1..71f8c0a1bc 100644 --- a/internal/pki/doc.go +++ b/internal/pki/doc.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 // Package pki provides types and functions to support the public key // infrastructure of the Postgres Operator. It enforces a two layer system diff --git a/internal/pki/encoding.go b/internal/pki/encoding.go index b7ebe4eed1..2d2cd851e3 100644 --- a/internal/pki/encoding.go +++ b/internal/pki/encoding.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pki diff --git a/internal/pki/encoding_test.go b/internal/pki/encoding_test.go index 699a38eef6..e1edc773d4 100644 --- a/internal/pki/encoding_test.go +++ b/internal/pki/encoding_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pki diff --git a/internal/pki/pki.go b/internal/pki/pki.go index 9f923bb9f7..7048810654 100644 --- a/internal/pki/pki.go +++ b/internal/pki/pki.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pki diff --git a/internal/pki/pki_test.go b/internal/pki/pki_test.go index 91d8014c64..efa3db8395 100644 --- a/internal/pki/pki_test.go +++ b/internal/pki/pki_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package pki diff --git a/internal/postgis/postgis.go b/internal/postgis/postgis.go index cd1a5c1a11..2b8732ca39 100644 --- a/internal/postgis/postgis.go +++ b/internal/postgis/postgis.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgis diff --git a/internal/postgis/postgis_test.go b/internal/postgis/postgis_test.go index 97cd338daa..5f604abc90 100644 --- a/internal/postgis/postgis_test.go +++ b/internal/postgis/postgis_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgis diff --git a/internal/postgres/config.go b/internal/postgres/config.go index 3ab3fe26e2..3538c40ff3 100644 --- a/internal/postgres/config.go +++ b/internal/postgres/config.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres @@ -128,7 +117,7 @@ func WALStorage(instance *v1beta1.PostgresInstanceSetSpec) string { // Environment returns the environment variables required to invoke PostgreSQL // utilities. func Environment(cluster *v1beta1.PostgresCluster) []corev1.EnvVar { - return []corev1.EnvVar{ + env := []corev1.EnvVar{ // - https://www.postgresql.org/docs/current/reference-server.html { Name: "PGDATA", @@ -158,6 +147,24 @@ func Environment(cluster *v1beta1.PostgresCluster) []corev1.EnvVar { Value: "/tmp", }, } + + if cluster.CompareVersion("2.6.0") >= 0 { + // This allows a custom CA certificate to be mounted for Postgres LDAP + // authentication via spec.config.files. + // - https://wiki.postgresql.org/wiki/LDAP_Authentication_against_AD + // + // When setting the TLS_CACERT for LDAP as an environment variable, 'LDAP' + // must be appended as a prefix. + // - https://www.openldap.org/software/man.cgi?query=ldap.conf + // + // Testing with LDAPTLS_CACERTDIR did not work as expected during testing. + env = append(env, corev1.EnvVar{ + Name: "LDAPTLS_CACERT", + Value: configMountPath + "/ldap/ca.crt", + }) + } + + return env } // reloadCommand returns an entrypoint that convinces PostgreSQL to reload diff --git a/internal/postgres/config_test.go b/internal/postgres/config_test.go index 03849485f2..af625fdb66 100644 --- a/internal/postgres/config_test.go +++ b/internal/postgres/config_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres diff --git a/internal/postgres/databases.go b/internal/postgres/databases.go index cefe32e145..e03e0b9056 100644 --- a/internal/postgres/databases.go +++ b/internal/postgres/databases.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres diff --git a/internal/postgres/databases_test.go b/internal/postgres/databases_test.go index ac74c21f45..fd9939f2f8 100644 --- a/internal/postgres/databases_test.go +++ b/internal/postgres/databases_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres diff --git a/internal/postgres/doc.go b/internal/postgres/doc.go index e84fce010a..bd616b5916 100644 --- a/internal/postgres/doc.go +++ b/internal/postgres/doc.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 // Package postgres is a collection of resources that interact with PostgreSQL // or provide functionality that makes it easier for other resources to interact diff --git a/internal/postgres/exec.go b/internal/postgres/exec.go index 326588bdff..a846a8aa57 100644 --- a/internal/postgres/exec.go +++ b/internal/postgres/exec.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres diff --git a/internal/postgres/exec_test.go b/internal/postgres/exec_test.go index 08994248d9..3101017b99 100644 --- a/internal/postgres/exec_test.go +++ b/internal/postgres/exec_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres diff --git a/internal/postgres/hba.go b/internal/postgres/hba.go index fd358ea96b..d9b5ce2680 100644 --- a/internal/postgres/hba.go +++ b/internal/postgres/hba.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres diff --git a/internal/postgres/hba_test.go b/internal/postgres/hba_test.go index e722c77ff5..212c4ad5d0 100644 --- a/internal/postgres/hba_test.go +++ b/internal/postgres/hba_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres diff --git a/internal/postgres/huge_pages.go b/internal/postgres/huge_pages.go index 3951c592ce..94e3cdfd57 100644 --- a/internal/postgres/huge_pages.go +++ b/internal/postgres/huge_pages.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres diff --git a/internal/postgres/huge_pages_test.go b/internal/postgres/huge_pages_test.go index 834d607eef..2478099e05 100644 --- a/internal/postgres/huge_pages_test.go +++ b/internal/postgres/huge_pages_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres diff --git a/internal/postgres/iana.go b/internal/postgres/iana.go index e43cec1fd8..4392b549f1 100644 --- a/internal/postgres/iana.go +++ b/internal/postgres/iana.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres diff --git a/internal/postgres/parameters.go b/internal/postgres/parameters.go index 35cc30aa9c..434d9fd1dd 100644 --- a/internal/postgres/parameters.go +++ b/internal/postgres/parameters.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres diff --git a/internal/postgres/parameters_test.go b/internal/postgres/parameters_test.go index f87738ed77..c6228d7958 100644 --- a/internal/postgres/parameters_test.go +++ b/internal/postgres/parameters_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres diff --git a/internal/postgres/password/doc.go b/internal/postgres/password/doc.go index 3abf99d988..eef7ed7db2 100644 --- a/internal/postgres/password/doc.go +++ b/internal/postgres/password/doc.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 // package password lets one create the appropriate password hashes and // verifiers that are used for adding the information into PostgreSQL diff --git a/internal/postgres/password/md5.go b/internal/postgres/password/md5.go index 648d4edc24..884dfb655e 100644 --- a/internal/postgres/password/md5.go +++ b/internal/postgres/password/md5.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package password diff --git a/internal/postgres/password/md5_test.go b/internal/postgres/password/md5_test.go index 11ee6465a2..80cb7742d6 100644 --- a/internal/postgres/password/md5_test.go +++ b/internal/postgres/password/md5_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package password diff --git a/internal/postgres/password/password.go b/internal/postgres/password/password.go index 07ec826a9a..337282cc74 100644 --- a/internal/postgres/password/password.go +++ b/internal/postgres/password/password.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package password diff --git a/internal/postgres/password/password_test.go b/internal/postgres/password/password_test.go index 9688616b01..3401dec4ac 100644 --- a/internal/postgres/password/password_test.go +++ b/internal/postgres/password/password_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package password diff --git a/internal/postgres/password/scram.go b/internal/postgres/password/scram.go index 66f5cd8151..8264cd87a0 100644 --- a/internal/postgres/password/scram.go +++ b/internal/postgres/password/scram.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package password diff --git a/internal/postgres/password/scram_test.go b/internal/postgres/password/scram_test.go index 6f2ca2505f..0552e519b7 100644 --- a/internal/postgres/password/scram_test.go +++ b/internal/postgres/password/scram_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package password diff --git a/internal/postgres/reconcile.go b/internal/postgres/reconcile.go index b84bd0a73a..6a1dba8ad5 100644 --- a/internal/postgres/reconcile.go +++ b/internal/postgres/reconcile.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres @@ -162,6 +151,7 @@ func InstancePod(ctx context.Context, }, } + // K8SPG-375 dbContainerMounts := []corev1.VolumeMount{ certVolumeMount, dataVolumeMount, diff --git a/internal/postgres/reconcile_test.go b/internal/postgres/reconcile_test.go index fab30edd03..edcfdeb499 100644 --- a/internal/postgres/reconcile_test.go +++ b/internal/postgres/reconcile_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres @@ -26,6 +15,7 @@ import ( "github.com/percona/percona-postgresql-operator/internal/feature" "github.com/percona/percona-postgresql-operator/internal/initialize" "github.com/percona/percona-postgresql-operator/internal/naming" + "github.com/percona/percona-postgresql-operator/internal/testing/cmp" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -78,7 +68,7 @@ func TestInstancePod(t *testing.T) { cluster.Spec.ImagePullPolicy = corev1.PullAlways cluster.Spec.PostgresVersion = 11 cluster.SetLabels(map[string]string{ - naming.LabelVersion: "2.5.0", + naming.LabelVersion: "2.6.0", }) dataVolume := new(corev1.PersistentVolumeClaim) @@ -131,7 +121,7 @@ func TestInstancePod(t *testing.T) { InstancePod(ctx, cluster, instance, serverSecretProjection, clientSecretProjection, dataVolume, nil, nil, pod) - assert.Assert(t, marshalMatches(pod, ` + assert.Assert(t, cmp.MarshalMatches(pod, ` containers: - env: - name: PGDATA @@ -144,6 +134,8 @@ containers: value: /etc/postgres/krb5.conf - name: KRB5RCACHEDIR value: /tmp + - name: LDAPTLS_CACERT + value: /etc/postgres/ldap/ca.crt imagePullPolicy: Always name: database ports: @@ -307,6 +299,8 @@ initContainers: value: /etc/postgres/krb5.conf - name: KRB5RCACHEDIR value: /tmp + - name: LDAPTLS_CACERT + value: /etc/postgres/ldap/ca.crt imagePullPolicy: Always name: postgres-startup resources: @@ -397,7 +391,7 @@ volumes: assert.Assert(t, len(pod.InitContainers) > 0) // Container has all mountPaths, including downwardAPI - assert.Assert(t, marshalMatches(pod.Containers[0].VolumeMounts, ` + assert.Assert(t, cmp.MarshalMatches(pod.Containers[0].VolumeMounts, ` - mountPath: /pgconf/tls name: cert-volume readOnly: true @@ -410,7 +404,7 @@ volumes: name: postgres-wal`), "expected WAL and downwardAPI mounts in %q container", pod.Containers[0].Name) // InitContainer has all mountPaths, except downwardAPI - assert.Assert(t, marshalMatches(pod.InitContainers[0].VolumeMounts, ` + assert.Assert(t, cmp.MarshalMatches(pod.InitContainers[0].VolumeMounts, ` - mountPath: /pgconf/tls name: cert-volume readOnly: true @@ -419,7 +413,7 @@ volumes: - mountPath: /pgwal name: postgres-wal`), "expected WAL mount, no downwardAPI mount in %q container", pod.InitContainers[0].Name) - assert.Assert(t, marshalMatches(pod.Volumes, ` + assert.Assert(t, cmp.MarshalMatches(pod.Volumes, ` - name: cert-volume projected: defaultMode: 384 @@ -505,7 +499,7 @@ volumes: // Container has all mountPaths, including downwardAPI, // and the postgres-config - assert.Assert(t, marshalMatches(pod.Containers[0].VolumeMounts, ` + assert.Assert(t, cmp.MarshalMatches(pod.Containers[0].VolumeMounts, ` - mountPath: /pgconf/tls name: cert-volume readOnly: true @@ -519,7 +513,7 @@ volumes: readOnly: true`), "expected WAL and downwardAPI mounts in %q container", pod.Containers[0].Name) // InitContainer has all mountPaths, except downwardAPI and additionalConfig - assert.Assert(t, marshalMatches(pod.InitContainers[0].VolumeMounts, ` + assert.Assert(t, cmp.MarshalMatches(pod.InitContainers[0].VolumeMounts, ` - mountPath: /pgconf/tls name: cert-volume readOnly: true @@ -587,7 +581,7 @@ volumes: InstancePod(ctx, cluster, instance, serverSecretProjection, clientSecretProjection, dataVolume, nil, tablespaceVolumes, pod) - assert.Assert(t, marshalMatches(pod.Containers[0].VolumeMounts, ` + assert.Assert(t, cmp.MarshalMatches(pod.Containers[0].VolumeMounts, ` - mountPath: /pgconf/tls name: cert-volume readOnly: true @@ -602,7 +596,7 @@ volumes: name: tablespace-trial`), "expected tablespace mount(s) in %q container", pod.Containers[0].Name) // InitContainer has all mountPaths, except downwardAPI and additionalConfig - assert.Assert(t, marshalMatches(pod.InitContainers[0].VolumeMounts, ` + assert.Assert(t, cmp.MarshalMatches(pod.InitContainers[0].VolumeMounts, ` - mountPath: /pgconf/tls name: cert-volume readOnly: true @@ -628,7 +622,7 @@ volumes: assert.Assert(t, len(pod.Containers) > 0) assert.Assert(t, len(pod.InitContainers) > 0) - assert.Assert(t, marshalMatches(pod.Containers[0].VolumeMounts, ` + assert.Assert(t, cmp.MarshalMatches(pod.Containers[0].VolumeMounts, ` - mountPath: /pgconf/tls name: cert-volume readOnly: true @@ -640,7 +634,7 @@ volumes: - mountPath: /pgwal name: postgres-wal`), "expected WAL and downwardAPI mounts in %q container", pod.Containers[0].Name) - assert.Assert(t, marshalMatches(pod.InitContainers[0].VolumeMounts, ` + assert.Assert(t, cmp.MarshalMatches(pod.InitContainers[0].VolumeMounts, ` - mountPath: /pgconf/tls name: cert-volume readOnly: true @@ -649,7 +643,7 @@ volumes: - mountPath: /pgwal name: postgres-wal`), "expected WAL mount, no downwardAPI mount in %q container", pod.InitContainers[0].Name) - assert.Assert(t, marshalMatches(pod.Volumes, ` + assert.Assert(t, cmp.MarshalMatches(pod.Volumes, ` - name: cert-volume projected: defaultMode: 384 @@ -719,23 +713,20 @@ func TestPodSecurityContext(t *testing.T) { cluster := new(v1beta1.PostgresCluster) cluster.Default() - assert.Assert(t, marshalMatches(PodSecurityContext(cluster), ` + assert.Assert(t, cmp.MarshalMatches(PodSecurityContext(cluster), ` fsGroup: 26 fsGroupChangePolicy: OnRootMismatch `)) cluster.Spec.OpenShift = initialize.Bool(true) - assert.Assert(t, marshalMatches(PodSecurityContext(cluster), ` -fsGroupChangePolicy: OnRootMismatch - `)) cluster.Spec.SupplementalGroups = []int64{} - assert.Assert(t, marshalMatches(PodSecurityContext(cluster), ` + assert.Assert(t, cmp.MarshalMatches(PodSecurityContext(cluster), ` fsGroupChangePolicy: OnRootMismatch `)) cluster.Spec.SupplementalGroups = []int64{999, 65000} - assert.Assert(t, marshalMatches(PodSecurityContext(cluster), ` + assert.Assert(t, cmp.MarshalMatches(PodSecurityContext(cluster), ` fsGroupChangePolicy: OnRootMismatch supplementalGroups: - 999 @@ -743,7 +734,7 @@ supplementalGroups: `)) *cluster.Spec.OpenShift = false - assert.Assert(t, marshalMatches(PodSecurityContext(cluster), ` + assert.Assert(t, cmp.MarshalMatches(PodSecurityContext(cluster), ` fsGroup: 26 fsGroupChangePolicy: OnRootMismatch supplementalGroups: diff --git a/internal/postgres/users.go b/internal/postgres/users.go index 94f7134dbe..2ea0188c9f 100644 --- a/internal/postgres/users.go +++ b/internal/postgres/users.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres @@ -170,7 +159,7 @@ SELECT pg_catalog.format('GRANT ALL PRIVILEGES ON DATABASE %I TO %I', log.V(1).Info("wrote PostgreSQL users", "stdout", stdout, "stderr", stderr) - // The operator will attemtp to write schemas for the users in the spec if + // The operator will attempt to write schemas for the users in the spec if // * the feature gate is enabled and // * the cluster is annotated. if feature.Enabled(ctx, feature.AutoCreateUserSchema) { diff --git a/internal/postgres/users_test.go b/internal/postgres/users_test.go index f75850275d..9b9530835e 100644 --- a/internal/postgres/users_test.go +++ b/internal/postgres/users_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package postgres diff --git a/internal/postgres/wal.md b/internal/postgres/wal.md index dc1e0c54a2..afb094c20e 100644 --- a/internal/postgres/wal.md +++ b/internal/postgres/wal.md @@ -1,16 +1,7 @@ PostgreSQL commits transactions by storing changes in its [write-ahead log][WAL]. diff --git a/internal/registration/interface.go b/internal/registration/interface.go index b8fcebabc5..2e4d8a9c3a 100644 --- a/internal/registration/interface.go +++ b/internal/registration/interface.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package registration diff --git a/internal/registration/runner.go b/internal/registration/runner.go index 9eba098a8b..19e5446aea 100644 --- a/internal/registration/runner.go +++ b/internal/registration/runner.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package registration @@ -86,8 +76,14 @@ func NewRunner(publicKey, tokenPath string, changed func()) (*Runner, error) { } // CheckToken loads and verifies the configured token, returning an error when -// the file exists but cannot be verified. -func (r *Runner) CheckToken() error { +// the file exists but cannot be verified, and +// returning the token if it can be verified. +// NOTE(upgradecheck): return the token/nil so that we can use the token +// in upgradecheck; currently a refresh of the token will cause a restart of the pod +// meaning that the token used in upgradecheck is always the current token. +// But if the restart behavior changes, we might drop the token return in main.go +// and change upgradecheck to retrieve the token itself +func (r *Runner) CheckToken() (*jwt.Token, error) { data, errFile := os.ReadFile(r.tokenPath) key := func(*jwt.Token) (any, error) { return r.publicKey, nil } @@ -96,7 +92,7 @@ func (r *Runner) CheckToken() error { r.token.Lock() defer r.token.Unlock() - _, errToken := jwt.ParseWithClaims(string(data), &r.token, key, + token, errToken := jwt.ParseWithClaims(string(data), &r.token, key, jwt.WithExpirationRequired(), jwt.WithValidMethods([]string{"RS256"}), ) @@ -112,11 +108,11 @@ func (r *Runner) CheckToken() error { switch { case !r.enabled || !r.token.Exists: - return nil + return nil, nil case errFile != nil: - return errFile + return nil, errFile default: - return errToken + return token, errToken } } @@ -162,7 +158,7 @@ func (r *Runner) Required( func (r *Runner) NeedLeaderElection() bool { return true } // Start watches for a mounted registration token when enabled. It blocks -// until ctx is canceled. +// until ctx is cancelled. func (r *Runner) Start(ctx context.Context) error { var ticks <-chan time.Time @@ -178,7 +174,7 @@ func (r *Runner) Start(ctx context.Context) error { select { case <-ticks: _, before := r.state() - if err := r.CheckToken(); err != nil { + if _, err := r.CheckToken(); err != nil { log.Error(err, "Unable to validate token") } if _, after := r.state(); before != after && r.changed != nil { diff --git a/internal/registration/runner_test.go b/internal/registration/runner_test.go index fa474cc251..b46f04909f 100644 --- a/internal/registration/runner_test.go +++ b/internal/registration/runner_test.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package registration @@ -111,19 +101,22 @@ func TestRunnerCheckToken(t *testing.T) { t.Run("SafeToCallDisabled", func(t *testing.T) { r := Runner{enabled: false} - assert.NilError(t, r.CheckToken()) + _, err := r.CheckToken() + assert.NilError(t, err) }) t.Run("FileMissing", func(t *testing.T) { r := Runner{enabled: true, tokenPath: filepath.Join(dir, "nope")} - assert.NilError(t, r.CheckToken()) + _, err := r.CheckToken() + assert.NilError(t, err) }) t.Run("FileUnreadable", func(t *testing.T) { r := Runner{enabled: true, tokenPath: filepath.Join(dir, "nope")} assert.NilError(t, os.WriteFile(r.tokenPath, nil, 0o200)) // Writeable - assert.ErrorContains(t, r.CheckToken(), "permission") + _, err := r.CheckToken() + assert.ErrorContains(t, err, "permission") assert.Assert(t, r.token.ExpiresAt == nil) }) @@ -131,7 +124,8 @@ func TestRunnerCheckToken(t *testing.T) { r := Runner{enabled: true, tokenPath: filepath.Join(dir, "empty")} assert.NilError(t, os.WriteFile(r.tokenPath, nil, 0o400)) // Readable - assert.ErrorContains(t, r.CheckToken(), "malformed") + _, err := r.CheckToken() + assert.ErrorContains(t, err, "malformed") assert.Assert(t, r.token.ExpiresAt == nil) }) @@ -150,7 +144,8 @@ func TestRunnerCheckToken(t *testing.T) { assert.NilError(t, err) assert.NilError(t, os.WriteFile(r.tokenPath, []byte(data), 0o400)) // Readable - assert.Assert(t, r.CheckToken() != nil, "HMAC algorithm should be rejected") + _, err = r.CheckToken() + assert.Assert(t, err != nil, "HMAC algorithm should be rejected") assert.Assert(t, r.token.ExpiresAt == nil) }) @@ -165,7 +160,7 @@ func TestRunnerCheckToken(t *testing.T) { assert.NilError(t, err) assert.NilError(t, os.WriteFile(r.tokenPath, []byte(data), 0o400)) // Readable - err = r.CheckToken() + _, err = r.CheckToken() assert.ErrorContains(t, err, "exp claim is required") assert.Assert(t, r.token.ExpiresAt == nil) }) @@ -183,7 +178,7 @@ func TestRunnerCheckToken(t *testing.T) { assert.NilError(t, err) assert.NilError(t, os.WriteFile(r.tokenPath, []byte(data), 0o400)) // Readable - err = r.CheckToken() + _, err = r.CheckToken() assert.ErrorContains(t, err, "is expired") assert.Assert(t, r.token.ExpiresAt == nil) }) @@ -195,14 +190,20 @@ func TestRunnerCheckToken(t *testing.T) { tokenPath: filepath.Join(dir, "valid"), } + expiration := jwt.NewNumericDate(time.Now().Add(time.Hour)) data, err := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ - "exp": jwt.NewNumericDate(time.Now().Add(time.Hour)), + "exp": expiration, }).SignedString(key) assert.NilError(t, err) assert.NilError(t, os.WriteFile(r.tokenPath, []byte(data), 0o400)) // Readable - assert.NilError(t, r.CheckToken()) + token, err := r.CheckToken() + assert.NilError(t, err) assert.Assert(t, r.token.ExpiresAt != nil) + assert.Assert(t, token.Valid) + exp, err := token.Claims.GetExpirationTime() + assert.NilError(t, err) + assert.Equal(t, exp.Time, expiration.Time) }) } @@ -557,7 +558,8 @@ func TestRunnerStart(t *testing.T) { // Begin with an invalid token. assert.NilError(t, os.WriteFile(runner.tokenPath, nil, 0o600)) - assert.Assert(t, runner.CheckToken() != nil) + _, err = runner.CheckToken() + assert.Assert(t, err != nil) // Replace it with a valid token. assert.NilError(t, os.WriteFile(runner.tokenPath, []byte(token), 0o600)) diff --git a/internal/registration/testing.go b/internal/registration/testing.go index fb9e9e4f4b..1418f6d2d3 100644 --- a/internal/registration/testing.go +++ b/internal/registration/testing.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package registration diff --git a/internal/testing/cmp/cmp.go b/internal/testing/cmp/cmp.go index 58e0a1e4de..265a598064 100644 --- a/internal/testing/cmp/cmp.go +++ b/internal/testing/cmp/cmp.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package cmp diff --git a/internal/testing/events/recorder.go b/internal/testing/events/recorder.go index 273a506521..23c03a4c40 100644 --- a/internal/testing/events/recorder.go +++ b/internal/testing/events/recorder.go @@ -1,17 +1,6 @@ -/* - Copyright 2022 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2022 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package events diff --git a/internal/testing/require/exec.go b/internal/testing/require/exec.go index 983bd49711..c182e84996 100644 --- a/internal/testing/require/exec.go +++ b/internal/testing/require/exec.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package require diff --git a/internal/testing/require/kubernetes.go b/internal/testing/require/kubernetes.go index 3f65d73f4c..7f8b18cca3 100644 --- a/internal/testing/require/kubernetes.go +++ b/internal/testing/require/kubernetes.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package require @@ -121,6 +110,7 @@ func kubernetes3(t testing.TB) (*envtest.Environment, client.Client) { ErrorIfPathMissing: true, Paths: []string{ filepath.Join(base, "config", "crd", "bases"), + filepath.Join(base, "hack", "tools", "external-snapshotter", "client", "config", "crd"), }, Scheme: runtime.Scheme, }) diff --git a/internal/testing/require/parallel.go b/internal/testing/require/parallel.go index 72c8dbd932..4fbdf42284 100644 --- a/internal/testing/require/parallel.go +++ b/internal/testing/require/parallel.go @@ -1,17 +1,6 @@ -/* - Copyright 2022 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2022 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package require diff --git a/internal/testing/validation/postgrescluster_test.go b/internal/testing/validation/postgrescluster_test.go index fe8d39a7d4..a411424bcb 100644 --- a/internal/testing/validation/postgrescluster_test.go +++ b/internal/testing/validation/postgrescluster_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package validation diff --git a/internal/upgradecheck/header.go b/internal/upgradecheck/header.go index 330ea09464..63941d457c 100644 --- a/internal/upgradecheck/header.go +++ b/internal/upgradecheck/header.go @@ -1,17 +1,6 @@ -/* - Copyright 2017 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2017 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package upgradecheck @@ -19,6 +8,7 @@ import ( "context" "encoding/json" "net/http" + "os" googleuuid "github.com/google/uuid" corev1 "k8s.io/api/core/v1" @@ -28,6 +18,7 @@ import ( "k8s.io/client-go/rest" crclient "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/percona/percona-postgresql-operator/internal/feature" "github.com/percona/percona-postgresql-operator/internal/logging" "github.com/percona/percona-postgresql-operator/internal/naming" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" @@ -44,24 +35,36 @@ var ( // Extensible struct for client upgrade data type clientUpgradeData struct { - DeploymentID string `json:"deployment_id"` - KubernetesEnv string `json:"kubernetes_env"` - PGOClustersTotal int `json:"pgo_clusters_total"` - PGOVersion string `json:"pgo_version"` - IsOpenShift bool `json:"is_open_shift"` + BridgeClustersTotal int `json:"bridge_clusters_total"` + BuildSource string `json:"build_source"` + DeploymentID string `json:"deployment_id"` + FeatureGatesEnabled string `json:"feature_gates_enabled"` + IsOpenShift bool `json:"is_open_shift"` + KubernetesEnv string `json:"kubernetes_env"` + PGOClustersTotal int `json:"pgo_clusters_total"` + PGOInstaller string `json:"pgo_installer"` + PGOInstallerOrigin string `json:"pgo_installer_origin"` + PGOVersion string `json:"pgo_version"` + RegistrationToken string `json:"registration_token"` } // generateHeader aggregates data and returns a struct of that data // If any errors are encountered, it logs those errors and uses the default values func generateHeader(ctx context.Context, cfg *rest.Config, crClient crclient.Client, - pgoVersion string, isOpenShift bool) *clientUpgradeData { + pgoVersion string, isOpenShift bool, registrationToken string) *clientUpgradeData { return &clientUpgradeData{ - PGOVersion: pgoVersion, - IsOpenShift: isOpenShift, - DeploymentID: ensureDeploymentID(ctx, crClient), - PGOClustersTotal: getManagedClusters(ctx, crClient), - KubernetesEnv: getServerVersion(ctx, cfg), + BridgeClustersTotal: getBridgeClusters(ctx, crClient), + BuildSource: os.Getenv("BUILD_SOURCE"), + DeploymentID: ensureDeploymentID(ctx, crClient), + FeatureGatesEnabled: feature.ShowGates(ctx), + IsOpenShift: isOpenShift, + KubernetesEnv: getServerVersion(ctx, cfg), + PGOClustersTotal: getManagedClusters(ctx, crClient), + PGOInstaller: os.Getenv("PGO_INSTALLER"), + PGOInstallerOrigin: os.Getenv("PGO_INSTALLER_ORIGIN"), + PGOVersion: pgoVersion, + RegistrationToken: registrationToken, } } @@ -169,6 +172,22 @@ func getManagedClusters(ctx context.Context, crClient crclient.Client) int { return count } +// getBridgeClusters returns a count of Bridge clusters managed by this PGO instance +// Any errors encountered will be logged and the count result will be 0 +func getBridgeClusters(ctx context.Context, crClient crclient.Client) int { + var count int + clusters := &v1beta1.CrunchyBridgeClusterList{} + err := crClient.List(ctx, clusters) + if err != nil { + log := logging.FromContext(ctx) + log.V(1).Info("upgrade check issue: could not count bridge clusters", + "response", err.Error()) + } else { + count = len(clusters.Items) + } + return count +} + // getServerVersion returns the stringified server version (i.e., the same info `kubectl version` // returns for the server) // Any errors encountered will be logged and will return an empty string diff --git a/internal/upgradecheck/header_test.go b/internal/upgradecheck/header_test.go index a0ff2b7597..29d26104ee 100644 --- a/internal/upgradecheck/header_test.go +++ b/internal/upgradecheck/header_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package upgradecheck @@ -19,6 +8,7 @@ import ( "context" "encoding/json" "net/http" + "strings" "testing" "gotest.tools/v3/assert" @@ -31,6 +21,7 @@ import ( "k8s.io/client-go/rest" "github.com/percona/percona-postgresql-operator/internal/controller/postgrescluster" + "github.com/percona/percona-postgresql-operator/internal/feature" "github.com/percona/percona-postgresql-operator/internal/naming" "github.com/percona/percona-postgresql-operator/internal/testing/cmp" "github.com/percona/percona-postgresql-operator/internal/testing/require" @@ -50,6 +41,10 @@ func TestGenerateHeader(t *testing.T) { reconciler := postgrescluster.Reconciler{Client: cc} + t.Setenv("PGO_INSTALLER", "test") + t.Setenv("PGO_INSTALLER_ORIGIN", "test-origin") + t.Setenv("BUILD_SOURCE", "developer") + t.Run("error ensuring ID", func(t *testing.T) { fakeClientWithOptionalError := &fakeClientWithError{ cc, "patch error", @@ -57,7 +52,7 @@ func TestGenerateHeader(t *testing.T) { ctx, calls := setupLogCapture(ctx) res := generateHeader(ctx, cfg, fakeClientWithOptionalError, - "1.2.3", reconciler.IsOpenShift) + "1.2.3", reconciler.IsOpenShift, "") assert.Equal(t, len(*calls), 1) assert.Assert(t, cmp.Contains((*calls)[0], `upgrade check issue: could not apply configmap`)) assert.Equal(t, res.IsOpenShift, reconciler.IsOpenShift) @@ -66,8 +61,15 @@ func TestGenerateHeader(t *testing.T) { err := cc.List(ctx, &pgoList) assert.NilError(t, err) assert.Equal(t, len(pgoList.Items), res.PGOClustersTotal) + bridgeList := v1beta1.CrunchyBridgeClusterList{} + err = cc.List(ctx, &bridgeList) + assert.NilError(t, err) + assert.Equal(t, len(bridgeList.Items), res.BridgeClustersTotal) assert.Equal(t, "1.2.3", res.PGOVersion) assert.Equal(t, server.String(), res.KubernetesEnv) + assert.Equal(t, "test", res.PGOInstaller) + assert.Equal(t, "test-origin", res.PGOInstallerOrigin) + assert.Equal(t, "developer", res.BuildSource) }) t.Run("error getting cluster count", func(t *testing.T) { @@ -77,14 +79,21 @@ func TestGenerateHeader(t *testing.T) { ctx, calls := setupLogCapture(ctx) res := generateHeader(ctx, cfg, fakeClientWithOptionalError, - "1.2.3", reconciler.IsOpenShift) - assert.Equal(t, len(*calls), 1) - assert.Assert(t, cmp.Contains((*calls)[0], `upgrade check issue: could not count postgres clusters`)) + "1.2.3", reconciler.IsOpenShift, "") + assert.Equal(t, len(*calls), 2) + // Aggregating the logs since we cannot determine which call will be first + callsAggregate := strings.Join(*calls, " ") + assert.Assert(t, cmp.Contains(callsAggregate, `upgrade check issue: could not count postgres clusters`)) + assert.Assert(t, cmp.Contains(callsAggregate, `upgrade check issue: could not count bridge clusters`)) assert.Equal(t, res.IsOpenShift, reconciler.IsOpenShift) assert.Equal(t, deploymentID, res.DeploymentID) assert.Equal(t, 0, res.PGOClustersTotal) + assert.Equal(t, 0, res.BridgeClustersTotal) assert.Equal(t, "1.2.3", res.PGOVersion) assert.Equal(t, server.String(), res.KubernetesEnv) + assert.Equal(t, "test", res.PGOInstaller) + assert.Equal(t, "test-origin", res.PGOInstallerOrigin) + assert.Equal(t, "developer", res.BuildSource) }) t.Run("error getting server version info", func(t *testing.T) { @@ -92,7 +101,7 @@ func TestGenerateHeader(t *testing.T) { badcfg := &rest.Config{} res := generateHeader(ctx, badcfg, cc, - "1.2.3", reconciler.IsOpenShift) + "1.2.3", reconciler.IsOpenShift, "") assert.Equal(t, len(*calls), 1) assert.Assert(t, cmp.Contains((*calls)[0], `upgrade check issue: could not retrieve server version`)) assert.Equal(t, res.IsOpenShift, reconciler.IsOpenShift) @@ -103,13 +112,21 @@ func TestGenerateHeader(t *testing.T) { assert.Equal(t, len(pgoList.Items), res.PGOClustersTotal) assert.Equal(t, "1.2.3", res.PGOVersion) assert.Equal(t, "", res.KubernetesEnv) + assert.Equal(t, "test", res.PGOInstaller) + assert.Equal(t, "test-origin", res.PGOInstallerOrigin) + assert.Equal(t, "developer", res.BuildSource) }) t.Run("success", func(t *testing.T) { ctx, calls := setupLogCapture(ctx) + gate := feature.NewGate() + assert.NilError(t, gate.SetFromMap(map[string]bool{ + feature.TablespaceVolumes: true, + })) + ctx = feature.NewContext(ctx, gate) res := generateHeader(ctx, cfg, cc, - "1.2.3", reconciler.IsOpenShift) + "1.2.3", reconciler.IsOpenShift, "") assert.Equal(t, len(*calls), 0) assert.Equal(t, res.IsOpenShift, reconciler.IsOpenShift) assert.Equal(t, deploymentID, res.DeploymentID) @@ -119,6 +136,10 @@ func TestGenerateHeader(t *testing.T) { assert.Equal(t, len(pgoList.Items), res.PGOClustersTotal) assert.Equal(t, "1.2.3", res.PGOVersion) assert.Equal(t, server.String(), res.KubernetesEnv) + assert.Equal(t, "TablespaceVolumes=true", res.FeatureGatesEnabled) + assert.Equal(t, "test", res.PGOInstaller) + assert.Equal(t, "test-origin", res.PGOInstallerOrigin) + assert.Equal(t, "developer", res.BuildSource) }) } @@ -511,12 +532,35 @@ func TestGetManagedClusters(t *testing.T) { } ctx, calls := setupLogCapture(ctx) count := getManagedClusters(ctx, fakeClientWithOptionalError) - assert.Equal(t, len(*calls), 1) + assert.Assert(t, len(*calls) > 0) assert.Assert(t, cmp.Contains((*calls)[0], `upgrade check issue: could not count postgres clusters`)) assert.Assert(t, count == 0) }) } +func TestGetBridgeClusters(t *testing.T) { + ctx := context.Background() + + t.Run("success", func(t *testing.T) { + fakeClient := setupFakeClientWithPGOScheme(t, true) + ctx, calls := setupLogCapture(ctx) + count := getBridgeClusters(ctx, fakeClient) + assert.Equal(t, len(*calls), 0) + assert.Assert(t, count == 2) + }) + + t.Run("list throw error", func(t *testing.T) { + fakeClientWithOptionalError := &fakeClientWithError{ + setupFakeClientWithPGOScheme(t, true), "list error", + } + ctx, calls := setupLogCapture(ctx) + count := getBridgeClusters(ctx, fakeClientWithOptionalError) + assert.Assert(t, len(*calls) > 0) + assert.Assert(t, cmp.Contains((*calls)[0], `upgrade check issue: could not count bridge clusters`)) + assert.Assert(t, count == 0) + }) +} + func TestGetServerVersion(t *testing.T) { t.Run("success", func(t *testing.T) { expect, server := setupVersionServer(t, true) diff --git a/internal/upgradecheck/helpers_test.go b/internal/upgradecheck/helpers_test.go index a9b56796f0..159f1b1d9b 100644 --- a/internal/upgradecheck/helpers_test.go +++ b/internal/upgradecheck/helpers_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package upgradecheck @@ -43,6 +32,7 @@ type fakeClientWithError struct { errorType string } +// Get returns the client.get OR an Error (`get error`) if the fakeClientWithError is set to error that way func (f *fakeClientWithError) Get(ctx context.Context, key types.NamespacedName, obj crclient.Object, opts ...crclient.GetOption) error { switch f.errorType { case "get error": @@ -52,6 +42,7 @@ func (f *fakeClientWithError) Get(ctx context.Context, key types.NamespacedName, } } +// Patch returns the client.get OR an Error (`patch error`) if the fakeClientWithError is set to error that way // TODO: PatchType is not supported currently by fake // - https://github.com/kubernetes/client-go/issues/970 // Once that gets fixed, we can test without envtest @@ -65,6 +56,7 @@ func (f *fakeClientWithError) Patch(ctx context.Context, obj crclient.Object, } } +// List returns the client.get OR an Error (`list error`) if the fakeClientWithError is set to error that way func (f *fakeClientWithError) List(ctx context.Context, objList crclient.ObjectList, opts ...crclient.ListOption) error { switch f.errorType { @@ -75,12 +67,16 @@ func (f *fakeClientWithError) List(ctx context.Context, objList crclient.ObjectL } } +// setupDeploymentID returns a UUID func setupDeploymentID(t *testing.T) string { t.Helper() deploymentID = string(uuid.NewUUID()) return deploymentID } +// setupFakeClientWithPGOScheme returns a fake client with the PGO scheme added; +// if `includeCluster` is true, also adds some empty PostgresCluster and CrunchyBridgeCluster +// items to the client func setupFakeClientWithPGOScheme(t *testing.T, includeCluster bool) crclient.Client { t.Helper() if includeCluster { @@ -98,11 +94,31 @@ func setupFakeClientWithPGOScheme(t *testing.T, includeCluster bool) crclient.Cl }, }, } - return fake.NewClientBuilder().WithScheme(runtime.Scheme).WithLists(pc).Build() + + bcl := &v1beta1.CrunchyBridgeClusterList{ + Items: []v1beta1.CrunchyBridgeCluster{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "hippo", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "elephant", + }, + }, + }, + } + + return fake.NewClientBuilder(). + WithScheme(runtime.Scheme). + WithLists(pc, bcl). + Build() } return fake.NewClientBuilder().WithScheme(runtime.Scheme).Build() } +// setupVersionServer sets up and tears down a server and version info for testing func setupVersionServer(t *testing.T, works bool) (version.Info, *httptest.Server) { t.Helper() expect := version.Info{ @@ -127,6 +143,7 @@ func setupVersionServer(t *testing.T, works bool) (version.Info, *httptest.Serve return expect, server } +// setupLogCapture captures the logs and keeps count of the logs captured func setupLogCapture(ctx context.Context) (context.Context, *[]string) { calls := []string{} testlog := funcr.NewJSON(func(object string) { diff --git a/internal/upgradecheck/http.go b/internal/upgradecheck/http.go index 650050fb76..5cb337e149 100644 --- a/internal/upgradecheck/http.go +++ b/internal/upgradecheck/http.go @@ -1,17 +1,6 @@ -/* - Copyright 2017 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2017 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package upgradecheck @@ -22,6 +11,7 @@ import ( "net/http" "time" + "github.com/golang-jwt/jwt/v5" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/rest" crclient "sigs.k8s.io/controller-runtime/pkg/client" @@ -77,7 +67,7 @@ func init() { func checkForUpgrades(ctx context.Context, url, versionString string, backoff wait.Backoff, crclient crclient.Client, cfg *rest.Config, - isOpenShift bool) (message string, header string, err error) { + isOpenShift bool, registrationToken string) (message string, header string, err error) { var headerPayloadStruct *clientUpgradeData // Prep request @@ -86,7 +76,7 @@ func checkForUpgrades(ctx context.Context, url, versionString string, backoff wa // generateHeader always returns some sort of struct, using defaults/nil values // in case some of the checks return errors headerPayloadStruct = generateHeader(ctx, cfg, crclient, - versionString, isOpenShift) + versionString, isOpenShift, registrationToken) req, err = addHeader(req, headerPayloadStruct) } @@ -136,24 +126,37 @@ type CheckForUpgradesScheduler struct { Client crclient.Client Config *rest.Config - OpenShift bool - Refresh time.Duration - URL, Version string + OpenShift bool + Refresh time.Duration + RegistrationToken string + URL, Version string } // ManagedScheduler creates a [CheckForUpgradesScheduler] and adds it to m. -func ManagedScheduler(m manager.Manager, openshift bool, url, version string) error { +// NOTE(registration): This takes a token/nil parameter when the operator is started. +// Currently the operator restarts when the token is updated, +// so this token is always current; but if that restart behavior is changed, +// we will want the upgrade mechanism to instantiate its own registration runner +// or otherwise get the most recent token. +func ManagedScheduler(m manager.Manager, openshift bool, + url, version string, registrationToken *jwt.Token) error { if url == "" { url = upgradeCheckURL } + var token string + if registrationToken != nil { + token = registrationToken.Raw + } + return m.Add(&CheckForUpgradesScheduler{ - Client: m.GetClient(), - Config: m.GetConfig(), - OpenShift: openshift, - Refresh: 24 * time.Hour, - URL: url, - Version: version, + Client: m.GetClient(), + Config: m.GetConfig(), + OpenShift: openshift, + Refresh: 24 * time.Hour, + RegistrationToken: token, + URL: url, + Version: version, }) } @@ -188,7 +191,7 @@ func (s *CheckForUpgradesScheduler) check(ctx context.Context) { }() info, header, err := checkForUpgrades(ctx, - s.URL, s.Version, backoff, s.Client, s.Config, s.OpenShift) + s.URL, s.Version, backoff, s.Client, s.Config, s.OpenShift, s.RegistrationToken) if err != nil { log.V(1).Info("could not complete upgrade check", "response", err.Error()) diff --git a/internal/upgradecheck/http_test.go b/internal/upgradecheck/http_test.go index c0b2b6c651..c0391e1cfa 100644 --- a/internal/upgradecheck/http_test.go +++ b/internal/upgradecheck/http_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package upgradecheck @@ -32,6 +21,7 @@ import ( "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/manager" + "github.com/percona/percona-postgresql-operator/internal/feature" "github.com/percona/percona-postgresql-operator/internal/logging" "github.com/percona/percona-postgresql-operator/internal/testing/cmp" ) @@ -58,10 +48,16 @@ func (m *MockClient) Do(req *http.Request) (*http.Response, error) { } func TestCheckForUpgrades(t *testing.T) { - fakeClient := setupFakeClientWithPGOScheme(t, false) - ctx := logging.NewContext(context.Background(), logging.Discard()) + fakeClient := setupFakeClientWithPGOScheme(t, true) cfg := &rest.Config{} + ctx := logging.NewContext(context.Background(), logging.Discard()) + gate := feature.NewGate() + assert.NilError(t, gate.SetFromMap(map[string]bool{ + feature.TablespaceVolumes: true, + })) + ctx = feature.NewContext(ctx, gate) + // Pass *testing.T to allows the correct messages from the assert package // in the event of certain failures. checkData := func(t *testing.T, header string) { @@ -70,6 +66,10 @@ func TestCheckForUpgrades(t *testing.T) { assert.NilError(t, err) assert.Assert(t, data.DeploymentID != "") assert.Equal(t, data.PGOVersion, "4.7.3") + assert.Equal(t, data.RegistrationToken, "speakFriend") + assert.Equal(t, data.BridgeClustersTotal, 2) + assert.Equal(t, data.PGOClustersTotal, 2) + assert.Equal(t, data.FeatureGatesEnabled, "TablespaceVolumes=true") } t.Run("success", func(t *testing.T) { @@ -83,7 +83,7 @@ func TestCheckForUpgrades(t *testing.T) { } res, header, err := checkForUpgrades(ctx, "", "4.7.3", backoff, - fakeClient, cfg, false) + fakeClient, cfg, false, "speakFriend") assert.NilError(t, err) assert.Equal(t, res, `{"pgo_versions":[{"tag":"v5.0.4"},{"tag":"v5.0.3"},{"tag":"v5.0.2"},{"tag":"v5.0.1"},{"tag":"v5.0.0"}]}`) checkData(t, header) @@ -98,7 +98,7 @@ func TestCheckForUpgrades(t *testing.T) { } res, header, err := checkForUpgrades(ctx, "", "4.7.3", backoff, - fakeClient, cfg, false) + fakeClient, cfg, false, "speakFriend") // Two failed calls because of env var assert.Equal(t, counter, 2) assert.Equal(t, res, "") @@ -118,7 +118,7 @@ func TestCheckForUpgrades(t *testing.T) { } res, header, err := checkForUpgrades(ctx, "", "4.7.3", backoff, - fakeClient, cfg, false) + fakeClient, cfg, false, "speakFriend") assert.Equal(t, res, "") // Two failed calls because of env var assert.Equal(t, counter, 2) @@ -147,7 +147,7 @@ func TestCheckForUpgrades(t *testing.T) { } res, header, err := checkForUpgrades(ctx, "", "4.7.3", backoff, - fakeClient, cfg, false) + fakeClient, cfg, false, "speakFriend") assert.Equal(t, counter, 2) assert.NilError(t, err) assert.Equal(t, res, `{"pgo_versions":[{"tag":"v5.0.4"},{"tag":"v5.0.3"},{"tag":"v5.0.2"},{"tag":"v5.0.1"},{"tag":"v5.0.0"}]}`) diff --git a/internal/util/secrets.go b/internal/util/secrets.go index 203f6bcfea..82768c9386 100644 --- a/internal/util/secrets.go +++ b/internal/util/secrets.go @@ -1,17 +1,6 @@ -/* - Copyright 2017 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2017 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package util diff --git a/internal/util/secrets_test.go b/internal/util/secrets_test.go index 39538d7368..5d549ca89e 100644 --- a/internal/util/secrets_test.go +++ b/internal/util/secrets_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package util diff --git a/internal/util/util.go b/internal/util/util.go index 2199b584fd..72634ebbc6 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -1,17 +1,6 @@ -/* - Copyright 2017 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2017 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package util diff --git a/pkg/apis/pgv2.percona.com/v2/zz_generated.deepcopy.go b/pkg/apis/pgv2.percona.com/v2/zz_generated.deepcopy.go index 1468283730..2b4951b9cc 100644 --- a/pkg/apis/pgv2.percona.com/v2/zz_generated.deepcopy.go +++ b/pkg/apis/pgv2.percona.com/v2/zz_generated.deepcopy.go @@ -1,19 +1,8 @@ //go:build !ignore_autogenerated -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 // Code generated by controller-gen. DO NOT EDIT. diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/crunchy_bridgecluster_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/crunchy_bridgecluster_types.go index c72ca07471..801e75f51d 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/crunchy_bridgecluster_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/crunchy_bridgecluster_types.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package v1beta1 @@ -53,10 +42,10 @@ type CrunchyBridgeClusterSpec struct { Plan string `json:"plan"` // The ID of the cluster's major Postgres version. - // Currently Bridge offers 13-16 + // Currently Bridge offers 13-17 // +kubebuilder:validation:Required // +kubebuilder:validation:Minimum=13 - // +kubebuilder:validation:Maximum=16 + // +kubebuilder:validation:Maximum=17 // +operator-sdk:csv:customresourcedefinitions:type=spec,order=1 PostgresVersion int `json:"majorVersion"` diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/groupversion_info.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/groupversion_info.go index 0c8e247bbd..15773a1815 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/groupversion_info.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/groupversion_info.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 // Package v1beta1 contains API Schema definitions for the postgres-operator v1beta1 API group // +kubebuilder:object:generate=true diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/patroni_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/patroni_types.go index 6788ef8b1a..4fb4723ebd 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/patroni_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/patroni_types.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package v1beta1 diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgadmin_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgadmin_types.go index 6f83b713c9..06c7321bc4 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgadmin_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgadmin_types.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package v1beta1 diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgbackrest_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgbackrest_types.go index f34bf223ca..3b578fb4d2 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgbackrest_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgbackrest_types.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package v1beta1 diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgbouncer_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgbouncer_types.go index c45971bbd0..9119cd6fe3 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgbouncer_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgbouncer_types.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package v1beta1 diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgmonitor_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgmonitor_types.go index 000ea72ba0..f2cd78335a 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgmonitor_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgmonitor_types.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package v1beta1 diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgupgrade_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgupgrade_types.go index 3a6494c5f0..90aca7b0b1 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgupgrade_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgupgrade_types.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package v1beta1 @@ -59,7 +49,7 @@ type PGUpgradeSpec struct { // The major version of PostgreSQL before the upgrade. // +kubebuilder:validation:Required // +kubebuilder:validation:Minimum=10 - // +kubebuilder:validation:Maximum=16 + // +kubebuilder:validation:Maximum=17 FromPostgresVersion int `json:"fromPostgresVersion"` // TODO(benjaminjb): define webhook validation to make sure @@ -70,7 +60,7 @@ type PGUpgradeSpec struct { // The major version of PostgreSQL to be upgraded to. // +kubebuilder:validation:Required // +kubebuilder:validation:Minimum=10 - // +kubebuilder:validation:Maximum=16 + // +kubebuilder:validation:Maximum=17 ToPostgresVersion int `json:"toPostgresVersion"` // The image name to use for PostgreSQL containers after upgrade. diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgres_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgres_types.go index f42105b926..562cb1f0f3 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgres_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgres_types.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package v1beta1 diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_test.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_test.go index befbd25992..d9e485f1c6 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_test.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package v1beta1 diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go index 943a26dc25..620b0a39fc 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package v1beta1 @@ -131,8 +120,8 @@ type PostgresClusterSpec struct { // The major version of PostgreSQL installed in the PostgreSQL image // +kubebuilder:validation:Required - // +kubebuilder:validation:Minimum=12 - // +kubebuilder:validation:Maximum=16 + // +kubebuilder:validation:Minimum=10 + // +kubebuilder:validation:Maximum=17 // +operator-sdk:csv:customresourcedefinitions:type=spec,order=1 PostgresVersion int `json:"postgresVersion"` @@ -351,8 +340,12 @@ func (s *PostgresClusterSpec) Default() { // Backups defines a PostgreSQL archive configuration type Backups struct { // pgBackRest archive configuration - // +kubebuilder:validation:Required + // +optional PGBackRest PGBackRestArchive `json:"pgbackrest"` + + // VolumeSnapshot configuration + // +optional + Snapshots *VolumeSnapshots `json:"snapshots,omitempty"` } // PostgresClusterStatus defines the observed state of PostgresCluster @@ -730,6 +723,14 @@ type MonitoringStatus struct { ExporterConfiguration string `json:"exporterConfiguration,omitempty"` } +// VolumeSnapshots defines the configuration for VolumeSnapshots +type VolumeSnapshots struct { + // Name of the VolumeSnapshotClass that should be used by VolumeSnapshots + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + VolumeSnapshotClassName string `json:"volumeSnapshotClassName"` +} + func NewPostgresCluster() *PostgresCluster { cluster := &PostgresCluster{} cluster.SetGroupVersionKind(GroupVersion.WithKind("PostgresCluster")) diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/shared_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/shared_types.go index a49624b5b9..e73ee6e7f6 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/shared_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/shared_types.go @@ -1,17 +1,6 @@ -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package v1beta1 diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/shared_types_test.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/shared_types_test.go index cc5749e9ec..96cd4da073 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/shared_types_test.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/shared_types_test.go @@ -1,17 +1,6 @@ -/* - Copyright 2022 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2022 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 package v1beta1 diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/standalone_pgadmin_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/standalone_pgadmin_types.go index 9b64476b64..4fbc90a3b9 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/standalone_pgadmin_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/standalone_pgadmin_types.go @@ -1,16 +1,6 @@ // Copyright 2023 - 2024 Crunchy Data Solutions, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 package v1beta1 diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/zz_generated.deepcopy.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/zz_generated.deepcopy.go index 5861d9f02b..9b2b364be9 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/zz_generated.deepcopy.go @@ -1,19 +1,8 @@ //go:build !ignore_autogenerated -/* - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 // Code generated by controller-gen. DO NOT EDIT. @@ -91,6 +80,11 @@ func (in *BackupJobs) DeepCopy() *BackupJobs { func (in *Backups) DeepCopyInto(out *Backups) { *out = *in in.PGBackRest.DeepCopyInto(&out.PGBackRest) + if in.Snapshots != nil { + in, out := &in.Snapshots, &out.Snapshots + *out = new(VolumeSnapshots) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backups. @@ -2411,3 +2405,18 @@ func (in *UserInterfaceSpec) DeepCopy() *UserInterfaceSpec { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeSnapshots) DeepCopyInto(out *VolumeSnapshots) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeSnapshots. +func (in *VolumeSnapshots) DeepCopy() *VolumeSnapshots { + if in == nil { + return nil + } + out := new(VolumeSnapshots) + in.DeepCopyInto(out) + return out +} diff --git a/testing/kuttl/e2e/cluster-pause/files/01-cluster-paused.yaml b/testing/kuttl/e2e/cluster-pause/files/01-cluster-paused.yaml index ecd459d3e1..6776fc542b 100644 --- a/testing/kuttl/e2e/cluster-pause/files/01-cluster-paused.yaml +++ b/testing/kuttl/e2e/cluster-pause/files/01-cluster-paused.yaml @@ -4,18 +4,6 @@ metadata: name: cluster-pause status: conditions: - - message: pgBackRest dedicated repository host is ready - reason: RepoHostReady - status: "True" - type: PGBackRestRepoHostReady - - message: pgBackRest replica create repo is ready for backups - reason: StanzaCreated - status: "True" - type: PGBackRestReplicaRepoReady - - message: pgBackRest replica creation is now possible - reason: RepoBackupComplete - status: "True" - type: PGBackRestReplicaCreate - message: No spec changes will be applied and no other statuses will be updated. reason: Paused status: "False" diff --git a/testing/kuttl/e2e/cluster-pause/files/02-cluster-resumed.yaml b/testing/kuttl/e2e/cluster-pause/files/02-cluster-resumed.yaml index 1c90fe5f22..82062fb908 100644 --- a/testing/kuttl/e2e/cluster-pause/files/02-cluster-resumed.yaml +++ b/testing/kuttl/e2e/cluster-pause/files/02-cluster-resumed.yaml @@ -3,19 +3,6 @@ kind: PostgresCluster metadata: name: cluster-pause status: - conditions: - - message: pgBackRest dedicated repository host is ready - reason: RepoHostReady - status: "True" - type: PGBackRestRepoHostReady - - message: pgBackRest replica create repo is ready for backups - reason: StanzaCreated - status: "True" - type: PGBackRestReplicaRepoReady - - message: pgBackRest replica creation is now possible - reason: RepoBackupComplete - status: "True" - type: PGBackRestReplicaCreate instances: - name: instance1 readyReplicas: 1 diff --git a/testing/kuttl/e2e/cluster-start/files/00-cluster-created.yaml b/testing/kuttl/e2e/cluster-start/files/00-cluster-created.yaml index ecc6ab7fe8..4eebece89e 100644 --- a/testing/kuttl/e2e/cluster-start/files/00-cluster-created.yaml +++ b/testing/kuttl/e2e/cluster-start/files/00-cluster-created.yaml @@ -9,15 +9,6 @@ status: replicas: 1 updatedReplicas: 1 --- -apiVersion: batch/v1 -kind: Job -metadata: - labels: - postgres-operator.crunchydata.com/cluster: cluster-start - postgres-operator.crunchydata.com/pgbackrest-backup: replica-create -status: - succeeded: 1 ---- apiVersion: v1 kind: Service metadata: diff --git a/testing/kuttl/e2e/cluster-start/files/00-create-cluster.yaml b/testing/kuttl/e2e/cluster-start/files/00-create-cluster.yaml index a870d940f1..713cd14eb3 100644 --- a/testing/kuttl/e2e/cluster-start/files/00-create-cluster.yaml +++ b/testing/kuttl/e2e/cluster-start/files/00-create-cluster.yaml @@ -12,14 +12,3 @@ spec: resources: requests: storage: 1Gi - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: - accessModes: - - "ReadWriteOnce" - resources: - requests: - storage: 1Gi diff --git a/testing/kuttl/e2e/exporter-custom-queries/files/exporter-custom-queries-cluster.yaml b/testing/kuttl/e2e/exporter-custom-queries/files/exporter-custom-queries-cluster.yaml index 6ff8ed5e67..5356b83be9 100644 --- a/testing/kuttl/e2e/exporter-custom-queries/files/exporter-custom-queries-cluster.yaml +++ b/testing/kuttl/e2e/exporter-custom-queries/files/exporter-custom-queries-cluster.yaml @@ -7,12 +7,6 @@ spec: instances: - name: instance1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } monitoring: pgmonitor: exporter: diff --git a/testing/kuttl/e2e/exporter-no-tls/files/exporter-no-tls-cluster.yaml b/testing/kuttl/e2e/exporter-no-tls/files/exporter-no-tls-cluster.yaml index 9cc6ec4877..690d5b505d 100644 --- a/testing/kuttl/e2e/exporter-no-tls/files/exporter-no-tls-cluster.yaml +++ b/testing/kuttl/e2e/exporter-no-tls/files/exporter-no-tls-cluster.yaml @@ -7,12 +7,6 @@ spec: instances: - name: instance1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } monitoring: pgmonitor: exporter: {} diff --git a/testing/kuttl/e2e/exporter-password-change/files/initial-postgrescluster.yaml b/testing/kuttl/e2e/exporter-password-change/files/initial-postgrescluster.yaml index e3fbb7b94a..d16c898ac2 100644 --- a/testing/kuttl/e2e/exporter-password-change/files/initial-postgrescluster.yaml +++ b/testing/kuttl/e2e/exporter-password-change/files/initial-postgrescluster.yaml @@ -7,12 +7,6 @@ spec: instances: - name: instance1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } monitoring: pgmonitor: exporter: {} diff --git a/testing/kuttl/e2e/exporter-tls/files/exporter-tls-cluster.yaml b/testing/kuttl/e2e/exporter-tls/files/exporter-tls-cluster.yaml index d445062bf3..4fa420664a 100644 --- a/testing/kuttl/e2e/exporter-tls/files/exporter-tls-cluster.yaml +++ b/testing/kuttl/e2e/exporter-tls/files/exporter-tls-cluster.yaml @@ -7,12 +7,6 @@ spec: instances: - name: instance1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } monitoring: pgmonitor: exporter: diff --git a/testing/kuttl/e2e/optional-backups/00--cluster.yaml b/testing/kuttl/e2e/optional-backups/00--cluster.yaml new file mode 100644 index 0000000000..7b927831e0 --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/00--cluster.yaml @@ -0,0 +1,15 @@ +apiVersion: postgres-operator.crunchydata.com/v1beta1 +kind: PostgresCluster +metadata: + name: created-without-backups +spec: + postgresVersion: ${KUTTL_PG_VERSION} + instances: + - name: instance1 + dataVolumeClaimSpec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: 1Gi + diff --git a/testing/kuttl/e2e/optional-backups/00-assert.yaml b/testing/kuttl/e2e/optional-backups/00-assert.yaml new file mode 100644 index 0000000000..86392d0308 --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/00-assert.yaml @@ -0,0 +1,38 @@ +apiVersion: postgres-operator.crunchydata.com/v1beta1 +kind: PostgresCluster +metadata: + name: created-without-backups +status: + instances: + - name: instance1 + pgbackrest: {} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + postgres-operator.crunchydata.com/cluster: created-without-backups + postgres-operator.crunchydata.com/data: postgres + postgres-operator.crunchydata.com/instance-set: instance1 + postgres-operator.crunchydata.com/role: pgdata +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + labels: + postgres-operator.crunchydata.com/cluster: created-without-backups + postgres-operator.crunchydata.com/data: postgres + postgres-operator.crunchydata.com/instance-set: instance1 +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + postgres-operator.crunchydata.com/cluster: created-without-backups + postgres-operator.crunchydata.com/data: postgres + postgres-operator.crunchydata.com/instance-set: instance1 + postgres-operator.crunchydata.com/role: master +status: + containerStatuses: + - ready: true + - ready: true diff --git a/testing/kuttl/e2e/optional-backups/01-errors.yaml b/testing/kuttl/e2e/optional-backups/01-errors.yaml new file mode 100644 index 0000000000..e702fcddb4 --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/01-errors.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: created-without-backups-repo1 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: created-without-backups-repo-host +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: created-without-backups-pgbackrest-config +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: created-without-backups-pgbackrest +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: created-without-backups-pgbackrest +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: created-without-backups-pgbackrest diff --git a/testing/kuttl/e2e/optional-backups/02-assert.yaml b/testing/kuttl/e2e/optional-backups/02-assert.yaml new file mode 100644 index 0000000000..eb3f70357f --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/02-assert.yaml @@ -0,0 +1,15 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: | + pod=$(kubectl get pods -o name -n "${NAMESPACE}" \ + -l postgres-operator.crunchydata.com/cluster=created-without-backups) + + kubectl exec --stdin "${pod}" --namespace "${NAMESPACE}" -c database \ + -- psql -qb --set ON_ERROR_STOP=1 --file=- <<'SQL' + DO $$ + BEGIN + ASSERT current_setting('archive_command') LIKE 'true', + format('expected "true", got %L', current_setting('archive_command')); + END $$ + SQL diff --git a/testing/kuttl/e2e/optional-backups/03-assert.yaml b/testing/kuttl/e2e/optional-backups/03-assert.yaml new file mode 100644 index 0000000000..17ca1e4062 --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/03-assert.yaml @@ -0,0 +1,14 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: | + pod=$(kubectl get pods -o name -n "${NAMESPACE}" \ + -l postgres-operator.crunchydata.com/cluster=created-without-backups) + + kubectl exec --stdin "${pod}" --namespace "${NAMESPACE}" -c database \ + -- psql -qb --set ON_ERROR_STOP=1 \ + -c "CREATE TABLE important (data) AS VALUES ('treasure');" + + kubectl exec --stdin "${pod}" --namespace "${NAMESPACE}" -c database \ + -- psql -qb --set ON_ERROR_STOP=1 \ + -c "CHECKPOINT;" diff --git a/testing/kuttl/e2e/optional-backups/04--cluster.yaml b/testing/kuttl/e2e/optional-backups/04--cluster.yaml new file mode 100644 index 0000000000..fc39ff6ebe --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/04--cluster.yaml @@ -0,0 +1,16 @@ +apiVersion: postgres-operator.crunchydata.com/v1beta1 +kind: PostgresCluster +metadata: + name: created-without-backups +spec: + postgresVersion: ${KUTTL_PG_VERSION} + instances: + - name: instance1 + replicas: 2 + dataVolumeClaimSpec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: 1Gi + diff --git a/testing/kuttl/e2e/optional-backups/05-assert.yaml b/testing/kuttl/e2e/optional-backups/05-assert.yaml new file mode 100644 index 0000000000..d346e01a04 --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/05-assert.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + postgres-operator.crunchydata.com/cluster: created-without-backups + postgres-operator.crunchydata.com/data: postgres + postgres-operator.crunchydata.com/instance-set: instance1 + postgres-operator.crunchydata.com/role: replica +status: + containerStatuses: + - ready: true + - ready: true diff --git a/testing/kuttl/e2e/optional-backups/06-assert.yaml b/testing/kuttl/e2e/optional-backups/06-assert.yaml new file mode 100644 index 0000000000..c366545508 --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/06-assert.yaml @@ -0,0 +1,18 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: | + pod=$(kubectl get pods -o name -n "${NAMESPACE}" \ + -l postgres-operator.crunchydata.com/cluster=created-without-backups \ + -l postgres-operator.crunchydata.com/role=replica) + + kubectl exec --stdin "${pod}" --namespace "${NAMESPACE}" -c database \ + -- psql -qb --set ON_ERROR_STOP=1 --file=- <<'SQL' + DO $$ + DECLARE + everything jsonb; + BEGIN + SELECT jsonb_agg(important) INTO everything FROM important; + ASSERT everything = '[{"data":"treasure"}]', format('got %L', everything); + END $$ + SQL diff --git a/testing/kuttl/e2e/optional-backups/10--cluster.yaml b/testing/kuttl/e2e/optional-backups/10--cluster.yaml new file mode 100644 index 0000000000..6da85c93f9 --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/10--cluster.yaml @@ -0,0 +1,27 @@ +apiVersion: postgres-operator.crunchydata.com/v1beta1 +kind: PostgresCluster +metadata: + name: created-without-backups +spec: + postgresVersion: ${KUTTL_PG_VERSION} + instances: + - name: instance1 + replicas: 1 + dataVolumeClaimSpec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: 1Gi + backups: + pgbackrest: + repos: + - name: repo1 + volume: + volumeClaimSpec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: 1Gi + diff --git a/testing/kuttl/e2e/optional-backups/10-assert.yaml b/testing/kuttl/e2e/optional-backups/10-assert.yaml new file mode 100644 index 0000000000..7b740b310d --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/10-assert.yaml @@ -0,0 +1,79 @@ +# It should be possible to turn backups back on. +apiVersion: postgres-operator.crunchydata.com/v1beta1 +kind: PostgresCluster +metadata: + name: created-without-backups +status: + pgbackrest: + repoHost: + apiVersion: apps/v1 + kind: StatefulSet + ready: true + repos: + - bound: true + name: repo1 + replicaCreateBackupComplete: true + stanzaCreated: true +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + postgres-operator.crunchydata.com/cluster: created-without-backups + postgres-operator.crunchydata.com/data: postgres + postgres-operator.crunchydata.com/instance-set: instance1 + postgres-operator.crunchydata.com/role: pgdata +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + labels: + postgres-operator.crunchydata.com/cluster: created-without-backups + postgres-operator.crunchydata.com/data: postgres + postgres-operator.crunchydata.com/instance-set: instance1 +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: created-without-backups-repo1 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: created-without-backups-repo-host +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: created-without-backups-pgbackrest-config +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: created-without-backups-pgbackrest +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: created-without-backups-pgbackrest +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: created-without-backups-pgbackrest +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + postgres-operator.crunchydata.com/cluster: created-without-backups + postgres-operator.crunchydata.com/data: postgres + postgres-operator.crunchydata.com/instance-set: instance1 + postgres-operator.crunchydata.com/patroni: created-without-backups-ha + postgres-operator.crunchydata.com/role: master +status: + containerStatuses: + - ready: true + - ready: true + - ready: true + - ready: true diff --git a/testing/kuttl/e2e/optional-backups/11-assert.yaml b/testing/kuttl/e2e/optional-backups/11-assert.yaml new file mode 100644 index 0000000000..5976d03f41 --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/11-assert.yaml @@ -0,0 +1,18 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: | + pod=$(kubectl get pods -o name -n "${NAMESPACE}" \ + -l postgres-operator.crunchydata.com/cluster=created-without-backup \ + -l postgres-operator.crunchydata.com/instance-set=instance1 \ + -l postgres-operator.crunchydata.com/patroni=created-without-backups-ha \ + -l postgres-operator.crunchydata.com/role=master) + + kubectl exec --stdin "${pod}" --namespace "${NAMESPACE}" -c database \ + -- psql -qb --set ON_ERROR_STOP=1 --file=- <<'SQL' + DO $$ + BEGIN + ASSERT current_setting('archive_command') LIKE 'pgbackrest --stanza=db archive-push "%p"', + format('expected "pgbackrest --stanza=db archive-push \"%p\"", got %L', current_setting('archive_command')); + END $$ + SQL diff --git a/testing/kuttl/e2e/optional-backups/20--cluster.yaml b/testing/kuttl/e2e/optional-backups/20--cluster.yaml new file mode 100644 index 0000000000..8e0d01cbf8 --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/20--cluster.yaml @@ -0,0 +1,6 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: +- command: |- + kubectl patch postgrescluster created-without-backups --type 'merge' -p '{"spec":{"backups": null}}' + namespaced: true diff --git a/testing/kuttl/e2e/optional-backups/20-assert.yaml b/testing/kuttl/e2e/optional-backups/20-assert.yaml new file mode 100644 index 0000000000..b469e277f8 --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/20-assert.yaml @@ -0,0 +1,63 @@ +# It should be possible to turn backups back on. +apiVersion: postgres-operator.crunchydata.com/v1beta1 +kind: PostgresCluster +metadata: + name: created-without-backups +status: + pgbackrest: + repoHost: + apiVersion: apps/v1 + kind: StatefulSet + ready: true + repos: + - bound: true + name: repo1 + replicaCreateBackupComplete: true + stanzaCreated: true +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + postgres-operator.crunchydata.com/cluster: created-without-backups + postgres-operator.crunchydata.com/data: postgres + postgres-operator.crunchydata.com/instance-set: instance1 + postgres-operator.crunchydata.com/role: pgdata +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + labels: + postgres-operator.crunchydata.com/cluster: created-without-backups + postgres-operator.crunchydata.com/data: postgres + postgres-operator.crunchydata.com/instance-set: instance1 +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: created-without-backups-repo1 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: created-without-backups-repo-host +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: created-without-backups-pgbackrest-config +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: created-without-backups-pgbackrest +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: created-without-backups-pgbackrest +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: created-without-backups-pgbackrest diff --git a/testing/kuttl/e2e/optional-backups/21-assert.yaml b/testing/kuttl/e2e/optional-backups/21-assert.yaml new file mode 100644 index 0000000000..5976d03f41 --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/21-assert.yaml @@ -0,0 +1,18 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: | + pod=$(kubectl get pods -o name -n "${NAMESPACE}" \ + -l postgres-operator.crunchydata.com/cluster=created-without-backup \ + -l postgres-operator.crunchydata.com/instance-set=instance1 \ + -l postgres-operator.crunchydata.com/patroni=created-without-backups-ha \ + -l postgres-operator.crunchydata.com/role=master) + + kubectl exec --stdin "${pod}" --namespace "${NAMESPACE}" -c database \ + -- psql -qb --set ON_ERROR_STOP=1 --file=- <<'SQL' + DO $$ + BEGIN + ASSERT current_setting('archive_command') LIKE 'pgbackrest --stanza=db archive-push "%p"', + format('expected "pgbackrest --stanza=db archive-push \"%p\"", got %L', current_setting('archive_command')); + END $$ + SQL diff --git a/testing/kuttl/e2e/optional-backups/22--cluster.yaml b/testing/kuttl/e2e/optional-backups/22--cluster.yaml new file mode 100644 index 0000000000..2e25309886 --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/22--cluster.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: +- command: kubectl annotate postgrescluster created-without-backups postgres-operator.crunchydata.com/authorizeBackupRemoval="true" + namespaced: true diff --git a/testing/kuttl/e2e/optional-backups/23-assert.yaml b/testing/kuttl/e2e/optional-backups/23-assert.yaml new file mode 100644 index 0000000000..8748ea015c --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/23-assert.yaml @@ -0,0 +1,26 @@ +# It should be possible to turn backups back on. +apiVersion: postgres-operator.crunchydata.com/v1beta1 +kind: PostgresCluster +metadata: + name: created-without-backups +status: + instances: + - name: instance1 + pgbackrest: {} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + postgres-operator.crunchydata.com/cluster: created-without-backups + postgres-operator.crunchydata.com/data: postgres + postgres-operator.crunchydata.com/instance-set: instance1 + postgres-operator.crunchydata.com/role: pgdata +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + labels: + postgres-operator.crunchydata.com/cluster: created-without-backups + postgres-operator.crunchydata.com/data: postgres + postgres-operator.crunchydata.com/instance-set: instance1 diff --git a/testing/kuttl/e2e/optional-backups/24-errors.yaml b/testing/kuttl/e2e/optional-backups/24-errors.yaml new file mode 100644 index 0000000000..e702fcddb4 --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/24-errors.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: created-without-backups-repo1 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: created-without-backups-repo-host +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: created-without-backups-pgbackrest-config +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: created-without-backups-pgbackrest +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: created-without-backups-pgbackrest +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: created-without-backups-pgbackrest diff --git a/testing/kuttl/e2e/optional-backups/25-assert.yaml b/testing/kuttl/e2e/optional-backups/25-assert.yaml new file mode 100644 index 0000000000..eb3f70357f --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/25-assert.yaml @@ -0,0 +1,15 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: | + pod=$(kubectl get pods -o name -n "${NAMESPACE}" \ + -l postgres-operator.crunchydata.com/cluster=created-without-backups) + + kubectl exec --stdin "${pod}" --namespace "${NAMESPACE}" -c database \ + -- psql -qb --set ON_ERROR_STOP=1 --file=- <<'SQL' + DO $$ + BEGIN + ASSERT current_setting('archive_command') LIKE 'true', + format('expected "true", got %L', current_setting('archive_command')); + END $$ + SQL diff --git a/testing/kuttl/e2e/optional-backups/README.md b/testing/kuttl/e2e/optional-backups/README.md new file mode 100644 index 0000000000..92c52d4136 --- /dev/null +++ b/testing/kuttl/e2e/optional-backups/README.md @@ -0,0 +1,13 @@ +## Optional backups + +### Steps + +00-02. Create cluster without backups, check that expected K8s objects do/don't exist, e.g., repo-host sts doesn't exist; check that the archive command is `true` + +03-06. Add data and a replica; check that the data successfully replicates to the replica. + +10-11. Update cluster to add backups, check that expected K8s objects do/don't exist, e.g., repo-host sts exists; check that the archive command is set to the usual + +20-21. Update cluster to remove backups but without annotation, check that no changes were made, including to the archive command + +22-25. Annotate cluster to remove existing backups, check that expected K8s objects do/don't exist, e.g., repo-host sts doesn't exist; check that the archive command is `true` diff --git a/testing/kuttl/e2e/password-change/00--cluster.yaml b/testing/kuttl/e2e/password-change/00--cluster.yaml index 2777286880..d7b7019b62 100644 --- a/testing/kuttl/e2e/password-change/00--cluster.yaml +++ b/testing/kuttl/e2e/password-change/00--cluster.yaml @@ -12,14 +12,3 @@ spec: resources: requests: storage: 1Gi - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: - accessModes: - - "ReadWriteOnce" - resources: - requests: - storage: 1Gi diff --git a/testing/kuttl/e2e/pgadmin/01--cluster.yaml b/testing/kuttl/e2e/pgadmin/01--cluster.yaml index 2cc932c463..d1afb7be04 100644 --- a/testing/kuttl/e2e/pgadmin/01--cluster.yaml +++ b/testing/kuttl/e2e/pgadmin/01--cluster.yaml @@ -25,12 +25,6 @@ spec: - name: instance1 replicas: 1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } userInterface: pgAdmin: dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } diff --git a/testing/kuttl/e2e/pgbouncer/00--cluster.yaml b/testing/kuttl/e2e/pgbouncer/00--cluster.yaml index c83bfea9d3..4699d90171 100644 --- a/testing/kuttl/e2e/pgbouncer/00--cluster.yaml +++ b/testing/kuttl/e2e/pgbouncer/00--cluster.yaml @@ -7,14 +7,8 @@ spec: postgresVersion: ${KUTTL_PG_VERSION} instances: - name: instance1 - replicas: 2 + replicas: 1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } proxy: pgBouncer: replicas: 1 diff --git a/testing/kuttl/e2e/pgbouncer/00-assert.yaml b/testing/kuttl/e2e/pgbouncer/00-assert.yaml index afe492faa0..6c3a33079f 100644 --- a/testing/kuttl/e2e/pgbouncer/00-assert.yaml +++ b/testing/kuttl/e2e/pgbouncer/00-assert.yaml @@ -5,9 +5,9 @@ metadata: status: instances: - name: instance1 - readyReplicas: 2 - replicas: 2 - updatedReplicas: 2 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 --- apiVersion: v1 kind: Service diff --git a/testing/kuttl/e2e/replica-read/00--cluster.yaml b/testing/kuttl/e2e/replica-read/00--cluster.yaml index a79666f4e1..c62f5418cd 100644 --- a/testing/kuttl/e2e/replica-read/00--cluster.yaml +++ b/testing/kuttl/e2e/replica-read/00--cluster.yaml @@ -13,14 +13,3 @@ spec: requests: storage: 1Gi replicas: 2 - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: - accessModes: - - "ReadWriteOnce" - resources: - requests: - storage: 1Gi diff --git a/testing/kuttl/e2e/root-cert-ownership/00--cluster.yaml b/testing/kuttl/e2e/root-cert-ownership/00--cluster.yaml index 461ae7ccba..2d23e1e3d3 100644 --- a/testing/kuttl/e2e/root-cert-ownership/00--cluster.yaml +++ b/testing/kuttl/e2e/root-cert-ownership/00--cluster.yaml @@ -9,12 +9,6 @@ spec: - name: instance1 replicas: 1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } --- apiVersion: postgres-operator.crunchydata.com/v1beta1 kind: PostgresCluster @@ -27,9 +21,3 @@ spec: - name: instance1 replicas: 1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } diff --git a/testing/kuttl/e2e/standalone-pgadmin-db-uri/files/00-cluster.yaml b/testing/kuttl/e2e/standalone-pgadmin-db-uri/files/00-cluster.yaml index a3b349844a..5f8678e5e9 100644 --- a/testing/kuttl/e2e/standalone-pgadmin-db-uri/files/00-cluster.yaml +++ b/testing/kuttl/e2e/standalone-pgadmin-db-uri/files/00-cluster.yaml @@ -9,9 +9,3 @@ spec: instances: - name: instance1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } diff --git a/testing/kuttl/e2e/standalone-pgadmin/00-assert.yaml b/testing/kuttl/e2e/standalone-pgadmin/00-assert.yaml new file mode 100644 index 0000000000..5b95b46964 --- /dev/null +++ b/testing/kuttl/e2e/standalone-pgadmin/00-assert.yaml @@ -0,0 +1,7 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +collectors: +- type: command + command: kubectl -n $NAMESPACE describe pods --selector postgres-operator.crunchydata.com/pgadmin=pgadmin +- namespace: $NAMESPACE + selector: postgres-operator.crunchydata.com/pgadmin=pgadmin diff --git a/testing/kuttl/e2e/standalone-pgadmin/files/02-cluster.yaml b/testing/kuttl/e2e/standalone-pgadmin/files/02-cluster.yaml index c1280caa01..d37cf895a2 100644 --- a/testing/kuttl/e2e/standalone-pgadmin/files/02-cluster.yaml +++ b/testing/kuttl/e2e/standalone-pgadmin/files/02-cluster.yaml @@ -9,9 +9,3 @@ spec: instances: - name: instance1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } diff --git a/testing/kuttl/e2e/standalone-pgadmin/files/02-pgadmin.yaml b/testing/kuttl/e2e/standalone-pgadmin/files/02-pgadmin.yaml index 953150b7fa..7ad3b0c4d3 100644 --- a/testing/kuttl/e2e/standalone-pgadmin/files/02-pgadmin.yaml +++ b/testing/kuttl/e2e/standalone-pgadmin/files/02-pgadmin.yaml @@ -3,7 +3,6 @@ kind: PGAdmin metadata: name: pgadmin spec: - adminUsername: admin@pgo.com dataVolumeClaimSpec: accessModes: - "ReadWriteOnce" diff --git a/testing/kuttl/e2e/standalone-pgadmin/files/04-cluster.yaml b/testing/kuttl/e2e/standalone-pgadmin/files/04-cluster.yaml index 63a44812e1..6ad5844c4a 100644 --- a/testing/kuttl/e2e/standalone-pgadmin/files/04-cluster.yaml +++ b/testing/kuttl/e2e/standalone-pgadmin/files/04-cluster.yaml @@ -9,9 +9,3 @@ spec: instances: - name: instance1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } diff --git a/testing/kuttl/e2e/standalone-pgadmin/files/06-cluster.yaml b/testing/kuttl/e2e/standalone-pgadmin/files/06-cluster.yaml index 40f60cf229..80e11eb957 100644 --- a/testing/kuttl/e2e/standalone-pgadmin/files/06-cluster.yaml +++ b/testing/kuttl/e2e/standalone-pgadmin/files/06-cluster.yaml @@ -9,9 +9,3 @@ spec: instances: - name: instance1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } diff --git a/testing/kuttl/e2e/standalone-pgadmin/files/11-cluster.yaml b/testing/kuttl/e2e/standalone-pgadmin/files/11-cluster.yaml index ec551d6e0f..b11b291d85 100644 --- a/testing/kuttl/e2e/standalone-pgadmin/files/11-cluster.yaml +++ b/testing/kuttl/e2e/standalone-pgadmin/files/11-cluster.yaml @@ -7,9 +7,3 @@ spec: instances: - name: instance1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } diff --git a/testing/kuttl/e2e/streaming-standby/01--primary-cluster.yaml b/testing/kuttl/e2e/streaming-standby/01--primary-cluster.yaml index cd0e05ac15..44d1386b59 100644 --- a/testing/kuttl/e2e/streaming-standby/01--primary-cluster.yaml +++ b/testing/kuttl/e2e/streaming-standby/01--primary-cluster.yaml @@ -11,9 +11,3 @@ spec: instances: - name: instance1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } diff --git a/testing/kuttl/e2e/streaming-standby/03--standby-cluster.yaml b/testing/kuttl/e2e/streaming-standby/03--standby-cluster.yaml index a3c542addb..ebe382041a 100644 --- a/testing/kuttl/e2e/streaming-standby/03--standby-cluster.yaml +++ b/testing/kuttl/e2e/streaming-standby/03--standby-cluster.yaml @@ -14,9 +14,3 @@ spec: instances: - name: instance1 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } diff --git a/testing/kuttl/e2e/switchover/01--cluster.yaml b/testing/kuttl/e2e/switchover/01--cluster.yaml index 4b0d598ff1..4c91dd85ec 100644 --- a/testing/kuttl/e2e/switchover/01--cluster.yaml +++ b/testing/kuttl/e2e/switchover/01--cluster.yaml @@ -12,9 +12,3 @@ spec: instances: - replicas: 2 dataVolumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: { accessModes: [ReadWriteOnce], resources: { requests: { storage: 1Gi } } } diff --git a/testing/kuttl/e2e/tablespace-enabled/00--cluster.yaml b/testing/kuttl/e2e/tablespace-enabled/00--cluster.yaml index edeebeb8bb..ea69a7264f 100644 --- a/testing/kuttl/e2e/tablespace-enabled/00--cluster.yaml +++ b/testing/kuttl/e2e/tablespace-enabled/00--cluster.yaml @@ -39,14 +39,3 @@ spec: resources: requests: storage: 1Gi - backups: - pgbackrest: - repos: - - name: repo1 - volume: - volumeClaimSpec: - accessModes: - - "ReadWriteOnce" - resources: - requests: - storage: 1Gi diff --git a/testing/kuttl/e2e/tablespace-enabled/00-assert.yaml b/testing/kuttl/e2e/tablespace-enabled/00-assert.yaml index 9351766c4f..ad436fc892 100644 --- a/testing/kuttl/e2e/tablespace-enabled/00-assert.yaml +++ b/testing/kuttl/e2e/tablespace-enabled/00-assert.yaml @@ -9,15 +9,6 @@ status: replicas: 1 updatedReplicas: 1 --- -apiVersion: batch/v1 -kind: Job -metadata: - labels: - postgres-operator.crunchydata.com/cluster: tablespace-enabled - postgres-operator.crunchydata.com/pgbackrest-backup: replica-create -status: - succeeded: 1 ---- apiVersion: v1 kind: Service metadata: diff --git a/trivy.yaml b/trivy.yaml new file mode 100644 index 0000000000..b2ef32d785 --- /dev/null +++ b/trivy.yaml @@ -0,0 +1,14 @@ +# https://aquasecurity.github.io/trivy/latest/docs/references/configuration/config-file/ +--- +# Specify an exact list of recognized and acceptable licenses. +# [A GitHub workflow](/.github/workflows/trivy.yaml) rejects pull requests that +# import licenses not in this list. +# +# https://aquasecurity.github.io/trivy/latest/docs/scanner/license/ +license: + ignored: + - Apache-2.0 + - BSD-2-Clause + - BSD-3-Clause + - ISC + - MIT