Merge branch 'master' into gh-pages
This commit is contained in:
commit
3cdcab9e97
|
|
@ -9,7 +9,7 @@ assignees: ''
|
|||
|
||||
Please, answer some short questions which should help us to understand your problem / question better?
|
||||
|
||||
- **Which image of the operator are you using?** e.g. ghcr.io/zalando/postgres-operator:v1.15.1
|
||||
- **Which image of the operator are you using?** e.g. ghcr.io/zalando/postgres-operator:v2.0.0
|
||||
- **Where do you run it - cloud or metal? Kubernetes or OpenShift?** [AWS K8s | GCP ... | Bare Metal K8s]
|
||||
- **Are you running Postgres Operator in production?** [yes | no]
|
||||
- **Type of issue?** [Bug report, question, feature request, etc.]
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@ jobs:
|
|||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25.3"
|
||||
go-version: "^1.26.4"
|
||||
|
||||
- name: Run unit tests
|
||||
run: make deps mocks test
|
||||
run: make test
|
||||
|
||||
- name: Define image name
|
||||
id: image
|
||||
|
|
@ -34,6 +34,12 @@ jobs:
|
|||
OPERATOR_IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${GITHUB_REF/refs\/tags\//}"
|
||||
echo "OPERATOR_IMAGE=$OPERATOR_IMAGE" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Define pooler image name
|
||||
id: image_pooler
|
||||
run: |
|
||||
POOLER_IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/pgbouncer:${GITHUB_REF/refs\/tags\//}"
|
||||
echo "POOLER_IMAGE=$POOLER_IMAGE" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Define UI image name
|
||||
id: image_ui
|
||||
run: |
|
||||
|
|
@ -47,20 +53,20 @@ jobs:
|
|||
echo "BACKUP_IMAGE=$BACKUP_IMAGE" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push multiarch operator image to ghcr
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile
|
||||
|
|
@ -69,8 +75,17 @@ jobs:
|
|||
tags: "${{ steps.image.outputs.OPERATOR_IMAGE }}"
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Build and push multiarch pooler image to ghcr
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: pooler
|
||||
push: true
|
||||
build-args: BASE_IMAGE=alpine:3.22
|
||||
tags: "${{ steps.image_pooler.outputs.POOLER_IMAGE }}"
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Build and push multiarch ui image to ghcr
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: ui
|
||||
push: true
|
||||
|
|
@ -79,10 +94,22 @@ jobs:
|
|||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Build and push multiarch logical-backup image to ghcr
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: logical-backup
|
||||
push: true
|
||||
build-args: BASE_IMAGE=ubuntu:22.04
|
||||
tags: "${{ steps.image_lb.outputs.BACKUP_IMAGE }}"
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Build and push postgres-operator chart to ghcr
|
||||
run: |
|
||||
helm package charts/postgres-operator
|
||||
helm push postgres-operator-*.tgz oci://${{ env.REGISTRY }}/zalando/charts
|
||||
rm -rf postgres-operator-*.tgz
|
||||
|
||||
- name: Build and push postgres-operator-ui chart to ghcr
|
||||
run: |
|
||||
helm package charts/postgres-operator-ui
|
||||
helm push postgres-operator-ui-*.tgz oci://${{ env.REGISTRY }}/zalando/charts
|
||||
rm -rf postgres-operator-ui-*.tgz
|
||||
|
|
|
|||
|
|
@ -11,15 +11,22 @@ jobs:
|
|||
name: End-2-End tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25.3"
|
||||
go-version: "^1.26.4"
|
||||
- name: Make dependencies
|
||||
run: make deps mocks
|
||||
run: make mocks
|
||||
- name: Code generation
|
||||
run: make codegen
|
||||
- name: Run unit tests
|
||||
run: make test
|
||||
- name: Pre-pull heavy images directly on Kind nodes
|
||||
run: |
|
||||
FOR_NODE=$(kind get nodes --name postgres-operator-e2e-tests 2>/dev/null || true)
|
||||
for node in $FOR_NODE; do
|
||||
echo "Pulling Spilo image on node $node..."
|
||||
docker exec "$node" crictl pull ghcr.io/zalando/spilo-18:4.1-p2
|
||||
done
|
||||
- name: Run end-2-end tests
|
||||
run: make e2e
|
||||
|
|
|
|||
|
|
@ -11,20 +11,20 @@ jobs:
|
|||
name: Unit tests and coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25.3"
|
||||
go-version: "^1.26.4"
|
||||
- name: Make dependencies
|
||||
run: make deps mocks
|
||||
run: make mocks
|
||||
- name: Compile
|
||||
run: make linux
|
||||
- name: Run unit tests
|
||||
run: go test -race -covermode atomic -coverprofile=coverage.out ./...
|
||||
- name: Convert coverage to lcov
|
||||
uses: jandelgado/gcov2lcov-action@v1.1.1
|
||||
uses: jandelgado/gcov2lcov-action@v1.2.0
|
||||
- name: Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
uses: coverallsapp/github-action@v2.3.4
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path-to-lcov: coverage.lcov
|
||||
file: coverage.lcov
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ _testmain.go
|
|||
*.test
|
||||
*.prof
|
||||
/vendor/
|
||||
/kubectl-pg/vendor/
|
||||
/build/
|
||||
/docker/build/
|
||||
/github.com/
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 Zalando SE
|
||||
Copyright (c) 2026 Zalando SE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
80
Makefile
80
Makefile
|
|
@ -1,7 +1,8 @@
|
|||
.PHONY: clean local test linux macos mocks docker push e2e
|
||||
.PHONY: clean local test linux macos mocks docker pooler push e2e
|
||||
|
||||
BINARY ?= postgres-operator
|
||||
BUILD_FLAGS ?= -v
|
||||
GOARCH ?= amd64
|
||||
CGO_ENABLED ?= 0
|
||||
ifeq ($(RACE),1)
|
||||
BUILD_FLAGS += -race -a
|
||||
|
|
@ -13,28 +14,34 @@ LDFLAGS ?= -X=main.version=$(VERSION)
|
|||
DOCKERDIR = docker
|
||||
|
||||
BASE_IMAGE ?= alpine:latest
|
||||
IMAGE ?= $(BINARY)
|
||||
IMAGE ?= ghcr.io/zalando/$(BINARY)
|
||||
TAG ?= $(VERSION)
|
||||
GITHEAD = $(shell git rev-parse --short HEAD)
|
||||
GITURL = $(shell git config --get remote.origin.url)
|
||||
GITSTATUS = $(shell git status --porcelain || echo "no changes")
|
||||
SOURCES = cmd/main.go
|
||||
VERSION ?= $(shell git describe --tags --always --dirty)
|
||||
CRD_SOURCES = $(shell find pkg/apis/zalando.org pkg/apis/acid.zalan.do -name '*.go' -not -name '*.deepcopy.go')
|
||||
GENERATED_CRDS = manifests/postgresteam.crd.yaml manifests/postgresql.crd.yaml pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml
|
||||
GENERATED = pkg/apis/zalando.org/v1/zz_generated.deepcopy.go pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go
|
||||
DIRS := cmd pkg
|
||||
PKG := `go list ./... | grep -v /vendor/`
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
DOCKERFILE = DebugDockerfile
|
||||
DEBUG_POSTFIX := -debug-$(shell date hhmmss)
|
||||
DEBUG_POSTFIX := -debug-$(shell date +"%H%M%S")
|
||||
BUILD_FLAGS += -gcflags "-N -l"
|
||||
else
|
||||
DOCKERFILE = Dockerfile
|
||||
endif
|
||||
|
||||
|
||||
ifeq ($(FRESH),1)
|
||||
DEBUG_FRESH=$(shell date +"%H-%M-%S")
|
||||
endif
|
||||
|
||||
SED := $(shell command -v gsed 2>/dev/null || command -v sed)
|
||||
|
||||
ifdef CDP_PULL_REQUEST_NUMBER
|
||||
CDP_TAG := -${CDP_BUILD_VERSION}
|
||||
endif
|
||||
|
|
@ -46,40 +53,64 @@ endif
|
|||
PATH := $(GOPATH)/bin:$(PATH)
|
||||
SHELL := env PATH="$(PATH)" $(SHELL)
|
||||
IMAGE_TAG := $(IMAGE):$(TAG)$(CDP_TAG)$(DEBUG_FRESH)$(DEBUG_POSTFIX)
|
||||
POOLER_TAG := $(IMAGE)/pgbouncer:$(TAG)$(CDP_TAG)$(DEBUG_FRESH)$(DEBUG_POSTFIX)
|
||||
|
||||
default: local
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
rm $(GENERATED)
|
||||
rm $(GENERATED_CRDS)
|
||||
|
||||
local: ${SOURCES}
|
||||
verify:
|
||||
hack/verify-codegen.sh
|
||||
CGO_ENABLED=${CGO_ENABLED} go build -o build/${BINARY} $(LOCAL_BUILD_FLAGS) -ldflags "$(LDFLAGS)" $^
|
||||
|
||||
linux: ${SOURCES}
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=${CGO_ENABLED} go build -o build/linux/${BINARY} ${BUILD_FLAGS} -ldflags "$(LDFLAGS)" $^
|
||||
$(GENERATED): go.mod $(CRD_SOURCES)
|
||||
hack/update-codegen.sh
|
||||
|
||||
macos: ${SOURCES}
|
||||
GOOS=darwin GOARCH=amd64 CGO_ENABLED=${CGO_ENABLED} go build -o build/macos/${BINARY} ${BUILD_FLAGS} -ldflags "$(LDFLAGS)" $^
|
||||
$(GENERATED_CRDS): $(GENERATED)
|
||||
go tool controller-gen crd:crdVersions=v1,allowDangerousTypes=true paths=./pkg/apis/acid.zalan.do/... output:crd:dir=manifests
|
||||
@mv manifests/acid.zalan.do_postgresqls.yaml manifests/postgresql.crd.yaml
|
||||
@# hack to use lowercase kind and listKind
|
||||
@$(SED) -i -e 's/kind: Postgresql/kind: postgresql/' manifests/postgresql.crd.yaml
|
||||
@$(SED) -i -e 's/listKind: PostgresqlList/listKind: postgresqlList/' manifests/postgresql.crd.yaml
|
||||
@hack/adjust_postgresql_crd.sh
|
||||
@mv manifests/acid.zalan.do_operatorconfigurations.yaml manifests/operatorconfiguration.crd.yaml
|
||||
@mv manifests/acid.zalan.do_postgresteams.yaml manifests/postgresteam.crd.yaml
|
||||
@cp manifests/postgresql.crd.yaml pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml
|
||||
@cp manifests/postgresql.crd.yaml charts/postgres-operator/crds/postgresqls.yaml
|
||||
@cp manifests/operatorconfiguration.crd.yaml pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml
|
||||
@cp manifests/operatorconfiguration.crd.yaml charts/postgres-operator/crds/operatorconfigurations.yaml
|
||||
@cp manifests/postgresteam.crd.yaml charts/postgres-operator/crds/postgresteams.yaml
|
||||
|
||||
docker: ${DOCKERDIR}/${DOCKERFILE}
|
||||
local: ${SOURCES} $(GENERATED_CRDS)
|
||||
CGO_ENABLED=${CGO_ENABLED} go build -o build/${BINARY} $(LOCAL_BUILD_FLAGS) -ldflags "$(LDFLAGS)" $(SOURCES)
|
||||
|
||||
wasm: ${SOURCES} $(GENERATED_CRDS)
|
||||
GOOS=wasip1 GOARCH=wasm CGO_ENABLED=${CGO_ENABLED} go build -o build/${BINARY}.wasm ${BUILD_FLAGS} -ldflags "$(LDFLAGS)" $(SOURCES)
|
||||
|
||||
linux: ${SOURCES} $(GENERATED_CRDS)
|
||||
GOOS=linux GOARCH=${GOARCH} CGO_ENABLED=${CGO_ENABLED} go build -o build/linux/${BINARY} ${BUILD_FLAGS} -ldflags "$(LDFLAGS)" $(SOURCES)
|
||||
|
||||
macos: ${SOURCES} $(GENERATED_CRDS)
|
||||
GOOS=darwin GOARCH=${GOARCH} CGO_ENABLED=${CGO_ENABLED} go build -o build/macos/${BINARY} ${BUILD_FLAGS} -ldflags "$(LDFLAGS)" $(SOURCES)
|
||||
|
||||
docker: $(GENERATED_CRDS) ${DOCKERDIR}/${DOCKERFILE}
|
||||
echo `(env)`
|
||||
echo "Tag ${TAG}"
|
||||
echo "Version ${VERSION}"
|
||||
echo "CDP tag ${CDP_TAG}"
|
||||
echo "git describe $(shell git describe --tags --always --dirty)"
|
||||
docker build --rm -t "$(IMAGE_TAG)" -f "${DOCKERDIR}/${DOCKERFILE}" --build-arg VERSION="${VERSION}" --build-arg BASE_IMAGE="${BASE_IMAGE}" .
|
||||
docker buildx build --load --rm -t "$(IMAGE_TAG)" -f "${DOCKERDIR}/${DOCKERFILE}" --build-arg VERSION="${VERSION}" --build-arg BASE_IMAGE="${BASE_IMAGE}" .
|
||||
|
||||
pooler:
|
||||
cd pooler; docker buildx build --load --rm -t "$(POOLER_TAG)" --build-arg VERSION="${VERSION}" --build-arg BASE_IMAGE="${BASE_IMAGE}" .
|
||||
|
||||
indocker-race:
|
||||
docker run --rm -v "${GOPATH}":"${GOPATH}" -e GOPATH="${GOPATH}" -e RACE=1 -w ${PWD} golang:1.25.3 bash -c "make linux"
|
||||
docker run --rm -v "${GOPATH}":"${GOPATH}" -e GOPATH="${GOPATH}" -e RACE=1 -w ${PWD} golang:1.26.4 bash -c "make linux"
|
||||
|
||||
mocks:
|
||||
GO111MODULE=on go generate ./...
|
||||
|
||||
tools:
|
||||
GO111MODULE=on go get k8s.io/client-go@kubernetes-1.32.9
|
||||
GO111MODULE=on go install github.com/golang/mock/mockgen@v1.6.0
|
||||
GO111MODULE=on go mod tidy
|
||||
go generate ./...
|
||||
|
||||
fmt:
|
||||
@gofmt -l -w -s $(DIRS)
|
||||
|
|
@ -88,15 +119,10 @@ vet:
|
|||
@go vet $(PKG)
|
||||
@staticcheck $(PKG)
|
||||
|
||||
deps: tools
|
||||
GO111MODULE=on go mod vendor
|
||||
test: mocks $(GENERATED) $(GENERATED_CRDS)
|
||||
go test ./...
|
||||
|
||||
test:
|
||||
hack/verify-codegen.sh
|
||||
GO111MODULE=on go test ./...
|
||||
codegen: $(GENERATED)
|
||||
|
||||
codegen:
|
||||
hack/update-codegen.sh
|
||||
|
||||
e2e: docker # build operator image to be tested
|
||||
e2e: docker pooler # build operator and pooler images to be tested
|
||||
cd e2e; make e2etest
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -20,22 +20,23 @@ pipelines with no access to Kubernetes API directly, promoting infrastructure as
|
|||
* Pod protection during bootstrap phase and configurable maintenance windows
|
||||
* Restore and cloning Postgres clusters on AWS, GCS and Azure
|
||||
* Additionally logical backups to S3 or GCS bucket can be configured
|
||||
* Standby cluster from S3 or GCS WAL archive
|
||||
* Standby cluster from S3 or GCS WAL archive or remote host
|
||||
* Configurable for non-cloud environments
|
||||
* Basic credential and user management on K8s, eases application deployments
|
||||
* Support for custom TLS certificates
|
||||
* UI to create and edit Postgres cluster manifests
|
||||
* Compatible with OpenShift
|
||||
* Multi-arch support
|
||||
|
||||
### PostgreSQL features
|
||||
|
||||
* Supports PostgreSQL 17, starting from 13+
|
||||
* Supports PostgreSQL 18, starting from 14+
|
||||
* Streaming replication cluster via Patroni
|
||||
* Point-In-Time-Recovery with
|
||||
[pg_basebackup](https://www.postgresql.org/docs/17/app-pgbasebackup.html) /
|
||||
[pg_basebackup](https://www.postgresql.org/docs/18/app-pgbasebackup.html) /
|
||||
[WAL-G](https://github.com/wal-g/wal-g) or [WAL-E](https://github.com/wal-e/wal-e) via [Spilo](https://github.com/zalando/spilo)
|
||||
* Preload libraries: [bg_mon](https://github.com/CyberDem0n/bg_mon),
|
||||
[pg_stat_statements](https://www.postgresql.org/docs/17/pgstatstatements.html),
|
||||
[pg_stat_statements](https://www.postgresql.org/docs/18/pgstatstatements.html),
|
||||
[pgextwlist](https://github.com/dimitri/pgextwlist),
|
||||
[pg_auth_mon](https://github.com/RafiaSabih/pg_auth_mon)
|
||||
* Incl. popular Postgres extensions such as
|
||||
|
|
@ -63,22 +64,21 @@ production for over five years.
|
|||
|
||||
| Release | Postgres versions | K8s versions | Golang |
|
||||
| :-------- | :---------------: | :---------------: | :-----: |
|
||||
| v2.0.0 | 14 → 18 | 1.27+ | 1.26.4 |
|
||||
| v1.15.1 | 13 → 17 | 1.27+ | 1.25.3 |
|
||||
| v1.14.0 | 13 → 17 | 1.27+ | 1.23.4 |
|
||||
| v1.13.0 | 12 → 16 | 1.27+ | 1.22.5 |
|
||||
| v1.12.0 | 11 → 16 | 1.27+ | 1.22.3 |
|
||||
| v1.11.0 | 11 → 16 | 1.27+ | 1.21.7 |
|
||||
| v1.10.1 | 10 → 15 | 1.21+ | 1.19.8 |
|
||||
|
||||
## Getting started
|
||||
|
||||
For a quick first impression follow the instructions of this
|
||||
[tutorial](docs/quickstart.md).
|
||||
|
||||
## Supported setups of Postgres and Applications
|
||||
## Migrating from v1 to v2 operator
|
||||
|
||||

|
||||

|
||||
If you have been using Postgres Operator since v1.x (thank you), make sure you have read the [migration docs](docs/migrate.md) before deploying a v2 operator.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
|
|
|||
|
|
@ -9,4 +9,4 @@ mkdir -p "$team_repo"
|
|||
ln -s "$PWD" "$project_dir"
|
||||
cd "$project_dir"
|
||||
|
||||
make deps clean docker push
|
||||
make clean docker push
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
apiVersion: v2
|
||||
name: postgres-operator-ui
|
||||
version: 1.15.1
|
||||
appVersion: 1.15.1
|
||||
version: 2.0.0
|
||||
appVersion: 2.0.0
|
||||
home: https://github.com/zalando/postgres-operator
|
||||
description: Postgres Operator UI provides a graphical interface for a convenient database-as-a-service user experience
|
||||
keywords:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,32 @@
|
|||
apiVersion: v1
|
||||
entries:
|
||||
postgres-operator-ui:
|
||||
- apiVersion: v2
|
||||
appVersion: 2.0.0
|
||||
created: "2026-07-27T16:59:53.405736+02:00"
|
||||
description: Postgres Operator UI provides a graphical interface for a convenient
|
||||
database-as-a-service user experience
|
||||
digest: 37d2392421822a6dfeed20f3ea40420c52e4e4aaf90354d20aa27175fbad6c29
|
||||
home: https://github.com/zalando/postgres-operator
|
||||
keywords:
|
||||
- postgres
|
||||
- operator
|
||||
- ui
|
||||
- cloud-native
|
||||
- patroni
|
||||
- spilo
|
||||
maintainers:
|
||||
- email: opensource@zalando.de
|
||||
name: Zalando
|
||||
name: postgres-operator-ui
|
||||
sources:
|
||||
- https://github.com/zalando/postgres-operator
|
||||
urls:
|
||||
- postgres-operator-ui-2.0.0.tgz
|
||||
version: 2.0.0
|
||||
- apiVersion: v2
|
||||
appVersion: 1.15.1
|
||||
created: "2025-12-11T12:44:25.470723322+01:00"
|
||||
created: "2026-07-27T16:59:53.405452+02:00"
|
||||
description: Postgres Operator UI provides a graphical interface for a convenient
|
||||
database-as-a-service user experience
|
||||
digest: 4bbb750934366038d692711f924151182b7be131b6822d011f5a4e51cf609482
|
||||
|
|
@ -26,7 +49,7 @@ entries:
|
|||
version: 1.15.1
|
||||
- apiVersion: v2
|
||||
appVersion: 1.14.0
|
||||
created: "2025-12-11T12:44:25.468680645+01:00"
|
||||
created: "2026-07-27T16:59:53.405196+02:00"
|
||||
description: Postgres Operator UI provides a graphical interface for a convenient
|
||||
database-as-a-service user experience
|
||||
digest: e87ed898079a852957a67a4caf3fbd27b9098e413f5d961b7a771a6ae8b3e17c
|
||||
|
|
@ -49,7 +72,7 @@ entries:
|
|||
version: 1.14.0
|
||||
- apiVersion: v2
|
||||
appVersion: 1.13.0
|
||||
created: "2025-12-11T12:44:25.466716836+01:00"
|
||||
created: "2026-07-27T16:59:53.404946+02:00"
|
||||
description: Postgres Operator UI provides a graphical interface for a convenient
|
||||
database-as-a-service user experience
|
||||
digest: e0444e516b50f82002d1a733527813c51759a627cefdd1005cea73659f824ea8
|
||||
|
|
@ -72,7 +95,7 @@ entries:
|
|||
version: 1.13.0
|
||||
- apiVersion: v2
|
||||
appVersion: 1.12.2
|
||||
created: "2025-12-11T12:44:25.464739895+01:00"
|
||||
created: "2026-07-27T16:59:53.404693+02:00"
|
||||
description: Postgres Operator UI provides a graphical interface for a convenient
|
||||
database-as-a-service user experience
|
||||
digest: cbcef400c23ccece27d97369ad629278265c013e0a45c0b7f33e7568a082fedd
|
||||
|
|
@ -95,7 +118,7 @@ entries:
|
|||
version: 1.12.2
|
||||
- apiVersion: v2
|
||||
appVersion: 1.11.0
|
||||
created: "2025-12-11T12:44:25.462698399+01:00"
|
||||
created: "2026-07-27T16:59:53.40439+02:00"
|
||||
description: Postgres Operator UI provides a graphical interface for a convenient
|
||||
database-as-a-service user experience
|
||||
digest: a45f2284045c2a9a79750a36997386444f39b01ac722b17c84b431457577a3a2
|
||||
|
|
@ -116,27 +139,4 @@ entries:
|
|||
urls:
|
||||
- postgres-operator-ui-1.11.0.tgz
|
||||
version: 1.11.0
|
||||
- apiVersion: v2
|
||||
appVersion: 1.10.1
|
||||
created: "2025-12-11T12:44:25.460357063+01:00"
|
||||
description: Postgres Operator UI provides a graphical interface for a convenient
|
||||
database-as-a-service user experience
|
||||
digest: 2e5e7a82aebee519ec57c6243eb8735124aa4585a3a19c66ffd69638fbeb11ce
|
||||
home: https://github.com/zalando/postgres-operator
|
||||
keywords:
|
||||
- postgres
|
||||
- operator
|
||||
- ui
|
||||
- cloud-native
|
||||
- patroni
|
||||
- spilo
|
||||
maintainers:
|
||||
- email: opensource@zalando.de
|
||||
name: Zalando
|
||||
name: postgres-operator-ui
|
||||
sources:
|
||||
- https://github.com/zalando/postgres-operator
|
||||
urls:
|
||||
- postgres-operator-ui-1.10.1.tgz
|
||||
version: 1.10.1
|
||||
generated: "2025-12-11T12:44:25.45732896+01:00"
|
||||
generated: "2026-07-27T16:59:53.404012+02:00"
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -81,14 +81,14 @@ spec:
|
|||
"cost_memory": 0.014375,
|
||||
"free_iops": 3000,
|
||||
"free_throughput": 125,
|
||||
"limit_iops": 16000,
|
||||
"limit_throughput": 1000,
|
||||
"limit_iops": 80000,
|
||||
"limit_throughput": 2000,
|
||||
"postgresql_versions": [
|
||||
"18",
|
||||
"17",
|
||||
"16",
|
||||
"15",
|
||||
"14",
|
||||
"13"
|
||||
]
|
||||
}
|
||||
{{- if .Values.extraEnvs }}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ replicaCount: 1
|
|||
image:
|
||||
registry: ghcr.io
|
||||
repository: zalando/postgres-operator-ui
|
||||
tag: v1.15.1
|
||||
tag: v2.0.0
|
||||
pullPolicy: "IfNotPresent"
|
||||
|
||||
# Optionally specify an array of imagePullSecrets.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
apiVersion: v2
|
||||
name: postgres-operator
|
||||
version: 1.15.1
|
||||
appVersion: 1.15.1
|
||||
version: 2.0.0
|
||||
appVersion: 2.0.0
|
||||
home: https://github.com/zalando/postgres-operator
|
||||
description: Postgres Operator creates and manages PostgreSQL clusters running in Kubernetes
|
||||
keywords:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,70 +1,84 @@
|
|||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: postgresteams.acid.zalan.do
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.17.3
|
||||
labels:
|
||||
app.kubernetes.io/name: postgres-operator
|
||||
name: postgresteams.acid.zalan.do
|
||||
spec:
|
||||
group: acid.zalan.do
|
||||
names:
|
||||
categories:
|
||||
- all
|
||||
kind: PostgresTeam
|
||||
listKind: PostgresTeamList
|
||||
plural: postgresteams
|
||||
singular: postgresteam
|
||||
shortNames:
|
||||
- pgteam
|
||||
categories:
|
||||
- all
|
||||
singular: postgresteam
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: PostgresTeam defines Custom Resource Definition Object for team
|
||||
management.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: PostgresTeamSpec defines the specification for the PostgresTeam
|
||||
TPR.
|
||||
properties:
|
||||
additionalMembers:
|
||||
additionalProperties:
|
||||
description: List of users who will also be added to the Postgres
|
||||
cluster.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
description: Map for teamId and associated additional users
|
||||
type: object
|
||||
additionalSuperuserTeams:
|
||||
additionalProperties:
|
||||
description: List of teams to become Postgres superusers
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
description: Map for teamId and associated additional superuser teams
|
||||
type: object
|
||||
additionalTeams:
|
||||
additionalProperties:
|
||||
description: List of teams whose members will also be added to the
|
||||
Postgres cluster.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
description: Map for teamId and associated additional teams
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- metadata
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- apiVersion
|
||||
- spec
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
enum:
|
||||
- PostgresTeam
|
||||
apiVersion:
|
||||
type: string
|
||||
enum:
|
||||
- acid.zalan.do/v1
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
additionalSuperuserTeams:
|
||||
type: object
|
||||
description: "Map for teamId and associated additional superuser teams"
|
||||
additionalProperties:
|
||||
type: array
|
||||
nullable: true
|
||||
description: "List of teams to become Postgres superusers"
|
||||
items:
|
||||
type: string
|
||||
additionalTeams:
|
||||
type: object
|
||||
description: "Map for teamId and associated additional teams"
|
||||
additionalProperties:
|
||||
type: array
|
||||
nullable: true
|
||||
description: "List of teams whose members will also be added to the Postgres cluster"
|
||||
items:
|
||||
type: string
|
||||
additionalMembers:
|
||||
type: object
|
||||
description: "Map for teamId and associated additional users"
|
||||
additionalProperties:
|
||||
type: array
|
||||
nullable: true
|
||||
description: "List of users who will also be added to the Postgres cluster"
|
||||
items:
|
||||
type: string
|
||||
|
|
|
|||
|
|
@ -1,9 +1,31 @@
|
|||
apiVersion: v1
|
||||
entries:
|
||||
postgres-operator:
|
||||
- apiVersion: v2
|
||||
appVersion: 2.0.0
|
||||
created: "2026-07-27T16:59:31.277476+02:00"
|
||||
description: Postgres Operator creates and manages PostgreSQL clusters running
|
||||
in Kubernetes
|
||||
digest: 488ae36a71c075ebee4b759f52db31029d559cab20efb329cf151f206087deaf
|
||||
home: https://github.com/zalando/postgres-operator
|
||||
keywords:
|
||||
- postgres
|
||||
- operator
|
||||
- cloud-native
|
||||
- patroni
|
||||
- spilo
|
||||
maintainers:
|
||||
- email: opensource@zalando.de
|
||||
name: Zalando
|
||||
name: postgres-operator
|
||||
sources:
|
||||
- https://github.com/zalando/postgres-operator
|
||||
urls:
|
||||
- postgres-operator-2.0.0.tgz
|
||||
version: 2.0.0
|
||||
- apiVersion: v2
|
||||
appVersion: 1.15.1
|
||||
created: "2025-12-17T14:48:33.832345061+01:00"
|
||||
created: "2026-07-27T16:59:31.276117+02:00"
|
||||
description: Postgres Operator creates and manages PostgreSQL clusters running
|
||||
in Kubernetes
|
||||
digest: 9f3edc3d796105c02c04eaae28a78e58fb08c1847a9de012245fd6ac2c0d2c00
|
||||
|
|
@ -25,7 +47,7 @@ entries:
|
|||
version: 1.15.1
|
||||
- apiVersion: v2
|
||||
appVersion: 1.15.0
|
||||
created: "2025-12-17T14:48:33.826117296+01:00"
|
||||
created: "2026-07-27T16:59:31.275355+02:00"
|
||||
description: Postgres Operator creates and manages PostgreSQL clusters running
|
||||
in Kubernetes
|
||||
digest: 002dd47647bf51fbba023bd1762d807be478cf37de7a44b80cd01ac1f20bd94a
|
||||
|
|
@ -47,7 +69,7 @@ entries:
|
|||
version: 1.15.0
|
||||
- apiVersion: v2
|
||||
appVersion: 1.14.0
|
||||
created: "2025-12-17T14:48:33.819729144+01:00"
|
||||
created: "2026-07-27T16:59:31.274572+02:00"
|
||||
description: Postgres Operator creates and manages PostgreSQL clusters running
|
||||
in Kubernetes
|
||||
digest: 36e1571f3f455b213f16cdda7b1158648e8e84deb804ba47ed6b9b6d19263ba8
|
||||
|
|
@ -69,7 +91,7 @@ entries:
|
|||
version: 1.14.0
|
||||
- apiVersion: v2
|
||||
appVersion: 1.13.0
|
||||
created: "2025-12-17T14:48:33.81038602+01:00"
|
||||
created: "2026-07-27T16:59:31.273293+02:00"
|
||||
description: Postgres Operator creates and manages PostgreSQL clusters running
|
||||
in Kubernetes
|
||||
digest: a839601689aea0a7e6bc0712a5244d435683cf3314c95794097ff08540e1dfef
|
||||
|
|
@ -91,7 +113,7 @@ entries:
|
|||
version: 1.13.0
|
||||
- apiVersion: v2
|
||||
appVersion: 1.12.2
|
||||
created: "2025-12-17T14:48:33.803256825+01:00"
|
||||
created: "2026-07-27T16:59:31.272505+02:00"
|
||||
description: Postgres Operator creates and manages PostgreSQL clusters running
|
||||
in Kubernetes
|
||||
digest: 65858d14a40d7fd90c32bd9fc60021acc9555c161079f43a365c70171eaf21d8
|
||||
|
|
@ -113,7 +135,7 @@ entries:
|
|||
version: 1.12.2
|
||||
- apiVersion: v2
|
||||
appVersion: 1.11.0
|
||||
created: "2025-12-17T14:48:33.797369053+01:00"
|
||||
created: "2026-07-27T16:59:31.271511+02:00"
|
||||
description: Postgres Operator creates and manages PostgreSQL clusters running
|
||||
in Kubernetes
|
||||
digest: 3914b5e117bda0834f05c9207f007e2ac372864cf6e86dcc2e1362bbe46c14d9
|
||||
|
|
@ -133,26 +155,4 @@ entries:
|
|||
urls:
|
||||
- postgres-operator-1.11.0.tgz
|
||||
version: 1.11.0
|
||||
- apiVersion: v2
|
||||
appVersion: 1.10.1
|
||||
created: "2025-12-17T14:48:33.791368349+01:00"
|
||||
description: Postgres Operator creates and manages PostgreSQL clusters running
|
||||
in Kubernetes
|
||||
digest: cc3baa41753da92466223d0b334df27e79c882296577b404a8e9071411fcf19c
|
||||
home: https://github.com/zalando/postgres-operator
|
||||
keywords:
|
||||
- postgres
|
||||
- operator
|
||||
- cloud-native
|
||||
- patroni
|
||||
- spilo
|
||||
maintainers:
|
||||
- email: opensource@zalando.de
|
||||
name: Zalando
|
||||
name: postgres-operator
|
||||
sources:
|
||||
- https://github.com/zalando/postgres-operator
|
||||
urls:
|
||||
- postgres-operator-1.10.1.tgz
|
||||
version: 1.10.1
|
||||
generated: "2025-12-17T14:48:33.785159183+01:00"
|
||||
generated: "2026-07-27T16:59:31.270324+02:00"
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -234,6 +234,7 @@ rules:
|
|||
verbs:
|
||||
- get
|
||||
- create
|
||||
- update
|
||||
# to create role bindings to the postgres-pod service account
|
||||
- apiGroups:
|
||||
- rbac.authorization.k8s.io
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ spec:
|
|||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
{{- if .Values.extraArgs }}
|
||||
args:
|
||||
{{ toYaml .Values.extraArgs | indent 8 }}
|
||||
{{- end }}
|
||||
env:
|
||||
{{- if .Values.enableJsonLogging }}
|
||||
- name: ENABLE_JSON_LOGGING
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
image:
|
||||
registry: ghcr.io
|
||||
repository: zalando/postgres-operator
|
||||
tag: v1.15.1
|
||||
tag: v2.0.0
|
||||
pullPolicy: "IfNotPresent"
|
||||
|
||||
# Optionally specify an array of imagePullSecrets.
|
||||
|
|
@ -18,6 +18,9 @@ configTarget: "OperatorConfigurationCRD"
|
|||
# JSON logging format
|
||||
enableJsonLogging: false
|
||||
|
||||
# Command-line options for the operator
|
||||
extraArgs: []
|
||||
|
||||
# general configuration parameters
|
||||
configGeneral:
|
||||
# the deployment should create/update the CRDs
|
||||
|
|
@ -27,24 +30,31 @@ configGeneral:
|
|||
- "all"
|
||||
# update only the statefulsets without immediately doing the rolling update
|
||||
enable_lazy_spilo_upgrade: false
|
||||
# toogle to use maintenance windows feature
|
||||
enable_maintenance_windows: true
|
||||
# set the PGVERSION env var instead of providing the version via postgresql.bin_dir in SPILO_CONFIGURATION
|
||||
enable_pgversion_env_var: true
|
||||
# start any new database pod without limitations on shm memory
|
||||
enable_shm_volume: true
|
||||
# enables backwards compatible path between Spilo 12 and Spilo 13+ images
|
||||
enable_spilo_wal_path_compat: false
|
||||
# operator will sync only clusters where name starts with teamId prefix
|
||||
enable_team_id_clustername_prefix: false
|
||||
# etcd connection string for Patroni. Empty uses K8s-native DCS.
|
||||
etcd_host: ""
|
||||
# Spilo docker image
|
||||
docker_image: ghcr.io/zalando/spilo-17:4.0-p3
|
||||
docker_image: ghcr.io/zalando/spilo-18:4.1-p2
|
||||
|
||||
# key name for annotation to ignore globally configured instance limits
|
||||
# ignore_instance_limits_annotation_key: ""
|
||||
|
||||
# key name for annotation to ignore globally configured resources thresholds
|
||||
# ignore_resources_limits_annotation_key: ""
|
||||
|
||||
# Select if setup uses endpoints (default), or configmaps to manage leader (DCS=k8s)
|
||||
# kubernetes_use_configmaps: false
|
||||
kubernetes_use_configmaps: true
|
||||
|
||||
# maintenance windows applied to all Postgres clusters unless overridden in the manifest
|
||||
# maintenance_windows:
|
||||
# - "Sun:01:00-06:00"
|
||||
|
||||
# min number of instances in Postgres cluster. -1 = no limit
|
||||
min_instances: -1
|
||||
|
|
@ -89,9 +99,9 @@ configMajorVersionUpgrade:
|
|||
# - acid
|
||||
|
||||
# minimal Postgres major version that will not automatically be upgraded
|
||||
minimal_major_version: "13"
|
||||
minimal_major_version: "14"
|
||||
# target Postgres major version when upgrading clusters automatically
|
||||
target_major_version: "17"
|
||||
target_major_version: "18"
|
||||
|
||||
configKubernetes:
|
||||
# list of additional capabilities for postgres container
|
||||
|
|
@ -221,6 +231,19 @@ configKubernetes:
|
|||
# whether the Spilo container should run with additional permissions other than parent.
|
||||
# required by cron which needs setuid
|
||||
spilo_allow_privilege_escalation: true
|
||||
|
||||
# liveness probe for the spilo pod
|
||||
# liveness_probe:
|
||||
# httpGet:
|
||||
# scheme: HTTP
|
||||
# path: /liveness
|
||||
# port: 8008
|
||||
# initialDelaySeconds: 10
|
||||
# periodSeconds: 10
|
||||
# timeoutSeconds: 5
|
||||
# successThreshold: 1
|
||||
# failureThreshold: 3
|
||||
|
||||
# storage resize strategy, available options are: ebs, pvc, off or mixed
|
||||
storage_resize_mode: pvc
|
||||
# pod toleration assigned to instances of every Postgres cluster
|
||||
|
|
@ -327,17 +350,15 @@ configAwsOrGcp:
|
|||
# AWS region used to store EBS volumes
|
||||
aws_region: eu-central-1
|
||||
|
||||
# enable automatic migration on AWS from gp2 to gp3 volumes
|
||||
enable_ebs_gp3_migration: false
|
||||
# defines maximum volume size in GB until which auto migration happens
|
||||
# enable_ebs_gp3_migration_max_size: 1000
|
||||
|
||||
# GCP credentials that will be used by the operator / pods
|
||||
# gcp_credentials: ""
|
||||
|
||||
# AWS IAM role to supply in the iam.amazonaws.com/role annotation of Postgres pods
|
||||
# kube_iam_role: ""
|
||||
|
||||
# Full ARN for IRSA (IAM Roles for Service Accounts) on EKS
|
||||
# irsa_role_arn: ""
|
||||
|
||||
# S3 bucket to use for shipping postgres daily logs
|
||||
# log_s3_bucket: ""
|
||||
|
||||
|
|
@ -364,7 +385,7 @@ configLogicalBackup:
|
|||
# logical_backup_memory_request: ""
|
||||
|
||||
# image for pods of the logical backup job (example runs pg_dumpall)
|
||||
logical_backup_docker_image: "ghcr.io/zalando/postgres-operator/logical-backup:v1.15.1"
|
||||
logical_backup_docker_image: "ghcr.io/zalando/postgres-operator/logical-backup:v2.0.0"
|
||||
# path of google cloud service account json file
|
||||
# logical_backup_google_application_credentials: ""
|
||||
|
||||
|
|
@ -392,6 +413,12 @@ configLogicalBackup:
|
|||
logical_backup_schedule: "30 00 * * *"
|
||||
# secret to be used as reference for env variables in cronjob
|
||||
logical_backup_cronjob_environment_secret: ""
|
||||
# number of successful backup jobs to keep in cronjob history
|
||||
logical_backup_successful_jobs_history_limit: 3
|
||||
# number of failed backup jobs to keep in cronjob history
|
||||
logical_backup_failed_jobs_history_limit: 3
|
||||
# TTL in seconds after which finished backup jobs are automatically deleted
|
||||
logical_backup_ttl_seconds_after_finished: 86400
|
||||
|
||||
# automate creation of human users with teams API service
|
||||
configTeamsApi:
|
||||
|
|
@ -436,7 +463,7 @@ configConnectionPooler:
|
|||
# db user for pooler to use
|
||||
connection_pooler_user: "pooler"
|
||||
# docker image
|
||||
connection_pooler_image: "registry.opensource.zalan.do/acid/pgbouncer:master-32"
|
||||
connection_pooler_image: "ghcr.io/zalando/postgres-operator/pgbouncer:latest"
|
||||
# max db connections the pooler should hold
|
||||
connection_pooler_max_db_connections: 60
|
||||
# default pooling mode
|
||||
|
|
@ -489,7 +516,6 @@ podPriorityClassName:
|
|||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 500Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ allow_concurrent_steps: true
|
|||
build_env: &BUILD_ENV
|
||||
PYTHON_BASE_IMAGE: container-registry.zalando.net/library/python-3.11-slim
|
||||
ALPINE_BASE_IMAGE: container-registry.zalando.net/library/alpine-3
|
||||
UBUNTU_BASE_IMAGE: container-registry.zalando.net/library/ubuntu-22.04
|
||||
MULTI_ARCH_REGISTRY: container-registry-test.zalando.net/acid
|
||||
|
||||
pipeline:
|
||||
|
|
@ -17,13 +18,16 @@ pipeline:
|
|||
image: cdp-runtime/go
|
||||
cache:
|
||||
paths:
|
||||
- /go/pkg/mod # pkg cache for Go modules
|
||||
- ~/.cache/go-build # Go build cache
|
||||
- /go/pkg/mod # pkg cache for Go modules
|
||||
- ~/.cache/go-build # Go build cache
|
||||
commands:
|
||||
- desc: Run unit tests
|
||||
cmd: |
|
||||
make deps mocks test
|
||||
|
||||
make mocks test
|
||||
if ! git diff --quiet; then
|
||||
echo "Build resulted in files being changed, likely they were not checked in"
|
||||
exit 1
|
||||
fi
|
||||
- desc: Build Docker image
|
||||
cmd: |
|
||||
if [ -z ${CDP_SOURCE_BRANCH} ]; then
|
||||
|
|
@ -39,6 +43,29 @@ pipeline:
|
|||
-f docker/Dockerfile \
|
||||
--push .
|
||||
|
||||
- id: build-pooler
|
||||
env:
|
||||
<<: *BUILD_ENV
|
||||
type: script
|
||||
vm_config:
|
||||
type: linux
|
||||
|
||||
commands:
|
||||
- desc: Build image
|
||||
cmd: |
|
||||
cd pooler
|
||||
if [ -z ${CDP_SOURCE_BRANCH} ]; then
|
||||
IMAGE=${MULTI_ARCH_REGISTRY}/pgbouncer
|
||||
else
|
||||
IMAGE=${MULTI_ARCH_REGISTRY}/pgbouncer-test
|
||||
fi
|
||||
|
||||
docker buildx create --config /etc/cdp-buildkitd.toml --driver-opt network=host --bootstrap --use
|
||||
docker buildx build --platform "linux/amd64,linux/arm64" \
|
||||
--build-arg BASE_IMAGE="${ALPINE_BASE_IMAGE}" \
|
||||
-t "${IMAGE}:${CDP_BUILD_VERSION}" \
|
||||
--push .
|
||||
|
||||
if [ -z ${CDP_SOURCE_BRANCH} ]; then
|
||||
cdp-promote-image ${IMAGE}:${CDP_BUILD_VERSION}
|
||||
fi
|
||||
|
|
@ -69,7 +96,7 @@ pipeline:
|
|||
else
|
||||
IMAGE=${MULTI_ARCH_REGISTRY}/postgres-operator-ui-test
|
||||
fi
|
||||
|
||||
|
||||
make appjs
|
||||
docker buildx create --config /etc/cdp-buildkitd.toml --driver-opt network=host --bootstrap --use
|
||||
docker buildx build --platform linux/amd64,linux/arm64 \
|
||||
|
|
@ -100,6 +127,7 @@ pipeline:
|
|||
|
||||
docker buildx create --config /etc/cdp-buildkitd.toml --driver-opt network=host --bootstrap --use
|
||||
docker buildx build --platform linux/amd64,linux/arm64 \
|
||||
--build-arg BASE_IMAGE="${UBUNTU_BASE_IMAGE}" \
|
||||
-t ${IMAGE}:${CDP_BUILD_VERSION} \
|
||||
--push .
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1.25-alpine
|
||||
FROM golang:1.26-alpine
|
||||
LABEL maintainer="Team ACID @ Zalando <team-acid@zalando.de>"
|
||||
|
||||
# We need root certificates to deal with teams api over https
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
ARG BASE_IMAGE=alpine:latest
|
||||
FROM golang:1.25-alpine AS builder
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS builder
|
||||
ARG VERSION=latest
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
COPY . /go/src/github.com/zalando/postgres-operator
|
||||
WORKDIR /go/src/github.com/zalando/postgres-operator
|
||||
|
||||
RUN GO111MODULE=on go mod vendor \
|
||||
&& CGO_ENABLED=0 go build -o build/postgres-operator -v -ldflags "-X=main.version=${VERSION}" cmd/main.go
|
||||
RUN go mod vendor \
|
||||
&& CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o build/postgres-operator -v -ldflags "-X=main.version=${VERSION}" cmd/main.go
|
||||
|
||||
ARG BASE_IMAGE
|
||||
FROM ${BASE_IMAGE}
|
||||
LABEL maintainer="Team ACID @ Zalando <team-acid@zalando.de>"
|
||||
LABEL org.opencontainers.image.source="https://github.com/zalando/postgres-operator"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ apt-get install -y wget
|
|||
|
||||
(
|
||||
cd /tmp
|
||||
wget -q "https://storage.googleapis.com/golang/go1.25.3.linux-${arch}.tar.gz" -O go.tar.gz
|
||||
wget -q "https://storage.googleapis.com/golang/go1.26.4.linux-${arch}.tar.gz" -O go.tar.gz
|
||||
tar -xf go.tar.gz
|
||||
mv go /usr/local
|
||||
ln -s /usr/local/go/bin/go /usr/bin/go
|
||||
|
|
@ -26,5 +26,5 @@ export PATH="$PATH:$HOME/go/bin"
|
|||
export GOPATH="$HOME/go"
|
||||
mkdir -p build
|
||||
|
||||
GO111MODULE=on go mod vendor
|
||||
go mod vendor
|
||||
CGO_ENABLED=0 go build -o build/postgres-operator -v -ldflags "$OPERATOR_LDFLAGS" cmd/main.go
|
||||
|
|
|
|||
|
|
@ -65,7 +65,10 @@ the `PGVERSION` environment variable is set for the database pods. Since
|
|||
In-place major version upgrades can be configured to be executed by the
|
||||
operator with the `major_version_upgrade_mode` option. By default, it is
|
||||
enabled (mode: `manual`). In any case, altering the version in the manifest
|
||||
will trigger a rolling update of pods to update the `PGVERSION` env variable.
|
||||
will update the desired `PGVERSION`. If `maintenanceWindows` are configured,
|
||||
major-version-related pod rotation is deferred until the next maintenance
|
||||
window. Without maintenance windows, the operator will trigger a rolling
|
||||
update of pods to apply the new `PGVERSION`.
|
||||
Spilo's [`configure_spilo`](https://github.com/zalando/spilo/blob/master/postgres-appliance/scripts/configure_spilo.py)
|
||||
script will notice the version mismatch but start the current version again.
|
||||
|
||||
|
|
@ -92,10 +95,11 @@ Thus, the `full` mode can create drift between desired and actual state.
|
|||
|
||||
### Upgrade during maintenance windows
|
||||
|
||||
When `maintenanceWindows` are defined in the Postgres manifest the operator
|
||||
will trigger a major version upgrade only during these periods. Make sure they
|
||||
are at least twice as long as your configured `resync_period` to guarantee
|
||||
that operator actions can be triggered.
|
||||
When `maintenanceWindows` are defined in the Postgres manifest or in the global
|
||||
config the operator will trigger major-version-related pod rotation and the
|
||||
major version upgrade only during these periods. Make sure they are at least
|
||||
twice as long as your configured `resync_period` to guarantee that operator
|
||||
actions can be triggered.
|
||||
|
||||
### Upgrade annotations
|
||||
|
||||
|
|
@ -635,9 +639,11 @@ masters in single-node clusters and/or the last remaining running instance in a
|
|||
cluster.
|
||||
|
||||
## PDB for critical operations
|
||||
The `MinAvailable` parameter of this PDB is equal to the `numberOfInstances` set in the
|
||||
cluster manifest, while label selector includes `critical-operation=true` condition. This
|
||||
allows to protect all pods of a cluster, given they are labeled accordingly.
|
||||
The `MaxUnavailable` parameter of this PDB is set to `0`, while label selector includes
|
||||
`critical-operation=true` condition. This blocks voluntary disruptions for all pods of a
|
||||
cluster that are labeled accordingly, without leaving an unsatisfiable budget behind when
|
||||
no pods carry the label (which previously kept monitoring alerts like
|
||||
`KubePdbNotEnoughHealthyPods` firing permanently).
|
||||
For example, Operator labels all Spilo pods with `critical-operation=true` during the major
|
||||
version upgrade run. You may want to protect cluster pods during other critical operations
|
||||
by assigning the label to pods yourself or using other means of automation.
|
||||
|
|
@ -647,7 +653,14 @@ The PDB is only relaxed in two scenarios:
|
|||
* If a cluster is scaled down to `0` instances (e.g. for draining nodes)
|
||||
* If the PDB is disabled in the configuration (`enable_pod_disruption_budget`)
|
||||
|
||||
The PDBs are still in place having `MinAvailable` set to `0`. Disabling PDBs
|
||||
The PDBs are still in place but fully relaxed: the primary PDB with `MinAvailable`
|
||||
set to `0` and the critical operations PDB with `MaxUnavailable` set to `100%`.
|
||||
The two PDBs intentionally use different budget fields matching their purposes:
|
||||
the primary PDB guarantees a minimum count of always-present pods
|
||||
(`MinAvailable`), while the critical operations PDB freezes disruptions for
|
||||
whatever pods currently carry the `critical-operation=true` label - a
|
||||
usually-empty set, which `MaxUnavailable: 0` expresses without producing an
|
||||
unsatisfiable budget while idle. Disabling PDBs
|
||||
helps avoiding blocking Kubernetes upgrades in managed K8s environments at the
|
||||
cost of prolonged DB downtime. See PR [#384](https://github.com/zalando/postgres-operator/pull/384)
|
||||
for the use case.
|
||||
|
|
@ -721,9 +734,9 @@ that cannot be overridden to guarantee core functionality. Only variables with
|
|||
shipping to be specified differently. There are three ways to specify extra
|
||||
environment variables (or override existing ones) for database pods:
|
||||
|
||||
* [Via ConfigMap](#via-configmap)
|
||||
* [Via Secret](#via-secret)
|
||||
* [Via Postgres Cluster Manifest](#via-postgres-cluster-manifest)
|
||||
* [Globally via ConfigMap](#via-configmap)
|
||||
* [Globally via Secret](#via-secret)
|
||||
* [Locally via Postgres Cluster Manifest](#via-postgres-cluster-manifest)
|
||||
|
||||
The first two options must be referenced from the operator configuration
|
||||
making them global settings for all Postgres cluster the operator watches.
|
||||
|
|
@ -732,10 +745,10 @@ environment variables. Another case could be to provide custom cloud
|
|||
provider or backup settings.
|
||||
|
||||
The last options allows for specifying environment variables individual to
|
||||
every cluster via the `env` section in the manifest. For example, if you use
|
||||
individual backup locations for each of your clusters. Or you want to disable
|
||||
WAL archiving for a certain cluster by setting `WAL_S3_BUCKET`, `WAL_GS_BUCKET`
|
||||
or `AZURE_STORAGE_ACCOUNT` to an empty string.
|
||||
every cluster via the `env` or `envFrom` section in the manifest. For example,
|
||||
if you use individual backup locations for each of your clusters. Or you want
|
||||
to disable WAL archiving for a certain cluster by setting `WAL_S3_BUCKET`,
|
||||
`WAL_GS_BUCKET` or `AZURE_STORAGE_ACCOUNT` to an empty string.
|
||||
|
||||
The operator will give precedence to environment variables in the following
|
||||
order (e.g. a variable defined in 4. overrides a variable with the same name
|
||||
|
|
@ -749,6 +762,9 @@ in 5.):
|
|||
6. Pod environment config map via operator config
|
||||
7. WAL and logical backup settings from operator config
|
||||
|
||||
The `envFrom` section is treated separately and allows for a very flexible
|
||||
local configuration referencing a ConfigMap or a Secret.
|
||||
|
||||
### Via ConfigMap
|
||||
|
||||
The ConfigMap with the additional settings is referenced in the operator's
|
||||
|
|
@ -887,15 +903,13 @@ cluster manifest. In the case any of these variables are omitted from the
|
|||
manifest, the operator configuration settings `enable_master_load_balancer` and
|
||||
`enable_replica_load_balancer` apply. Note that the operator settings affect
|
||||
all Postgresql services running in all namespaces watched by the operator.
|
||||
If load balancing is enabled two default annotations will be applied to its
|
||||
services:
|
||||
If load balancing is enabled the following default annotation will be applied to
|
||||
its services:
|
||||
|
||||
- `external-dns.alpha.kubernetes.io/hostname` with the value defined by the
|
||||
operator configs `master_dns_name_format` and `replica_dns_name_format`.
|
||||
This value can't be overwritten. If any changing in its value is needed, it
|
||||
MUST be done changing the DNS format operator config parameters; and
|
||||
- `service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout` with
|
||||
a default value of "3600".
|
||||
|
||||
There are multiple options to specify service annotations that will be merged
|
||||
with each other and override in the following order (where latter take
|
||||
|
|
@ -926,6 +940,41 @@ For the `external-dns.alpha.kubernetes.io/hostname` annotation the `-pooler`
|
|||
suffix will be appended to the cluster name used in the template which is
|
||||
defined in `master|replica_dns_name_format`.
|
||||
|
||||
## Node Ports
|
||||
|
||||
Alternatively to Load Balancers Node Ports can be used. Kubernetes services with type
|
||||
`NodePort` redirect traffic from a specified port on your kubernetes nodes to your service.
|
||||
To expose your services to an external network with NodePorts you can set `enableMasterNodePort` and/or `enableReplicaNodePort` to `true`
|
||||
in your cluster manifest. In the case any of these variables are omitted from the manifest, the operator configuration settings `enable_master_node_port` and `enable_replica_node_port` apply.
|
||||
Note that the operator settings affect all Postgresql services running in all namespaces watched
|
||||
by the operator.
|
||||
|
||||
**Enabling a NodePort configuration will override the corresponding LoadBalancer configuration.**
|
||||
|
||||
There are multiple options to specify service annotations that will be merged
|
||||
with each other and override in the following order (where latter take
|
||||
precedence):
|
||||
|
||||
1. Globally configured `custom_service_annotations`
|
||||
2. `serviceAnnotations` specified in the cluster manifest
|
||||
3. `masterServiceAnnotations` and `replicaServiceAnnotations` specified in the cluster manifest
|
||||
|
||||
Load-Balancer specific annotations are not applied.
|
||||
|
||||
Node port services can also be configured for the [connection pooler](user.md#connection-pooler) pods
|
||||
with the manifest flags `enableMasterPoolerNodePort` and/or `enableReplicaPoolerNodePort` or in the operator configuration with `enable_master_pooler_node_port`
|
||||
and/or `enable_replica_pooler_node_port`.
|
||||
|
||||
To configure which ports Kubernetes should use for your NodePort service you can configure ports in your cluster manifest
|
||||
for each type:
|
||||
|
||||
- masterNodePort
|
||||
- masterPoolerNodePort
|
||||
- replicaNodePort
|
||||
- replicaPoolerNodePort
|
||||
|
||||
When not defined or set to 0 kubernetes will choose a port for you from [your kubernetes cluster's configured range](https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport).
|
||||
|
||||
## Running periodic 'autorepair' scans of K8s objects
|
||||
|
||||
The Postgres Operator periodically scans all K8s objects belonging to each
|
||||
|
|
@ -1057,6 +1106,32 @@ configuration:
|
|||
wal_s3_bucket: your-backup-path
|
||||
```
|
||||
|
||||
Alternatively, if your cluster uses EKS with OIDC, you can use
|
||||
[IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)
|
||||
(IAM Roles for Service Accounts) instead of kube2iam. Set `irsa_role_arn` to
|
||||
the full ARN of the IAM role:
|
||||
|
||||
**OperatorConfiguration**
|
||||
|
||||
```yaml
|
||||
apiVersion: "acid.zalan.do/v1"
|
||||
kind: OperatorConfiguration
|
||||
metadata:
|
||||
name: postgresql-operator-configuration
|
||||
configuration:
|
||||
aws_or_gcp:
|
||||
aws_region: eu-central-1
|
||||
irsa_role_arn: arn:aws:iam::123456789012:role/postgres-pod-role
|
||||
wal_s3_bucket: your-backup-path
|
||||
```
|
||||
|
||||
When `irsa_role_arn` is set the operator annotates the pod service account with
|
||||
`eks.amazonaws.com/role-arn` on every reconcile. The EKS OIDC webhook then
|
||||
injects an AWS web identity token into each pod, which takes precedence over
|
||||
the EC2 metadata credentials used by kube2iam. Both `kube_iam_role` and
|
||||
`irsa_role_arn` can coexist during a migration — existing pods retain the
|
||||
kube2iam annotation until they are rotated, at which point only IRSA is used.
|
||||
|
||||
The referenced IAM role should contain the following privileges to make sure
|
||||
Postgres can send compressed WAL files to the given S3 bucket:
|
||||
|
||||
|
|
@ -1167,6 +1242,7 @@ aws_or_gcp:
|
|||
# additional_secret_mount_path: ""
|
||||
# aws_region: eu-central-1
|
||||
# kube_iam_role: ""
|
||||
# irsa_role_arn: ""
|
||||
# log_s3_bucket: ""
|
||||
# wal_s3_bucket: ""
|
||||
wal_gs_bucket: "postgres-backups-bucket-28302F2" # name of bucket on where to save the WAL-E logs
|
||||
|
|
@ -1216,6 +1292,7 @@ aws_or_gcp:
|
|||
additional_secret_mount_path: "/var/secrets/google" # or where ever you want to mount the file
|
||||
# aws_region: eu-central-1
|
||||
# kube_iam_role: ""
|
||||
# irsa_role_arn: ""
|
||||
# log_s3_bucket: ""
|
||||
# wal_s3_bucket: ""
|
||||
wal_gs_bucket: "postgres-backups-bucket-28302F2" # name of bucket on where to save the WAL-E logs
|
||||
|
|
@ -1312,7 +1389,7 @@ aws_or_gcp:
|
|||
|
||||
If cluster members have to be (re)initialized restoring physical backups
|
||||
happens automatically either from the backup location or by running
|
||||
[pg_basebackup](https://www.postgresql.org/docs/17/app-pgbasebackup.html)
|
||||
[pg_basebackup](https://www.postgresql.org/docs/18/app-pgbasebackup.html)
|
||||
on one of the other running instances (preferably replicas if they do not lag
|
||||
behind). You can test restoring backups by [cloning](user.md#how-to-clone-an-existing-postgresql-cluster)
|
||||
clusters.
|
||||
|
|
@ -1346,10 +1423,12 @@ If you are using [additional environment variables](#custom-pod-environment-vari
|
|||
to access your backup location you have to copy those variables and prepend
|
||||
the `STANDBY_` prefix for Spilo to find the backups and WAL files to stream.
|
||||
|
||||
Alternatively, standby clusters can also stream from a remote primary cluster.
|
||||
Standby clusters can also stream from a remote primary cluster.
|
||||
You have to specify the host address. Port is optional and defaults to 5432.
|
||||
Note, that only one of the options (`s3_wal_path`, `gs_wal_path`,
|
||||
`standby_host`) can be present under the `standby` top-level key.
|
||||
You can combine `standby_host` with either `s3_wal_path` or `gs_wal_path`
|
||||
for additional redundancy. Note that `s3_wal_path` and `gs_wal_path` are
|
||||
mutually exclusive. At least one of `s3_wal_path`, `gs_wal_path`, or
|
||||
`standby_host` must be specified under the `standby` top-level key.
|
||||
|
||||
## Logical backups
|
||||
|
||||
|
|
@ -1498,7 +1577,7 @@ make docker
|
|||
|
||||
# build in image in minikube docker env
|
||||
eval $(minikube docker-env)
|
||||
docker build -t ghcr.io/zalando/postgres-operator-ui:v1.15.1 .
|
||||
docker buildx build --load -t ghcr.io/zalando/postgres-operator-ui:v2.0.0 .
|
||||
|
||||
# apply UI manifests next to a running Postgres Operator
|
||||
kubectl apply -f manifests/
|
||||
|
|
|
|||
|
|
@ -27,23 +27,10 @@ git clone https://github.com/zalando/postgres-operator.git
|
|||
|
||||
## Building the operator
|
||||
|
||||
We use [Go Modules](https://github.com/golang/go/wiki/Modules) for handling
|
||||
dependencies. When using Go below v1.13 you need to explicitly enable Go modules
|
||||
by setting the `GO111MODULE` environment variable to `on`. The make targets do
|
||||
this for you, so simply run
|
||||
We use [Go Modules](https://github.com/golang/go/wiki/Modules) for handling dependencies.
|
||||
Run `go mod vendor && go mod tidy` to install them.
|
||||
|
||||
```bash
|
||||
make deps
|
||||
```
|
||||
|
||||
This would take a while to complete. You have to redo `make deps` every time
|
||||
your dependencies list changes, i.e. after adding a new library dependency.
|
||||
|
||||
Build the operator with the `make docker` command. You may define the TAG
|
||||
variable to assign an explicit tag to your Docker image and the IMAGE to set
|
||||
the image name. By default, the tag is computed with
|
||||
`git describe --tags --always --dirty` and the image is
|
||||
`ghcr.io/zalando/postgres-operator`
|
||||
Build the operator with the `make docker` command. You may define the TAG variable to assign an explicit tag to your Docker image and the IMAGE to set the image name. By default, the tag is computed with `git describe --tags --always --dirty` and the image is `ghcr.io/zalando/postgres-operator`.
|
||||
|
||||
```bash
|
||||
export TAG=$(git describe --tags --always --dirty)
|
||||
|
|
@ -223,14 +210,13 @@ dlv connect 127.0.0.1:DLV_PORT
|
|||
Prerequisites:
|
||||
|
||||
```bash
|
||||
make deps
|
||||
make mocks
|
||||
```
|
||||
|
||||
To run all unit tests, you can simply do:
|
||||
|
||||
```bash
|
||||
go test ./pkg/...
|
||||
make test
|
||||
```
|
||||
|
||||
In case if you need to debug your unit test, it's possible to use delve:
|
||||
|
|
@ -300,8 +286,7 @@ Please run flake8 [before submitting a PR](http://flake8.pycqa.org/en/latest/use
|
|||
In the case you want to add functionality to the operator that shall be
|
||||
controlled via the operator configuration there are a few places that need to
|
||||
be updated. As explained [here](reference/operator_parameters.md), it's possible
|
||||
to configure the operator either with a ConfigMap or CRD, but currently we aim
|
||||
to synchronize parameters everywhere.
|
||||
to configure the operator either with a ConfigMap or CRD.
|
||||
|
||||
When choosing a parameter name for a new option in a Postgres cluster manifest,
|
||||
keep in mind the naming conventions there. We use `camelCase` for manifest
|
||||
|
|
@ -324,32 +309,28 @@ manifest files:
|
|||
|
||||
Postgres manifest parameters are defined in the [api package](https://github.com/zalando/postgres-operator/blob/master/pkg/apis/acid.zalan.do/v1/postgresql_type.go).
|
||||
The operator behavior has to be implemented at least in [k8sres.go](https://github.com/zalando/postgres-operator/blob/master/pkg/cluster/k8sres.go).
|
||||
Validation of CRD parameters is controlled in [crds.go](https://github.com/zalando/postgres-operator/blob/master/pkg/apis/acid.zalan.do/v1/crds.go).
|
||||
Please, reflect your changes in tests, for example in:
|
||||
|
||||
* [config_test.go](https://github.com/zalando/postgres-operator/blob/master/pkg/util/config/config_test.go)
|
||||
* [k8sres_test.go](https://github.com/zalando/postgres-operator/blob/master/pkg/cluster/k8sres_test.go)
|
||||
* [util_test.go](https://github.com/zalando/postgres-operator/blob/master/pkg/apis/acid.zalan.do/v1/util_test.go)
|
||||
|
||||
### Updating manifest files
|
||||
### Generating the CRDs
|
||||
|
||||
For the CRD-based configuration, please update the following files:
|
||||
|
||||
* the default [OperatorConfiguration](https://github.com/zalando/postgres-operator/blob/master/manifests/postgresql-operator-default-configuration.yaml)
|
||||
* the CRD's [validation](https://github.com/zalando/postgres-operator/blob/master/manifests/operatorconfiguration.crd.yaml)
|
||||
* the CRD's validation in the [Helm chart](https://github.com/zalando/postgres-operator/blob/master/charts/postgres-operator/crds/operatorconfigurations.yaml)
|
||||
|
||||
Add new options also to the Helm chart's [values file](https://github.com/zalando/postgres-operator/blob/master/charts/postgres-operator/values.yaml) file.
|
||||
It follows the OperatorConfiguration CRD layout. Nested values will be flattened for the ConfigMap.
|
||||
Last but no least, update the [ConfigMap](https://github.com/zalando/postgres-operator/blob/master/manifests/configmap.yaml) manifest example as well.
|
||||
The CRDs can be automatically generated from the go structs. Use the correct kubebuilder annotations for defining the validation, constraints or default values etc.. Run `make` to update the CRDs which are stored in three locations:
|
||||
- In the Go api package
|
||||
- The example manifests folder
|
||||
- The helm chart folder
|
||||
|
||||
### Updating documentation
|
||||
|
||||
Finally, add a section for each new configuration option and/or cluster manifest
|
||||
Config changes need to be reflected in the Helm chart's [values file](https://github.com/zalando/postgres-operator/blob/master/charts/postgres-operator/values.yaml), too. It follows the OperatorConfiguration CRD layout. Nested values will be flattened for the ConfigMap.
|
||||
|
||||
Add a section for each new configuration option and/or cluster manifest
|
||||
parameter in the reference documents:
|
||||
|
||||
* [config reference](reference/operator_parameters.md)
|
||||
* [manifest reference](reference/cluster_manifest.md)
|
||||
|
||||
It also helps users to explain new features with examples in the
|
||||
[administrator docs](administrator.md).
|
||||
It can also help other K8s admins to explain new features with examples in the
|
||||
[administrator docs](administrator.md) and also update the [OperatorConfiguration CRD](https://github.com/zalando/postgres-operator/blob/master/manifests/postgresql-operator-default-configuration.yaml) and [ConfigMap](https://github.com/zalando/postgres-operator/blob/master/manifests/configmap.yaml) manifest examples.
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 253 KiB |
|
|
@ -1,101 +0,0 @@
|
|||
\documentclass{article}
|
||||
\usepackage{tikz}
|
||||
\usepackage[graphics,tightpage,active]{preview}
|
||||
\usetikzlibrary{arrows, shadows.blur, positioning, fit, calc, backgrounds}
|
||||
\usepackage{lscape}
|
||||
|
||||
\pagenumbering{gobble}
|
||||
|
||||
\PreviewEnvironment{tikzpicture}
|
||||
\PreviewEnvironment{equation}
|
||||
\PreviewEnvironment{equation*}
|
||||
\newlength{\imagewidth}
|
||||
\newlength{\imagescale}
|
||||
\pagestyle{empty}
|
||||
\thispagestyle{empty}
|
||||
|
||||
\begin{document}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[
|
||||
scale=0.5,transform shape,
|
||||
font=\sffamily,
|
||||
every matrix/.style={ampersand replacement=\&,column sep=2cm,row sep=2cm},
|
||||
operator/.style={draw,solid,thick,circle,fill=red!20,inner sep=.3cm, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
component/.style={draw,solid,thick,rounded corners,fill=yellow!20,inner sep=.3cm, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
border/.style={draw,dashed,rounded corners,fill=gray!20,inner sep=.3cm, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
pod/.style={draw,solid,thick,rounded corners,fill=blue!20, inner sep=.3cm, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
service/.style={draw,solid,thick,rounded corners,fill=blue!20, inner sep=.3cm, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
endpoint/.style={draw,solid,thick,rounded corners,fill=blue!20, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
secret/.style={draw,solid,thick,rounded corners,fill=blue!20, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
pvc/.style={draw,solid,thick,rounded corners,fill=blue!20, inner sep=.3cm, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
label/.style={rectangle,inner sep=0,outer sep=0},
|
||||
to/.style={->,>=stealth',shorten >=1pt,semithick,font=\sffamily\footnotesize},
|
||||
every node/.style={align=center}]
|
||||
|
||||
% Position the nodes using a matrix layout
|
||||
|
||||
\matrix{
|
||||
\& \node[component] (crd) {CRD}; \\
|
||||
\& \node[operator] (operator) {Operator}; \\
|
||||
\path
|
||||
node[service] (service-master) {Master}
|
||||
node[label, right of=service-master] (service-middle) {}
|
||||
node[label, below of=service-middle] (services-label) {Services}
|
||||
node[service, right=.5cm of service-master] (service-replica) {Replica}
|
||||
node[border, behind path,
|
||||
fit=(service-master)(service-replica)(services-label)
|
||||
] (services) {};
|
||||
\&
|
||||
\node[component] (sts) {Statefulset}; \& \node[component] (pdb) {Pod Disruption Budget}; \\
|
||||
\path
|
||||
node[service] (master-endpoint) {Master}
|
||||
node[service, right=.5cm of master-endpoint] (replica-endpoint) {Replica}
|
||||
node[label, right of=master-endpoint] (endpoint-middle) {}
|
||||
node[label, below of=endpoint-middle] (endpoint-label) {Endpoints}
|
||||
node[border, behind path,
|
||||
fit=(master-endpoint)(replica-endpoint)(endpoint-label)
|
||||
] (endpoints) {}; \&
|
||||
\node[component] (pod-template) {Pod Template}; \&
|
||||
\node[border] (secrets) {
|
||||
\begin{tikzpicture}[]
|
||||
\node[secret] (users-secret) at (0, 0) {Users};
|
||||
\node[secret] (robots-secret) at (2, 0) {Robots};
|
||||
\node[secret] (standby-secret) at (4, 0) {Standby};
|
||||
\end{tikzpicture} \\
|
||||
Secrets
|
||||
}; \\ \&
|
||||
\path
|
||||
node[pod] (replica1-pod) {Replica}
|
||||
node[pod, left=.5cm of replica1-pod] (master-pod) {Master}
|
||||
node[pod, right=.5cm of replica1-pod] (replica2-pod) {Replica}
|
||||
node[label, below of=replica1-pod] (pod-label) {Pods}
|
||||
node[border, behind path,
|
||||
fit=(master-pod)(replica1-pod)(replica2-pod)(pod-label)
|
||||
] (pods) {}; \\ \&
|
||||
\path
|
||||
node[pvc] (replica1-pvc) {Replica}
|
||||
node[pvc, left=.5cm of replica1-pvc] (master-pvc) {Master}
|
||||
node[pvc, right=.5cm of replica1-pvc] (replica2-pvc) {Replica}
|
||||
node[label, below of=replica1-pvc] (pvc-label) {Persistent Volume Claims}
|
||||
node[border, behind path,
|
||||
fit=(master-pvc)(replica1-pvc)(replica2-pvc)(pvc-label)
|
||||
] (pvcs) {}; \&
|
||||
\\ \& \\
|
||||
};
|
||||
|
||||
% Draw the arrows between the nodes and label them.
|
||||
\draw[to] (crd) -- node[midway,above] {} node[midway,below] {} (operator);
|
||||
\draw[to] (operator) -- node[midway,above] {} node[midway,below] {} (sts);
|
||||
\draw[to] (operator) -- node[midway,above] {} node[midway,below] {} (secrets);
|
||||
\draw[to] (operator) -| node[midway,above] {} node[midway,below] {} (pdb);
|
||||
\draw[to] (service-master) -- node[midway,above] {} node[midway,below] {} (master-endpoint);
|
||||
\draw[to] (service-replica) -- node[midway,above] {} node[midway,below] {} (replica-endpoint);
|
||||
\draw[to] (master-pod) -- node[midway,above] {} node[midway,below] {} (master-pvc);
|
||||
\draw[to] (replica1-pod) -- node[midway,above] {} node[midway,below] {} (replica1-pvc);
|
||||
\draw[to] (replica2-pod) -- node[midway,above] {} node[midway,below] {} (replica2-pvc);
|
||||
\draw[to] (operator) -| node[midway,above] {} node[midway,below] {} (services);
|
||||
\draw[to] (sts) -- node[midway,above] {} node[midway,below] {} (pod-template);
|
||||
\draw[to] (pod-template) -- node[midway,above] {} node[midway,below] {} (pods);
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
\end{document}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 236 KiB |
|
|
@ -1,92 +0,0 @@
|
|||
\documentclass{article}
|
||||
\usepackage{tikz}
|
||||
\usepackage[graphics,tightpage,active]{preview}
|
||||
\usetikzlibrary{arrows, shadows.blur, positioning, fit, calc, backgrounds}
|
||||
\usepackage{lscape}
|
||||
|
||||
\pagenumbering{gobble}
|
||||
|
||||
\PreviewEnvironment{tikzpicture}
|
||||
\PreviewEnvironment{equation}
|
||||
\PreviewEnvironment{equation*}
|
||||
\newlength{\imagewidth}
|
||||
\newlength{\imagescale}
|
||||
\pagestyle{empty}
|
||||
\thispagestyle{empty}
|
||||
|
||||
\begin{document}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[
|
||||
scale=0.5,transform shape,
|
||||
font=\sffamily,
|
||||
every matrix/.style={ampersand replacement=\&,column sep=2cm,row sep=2cm},
|
||||
pod/.style={draw,solid,thick,circle,fill=red!20,inner sep=.3cm, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
component/.style={draw,solid,thick,rounded corners,fill=yellow!20,inner sep=.3cm, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
border/.style={draw,dashed,rounded corners,fill=gray!20,inner sep=.3cm, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
volume/.style={draw,solid,thick,rounded corners,fill=blue!20, inner sep=.3cm, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
sidecar/.style={draw,solid,thick,rounded corners,fill=blue!20, inner sep=.3cm, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
k8s-label/.style={draw,solid,thick,rounded corners,fill=blue!20, minimum width=1.5cm, inner sep=.3cm, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
affinity/.style={draw,solid,thick,rounded corners,fill=blue!20, minimum width=2cm, inner sep=.3cm, blur shadow={shadow blur steps=5,shadow blur extra rounding=1.3pt}},
|
||||
label/.style={rectangle,inner sep=0,outer sep=0},
|
||||
to/.style={->,>=stealth',shorten >=1pt,semithick,font=\sffamily\footnotesize},
|
||||
every node/.style={align=center}]
|
||||
|
||||
% Position the nodes using a matrix layout
|
||||
|
||||
\matrix{
|
||||
\path
|
||||
node[k8s-label] (app-label) {App}
|
||||
node[k8s-label, right=.25cm of app-label] (role-label) {Role}
|
||||
node[k8s-label, right=.25cm of role-label] (custom-label) {Custom}
|
||||
node[label, below of=role-label] (k8s-label-label) {K8s Labels}
|
||||
node[border, behind path,
|
||||
fit=(app-label)(role-label)(custom-label)(k8s-label-label)
|
||||
] (k8s-labels) {}; \& \&
|
||||
\path
|
||||
node[affinity] (affinity) {Affinity}
|
||||
node[label, right=.25cm of affinity] (affinity-middle) {}
|
||||
node[affinity, right=.25cm of affinity-middle] (anti-affinity) {Anti-affinity}
|
||||
node[label, below of=affinity-middle] (affinity-label) {Assigning to nodes}
|
||||
node[border, behind path,
|
||||
fit=(affinity)(anti-affinity)(affinity-label)
|
||||
] (affinity) {}; \\
|
||||
\& \node[pod] (pod) {Pod}; \& \\
|
||||
\path
|
||||
node[volume, minimum width={width("shm-volume")}] (data-volume) {Data}
|
||||
node[volume, right=.25cm of data-volume, minimum width={width("shm-volume")}] (tokens-volume) {Tokens}
|
||||
node[volume, right=.25cm of tokens-volume] (shm-volume) {/dev/shm}
|
||||
node[label, below of=tokens-volume] (volumes-label) {Volumes}
|
||||
node[border, behind path,
|
||||
fit=(data-volume)(shm-volume)(tokens-volume)(volumes-label)
|
||||
] (volumes) {}; \&
|
||||
\node[component] (spilo) {Spilo}; \&
|
||||
\node[sidecar] (scalyr) {Scalyr}; \& \\ \&
|
||||
\path
|
||||
node[component] (patroni) {Patroni}
|
||||
node[component, below=.25cm of patroni] (postgres) {PostgreSQL}
|
||||
node[border, behind path,
|
||||
fit=(postgres)(patroni)
|
||||
] (spilo-components) {}; \&
|
||||
\path
|
||||
node[sidecar] (custom-sidecar1) {User defined}
|
||||
node[label, right=.25cm of custom-sidecar1] (sidecars-middle) {}
|
||||
node[sidecar, right=.25cm of sidecars-middle] (custom-sidecar2) {User defined}
|
||||
node[label, below of=sidecars-middle] (sidecars-label) {Custom sidecars}
|
||||
node[border, behind path,
|
||||
fit=(custom-sidecar1)(custom-sidecar2)(sidecars-label)
|
||||
] (sidecars) {};
|
||||
\\ \& \\
|
||||
};
|
||||
|
||||
% Draw the arrows between the nodes and label them.
|
||||
\draw[to] (pod) to [bend left=25] (volumes);
|
||||
\draw[to] (pod) to [bend left=25] (k8s-labels);
|
||||
\draw[to] (pod) to [bend right=25] (affinity);
|
||||
\draw[to] (pod) to [bend right=25] (scalyr);
|
||||
\draw[to] (pod) to [bend right=25] (sidecars);
|
||||
\draw[to] (pod) -- node[midway,above] {} node[midway,below] {} (spilo);
|
||||
\draw[to] (spilo) -- node[midway,above] {} node[midway,below] {} (spilo-components);
|
||||
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
\end{document}
|
||||
|
|
@ -47,15 +47,8 @@ flexibility to complement it with other tools like [ZMON](https://opensource.zal
|
|||
Here is a diagram, that summarizes what would be created by the operator, when a
|
||||
new Postgres cluster CRD is submitted:
|
||||
|
||||

|
||||
|
||||
This picture is not complete without an overview of what is inside a single
|
||||
cluster pod, so let's zoom in:
|
||||
|
||||

|
||||
|
||||
These two diagrams should help you to understand the basics of what kind of
|
||||
functionality the operator provides.
|
||||

|
||||

|
||||
|
||||
## Status
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
<h1>Migrate from v1 to v2</h1>
|
||||
|
||||
Version 2.0 changes some default settings and removes deprecated fields. Please read the following sections before upgrading the Postgres Operator deployment.
|
||||
|
||||
## scram-sha-256 by default
|
||||
|
||||
The new operator will default password encryption to `scram-sha-256`. Unless you configure `password_encryption: md5` in the manifest under `spec.postgresql.parameters` the operator will encrypt existing passwords in the secrets with `scram-sha-256` and alter the database passwords. Make sure that your used clients and drivers support `scram-sha-256` as pods will get rotated in rolling fashion after updating to Postgres Operator v2.
|
||||
|
||||
The default Spilo image (`spilo-18:4.1-p2`) still configures the pg_hba.conf file to allow `md5` passwords but Postgres will validate `scram-sha-256` passwords correctly. Passwords of users that are not managed by the operator and are still `md5` encrypted need be altered before the next tagged Spilo image which will drop `md5` completely.
|
||||
|
||||
## K8s Endpoints are deprecated
|
||||
|
||||
If your current operator v1.x deployment is relying on K8s endpoints (the default setup) for Patroni to manage the HA state you have to start planning to switch to configmaps, because endpoints are deprecated from K8s 1.33 onwards. The default of the corresponding parameter `kubernetes_use_configmaps` is changing to `true` with v2.0 of the operator. This means you have to explicity set it to `false` in your configuration before you start the upgrade.
|
||||
|
||||
We explicitly warn you to go straight to configmap-based HA management with database clusters that use replicas, because there's is a danger to run into split-brain scenarios during the rolling update of pods when there exists a leader endpoint and leader config map at the same time. To play it safe, here is what you should do - before or after the Postgres Operator upgrade:
|
||||
|
||||
1. Scale-in all your database clusters to only one primary instance. This can be done by changing the global config options `max_instances` and `min_instances` to `1`. If you have allowed users to ignore globally defined instance limits by configuring an `ignore_instance_limits_annotation_key`, remove it for now.
|
||||
|
||||
2. Wait for all clusters to be healthy and change the `kubernetes_use_configmaps` setting to `true`. This will trigger the replacement of the primary pod of all clusters and cause downtime for as long as the pods are rescheduled and start up.
|
||||
|
||||
3. Check again that all clusters are healthy with configmaps created. There should be three for each cluster called like cluster name with suffixes `-config`, `-failover` and `-leader`. Now, revert the changes from step 1 and scale-out the to number of instances set in the manifests.
|
||||
|
||||
4. The orphaned endpoints, which use the same names like the new configmaps, have to be deleted by you or your K8s garbage collection.
|
||||
|
||||
## Dropped manifest fields
|
||||
|
||||
We removed some deprecated fields from the Postgresql CRD. Please, make sure that you do not specify them in any of your cluster manifests. If you do, switch to the listed alternative:
|
||||
|
||||
| Removed field in v2 | Alternative |
|
||||
| --- | --- |
|
||||
| init_containers | initContainers |
|
||||
| pod_priority_class_name | podPriorityClassName |
|
||||
| replicaLoadBalancer | enableReplicaLoadBalancer|
|
||||
| useLoadBalancer | enableMasterLoadBalancer |
|
||||
|
|
@ -85,6 +85,10 @@ These parameters are grouped directly under the `spec` key in the manifest.
|
|||
requires a custom Spilo image. Note the FSGroup of a Pod cannot be changed
|
||||
without recreating a new Pod. Optional.
|
||||
|
||||
* **livenessProbe**
|
||||
Allows for adding a liveness probe to the Spilo container to detect if it's
|
||||
running properly.
|
||||
|
||||
* **enableMasterLoadBalancer**
|
||||
boolean flag to override the operator defaults (set by the
|
||||
`enable_master_load_balancer` parameter) to define whether to enable the load
|
||||
|
|
@ -109,16 +113,61 @@ These parameters are grouped directly under the `spec` key in the manifest.
|
|||
|
||||
* **allowedSourceRanges**
|
||||
when one or more load balancers are enabled for the cluster, this parameter
|
||||
defines the comma-separated range of IP networks (in CIDR-notation). The
|
||||
corresponding load balancer is accessible only to the networks defined by
|
||||
this parameter. Optional, when empty the load balancer service becomes
|
||||
inaccessible from outside of the Kubernetes cluster.
|
||||
defines the comma-separated range of IP networks (in CIDR-notation). Both
|
||||
IPv4 (e.g. `192.168.1.0/24`) and IPv6 (e.g. `fd01::/48`) CIDR ranges are
|
||||
supported. The corresponding load balancer is accessible only to the networks
|
||||
defined by this parameter. Optional, when empty the load balancer service
|
||||
becomes inaccessible from outside of the Kubernetes cluster.
|
||||
|
||||
* **enableMasterNodePort**
|
||||
boolean flag to override the operator defaults (set by the
|
||||
`enable_master_node_port` parameter) to define whether to enable the node
|
||||
port pointing to the Postgres primary. Optional. Overrides `enableMasterLoadBalancer`.
|
||||
|
||||
* **enableMasterPoolerNodePort**
|
||||
boolean flag to override the operator defaults (set by the
|
||||
`enable_master_pooler_node_port` parameter) to define whether to enable
|
||||
the node port for master pooler pods pointing to the Postgres primary.
|
||||
Optional. Overrides `enableMasterPoolerLoadBalancer`.
|
||||
|
||||
* **enableReplicaNodePort**
|
||||
boolean flag to override the operator defaults (set by the
|
||||
`enable_replica_node_port` parameter) to define whether to enable the node
|
||||
port pointing to the Postgres standby instances. Optional. Overrides `enableReplicaLoadBalancer`.
|
||||
|
||||
* **enableReplicaPoolerNodePort**
|
||||
boolean flag to override the operator defaults (set by the
|
||||
`enable_replica_pooler_node_port` parameter) to define whether to enable
|
||||
the node port for replica pooler pods pointing to the Postgres standby
|
||||
instances. Optional. Overrides `enableReplicaPoolerLoadBalancer`.
|
||||
|
||||
* **masterNodePort**
|
||||
integer flag to specify a port number for the node port to the Postgres primary.
|
||||
Only used when `enableMasterNodePort` or `enable_master_node_port` are enabled.
|
||||
Optional. Kubernetes will provide a port number for you if not specified.
|
||||
|
||||
* **masterPoolerNodePort**
|
||||
integer flag to specify a port number for the node port for the master pooler pods pointing to the Postgres primary.
|
||||
Only used when `enableMasterPoolerNodePort` or `enable_master_pooler_node_port` are enabled.
|
||||
Optional. Kubernetes will provide a port number for you if not specified.
|
||||
|
||||
* **replicaNodePort**
|
||||
integer flag to specify a port number for the node port pointing to the Postgres standby instances.
|
||||
Only used when `enableReplicaNodePort` or `enable_replica_node_port` are enabled.
|
||||
Optional. Kubernetes will provide a port number for you if not specified.
|
||||
|
||||
* **replicaPoolerNodePort**
|
||||
integer flag to specify a port number for the node port for the replica pooler pods pointing to the Postgres standby instances
|
||||
Only used when `enableReplicaPoolerNodePort` or `enable_replica_pooler_node_port` are enabled.
|
||||
Optional. Kubernetes will provide a port number for you if not specified.
|
||||
|
||||
* **maintenanceWindows**
|
||||
a list which defines specific time frames when certain maintenance operations
|
||||
such as automatic major upgrades or master pod migration. Accepted formats
|
||||
are "01:00-06:00" for daily maintenance windows or "Sat:00:00-04:00" for specific
|
||||
days, with all times in UTC.
|
||||
such as automatic major upgrades or master pod migration are allowed to happen.
|
||||
Accepted formats are "01:00-06:00" for daily maintenance windows or
|
||||
"Sat:00:00-04:00" for specific days, with all times in UTC. Note, when the
|
||||
global config option `enable_maintenance_windows` is false, the specified
|
||||
windows will be ignored.
|
||||
|
||||
* **users**
|
||||
a map of usernames to user flags for the users that should be created in the
|
||||
|
|
@ -457,22 +506,31 @@ under the `clone` top-level key and do not affect the already running cluster.
|
|||
|
||||
On startup, an existing `standby` top-level key creates a standby Postgres
|
||||
cluster streaming from a remote location - either from a S3 or GCS WAL
|
||||
archive or a remote primary. Only one of options is allowed and required
|
||||
if the `standby` key is present.
|
||||
archive, a remote primary, or a combination of both. At least one of
|
||||
`s3_wal_path`, `gs_wal_path`, or `standby_host` must be specified.
|
||||
Note that `s3_wal_path` and `gs_wal_path` are mutually exclusive.
|
||||
|
||||
* **s3_wal_path**
|
||||
the url to S3 bucket containing the WAL archive of the remote primary.
|
||||
Can be combined with `standby_host` for additional redundancy.
|
||||
|
||||
* **gs_wal_path**
|
||||
the url to GS bucket containing the WAL archive of the remote primary.
|
||||
Can be combined with `standby_host` for additional redundancy.
|
||||
|
||||
* **standby_host**
|
||||
hostname or IP address of the primary to stream from.
|
||||
Can be specified alone or combined with either `s3_wal_path` or `gs_wal_path`.
|
||||
|
||||
* **standby_port**
|
||||
TCP port on which the primary is listening for connections. Patroni will
|
||||
use `"5432"` if not set.
|
||||
|
||||
* **standby_primary_slot_name**
|
||||
name of the replication slot to use on the primary server when streaming
|
||||
from a remote primary. See the Patroni documentation
|
||||
[here](https://patroni.readthedocs.io/en/latest/standby_cluster.html) for more details. Optional.
|
||||
|
||||
## Volume properties
|
||||
|
||||
Those parameters are grouped under the `volume` top-level key and define the
|
||||
|
|
@ -496,11 +554,11 @@ properties of the persistent storage that stores Postgres data.
|
|||
|
||||
* **iops**
|
||||
When running the operator on AWS the latest generation of EBS volumes (`gp3`)
|
||||
allows for configuring the number of IOPS. Maximum is 16000. Optional.
|
||||
allows for configuring the number of IOPS. Maximum is 80000. Optional.
|
||||
|
||||
* **throughput**
|
||||
When running the operator on AWS the latest generation of EBS volumes (`gp3`)
|
||||
allows for configuring the throughput in MB/s. Maximum is 1000. Optional.
|
||||
allows for configuring the throughput in MB/s. Maximum is 2000. Optional.
|
||||
|
||||
* **selector**
|
||||
A label query over PVs to consider for binding. See the [Kubernetes
|
||||
|
|
@ -638,7 +696,7 @@ the global configuration before adding the `tls` section'.
|
|||
## Change data capture streams
|
||||
|
||||
This sections enables change data capture (CDC) streams via Postgres'
|
||||
[logical decoding](https://www.postgresql.org/docs/17/logicaldecoding.html)
|
||||
[logical decoding](https://www.postgresql.org/docs/18/logicaldecoding.html)
|
||||
feature and `pgoutput` plugin. While the Postgres operator takes responsibility
|
||||
for providing the setup to publish change events, it relies on external tools
|
||||
to consume them. At Zalando, we are using a workflow based on
|
||||
|
|
@ -671,7 +729,7 @@ can have the following properties:
|
|||
The CDC operator is following the [outbox pattern](https://debezium.io/blog/2019/02/19/reliable-microservices-data-exchange-with-the-outbox-pattern/).
|
||||
The application is responsible for putting events into a (JSON/B or VARCHAR)
|
||||
payload column of the outbox table in the structure of the specified target
|
||||
event type. The operator will create a [PUBLICATION](https://www.postgresql.org/docs/17/logical-replication-publication.html)
|
||||
event type. The operator will create a [PUBLICATION](https://www.postgresql.org/docs/18/logical-replication-publication.html)
|
||||
in Postgres for all tables specified for one `database` and `applicationId`.
|
||||
The CDC operator will consume from it shortly after transactions are
|
||||
committed to the outbox table. The `idColumn` will be used in telemetry for
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@ The following command-line options are supported for the operator:
|
|||
off can can be overridden by the aforementioned operator configuration
|
||||
option.
|
||||
|
||||
* **-kubeqps**
|
||||
set the maximum number of Kubernetes API requests per second. Default is 10.
|
||||
|
||||
* **-kubeburst**
|
||||
set the burst limit for Kubernetes API requests, allowing temporary spikes beyond the configured QPS. Default is 20.
|
||||
|
||||
In addition to that, standard [glog
|
||||
flags](https://godoc.org/github.com/golang/glog) are also supported. For
|
||||
instance, one may want to add `-alsologtostderr` and `-v=8` to debug the
|
||||
|
|
|
|||
|
|
@ -42,14 +42,14 @@ and change it.
|
|||
|
||||
To test the CRD-based configuration locally, use the following
|
||||
|
||||
```bash
|
||||
kubectl create -f manifests/operatorconfiguration.crd.yaml # registers the CRD
|
||||
kubectl create -f manifests/postgresql-operator-default-configuration.yaml
|
||||
```
|
||||
kubectl create -f manifests/operatorconfiguration.crd.yaml # registers the CRD
|
||||
kubectl create -f manifests/postgresql-operator-default-configuration.yaml
|
||||
|
||||
kubectl create -f manifests/operator-service-account-rbac.yaml
|
||||
kubectl create -f manifests/postgres-operator.yaml # set the env var as mentioned above
|
||||
kubectl create -f manifests/operator-service-account-rbac.yaml
|
||||
kubectl create -f manifests/postgres-operator.yaml # set the env var as mentioned above
|
||||
|
||||
kubectl get operatorconfigurations postgresql-operator-default-configuration -o yaml
|
||||
kubectl get operatorconfigurations postgresql-operator-default-configuration -o yaml
|
||||
```
|
||||
|
||||
The CRD-based configuration is more powerful than the one based on ConfigMaps
|
||||
|
|
@ -79,11 +79,6 @@ Those are top-level keys, containing both leaf keys and groups.
|
|||
Instruct the operator to create/update the CRDs. If disabled the operator will rely on the CRDs being managed separately.
|
||||
The default is `true`.
|
||||
|
||||
* **enable_crd_validation**
|
||||
*deprecated*: toggles if the operator will create or update CRDs with
|
||||
[OpenAPI v3 schema validation](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#validation)
|
||||
The default is `true`. `false` will be ignored, since `apiextensions.io/v1` requires a structural schema definition.
|
||||
|
||||
* **crd_categories**
|
||||
The operator will register CRDs in the `all` category by default so that they will be returned by a `kubectl get all` call. You are free to change categories or leave them empty.
|
||||
|
||||
|
|
@ -105,15 +100,13 @@ Those are top-level keys, containing both leaf keys and groups.
|
|||
Kubernetes-native DCS).
|
||||
|
||||
* **kubernetes_use_configmaps**
|
||||
Select if setup uses endpoints (default), or configmaps to manage leader when
|
||||
Select if setup uses endpoints or configmaps (default) to manage leader when
|
||||
DCS is kubernetes (not etcd or similar). In OpenShift it is not possible to
|
||||
use endpoints option, and configmaps is required. Starting with K8s 1.33,
|
||||
endpoints are marked as deprecated. It's recommended to switch to config maps
|
||||
instead. But, to do so make sure you scale the Postgres cluster down to just
|
||||
one primary pod (e.g. using `max_instances` option). Otherwise, you risk
|
||||
running into a split-brain scenario.
|
||||
By default, `kubernetes_use_configmaps: false`, meaning endpoints will be used.
|
||||
Starting from v1.16.0 the default will be changed to `true`.
|
||||
running into a split-brain scenario. Default is `true`.
|
||||
|
||||
* **docker_image**
|
||||
Spilo Docker image for Postgres instances. For production, don't rely on the
|
||||
|
|
@ -163,7 +156,26 @@ Those are top-level keys, containing both leaf keys and groups.
|
|||
for some clusters it might be required to scale beyond the limits that can be
|
||||
configured with `min_instances` and `max_instances` options. You can define
|
||||
an annotation key that can be used as a toggle in cluster manifests to ignore
|
||||
globally configured instance limits. The default is empty.
|
||||
globally configured instance limits. The value must be `"true"` to be
|
||||
effective. The default is empty which means the feature is disabled.
|
||||
|
||||
* **ignore_resources_limits_annotation_key**
|
||||
for some clusters it might be required to request resources beyond the globally
|
||||
configured thresholds for maximum requests and minimum limits. You can define
|
||||
an annotation key that can be used as a toggle in cluster manifests to ignore
|
||||
the thresholds. The value must be `"true"` to be effective. The default is empty
|
||||
which means the feature is disabled.
|
||||
|
||||
* **enable_maintenance_windows**
|
||||
toggle for using the maintenance windows feature. Default is `"true"`.
|
||||
|
||||
* **maintenance_windows**
|
||||
a list which defines specific time frames when certain maintenance
|
||||
operations such as automatic major upgrades or master pod migration are
|
||||
allowed to happen for all database clusters. Accepted formats are
|
||||
"01:00-06:00" for daily maintenance windows or "Sat:00:00-04:00" for
|
||||
specific days, with all times in UTC. Locally defined maintenance
|
||||
windows take precedence over globally configured ones.
|
||||
|
||||
* **resync_period**
|
||||
period between consecutive sync requests. The default is `30m`.
|
||||
|
|
@ -252,12 +264,12 @@ CRD-configuration, they are grouped under the `major_version_upgrade` key.
|
|||
|
||||
* **minimal_major_version**
|
||||
The minimal Postgres major version that will not automatically be upgraded
|
||||
when `major_version_upgrade_mode` is set to `"full"`. The default is `"13"`.
|
||||
when `major_version_upgrade_mode` is set to `"full"`. The default is `"14"`.
|
||||
|
||||
* **target_major_version**
|
||||
The target Postgres major version when upgrading clusters automatically
|
||||
which violate the configured allowed `minimal_major_version` when
|
||||
`major_version_upgrade_mode` is set to `"full"`. The default is `"17"`.
|
||||
`major_version_upgrade_mode` is set to `"full"`. The default is `"18"`.
|
||||
|
||||
## Kubernetes resources
|
||||
|
||||
|
|
@ -315,6 +327,10 @@ configuration they are grouped under the `kubernetes` key.
|
|||
Postgres pods are [terminated forcefully](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination)
|
||||
after this timeout. The default is `5m`.
|
||||
|
||||
* **liveness_probe**
|
||||
Allows for adding a liveness probe to the Spilo container to detect if it's
|
||||
running properly. Cannot be configured via ConfigMap. Default is empty.
|
||||
|
||||
* **custom_pod_annotations**
|
||||
This key/value map provides a list of annotations that get attached to each pod
|
||||
of a database created by the operator. If the annotation key is also provided
|
||||
|
|
@ -566,7 +582,7 @@ configuration they are grouped under the `kubernetes` key.
|
|||
1. `ebs` : operator resizes EBS volumes directly and executes `resizefs` within a pod
|
||||
2. `pvc` : operator only changes PVC definition
|
||||
3. `off` : disables resize of the volumes.
|
||||
4. `mixed` : operator uses AWS API to adjust size, throughput, and IOPS, and calls pvc change for file system resize
|
||||
4. `mixed` : operator uses AWS API to adjust size, type, throughput, and IOPS, and calls pvc change for file system resize
|
||||
Default is "pvc".
|
||||
|
||||
## Kubernetes resource requests
|
||||
|
|
@ -782,6 +798,15 @@ yet officially supported.
|
|||
[kube2iam](https://github.com/jtblin/kube2iam) project on AWS. The default is
|
||||
empty.
|
||||
|
||||
* **irsa_role_arn**
|
||||
Full AWS IAM role ARN to supply in the `eks.amazonaws.com/role-arn` annotation
|
||||
of the Postgres pod service account, enabling
|
||||
[IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)
|
||||
(IAM Roles for Service Accounts) on EKS. When set, the operator annotates the
|
||||
pod service account on every sync so that the EKS OIDC webhook can inject AWS
|
||||
credentials directly into pods. Must be a full ARN, e.g.
|
||||
`arn:aws:iam::123456789012:role/my-postgres-role`. The default is empty.
|
||||
|
||||
* **aws_region**
|
||||
AWS region used to store EBS volumes. The default is `eu-central-1`. Note,
|
||||
this option is not meant for specifying the AWS region for backups and
|
||||
|
|
@ -796,16 +821,6 @@ yet officially supported.
|
|||
Path to mount the above Secret in the filesystem of the container(s).
|
||||
The default is empty.
|
||||
|
||||
* **enable_ebs_gp3_migration**
|
||||
enable automatic migration on AWS from gp2 to gp3 volumes, that are smaller
|
||||
than the configured max size (see below). This ignores that EBS gp3 is by
|
||||
default only 125 MB/sec vs 250 MB/sec for gp2 >= 333GB.
|
||||
The default is `false`.
|
||||
|
||||
* **enable_ebs_gp3_migration_max_size**
|
||||
defines the maximum volume size in GB until which auto migration happens.
|
||||
Default is 1000 (1TB) which matches 3000 IOPS.
|
||||
|
||||
## Logical backup
|
||||
|
||||
These parameters configure a K8s cron job managed by the operator to produce
|
||||
|
|
@ -824,7 +839,7 @@ grouped under the `logical_backup` key.
|
|||
runs `pg_dumpall` on a replica if possible and uploads compressed results to
|
||||
an S3 bucket under the key `/<configured-s3-bucket-prefix>/<pg_cluster_name>/<cluster_k8s_uuid>/logical_backups`.
|
||||
The default image is the same image built with the Zalando-internal CI
|
||||
pipeline. Default: "ghcr.io/zalando/postgres-operator/logical-backup:v1.15.1"
|
||||
pipeline. Default: "ghcr.io/zalando/postgres-operator/logical-backup:v2.0.0"
|
||||
|
||||
* **logical_backup_google_application_credentials**
|
||||
Specifies the path of the google cloud service account json file. Default is empty.
|
||||
|
|
@ -881,6 +896,28 @@ grouped under the `logical_backup` key.
|
|||
* **logical_backup_cronjob_environment_secret**
|
||||
Reference to a Kubernetes secret, which keys will be added as environment variables to the cronjob. Default: ""
|
||||
|
||||
* **logical_backup_successful_jobs_history_limit**
|
||||
number of successful backup jobs to keep in cronjob history. The default is `3`.
|
||||
|
||||
* **logical_backup_failed_jobs_history_limit**
|
||||
number of failed backup jobs to keep in cronjob history. The default is `3`.
|
||||
|
||||
* **logical_backup_ttl_seconds_after_finished**
|
||||
TTL in seconds after which finished backup jobs are automatically deleted. The default is `86400`.
|
||||
|
||||
The following environment variables can be passed to the logical backup
|
||||
cronjob via `logical_backup_cronjob_environment_secret` to control
|
||||
connectivity checks before the backup starts:
|
||||
|
||||
* **LOGICAL_BACKUP_CONNECT_RETRIES**
|
||||
Number of times to retry connecting to the target PostgreSQL pod before
|
||||
giving up. This is useful when NetworkPolicy enforcement introduces a
|
||||
short delay before a newly-created pod's IP is allowed through ingress
|
||||
rules on the destination node. Default: "10"
|
||||
|
||||
* **LOGICAL_BACKUP_CONNECT_RETRY_DELAY**
|
||||
Delay in seconds between connectivity retries. Default: "2"
|
||||
|
||||
## Debugging the operator
|
||||
|
||||
Options to aid debugging of the operator itself. Grouped under the `debug` key.
|
||||
|
|
@ -1043,7 +1080,7 @@ operator being able to provide some reasonable defaults.
|
|||
|
||||
* **connection_pooler_image**
|
||||
Docker image to use for connection pooler deployment.
|
||||
Default: "registry.opensource.zalan.do/acid/pgbouncer"
|
||||
Default: "ghcr.io/zalando/postgres-operator/pgbouncer:latest"
|
||||
|
||||
* **connection_pooler_max_db_connections**
|
||||
How many connections the pooler can max hold. This value is divided among the
|
||||
|
|
|
|||
61
docs/user.md
61
docs/user.md
|
|
@ -30,7 +30,7 @@ spec:
|
|||
databases:
|
||||
foo: zalando
|
||||
postgresql:
|
||||
version: "17"
|
||||
version: "18"
|
||||
```
|
||||
|
||||
Once you cloned the Postgres Operator [repository](https://github.com/zalando/postgres-operator)
|
||||
|
|
@ -96,10 +96,7 @@ psql -U postgres -h localhost -p 6432
|
|||
|
||||
## Password encryption
|
||||
|
||||
Passwords are encrypted with `md5` hash generation by default. However, it is
|
||||
possible to use the more recent `scram-sha-256` method by changing the
|
||||
`password_encryption` parameter in the Postgres config. You can define it
|
||||
directly from the cluster manifest:
|
||||
Passwords are encrypted using the `scram-sha-256` hashing method by default. Other methods can be configured by changing the `password_encryption` parameter in the cluster manifest:
|
||||
|
||||
```yaml
|
||||
apiVersion: "acid.zalan.do/v1"
|
||||
|
|
@ -109,7 +106,7 @@ metadata:
|
|||
spec:
|
||||
[...]
|
||||
postgresql:
|
||||
version: "17"
|
||||
version: "18"
|
||||
parameters:
|
||||
password_encryption: scram-sha-256
|
||||
```
|
||||
|
|
@ -517,7 +514,7 @@ Postgres Operator will create the following NOLOGIN roles:
|
|||
|
||||
The `<dbname>_owner` role is the database owner and should be used when creating
|
||||
new database objects. All members of the `admin` role, e.g. teams API roles, can
|
||||
become the owner with the `SET ROLE` command. [Default privileges](https://www.postgresql.org/docs/17/sql-alterdefaultprivileges.html)
|
||||
become the owner with the `SET ROLE` command. [Default privileges](https://www.postgresql.org/docs/18/sql-alterdefaultprivileges.html)
|
||||
are configured for the owner role so that the `<dbname>_reader` role
|
||||
automatically gets read-access (SELECT) to new tables and sequences and the
|
||||
`<dbname>_writer` receives write-access (INSERT, UPDATE, DELETE on tables,
|
||||
|
|
@ -594,7 +591,7 @@ spec:
|
|||
|
||||
### Schema `search_path` for default roles
|
||||
|
||||
The schema [`search_path`](https://www.postgresql.org/docs/17/ddl-schemas.html#DDL-SCHEMAS-PATH)
|
||||
The schema [`search_path`](https://www.postgresql.org/docs/18/ddl-schemas.html#DDL-SCHEMAS-PATH)
|
||||
for each role will include the role name and the schemas, this role should have
|
||||
access to. So `foo_bar_writer` does not have to schema-qualify tables from
|
||||
schemas `foo_bar_writer, bar`, while `foo_writer` can look up `foo_writer` and
|
||||
|
|
@ -695,7 +692,7 @@ handle it.
|
|||
|
||||
### HugePages support
|
||||
|
||||
The operator supports [HugePages](https://www.postgresql.org/docs/17/kernel-resources.html#LINUX-HUGEPAGES).
|
||||
The operator supports [HugePages](https://www.postgresql.org/docs/18/kernel-resources.html#LINUX-HUGEPAGES).
|
||||
To enable HugePages, set the matching resource requests and/or limits in the manifest:
|
||||
|
||||
```yaml
|
||||
|
|
@ -714,7 +711,7 @@ but Kubernetes will not spin up the pod if the requested HugePages cannot be all
|
|||
For more information on HugePages in Kubernetes, see also
|
||||
[https://kubernetes.io/docs/tasks/manage-hugepages/scheduling-hugepages/](https://kubernetes.io/docs/tasks/manage-hugepages/scheduling-hugepages/)
|
||||
|
||||
## Use taints, tolerations and node affinity for dedicated PostgreSQL nodes
|
||||
## Use taints, tolerations, node affinity and topology spread constraint for dedicated PostgreSQL nodes
|
||||
|
||||
To ensure Postgres pods are running on nodes without any other application pods,
|
||||
you can use [taints and tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/)
|
||||
|
|
@ -755,9 +752,26 @@ spec:
|
|||
If you need to define a `nodeAffinity` for all your Postgres clusters use the
|
||||
`node_readiness_label` [configuration](administrator.md#node-readiness-labels).
|
||||
|
||||
If you need PostgreSQL Pods to run on separate nodes, you can use the
|
||||
[topologySpreadConstraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/) to control how they are distributed across your cluster.
|
||||
This ensures they are spread among failure domains such as
|
||||
regions, zones, nodes, or other user-defined topology domains.
|
||||
|
||||
```yaml
|
||||
apiVersion: "acid.zalan.do/v1"
|
||||
kind: postgresql
|
||||
metadata:
|
||||
name: acid-minimal-cluster
|
||||
spec:
|
||||
topologySpreadConstraints:
|
||||
- maxskew: 1
|
||||
topologyKey: topology.kubernetes.io/zone
|
||||
whenUnsatisfiable: DoNotSchedule
|
||||
```
|
||||
|
||||
## In-place major version upgrade
|
||||
|
||||
Starting with Spilo 13, operator supports in-place major version upgrade to a
|
||||
Starting with Spilo 14, operator supports in-place major version upgrade to a
|
||||
higher major version (e.g. from PG 14 to PG 16). To trigger the upgrade,
|
||||
simply increase the version in the manifest. It is your responsibility to test
|
||||
your applications against the new version before the upgrade; downgrading is
|
||||
|
|
@ -792,7 +806,7 @@ spec:
|
|||
clone:
|
||||
uid: "efd12e58-5786-11e8-b5a7-06148230260c"
|
||||
cluster: "acid-minimal-cluster"
|
||||
timestamp: "2017-12-19T12:40:33+01:00"
|
||||
timestamp: "2025-12-19T12:40:33+01:00"
|
||||
```
|
||||
|
||||
Here `cluster` is a name of a source cluster that is going to be cloned. A new
|
||||
|
|
@ -827,7 +841,7 @@ spec:
|
|||
clone:
|
||||
uid: "efd12e58-5786-11e8-b5a7-06148230260c"
|
||||
cluster: "acid-minimal-cluster"
|
||||
timestamp: "2017-12-19T12:40:33+01:00"
|
||||
timestamp: "2025-12-19T12:40:33+01:00"
|
||||
s3_wal_path: "s3://custom/path/to/bucket"
|
||||
s3_endpoint: https://s3.acme.org
|
||||
s3_access_key_id: 0123456789abcdef0123456789abcdef
|
||||
|
|
@ -838,7 +852,7 @@ spec:
|
|||
### Clone directly
|
||||
|
||||
Another way to get a fresh copy of your source DB cluster is via
|
||||
[pg_basebackup](https://www.postgresql.org/docs/17/app-pgbasebackup.html). To
|
||||
[pg_basebackup](https://www.postgresql.org/docs/18/app-pgbasebackup.html). To
|
||||
use this feature simply leave out the timestamp field from the clone section.
|
||||
The operator will connect to the service of the source cluster by name. If the
|
||||
cluster is called test, then the connection string will look like host=test
|
||||
|
|
@ -900,8 +914,9 @@ the PostgreSQL version between source and target cluster has to be the same.
|
|||
|
||||
To start a cluster as standby, add the following `standby` section in the YAML
|
||||
file. You can stream changes from archived WAL files (AWS S3 or Google Cloud
|
||||
Storage) or from a remote primary. Only one option can be specified in the
|
||||
manifest:
|
||||
Storage), from a remote primary, or combine a remote primary with a WAL archive.
|
||||
At least one of `s3_wal_path`, `gs_wal_path`, or `standby_host` must be specified.
|
||||
Note that `s3_wal_path` and `gs_wal_path` are mutually exclusive.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
|
|
@ -929,6 +944,16 @@ spec:
|
|||
standby_port: "5433"
|
||||
```
|
||||
|
||||
You can also combine a remote primary with a WAL archive for additional redundancy:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
standby:
|
||||
standby_host: "acid-minimal-cluster.default"
|
||||
standby_port: "5433"
|
||||
s3_wal_path: "s3://<bucketname>/spilo/<source_db_cluster>/<UID>/wal/<PGVERSION>"
|
||||
```
|
||||
|
||||
Note, that the pods and services use the same role labels like for normal clusters:
|
||||
The standby leader is labeled as `master`. When using the `standby_host` option
|
||||
you have to copy the credentials from the source cluster's secrets to successfully
|
||||
|
|
@ -1053,7 +1078,7 @@ spec:
|
|||
- all
|
||||
volumeSource:
|
||||
emptyDir: {}
|
||||
sidecars:
|
||||
sidecars:
|
||||
- name: "container-name"
|
||||
image: "company/image:tag"
|
||||
volumeMounts:
|
||||
|
|
@ -1104,7 +1129,7 @@ When using AWS with gp3 volumes you should set the mode to `mixed` because it
|
|||
will also adjust the IOPS and throughput that can be defined in the manifest.
|
||||
Check the [AWS docs](https://aws.amazon.com/ebs/general-purpose/) to learn
|
||||
about default and maximum values. Keep in mind that AWS rate-limits updating
|
||||
volume specs to no more than once every 6 hours.
|
||||
volume specs to no more than 4 times within 24 hours.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
FROM python:3.11-slim
|
||||
LABEL maintainer="Team ACID @ Zalando <team-acid@zalando.de>"
|
||||
|
||||
ENV TERM xterm-256color
|
||||
ENV TERM=xterm-256color
|
||||
|
||||
RUN apt-get -qq -y update \
|
||||
# https://www.psycopg.org/docs/install.html#psycopg-vs-psycopg-binary
|
||||
&& apt-get -qq -y install --no-install-recommends curl vim python3-dev \
|
||||
&& curl -LO https://dl.k8s.io/release/v1.32.9/bin/linux/amd64/kubectl \
|
||||
&& curl -LO https://dl.k8s.io/release/v1.36.1/bin/linux/amd64/kubectl \
|
||||
&& chmod +x ./kubectl \
|
||||
&& mv ./kubectl /usr/local/bin/kubectl \
|
||||
&& apt-get -qq -y clean \
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ copy: clean
|
|||
mkdir tls
|
||||
|
||||
docker:
|
||||
docker build -t "$(IMAGE):$(TAG)" .
|
||||
docker buildx build --load --rm -t "$(IMAGE):$(TAG)" .
|
||||
|
||||
push: docker
|
||||
docker push "$(IMAGE):$(TAG)"
|
||||
|
|
@ -46,7 +46,7 @@ tools:
|
|||
# install pinned version of 'kind'
|
||||
# go install must run outside of a dir with a (module-based) Go project !
|
||||
# otherwise go install updates project's dependencies and/or behaves differently
|
||||
cd "/tmp" && GO111MODULE=on go install sigs.k8s.io/kind@v0.27.0
|
||||
cd "/tmp" && go install sigs.k8s.io/kind@v0.27.0
|
||||
|
||||
e2etest: tools copy clean
|
||||
./run.sh main
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
export cluster_name="postgres-operator-e2e-tests"
|
||||
export kubeconfig_path="/tmp/kind-config-${cluster_name}"
|
||||
export operator_image="ghcr.io/zalando/postgres-operator:latest"
|
||||
export pooler_image="ghcr.io/zalando/postgres-operator/pgbouncer:latest"
|
||||
export e2e_test_runner_image="ghcr.io/zalando/postgres-operator-e2e-tests-runner:latest"
|
||||
|
||||
docker run -it --entrypoint /bin/bash --network=host -e "TERM=xterm-256color" \
|
||||
|
|
@ -11,4 +12,5 @@ docker run -it --entrypoint /bin/bash --network=host -e "TERM=xterm-256color" \
|
|||
--mount type=bind,source="$(readlink -f tests)",target=/tests \
|
||||
--mount type=bind,source="$(readlink -f exec.sh)",target=/exec.sh \
|
||||
--mount type=bind,source="$(readlink -f scripts)",target=/scripts \
|
||||
-e OPERATOR_IMAGE="${operator_image}" "${e2e_test_runner_image}"
|
||||
-e OPERATOR_IMAGE="${operator_image}" -e POOLER_IMAGE="${pooler_image}" \
|
||||
"${e2e_test_runner_image}"
|
||||
|
|
|
|||
70
e2e/run.sh
70
e2e/run.sh
|
|
@ -7,23 +7,61 @@ set -o pipefail
|
|||
IFS=$'\n\t'
|
||||
|
||||
readonly cluster_name="postgres-operator-e2e-tests"
|
||||
readonly kubeconfig_path="/tmp/kind-config-${cluster_name}"
|
||||
readonly spilo_image="registry.opensource.zalan.do/acid/spilo-17-e2e:0.3"
|
||||
readonly kubeconfig_path="${HOME}/kind-config-${cluster_name}"
|
||||
readonly spilo_image="ghcr.io/zalando/spilo-18:4.1-p2"
|
||||
readonly e2e_test_runner_image="ghcr.io/zalando/postgres-operator-e2e-tests-runner:latest"
|
||||
|
||||
export GOPATH=${GOPATH-~/go}
|
||||
export PATH=${GOPATH}/bin:$PATH
|
||||
|
||||
# detect system architecture for pulling the correct Spilo image
|
||||
case "$(uname -m)" in
|
||||
x86_64) readonly PLATFORM="linux/amd64" ;;
|
||||
aarch64|arm64) readonly PLATFORM="linux/arm64" ;;
|
||||
*) echo "Unsupported architecture: $(uname -m)"; exit 1 ;;
|
||||
esac
|
||||
|
||||
echo "Clustername: ${cluster_name}"
|
||||
echo "Kubeconfig path: ${kubeconfig_path}"
|
||||
|
||||
# Helper function to pull images directly into kind nodes via crictl (bypasses host disk duplication)
|
||||
function pull_on_kind_nodes() {
|
||||
local img="$1"
|
||||
echo "Pulling ${img} directly on kind nodes..."
|
||||
for node in $(kind get nodes --name "${cluster_name}"); do
|
||||
docker exec "$node" crictl pull "${img}"
|
||||
done
|
||||
}
|
||||
|
||||
function pull_images(){
|
||||
operator_tag=$(git describe --tags --always --dirty)
|
||||
if [[ -z $(docker images -q ghcr.io/zalando/postgres-operator:${operator_tag}) ]]
|
||||
then
|
||||
docker pull ghcr.io/zalando/postgres-operator:latest
|
||||
fi
|
||||
operator_image=$(docker images --filter=reference="ghcr.io/zalando/postgres-operator" --format "{{.Repository}}:{{.Tag}}" | head -1)
|
||||
components=("postgres-operator" "pooler")
|
||||
image_urls=("ghcr.io/zalando/postgres-operator:${operator_tag}" "ghcr.io/zalando/postgres-operator/pgbouncer:${operator_tag}")
|
||||
|
||||
for i in "${!components[@]}"; do
|
||||
component="${components[$i]}"
|
||||
image="${image_urls[$i]}"
|
||||
|
||||
if [[ -z $(docker images -q "$image") ]]; then
|
||||
echo "Pulling $component image: $image"
|
||||
if ! docker pull "$image"; then
|
||||
echo "Failed to pull $component image: $image"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "$component image already exists: $image"
|
||||
fi
|
||||
|
||||
# Set variables for later use
|
||||
if [[ "$component" == "postgres-operator" ]]; then
|
||||
operator_image="$image"
|
||||
elif [[ "$component" == "pooler" ]]; then
|
||||
pooler_image="$image"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Using operator image: $operator_image"
|
||||
echo "Using pooler image: $pooler_image"
|
||||
}
|
||||
|
||||
function start_kind(){
|
||||
|
|
@ -36,14 +74,17 @@ function start_kind(){
|
|||
|
||||
export KUBECONFIG="${kubeconfig_path}"
|
||||
kind create cluster --name ${cluster_name} --config kind-cluster-postgres-operator-e2e-tests.yaml
|
||||
docker pull "${spilo_image}"
|
||||
kind load docker-image "${spilo_image}" --name ${cluster_name}
|
||||
|
||||
echo "Pulling Spilo image on kind nodes directly..."
|
||||
pull_on_kind_nodes "${spilo_image}"
|
||||
}
|
||||
|
||||
function load_operator_image() {
|
||||
echo "Loading operator image"
|
||||
function load_operator_images() {
|
||||
echo "Loading operator images"
|
||||
export KUBECONFIG="${kubeconfig_path}"
|
||||
# For locally built operator images, kind load is still fine since they're light
|
||||
kind load docker-image "${operator_image}" --name ${cluster_name}
|
||||
kind load docker-image "${pooler_image}" --name ${cluster_name}
|
||||
}
|
||||
|
||||
function set_kind_api_server_ip(){
|
||||
|
|
@ -52,7 +93,7 @@ function set_kind_api_server_ip(){
|
|||
# but update the IP address of the API server to the one from the Docker 'bridge' network
|
||||
readonly local kind_api_server_port=6443 # well-known in the 'kind' codebase
|
||||
readonly local kind_api_server=$(docker inspect --format "{{ .NetworkSettings.Networks.kind.IPAddress }}:${kind_api_server_port}" "${cluster_name}"-control-plane)
|
||||
sed -i "s/server.*$/server: https:\/\/$kind_api_server/g" "${kubeconfig_path}"
|
||||
sed "s/server.*$/server: https:\/\/$kind_api_server/g" "${kubeconfig_path}" > "${kubeconfig_path}".tmp && mv "${kubeconfig_path}".tmp "${kubeconfig_path}"
|
||||
}
|
||||
|
||||
function generate_certificate(){
|
||||
|
|
@ -70,7 +111,8 @@ function run_tests(){
|
|||
--mount type=bind,source="$(readlink -f tests)",target=/tests \
|
||||
--mount type=bind,source="$(readlink -f exec.sh)",target=/exec.sh \
|
||||
--mount type=bind,source="$(readlink -f scripts)",target=/scripts \
|
||||
-e OPERATOR_IMAGE="${operator_image}" "${e2e_test_runner_image}" ${E2E_TEST_CASE-} $@
|
||||
-e OPERATOR_IMAGE="${operator_image}" -e POOLER_IMAGE="${pooler_image}" \
|
||||
"${e2e_test_runner_image}" ${E2E_TEST_CASE-} $@
|
||||
}
|
||||
|
||||
function cleanup(){
|
||||
|
|
@ -85,7 +127,7 @@ function main(){
|
|||
[[ -z ${NOCLEANUP-} ]] && trap "cleanup" QUIT TERM EXIT
|
||||
pull_images
|
||||
[[ ! -f ${kubeconfig_path} ]] && start_kind
|
||||
load_operator_image
|
||||
load_operator_images
|
||||
set_kind_api_server_ip
|
||||
generate_certificate
|
||||
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ class K8s:
|
|||
time.sleep(self.RETRY_TIMEOUT_SEC)
|
||||
|
||||
def get_logical_backup_job(self, namespace='default'):
|
||||
return self.api.batch_v1.list_namespaced_cron_job(namespace, label_selector="application=spilo")
|
||||
return self.api.batch_v1.list_namespaced_cron_job(namespace, label_selector="application=spilo-logical-backup")
|
||||
|
||||
def wait_for_logical_backup_job(self, expected_num_of_jobs):
|
||||
while (len(self.get_logical_backup_job().items) != expected_num_of_jobs):
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ from kubernetes import client
|
|||
from tests.k8s_api import K8s
|
||||
from kubernetes.client.rest import ApiException
|
||||
|
||||
SPILO_CURRENT = "registry.opensource.zalan.do/acid/spilo-17-e2e:0.3"
|
||||
SPILO_LAZY = "registry.opensource.zalan.do/acid/spilo-17-e2e:0.4"
|
||||
SPILO_FULL_IMAGE = "ghcr.io/zalando/spilo-17:4.0-p3"
|
||||
SPILO_CURRENT = "ghcr.io/zalando/spilo-e2e:dev-18.3"
|
||||
SPILO_LAZY = "ghcr.io/zalando/spilo-e2e:dev-18.4"
|
||||
SPILO_FULL_IMAGE = "ghcr.io/zalando/spilo-18:4.1-p2"
|
||||
|
||||
def to_selector(labels):
|
||||
return ",".join(["=".join(lbl) for lbl in labels.items()])
|
||||
|
|
@ -116,6 +116,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
configmap["data"]["workers"] = "1"
|
||||
configmap["data"]["docker_image"] = SPILO_CURRENT
|
||||
configmap["data"]["major_version_upgrade_mode"] = "full"
|
||||
configmap["data"]["connection_pooler_image"] = os.environ['POOLER_IMAGE']
|
||||
|
||||
with open("manifests/configmap.yaml", 'w') as f:
|
||||
yaml.dump(configmap, f, Dumper=yaml.Dumper)
|
||||
|
|
@ -151,6 +152,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
'default', label_selector='name=postgres-operator').items[0].spec.containers[0].image
|
||||
print("Tested operator image: {}".format(actual_operator_image)) # shows up after tests finish
|
||||
|
||||
# load minimal Postgres manifest and wait for cluster to be up and running
|
||||
result = k8s.create_with_kubectl("manifests/minimal-postgres-manifest.yaml")
|
||||
print('stdout: {}, stderr: {}'.format(result.stdout, result.stderr))
|
||||
try:
|
||||
|
|
@ -559,7 +561,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
|
||||
pg_patch_config["spec"]["patroni"]["slots"][slot_to_change]["database"] = "bar"
|
||||
del pg_patch_config["spec"]["patroni"]["slots"][slot_to_remove]
|
||||
|
||||
|
||||
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||
"acid.zalan.do", "v1", "default", "postgresqls", "acid-minimal-cluster", pg_delete_slot_patch)
|
||||
|
||||
|
|
@ -576,7 +578,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
|
||||
self.eventuallyEqual(lambda: self.query_database(leader.metadata.name, "postgres", get_slot_query%("database", slot_to_change))[0], "bar",
|
||||
"The replication slot cannot be updated", 10, 5)
|
||||
|
||||
|
||||
# make sure slot from Patroni didn't get deleted
|
||||
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", get_slot_query%("slot_name", patroni_slot))), 1,
|
||||
"The replication slot from Patroni gets deleted", 10, 5)
|
||||
|
|
@ -697,7 +699,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
self.eventuallyEqual(lambda: k8s.count_running_pods(master_pooler_label), 2, "No pooler pods found")
|
||||
self.eventuallyEqual(lambda: k8s.count_running_pods(replica_pooler_label), 2, "No pooler replica pods found")
|
||||
self.eventuallyEqual(lambda: k8s.count_services_with_label(pooler_label), 2, "No pooler service found")
|
||||
self.eventuallyEqual(lambda: k8s.count_secrets_with_label(pooler_label), 1, "Pooler secret not created")
|
||||
self.eventuallyEqual(lambda: k8s.count_secrets_with_label(pooler_label), 3, "Not all pooler secrets found")
|
||||
|
||||
# TLS still enabled so check existing env variables and volume mounts
|
||||
self.eventuallyEqual(lambda: k8s.count_pods_with_env_variable("CONNECTION_POOLER_CLIENT_TLS_CRT", pooler_label), 4, "TLS env variable CONNECTION_POOLER_CLIENT_TLS_CRT missing in pooler pods")
|
||||
|
|
@ -723,14 +725,12 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
|
||||
master_annotations = {
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-minimal-cluster-pooler.default.db.example.com",
|
||||
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
|
||||
}
|
||||
self.eventuallyTrue(lambda: k8s.check_service_annotations(
|
||||
master_pooler_label+","+pooler_label, master_annotations), "Wrong annotations")
|
||||
|
||||
replica_annotations = {
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-minimal-cluster-pooler-repl.default.db.example.com",
|
||||
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
|
||||
}
|
||||
self.eventuallyTrue(lambda: k8s.check_service_annotations(
|
||||
replica_pooler_label+","+pooler_label, replica_annotations), "Wrong annotations")
|
||||
|
|
@ -757,7 +757,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
self.eventuallyEqual(lambda: k8s.count_services_with_label(pooler_label),
|
||||
1, "No pooler service found")
|
||||
self.eventuallyEqual(lambda: k8s.count_secrets_with_label(pooler_label),
|
||||
1, "Secret not created")
|
||||
2, "Not all pooler secrets created")
|
||||
|
||||
# Turn off only replica connection pooler
|
||||
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||
|
|
@ -785,7 +785,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
'ClusterIP',
|
||||
"Expected LoadBalancer service type for master, found {}")
|
||||
self.eventuallyEqual(lambda: k8s.count_secrets_with_label(pooler_label),
|
||||
1, "Secret not created")
|
||||
2, "Not all pooler secrets created")
|
||||
|
||||
# scale up connection pooler deployment
|
||||
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||
|
|
@ -820,8 +820,8 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
0, "Pooler pods not scaled down")
|
||||
self.eventuallyEqual(lambda: k8s.count_services_with_label(pooler_label),
|
||||
0, "Pooler service not removed")
|
||||
self.eventuallyEqual(lambda: k8s.count_secrets_with_label('application=spilo,cluster-name=acid-minimal-cluster'),
|
||||
4, "Secrets not deleted")
|
||||
self.eventuallyEqual(lambda: k8s.count_secrets_with_label(pooler_label),
|
||||
0, "Not all pooler secrets deleted")
|
||||
|
||||
# Verify that all the databases have pooler schema installed.
|
||||
# Do this via psql, since otherwise we need to deal with
|
||||
|
|
@ -932,7 +932,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
old_sts_creation_timestamp = sts.metadata.creation_timestamp
|
||||
k8s.api.apps_v1.patch_namespaced_stateful_set(sts.metadata.name, sts.metadata.namespace, annotation_patch)
|
||||
old_svc_creation_timestamp = svc.metadata.creation_timestamp
|
||||
|
|
@ -1211,25 +1211,25 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
k8s.create_with_kubectl("manifests/minimal-postgres-lowest-version-manifest.yaml")
|
||||
self.eventuallyEqual(lambda: k8s.count_running_pods(labels=cluster_label), 2, "No 2 pods running")
|
||||
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
|
||||
self.eventuallyEqual(check_version, 13, "Version is not correct")
|
||||
self.eventuallyEqual(check_version, 14, "Version is not correct")
|
||||
|
||||
master_nodes, _ = k8s.get_cluster_nodes(cluster_labels=cluster_label)
|
||||
# should upgrade immediately
|
||||
pg_patch_version_14 = {
|
||||
pg_patch_version_higher_version = {
|
||||
"spec": {
|
||||
"postgresql": {
|
||||
"version": "14"
|
||||
"version": "15"
|
||||
}
|
||||
}
|
||||
}
|
||||
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||
"acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_14)
|
||||
"acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_higher_version)
|
||||
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
|
||||
|
||||
k8s.wait_for_pod_failover(master_nodes, 'spilo-role=replica,' + cluster_label)
|
||||
k8s.wait_for_pod_start('spilo-role=master,' + cluster_label)
|
||||
k8s.wait_for_pod_start('spilo-role=replica,' + cluster_label)
|
||||
self.eventuallyEqual(check_version, 14, "Version should be upgraded from 13 to 14")
|
||||
self.eventuallyEqual(check_version, 15, "Version should be upgraded from 14 to 15")
|
||||
|
||||
# check if annotation for last upgrade's success is set
|
||||
annotations = get_annotations()
|
||||
|
|
@ -1238,10 +1238,10 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
# should not upgrade because current time is not in maintenanceWindow
|
||||
current_time = datetime.now()
|
||||
maintenance_window_future = f"{(current_time+timedelta(minutes=60)).strftime('%H:%M')}-{(current_time+timedelta(minutes=120)).strftime('%H:%M')}"
|
||||
pg_patch_version_15_outside_mw = {
|
||||
pg_patch_version_higher_version_outside_mw = {
|
||||
"spec": {
|
||||
"postgresql": {
|
||||
"version": "15"
|
||||
"version": "16"
|
||||
},
|
||||
"maintenanceWindows": [
|
||||
maintenance_window_future
|
||||
|
|
@ -1249,23 +1249,23 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
}
|
||||
}
|
||||
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||
"acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_15_outside_mw)
|
||||
"acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_higher_version_outside_mw)
|
||||
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
|
||||
|
||||
# no pod replacement outside of the maintenance window
|
||||
k8s.wait_for_pod_start('spilo-role=master,' + cluster_label)
|
||||
k8s.wait_for_pod_start('spilo-role=replica,' + cluster_label)
|
||||
self.eventuallyEqual(check_version, 14, "Version should not be upgraded")
|
||||
self.eventuallyEqual(check_version, 15, "Version should not be upgraded")
|
||||
|
||||
second_annotations = get_annotations()
|
||||
self.assertIsNone(second_annotations.get("last-major-upgrade-failure"), "Annotation for last upgrade's failure should not be set")
|
||||
|
||||
# change maintenanceWindows to current
|
||||
maintenance_window_current = f"{(current_time-timedelta(minutes=30)).strftime('%H:%M')}-{(current_time+timedelta(minutes=30)).strftime('%H:%M')}"
|
||||
pg_patch_version_15_in_mw = {
|
||||
pg_patch_version_higher_version_in_mw = {
|
||||
"spec": {
|
||||
"postgresql": {
|
||||
"version": "15"
|
||||
"version": "16"
|
||||
},
|
||||
"maintenanceWindows": [
|
||||
maintenance_window_current
|
||||
|
|
@ -1274,13 +1274,13 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
}
|
||||
|
||||
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||
"acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_15_in_mw)
|
||||
"acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_higher_version_in_mw)
|
||||
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
|
||||
|
||||
k8s.wait_for_pod_failover(master_nodes, 'spilo-role=master,' + cluster_label)
|
||||
k8s.wait_for_pod_start('spilo-role=master,' + cluster_label)
|
||||
k8s.wait_for_pod_start('spilo-role=replica,' + cluster_label)
|
||||
self.eventuallyEqual(check_version, 15, "Version should be upgraded from 14 to 15")
|
||||
self.eventuallyEqual(check_version, 16, "Version should be upgraded from 15 to 16")
|
||||
|
||||
# check if annotation for last upgrade's success is updated after second upgrade
|
||||
third_annotations = get_annotations()
|
||||
|
|
@ -1288,7 +1288,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
self.assertNotEqual(annotations.get("last-major-upgrade-success"), third_annotations.get("last-major-upgrade-success"), "Annotation for last upgrade's success is not updated")
|
||||
|
||||
# test upgrade with failed upgrade annotation
|
||||
pg_patch_version_17 = {
|
||||
pg_patch_version_highest_version = {
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"last-major-upgrade-failure": "2024-01-02T15:04:05Z"
|
||||
|
|
@ -1296,28 +1296,28 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
},
|
||||
"spec": {
|
||||
"postgresql": {
|
||||
"version": "17"
|
||||
"version": "18"
|
||||
},
|
||||
},
|
||||
}
|
||||
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||
"acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_17)
|
||||
"acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_highest_version)
|
||||
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
|
||||
|
||||
k8s.wait_for_pod_failover(master_nodes, 'spilo-role=replica,' + cluster_label)
|
||||
k8s.wait_for_pod_start('spilo-role=master,' + cluster_label)
|
||||
k8s.wait_for_pod_start('spilo-role=replica,' + cluster_label)
|
||||
self.eventuallyEqual(check_version, 15, "Version should not be upgraded because annotation for last upgrade's failure is set")
|
||||
self.eventuallyEqual(check_version, 16, "Version should not be upgraded because annotation for last upgrade's failure is set")
|
||||
|
||||
# change the version back to 15 and should remove failure annotation
|
||||
# change the version back to 16 and should remove failure annotation
|
||||
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||
"acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_15_in_mw)
|
||||
"acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_higher_version_in_mw)
|
||||
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
|
||||
|
||||
k8s.wait_for_pod_start('spilo-role=master,' + cluster_label)
|
||||
k8s.wait_for_pod_start('spilo-role=replica,' + cluster_label)
|
||||
|
||||
self.eventuallyEqual(check_version, 15, "Version should not be upgraded from 15")
|
||||
self.eventuallyEqual(check_version, 16, "Version should not be upgraded from 16")
|
||||
fourth_annotations = get_annotations()
|
||||
self.assertIsNone(fourth_annotations.get("last-major-upgrade-failure"), "Annotation for last upgrade's failure is not removed")
|
||||
|
||||
|
|
@ -1370,7 +1370,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
}
|
||||
k8s.update_config(patch_scaled_policy_retain)
|
||||
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
|
||||
|
||||
|
||||
# decrease the number of instances
|
||||
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||
'acid.zalan.do', 'v1', 'default', 'postgresqls', 'acid-minimal-cluster', pg_patch_scale_down_instances)
|
||||
|
|
@ -1430,7 +1430,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||
"acid.zalan.do", "v1", "default", "postgresqls", "acid-minimal-cluster", pg_patch_resources)
|
||||
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"},
|
||||
"Operator does not get in sync")
|
||||
"Operator does not get in sync", retries=120)
|
||||
|
||||
# wait for switched over
|
||||
k8s.wait_for_pod_failover(replica_nodes, 'spilo-role=master,' + cluster_label)
|
||||
|
|
@ -1647,7 +1647,6 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
# toggle pod anti affinity to move replica away from master node
|
||||
self.assert_distributed_pods(master_nodes)
|
||||
|
||||
|
||||
@timeout_decorator.timeout(TEST_TIMEOUT_SEC)
|
||||
def test_overwrite_pooler_deployment(self):
|
||||
pooler_name = 'acid-minimal-cluster-pooler'
|
||||
|
|
@ -1800,7 +1799,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
},
|
||||
}
|
||||
k8s.api.core_v1.patch_namespaced_secret(
|
||||
name="foo-user.acid-minimal-cluster.credentials.postgresql.acid.zalan.do",
|
||||
name="foo-user.acid-minimal-cluster.credentials.postgresql.acid.zalan.do",
|
||||
namespace="default",
|
||||
body=secret_fake_rotation)
|
||||
|
||||
|
|
@ -1817,7 +1816,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
"enable_password_rotation": "true",
|
||||
"inherited_annotations": "environment",
|
||||
"password_rotation_interval": "30",
|
||||
"password_rotation_user_retention": "30", # should be set to 60
|
||||
"password_rotation_user_retention": "30", # should be set to 60
|
||||
},
|
||||
}
|
||||
k8s.update_config(enable_password_rotation)
|
||||
|
|
@ -1886,7 +1885,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
self.assertTrue("environment" in db_user_secret.metadata.annotations, "Added annotation was not propagated to secret")
|
||||
|
||||
# disable password rotation for all other users (foo_user)
|
||||
# and pick smaller intervals to see if the third fake rotation user is dropped
|
||||
# and pick smaller intervals to see if the third fake rotation user is dropped
|
||||
enable_password_rotation = {
|
||||
"data": {
|
||||
"enable_password_rotation": "false",
|
||||
|
|
@ -2386,6 +2385,90 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
# toggle pod anti affinity to move replica away from master node
|
||||
self.assert_distributed_pods(master_nodes)
|
||||
|
||||
@timeout_decorator.timeout(TEST_TIMEOUT_SEC)
|
||||
def test_topology_spread_constraints(self):
|
||||
'''
|
||||
Enable topologySpreadConstraints for pods
|
||||
'''
|
||||
k8s = self.k8s
|
||||
cluster_labels = "application=spilo,cluster-name=acid-minimal-cluster"
|
||||
|
||||
# Verify we are in good state from potential previous tests
|
||||
self.eventuallyEqual(lambda: k8s.count_running_pods(), 2, "No 2 pods running")
|
||||
|
||||
# patch the pvc retention policy to enable delete when scale down
|
||||
patch_scaled_policy_delete = {
|
||||
"data": {
|
||||
"persistent_volume_claim_retention_policy": "when_deleted:retain,when_scaled:delete"
|
||||
}
|
||||
}
|
||||
k8s.update_config(patch_scaled_policy_delete)
|
||||
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
|
||||
|
||||
master_nodes, replica_nodes = k8s.get_cluster_nodes()
|
||||
self.assertNotEqual(master_nodes, [])
|
||||
self.assertNotEqual(replica_nodes, [])
|
||||
|
||||
# Patch label to nodes for topologySpreadConstraints
|
||||
patch_node_label = {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"topology.kubernetes.io/zone": "zalando"
|
||||
}
|
||||
}
|
||||
}
|
||||
k8s.api.core_v1.patch_node(master_nodes[0], patch_node_label)
|
||||
k8s.api.core_v1.patch_node(replica_nodes[0], patch_node_label)
|
||||
|
||||
# Patch topologySpreadConstraint and scale-out postgresql pods to postgresqls manifest.
|
||||
patch_topologySpreadConstraint_config = {
|
||||
"spec": {
|
||||
"numberOfInstances": 6,
|
||||
"topologySpreadConstraint": [
|
||||
{
|
||||
"maxskew": 1,
|
||||
"topologyKey": "topology.kubernetes.io/zone",
|
||||
"whenUnsatisfiable": "DoNotSchedule"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||
"acid.zalan.do", "v1", "default",
|
||||
"postgresqls", "acid-minimal-cluster",
|
||||
patch_topologySpreadConstraint_config)
|
||||
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
|
||||
self.eventuallyEqual(lambda: k8s.count_pods_with_label(cluster_labels), 6, "Postgresql StatefulSet are scale to 6")
|
||||
self.eventuallyEqual(lambda: k8s.count_running_pods(), 6, "All pods are running")
|
||||
|
||||
worker_node_1 = 0
|
||||
worker_node_2 = 0
|
||||
pods = k8s.api.core_v1.list_namespaced_pod('default', label_selector=cluster_labels)
|
||||
for pod in pods.items:
|
||||
if pod.spec.node_name == 'postgres-operator-e2e-tests-worker':
|
||||
worker_node_1 += 1
|
||||
elif pod.spec.node_name == 'postgres-operator-e2e-tests-worker2':
|
||||
worker_node_2 += 1
|
||||
|
||||
self.assertEqual(worker_node_1, worker_node_2)
|
||||
self.assertEqual(worker_node_1, 3)
|
||||
self.assertEqual(worker_node_2, 3)
|
||||
|
||||
# Reset configurations
|
||||
patch_topologySpreadConstraint_config = {
|
||||
"spec": {
|
||||
"numberOfInstances": 2,
|
||||
"topologySpreadConstraint": []
|
||||
}
|
||||
}
|
||||
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||
"acid.zalan.do", "v1", "default",
|
||||
"postgresqls", "acid-minimal-cluster",
|
||||
patch_topologySpreadConstraint_config)
|
||||
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
|
||||
self.eventuallyEqual(lambda: k8s.count_pods_with_label(cluster_labels), 2, "Postgresql StatefulSet are scale to 2")
|
||||
self.eventuallyEqual(lambda: k8s.count_running_pods(), 2, "All pods are running")
|
||||
|
||||
@timeout_decorator.timeout(TEST_TIMEOUT_SEC)
|
||||
def test_zz_cluster_deletion(self):
|
||||
'''
|
||||
|
|
@ -2461,7 +2544,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
self.eventuallyEqual(lambda: k8s.count_deployments_with_label(cluster_label), 0, "Deployments not deleted")
|
||||
self.eventuallyEqual(lambda: k8s.count_pdbs_with_label(cluster_label), 0, "Pod disruption budget not deleted")
|
||||
self.eventuallyEqual(lambda: k8s.count_secrets_with_label(cluster_label), 8, "Secrets were deleted although disabled in config")
|
||||
self.eventuallyEqual(lambda: k8s.count_pvcs_with_label(cluster_label), 3, "PVCs were deleted although disabled in config")
|
||||
self.eventuallyEqual(lambda: k8s.count_pvcs_with_label(cluster_label), 2, "PVCs were deleted although disabled in config")
|
||||
|
||||
except timeout_decorator.TimeoutError:
|
||||
print('Operator log: {}'.format(k8s.get_operator_log()))
|
||||
|
|
@ -2503,7 +2586,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
|
||||
# if nodes are different we can quit here
|
||||
if master_nodes[0] not in replica_nodes:
|
||||
return True
|
||||
return True
|
||||
|
||||
# enable pod anti affintiy in config map which should trigger movement of replica
|
||||
patch_enable_antiaffinity = {
|
||||
|
|
@ -2527,7 +2610,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
}
|
||||
k8s.update_config(patch_disable_antiaffinity, "disable antiaffinity")
|
||||
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
|
||||
|
||||
|
||||
k8s.wait_for_pod_start('spilo-role=replica,' + cluster_labels)
|
||||
k8s.wait_for_running_pods(cluster_labels, 2)
|
||||
|
||||
|
|
@ -2538,7 +2621,7 @@ class EndToEndTestCase(unittest.TestCase):
|
|||
# if nodes are different we can quit here
|
||||
for target_node in target_nodes:
|
||||
if (target_node not in master_nodes or target_node not in replica_nodes) and master_nodes[0] in replica_nodes:
|
||||
print('Pods run on the same node')
|
||||
print('Pods run on the same node')
|
||||
return False
|
||||
|
||||
except timeout_decorator.TimeoutError:
|
||||
|
|
|
|||
118
go.mod
118
go.mod
|
|
@ -1,75 +1,101 @@
|
|||
module github.com/zalando/postgres-operator
|
||||
|
||||
go 1.25.3
|
||||
go 1.26.4
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/aws/aws-sdk-go v1.55.8
|
||||
github.com/Masterminds/semver/v3 v3.5.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.42.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.24
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.305.3
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/lib/pq v1.12.3
|
||||
github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/r3labs/diff v1.1.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/stretchr/testify v1.11.1
|
||||
golang.org/x/crypto v0.45.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/api v0.32.9
|
||||
k8s.io/apiextensions-apiserver v0.25.9
|
||||
k8s.io/apimachinery v0.32.9
|
||||
k8s.io/client-go v0.32.9
|
||||
k8s.io/code-generator v0.25.9
|
||||
golang.org/x/crypto v0.52.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.36.1
|
||||
k8s.io/apiextensions-apiserver v0.36.1
|
||||
k8s.io/apimachinery v0.36.1
|
||||
k8s.io/client-go v0.36.1
|
||||
sigs.k8s.io/yaml v1.6.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.1.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.31.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 // indirect
|
||||
github.com/aws/smithy-go v1.27.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/gobuffalo/flect v1.0.3 // indirect
|
||||
github.com/google/gnostic-models v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/moby/spdystream v0.5.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/moby/spdystream v0.5.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/cobra v1.10.2 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/oauth2 v0.27.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/term v0.37.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/sync v0.21.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/term v0.44.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/gengo v0.0.0-20220902162205-c0856e24416d // indirect
|
||||
k8s.io/gengo/v2 v2.0.0-20240826214909-a7b603a56eb7 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/code-generator v0.36.1 // indirect
|
||||
k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect
|
||||
k8s.io/klog/v2 v2.140.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
|
||||
k8s.io/streaming v0.36.1 // indirect
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
|
||||
sigs.k8s.io/controller-tools v0.17.3 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
|
||||
)
|
||||
|
||||
tool (
|
||||
github.com/golang/mock/mockgen
|
||||
k8s.io/code-generator
|
||||
k8s.io/code-generator/cmd/client-gen
|
||||
k8s.io/code-generator/cmd/deepcopy-gen
|
||||
k8s.io/code-generator/cmd/informer-gen
|
||||
k8s.io/code-generator/cmd/lister-gen
|
||||
sigs.k8s.io/controller-tools/cmd/controller-gen
|
||||
)
|
||||
|
|
|
|||
262
go.sum
262
go.sum
|
|
@ -1,21 +1,53 @@
|
|||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
|
||||
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
|
||||
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.42.0 h1:XvXMJTkFQtpBKIWZnmr9ZEOc2InWM2yldjXEJ/bymhA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.42.0/go.mod h1:27+ACypSLljLAEKsCYOmrjKh83vuTRkuAe9Uv/3A4bg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.24 h1:aEDEj533yGdVvEHfkCY0D/1FbDrjnZr4pIulxRjqpHs=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.24/go.mod h1:yZtrGKJGlqfEW+/m2uTsJK+Jz7xF5R0eZfgcIG9m1ss=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.23 h1:Zhu3GOpRCkNjtE/gJpuPDsytSnaCCTQk8neAGsgzG5Y=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.23/go.mod h1:VsJF2ropPB37gDr7M2rLSpCE8IQWdpl62uae7qxZmqU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29 h1:r6qZHbT+wxgWO/e9vYNUEtg7lv5+UN3pRqKhLXvnArg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29/go.mod h1:QRnaRcTVGKPGRy8w78HMQtKUGRYcnMZAANATkeVA6Mo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 h1:f3vKqSo13fhTYb+JEcXwXefZQE26I1FB5eTSniU67ko=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29/go.mod h1:MzoLFUArKGpGD+ukmPiTPG1X5x4o6M2kq4v2dr1FiEc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 h1:RdwIf/CuUsvJX3RgJagbOyotl/cxoLY4xviKuE7p2GY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29/go.mod h1:71wt8W2EgswdZy9Mf9KNnzxZ3TiZlv4caKghPktDOkA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 h1:VTGy885W5DKBxWRUJbym9hytNaYzsyaPkCHGRRMAOhU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30/go.mod h1:AS0HycUvJRFvTt613AYDOgO2jzw+00cVSMny8XB3yMY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.305.3 h1:rEay0b3E0qqyY+W0c3ox8wGRsVK+GjxXadj9B9vpg4c=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.305.3/go.mod h1:8mrDF7OtbuL0QpwP4YCvLuoOE4/5lL7D33MXgp069/Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 h1:ZD2+BSw9vFsNlKYIasSNt3uDbjqqXIBcM13UJv/Lx2k=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12/go.mod h1:Ms4zlcVBbXbiP7EVLhl+lgjvA/a7YphqQ3Ih3174EmI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 h1:DRebniUGZ2MqiiIVmQJ04vIXr918hubdHMnarSLEWyU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29/go.mod h1:LfRkPCD8YHDM2E5eTkos2UpwYeZnBcVarTa8L59bJHA=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.1.5 h1:6Xt6Ztjkwdia/7EtEaG7ki/qZUYlCcd7tGUotQed1QE=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.1.5/go.mod h1:LxYujSTLPRlp2vTtcUO/+1ilrew8ytt6SvQyOgejzFQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.31.3 h1:ey1XLTYXb9PcLt4535632o5kCGXNXEhNb620Dqwuylo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.31.3/go.mod h1:Lk7PlmoTYryQmyBG0EXqj5BcUbj3whXdU2s3yGI3EAc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6 h1:yLr03zQE/5Eu5l3QU0Si+xMbLMbSDF2YXsigqXngs6g=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6/go.mod h1:Q5N6icH+KJZDLh+ESNwzdv6cZ6vLFF/egy3IOxWhmz4=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 h1:VrIhKRCSK1umelSgB9RghvA9RTUYeQffyAS5ApXehNI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.43.3/go.mod h1:r8wkDOuLaaMFqFiYAb8dGY2A3gJCOujMc6CFOVC4Zhc=
|
||||
github.com/aws/smithy-go v1.27.1 h1:4T340VFndXtADGF52gYa1POyL7s9E4Z1OeZ1hCscIw8=
|
||||
github.com/aws/smithy-go v1.27.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
|
|
@ -24,41 +56,25 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En
|
|||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4=
|
||||
github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
|
||||
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
|
|
@ -66,27 +82,33 @@ 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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
||||
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y=
|
||||
github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d h1:LznySqW8MqVeFh+pW6rOkFdld9QQ7jRydBKKM6jyPVI=
|
||||
github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d/go.mod h1:u3hJ0kqCQu/cPpsu3RbCOPZ0d7V3IjPjv1adNRleM9I=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
|
@ -94,18 +116,22 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
|||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/r3labs/diff v1.1.0 h1:V53xhrbTHrWFWq3gI4b94AjgEJOerO1+1l0xyHOBi8M=
|
||||
github.com/r3labs/diff v1.1.0/go.mod h1:7WjXasNzi0vJetRcB/RqNl5dlIsmXcTTLmF5IoH6Xig=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
|
|
@ -113,115 +139,105 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
|||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY=
|
||||
golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
|
||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.32.9 h1:q/59kk8lnecgG0grJqzrmXC1Jcl2hPWp9ltz0FQuoLI=
|
||||
k8s.io/api v0.32.9/go.mod h1:jIfT3rwW4EU1IXZm9qjzSk/2j91k4CJL5vUULrxqp3Y=
|
||||
k8s.io/apiextensions-apiserver v0.25.9 h1:Pycd6lm2auABp9wKQHCFSEPG+NPdFSTJXPST6NJFzB8=
|
||||
k8s.io/apiextensions-apiserver v0.25.9/go.mod h1:ijGxmSG1GLOEaWhTuaEr0M7KUeia3mWCZa6FFQqpt1M=
|
||||
k8s.io/apimachinery v0.32.9 h1:fXk8ktfsxrdThaEOAQFgkhCK7iyoyvS8nbYJ83o/SSs=
|
||||
k8s.io/apimachinery v0.32.9/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||
k8s.io/client-go v0.32.9 h1:ZMyIQ1TEpTDAQni3L2gH1NZzyOA/gHfNcAazzCxMJ0c=
|
||||
k8s.io/client-go v0.32.9/go.mod h1:2OT8aFSYvUjKGadaeT+AVbhkXQSpMAkiSb88Kz2WggI=
|
||||
k8s.io/code-generator v0.25.9 h1:lgyAV9AIRYNxZxgLRXqsCAtqJLHvakot41CjEqD5W0w=
|
||||
k8s.io/code-generator v0.25.9/go.mod h1:DHfpdhSUrwqF0f4oLqCtF8gYbqlndNetjBEz45nWzJI=
|
||||
k8s.io/gengo v0.0.0-20220902162205-c0856e24416d h1:U9tB195lKdzwqicbJvyJeOXV7Klv+wNAWENRnXEGi08=
|
||||
k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||
k8s.io/gengo/v2 v2.0.0-20240826214909-a7b603a56eb7 h1:cErOOTkQ3JW19o4lo91fFurouhP8NcoBvb7CkvhZZpk=
|
||||
k8s.io/gengo/v2 v2.0.0-20240826214909-a7b603a56eb7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
|
||||
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
|
||||
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY=
|
||||
k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo=
|
||||
k8s.io/apiextensions-apiserver v0.36.1 h1:6JfYmPUsuUIHuN+3QxutXYWj492RqF5fBSx67GYK5Ks=
|
||||
k8s.io/apiextensions-apiserver v0.36.1/go.mod h1:pLzZin90riwisdzKwv/GoTwENooytoIx5zWJb4Hkby8=
|
||||
k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=
|
||||
k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=
|
||||
k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0=
|
||||
k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU=
|
||||
k8s.io/code-generator v0.36.1 h1:5bHQ7NbBcFFLHcoyo/hgU3m2tQV5RLz2nv4QNDlsbXc=
|
||||
k8s.io/code-generator v0.36.1/go.mod h1:oCv8WmrW2RGdcMyvSk1aYbBfSs51ggtSFQr1YNeuAuo=
|
||||
k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ=
|
||||
k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM=
|
||||
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
|
||||
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
|
||||
k8s.io/streaming v0.36.1 h1:L+K68n4Gg940BGNNYtUBvL1WTLL0YnKT3s+P1MNAmR4=
|
||||
k8s.io/streaming v0.36.1/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
sigs.k8s.io/controller-tools v0.17.3 h1:lwFPLicpBKLgIepah+c8ikRBubFW5kOQyT88r3EwfNw=
|
||||
sigs.k8s.io/controller-tools v0.17.3/go.mod h1:1ii+oXcYZkxcBXzwv3YZBlzjt1fvkrCGjVF73blosJI=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Hack to adjust the generated postgresql CRD YAML file and add missing field
|
||||
# settings which can not be expressed via kubebuilder markers.
|
||||
#
|
||||
# Injections:
|
||||
#
|
||||
# * oneOf: for the standby field to enforce validation rules:
|
||||
# - s3_wal_path and gs_wal_path are mutually exclusive
|
||||
# - standby_host can be specified alone or with either s3_wal_path OR gs_wal_path
|
||||
# - at least one of s3_wal_path, gs_wal_path, or standby_host must be set
|
||||
# * type: string and pattern for the maintenanceWindows items.
|
||||
|
||||
file="${1:-"manifests/postgresql.crd.yaml"}"
|
||||
|
||||
SED=$(command -v gsed 2>/dev/null || command -v sed)
|
||||
|
||||
$SED -i '/^[[:space:]]*standby:$/{
|
||||
# Capture the indentation
|
||||
s/^\([[:space:]]*\)standby:$/\1standby:\n\1 anyOf:\n\1 - required:\n\1 - s3_wal_path\n\1 - required:\n\1 - gs_wal_path\n\1 - required:\n\1 - standby_host\n\1 not:\n\1 required:\n\1 - s3_wal_path\n\1 - gs_wal_path/
|
||||
}' "$file"
|
||||
|
||||
$SED -i '/^[[:space:]]*maintenanceWindows:$/{
|
||||
# Capture the indentation
|
||||
s/^\([[:space:]]*\)maintenanceWindows:$/\1maintenanceWindows:\n\1 items:\n\1 pattern: '\''^\\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))-((2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))\\ *$'\''\n\1 type: string/
|
||||
}' "$file"
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
// +build tools
|
||||
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
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.
|
||||
*/
|
||||
|
||||
// This package imports things required by build scripts, to force `go mod` to see them as dependencies
|
||||
package tools
|
||||
|
||||
import _ "k8s.io/code-generator"
|
||||
|
|
@ -1,26 +1,66 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2017 The Kubernetes Authors.
|
||||
#
|
||||
# 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.
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
GENERATED_PACKAGE_ROOT="github.com"
|
||||
OPERATOR_PACKAGE_ROOT="${GENERATED_PACKAGE_ROOT}/zalando/postgres-operator"
|
||||
SCRIPT_ROOT=$(dirname ${BASH_SOURCE})/..
|
||||
TARGET_CODE_DIR=${1-${SCRIPT_ROOT}/pkg}
|
||||
CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo "${GOPATH}"/src/k8s.io/code-generator)}
|
||||
SRC="github.com"
|
||||
GOPKG="$SRC/zalando/postgres-operator"
|
||||
CUSTOM_RESOURCE_NAME_ZAL="zalando.org"
|
||||
CUSTOM_RESOURCE_NAME_ACID="acid.zalan.do"
|
||||
CUSTOM_RESOURCE_VERSION="v1"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "${GENERATED_PACKAGE_ROOT}"
|
||||
}
|
||||
trap "cleanup" EXIT SIGINT
|
||||
SCRIPT_ROOT="$(dirname "${BASH_SOURCE[0]}")/.."
|
||||
|
||||
bash "${CODEGEN_PKG}/generate-groups.sh" client,deepcopy,informer,lister \
|
||||
"${OPERATOR_PACKAGE_ROOT}/pkg/generated" "${OPERATOR_PACKAGE_ROOT}/pkg/apis" \
|
||||
"acid.zalan.do:v1 zalando.org:v1" \
|
||||
--go-header-file "${SCRIPT_ROOT}"/hack/custom-boilerplate.go.txt \
|
||||
-o ./
|
||||
OUTPUT_DIR="pkg/generated"
|
||||
OUTPUT_PKG="${GOPKG}/${OUTPUT_DIR}"
|
||||
APIS_PKG="${GOPKG}/pkg/apis"
|
||||
GROUPS_WITH_VERSIONS="${CUSTOM_RESOURCE_NAME_ZAL}:${CUSTOM_RESOURCE_VERSION},${CUSTOM_RESOURCE_NAME_ACID}:${CUSTOM_RESOURCE_VERSION}"
|
||||
|
||||
cp -r "${OPERATOR_PACKAGE_ROOT}"/pkg/* "${TARGET_CODE_DIR}"
|
||||
echo "Generating deepcopy funcs"
|
||||
go tool deepcopy-gen \
|
||||
--output-file zz_generated.deepcopy.go \
|
||||
--go-header-file "${SCRIPT_ROOT}/hack/custom-boilerplate.go.txt" \
|
||||
"${APIS_PKG}/${CUSTOM_RESOURCE_NAME_ZAL}/${CUSTOM_RESOURCE_VERSION}" \
|
||||
"${APIS_PKG}/${CUSTOM_RESOURCE_NAME_ACID}/${CUSTOM_RESOURCE_VERSION}"
|
||||
|
||||
cleanup
|
||||
echo "Generating clientset for ${GROUPS_WITH_VERSIONS} at ${OUTPUT_PKG}/${CLIENTSET_PKG_NAME:-clientset}"
|
||||
go tool client-gen \
|
||||
--clientset-name versioned \
|
||||
--input-base "${APIS_PKG}" \
|
||||
--input "${CUSTOM_RESOURCE_NAME_ZAL}/${CUSTOM_RESOURCE_VERSION},${CUSTOM_RESOURCE_NAME_ACID}/${CUSTOM_RESOURCE_VERSION}" \
|
||||
--output-pkg "${OUTPUT_PKG}/clientset" \
|
||||
--go-header-file "${SCRIPT_ROOT}/hack/custom-boilerplate.go.txt" \
|
||||
--output-dir "${OUTPUT_DIR}/clientset"
|
||||
|
||||
echo "Generating listers for ${GROUPS_WITH_VERSIONS} at ${OUTPUT_PKG}/listers"
|
||||
go tool lister-gen \
|
||||
--output-pkg "${OUTPUT_PKG}/listers" \
|
||||
--go-header-file "${SCRIPT_ROOT}/hack/custom-boilerplate.go.txt" \
|
||||
--output-dir "${OUTPUT_DIR}/listers" \
|
||||
"${APIS_PKG}/${CUSTOM_RESOURCE_NAME_ZAL}/${CUSTOM_RESOURCE_VERSION}" \
|
||||
"${APIS_PKG}/${CUSTOM_RESOURCE_NAME_ACID}/${CUSTOM_RESOURCE_VERSION}"
|
||||
|
||||
echo "Generating informers for ${GROUPS_WITH_VERSIONS} at ${OUTPUT_PKG}/informers"
|
||||
go tool informer-gen \
|
||||
--versioned-clientset-package "${OUTPUT_PKG}/${CLIENTSET_PKG_NAME:-clientset}/${CLIENTSET_NAME_VERSIONED:-versioned}" \
|
||||
--listers-package "${OUTPUT_PKG}/listers" \
|
||||
--output-pkg "${OUTPUT_PKG}/informers" \
|
||||
--go-header-file "${SCRIPT_ROOT}/hack/custom-boilerplate.go.txt" \
|
||||
--output-dir "${OUTPUT_DIR}/informers" \
|
||||
"${APIS_PKG}/${CUSTOM_RESOURCE_NAME_ZAL}/${CUSTOM_RESOURCE_VERSION}" \
|
||||
"${APIS_PKG}/${CUSTOM_RESOURCE_NAME_ACID}/${CUSTOM_RESOURCE_VERSION}"
|
||||
|
|
|
|||
|
|
@ -1,137 +0,0 @@
|
|||
# Kubectl Plugin for Zalando's Postgres Operator
|
||||
|
||||
This plugin is a prototype developed as a part of **Google Summer of Code 2019** under the [Postgres Operator](https://summerofcode.withgoogle.com/archive/2019/organizations/6187982082539520/) organization.
|
||||
|
||||
## Installation of kubectl pg plugin
|
||||
|
||||
This project uses Go Modules for dependency management to build locally.
|
||||
Install go and enable go modules with ```export GO111MODULE=on```.
|
||||
From Go >=1.13 Go modules will be enabled by default.
|
||||
|
||||
```bash
|
||||
# Assumes you have a working KUBECONFIG
|
||||
$ GO111MODULE="on"
|
||||
$ GOPATH/src/github.com/zalando/postgres-operator/kubectl-pg go mod vendor
|
||||
# This generate a vendor directory with all dependencies needed by the plugin.
|
||||
$ $GOPATH/src/github.com/zalando/postgres-operator/kubectl-pg go install
|
||||
# This will place the kubectl-pg binary in your $GOPATH/bin
|
||||
```
|
||||
|
||||
## Before using the kubectl pg plugin make sure to set KUBECONFIG env variable
|
||||
|
||||
Ideally KUBECONFIG is found in $HOME/.kube/config else specify the KUBECONFIG path here.
|
||||
```export KUBECONFIG=$HOME/.kube/config```
|
||||
|
||||
## List all commands available in kubectl pg
|
||||
|
||||
```kubectl pg --help``` (or) ```kubectl pg```
|
||||
|
||||
## Check if Postgres Operator is installed and running
|
||||
|
||||
```kubectl pg check```
|
||||
|
||||
## Create a new cluster using manifest file
|
||||
|
||||
```kubectl pg create -f acid-minimal-cluster.yaml```
|
||||
|
||||
## List postgres resources
|
||||
|
||||
```kubectl pg list```
|
||||
|
||||
List clusters across namespaces
|
||||
```kubectl pg list all```
|
||||
|
||||
## Update existing cluster using manifest file
|
||||
|
||||
```kubectl pg update -f acid-minimal-cluster.yaml```
|
||||
|
||||
## Delete existing cluster
|
||||
|
||||
Using the manifest file:
|
||||
```kubectl pg delete -f acid-minimal-cluster.yaml```
|
||||
|
||||
Or by specifying the cluster name:
|
||||
```kubectl pg delete acid-minimal-cluster```
|
||||
|
||||
Use `--namespace` or `-n` flag if your cluster is in a different namespace to where your current context is pointing to:
|
||||
```kubectl pg delete acid-minimal-cluster -n namespace01```
|
||||
|
||||
## Adding manifest roles to an existing cluster
|
||||
|
||||
```kubectl pg add-user USER01 -p CREATEDB,LOGIN -c acid-minimal-cluster```
|
||||
|
||||
Privileges can only be [SUPERUSER, REPLICATION, INHERIT, LOGIN, NOLOGIN, CREATEROLE, CREATEDB, BYPASSRLS]
|
||||
Note: By default, a LOGIN user is created (unless NOLOGIN is specified).
|
||||
|
||||
## Adding databases to an existing cluster
|
||||
|
||||
You have to specify an owner of the new database and this role must already exist in the cluster:
|
||||
```kubectl pg add-db DB01 -o OWNER01 -c acid-minimal-cluster```
|
||||
|
||||
## Extend the volume of an existing pg cluster
|
||||
|
||||
```kubectl pg ext-volume 2Gi -c acid-minimal-cluster```
|
||||
|
||||
## Print the version of Postgres Operator and kubectl pg plugin
|
||||
|
||||
```kubectl pg version```
|
||||
|
||||
## Connect to the shell of a postgres pod
|
||||
|
||||
Connect to the master pod:
|
||||
```kubectl pg connect -c CLUSTER -m```
|
||||
|
||||
Connect to a random replica pod:
|
||||
```kubectl pg connect -c CLUSTER```
|
||||
|
||||
Connect to a certain replica pod:
|
||||
```kubectl pg connect -c CLUSTER -r 0```
|
||||
|
||||
## Connect to a database via psql
|
||||
|
||||
Adding the `-p` flag allows you to directly connect to a given database with the psql client.
|
||||
With `-u` you specify the user. If left out the name of the current OS user is taken.
|
||||
`-d` lets you specify the database. If no database is specified, it will be the same as the user name.
|
||||
|
||||
Connect to `app_db` database on the master with role `app_user`:
|
||||
```kubectl pg connect -c CLUSTER -m -p -u app_user -d app_db```
|
||||
|
||||
Connect to the `postgres` database on a random replica with role `postgres`:
|
||||
```kubectl pg connect -c CLUSTER -p -u postgres```
|
||||
|
||||
Connect to a certain replica assuming name of OS user, database role and name are all the same:
|
||||
```kubectl pg connect -c CLUSTER -r 0 -p```
|
||||
|
||||
|
||||
## Access Postgres Operator logs
|
||||
|
||||
```kubectl pg logs -o```
|
||||
|
||||
## Access Patroni logs of different database pods
|
||||
|
||||
Fetch logs of master:
|
||||
```kubectl pg logs -c CLUSTER -m```
|
||||
|
||||
Fetch logs of a random replica pod:
|
||||
```kubectl pg logs -c CLUSTER```
|
||||
|
||||
Fetch logs of specified replica
|
||||
```kubectl pg logs -c CLUSTER -r 2```
|
||||
|
||||
## Development
|
||||
|
||||
When making changes to the plugin make sure to change the (major/patch) version of plugin in `build.sh` script and run `./build.sh`.
|
||||
|
||||
## Google Summer of Code 2019
|
||||
|
||||
### GSoC Proposal
|
||||
|
||||
[kubectl pg proposal](https://docs.google.com/document/d/1-WMy9HkfZ1XnnMbzplMe9rCzKrRMGaMz4owLVXXPb7w/edit)
|
||||
|
||||
### Weekly Reports
|
||||
|
||||
https://github.com/VineethReddy02/GSoC-Kubectl-Plugin-for-Postgres-Operator-tracker
|
||||
|
||||
### Final Project Report
|
||||
|
||||
https://gist.github.com/VineethReddy02/159283bd368a710379eaf0f6bd60a40a
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
VERSION=1.0
|
||||
sed -i "s/KubectlPgVersion string = \"[^\"]*\"/KubectlPgVersion string = \"${VERSION}\"/" cmd/version.go
|
||||
go install
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// addDbCmd represents the addDb command
|
||||
var addDbCmd = &cobra.Command{
|
||||
Use: "add-db",
|
||||
Short: "Adds a DB and its owner to a Postgres cluster. The owner role is created if it does not exist",
|
||||
Long: `Adds a new DB to the Postgres cluster. Owner needs to be specified by the -o flag, cluster with -c flag.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) > 0 {
|
||||
dbName := args[0]
|
||||
dbOwner, _ := cmd.Flags().GetString("owner")
|
||||
clusterName, _ := cmd.Flags().GetString("cluster")
|
||||
addDb(dbName, dbOwner, clusterName)
|
||||
} else {
|
||||
fmt.Println("database name can't be empty. Use kubectl pg add-db [-h | --help] for more info")
|
||||
}
|
||||
|
||||
},
|
||||
Example: `
|
||||
kubectl pg add-db db01 -o owner01 -c cluster01
|
||||
`,
|
||||
}
|
||||
|
||||
// add db and it's owner to the cluster
|
||||
func addDb(dbName string, dbOwner string, clusterName string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
namespace := getCurrentNamespace()
|
||||
postgresql, err := postgresConfig.Postgresqls(namespace).Get(context.TODO(), clusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var dbOwnerExists bool
|
||||
dbUsers := postgresql.Spec.Users
|
||||
for key := range dbUsers {
|
||||
if key == dbOwner {
|
||||
dbOwnerExists = true
|
||||
}
|
||||
}
|
||||
var patch []byte
|
||||
// validating reserved DB names
|
||||
if dbOwnerExists && dbName != "postgres" && dbName != "template0" && dbName != "template1" {
|
||||
patch = dbPatch(dbName, dbOwner)
|
||||
} else if !dbOwnerExists {
|
||||
log.Fatal("The provided db-owner doesn't exist")
|
||||
} else {
|
||||
log.Fatal("The provided db-name is reserved by postgres")
|
||||
}
|
||||
|
||||
updatedPostgres, err := postgresConfig.Postgresqls(namespace).Patch(context.TODO(), postgresql.Name, types.MergePatchType, patch, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if updatedPostgres.ResourceVersion != postgresql.ResourceVersion {
|
||||
fmt.Printf("Created new database %s with owner %s in PostgreSQL cluster %s.\n", dbName, dbOwner, updatedPostgres.Name)
|
||||
} else {
|
||||
fmt.Printf("postgresql %s is unchanged.\n", updatedPostgres.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func dbPatch(dbname string, owner string) []byte {
|
||||
ins := map[string]map[string]map[string]string{"spec": {"databases": {dbname: owner}}}
|
||||
patchInstances, err := json.Marshal(ins)
|
||||
if err != nil {
|
||||
log.Fatal(err, "unable to parse patch for add-db")
|
||||
}
|
||||
return patchInstances
|
||||
}
|
||||
|
||||
func init() {
|
||||
addDbCmd.Flags().StringP("owner", "o", "", "provide owner of the database.")
|
||||
addDbCmd.Flags().StringP("cluster", "c", "", "provide a postgres cluster name.")
|
||||
rootCmd.AddCommand(addDbCmd)
|
||||
}
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
var allowedPrivileges = []string{"SUPERUSER", "REPLICATION", "INHERIT", "LOGIN", "NOLOGIN", "CREATEROLE", "CREATEDB", "BYPASSRLS"}
|
||||
|
||||
// addUserCmd represents the addUser command
|
||||
var addUserCmd = &cobra.Command{
|
||||
Use: "add-user",
|
||||
Short: "Adds a user to the postgres cluster with given privileges",
|
||||
Long: `Adds a user to the postgres cluster. You can add privileges as well with -p flag.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clusterName, _ := cmd.Flags().GetString("cluster")
|
||||
privileges, _ := cmd.Flags().GetString("privileges")
|
||||
|
||||
if len(args) > 0 {
|
||||
user := args[0]
|
||||
var permissions []string
|
||||
var perms []string
|
||||
|
||||
if privileges != "" {
|
||||
parsedRoles := strings.Replace(privileges, ",", " ", -1)
|
||||
parsedRoles = strings.ToUpper(parsedRoles)
|
||||
permissions = strings.Fields(parsedRoles)
|
||||
var invalidPerms []string
|
||||
|
||||
for _, userPrivilege := range permissions {
|
||||
validPerm := false
|
||||
for _, privilege := range allowedPrivileges {
|
||||
if privilege == userPrivilege {
|
||||
perms = append(perms, userPrivilege)
|
||||
validPerm = true
|
||||
}
|
||||
}
|
||||
if !validPerm {
|
||||
invalidPerms = append(invalidPerms, userPrivilege)
|
||||
}
|
||||
}
|
||||
|
||||
if len(invalidPerms) > 0 {
|
||||
fmt.Printf("Invalid privileges %s\n", invalidPerms)
|
||||
return
|
||||
}
|
||||
}
|
||||
addUser(user, clusterName, perms)
|
||||
}
|
||||
},
|
||||
Example: `
|
||||
kubectl pg add-user user01 -p login,createdb -c cluster01
|
||||
`,
|
||||
}
|
||||
|
||||
// add user to the cluster with provided permissions
|
||||
func addUser(user string, clusterName string, permissions []string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
namespace := getCurrentNamespace()
|
||||
postgresql, err := postgresConfig.Postgresqls(namespace).Get(context.TODO(), clusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
setUsers := make(map[string]bool)
|
||||
for _, k := range permissions {
|
||||
setUsers[k] = true
|
||||
}
|
||||
|
||||
if existingRoles, key := postgresql.Spec.Users[user]; key {
|
||||
for _, k := range existingRoles {
|
||||
setUsers[k] = true
|
||||
}
|
||||
}
|
||||
|
||||
Privileges := []string{}
|
||||
for keys, values := range setUsers {
|
||||
if values {
|
||||
Privileges = append(Privileges, keys)
|
||||
}
|
||||
}
|
||||
|
||||
patch := applyUserPatch(user, Privileges)
|
||||
updatedPostgresql, err := postgresConfig.Postgresqls(namespace).Patch(context.TODO(), postgresql.Name, types.MergePatchType, patch, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if updatedPostgresql.ResourceVersion != postgresql.ResourceVersion {
|
||||
fmt.Printf("postgresql %s is updated with new user %s and with privileges %s.\n", updatedPostgresql.Name, user, permissions)
|
||||
} else {
|
||||
fmt.Printf("postgresql %s is unchanged.\n", updatedPostgresql.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func applyUserPatch(user string, value []string) []byte {
|
||||
ins := map[string]map[string]map[string][]string{"spec": {"users": {user: value}}}
|
||||
patchInstances, err := json.Marshal(ins)
|
||||
if err != nil {
|
||||
log.Fatal(err, "unable to parse number of instances json")
|
||||
}
|
||||
return patchInstances
|
||||
}
|
||||
|
||||
func init() {
|
||||
addUserCmd.Flags().StringP("cluster", "c", "", "add user to the provided cluster.")
|
||||
addUserCmd.Flags().StringP("privileges", "p", "", "add privileges separated by commas without spaces")
|
||||
rootCmd.AddCommand(addUserCmd)
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
postgresConstants "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apiextv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// checkCmd represent kubectl pg check.
|
||||
var checkCmd = &cobra.Command{
|
||||
Use: "check",
|
||||
Short: "Checks the Postgres operator is installed in the k8s cluster",
|
||||
Long: `Checks that the Postgres CRD is registered in a k8s cluster.
|
||||
This means that the operator pod was able to start normally.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
check()
|
||||
},
|
||||
Example: `
|
||||
kubectl pg check
|
||||
`,
|
||||
}
|
||||
|
||||
// check validates postgresql CRD registered or not.
|
||||
func check() *v1.CustomResourceDefinition {
|
||||
config := getConfig()
|
||||
apiExtClient, err := apiextv1.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
crdInfo, err := apiExtClient.CustomResourceDefinitions().Get(context.TODO(), postgresConstants.PostgresCRDResouceName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if crdInfo.Name == postgresConstants.PostgresCRDResouceName {
|
||||
fmt.Printf("Postgres Operator is installed in the k8s cluster.\n")
|
||||
} else {
|
||||
fmt.Printf("Postgres Operator is not installed in the k8s cluster.\n")
|
||||
}
|
||||
return crdInfo
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(checkCmd)
|
||||
}
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
user "os/user"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// connectCmd represents the kubectl pg connect command
|
||||
var connectCmd = &cobra.Command{
|
||||
Use: "connect",
|
||||
Short: "Connects to the shell prompt, psql prompt of postgres cluster",
|
||||
Long: `Connects to the shell prompt, psql prompt of postgres cluster and also to specified replica or master.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clusterName, _ := cmd.Flags().GetString("cluster")
|
||||
master, _ := cmd.Flags().GetBool("master")
|
||||
replica, _ := cmd.Flags().GetString("replica")
|
||||
psql, _ := cmd.Flags().GetBool("psql")
|
||||
userName, _ := cmd.Flags().GetString("user")
|
||||
dbName, _ := cmd.Flags().GetString("database")
|
||||
|
||||
if psql {
|
||||
if userName == "" {
|
||||
userInfo, err := user.Current()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
userName = userInfo.Username
|
||||
}
|
||||
}
|
||||
if dbName == "" {
|
||||
dbName = userName
|
||||
}
|
||||
|
||||
connect(clusterName, master, replica, psql, userName, dbName)
|
||||
},
|
||||
Example: `
|
||||
#connects to the master of postgres cluster
|
||||
kubectl pg connect -c cluster -m
|
||||
|
||||
#connects to the random replica of postgres cluster
|
||||
kubectl pg connect -c cluster
|
||||
|
||||
#connects to the provided replica number of postgres cluster
|
||||
kubectl pg connect -c cluster -r 2
|
||||
|
||||
#connects to psql prompt of master for provided postgres cluster with current shell user
|
||||
kubectl pg connect -c cluster -p -m
|
||||
|
||||
#connects to psql prompt of random replica for provided postgres cluster with provided user and db
|
||||
kubectl pg connect -c cluster -p -u user01 -d db01
|
||||
`,
|
||||
}
|
||||
|
||||
func connect(clusterName string, master bool, replica string, psql bool, user string, dbName string) {
|
||||
config := getConfig()
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
podName := getPodName(clusterName, master, replica)
|
||||
var execRequest *rest.Request
|
||||
|
||||
if psql {
|
||||
execRequest = client.CoreV1().RESTClient().Post().Resource("pods").
|
||||
Name(podName).
|
||||
Namespace(getCurrentNamespace()).
|
||||
SubResource("exec").
|
||||
Param("container", "postgres").
|
||||
Param("command", "psql").
|
||||
Param("command", dbName).
|
||||
Param("command", user).
|
||||
Param("stdin", "true").
|
||||
Param("stdout", "true").
|
||||
Param("stderr", "true").
|
||||
Param("tty", "true")
|
||||
} else {
|
||||
execRequest = client.CoreV1().RESTClient().Post().Resource("pods").
|
||||
Name(podName).
|
||||
Namespace(getCurrentNamespace()).
|
||||
SubResource("exec").
|
||||
Param("container", "postgres").
|
||||
Param("command", "su").
|
||||
Param("command", "postgres").
|
||||
Param("stdin", "true").
|
||||
Param("stdout", "true").
|
||||
Param("stderr", "true").
|
||||
Param("tty", "true")
|
||||
}
|
||||
|
||||
exec, err := remotecommand.NewSPDYExecutor(config, "POST", execRequest.URL())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = exec.StreamWithContext(context.TODO(), remotecommand.StreamOptions{
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
Tty: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
connectCmd.Flags().StringP("cluster", "c", "", "provide the cluster name.")
|
||||
connectCmd.Flags().BoolP("master", "m", false, "connect to master.")
|
||||
connectCmd.Flags().StringP("replica", "r", "", "connect to replica. Specify replica number.")
|
||||
connectCmd.Flags().BoolP("psql", "p", false, "connect to psql prompt.")
|
||||
connectCmd.Flags().StringP("user", "u", "", "provide user.")
|
||||
connectCmd.Flags().StringP("database", "d", "", "provide database name.")
|
||||
rootCmd.AddCommand(connectCmd)
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
v1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// createCmd kubectl pg create.
|
||||
var createCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Creates postgres object using manifest file",
|
||||
Long: `Creates postgres custom resource objects from a manifest file.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fileName, _ := cmd.Flags().GetString("file")
|
||||
create(fileName)
|
||||
},
|
||||
Example: `
|
||||
kubectl pg create -f cluster-manifest.yaml
|
||||
`,
|
||||
}
|
||||
|
||||
// Create postgresql resources.
|
||||
func create(fileName string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ymlFile, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
obj, _, err := decode([]byte(ymlFile), nil, &v1.Postgresql{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
postgresSql := obj.(*v1.Postgresql)
|
||||
_, err = postgresConfig.Postgresqls(postgresSql.Namespace).Create(context.TODO(), postgresSql, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("postgresql %s created.\n", postgresSql.Name)
|
||||
}
|
||||
|
||||
func init() {
|
||||
createCmd.Flags().StringP("file", "f", "", "manifest file with the cluster definition.")
|
||||
rootCmd.AddCommand(createCmd)
|
||||
}
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
v1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// deleteCmd represents kubectl pg delete.
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Deletes postgresql object by cluster-name/manifest file",
|
||||
Long: `Deletes the postgres objects identified by a manifest file or cluster-name.
|
||||
Deleting the manifest is sufficient to delete the cluster.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
namespace, _ := cmd.Flags().GetString("namespace")
|
||||
file, _ := cmd.Flags().GetString("file")
|
||||
|
||||
if file != "" {
|
||||
deleteByFile(file)
|
||||
} else if namespace != "" {
|
||||
if len(args) != 0 {
|
||||
clusterName := args[0]
|
||||
deleteByName(clusterName, namespace)
|
||||
} else {
|
||||
fmt.Println("cluster name can't be empty")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("use the flag either -n or -f to delete a resource.")
|
||||
}
|
||||
},
|
||||
Example: `
|
||||
#Deleting the postgres cluster using manifest file
|
||||
kubectl pg delete -f cluster-manifest.yaml
|
||||
|
||||
#Deleting the postgres cluster using cluster name in current namespace.
|
||||
kubectl pg delete cluster01
|
||||
|
||||
#Deleting the postgres cluster using cluster name in provided namespace
|
||||
kubectl pg delete cluster01 -n namespace01
|
||||
`,
|
||||
}
|
||||
|
||||
func deleteByFile(file string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ymlFile, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
obj, _, err := decode([]byte(ymlFile), nil, &v1.Postgresql{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
postgresSql := obj.(*v1.Postgresql)
|
||||
_, err = postgresConfig.Postgresqls(postgresSql.Namespace).Get(context.TODO(), postgresSql.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
fmt.Printf("Postgresql %s not found with the provided namespace %s : %s \n", postgresSql.Name, postgresSql.Namespace, err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Are you sure you want to remove this PostgreSQL cluster? If so, please type (%s/%s) and hit Enter\n", postgresSql.Namespace, postgresSql.Name)
|
||||
|
||||
confirmAction(postgresSql.Name, postgresSql.Namespace)
|
||||
err = postgresConfig.Postgresqls(postgresSql.Namespace).Delete(context.TODO(), postgresSql.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Postgresql %s deleted from %s.\n", postgresSql.Name, postgresSql.Namespace)
|
||||
}
|
||||
|
||||
func deleteByName(clusterName string, namespace string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = postgresConfig.Postgresqls(namespace).Get(context.TODO(), clusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
fmt.Printf("Postgresql %s not found with the provided namespace %s : %s \n", clusterName, namespace, err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Are you sure you want to remove this PostgreSQL cluster? If so, please type (%s/%s) and hit Enter\n", namespace, clusterName)
|
||||
|
||||
confirmAction(clusterName, namespace)
|
||||
err = postgresConfig.Postgresqls(namespace).Delete(context.TODO(), clusterName, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Postgresql %s deleted from %s.\n", clusterName, namespace)
|
||||
}
|
||||
|
||||
func init() {
|
||||
namespace := getCurrentNamespace()
|
||||
deleteCmd.Flags().StringP("namespace", "n", namespace, "namespace of the cluster to be deleted.")
|
||||
deleteCmd.Flags().StringP("file", "f", "", "manifest file with the cluster definition.")
|
||||
rootCmd.AddCommand(deleteCmd)
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// extVolumeCmd represents the extVolume command
|
||||
var extVolumeCmd = &cobra.Command{
|
||||
Use: "ext-volume",
|
||||
Short: "Increases the volume size of a given Postgres cluster",
|
||||
Long: `Extends the volume of the postgres cluster. But volume cannot be shrinked.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clusterName, _ := cmd.Flags().GetString("cluster")
|
||||
if len(args) > 0 {
|
||||
volume := args[0]
|
||||
extVolume(volume, clusterName)
|
||||
} else {
|
||||
fmt.Println("please enter the cluster name with -c flag & volume in desired units")
|
||||
}
|
||||
},
|
||||
Example: `
|
||||
#Extending the volume size of provided cluster
|
||||
kubectl pg ext-volume 2Gi -c cluster01
|
||||
`,
|
||||
}
|
||||
|
||||
// extend volume with provided size & cluster name
|
||||
func extVolume(increasedVolumeSize string, clusterName string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
namespace := getCurrentNamespace()
|
||||
postgresql, err := postgresConfig.Postgresqls(namespace).Get(context.TODO(), clusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
oldSize, err := resource.ParseQuantity(postgresql.Spec.Volume.Size)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
newSize, err := resource.ParseQuantity(increasedVolumeSize)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = strconv.Atoi(newSize.String())
|
||||
if err == nil {
|
||||
fmt.Println("provide the valid volume size with respective units i.e Ki, Mi, Gi")
|
||||
return
|
||||
}
|
||||
|
||||
if newSize.Value() > oldSize.Value() {
|
||||
patchInstances := volumePatch(newSize)
|
||||
response, err := postgresConfig.Postgresqls(namespace).Patch(context.TODO(), postgresql.Name, types.MergePatchType, patchInstances, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if postgresql.ResourceVersion != response.ResourceVersion {
|
||||
fmt.Printf("%s volume is extended to %s.\n", response.Name, increasedVolumeSize)
|
||||
} else {
|
||||
fmt.Printf("%s volume %s is unchanged.\n", response.Name, postgresql.Spec.Volume.Size)
|
||||
}
|
||||
} else if newSize.Value() == oldSize.Value() {
|
||||
fmt.Println("volume already has the desired size.")
|
||||
} else {
|
||||
fmt.Printf("volume %s size cannot be shrinked.\n", postgresql.Spec.Volume.Size)
|
||||
}
|
||||
}
|
||||
|
||||
func volumePatch(volume resource.Quantity) []byte {
|
||||
patchData := map[string]map[string]map[string]resource.Quantity{"spec": {"volume": {"size": volume}}}
|
||||
patch, err := json.Marshal(patchData)
|
||||
if err != nil {
|
||||
log.Fatal(err, "unable to parse patch to extend volume")
|
||||
}
|
||||
return patch
|
||||
}
|
||||
|
||||
func init() {
|
||||
extVolumeCmd.Flags().StringP("cluster", "c", "", "provide cluster name.")
|
||||
rootCmd.AddCommand(extVolumeCmd)
|
||||
}
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
v1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
TrimCreateTimestamp = 6000000000
|
||||
)
|
||||
|
||||
// listCmd represents kubectl pg list.
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "Lists all the resources of kind postgresql",
|
||||
Long: `Lists all the info specific to postgresql objects.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
allNamespaces, _ := cmd.Flags().GetBool("all-namespaces")
|
||||
namespace, _ := cmd.Flags().GetString("namespace")
|
||||
if allNamespaces {
|
||||
list(allNamespaces, "")
|
||||
} else {
|
||||
list(allNamespaces, namespace)
|
||||
}
|
||||
|
||||
},
|
||||
Example: `
|
||||
#Lists postgres cluster in current namespace
|
||||
kubectl pg list
|
||||
|
||||
#Lists postgres clusters in all namespaces
|
||||
kubectl pg list -A
|
||||
`,
|
||||
}
|
||||
|
||||
// list command to list postgres.
|
||||
func list(allNamespaces bool, namespace string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
listPostgres, err := postgresConfig.Postgresqls(namespace).List(context.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(listPostgres.Items) == 0 {
|
||||
if namespace != "" {
|
||||
fmt.Printf("No Postgresql clusters found in namespace: %v\n", namespace)
|
||||
} else {
|
||||
fmt.Println("No Postgresql clusters found in all namespaces")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if allNamespaces {
|
||||
listAll(listPostgres)
|
||||
} else {
|
||||
listWithNamespace(listPostgres)
|
||||
}
|
||||
}
|
||||
|
||||
func listAll(listPostgres *v1.PostgresqlList) {
|
||||
template := "%-32s%-16s%-12s%-12s%-12s%-12s%-12s\n"
|
||||
fmt.Printf(template, "NAME", "STATUS", "INSTANCES", "VERSION", "AGE", "VOLUME", "NAMESPACE")
|
||||
for _, pgObjs := range listPostgres.Items {
|
||||
fmt.Printf(template, pgObjs.Name,
|
||||
pgObjs.Status.PostgresClusterStatus,
|
||||
strconv.Itoa(int(pgObjs.Spec.NumberOfInstances)),
|
||||
pgObjs.Spec.PostgresqlParam.PgVersion,
|
||||
time.Since(pgObjs.CreationTimestamp.Time).Truncate(TrimCreateTimestamp),
|
||||
pgObjs.Spec.Size, pgObjs.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
func listWithNamespace(listPostgres *v1.PostgresqlList) {
|
||||
template := "%-32s%-16s%-12s%-12s%-12s%-12s\n"
|
||||
fmt.Printf(template, "NAME", "STATUS", "INSTANCES", "VERSION", "AGE", "VOLUME")
|
||||
for _, pgObjs := range listPostgres.Items {
|
||||
fmt.Printf(template, pgObjs.Name,
|
||||
pgObjs.Status.PostgresClusterStatus,
|
||||
strconv.Itoa(int(pgObjs.Spec.NumberOfInstances)),
|
||||
pgObjs.Spec.PostgresqlParam.PgVersion,
|
||||
time.Since(pgObjs.CreationTimestamp.Time).Truncate(TrimCreateTimestamp),
|
||||
pgObjs.Spec.Size)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
listCmd.Flags().BoolP("all-namespaces", "A", false, "list pg resources across all namespaces.")
|
||||
listCmd.Flags().StringP("namespace", "n", getCurrentNamespace(), "provide the namespace")
|
||||
rootCmd.AddCommand(listCmd)
|
||||
}
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
// logsCmd represents the logs command
|
||||
var logsCmd = &cobra.Command{
|
||||
Use: "logs",
|
||||
Short: "This will fetch the logs of the specified postgres cluster & postgres operator",
|
||||
Long: `Fetches the logs of the postgres cluster (i.e master( with -m flag) & replica with (-r 1 pod number) and without -m or -r connects to random replica`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
opLogs, _ := cmd.Flags().GetBool("operator")
|
||||
clusterName, _ := cmd.Flags().GetString("cluster")
|
||||
master, _ := cmd.Flags().GetBool("master")
|
||||
replica, _ := cmd.Flags().GetString("replica")
|
||||
|
||||
if opLogs {
|
||||
operatorLogs()
|
||||
} else {
|
||||
clusterLogs(clusterName, master, replica)
|
||||
}
|
||||
},
|
||||
Example: `
|
||||
#Fetch the logs of the postgres operator
|
||||
kubectl pg logs -o
|
||||
|
||||
#Fetch the logs of the master for provided cluster
|
||||
kubectl pg logs -c cluster01 -m
|
||||
|
||||
#Fetch the logs of the random replica for provided cluster
|
||||
kubectl pg logs -c cluster01
|
||||
|
||||
#Fetch the logs of the provided replica number of the cluster
|
||||
kubectl pg logs -c cluster01 -r 3
|
||||
`,
|
||||
}
|
||||
|
||||
func operatorLogs() {
|
||||
config := getConfig()
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
operator := getPostgresOperator(client)
|
||||
allPods, err := client.CoreV1().Pods(operator.Namespace).List(context.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var operatorPodName string
|
||||
for _, pod := range allPods.Items {
|
||||
for key, value := range pod.Labels {
|
||||
if (key == "name" && value == OperatorName) || (key == "app.kubernetes.io/name" && value == OperatorName) {
|
||||
operatorPodName = pod.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
execRequest := client.CoreV1().RESTClient().Get().Namespace(operator.Namespace).
|
||||
Name(operatorPodName).
|
||||
Resource("pods").
|
||||
SubResource("log").
|
||||
Param("follow", "--follow").
|
||||
Param("container", OperatorName)
|
||||
|
||||
readCloser, err := execRequest.Stream(context.TODO())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer readCloser.Close()
|
||||
_, err = io.Copy(os.Stdout, readCloser)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func clusterLogs(clusterName string, master bool, replica string) {
|
||||
config := getConfig()
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
podName := getPodName(clusterName, master, replica)
|
||||
execRequest := client.CoreV1().RESTClient().Get().Namespace(getCurrentNamespace()).
|
||||
Name(podName).
|
||||
Resource("pods").
|
||||
SubResource("log").
|
||||
Param("follow", "--follow").
|
||||
Param("container", "postgres")
|
||||
|
||||
readCloser, err := execRequest.Stream(context.TODO())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer readCloser.Close()
|
||||
_, err = io.Copy(os.Stdout, readCloser)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(logsCmd)
|
||||
logsCmd.Flags().BoolP("operator", "o", false, "logs of operator")
|
||||
logsCmd.Flags().StringP("cluster", "c", "", "logs for the provided cluster")
|
||||
logsCmd.Flags().BoolP("master", "m", false, "Patroni logs of master")
|
||||
logsCmd.Flags().StringP("replica", "r", "", "Patroni logs of replica. Specify replica number.")
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "kubectl-pg",
|
||||
Short: "kubectl plugin for the Zalando Postgres operator.",
|
||||
Long: `kubectl pg plugin for interaction with Zalando postgres operator.`,
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
viper.SetDefault("author", "Vineeth Pothulapati <vineethpothulapati@outlook.com>")
|
||||
viper.SetDefault("license", "mit")
|
||||
}
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// scaleCmd represents the scale command
|
||||
var scaleCmd = &cobra.Command{
|
||||
Use: "scale",
|
||||
Short: "Add/remove pods to a Postgres cluster",
|
||||
Long: `Scales the postgres objects using cluster-name.
|
||||
Scaling to 0 leads to down time.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clusterName, err := cmd.Flags().GetString("cluster")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
namespace, err := cmd.Flags().GetString("namespace")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
numberOfInstances, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
scale(int32(numberOfInstances), clusterName, namespace)
|
||||
} else {
|
||||
fmt.Println("Please enter number of instances to scale.")
|
||||
}
|
||||
|
||||
},
|
||||
Example: `
|
||||
#Usage
|
||||
kubectl pg scale [NUMBER-OF-INSTANCES] -c [CLUSTER-NAME] -n [NAMESPACE]
|
||||
|
||||
#Scales the number of instances of the provided cluster
|
||||
kubectl pg scale 5 -c cluster01
|
||||
`,
|
||||
}
|
||||
|
||||
func scale(numberOfInstances int32, clusterName string, namespace string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
postgresql, err := postgresConfig.Postgresqls(namespace).Get(context.TODO(), clusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
minInstances, maxInstances := allowedMinMaxInstances(config)
|
||||
|
||||
if minInstances == -1 && maxInstances == -1 {
|
||||
postgresql.Spec.NumberOfInstances = numberOfInstances
|
||||
} else if numberOfInstances <= maxInstances && numberOfInstances >= minInstances {
|
||||
postgresql.Spec.NumberOfInstances = numberOfInstances
|
||||
} else if minInstances == -1 && numberOfInstances < postgresql.Spec.NumberOfInstances ||
|
||||
maxInstances == -1 && numberOfInstances > postgresql.Spec.NumberOfInstances {
|
||||
postgresql.Spec.NumberOfInstances = numberOfInstances
|
||||
} else {
|
||||
log.Fatalf("cannot scale to the provided instances as they don't adhere to MIN_INSTANCES: %v and MAX_INSTANCES: %v provided in configmap or operatorconfiguration", maxInstances, minInstances)
|
||||
}
|
||||
|
||||
if numberOfInstances == 0 {
|
||||
fmt.Printf("Scaling to zero leads to down time. please type %s/%s and hit Enter this serves to confirm the action\n", namespace, clusterName)
|
||||
confirmAction(clusterName, namespace)
|
||||
}
|
||||
|
||||
patchInstances := scalePatch(numberOfInstances)
|
||||
UpdatedPostgres, err := postgresConfig.Postgresqls(namespace).Patch(context.TODO(), postgresql.Name, types.MergePatchType, patchInstances, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if UpdatedPostgres.ResourceVersion != postgresql.ResourceVersion {
|
||||
fmt.Printf("scaled postgresql %s/%s to %d instances\n", UpdatedPostgres.Namespace, UpdatedPostgres.Name, UpdatedPostgres.Spec.NumberOfInstances)
|
||||
return
|
||||
}
|
||||
fmt.Printf("postgresql %s is unchanged.\n", postgresql.Name)
|
||||
}
|
||||
|
||||
func scalePatch(value int32) []byte {
|
||||
instances := map[string]map[string]int32{"spec": {"numberOfInstances": value}}
|
||||
patchInstances, err := json.Marshal(instances)
|
||||
if err != nil {
|
||||
log.Fatal(err, "unable to parse patch for scale")
|
||||
}
|
||||
return patchInstances
|
||||
}
|
||||
|
||||
func allowedMinMaxInstances(config *rest.Config) (int32, int32) {
|
||||
k8sClient, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
operator := getPostgresOperator(k8sClient)
|
||||
|
||||
operatorContainer := operator.Spec.Template.Spec.Containers
|
||||
var configMapName, operatorConfigName string
|
||||
// -1 indicates no limitations for min/max instances
|
||||
minInstances := -1
|
||||
maxInstances := -1
|
||||
for _, envData := range operatorContainer[0].Env {
|
||||
if envData.Name == "CONFIG_MAP_NAME" {
|
||||
configMapName = envData.Value
|
||||
}
|
||||
if envData.Name == "POSTGRES_OPERATOR_CONFIGURATION_OBJECT" {
|
||||
operatorConfigName = envData.Value
|
||||
}
|
||||
}
|
||||
|
||||
if operatorConfigName == "" {
|
||||
configMap, err := k8sClient.CoreV1().ConfigMaps(operator.Namespace).Get(context.TODO(), configMapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
configMapData := configMap.Data
|
||||
for key, value := range configMapData {
|
||||
if key == "min_instances" {
|
||||
minInstances, err = strconv.Atoi(value)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid min instances in configmap %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if key == "max_instances" {
|
||||
maxInstances, err = strconv.Atoi(value)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid max instances in configmap %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if configMapName == "" {
|
||||
pgClient, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
operatorConfig, err := pgClient.OperatorConfigurations(operator.Namespace).Get(context.TODO(), operatorConfigName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatalf("unable to read operator configuration %v", err)
|
||||
}
|
||||
|
||||
minInstances = int(operatorConfig.Configuration.MinInstances)
|
||||
maxInstances = int(operatorConfig.Configuration.MaxInstances)
|
||||
}
|
||||
return int32(minInstances), int32(maxInstances)
|
||||
}
|
||||
|
||||
func init() {
|
||||
namespace := getCurrentNamespace()
|
||||
scaleCmd.Flags().StringP("namespace", "n", namespace, "namespace of the cluster to be scaled")
|
||||
scaleCmd.Flags().StringP("cluster", "c", "", "provide the cluster name.")
|
||||
rootCmd.AddCommand(scaleCmd)
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
v1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// updateCmd represents kubectl pg update
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Updates postgresql object using manifest file",
|
||||
Long: `Updates the state of cluster using manifest file to reflect the changes on the cluster.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fileName, _ := cmd.Flags().GetString("file")
|
||||
updatePgResources(fileName)
|
||||
},
|
||||
Example: `
|
||||
#usage
|
||||
kubectl pg update -f [File-NAME]
|
||||
|
||||
#update the postgres cluster with updated manifest file
|
||||
kubectl pg update -f cluster-manifest.yaml
|
||||
`,
|
||||
}
|
||||
|
||||
// Update postgresql resources.
|
||||
func updatePgResources(fileName string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ymlFile, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
obj, _, err := decode([]byte(ymlFile), nil, &v1.Postgresql{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
newPostgresObj := obj.(*v1.Postgresql)
|
||||
oldPostgresObj, err := postgresConfig.Postgresqls(newPostgresObj.Namespace).Get(context.TODO(), newPostgresObj.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
newPostgresObj.ResourceVersion = oldPostgresObj.ResourceVersion
|
||||
response, err := postgresConfig.Postgresqls(newPostgresObj.Namespace).Update(context.TODO(), newPostgresObj, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if newPostgresObj.ResourceVersion != response.ResourceVersion {
|
||||
fmt.Printf("postgresql %s updated.\n", response.Name)
|
||||
} else {
|
||||
fmt.Printf("postgresql %s is unchanged.\n", response.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
updateCmd.Flags().StringP("file", "f", "", "manifest file with the cluster definition.")
|
||||
rootCmd.AddCommand(updateCmd)
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
)
|
||||
|
||||
const (
|
||||
OperatorName = "postgres-operator"
|
||||
DefaultNamespace = "default"
|
||||
)
|
||||
|
||||
func getConfig() *restclient.Config {
|
||||
var kubeconfig *string
|
||||
var config *restclient.Config
|
||||
envKube := os.Getenv("KUBECONFIG")
|
||||
if envKube != "" {
|
||||
kubeconfig = &envKube
|
||||
} else {
|
||||
if home := homedir.HomeDir(); home != "" {
|
||||
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
|
||||
} else {
|
||||
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
|
||||
}
|
||||
}
|
||||
flag.Parse()
|
||||
var err error
|
||||
config, err = clientcmd.BuildConfigFromFlags("", *kubeconfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func getCurrentNamespace() string {
|
||||
namespace, err := exec.Command("kubectl", "config", "view", "--minify", "--output", "jsonpath={..namespace}").CombinedOutput()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
currentNamespace := string(namespace)
|
||||
if currentNamespace == "" {
|
||||
currentNamespace = DefaultNamespace
|
||||
}
|
||||
return currentNamespace
|
||||
}
|
||||
|
||||
func confirmAction(clusterName string, namespace string) {
|
||||
for {
|
||||
confirmClusterDetails := ""
|
||||
_, err := fmt.Scan(&confirmClusterDetails)
|
||||
if err != nil {
|
||||
log.Fatalf("couldn't get confirmation from the user %v", err)
|
||||
}
|
||||
clusterDetails := strings.Split(confirmClusterDetails, "/")
|
||||
if clusterDetails[0] != namespace || clusterDetails[1] != clusterName {
|
||||
fmt.Printf("cluster name or namespace does not match. Please re-enter %s/%s\nHint: Press (ctrl+c) to exit\n", namespace, clusterName)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getPodName(clusterName string, master bool, replicaNumber string) string {
|
||||
config := getConfig()
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
postgresCluster, err := postgresConfig.Postgresqls(getCurrentNamespace()).Get(context.TODO(), clusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
numOfInstances := postgresCluster.Spec.NumberOfInstances
|
||||
var podName string
|
||||
var podRole string
|
||||
replica := clusterName + "-" + replicaNumber
|
||||
|
||||
for ins := 0; ins < int(numOfInstances); ins++ {
|
||||
pod, err := client.CoreV1().Pods(getCurrentNamespace()).Get(context.TODO(), clusterName+"-"+strconv.Itoa(ins), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
podRole = pod.Labels["spilo-role"]
|
||||
if podRole == "master" && master {
|
||||
podName = pod.Name
|
||||
fmt.Printf("connected to %s with pod name as %s\n", podRole, podName)
|
||||
break
|
||||
} else if podRole == "replica" && !master && (pod.Name == replica || replicaNumber == "") {
|
||||
podName = pod.Name
|
||||
fmt.Printf("connected to %s with pod name as %s\n", podRole, podName)
|
||||
break
|
||||
}
|
||||
}
|
||||
if podName == "" {
|
||||
log.Fatal("Provided replica doesn't exist")
|
||||
}
|
||||
return podName
|
||||
}
|
||||
|
||||
func getPostgresOperator(k8sClient *kubernetes.Clientset) *v1.Deployment {
|
||||
var operator *v1.Deployment
|
||||
operator, err := k8sClient.AppsV1().Deployments(getCurrentNamespace()).Get(context.TODO(), OperatorName, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
return operator
|
||||
}
|
||||
|
||||
allDeployments := k8sClient.AppsV1().Deployments("")
|
||||
listDeployments, err := allDeployments.List(context.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, deployment := range listDeployments.Items {
|
||||
if deployment.Name == OperatorName {
|
||||
operator = deployment.DeepCopy()
|
||||
break
|
||||
} else {
|
||||
for key, value := range deployment.Labels {
|
||||
if key == "app.kubernetes.io/name" && value == OperatorName {
|
||||
operator = deployment.DeepCopy()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return operator
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
var KubectlPgVersion string = "1.0"
|
||||
|
||||
// versionCmd represents the version command
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "version of kubectl-pg & postgres-operator",
|
||||
Long: `version of kubectl-pg and current running postgres-operator`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
namespace, err := cmd.Flags().GetString("namespace")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
version(namespace)
|
||||
},
|
||||
Example: `
|
||||
#Lists the version of kubectl pg plugin and postgres operator in current namespace
|
||||
kubectl pg version
|
||||
|
||||
#Lists the version of kubectl pg plugin and postgres operator in provided namespace
|
||||
kubectl pg version -n namespace01
|
||||
`,
|
||||
}
|
||||
|
||||
func version(namespace string) {
|
||||
fmt.Printf("kubectl-pg: %s\n", KubectlPgVersion)
|
||||
|
||||
config := getConfig()
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
operatorDeployment := getPostgresOperator(client)
|
||||
if operatorDeployment.Name == "" {
|
||||
log.Fatalf("make sure zalando's postgres operator is running in namespace %s", namespace)
|
||||
}
|
||||
operatorImage := operatorDeployment.Spec.Template.Spec.Containers[0].Image
|
||||
imageDetails := strings.Split(operatorImage, ":")
|
||||
imageSplit := len(imageDetails)
|
||||
imageVersion := imageDetails[imageSplit-1]
|
||||
fmt.Printf("Postgres-Operator: %s\n", imageVersion)
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
versionCmd.Flags().StringP("namespace", "n", DefaultNamespace, "provide the namespace.")
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
module github.com/zalando/postgres-operator/kubectl-pg
|
||||
|
||||
go 1.25.3
|
||||
|
||||
require (
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/zalando/postgres-operator v1.15.0
|
||||
k8s.io/api v0.32.9
|
||||
k8s.io/apiextensions-apiserver v0.25.9
|
||||
k8s.io/apimachinery v0.32.9
|
||||
k8s.io/client-go v0.32.9
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/moby/spdystream v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/oauth2 v0.27.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/term v0.37.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
|
@ -1,206 +0,0 @@
|
|||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
|
||||
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
||||
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d h1:LznySqW8MqVeFh+pW6rOkFdld9QQ7jRydBKKM6jyPVI=
|
||||
github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d/go.mod h1:u3hJ0kqCQu/cPpsu3RbCOPZ0d7V3IjPjv1adNRleM9I=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/zalando/postgres-operator v1.15.0 h1:is/7cOrpuV7OwMiN7TG7GgiYHKvaWx8Ptw3hJruFO1I=
|
||||
github.com/zalando/postgres-operator v1.15.0/go.mod h1:1cSOA5dG2dEqdG0uami1RHTGYX92bgAKYASfAhuMtHE=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.32.9 h1:q/59kk8lnecgG0grJqzrmXC1Jcl2hPWp9ltz0FQuoLI=
|
||||
k8s.io/api v0.32.9/go.mod h1:jIfT3rwW4EU1IXZm9qjzSk/2j91k4CJL5vUULrxqp3Y=
|
||||
k8s.io/apiextensions-apiserver v0.25.9 h1:Pycd6lm2auABp9wKQHCFSEPG+NPdFSTJXPST6NJFzB8=
|
||||
k8s.io/apiextensions-apiserver v0.25.9/go.mod h1:ijGxmSG1GLOEaWhTuaEr0M7KUeia3mWCZa6FFQqpt1M=
|
||||
k8s.io/apimachinery v0.32.9 h1:fXk8ktfsxrdThaEOAQFgkhCK7iyoyvS8nbYJ83o/SSs=
|
||||
k8s.io/apimachinery v0.32.9/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||
k8s.io/client-go v0.32.9 h1:ZMyIQ1TEpTDAQni3L2gH1NZzyOA/gHfNcAazzCxMJ0c=
|
||||
k8s.io/client-go v0.32.9/go.mod h1:2OT8aFSYvUjKGadaeT+AVbhkXQSpMAkiSb88Kz2WggI=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
|
||||
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/zalando/postgres-operator/kubectl-pg/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
ARG BASE_IMAGE=registry.opensource.zalan.do/library/ubuntu-22.04:latest
|
||||
ARG BASE_IMAGE=ubuntu:22.04
|
||||
FROM ${BASE_IMAGE}
|
||||
LABEL maintainer="Team ACID @ Zalando <team-acid@zalando.de>"
|
||||
|
||||
|
|
@ -25,11 +25,11 @@ RUN apt-get update \
|
|||
&& curl --silent https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
|
||||
&& apt-get update \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
postgresql-client-18 \
|
||||
postgresql-client-17 \
|
||||
postgresql-client-16 \
|
||||
postgresql-client-15 \
|
||||
postgresql-client-14 \
|
||||
postgresql-client-13 \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
|
|
|||
|
|
@ -183,6 +183,25 @@ function get_master_pod {
|
|||
get_pods "labelSelector=${CLUSTER_NAME_LABEL}%3D${SCOPE},spilo-role%3Dmaster" | tee | head -n 1
|
||||
}
|
||||
|
||||
# Wait for TCP connectivity to the target PostgreSQL pod.
|
||||
# When NetworkPolicy is enforced via iptables, a newly-created pod's IP may not
|
||||
# yet be present in the destination node's ingress allow lists, causing
|
||||
# cross-node connections to be rejected until the next policy sync.
|
||||
function wait_for_pg {
|
||||
local retries=${LOGICAL_BACKUP_CONNECT_RETRIES:-10}
|
||||
local delay=${LOGICAL_BACKUP_CONNECT_RETRY_DELAY:-2}
|
||||
local i
|
||||
for (( i=1; i<=retries; i++ )); do
|
||||
if "$PG_BIN"/pg_isready -h "$PGHOST" -p "${PGPORT:-5432}" -q 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
echo "waiting for $PGHOST:${PGPORT:-5432} to become reachable (attempt $i/$retries)..."
|
||||
sleep "$delay"
|
||||
done
|
||||
echo "ERROR: $PGHOST:${PGPORT:-5432} not reachable after $((retries * delay))s"
|
||||
return 1
|
||||
}
|
||||
|
||||
CURRENT_NODENAME=$(get_current_pod | jq .items[].spec.nodeName --raw-output)
|
||||
export CURRENT_NODENAME
|
||||
|
||||
|
|
@ -197,6 +216,8 @@ for search in "${search_strategy[@]}"; do
|
|||
|
||||
done
|
||||
|
||||
wait_for_pg
|
||||
|
||||
set -x
|
||||
if [ "$LOGICAL_BACKUP_PROVIDER" == "az" ]; then
|
||||
dump | compress > /tmp/azure-backup.sql.gz
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ metadata:
|
|||
# "delete-date": "2020-08-31" # can only be deleted on that day if "delete-date "key is configured
|
||||
# "delete-clustername": "acid-test-cluster" # can only be deleted when name matches if "delete-clustername" key is configured
|
||||
spec:
|
||||
dockerImage: ghcr.io/zalando/spilo-17:4.0-p3
|
||||
dockerImage: ghcr.io/zalando/spilo-18:4.1-p2
|
||||
teamId: "acid"
|
||||
numberOfInstances: 2
|
||||
users: # Application/Robot users
|
||||
|
|
@ -48,7 +48,7 @@ spec:
|
|||
defaultRoles: true
|
||||
defaultUsers: false
|
||||
postgresql:
|
||||
version: "17"
|
||||
version: "18"
|
||||
parameters: # Expert section
|
||||
shared_buffers: "32MB"
|
||||
max_connections: "10"
|
||||
|
|
@ -60,8 +60,8 @@ spec:
|
|||
volume:
|
||||
size: 1Gi
|
||||
# storageClass: my-sc
|
||||
# iops: 1000 # for EBS gp3
|
||||
# throughput: 250 # in MB/s for EBS gp3
|
||||
# iops: 1000
|
||||
# throughput: 250 # in MB/s
|
||||
# selector:
|
||||
# matchExpressions:
|
||||
# - { key: flavour, operator: In, values: [ "banana", "chocolate" ] }
|
||||
|
|
@ -232,6 +232,12 @@ spec:
|
|||
# values:
|
||||
# - enabled
|
||||
|
||||
# Add topology spread constraint to distribute PostgreSQL pods across all nodes labeled with "topology.kubernetes.io/zone".
|
||||
# topologySpreadConstraint:
|
||||
# - maxSkew: 1
|
||||
# topologyKey: topology.kubernetes.io/zone
|
||||
# whenUnsatisfiable: DoNotSchedule
|
||||
|
||||
# Enables change data capture streams for defined database tables
|
||||
# streams:
|
||||
# - applicationId: test-app
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ data:
|
|||
connection_pooler_default_cpu_request: "500m"
|
||||
connection_pooler_default_memory_limit: 100Mi
|
||||
connection_pooler_default_memory_request: 100Mi
|
||||
connection_pooler_image: "registry.opensource.zalan.do/acid/pgbouncer:master-32"
|
||||
connection_pooler_image: "ghcr.io/zalando/postgres-operator/pgbouncer:latest"
|
||||
connection_pooler_max_db_connections: "60"
|
||||
connection_pooler_mode: "transaction"
|
||||
connection_pooler_number_of_instances: "2"
|
||||
|
|
@ -34,18 +34,16 @@ data:
|
|||
default_memory_request: 100Mi
|
||||
# delete_annotation_date_key: delete-date
|
||||
# delete_annotation_name_key: delete-clustername
|
||||
docker_image: ghcr.io/zalando/spilo-17:4.0-p3
|
||||
docker_image: ghcr.io/zalando/spilo-18:4.1-p2
|
||||
# downscaler_annotations: "deployment-time,downscaler/*"
|
||||
enable_admin_role_for_users: "true"
|
||||
enable_crd_registration: "true"
|
||||
enable_crd_validation: "true"
|
||||
enable_cross_namespace_secret: "false"
|
||||
enable_finalizers: "false"
|
||||
enable_database_access: "true"
|
||||
enable_ebs_gp3_migration: "false"
|
||||
enable_ebs_gp3_migration_max_size: "1000"
|
||||
enable_init_containers: "true"
|
||||
enable_lazy_spilo_upgrade: "false"
|
||||
enable_maintenance_windows: "true"
|
||||
enable_master_load_balancer: "false"
|
||||
enable_master_pooler_load_balancer: "false"
|
||||
enable_password_rotation: "false"
|
||||
|
|
@ -75,9 +73,11 @@ data:
|
|||
# infrastructure_roles_secret_name: "postgresql-infrastructure-roles"
|
||||
# infrastructure_roles_secrets: "secretname:monitoring-roles,userkey:user,passwordkey:password,rolekey:inrole"
|
||||
# ignore_instance_limits_annotation_key: ""
|
||||
# ignore_resources_limits_annotation_key: ""
|
||||
# inherited_annotations: owned-by
|
||||
# inherited_labels: application,environment
|
||||
# kube_iam_role: ""
|
||||
# irsa_role_arn: ""
|
||||
kubernetes_use_configmaps: "false"
|
||||
# log_s3_bucket: ""
|
||||
# logical_backup_azure_storage_account_name: ""
|
||||
|
|
@ -86,7 +86,7 @@ data:
|
|||
# logical_backup_cpu_limit: ""
|
||||
# logical_backup_cpu_request: ""
|
||||
logical_backup_cronjob_environment_secret: ""
|
||||
logical_backup_docker_image: "ghcr.io/zalando/postgres-operator/logical-backup:v1.15.1"
|
||||
logical_backup_docker_image: "ghcr.io/zalando/postgres-operator/logical-backup:v2.0.0"
|
||||
# logical_backup_google_application_credentials: ""
|
||||
logical_backup_job_prefix: "logical-backup-"
|
||||
# logical_backup_memory_limit: ""
|
||||
|
|
@ -101,6 +101,7 @@ data:
|
|||
logical_backup_s3_sse: "AES256"
|
||||
logical_backup_s3_retention_time: ""
|
||||
logical_backup_schedule: "30 00 * * *"
|
||||
# maintenance_windows: "Sat:22:00-23:59,Sun:00:00-01:00"
|
||||
major_version_upgrade_mode: "manual"
|
||||
# major_version_upgrade_team_allow_list: ""
|
||||
master_dns_name_format: "{cluster}.{namespace}.{hostedzone}"
|
||||
|
|
@ -112,7 +113,7 @@ data:
|
|||
min_cpu_limit: 250m
|
||||
min_instances: "-1"
|
||||
min_memory_limit: 250Mi
|
||||
minimal_major_version: "13"
|
||||
minimal_major_version: "14"
|
||||
# node_readiness_label: "status:ready"
|
||||
# node_readiness_label_merge: "OR"
|
||||
oauth_token_secret_name: postgresql-operator
|
||||
|
|
@ -162,7 +163,7 @@ data:
|
|||
spilo_privileged: "false"
|
||||
storage_resize_mode: "pvc"
|
||||
super_username: postgres
|
||||
target_major_version: "17"
|
||||
target_major_version: "18"
|
||||
team_admin_role: "admin"
|
||||
team_api_role_configuration: "log_statement:all"
|
||||
teams_api_url: http://fake-teams-api.default.svc.cluster.local
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ spec:
|
|||
serviceAccountName: postgres-operator
|
||||
containers:
|
||||
- name: postgres-operator
|
||||
image: registry.opensource.zalan.do/acid/pgbouncer:master-32
|
||||
image: ghcr.io/zalando/postgres-operator/pgbouncer:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
requests:
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ spec:
|
|||
preparedDatabases:
|
||||
bar: {}
|
||||
postgresql:
|
||||
version: "13"
|
||||
version: "18"
|
||||
sidecars:
|
||||
- name: "exporter"
|
||||
image: "quay.io/prometheuscommunity/postgres-exporter:v0.15.0"
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@ spec:
|
|||
preparedDatabases:
|
||||
bar: {}
|
||||
postgresql:
|
||||
version: "13"
|
||||
version: "14"
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@ spec:
|
|||
preparedDatabases:
|
||||
bar: {}
|
||||
postgresql:
|
||||
version: "17"
|
||||
version: "18"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -19,7 +19,7 @@ spec:
|
|||
serviceAccountName: postgres-operator
|
||||
containers:
|
||||
- name: postgres-operator
|
||||
image: ghcr.io/zalando/postgres-operator:v1.15.1
|
||||
image: ghcr.io/zalando/postgres-operator:v2.0.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
requests:
|
||||
|
|
|
|||
|
|
@ -3,18 +3,23 @@ kind: OperatorConfiguration
|
|||
metadata:
|
||||
name: postgresql-operator-default-configuration
|
||||
configuration:
|
||||
docker_image: ghcr.io/zalando/spilo-17:4.0-p3
|
||||
docker_image: ghcr.io/zalando/spilo-18:4.1-p2
|
||||
# enable_crd_registration: true
|
||||
# crd_categories:
|
||||
# - all
|
||||
# enable_lazy_spilo_upgrade: false
|
||||
enable_maintenance_windows: true
|
||||
enable_pgversion_env_var: true
|
||||
# enable_shm_volume: true
|
||||
enable_spilo_wal_path_compat: false
|
||||
enable_team_id_clustername_prefix: false
|
||||
etcd_host: ""
|
||||
# ignore_instance_limits_annotation_key: ""
|
||||
# kubernetes_use_configmaps: false
|
||||
# ignore_resources_limits_annotation_key: ""
|
||||
kubernetes_use_configmaps: true
|
||||
# maintenance_windows:
|
||||
# - "Sat:22:00-23:59"
|
||||
# - "Sun:00:00-01:00"
|
||||
max_instances: -1
|
||||
min_instances: -1
|
||||
resync_period: 30m
|
||||
|
|
@ -39,8 +44,8 @@ configuration:
|
|||
major_version_upgrade_mode: "manual"
|
||||
# major_version_upgrade_team_allow_list:
|
||||
# - acid
|
||||
minimal_major_version: "13"
|
||||
target_major_version: "17"
|
||||
minimal_major_version: "14"
|
||||
target_major_version: "18"
|
||||
kubernetes:
|
||||
# additional_pod_capabilities:
|
||||
# - "SYS_NICE"
|
||||
|
|
@ -82,6 +87,16 @@ configuration:
|
|||
# inherited_labels:
|
||||
# - application
|
||||
# - environment
|
||||
# liveness_probe:
|
||||
# httpGet:
|
||||
# scheme: HTTP
|
||||
# path: /liveness
|
||||
# port: 8008
|
||||
# initialDelaySeconds: 10
|
||||
# periodSeconds: 10
|
||||
# timeoutSeconds: 5
|
||||
# successThreshold: 1
|
||||
# failureThreshold: 3
|
||||
master_pod_move_timeout: 20m
|
||||
# node_readiness_label:
|
||||
# status: ready
|
||||
|
|
@ -152,10 +167,9 @@ configuration:
|
|||
# additional_secret_mount: "some-secret-name"
|
||||
# additional_secret_mount_path: "/some/dir"
|
||||
aws_region: eu-central-1
|
||||
enable_ebs_gp3_migration: false
|
||||
# enable_ebs_gp3_migration_max_size: 1000
|
||||
# gcp_credentials: ""
|
||||
# kube_iam_role: ""
|
||||
# irsa_role_arn: ""
|
||||
# log_s3_bucket: ""
|
||||
# wal_az_storage_account: ""
|
||||
# wal_gs_bucket: ""
|
||||
|
|
@ -168,7 +182,7 @@ configuration:
|
|||
# logical_backup_cpu_request: ""
|
||||
# logical_backup_memory_limit: ""
|
||||
# logical_backup_memory_request: ""
|
||||
logical_backup_docker_image: "ghcr.io/zalando/postgres-operator/logical-backup:v1.15.1"
|
||||
logical_backup_docker_image: "ghcr.io/zalando/postgres-operator/logical-backup:v2.0.0"
|
||||
# logical_backup_google_application_credentials: ""
|
||||
logical_backup_job_prefix: "logical-backup-"
|
||||
logical_backup_provider: "s3"
|
||||
|
|
@ -213,7 +227,7 @@ configuration:
|
|||
connection_pooler_default_cpu_request: "500m"
|
||||
connection_pooler_default_memory_limit: 100Mi
|
||||
connection_pooler_default_memory_request: 100Mi
|
||||
connection_pooler_image: "registry.opensource.zalan.do/acid/pgbouncer:master-32"
|
||||
connection_pooler_image: "ghcr.io/zalando/postgres-operator/pgbouncer:latest"
|
||||
# connection_pooler_max_db_connections: 60
|
||||
connection_pooler_mode: "transaction"
|
||||
connection_pooler_number_of_instances: 2
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,68 +1,84 @@
|
|||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.17.3
|
||||
labels:
|
||||
app.kubernetes.io/name: postgres-operator
|
||||
name: postgresteams.acid.zalan.do
|
||||
spec:
|
||||
group: acid.zalan.do
|
||||
names:
|
||||
categories:
|
||||
- all
|
||||
kind: PostgresTeam
|
||||
listKind: PostgresTeamList
|
||||
plural: postgresteams
|
||||
singular: postgresteam
|
||||
shortNames:
|
||||
- pgteam
|
||||
categories:
|
||||
- all
|
||||
singular: postgresteam
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: PostgresTeam defines Custom Resource Definition Object for team
|
||||
management.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: PostgresTeamSpec defines the specification for the PostgresTeam
|
||||
TPR.
|
||||
properties:
|
||||
additionalMembers:
|
||||
additionalProperties:
|
||||
description: List of users who will also be added to the Postgres
|
||||
cluster.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
description: Map for teamId and associated additional users
|
||||
type: object
|
||||
additionalSuperuserTeams:
|
||||
additionalProperties:
|
||||
description: List of teams to become Postgres superusers
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
description: Map for teamId and associated additional superuser teams
|
||||
type: object
|
||||
additionalTeams:
|
||||
additionalProperties:
|
||||
description: List of teams whose members will also be added to the
|
||||
Postgres cluster.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
description: Map for teamId and associated additional teams
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- metadata
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- apiVersion
|
||||
- spec
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
enum:
|
||||
- PostgresTeam
|
||||
apiVersion:
|
||||
type: string
|
||||
enum:
|
||||
- acid.zalan.do/v1
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
additionalSuperuserTeams:
|
||||
type: object
|
||||
description: "Map for teamId and associated additional superuser teams"
|
||||
additionalProperties:
|
||||
type: array
|
||||
nullable: true
|
||||
description: "List of teams to become Postgres superusers"
|
||||
items:
|
||||
type: string
|
||||
additionalTeams:
|
||||
type: object
|
||||
description: "Map for teamId and associated additional teams"
|
||||
additionalProperties:
|
||||
type: array
|
||||
nullable: true
|
||||
description: "List of teams whose members will also be added to the Postgres cluster"
|
||||
items:
|
||||
type: string
|
||||
additionalMembers:
|
||||
type: object
|
||||
description: "Map for teamId and associated additional users"
|
||||
additionalProperties:
|
||||
type: array
|
||||
nullable: true
|
||||
description: "List of users who will also be added to the Postgres cluster"
|
||||
items:
|
||||
type: string
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ spec:
|
|||
size: 1Gi
|
||||
numberOfInstances: 1
|
||||
postgresql:
|
||||
version: "17"
|
||||
# Make this a standby cluster and provide either the s3 bucket path of source cluster or the remote primary host for continuous streaming.
|
||||
version: "18"
|
||||
# Make this a standby cluster. You can specify s3_wal_path or gs_wal_path for WAL archive,
|
||||
# standby_host for remote primary streaming, or combine standby_host with either WAL path.
|
||||
# Note: s3_wal_path and gs_wal_path are mutually exclusive.
|
||||
standby:
|
||||
# s3_wal_path: "s3://mybucket/spilo/acid-minimal-cluster/abcd1234-2a4b-4b2a-8c9c-c1234defg567/wal/14/"
|
||||
standby_host: "acid-minimal-cluster.default"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ theme: readthedocs
|
|||
nav:
|
||||
- Concepts: 'index.md'
|
||||
- Quickstart: 'quickstart.md'
|
||||
- Migration from v1: 'migrate.md'
|
||||
- Postgres Operator UI: 'operator-ui.md'
|
||||
- Admin guide: 'administrator.md'
|
||||
- User guide: 'user.md'
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -4,7 +4,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type postgresqlCopy Postgresql
|
||||
|
|
@ -31,7 +30,8 @@ func (m *MaintenanceWindow) UnmarshalJSON(data []byte) error {
|
|||
err error
|
||||
)
|
||||
|
||||
parts := strings.Split(string(data[1:len(data)-1]), "-")
|
||||
dataStr := strings.Trim(string(data), "\"")
|
||||
parts := strings.Split(dataStr, "-")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("incorrect maintenance window format")
|
||||
}
|
||||
|
|
@ -119,29 +119,3 @@ func (p *Postgresql) UnmarshalJSON(data []byte) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON convert to Duration from byte slice of json
|
||||
func (d *Duration) UnmarshalJSON(b []byte) error {
|
||||
var (
|
||||
v interface{}
|
||||
err error
|
||||
)
|
||||
if err = json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
t, err := time.ParseDuration(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = Duration(t)
|
||||
return nil
|
||||
case float64:
|
||||
t := time.Duration(val)
|
||||
*d = Duration(t)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("could not recognize type %T as a valid type to unmarshal to Duration", val)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,19 +5,24 @@ package v1
|
|||
import (
|
||||
"github.com/zalando/postgres-operator/pkg/util/config"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/zalando/postgres-operator/pkg/spec"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +genclient
|
||||
// +genclient:onlyVerbs=get
|
||||
// +genclient:noStatus
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// OperatorConfiguration defines the specification for the OperatorConfiguration.
|
||||
// +k8s:deepcopy-gen=true
|
||||
// +kubebuilder:resource:categories=all,shortName=opconfig,scope=Namespaced
|
||||
// +kubebuilder:printcolumn:name="Image",type=string,JSONPath=`.configuration.docker_image`,description="Spilo image to be used for Pods"
|
||||
// +kubebuilder:printcolumn:name="Cluster-Label",type=string,JSONPath=`.configuration.kubernetes.cluster_name_label`,description="Label for K8s resources created by operator"
|
||||
// +kubebuilder:printcolumn:name="Service-Account",type=string,JSONPath=`.configuration.kubernetes.pod_service_account_name`,description="Name of service account to be used"
|
||||
// +kubebuilder:printcolumn:name="Min-Instances",type=integer,JSONPath=`.configuration.min_instances`,description="Minimum number of instances per Postgres cluster"
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,description="Age of the OperatorConfiguration resource"
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:metadata:labels=app.kubernetes.io/name=postgres-operator
|
||||
type OperatorConfiguration struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata"`
|
||||
|
|
@ -37,194 +42,320 @@ type OperatorConfigurationList struct {
|
|||
|
||||
// PostgresUsersConfiguration defines the system users of Postgres.
|
||||
type PostgresUsersConfiguration struct {
|
||||
SuperUsername string `json:"super_username,omitempty"`
|
||||
ReplicationUsername string `json:"replication_username,omitempty"`
|
||||
AdditionalOwnerRoles []string `json:"additional_owner_roles,omitempty"`
|
||||
EnablePasswordRotation bool `json:"enable_password_rotation,omitempty"`
|
||||
PasswordRotationInterval uint32 `json:"password_rotation_interval,omitempty"`
|
||||
PasswordRotationUserRetention uint32 `json:"password_rotation_user_retention,omitempty"`
|
||||
// +kubebuilder:default=postgres
|
||||
SuperUsername string `json:"super_username,omitempty"`
|
||||
// +kubebuilder:default=standby
|
||||
ReplicationUsername string `json:"replication_username,omitempty"`
|
||||
AdditionalOwnerRoles []string `json:"additional_owner_roles,omitempty"`
|
||||
EnablePasswordRotation bool `json:"enable_password_rotation,omitempty"`
|
||||
// +kubebuilder:default=90
|
||||
PasswordRotationInterval uint32 `json:"password_rotation_interval,omitempty"`
|
||||
// +kubebuilder:default=120
|
||||
PasswordRotationUserRetention uint32 `json:"password_rotation_user_retention,omitempty"`
|
||||
}
|
||||
|
||||
// MajorVersionUpgradeConfiguration defines how to execute major version upgrades of Postgres.
|
||||
type MajorVersionUpgradeConfiguration struct {
|
||||
MajorVersionUpgradeMode string `json:"major_version_upgrade_mode" default:"manual"` // off - no actions, manual - manifest triggers action, full - manifest and minimal version violation trigger upgrade
|
||||
// +kubebuilder:validation:Enum=off;manual;full
|
||||
// +kubebuilder:default=manual
|
||||
MajorVersionUpgradeMode string `json:"major_version_upgrade_mode,omitempty"` // off - no actions, manual - manifest triggers action, full - manifest and minimal version violation trigger upgrade
|
||||
MajorVersionUpgradeTeamAllowList []string `json:"major_version_upgrade_team_allow_list,omitempty"`
|
||||
MinimalMajorVersion string `json:"minimal_major_version" default:"13"`
|
||||
TargetMajorVersion string `json:"target_major_version" default:"17"`
|
||||
// +kubebuilder:default="14"
|
||||
MinimalMajorVersion string `json:"minimal_major_version,omitempty"`
|
||||
// +kubebuilder:default="18"
|
||||
TargetMajorVersion string `json:"target_major_version,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesMetaConfiguration defines k8s conf required for all Postgres clusters and the operator itself
|
||||
type KubernetesMetaConfiguration struct {
|
||||
EnableOwnerReferences *bool `json:"enable_owner_references,omitempty"`
|
||||
EnableOwnerReferences *bool `json:"enable_owner_references,omitempty"`
|
||||
// +kubebuilder:default=postgres-pod
|
||||
PodServiceAccountName string `json:"pod_service_account_name,omitempty"`
|
||||
// TODO: change it to the proper json
|
||||
PodServiceAccountDefinition string `json:"pod_service_account_definition,omitempty"`
|
||||
PodServiceAccountRoleBindingDefinition string `json:"pod_service_account_role_binding_definition,omitempty"`
|
||||
PodTerminateGracePeriod Duration `json:"pod_terminate_grace_period,omitempty"`
|
||||
SpiloPrivileged bool `json:"spilo_privileged,omitempty"`
|
||||
SpiloAllowPrivilegeEscalation *bool `json:"spilo_allow_privilege_escalation,omitempty"`
|
||||
SpiloRunAsUser *int64 `json:"spilo_runasuser,omitempty"`
|
||||
SpiloRunAsGroup *int64 `json:"spilo_runasgroup,omitempty"`
|
||||
SpiloFSGroup *int64 `json:"spilo_fsgroup,omitempty"`
|
||||
AdditionalPodCapabilities []string `json:"additional_pod_capabilities,omitempty"`
|
||||
WatchedNamespace string `json:"watched_namespace,omitempty"`
|
||||
PDBNameFormat config.StringTemplate `json:"pdb_name_format,omitempty"`
|
||||
PDBMasterLabelSelector *bool `json:"pdb_master_label_selector,omitempty"`
|
||||
EnablePodDisruptionBudget *bool `json:"enable_pod_disruption_budget,omitempty"`
|
||||
StorageResizeMode string `json:"storage_resize_mode,omitempty"`
|
||||
EnableInitContainers *bool `json:"enable_init_containers,omitempty"`
|
||||
EnableSidecars *bool `json:"enable_sidecars,omitempty"`
|
||||
SharePgSocketWithSidecars *bool `json:"share_pgsocket_with_sidecars,omitempty"`
|
||||
SecretNameTemplate config.StringTemplate `json:"secret_name_template,omitempty"`
|
||||
ClusterDomain string `json:"cluster_domain,omitempty"`
|
||||
OAuthTokenSecretName spec.NamespacedName `json:"oauth_token_secret_name,omitempty"`
|
||||
InfrastructureRolesSecretName spec.NamespacedName `json:"infrastructure_roles_secret_name,omitempty"`
|
||||
InfrastructureRolesDefs []*config.InfrastructureRole `json:"infrastructure_roles_secrets,omitempty"`
|
||||
PodRoleLabel string `json:"pod_role_label,omitempty"`
|
||||
ClusterLabels map[string]string `json:"cluster_labels,omitempty"`
|
||||
InheritedLabels []string `json:"inherited_labels,omitempty"`
|
||||
InheritedAnnotations []string `json:"inherited_annotations,omitempty"`
|
||||
DownscalerAnnotations []string `json:"downscaler_annotations,omitempty"`
|
||||
IgnoredAnnotations []string `json:"ignored_annotations,omitempty"`
|
||||
ClusterNameLabel string `json:"cluster_name_label,omitempty"`
|
||||
DeleteAnnotationDateKey string `json:"delete_annotation_date_key,omitempty"`
|
||||
DeleteAnnotationNameKey string `json:"delete_annotation_name_key,omitempty"`
|
||||
NodeReadinessLabel map[string]string `json:"node_readiness_label,omitempty"`
|
||||
NodeReadinessLabelMerge string `json:"node_readiness_label_merge,omitempty"`
|
||||
CustomPodAnnotations map[string]string `json:"custom_pod_annotations,omitempty"`
|
||||
PodServiceAccountDefinition string `json:"pod_service_account_definition,omitempty"`
|
||||
PodServiceAccountRoleBindingDefinition string `json:"pod_service_account_role_binding_definition,omitempty"`
|
||||
// +kubebuilder:default="5m"
|
||||
// Postgres pods are terminated forcefully after this timeout
|
||||
PodTerminateGracePeriod *metav1.Duration `json:"pod_terminate_grace_period,omitempty"`
|
||||
// +optional
|
||||
LivenessProbe *v1.Probe `json:"liveness_probe"`
|
||||
SpiloPrivileged bool `json:"spilo_privileged,omitempty"`
|
||||
// +kubebuilder:default=true
|
||||
SpiloAllowPrivilegeEscalation *bool `json:"spilo_allow_privilege_escalation,omitempty"`
|
||||
SpiloRunAsUser *int64 `json:"spilo_runasuser,omitempty"`
|
||||
SpiloRunAsGroup *int64 `json:"spilo_runasgroup,omitempty"`
|
||||
SpiloFSGroup *int64 `json:"spilo_fsgroup,omitempty"`
|
||||
AdditionalPodCapabilities []string `json:"additional_pod_capabilities,omitempty"`
|
||||
WatchedNamespace string `json:"watched_namespace,omitempty"`
|
||||
// +kubebuilder:default="postgres-{cluster}-pdb"
|
||||
// defines the template for PDB names
|
||||
PDBNameFormat config.StringTemplate `json:"pdb_name_format,omitempty"`
|
||||
// +kubebuilder:default=true
|
||||
PDBMasterLabelSelector *bool `json:"pdb_master_label_selector,omitempty"`
|
||||
// +kubebuilder:default=true
|
||||
EnablePodDisruptionBudget *bool `json:"enable_pod_disruption_budget,omitempty"`
|
||||
// +kubebuilder:validation:Enum=ebs;mixed;pvc;off
|
||||
// +kubebuilder:default=pvc
|
||||
StorageResizeMode string `json:"storage_resize_mode,omitempty"`
|
||||
// +kubebuilder:default=true
|
||||
EnableInitContainers *bool `json:"enable_init_containers,omitempty"`
|
||||
// +kubebuilder:default=true
|
||||
EnableSidecars *bool `json:"enable_sidecars,omitempty"`
|
||||
SharePgSocketWithSidecars *bool `json:"share_pgsocket_with_sidecars,omitempty"`
|
||||
// +kubebuilder:default="{username}.{cluster}.credentials.{tprkind}.{tprgroup}"
|
||||
// template for database user secrets generated by the operator,
|
||||
// here username contains the namespace in the format namespace.username
|
||||
// if the user is in different namespace than cluster and cross namespace secrets
|
||||
// are enabled via `enable_cross_namespace_secret` flag in the configuration.
|
||||
SecretNameTemplate config.StringTemplate `json:"secret_name_template,omitempty"`
|
||||
// +kubebuilder:default="cluster.local"
|
||||
ClusterDomain string `json:"cluster_domain,omitempty"`
|
||||
// +kubebuilder:default=postgres-operator
|
||||
// namespaced name of the secret containing the OAuth2 token to pass to the teams API
|
||||
OAuthTokenSecretName spec.NamespacedName `json:"oauth_token_secret_name,omitempty"`
|
||||
InfrastructureRolesSecretName spec.NamespacedName `json:"infrastructure_roles_secret_name,omitempty"`
|
||||
// +kubebuilder:validation:Type=array
|
||||
// namespaced name of the secret containing infrastructure roles names and passwords
|
||||
InfrastructureRolesDefs []*config.InfrastructureRole `json:"infrastructure_roles_secrets,omitempty"`
|
||||
// +kubebuilder:default=spilo-role
|
||||
PodRoleLabel string `json:"pod_role_label,omitempty"`
|
||||
// +kubebuilder:default={application: spilo}
|
||||
ClusterLabels map[string]string `json:"cluster_labels,omitempty"`
|
||||
InheritedLabels []string `json:"inherited_labels,omitempty"`
|
||||
InheritedAnnotations []string `json:"inherited_annotations,omitempty"`
|
||||
DownscalerAnnotations []string `json:"downscaler_annotations,omitempty"`
|
||||
IgnoredAnnotations []string `json:"ignored_annotations,omitempty"`
|
||||
// +kubebuilder:default=cluster-name
|
||||
ClusterNameLabel string `json:"cluster_name_label,omitempty"`
|
||||
DeleteAnnotationDateKey string `json:"delete_annotation_date_key,omitempty"`
|
||||
DeleteAnnotationNameKey string `json:"delete_annotation_name_key,omitempty"`
|
||||
NodeReadinessLabel map[string]string `json:"node_readiness_label,omitempty"`
|
||||
// +kubebuilder:validation:Enum=AND;OR
|
||||
NodeReadinessLabelMerge string `json:"node_readiness_label_merge,omitempty"`
|
||||
CustomPodAnnotations map[string]string `json:"custom_pod_annotations,omitempty"`
|
||||
// TODO: use a proper toleration structure?
|
||||
PodToleration map[string]string `json:"toleration,omitempty"`
|
||||
PodEnvironmentConfigMap spec.NamespacedName `json:"pod_environment_configmap,omitempty"`
|
||||
PodEnvironmentSecret string `json:"pod_environment_secret,omitempty"`
|
||||
PodPriorityClassName string `json:"pod_priority_class_name,omitempty"`
|
||||
MasterPodMoveTimeout Duration `json:"master_pod_move_timeout,omitempty"`
|
||||
EnablePodAntiAffinity bool `json:"enable_pod_antiaffinity,omitempty"`
|
||||
PodAntiAffinityPreferredDuringScheduling bool `json:"pod_antiaffinity_preferred_during_scheduling,omitempty"`
|
||||
PodAntiAffinityTopologyKey string `json:"pod_antiaffinity_topology_key,omitempty"`
|
||||
PodManagementPolicy string `json:"pod_management_policy,omitempty"`
|
||||
PersistentVolumeClaimRetentionPolicy map[string]string `json:"persistent_volume_claim_retention_policy,omitempty"`
|
||||
EnableSecretsDeletion *bool `json:"enable_secrets_deletion,omitempty"`
|
||||
EnablePersistentVolumeClaimDeletion *bool `json:"enable_persistent_volume_claim_deletion,omitempty"`
|
||||
EnableReadinessProbe bool `json:"enable_readiness_probe,omitempty"`
|
||||
EnableCrossNamespaceSecret bool `json:"enable_cross_namespace_secret,omitempty"`
|
||||
EnableFinalizers *bool `json:"enable_finalizers,omitempty"`
|
||||
PodToleration map[string]string `json:"toleration,omitempty"`
|
||||
// namespaced name of the ConfigMap with environment variables to populate on every pod
|
||||
PodEnvironmentConfigMap spec.NamespacedName `json:"pod_environment_configmap,omitempty"`
|
||||
PodEnvironmentSecret string `json:"pod_environment_secret,omitempty"`
|
||||
PodPriorityClassName string `json:"pod_priority_class_name,omitempty"`
|
||||
// +kubebuilder:default="20m"
|
||||
// timeout for successful migration of master pods from unschedulable node
|
||||
MasterPodMoveTimeout *metav1.Duration `json:"master_pod_move_timeout,omitempty"`
|
||||
EnablePodAntiAffinity bool `json:"enable_pod_antiaffinity,omitempty"`
|
||||
PodAntiAffinityPreferredDuringScheduling bool `json:"pod_antiaffinity_preferred_during_scheduling,omitempty"`
|
||||
// +kubebuilder:default="kubernetes.io/hostname"
|
||||
PodAntiAffinityTopologyKey string `json:"pod_antiaffinity_topology_key,omitempty"`
|
||||
// +kubebuilder:validation:Enum=ordered_ready;parallel
|
||||
// +kubebuilder:default=ordered_ready
|
||||
PodManagementPolicy string `json:"pod_management_policy,omitempty"`
|
||||
PersistentVolumeClaimRetentionPolicy map[string]string `json:"persistent_volume_claim_retention_policy,omitempty"`
|
||||
|
||||
// +kubebuilder:default=true
|
||||
EnableSecretsDeletion *bool `json:"enable_secrets_deletion,omitempty"`
|
||||
// +kubebuilder:default=true
|
||||
EnablePersistentVolumeClaimDeletion *bool `json:"enable_persistent_volume_claim_deletion,omitempty"`
|
||||
EnableReadinessProbe bool `json:"enable_readiness_probe,omitempty"`
|
||||
EnableCrossNamespaceSecret bool `json:"enable_cross_namespace_secret,omitempty"`
|
||||
EnableFinalizers *bool `json:"enable_finalizers,omitempty"`
|
||||
}
|
||||
|
||||
// PostgresPodResourcesDefaults defines the spec of default resources
|
||||
type PostgresPodResourcesDefaults struct {
|
||||
DefaultCPURequest string `json:"default_cpu_request,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+m|\d+(\.\d{1,3})?)$`
|
||||
DefaultCPURequest string `json:"default_cpu_request,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
DefaultMemoryRequest string `json:"default_memory_request,omitempty"`
|
||||
DefaultCPULimit string `json:"default_cpu_limit,omitempty"`
|
||||
DefaultMemoryLimit string `json:"default_memory_limit,omitempty"`
|
||||
MinCPULimit string `json:"min_cpu_limit,omitempty"`
|
||||
MinMemoryLimit string `json:"min_memory_limit,omitempty"`
|
||||
MaxCPURequest string `json:"max_cpu_request,omitempty"`
|
||||
MaxMemoryRequest string `json:"max_memory_request,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+m|\d+(\.\d{1,3})?)$`
|
||||
DefaultCPULimit string `json:"default_cpu_limit,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
DefaultMemoryLimit string `json:"default_memory_limit,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+m|\d+(\.\d{1,3})?)$`
|
||||
MinCPULimit string `json:"min_cpu_limit,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
MinMemoryLimit string `json:"min_memory_limit,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+m|\d+(\.\d{1,3})?)$`
|
||||
MaxCPURequest string `json:"max_cpu_request,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
MaxMemoryRequest string `json:"max_memory_request,omitempty"`
|
||||
}
|
||||
|
||||
// OperatorTimeouts defines the timeout of ResourceCheck, PodWait, ReadyWait
|
||||
type OperatorTimeouts struct {
|
||||
ResourceCheckInterval Duration `json:"resource_check_interval,omitempty"`
|
||||
ResourceCheckTimeout Duration `json:"resource_check_timeout,omitempty"`
|
||||
PodLabelWaitTimeout Duration `json:"pod_label_wait_timeout,omitempty"`
|
||||
PodDeletionWaitTimeout Duration `json:"pod_deletion_wait_timeout,omitempty"`
|
||||
ReadyWaitInterval Duration `json:"ready_wait_interval,omitempty"`
|
||||
ReadyWaitTimeout Duration `json:"ready_wait_timeout,omitempty"`
|
||||
PatroniAPICheckInterval Duration `json:"patroni_api_check_interval,omitempty"`
|
||||
PatroniAPICheckTimeout Duration `json:"patroni_api_check_timeout,omitempty"`
|
||||
// +kubebuilder:default="3s"
|
||||
// interval to wait between consecutive attempts to check for some K8s resources
|
||||
ResourceCheckInterval *metav1.Duration `json:"resource_check_interval,omitempty"`
|
||||
// +kubebuilder:default="10m"
|
||||
// timeout when waiting for the presence of a certain K8s resource
|
||||
ResourceCheckTimeout *metav1.Duration `json:"resource_check_timeout,omitempty"`
|
||||
// +kubebuilder:default="10m"
|
||||
// timeout when waiting for pod role and cluster labels
|
||||
PodLabelWaitTimeout *metav1.Duration `json:"pod_label_wait_timeout,omitempty"`
|
||||
// +kubebuilder:default="10m"
|
||||
// timeout when waiting for the Postgres pods to be deleted
|
||||
PodDeletionWaitTimeout *metav1.Duration `json:"pod_deletion_wait_timeout,omitempty"`
|
||||
// +kubebuilder:default="4s"
|
||||
// interval between consecutive attempts waiting for postgresql CRD to be created
|
||||
ReadyWaitInterval *metav1.Duration `json:"ready_wait_interval,omitempty"`
|
||||
// +kubebuilder:default="30s"
|
||||
// timeout for the complete postgres CRD creation
|
||||
ReadyWaitTimeout *metav1.Duration `json:"ready_wait_timeout,omitempty"`
|
||||
// +kubebuilder:default="1s"
|
||||
// interval between consecutive attempts of operator calling the Patroni API
|
||||
PatroniAPICheckInterval *metav1.Duration `json:"patroni_api_check_interval,omitempty"`
|
||||
// +kubebuilder:default="5s"
|
||||
// timeout when waiting for successful response from Patroni API
|
||||
PatroniAPICheckTimeout *metav1.Duration `json:"patroni_api_check_timeout,omitempty"`
|
||||
}
|
||||
|
||||
// LoadBalancerConfiguration defines the LB configuration
|
||||
type LoadBalancerConfiguration struct {
|
||||
DbHostedZone string `json:"db_hosted_zone,omitempty"`
|
||||
EnableMasterLoadBalancer bool `json:"enable_master_load_balancer,omitempty"`
|
||||
EnableMasterPoolerLoadBalancer bool `json:"enable_master_pooler_load_balancer,omitempty"`
|
||||
EnableReplicaLoadBalancer bool `json:"enable_replica_load_balancer,omitempty"`
|
||||
EnableReplicaPoolerLoadBalancer bool `json:"enable_replica_pooler_load_balancer,omitempty"`
|
||||
CustomServiceAnnotations map[string]string `json:"custom_service_annotations,omitempty"`
|
||||
MasterDNSNameFormat config.StringTemplate `json:"master_dns_name_format,omitempty"`
|
||||
MasterLegacyDNSNameFormat config.StringTemplate `json:"master_legacy_dns_name_format,omitempty"`
|
||||
ReplicaDNSNameFormat config.StringTemplate `json:"replica_dns_name_format,omitempty"`
|
||||
ReplicaLegacyDNSNameFormat config.StringTemplate `json:"replica_legacy_dns_name_format,omitempty"`
|
||||
ExternalTrafficPolicy string `json:"external_traffic_policy" default:"Cluster"`
|
||||
DbHostedZone string `json:"db_hosted_zone,omitempty"`
|
||||
EnableMasterLoadBalancer bool `json:"enable_master_load_balancer,omitempty"`
|
||||
EnableMasterPoolerLoadBalancer bool `json:"enable_master_pooler_load_balancer,omitempty"`
|
||||
EnableReplicaLoadBalancer bool `json:"enable_replica_load_balancer,omitempty"`
|
||||
EnableReplicaPoolerLoadBalancer bool `json:"enable_replica_pooler_load_balancer,omitempty"`
|
||||
|
||||
// NodePort flags kept in LoadBalancerConfiguration because all the other parameters apply here too
|
||||
|
||||
EnableMasterNodePort bool `json:"enable_master_node_port,omitempty"`
|
||||
EnableMasterPoolerNodePort bool `json:"enable_master_pooler_node_port,omitempty"`
|
||||
EnableReplicaNodePort bool `json:"enable_replica_node_port,omitempty"`
|
||||
EnableReplicaPoolerNodePort bool `json:"enable_replica_pooler_node_port,omitempty"`
|
||||
|
||||
CustomServiceAnnotations map[string]string `json:"custom_service_annotations,omitempty"`
|
||||
// +kubebuilder:default="{cluster}.{namespace}.{hostedzone}"
|
||||
// defines the DNS name string template for the master load balancer cluster
|
||||
MasterDNSNameFormat config.StringTemplate `json:"master_dns_name_format,omitempty"`
|
||||
// +kubebuilder:default="{cluster}.{team}.{hostedzone}"
|
||||
// deprecated DNS template for master load balancer using team name
|
||||
MasterLegacyDNSNameFormat config.StringTemplate `json:"master_legacy_dns_name_format,omitempty"`
|
||||
// +kubebuilder:default="{cluster}-repl.{namespace}.{hostedzone}"
|
||||
// defines the DNS name string template for the replica load balancer cluster
|
||||
ReplicaDNSNameFormat config.StringTemplate `json:"replica_dns_name_format,omitempty"`
|
||||
// +kubebuilder:default="{cluster}-repl.{team}.{hostedzone}"
|
||||
// deprecated DNS template for replica load balancer using team name
|
||||
ReplicaLegacyDNSNameFormat config.StringTemplate `json:"replica_legacy_dns_name_format,omitempty"`
|
||||
// +kubebuilder:validation:Enum=Cluster;Local
|
||||
// +kubebuilder:default=Cluster
|
||||
ExternalTrafficPolicy string `json:"external_traffic_policy,omitempty"`
|
||||
}
|
||||
|
||||
// AWSGCPConfiguration defines the configuration for AWS
|
||||
// TODO complete Google Cloud Platform (GCP) configuration
|
||||
type AWSGCPConfiguration struct {
|
||||
WALES3Bucket string `json:"wal_s3_bucket,omitempty"`
|
||||
AWSRegion string `json:"aws_region,omitempty"`
|
||||
WALGSBucket string `json:"wal_gs_bucket,omitempty"`
|
||||
GCPCredentials string `json:"gcp_credentials,omitempty"`
|
||||
WALAZStorageAccount string `json:"wal_az_storage_account,omitempty"`
|
||||
LogS3Bucket string `json:"log_s3_bucket,omitempty"`
|
||||
KubeIAMRole string `json:"kube_iam_role,omitempty"`
|
||||
AdditionalSecretMount string `json:"additional_secret_mount,omitempty"`
|
||||
AdditionalSecretMountPath string `json:"additional_secret_mount_path,omitempty"`
|
||||
EnableEBSGp3Migration bool `json:"enable_ebs_gp3_migration" default:"false"`
|
||||
EnableEBSGp3MigrationMaxSize int64 `json:"enable_ebs_gp3_migration_max_size" default:"1000"`
|
||||
WALES3Bucket string `json:"wal_s3_bucket,omitempty"`
|
||||
// +kubebuilder:default=eu-central-1
|
||||
AWSRegion string `json:"aws_region,omitempty"`
|
||||
WALGSBucket string `json:"wal_gs_bucket,omitempty"`
|
||||
GCPCredentials string `json:"gcp_credentials,omitempty"`
|
||||
WALAZStorageAccount string `json:"wal_az_storage_account,omitempty"`
|
||||
LogS3Bucket string `json:"log_s3_bucket,omitempty"`
|
||||
KubeIAMRole string `json:"kube_iam_role,omitempty"`
|
||||
IRSARoleARN string `json:"irsa_role_arn,omitempty"`
|
||||
AdditionalSecretMount string `json:"additional_secret_mount,omitempty"`
|
||||
AdditionalSecretMountPath string `json:"additional_secret_mount_path,omitempty"`
|
||||
}
|
||||
|
||||
// OperatorDebugConfiguration defines options for the debug mode
|
||||
type OperatorDebugConfiguration struct {
|
||||
DebugLogging bool `json:"debug_logging,omitempty"`
|
||||
EnableDBAccess bool `json:"enable_database_access,omitempty"`
|
||||
// +kubebuilder:default=true
|
||||
DebugLogging *bool `json:"debug_logging,omitempty"`
|
||||
// +kubebuilder:default=true
|
||||
EnableDBAccess *bool `json:"enable_database_access,omitempty"`
|
||||
}
|
||||
|
||||
// TeamsAPIConfiguration defines the configuration of TeamsAPI
|
||||
type TeamsAPIConfiguration struct {
|
||||
EnableTeamsAPI bool `json:"enable_teams_api,omitempty"`
|
||||
TeamsAPIUrl string `json:"teams_api_url,omitempty"`
|
||||
TeamAPIRoleConfiguration map[string]string `json:"team_api_role_configuration,omitempty"`
|
||||
EnableTeamSuperuser bool `json:"enable_team_superuser,omitempty"`
|
||||
EnableAdminRoleForUsers bool `json:"enable_admin_role_for_users,omitempty"`
|
||||
TeamAdminRole string `json:"team_admin_role,omitempty"`
|
||||
PamRoleName string `json:"pam_role_name,omitempty"`
|
||||
PamConfiguration string `json:"pam_configuration,omitempty"`
|
||||
ProtectedRoles []string `json:"protected_role_names,omitempty"`
|
||||
PostgresSuperuserTeams []string `json:"postgres_superuser_teams,omitempty"`
|
||||
EnablePostgresTeamCRD bool `json:"enable_postgres_team_crd,omitempty"`
|
||||
EnablePostgresTeamCRDSuperusers bool `json:"enable_postgres_team_crd_superusers,omitempty"`
|
||||
EnableTeamMemberDeprecation bool `json:"enable_team_member_deprecation,omitempty"`
|
||||
RoleDeletionSuffix string `json:"role_deletion_suffix,omitempty"`
|
||||
EnableTeamsAPI bool `json:"enable_teams_api,omitempty"`
|
||||
// +kubebuilder:default="https://teams.example.com/api/"
|
||||
TeamsAPIUrl string `json:"teams_api_url,omitempty"`
|
||||
// +kubebuilder:default={log_statement: all}
|
||||
TeamAPIRoleConfiguration map[string]string `json:"team_api_role_configuration,omitempty"`
|
||||
EnableTeamSuperuser bool `json:"enable_team_superuser,omitempty"`
|
||||
// +kubebuilder:default=true
|
||||
EnableAdminRoleForUsers bool `json:"enable_admin_role_for_users,omitempty"`
|
||||
// +kubebuilder:default=admin
|
||||
TeamAdminRole string `json:"team_admin_role,omitempty"`
|
||||
// +kubebuilder:default=zalandos
|
||||
PamRoleName string `json:"pam_role_name,omitempty"`
|
||||
// +kubebuilder:default="https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"
|
||||
PamConfiguration string `json:"pam_configuration,omitempty"`
|
||||
// +kubebuilder:default={"admin", "cron_admin"}
|
||||
ProtectedRoles []string `json:"protected_role_names,omitempty"`
|
||||
PostgresSuperuserTeams []string `json:"postgres_superuser_teams,omitempty"`
|
||||
// +kubebuilder:default=true
|
||||
EnablePostgresTeamCRD bool `json:"enable_postgres_team_crd,omitempty"`
|
||||
EnablePostgresTeamCRDSuperusers bool `json:"enable_postgres_team_crd_superusers,omitempty"`
|
||||
EnableTeamMemberDeprecation bool `json:"enable_team_member_deprecation,omitempty"`
|
||||
// +kubebuilder:default=_deleted
|
||||
RoleDeletionSuffix string `json:"role_deletion_suffix,omitempty"`
|
||||
}
|
||||
|
||||
// LoggingRESTAPIConfiguration defines Logging API conf
|
||||
type LoggingRESTAPIConfiguration struct {
|
||||
APIPort int `json:"api_port,omitempty"`
|
||||
RingLogLines int `json:"ring_log_lines,omitempty"`
|
||||
// +kubebuilder:default=8080
|
||||
APIPort int `json:"api_port,omitempty"`
|
||||
// +kubebuilder:default=100
|
||||
RingLogLines int `json:"ring_log_lines,omitempty"`
|
||||
// +kubebuilder:default=1000
|
||||
ClusterHistoryEntries int `json:"cluster_history_entries,omitempty"`
|
||||
}
|
||||
|
||||
// ScalyrConfiguration defines the configuration for ScalyrAPI
|
||||
type ScalyrConfiguration struct {
|
||||
ScalyrAPIKey string `json:"scalyr_api_key,omitempty"`
|
||||
ScalyrImage string `json:"scalyr_image,omitempty"`
|
||||
ScalyrServerURL string `json:"scalyr_server_url,omitempty"`
|
||||
ScalyrCPURequest string `json:"scalyr_cpu_request,omitempty"`
|
||||
ScalyrAPIKey string `json:"scalyr_api_key,omitempty"`
|
||||
ScalyrImage string `json:"scalyr_image,omitempty"`
|
||||
// +kubebuilder:default="https://upload.eu.scalyr.com"
|
||||
ScalyrServerURL string `json:"scalyr_server_url,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+m|\d+(\.\d{1,3})?)$`
|
||||
// +kubebuilder:default="100m"
|
||||
ScalyrCPURequest string `json:"scalyr_cpu_request,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
// +kubebuilder:default="50Mi"
|
||||
ScalyrMemoryRequest string `json:"scalyr_memory_request,omitempty"`
|
||||
ScalyrCPULimit string `json:"scalyr_cpu_limit,omitempty"`
|
||||
ScalyrMemoryLimit string `json:"scalyr_memory_limit,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+m|\d+(\.\d{1,3})?)$`
|
||||
// +kubebuilder:default="1"
|
||||
ScalyrCPULimit string `json:"scalyr_cpu_limit,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
// +kubebuilder:default="500Mi"
|
||||
ScalyrMemoryLimit string `json:"scalyr_memory_limit,omitempty"`
|
||||
}
|
||||
|
||||
// ConnectionPoolerConfiguration defines default configuration for connection pooler
|
||||
type ConnectionPoolerConfiguration struct {
|
||||
NumberOfInstances *int32 `json:"connection_pooler_number_of_instances,omitempty"`
|
||||
Schema string `json:"connection_pooler_schema,omitempty"`
|
||||
User string `json:"connection_pooler_user,omitempty"`
|
||||
Image string `json:"connection_pooler_image,omitempty"`
|
||||
Mode string `json:"connection_pooler_mode,omitempty"`
|
||||
MaxDBConnections *int32 `json:"connection_pooler_max_db_connections,omitempty"`
|
||||
DefaultCPURequest string `json:"connection_pooler_default_cpu_request,omitempty"`
|
||||
// +kubebuilder:validation:Minimum=1
|
||||
// +kubebuilder:default=2
|
||||
NumberOfInstances *int32 `json:"connection_pooler_number_of_instances,omitempty"`
|
||||
// +kubebuilder:default=pooler
|
||||
Schema string `json:"connection_pooler_schema,omitempty"`
|
||||
// +kubebuilder:default=pooler
|
||||
User string `json:"connection_pooler_user,omitempty"`
|
||||
// +kubebuilder:default="ghcr.io/zalando/postgres-operator/pgbouncer:latest"
|
||||
Image string `json:"connection_pooler_image,omitempty"`
|
||||
// +kubebuilder:validation:Enum=session;transaction
|
||||
// +kubebuilder:default=transaction
|
||||
Mode string `json:"connection_pooler_mode,omitempty"`
|
||||
MaxDBConnections *int32 `json:"connection_pooler_max_db_connections,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+m|\d+(\.\d{1,3})?)$`
|
||||
DefaultCPURequest string `json:"connection_pooler_default_cpu_request,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
DefaultMemoryRequest string `json:"connection_pooler_default_memory_request,omitempty"`
|
||||
DefaultCPULimit string `json:"connection_pooler_default_cpu_limit,omitempty"`
|
||||
DefaultMemoryLimit string `json:"connection_pooler_default_memory_limit,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+m|\d+(\.\d{1,3})?)$`
|
||||
DefaultCPULimit string `json:"connection_pooler_default_cpu_limit,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
DefaultMemoryLimit string `json:"connection_pooler_default_memory_limit,omitempty"`
|
||||
}
|
||||
|
||||
// OperatorLogicalBackupConfiguration defines configuration for logical backup
|
||||
type OperatorLogicalBackupConfiguration struct {
|
||||
Schedule string `json:"logical_backup_schedule,omitempty"`
|
||||
DockerImage string `json:"logical_backup_docker_image,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$`
|
||||
// +kubebuilder:default="30 00 * * *"
|
||||
Schedule string `json:"logical_backup_schedule,omitempty"`
|
||||
// +kubebuilder:default="ghcr.io/zalando/postgres-operator/logical-backup:v2.0.0"
|
||||
DockerImage string `json:"logical_backup_docker_image,omitempty"`
|
||||
// +kubebuilder:validation:Enum=az;gcs;s3
|
||||
// +kubebuilder:default=s3
|
||||
BackupProvider string `json:"logical_backup_provider,omitempty"`
|
||||
AzureStorageAccountName string `json:"logical_backup_azure_storage_account_name,omitempty"`
|
||||
AzureStorageContainer string `json:"logical_backup_azure_storage_container,omitempty"`
|
||||
|
|
@ -238,12 +369,26 @@ type OperatorLogicalBackupConfiguration struct {
|
|||
S3SSE string `json:"logical_backup_s3_sse,omitempty"`
|
||||
RetentionTime string `json:"logical_backup_s3_retention_time,omitempty"`
|
||||
GoogleApplicationCredentials string `json:"logical_backup_google_application_credentials,omitempty"`
|
||||
JobPrefix string `json:"logical_backup_job_prefix,omitempty"`
|
||||
CronjobEnvironmentSecret string `json:"logical_backup_cronjob_environment_secret,omitempty"`
|
||||
CPURequest string `json:"logical_backup_cpu_request,omitempty"`
|
||||
MemoryRequest string `json:"logical_backup_memory_request,omitempty"`
|
||||
CPULimit string `json:"logical_backup_cpu_limit,omitempty"`
|
||||
MemoryLimit string `json:"logical_backup_memory_limit,omitempty"`
|
||||
// +kubebuilder:default=logical-backup-
|
||||
JobPrefix string `json:"logical_backup_job_prefix,omitempty"`
|
||||
CronjobEnvironmentSecret string `json:"logical_backup_cronjob_environment_secret,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+m|\d+(\.\d{1,3})?)$`
|
||||
CPURequest string `json:"logical_backup_cpu_request,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
MemoryRequest string `json:"logical_backup_memory_request,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+m|\d+(\.\d{1,3})?)$`
|
||||
CPULimit string `json:"logical_backup_cpu_limit,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
MemoryLimit string `json:"logical_backup_memory_limit,omitempty"`
|
||||
// +kubebuilder:validation:Minimum=0
|
||||
// +kubebuilder:default=3
|
||||
SuccessfulJobsHistoryLimit *int32 `json:"logical_backup_successful_jobs_history_limit,omitempty"`
|
||||
// +kubebuilder:validation:Minimum=0
|
||||
// +kubebuilder:default=3
|
||||
FailedJobsHistoryLimit *int32 `json:"logical_backup_failed_jobs_history_limit,omitempty"`
|
||||
// +kubebuilder:validation:Minimum=0
|
||||
// +kubebuilder:default=86400
|
||||
TTLSecondsAfterFinished *int32 `json:"logical_backup_ttl_seconds_after_finished,omitempty"`
|
||||
}
|
||||
|
||||
// PatroniConfiguration defines configuration for Patroni
|
||||
|
|
@ -253,42 +398,80 @@ type PatroniConfiguration struct {
|
|||
|
||||
// OperatorConfigurationData defines the operation config
|
||||
type OperatorConfigurationData struct {
|
||||
EnableCRDRegistration *bool `json:"enable_crd_registration,omitempty"`
|
||||
EnableCRDValidation *bool `json:"enable_crd_validation,omitempty"`
|
||||
CRDCategories []string `json:"crd_categories,omitempty"`
|
||||
EnableLazySpiloUpgrade bool `json:"enable_lazy_spilo_upgrade,omitempty"`
|
||||
EnablePgVersionEnvVar bool `json:"enable_pgversion_env_var,omitempty"`
|
||||
EnableSpiloWalPathCompat bool `json:"enable_spilo_wal_path_compat,omitempty"`
|
||||
EnableTeamIdClusternamePrefix bool `json:"enable_team_id_clustername_prefix,omitempty"`
|
||||
EtcdHost string `json:"etcd_host,omitempty"`
|
||||
KubernetesUseConfigMaps bool `json:"kubernetes_use_configmaps,omitempty"`
|
||||
DockerImage string `json:"docker_image,omitempty"`
|
||||
Workers uint32 `json:"workers,omitempty"`
|
||||
ResyncPeriod Duration `json:"resync_period,omitempty"`
|
||||
RepairPeriod Duration `json:"repair_period,omitempty"`
|
||||
SetMemoryRequestToLimit bool `json:"set_memory_request_to_limit,omitempty"`
|
||||
ShmVolume *bool `json:"enable_shm_volume,omitempty"`
|
||||
SidecarImages map[string]string `json:"sidecar_docker_images,omitempty"` // deprecated in favour of SidecarContainers
|
||||
SidecarContainers []v1.Container `json:"sidecars,omitempty"`
|
||||
PostgresUsersConfiguration PostgresUsersConfiguration `json:"users"`
|
||||
MajorVersionUpgrade MajorVersionUpgradeConfiguration `json:"major_version_upgrade"`
|
||||
Kubernetes KubernetesMetaConfiguration `json:"kubernetes"`
|
||||
PostgresPodResources PostgresPodResourcesDefaults `json:"postgres_pod_resources"`
|
||||
Timeouts OperatorTimeouts `json:"timeouts"`
|
||||
LoadBalancer LoadBalancerConfiguration `json:"load_balancer"`
|
||||
AWSGCP AWSGCPConfiguration `json:"aws_or_gcp"`
|
||||
OperatorDebug OperatorDebugConfiguration `json:"debug"`
|
||||
TeamsAPI TeamsAPIConfiguration `json:"teams_api"`
|
||||
LoggingRESTAPI LoggingRESTAPIConfiguration `json:"logging_rest_api"`
|
||||
Scalyr ScalyrConfiguration `json:"scalyr"`
|
||||
LogicalBackup OperatorLogicalBackupConfiguration `json:"logical_backup"`
|
||||
ConnectionPooler ConnectionPoolerConfiguration `json:"connection_pooler"`
|
||||
Patroni PatroniConfiguration `json:"patroni"`
|
||||
// +kubebuilder:default=true
|
||||
EnableCRDRegistration *bool `json:"enable_crd_registration,omitempty"`
|
||||
CRDCategories []string `json:"crd_categories,omitempty"`
|
||||
EnableLazySpiloUpgrade bool `json:"enable_lazy_spilo_upgrade,omitempty"`
|
||||
// +kubebuilder:default=true
|
||||
EnablePgVersionEnvVar bool `json:"enable_pgversion_env_var,omitempty"`
|
||||
EnableSpiloWalPathCompat bool `json:"enable_spilo_wal_path_compat,omitempty"`
|
||||
EnableTeamIdClusternamePrefix bool `json:"enable_team_id_clustername_prefix,omitempty"`
|
||||
// +kubebuilder:default=""
|
||||
EtcdHost string `json:"etcd_host,omitempty"`
|
||||
// +kubebuilder:default=true
|
||||
KubernetesUseConfigMaps *bool `json:"kubernetes_use_configmaps,omitempty"`
|
||||
// +kubebuilder:default="ghcr.io/zalando/spilo-18:4.1-p2"
|
||||
DockerImage string `json:"docker_image,omitempty"`
|
||||
// +kubebuilder:validation:Minimum=1
|
||||
// +kubebuilder:default=8
|
||||
Workers uint32 `json:"workers,omitempty"`
|
||||
// +kubebuilder:default="30m"
|
||||
// period between consecutive sync requests
|
||||
ResyncPeriod *metav1.Duration `json:"resync_period,omitempty"`
|
||||
// +kubebuilder:default="5m"
|
||||
// period between consecutive repair requests
|
||||
RepairPeriod *metav1.Duration `json:"repair_period,omitempty"`
|
||||
// +kubebuilder:default=true
|
||||
EnableMaintenanceWindows *bool `json:"enable_maintenance_windows,omitempty"`
|
||||
// +kubebuilder:validation:Type=array
|
||||
// +kubebuilder:validation:items:Pattern=`^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ *$`
|
||||
MaintenanceWindows []string `json:"maintenance_windows,omitempty"`
|
||||
SetMemoryRequestToLimit bool `json:"set_memory_request_to_limit,omitempty"`
|
||||
// +kubebuilder:default=true
|
||||
ShmVolume *bool `json:"enable_shm_volume,omitempty"`
|
||||
SidecarImages map[string]string `json:"sidecar_docker_images,omitempty"` // deprecated in favour of SidecarContainers
|
||||
// +kubebuilder:validation:XPreserveUnknownFields
|
||||
// +kubebuilder:validation:Type=object
|
||||
// +kubebuilder:validation:Schemaless
|
||||
SidecarContainers []v1.Container `json:"sidecars,omitempty"`
|
||||
// +optional
|
||||
PostgresUsersConfiguration PostgresUsersConfiguration `json:"users"`
|
||||
// +optional
|
||||
MajorVersionUpgrade MajorVersionUpgradeConfiguration `json:"major_version_upgrade"`
|
||||
// +optional
|
||||
Kubernetes KubernetesMetaConfiguration `json:"kubernetes"`
|
||||
// +optional
|
||||
PostgresPodResources PostgresPodResourcesDefaults `json:"postgres_pod_resources"`
|
||||
// +optional
|
||||
Timeouts OperatorTimeouts `json:"timeouts"`
|
||||
// +optional
|
||||
LoadBalancer LoadBalancerConfiguration `json:"load_balancer"`
|
||||
// +optional
|
||||
AWSGCP AWSGCPConfiguration `json:"aws_or_gcp"`
|
||||
// +optional
|
||||
OperatorDebug OperatorDebugConfiguration `json:"debug"`
|
||||
// +optional
|
||||
TeamsAPI TeamsAPIConfiguration `json:"teams_api"`
|
||||
// +optional
|
||||
LoggingRESTAPI LoggingRESTAPIConfiguration `json:"logging_rest_api"`
|
||||
// +optional
|
||||
Scalyr ScalyrConfiguration `json:"scalyr"`
|
||||
// +optional
|
||||
LogicalBackup OperatorLogicalBackupConfiguration `json:"logical_backup"`
|
||||
// +optional
|
||||
ConnectionPooler ConnectionPoolerConfiguration `json:"connection_pooler"`
|
||||
// +optional
|
||||
Patroni PatroniConfiguration `json:"patroni"`
|
||||
|
||||
MinInstances int32 `json:"min_instances,omitempty"`
|
||||
MaxInstances int32 `json:"max_instances,omitempty"`
|
||||
IgnoreInstanceLimitsAnnotationKey string `json:"ignore_instance_limits_annotation_key,omitempty"`
|
||||
// +kubebuilder:validation:Minimum=-1
|
||||
// +kubebuilder:default=-1
|
||||
// -1 = disabled
|
||||
MinInstances int32 `json:"min_instances,omitempty"`
|
||||
// +kubebuilder:validation:Minimum=-1
|
||||
// +kubebuilder:default=-1
|
||||
// -1 = disabled
|
||||
MaxInstances int32 `json:"max_instances,omitempty"`
|
||||
|
||||
IgnoreInstanceLimitsAnnotationKey string `json:"ignore_instance_limits_annotation_key,omitempty"`
|
||||
IgnoreResourcesLimitsAnnotationKey string `json:"ignore_resources_limits_annotation_key,omitempty"`
|
||||
}
|
||||
|
||||
// Duration shortens this frequently used name
|
||||
type Duration time.Duration
|
||||
|
|
|
|||
|
|
@ -0,0 +1,964 @@
|
|||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.17.3
|
||||
labels:
|
||||
app.kubernetes.io/name: postgres-operator
|
||||
name: operatorconfigurations.acid.zalan.do
|
||||
spec:
|
||||
group: acid.zalan.do
|
||||
names:
|
||||
categories:
|
||||
- all
|
||||
kind: OperatorConfiguration
|
||||
listKind: OperatorConfigurationList
|
||||
plural: operatorconfigurations
|
||||
shortNames:
|
||||
- opconfig
|
||||
singular: operatorconfiguration
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: Spilo image to be used for Pods
|
||||
jsonPath: .configuration.docker_image
|
||||
name: Image
|
||||
type: string
|
||||
- description: Label for K8s resources created by operator
|
||||
jsonPath: .configuration.kubernetes.cluster_name_label
|
||||
name: Cluster-Label
|
||||
type: string
|
||||
- description: Name of service account to be used
|
||||
jsonPath: .configuration.kubernetes.pod_service_account_name
|
||||
name: Service-Account
|
||||
type: string
|
||||
- description: Minimum number of instances per Postgres cluster
|
||||
jsonPath: .configuration.min_instances
|
||||
name: Min-Instances
|
||||
type: integer
|
||||
- description: Age of the OperatorConfiguration resource
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: OperatorConfiguration defines the specification for the OperatorConfiguration.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
configuration:
|
||||
description: OperatorConfigurationData defines the operation config
|
||||
properties:
|
||||
aws_or_gcp:
|
||||
description: AWSGCPConfiguration defines the configuration for AWS
|
||||
properties:
|
||||
additional_secret_mount:
|
||||
type: string
|
||||
additional_secret_mount_path:
|
||||
type: string
|
||||
aws_region:
|
||||
default: eu-central-1
|
||||
type: string
|
||||
gcp_credentials:
|
||||
type: string
|
||||
irsa_role_arn:
|
||||
type: string
|
||||
kube_iam_role:
|
||||
type: string
|
||||
log_s3_bucket:
|
||||
type: string
|
||||
wal_az_storage_account:
|
||||
type: string
|
||||
wal_gs_bucket:
|
||||
type: string
|
||||
wal_s3_bucket:
|
||||
type: string
|
||||
type: object
|
||||
connection_pooler:
|
||||
description: ConnectionPoolerConfiguration defines default configuration
|
||||
for connection pooler
|
||||
properties:
|
||||
connection_pooler_default_cpu_limit:
|
||||
pattern: ^(\d+m|\d+(\.\d{1,3})?)$
|
||||
type: string
|
||||
connection_pooler_default_cpu_request:
|
||||
pattern: ^(\d+m|\d+(\.\d{1,3})?)$
|
||||
type: string
|
||||
connection_pooler_default_memory_limit:
|
||||
pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$
|
||||
type: string
|
||||
connection_pooler_default_memory_request:
|
||||
pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$
|
||||
type: string
|
||||
connection_pooler_image:
|
||||
default: ghcr.io/zalando/postgres-operator/pgbouncer:latest
|
||||
type: string
|
||||
connection_pooler_max_db_connections:
|
||||
format: int32
|
||||
type: integer
|
||||
connection_pooler_mode:
|
||||
default: transaction
|
||||
enum:
|
||||
- session
|
||||
- transaction
|
||||
type: string
|
||||
connection_pooler_number_of_instances:
|
||||
default: 2
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
connection_pooler_schema:
|
||||
default: pooler
|
||||
type: string
|
||||
connection_pooler_user:
|
||||
default: pooler
|
||||
type: string
|
||||
type: object
|
||||
crd_categories:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
debug:
|
||||
description: OperatorDebugConfiguration defines options for the debug
|
||||
mode
|
||||
properties:
|
||||
debug_logging:
|
||||
default: true
|
||||
type: boolean
|
||||
enable_database_access:
|
||||
default: true
|
||||
type: boolean
|
||||
type: object
|
||||
docker_image:
|
||||
default: ghcr.io/zalando/spilo-18:4.1-p2
|
||||
type: string
|
||||
enable_crd_registration:
|
||||
default: true
|
||||
type: boolean
|
||||
enable_lazy_spilo_upgrade:
|
||||
type: boolean
|
||||
enable_maintenance_windows:
|
||||
default: true
|
||||
type: boolean
|
||||
enable_pgversion_env_var:
|
||||
default: true
|
||||
type: boolean
|
||||
enable_shm_volume:
|
||||
default: true
|
||||
type: boolean
|
||||
enable_spilo_wal_path_compat:
|
||||
type: boolean
|
||||
enable_team_id_clustername_prefix:
|
||||
type: boolean
|
||||
etcd_host:
|
||||
default: ""
|
||||
type: string
|
||||
ignore_instance_limits_annotation_key:
|
||||
type: string
|
||||
ignore_resources_limits_annotation_key:
|
||||
type: string
|
||||
kubernetes:
|
||||
description: KubernetesMetaConfiguration defines k8s conf required
|
||||
for all Postgres clusters and the operator itself
|
||||
properties:
|
||||
additional_pod_capabilities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
cluster_domain:
|
||||
default: cluster.local
|
||||
type: string
|
||||
cluster_labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
default:
|
||||
application: spilo
|
||||
type: object
|
||||
cluster_name_label:
|
||||
default: cluster-name
|
||||
type: string
|
||||
custom_pod_annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
delete_annotation_date_key:
|
||||
type: string
|
||||
delete_annotation_name_key:
|
||||
type: string
|
||||
downscaler_annotations:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
enable_cross_namespace_secret:
|
||||
type: boolean
|
||||
enable_finalizers:
|
||||
type: boolean
|
||||
enable_init_containers:
|
||||
default: true
|
||||
type: boolean
|
||||
enable_owner_references:
|
||||
type: boolean
|
||||
enable_persistent_volume_claim_deletion:
|
||||
default: true
|
||||
type: boolean
|
||||
enable_pod_antiaffinity:
|
||||
type: boolean
|
||||
enable_pod_disruption_budget:
|
||||
default: true
|
||||
type: boolean
|
||||
enable_readiness_probe:
|
||||
type: boolean
|
||||
enable_secrets_deletion:
|
||||
default: true
|
||||
type: boolean
|
||||
enable_sidecars:
|
||||
default: true
|
||||
type: boolean
|
||||
ignored_annotations:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
infrastructure_roles_secret_name:
|
||||
description: |-
|
||||
NamespacedName comprises a resource name, with a mandatory namespace,
|
||||
rendered as "<namespace>/<name>". Being a type captures intent and
|
||||
helps make sure that UIDs, namespaced names and non-namespaced names
|
||||
do not get conflated in code. For most use cases, namespace and name
|
||||
will already have been format validated at the API entry point, so we
|
||||
don't do that here. Where that's not the case (e.g. in testing),
|
||||
consider using NamespacedNameOrDie() in testing.go in this package.
|
||||
|
||||
from: https://github.com/kubernetes/apimachinery/blob/master/pkg/types/namespacedname.go
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
infrastructure_roles_secrets:
|
||||
description: namespaced name of the secret containing infrastructure
|
||||
roles names and passwords
|
||||
items:
|
||||
properties:
|
||||
defaultrolevalue:
|
||||
type: string
|
||||
defaultuservalue:
|
||||
type: string
|
||||
details:
|
||||
description: This field point out the detailed yaml definition
|
||||
of the role, if exists
|
||||
type: string
|
||||
passwordkey:
|
||||
type: string
|
||||
rolekey:
|
||||
type: string
|
||||
secretname:
|
||||
description: |-
|
||||
Name of a secret which describes the role, and optionally name of a
|
||||
configmap with an extra information
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
template:
|
||||
type: boolean
|
||||
userkey:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
inherited_annotations:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
inherited_labels:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
liveness_probe:
|
||||
description: |-
|
||||
Probe describes a health check to be performed against a container to determine whether it is
|
||||
alive or ready to receive traffic.
|
||||
properties:
|
||||
exec:
|
||||
description: Exec specifies a command to execute in the container.
|
||||
properties:
|
||||
command:
|
||||
description: |-
|
||||
Command is the command line to execute inside the container, the working directory for the
|
||||
command is root ('/') in the container's filesystem. The command is simply exec'd, it is
|
||||
not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use
|
||||
a shell, you need to explicitly call out to that shell.
|
||||
Exit status of 0 is treated as live/healthy and non-zero is unhealthy.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
type: object
|
||||
failureThreshold:
|
||||
description: |-
|
||||
Minimum consecutive failures for the probe to be considered failed after having succeeded.
|
||||
Defaults to 3. Minimum value is 1.
|
||||
format: int32
|
||||
type: integer
|
||||
grpc:
|
||||
description: GRPC specifies a GRPC HealthCheckRequest.
|
||||
properties:
|
||||
port:
|
||||
description: Port number of the gRPC service. Number must
|
||||
be in the range 1 to 65535.
|
||||
format: int32
|
||||
type: integer
|
||||
service:
|
||||
default: ""
|
||||
description: |-
|
||||
Service is the name of the service to place in the gRPC HealthCheckRequest
|
||||
(see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).
|
||||
|
||||
If this is not specified, the default behavior is defined by gRPC.
|
||||
type: string
|
||||
required:
|
||||
- port
|
||||
type: object
|
||||
httpGet:
|
||||
description: HTTPGet specifies an HTTP GET request to perform.
|
||||
properties:
|
||||
host:
|
||||
description: |-
|
||||
Host name to connect to, defaults to the pod IP. You probably want to set
|
||||
"Host" in httpHeaders instead.
|
||||
type: string
|
||||
httpHeaders:
|
||||
description: Custom headers to set in the request. HTTP
|
||||
allows repeated headers.
|
||||
items:
|
||||
description: HTTPHeader describes a custom header to
|
||||
be used in HTTP probes
|
||||
properties:
|
||||
name:
|
||||
description: |-
|
||||
The header field name.
|
||||
This will be canonicalized upon output, so case-variant names will be understood as the same header.
|
||||
type: string
|
||||
value:
|
||||
description: The header field value
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- value
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
path:
|
||||
description: Path to access on the HTTP server.
|
||||
type: string
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Name or number of the port to access on the container.
|
||||
Number must be in the range 1 to 65535.
|
||||
Name must be an IANA_SVC_NAME.
|
||||
x-kubernetes-int-or-string: true
|
||||
scheme:
|
||||
description: |-
|
||||
Scheme to use for connecting to the host.
|
||||
Defaults to HTTP.
|
||||
type: string
|
||||
required:
|
||||
- port
|
||||
type: object
|
||||
initialDelaySeconds:
|
||||
description: |-
|
||||
Number of seconds after the container has started before liveness probes are initiated.
|
||||
More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
|
||||
format: int32
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: |-
|
||||
How often (in seconds) to perform the probe.
|
||||
Default to 10 seconds. Minimum value is 1.
|
||||
format: int32
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
Minimum consecutive successes for the probe to be considered successful after having failed.
|
||||
Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1.
|
||||
format: int32
|
||||
type: integer
|
||||
tcpSocket:
|
||||
description: TCPSocket specifies a connection to a TCP port.
|
||||
properties:
|
||||
host:
|
||||
description: 'Optional: Host name to connect to, defaults
|
||||
to the pod IP.'
|
||||
type: string
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
Number or name of the port to access on the container.
|
||||
Number must be in the range 1 to 65535.
|
||||
Name must be an IANA_SVC_NAME.
|
||||
x-kubernetes-int-or-string: true
|
||||
required:
|
||||
- port
|
||||
type: object
|
||||
terminationGracePeriodSeconds:
|
||||
description: |-
|
||||
Optional duration in seconds the pod needs to terminate gracefully upon probe failure.
|
||||
The grace period is the duration in seconds after the processes running in the pod are sent
|
||||
a termination signal and the time when the processes are forcibly halted with a kill signal.
|
||||
Set this value longer than the expected cleanup time for your process.
|
||||
If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this
|
||||
value overrides the value provided by the pod spec.
|
||||
Value must be non-negative integer. The value zero indicates stop immediately via
|
||||
the kill signal (no opportunity to shut down).
|
||||
This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.
|
||||
Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset.
|
||||
format: int64
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: |-
|
||||
Number of seconds after which the probe times out.
|
||||
Defaults to 1 second. Minimum value is 1.
|
||||
More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
|
||||
format: int32
|
||||
type: integer
|
||||
type: object
|
||||
master_pod_move_timeout:
|
||||
default: 20m
|
||||
description: timeout for successful migration of master pods from
|
||||
unschedulable node
|
||||
type: string
|
||||
node_readiness_label:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
node_readiness_label_merge:
|
||||
enum:
|
||||
- AND
|
||||
- OR
|
||||
type: string
|
||||
oauth_token_secret_name:
|
||||
default: postgres-operator
|
||||
description: namespaced name of the secret containing the OAuth2
|
||||
token to pass to the teams API
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
pdb_master_label_selector:
|
||||
default: true
|
||||
type: boolean
|
||||
pdb_name_format:
|
||||
default: postgres-{cluster}-pdb
|
||||
description: defines the template for PDB names
|
||||
type: string
|
||||
persistent_volume_claim_retention_policy:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
pod_antiaffinity_preferred_during_scheduling:
|
||||
type: boolean
|
||||
pod_antiaffinity_topology_key:
|
||||
default: kubernetes.io/hostname
|
||||
type: string
|
||||
pod_environment_configmap:
|
||||
description: namespaced name of the ConfigMap with environment
|
||||
variables to populate on every pod
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
pod_environment_secret:
|
||||
type: string
|
||||
pod_management_policy:
|
||||
default: ordered_ready
|
||||
enum:
|
||||
- ordered_ready
|
||||
- parallel
|
||||
type: string
|
||||
pod_priority_class_name:
|
||||
type: string
|
||||
pod_role_label:
|
||||
default: spilo-role
|
||||
type: string
|
||||
pod_service_account_definition:
|
||||
type: string
|
||||
pod_service_account_name:
|
||||
default: postgres-pod
|
||||
type: string
|
||||
pod_service_account_role_binding_definition:
|
||||
type: string
|
||||
pod_terminate_grace_period:
|
||||
default: 5m
|
||||
description: Postgres pods are terminated forcefully after this
|
||||
timeout
|
||||
type: string
|
||||
secret_name_template:
|
||||
default: '{username}.{cluster}.credentials.{tprkind}.{tprgroup}'
|
||||
description: |-
|
||||
template for database user secrets generated by the operator,
|
||||
here username contains the namespace in the format namespace.username
|
||||
if the user is in different namespace than cluster and cross namespace secrets
|
||||
are enabled via `enable_cross_namespace_secret` flag in the configuration.
|
||||
type: string
|
||||
share_pgsocket_with_sidecars:
|
||||
type: boolean
|
||||
spilo_allow_privilege_escalation:
|
||||
default: true
|
||||
type: boolean
|
||||
spilo_fsgroup:
|
||||
format: int64
|
||||
type: integer
|
||||
spilo_privileged:
|
||||
type: boolean
|
||||
spilo_runasgroup:
|
||||
format: int64
|
||||
type: integer
|
||||
spilo_runasuser:
|
||||
format: int64
|
||||
type: integer
|
||||
storage_resize_mode:
|
||||
default: pvc
|
||||
enum:
|
||||
- ebs
|
||||
- mixed
|
||||
- pvc
|
||||
- "off"
|
||||
type: string
|
||||
toleration:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
watched_namespace:
|
||||
type: string
|
||||
type: object
|
||||
kubernetes_use_configmaps:
|
||||
default: true
|
||||
type: boolean
|
||||
load_balancer:
|
||||
description: LoadBalancerConfiguration defines the LB configuration
|
||||
properties:
|
||||
custom_service_annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
db_hosted_zone:
|
||||
type: string
|
||||
enable_master_load_balancer:
|
||||
type: boolean
|
||||
enable_master_node_port:
|
||||
type: boolean
|
||||
enable_master_pooler_load_balancer:
|
||||
type: boolean
|
||||
enable_master_pooler_node_port:
|
||||
type: boolean
|
||||
enable_replica_load_balancer:
|
||||
type: boolean
|
||||
enable_replica_node_port:
|
||||
type: boolean
|
||||
enable_replica_pooler_load_balancer:
|
||||
type: boolean
|
||||
enable_replica_pooler_node_port:
|
||||
type: boolean
|
||||
external_traffic_policy:
|
||||
default: Cluster
|
||||
enum:
|
||||
- Cluster
|
||||
- Local
|
||||
type: string
|
||||
master_dns_name_format:
|
||||
default: '{cluster}.{namespace}.{hostedzone}'
|
||||
description: defines the DNS name string template for the master
|
||||
load balancer cluster
|
||||
type: string
|
||||
master_legacy_dns_name_format:
|
||||
default: '{cluster}.{team}.{hostedzone}'
|
||||
description: deprecated DNS template for master load balancer
|
||||
using team name
|
||||
type: string
|
||||
replica_dns_name_format:
|
||||
default: '{cluster}-repl.{namespace}.{hostedzone}'
|
||||
description: defines the DNS name string template for the replica
|
||||
load balancer cluster
|
||||
type: string
|
||||
replica_legacy_dns_name_format:
|
||||
default: '{cluster}-repl.{team}.{hostedzone}'
|
||||
description: deprecated DNS template for replica load balancer
|
||||
using team name
|
||||
type: string
|
||||
type: object
|
||||
logging_rest_api:
|
||||
description: LoggingRESTAPIConfiguration defines Logging API conf
|
||||
properties:
|
||||
api_port:
|
||||
default: 8080
|
||||
type: integer
|
||||
cluster_history_entries:
|
||||
default: 1000
|
||||
type: integer
|
||||
ring_log_lines:
|
||||
default: 100
|
||||
type: integer
|
||||
type: object
|
||||
logical_backup:
|
||||
description: OperatorLogicalBackupConfiguration defines configuration
|
||||
for logical backup
|
||||
properties:
|
||||
logical_backup_azure_storage_account_key:
|
||||
type: string
|
||||
logical_backup_azure_storage_account_name:
|
||||
type: string
|
||||
logical_backup_azure_storage_container:
|
||||
type: string
|
||||
logical_backup_cpu_limit:
|
||||
pattern: ^(\d+m|\d+(\.\d{1,3})?)$
|
||||
type: string
|
||||
logical_backup_cpu_request:
|
||||
pattern: ^(\d+m|\d+(\.\d{1,3})?)$
|
||||
type: string
|
||||
logical_backup_cronjob_environment_secret:
|
||||
type: string
|
||||
logical_backup_docker_image:
|
||||
default: ghcr.io/zalando/postgres-operator/logical-backup:v2.0.0
|
||||
type: string
|
||||
logical_backup_failed_jobs_history_limit:
|
||||
default: 3
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
logical_backup_google_application_credentials:
|
||||
type: string
|
||||
logical_backup_job_prefix:
|
||||
default: logical-backup-
|
||||
type: string
|
||||
logical_backup_memory_limit:
|
||||
pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$
|
||||
type: string
|
||||
logical_backup_memory_request:
|
||||
pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$
|
||||
type: string
|
||||
logical_backup_provider:
|
||||
default: s3
|
||||
enum:
|
||||
- az
|
||||
- gcs
|
||||
- s3
|
||||
type: string
|
||||
logical_backup_s3_access_key_id:
|
||||
type: string
|
||||
logical_backup_s3_bucket:
|
||||
type: string
|
||||
logical_backup_s3_bucket_prefix:
|
||||
type: string
|
||||
logical_backup_s3_endpoint:
|
||||
type: string
|
||||
logical_backup_s3_region:
|
||||
type: string
|
||||
logical_backup_s3_retention_time:
|
||||
type: string
|
||||
logical_backup_s3_secret_access_key:
|
||||
type: string
|
||||
logical_backup_s3_sse:
|
||||
type: string
|
||||
logical_backup_schedule:
|
||||
default: 30 00 * * *
|
||||
pattern: ^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$
|
||||
type: string
|
||||
logical_backup_successful_jobs_history_limit:
|
||||
default: 3
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
logical_backup_ttl_seconds_after_finished:
|
||||
default: 86400
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
type: object
|
||||
maintenance_windows:
|
||||
items:
|
||||
pattern: ^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\
|
||||
*$
|
||||
type: string
|
||||
type: array
|
||||
major_version_upgrade:
|
||||
description: MajorVersionUpgradeConfiguration defines how to execute
|
||||
major version upgrades of Postgres.
|
||||
properties:
|
||||
major_version_upgrade_mode:
|
||||
default: manual
|
||||
enum:
|
||||
- "off"
|
||||
- manual
|
||||
- full
|
||||
type: string
|
||||
major_version_upgrade_team_allow_list:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
minimal_major_version:
|
||||
default: "14"
|
||||
type: string
|
||||
target_major_version:
|
||||
default: "18"
|
||||
type: string
|
||||
type: object
|
||||
max_instances:
|
||||
default: -1
|
||||
description: -1 = disabled
|
||||
format: int32
|
||||
minimum: -1
|
||||
type: integer
|
||||
min_instances:
|
||||
default: -1
|
||||
description: -1 = disabled
|
||||
format: int32
|
||||
minimum: -1
|
||||
type: integer
|
||||
patroni:
|
||||
description: PatroniConfiguration defines configuration for Patroni
|
||||
properties:
|
||||
enable_patroni_failsafe_mode:
|
||||
type: boolean
|
||||
type: object
|
||||
postgres_pod_resources:
|
||||
description: PostgresPodResourcesDefaults defines the spec of default
|
||||
resources
|
||||
properties:
|
||||
default_cpu_limit:
|
||||
pattern: ^(\d+m|\d+(\.\d{1,3})?)$
|
||||
type: string
|
||||
default_cpu_request:
|
||||
pattern: ^(\d+m|\d+(\.\d{1,3})?)$
|
||||
type: string
|
||||
default_memory_limit:
|
||||
pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$
|
||||
type: string
|
||||
default_memory_request:
|
||||
pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$
|
||||
type: string
|
||||
max_cpu_request:
|
||||
pattern: ^(\d+m|\d+(\.\d{1,3})?)$
|
||||
type: string
|
||||
max_memory_request:
|
||||
pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$
|
||||
type: string
|
||||
min_cpu_limit:
|
||||
pattern: ^(\d+m|\d+(\.\d{1,3})?)$
|
||||
type: string
|
||||
min_memory_limit:
|
||||
pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$
|
||||
type: string
|
||||
type: object
|
||||
repair_period:
|
||||
default: 5m
|
||||
description: period between consecutive repair requests
|
||||
type: string
|
||||
resync_period:
|
||||
default: 30m
|
||||
description: period between consecutive sync requests
|
||||
type: string
|
||||
scalyr:
|
||||
description: ScalyrConfiguration defines the configuration for ScalyrAPI
|
||||
properties:
|
||||
scalyr_api_key:
|
||||
type: string
|
||||
scalyr_cpu_limit:
|
||||
default: "1"
|
||||
pattern: ^(\d+m|\d+(\.\d{1,3})?)$
|
||||
type: string
|
||||
scalyr_cpu_request:
|
||||
default: 100m
|
||||
pattern: ^(\d+m|\d+(\.\d{1,3})?)$
|
||||
type: string
|
||||
scalyr_image:
|
||||
type: string
|
||||
scalyr_memory_limit:
|
||||
default: 500Mi
|
||||
pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$
|
||||
type: string
|
||||
scalyr_memory_request:
|
||||
default: 50Mi
|
||||
pattern: ^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$
|
||||
type: string
|
||||
scalyr_server_url:
|
||||
default: https://upload.eu.scalyr.com
|
||||
type: string
|
||||
type: object
|
||||
set_memory_request_to_limit:
|
||||
type: boolean
|
||||
sidecar_docker_images:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
sidecars:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
teams_api:
|
||||
description: TeamsAPIConfiguration defines the configuration of TeamsAPI
|
||||
properties:
|
||||
enable_admin_role_for_users:
|
||||
default: true
|
||||
type: boolean
|
||||
enable_postgres_team_crd:
|
||||
default: true
|
||||
type: boolean
|
||||
enable_postgres_team_crd_superusers:
|
||||
type: boolean
|
||||
enable_team_member_deprecation:
|
||||
type: boolean
|
||||
enable_team_superuser:
|
||||
type: boolean
|
||||
enable_teams_api:
|
||||
type: boolean
|
||||
pam_configuration:
|
||||
default: https://info.example.com/oauth2/tokeninfo?access_token=
|
||||
uid realm=/employees
|
||||
type: string
|
||||
pam_role_name:
|
||||
default: zalandos
|
||||
type: string
|
||||
postgres_superuser_teams:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
protected_role_names:
|
||||
default:
|
||||
- admin
|
||||
- cron_admin
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
role_deletion_suffix:
|
||||
default: _deleted
|
||||
type: string
|
||||
team_admin_role:
|
||||
default: admin
|
||||
type: string
|
||||
team_api_role_configuration:
|
||||
additionalProperties:
|
||||
type: string
|
||||
default:
|
||||
log_statement: all
|
||||
type: object
|
||||
teams_api_url:
|
||||
default: https://teams.example.com/api/
|
||||
type: string
|
||||
type: object
|
||||
timeouts:
|
||||
description: OperatorTimeouts defines the timeout of ResourceCheck,
|
||||
PodWait, ReadyWait
|
||||
properties:
|
||||
patroni_api_check_interval:
|
||||
default: 1s
|
||||
description: interval between consecutive attempts of operator
|
||||
calling the Patroni API
|
||||
type: string
|
||||
patroni_api_check_timeout:
|
||||
default: 5s
|
||||
description: timeout when waiting for successful response from
|
||||
Patroni API
|
||||
type: string
|
||||
pod_deletion_wait_timeout:
|
||||
default: 10m
|
||||
description: timeout when waiting for the Postgres pods to be
|
||||
deleted
|
||||
type: string
|
||||
pod_label_wait_timeout:
|
||||
default: 10m
|
||||
description: timeout when waiting for pod role and cluster labels
|
||||
type: string
|
||||
ready_wait_interval:
|
||||
default: 4s
|
||||
description: interval between consecutive attempts waiting for
|
||||
postgresql CRD to be created
|
||||
type: string
|
||||
ready_wait_timeout:
|
||||
default: 30s
|
||||
description: timeout for the complete postgres CRD creation
|
||||
type: string
|
||||
resource_check_interval:
|
||||
default: 3s
|
||||
description: interval to wait between consecutive attempts to
|
||||
check for some K8s resources
|
||||
type: string
|
||||
resource_check_timeout:
|
||||
default: 10m
|
||||
description: timeout when waiting for the presence of a certain
|
||||
K8s resource
|
||||
type: string
|
||||
type: object
|
||||
users:
|
||||
description: PostgresUsersConfiguration defines the system users of
|
||||
Postgres.
|
||||
properties:
|
||||
additional_owner_roles:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
enable_password_rotation:
|
||||
type: boolean
|
||||
password_rotation_interval:
|
||||
default: 90
|
||||
format: int32
|
||||
type: integer
|
||||
password_rotation_user_retention:
|
||||
default: 120
|
||||
format: int32
|
||||
type: integer
|
||||
replication_username:
|
||||
default: standby
|
||||
type: string
|
||||
super_username:
|
||||
default: postgres
|
||||
type: string
|
||||
type: object
|
||||
workers:
|
||||
default: 8
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
required:
|
||||
- configuration
|
||||
- metadata
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
|
|
@ -8,18 +8,34 @@ import (
|
|||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// PostgresTeam defines Custom Resource Definition Object for team management.
|
||||
// +k8s:deepcopy-gen=true
|
||||
// +kubebuilder:resource:shortName=pgteam,categories=all
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:metadata:labels=app.kubernetes.io/name=postgres-operator
|
||||
type PostgresTeam struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
metav1.ObjectMeta `json:"metadata"`
|
||||
|
||||
Spec PostgresTeamSpec `json:"spec"`
|
||||
}
|
||||
|
||||
// List of users who will also be added to the Postgres cluster.
|
||||
type Users []string
|
||||
|
||||
// List of teams whose members will also be added to the Postgres cluster.
|
||||
type Teams []string
|
||||
|
||||
// List of teams to become Postgres superusers
|
||||
type SuperUserTeams []string
|
||||
|
||||
// PostgresTeamSpec defines the specification for the PostgresTeam TPR.
|
||||
type PostgresTeamSpec struct {
|
||||
AdditionalSuperuserTeams map[string][]string `json:"additionalSuperuserTeams,omitempty"`
|
||||
AdditionalTeams map[string][]string `json:"additionalTeams,omitempty"`
|
||||
AdditionalMembers map[string][]string `json:"additionalMembers,omitempty"`
|
||||
// Map for teamId and associated additional superuser teams
|
||||
AdditionalSuperuserTeams map[string]SuperUserTeams `json:"additionalSuperuserTeams,omitempty"`
|
||||
// Map for teamId and associated additional teams
|
||||
AdditionalTeams map[string]Teams `json:"additionalTeams,omitempty"`
|
||||
// Map for teamId and associated additional users
|
||||
AdditionalMembers map[string]Users `json:"additionalMembers,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -11,13 +11,26 @@ import (
|
|||
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// Postgresql defines PostgreSQL Custom Resource Definition Object.
|
||||
// +kubebuilder:resource:categories=all,shortName=pg,scope=Namespaced
|
||||
// +kubebuilder:printcolumn:name="Team",type=string,JSONPath=`.spec.teamId`,description="Team responsible for Postgres cluster"
|
||||
// +kubebuilder:printcolumn:name="Version",type=string,JSONPath=`.spec.postgresql.version`,description="PostgreSQL version"
|
||||
// +kubebuilder:printcolumn:name="Pods",type=integer,JSONPath=`.spec.numberOfInstances`,description="Number of Pods per Postgres cluster"
|
||||
// +kubebuilder:printcolumn:name="Volume",type=string,JSONPath=`.spec.volume.size`,description="Size of the bound volume"
|
||||
// +kubebuilder:printcolumn:name="CPU-Request",type=string,JSONPath=`.spec.resources.requests.cpu`,description="Requested CPU for Postgres containers"
|
||||
// +kubebuilder:printcolumn:name="Memory-Request",type=string,JSONPath=`.spec.resources.requests.memory`,description="Requested memory for Postgres containers"
|
||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,description="Age of the PostgreSQL cluster"
|
||||
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.PostgresClusterStatus`,description="Current sync status of postgresql resource"
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:metadata:labels=app.kubernetes.io/name=postgres-operator
|
||||
type Postgresql struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
metav1.ObjectMeta `json:"metadata"`
|
||||
|
||||
Spec PostgresSpec `json:"spec"`
|
||||
Spec PostgresSpec `json:"spec"`
|
||||
// +optional
|
||||
Status PostgresStatus `json:"status"`
|
||||
Error string `json:"-"`
|
||||
}
|
||||
|
|
@ -25,9 +38,10 @@ type Postgresql struct {
|
|||
// PostgresSpec defines the specification for the PostgreSQL TPR.
|
||||
type PostgresSpec struct {
|
||||
PostgresqlParam `json:"postgresql"`
|
||||
Volume `json:"volume,omitempty"`
|
||||
Patroni `json:"patroni,omitempty"`
|
||||
*Resources `json:"resources,omitempty"`
|
||||
Volume `json:"volume"`
|
||||
// +optional
|
||||
Patroni `json:"patroni"`
|
||||
*Resources `json:"resources,omitempty"`
|
||||
|
||||
EnableConnectionPooler *bool `json:"enableConnectionPooler,omitempty"`
|
||||
EnableReplicaConnectionPooler *bool `json:"enableReplicaConnectionPooler,omitempty"`
|
||||
|
|
@ -50,37 +64,58 @@ type PostgresSpec struct {
|
|||
EnableReplicaLoadBalancer *bool `json:"enableReplicaLoadBalancer,omitempty"`
|
||||
EnableReplicaPoolerLoadBalancer *bool `json:"enableReplicaPoolerLoadBalancer,omitempty"`
|
||||
|
||||
// deprecated load balancer settings maintained for backward compatibility
|
||||
// see "Load balancers" operator docs
|
||||
UseLoadBalancer *bool `json:"useLoadBalancer,omitempty"`
|
||||
ReplicaLoadBalancer *bool `json:"replicaLoadBalancer,omitempty"`
|
||||
// vars to enable and configure nodeport services
|
||||
// set ports to 0 or nil to let kubernetes decide which port to use
|
||||
// overrides loadbalancer configuration
|
||||
EnableMasterNodePort *bool `json:"enableMasterNodePort,omitempty"`
|
||||
MasterNodePort *int32 `json:"masterNodePort,omitempty"`
|
||||
EnableMasterPoolerNodePort *bool `json:"enableMasterPoolerNodePort,omitempty"`
|
||||
MasterPoolerNodePort *int32 `json:"masterPoolerNodePort,omitempty"`
|
||||
EnableReplicaNodePort *bool `json:"enableReplicaNodePort,omitempty"`
|
||||
ReplicaNodePort *int32 `json:"replicaNodePort,omitempty"`
|
||||
EnableReplicaPoolerNodePort *bool `json:"enableReplicaPoolerNodePort,omitempty"`
|
||||
ReplicaPoolerNodePort *int32 `json:"replicaPoolerNodePort,omitempty"`
|
||||
|
||||
// load balancers' source ranges are the same for master and replica services
|
||||
// +nullable
|
||||
// +kubebuilder:validation:items:Pattern=`^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])|(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9]))$`
|
||||
// +optional
|
||||
AllowedSourceRanges []string `json:"allowedSourceRanges"`
|
||||
|
||||
Users map[string]UserFlags `json:"users,omitempty"`
|
||||
UsersIgnoringSecretRotation []string `json:"usersIgnoringSecretRotation,omitempty"`
|
||||
UsersWithSecretRotation []string `json:"usersWithSecretRotation,omitempty"`
|
||||
UsersWithInPlaceSecretRotation []string `json:"usersWithInPlaceSecretRotation,omitempty"`
|
||||
Users map[string]UserFlags `json:"users,omitempty"`
|
||||
// +nullable
|
||||
UsersIgnoringSecretRotation []string `json:"usersIgnoringSecretRotation,omitempty"`
|
||||
// +nullable
|
||||
UsersWithSecretRotation []string `json:"usersWithSecretRotation,omitempty"`
|
||||
// +nullable
|
||||
UsersWithInPlaceSecretRotation []string `json:"usersWithInPlaceSecretRotation,omitempty"`
|
||||
|
||||
NumberOfInstances int32 `json:"numberOfInstances"`
|
||||
MaintenanceWindows []MaintenanceWindow `json:"maintenanceWindows,omitempty"`
|
||||
Clone *CloneDescription `json:"clone,omitempty"`
|
||||
Databases map[string]string `json:"databases,omitempty"`
|
||||
PreparedDatabases map[string]PreparedDatabase `json:"preparedDatabases,omitempty"`
|
||||
SchedulerName *string `json:"schedulerName,omitempty"`
|
||||
NodeAffinity *v1.NodeAffinity `json:"nodeAffinity,omitempty"`
|
||||
Tolerations []v1.Toleration `json:"tolerations,omitempty"`
|
||||
Sidecars []Sidecar `json:"sidecars,omitempty"`
|
||||
InitContainers []v1.Container `json:"initContainers,omitempty"`
|
||||
PodPriorityClassName string `json:"podPriorityClassName,omitempty"`
|
||||
ShmVolume *bool `json:"enableShmVolume,omitempty"`
|
||||
EnableLogicalBackup bool `json:"enableLogicalBackup,omitempty"`
|
||||
LogicalBackupRetention string `json:"logicalBackupRetention,omitempty"`
|
||||
LogicalBackupSchedule string `json:"logicalBackupSchedule,omitempty"`
|
||||
StandbyCluster *StandbyDescription `json:"standby,omitempty"`
|
||||
PodAnnotations map[string]string `json:"podAnnotations,omitempty"`
|
||||
ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"`
|
||||
// +kubebuilder:validation:Minimum=0
|
||||
NumberOfInstances int32 `json:"numberOfInstances"`
|
||||
// +kubebuilder:validation:Schemaless
|
||||
// +kubebuilder:validation:Type=array
|
||||
MaintenanceWindows []MaintenanceWindow `json:"maintenanceWindows,omitempty"`
|
||||
Clone *CloneDescription `json:"clone,omitempty"`
|
||||
// Note: usernames specified here as database owners must be declared
|
||||
// in the users key of the spec key.
|
||||
Databases map[string]string `json:"databases,omitempty"`
|
||||
PreparedDatabases map[string]PreparedDatabase `json:"preparedDatabases,omitempty"`
|
||||
SchedulerName *string `json:"schedulerName,omitempty"`
|
||||
NodeAffinity *v1.NodeAffinity `json:"nodeAffinity,omitempty"`
|
||||
TopologySpreadConstraints []v1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"`
|
||||
Tolerations []v1.Toleration `json:"tolerations,omitempty"`
|
||||
LivenessProbe *v1.Probe `json:"livenessProbe,omitempty"`
|
||||
Sidecars []Sidecar `json:"sidecars,omitempty"`
|
||||
InitContainers []v1.Container `json:"initContainers,omitempty"`
|
||||
PodPriorityClassName string `json:"podPriorityClassName,omitempty"`
|
||||
ShmVolume *bool `json:"enableShmVolume,omitempty"`
|
||||
EnableLogicalBackup bool `json:"enableLogicalBackup,omitempty"`
|
||||
LogicalBackupRetention string `json:"logicalBackupRetention,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$`
|
||||
LogicalBackupSchedule string `json:"logicalBackupSchedule,omitempty"`
|
||||
StandbyCluster *StandbyDescription `json:"standby,omitempty"`
|
||||
PodAnnotations map[string]string `json:"podAnnotations,omitempty"`
|
||||
ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"`
|
||||
// MasterServiceAnnotations takes precedence over ServiceAnnotations for master role if not empty
|
||||
MasterServiceAnnotations map[string]string `json:"masterServiceAnnotations,omitempty"`
|
||||
// ReplicaServiceAnnotations takes precedence over ServiceAnnotations for replica role if not empty
|
||||
|
|
@ -89,10 +124,7 @@ type PostgresSpec struct {
|
|||
AdditionalVolumes []AdditionalVolume `json:"additionalVolumes,omitempty"`
|
||||
Streams []Stream `json:"streams,omitempty"`
|
||||
Env []v1.EnvVar `json:"env,omitempty"`
|
||||
|
||||
// deprecated json tags
|
||||
InitContainersOld []v1.Container `json:"init_containers,omitempty"`
|
||||
PodPriorityClassNameOld string `json:"pod_priority_class_name,omitempty"`
|
||||
EnvFrom []v1.EnvFromSource `json:"envFrom,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
|
@ -123,50 +155,86 @@ type PreparedSchema struct {
|
|||
type MaintenanceWindow struct {
|
||||
Everyday bool `json:"everyday,omitempty"`
|
||||
Weekday time.Weekday `json:"weekday,omitempty"`
|
||||
StartTime metav1.Time `json:"startTime,omitempty"`
|
||||
EndTime metav1.Time `json:"endTime,omitempty"`
|
||||
StartTime metav1.Time `json:"startTime"`
|
||||
EndTime metav1.Time `json:"endTime"`
|
||||
}
|
||||
|
||||
// Volume describes a single volume in the manifest.
|
||||
type Volume struct {
|
||||
Selector *metav1.LabelSelector `json:"selector,omitempty"`
|
||||
Size string `json:"size"`
|
||||
StorageClass string `json:"storageClass,omitempty"`
|
||||
SubPath string `json:"subPath,omitempty"`
|
||||
IsSubPathExpr *bool `json:"isSubPathExpr,omitempty"`
|
||||
Iops *int64 `json:"iops,omitempty"`
|
||||
Throughput *int64 `json:"throughput,omitempty"`
|
||||
VolumeType string `json:"type,omitempty"`
|
||||
Selector *metav1.LabelSelector `json:"selector,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
Size string `json:"size"`
|
||||
StorageClass string `json:"storageClass,omitempty"`
|
||||
SubPath string `json:"subPath,omitempty"`
|
||||
IsSubPathExpr *bool `json:"isSubPathExpr,omitempty"`
|
||||
// +kubebuilder:validation:Maximum=80000
|
||||
Iops *int32 `json:"iops,omitempty"`
|
||||
// +kubebuilder:validation:Maximum=2000
|
||||
Throughput *int32 `json:"throughput,omitempty"`
|
||||
VolumeType string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// AdditionalVolume specs additional optional volumes for statefulset
|
||||
type AdditionalVolume struct {
|
||||
Name string `json:"name"`
|
||||
MountPath string `json:"mountPath"`
|
||||
SubPath string `json:"subPath,omitempty"`
|
||||
IsSubPathExpr *bool `json:"isSubPathExpr,omitempty"`
|
||||
TargetContainers []string `json:"targetContainers"`
|
||||
VolumeSource v1.VolumeSource `json:"volumeSource"`
|
||||
Name string `json:"name"`
|
||||
MountPath string `json:"mountPath"`
|
||||
SubPath string `json:"subPath,omitempty"`
|
||||
IsSubPathExpr *bool `json:"isSubPathExpr,omitempty"`
|
||||
// +nullable
|
||||
// +optional
|
||||
TargetContainers []string `json:"targetContainers"`
|
||||
// +kubebuilder:validation:XPreserveUnknownFields
|
||||
// +kubebuilder:validation:Type=object
|
||||
// +kubebuilder:validation:Schemaless
|
||||
VolumeSource v1.VolumeSource `json:"volumeSource"`
|
||||
}
|
||||
|
||||
// PostgresqlParam describes PostgreSQL version and pairs of configuration parameter name - values.
|
||||
type PostgresqlParam struct {
|
||||
// +kubebuilder:validation:Enum="14";"15";"16";"17";"18"
|
||||
PgVersion string `json:"version"`
|
||||
Parameters map[string]string `json:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
// ResourceDescription describes CPU and memory resources defined for a cluster.
|
||||
type ResourceDescription struct {
|
||||
CPU *string `json:"cpu,omitempty"`
|
||||
Memory *string `json:"memory,omitempty"`
|
||||
// Decimal natural followed by m, or decimal natural followed by
|
||||
// dot followed by up to three decimal digits.
|
||||
//
|
||||
// This is because the Kubernetes CPU resource has millis as the
|
||||
// maximum precision. The actual values are checked in code
|
||||
// because the regular expression would be huge and horrible and
|
||||
// not very helpful in validation error messages; this one checks
|
||||
// only the format of the given number.
|
||||
//
|
||||
// https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu
|
||||
//
|
||||
// Note: the value specified here must not be zero or be lower
|
||||
// than the corresponding request.
|
||||
// +kubebuilder:validation:Pattern=`^(\d+m|\d+(\.\d{1,3})?)$`
|
||||
CPU *string `json:"cpu,omitempty"`
|
||||
// You can express memory as a plain integer or as a fixed-point
|
||||
// integer using one of these suffixes: E, P, T, G, M, k. You can
|
||||
// also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki
|
||||
//
|
||||
// https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory
|
||||
//
|
||||
// Note: the value specified here must not be zero or be higher
|
||||
// than the corresponding limit.
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
Memory *string `json:"memory,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
HugePages2Mi *string `json:"hugepages-2Mi,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
HugePages1Gi *string `json:"hugepages-1Gi,omitempty"`
|
||||
}
|
||||
|
||||
// Resources describes requests and limits for the cluster resouces.
|
||||
type Resources struct {
|
||||
ResourceRequests ResourceDescription `json:"requests,omitempty"`
|
||||
ResourceLimits ResourceDescription `json:"limits,omitempty"`
|
||||
// +optional
|
||||
ResourceRequests ResourceDescription `json:"requests"`
|
||||
// +optional
|
||||
ResourceLimits ResourceDescription `json:"limits"`
|
||||
}
|
||||
|
||||
// Patroni contains Patroni-specific configuration
|
||||
|
|
@ -176,7 +244,7 @@ type Patroni struct {
|
|||
TTL uint32 `json:"ttl,omitempty"`
|
||||
LoopWait uint32 `json:"loop_wait,omitempty"`
|
||||
RetryTimeout uint32 `json:"retry_timeout,omitempty"`
|
||||
MaximumLagOnFailover float32 `json:"maximum_lag_on_failover,omitempty"` // float32 because https://github.com/kubernetes/kubernetes/issues/30213
|
||||
MaximumLagOnFailover int64 `json:"maximum_lag_on_failover,omitempty"`
|
||||
Slots map[string]map[string]string `json:"slots,omitempty"`
|
||||
SynchronousMode bool `json:"synchronous_mode,omitempty"`
|
||||
SynchronousModeStrict bool `json:"synchronous_mode_strict,omitempty"`
|
||||
|
|
@ -184,16 +252,20 @@ type Patroni struct {
|
|||
FailsafeMode *bool `json:"failsafe_mode,omitempty"`
|
||||
}
|
||||
|
||||
// StandbyDescription contains remote primary config or s3/gs wal path
|
||||
// StandbyDescription contains remote primary config and/or s3/gs wal path.
|
||||
// standby_host can be specified alone or together with either s3_wal_path OR gs_wal_path (mutually exclusive).
|
||||
// At least one field must be specified. s3_wal_path and gs_wal_path are mutually exclusive.
|
||||
type StandbyDescription struct {
|
||||
S3WalPath string `json:"s3_wal_path,omitempty"`
|
||||
GSWalPath string `json:"gs_wal_path,omitempty"`
|
||||
StandbyHost string `json:"standby_host,omitempty"`
|
||||
StandbyPort string `json:"standby_port,omitempty"`
|
||||
S3WalPath string `json:"s3_wal_path,omitempty"`
|
||||
GSWalPath string `json:"gs_wal_path,omitempty"`
|
||||
StandbyHost string `json:"standby_host,omitempty"`
|
||||
StandbyPort string `json:"standby_port,omitempty"`
|
||||
StandbyPrimarySlotName string `json:"standby_primary_slot_name,omitempty"`
|
||||
}
|
||||
|
||||
// TLSDescription specs TLS properties
|
||||
type TLSDescription struct {
|
||||
// +required
|
||||
SecretName string `json:"secretName,omitempty"`
|
||||
CertificateFile string `json:"certificateFile,omitempty"`
|
||||
PrivateKeyFile string `json:"privateKeyFile,omitempty"`
|
||||
|
|
@ -203,8 +275,14 @@ type TLSDescription struct {
|
|||
|
||||
// CloneDescription describes which cluster the new should clone and up to which point in time
|
||||
type CloneDescription struct {
|
||||
ClusterName string `json:"cluster,omitempty"`
|
||||
UID string `json:"uid,omitempty"`
|
||||
// +required
|
||||
ClusterName string `json:"cluster,omitempty"`
|
||||
// +kubebuilder:validation:Format=uuid
|
||||
UID string `json:"uid,omitempty"`
|
||||
// The regexp matches the date-time format (RFC 3339 Section 5.6) that specifies a timezone as an offset relative to UTC
|
||||
// Example: 1996-12-19T16:39:57-08:00
|
||||
// Note: this field requires a timezone
|
||||
// +kubebuilder:validation:Pattern=`^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([+-]([01][0-9]|2[0-3]):[0-5][0-9]))$`
|
||||
EndTimestamp string `json:"timestamp,omitempty"`
|
||||
S3WalPath string `json:"s3_wal_path,omitempty"`
|
||||
S3Endpoint string `json:"s3_endpoint,omitempty"`
|
||||
|
|
@ -224,6 +302,8 @@ type Sidecar struct {
|
|||
}
|
||||
|
||||
// UserFlags defines flags (such as superuser, nologin) that could be assigned to individual users
|
||||
// +kubebuilder:validation:items:Enum=bypassrls;BYPASSRLS;nobypassrls;NOBYPASSRLS;createdb;CREATEDB;nocreatedb;NOCREATEDB;createrole;CREATEROLE;nocreaterole;NOCREATEROLE;inherit;INHERIT;noinherit;NOINHERIT;login;LOGIN;nologin;NOLOGIN;replication;REPLICATION;noreplication;NOREPLICATION;superuser;SUPERUSER;nosuperuser;NOSUPERUSER
|
||||
// +nullable
|
||||
type UserFlags []string
|
||||
|
||||
// PostgresStatus contains status of the PostgreSQL cluster (running, creation failed etc.)
|
||||
|
|
@ -242,26 +322,30 @@ type PostgresStatus struct {
|
|||
// makes sense to expose. E.g. pool size (min/max boundaries), max client
|
||||
// connections etc.
|
||||
type ConnectionPooler struct {
|
||||
// +kubebuilder:validation:Minimum=1
|
||||
NumberOfInstances *int32 `json:"numberOfInstances,omitempty"`
|
||||
Schema string `json:"schema,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
DockerImage string `json:"dockerImage,omitempty"`
|
||||
MaxDBConnections *int32 `json:"maxDBConnections,omitempty"`
|
||||
// +kubebuilder:validation:Enum=session;transaction
|
||||
Mode string `json:"mode,omitempty"`
|
||||
DockerImage string `json:"dockerImage,omitempty"`
|
||||
MaxDBConnections *int32 `json:"maxDBConnections,omitempty"`
|
||||
|
||||
*Resources `json:"resources,omitempty"`
|
||||
}
|
||||
|
||||
// Stream defines properties for creating FabricEventStream resources
|
||||
type Stream struct {
|
||||
ApplicationId string `json:"applicationId"`
|
||||
Database string `json:"database"`
|
||||
Tables map[string]StreamTable `json:"tables"`
|
||||
Filter map[string]*string `json:"filter,omitempty"`
|
||||
BatchSize *uint32 `json:"batchSize,omitempty"`
|
||||
CPU *string `json:"cpu,omitempty"`
|
||||
Memory *string `json:"memory,omitempty"`
|
||||
EnableRecovery *bool `json:"enableRecovery,omitempty"`
|
||||
ApplicationId string `json:"applicationId"`
|
||||
Database string `json:"database"`
|
||||
Tables map[string]StreamTable `json:"tables"`
|
||||
Filter map[string]*string `json:"filter,omitempty"`
|
||||
BatchSize *uint32 `json:"batchSize,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+m|\d+(\.\d{1,3})?)$`
|
||||
CPU *string `json:"cpu,omitempty"`
|
||||
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
|
||||
Memory *string `json:"memory,omitempty"`
|
||||
EnableRecovery *bool `json:"enableRecovery,omitempty"`
|
||||
}
|
||||
|
||||
// StreamTable defines properties of outbox tables for FabricEventStreams
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/zalando/postgres-operator/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
|
|
@ -91,6 +91,13 @@ var maintenanceWindows = []struct {
|
|||
StartTime: mustParseTime("10:00"),
|
||||
EndTime: mustParseTime("20:00"),
|
||||
}, nil},
|
||||
{"regular every day scenario",
|
||||
[]byte(`"05:00-07:00"`),
|
||||
MaintenanceWindow{
|
||||
Everyday: true,
|
||||
StartTime: mustParseTime("05:00"),
|
||||
EndTime: mustParseTime("07:00"),
|
||||
}, nil},
|
||||
{"starts and ends at the same time",
|
||||
[]byte(`"Mon:10:00-10:00"`),
|
||||
MaintenanceWindow{
|
||||
|
|
@ -170,7 +177,8 @@ var unmarshalCluster = []struct {
|
|||
"metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": 100}}`), &tmp).Error(),
|
||||
},
|
||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":null},"status":"Invalid"}`),
|
||||
err: nil},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
about: "example with /status subresource",
|
||||
in: []byte(`{
|
||||
|
|
@ -191,156 +199,8 @@ var unmarshalCluster = []struct {
|
|||
"metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": 100}}`), &tmp).Error(),
|
||||
},
|
||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":null},"status":{"PostgresClusterStatus":"Invalid"}}`),
|
||||
err: nil},
|
||||
{
|
||||
about: "example with detailed input manifest and deprecated pod_priority_class_name -> podPriorityClassName",
|
||||
in: []byte(`{
|
||||
"kind": "Postgresql",
|
||||
"apiVersion": "acid.zalan.do/v1",
|
||||
"metadata": {
|
||||
"name": "acid-testcluster1"
|
||||
},
|
||||
"spec": {
|
||||
"teamId": "acid",
|
||||
"pod_priority_class_name": "spilo-pod-priority",
|
||||
"volume": {
|
||||
"size": "5Gi",
|
||||
"storageClass": "SSD",
|
||||
"subPath": "subdir"
|
||||
},
|
||||
"numberOfInstances": 2,
|
||||
"users": {
|
||||
"zalando": [
|
||||
"superuser",
|
||||
"createdb"
|
||||
]
|
||||
},
|
||||
"allowedSourceRanges": [
|
||||
"127.0.0.1/32"
|
||||
],
|
||||
"postgresql": {
|
||||
"version": "17",
|
||||
"parameters": {
|
||||
"shared_buffers": "32MB",
|
||||
"max_connections": "10",
|
||||
"log_statement": "all"
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"requests": {
|
||||
"cpu": "10m",
|
||||
"memory": "50Mi"
|
||||
},
|
||||
"limits": {
|
||||
"cpu": "300m",
|
||||
"memory": "3000Mi"
|
||||
}
|
||||
},
|
||||
"clone" : {
|
||||
"cluster": "acid-batman"
|
||||
},
|
||||
"enableShmVolume": false,
|
||||
"patroni": {
|
||||
"initdb": {
|
||||
"encoding": "UTF8",
|
||||
"locale": "en_US.UTF-8",
|
||||
"data-checksums": "true"
|
||||
},
|
||||
"pg_hba": [
|
||||
"hostssl all all 0.0.0.0/0 md5",
|
||||
"host all all 0.0.0.0/0 md5"
|
||||
],
|
||||
"ttl": 30,
|
||||
"loop_wait": 10,
|
||||
"retry_timeout": 10,
|
||||
"maximum_lag_on_failover": 33554432,
|
||||
"slots" : {
|
||||
"permanent_logical_1" : {
|
||||
"type" : "logical",
|
||||
"database" : "foo",
|
||||
"plugin" : "pgoutput"
|
||||
}
|
||||
}
|
||||
},
|
||||
"maintenanceWindows": [
|
||||
"Mon:01:00-06:00",
|
||||
"Sat:00:00-04:00",
|
||||
"05:00-05:15"
|
||||
]
|
||||
}
|
||||
}`),
|
||||
out: Postgresql{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Postgresql",
|
||||
APIVersion: "acid.zalan.do/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "acid-testcluster1",
|
||||
},
|
||||
Spec: PostgresSpec{
|
||||
PostgresqlParam: PostgresqlParam{
|
||||
PgVersion: "17",
|
||||
Parameters: map[string]string{
|
||||
"shared_buffers": "32MB",
|
||||
"max_connections": "10",
|
||||
"log_statement": "all",
|
||||
},
|
||||
},
|
||||
PodPriorityClassNameOld: "spilo-pod-priority",
|
||||
Volume: Volume{
|
||||
Size: "5Gi",
|
||||
StorageClass: "SSD",
|
||||
SubPath: "subdir",
|
||||
},
|
||||
ShmVolume: util.False(),
|
||||
Patroni: Patroni{
|
||||
InitDB: map[string]string{
|
||||
"encoding": "UTF8",
|
||||
"locale": "en_US.UTF-8",
|
||||
"data-checksums": "true",
|
||||
},
|
||||
PgHba: []string{"hostssl all all 0.0.0.0/0 md5", "host all all 0.0.0.0/0 md5"},
|
||||
TTL: 30,
|
||||
LoopWait: 10,
|
||||
RetryTimeout: 10,
|
||||
MaximumLagOnFailover: 33554432,
|
||||
Slots: map[string]map[string]string{"permanent_logical_1": {"type": "logical", "database": "foo", "plugin": "pgoutput"}},
|
||||
},
|
||||
Resources: &Resources{
|
||||
ResourceRequests: ResourceDescription{CPU: stringToPointer("10m"), Memory: stringToPointer("50Mi")},
|
||||
ResourceLimits: ResourceDescription{CPU: stringToPointer("300m"), Memory: stringToPointer("3000Mi")},
|
||||
},
|
||||
|
||||
TeamID: "acid",
|
||||
AllowedSourceRanges: []string{"127.0.0.1/32"},
|
||||
NumberOfInstances: 2,
|
||||
Users: map[string]UserFlags{"zalando": {"superuser", "createdb"}},
|
||||
MaintenanceWindows: []MaintenanceWindow{{
|
||||
Everyday: false,
|
||||
Weekday: time.Monday,
|
||||
StartTime: mustParseTime("01:00"),
|
||||
EndTime: mustParseTime("06:00"),
|
||||
}, {
|
||||
Everyday: false,
|
||||
Weekday: time.Saturday,
|
||||
StartTime: mustParseTime("00:00"),
|
||||
EndTime: mustParseTime("04:00"),
|
||||
},
|
||||
{
|
||||
Everyday: true,
|
||||
Weekday: time.Sunday,
|
||||
StartTime: mustParseTime("05:00"),
|
||||
EndTime: mustParseTime("05:15"),
|
||||
},
|
||||
},
|
||||
Clone: &CloneDescription{
|
||||
ClusterName: "acid-batman",
|
||||
},
|
||||
},
|
||||
Error: "",
|
||||
},
|
||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"17","parameters":{"log_statement":"all","max_connections":"10","shared_buffers":"32MB"}},"pod_priority_class_name":"spilo-pod-priority","volume":{"size":"5Gi","storageClass":"SSD", "subPath": "subdir"},"enableShmVolume":false,"patroni":{"initdb":{"data-checksums":"true","encoding":"UTF8","locale":"en_US.UTF-8"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"],"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"slots":{"permanent_logical_1":{"database":"foo","plugin":"pgoutput","type":"logical"}}},"resources":{"requests":{"cpu":"10m","memory":"50Mi"},"limits":{"cpu":"300m","memory":"3000Mi"}},"teamId":"acid","allowedSourceRanges":["127.0.0.1/32"],"numberOfInstances":2,"users":{"zalando":["superuser","createdb"]},"maintenanceWindows":["Mon:01:00-06:00","Sat:00:00-04:00","05:00-05:15"],"clone":{"cluster":"acid-batman"}},"status":{"PostgresClusterStatus":""}}`),
|
||||
err: nil},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
about: "example with clone",
|
||||
in: []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": "acid", "clone": {"cluster": "team-batman"}}}`),
|
||||
|
|
@ -361,7 +221,8 @@ var unmarshalCluster = []struct {
|
|||
Error: "",
|
||||
},
|
||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{"cluster":"team-batman"}},"status":{"PostgresClusterStatus":""}}`),
|
||||
err: nil},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
about: "standby example",
|
||||
in: []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": "acid", "standby": {"s3_wal_path": "s3://custom/path/to/bucket/"}}}`),
|
||||
|
|
@ -382,7 +243,8 @@ var unmarshalCluster = []struct {
|
|||
Error: "",
|
||||
},
|
||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"standby":{"s3_wal_path":"s3://custom/path/to/bucket/"}},"status":{"PostgresClusterStatus":""}}`),
|
||||
err: nil},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
about: "expect error on malformatted JSON",
|
||||
in: []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`),
|
||||
|
|
@ -404,7 +266,7 @@ var postgresqlList = []struct {
|
|||
out PostgresqlList
|
||||
err error
|
||||
}{
|
||||
{"expect success", []byte(`{"apiVersion":"v1","items":[{"apiVersion":"acid.zalan.do/v1","kind":"Postgresql","metadata":{"labels":{"team":"acid"},"name":"acid-testcluster42","namespace":"default","resourceVersion":"30446957","selfLink":"/apis/acid.zalan.do/v1/namespaces/default/postgresqls/acid-testcluster42","uid":"857cd208-33dc-11e7-b20a-0699041e4b03"},"spec":{"allowedSourceRanges":["185.85.220.0/22"],"numberOfInstances":1,"postgresql":{"version":"17"},"teamId":"acid","volume":{"size":"10Gi"}},"status":{"PostgresClusterStatus":"Running"}}],"kind":"List","metadata":{},"resourceVersion":"","selfLink":""}`),
|
||||
{"expect success", []byte(`{"apiVersion":"v1","items":[{"apiVersion":"acid.zalan.do/v1","kind":"Postgresql","metadata":{"labels":{"team":"acid"},"name":"acid-testcluster42","namespace":"default","resourceVersion":"30446957","selfLink":"/apis/acid.zalan.do/v1/namespaces/default/postgresqls/acid-testcluster42","uid":"857cd208-33dc-11e7-b20a-0699041e4b03"},"spec":{"allowedSourceRanges":["185.85.220.0/22"],"numberOfInstances":1,"postgresql":{"version":"18"},"teamId":"acid","volume":{"size":"10Gi"}},"status":{"PostgresClusterStatus":"Running"}}],"kind":"List","metadata":{},"resourceVersion":"","selfLink":""}`),
|
||||
PostgresqlList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "List",
|
||||
|
|
@ -425,7 +287,7 @@ var postgresqlList = []struct {
|
|||
},
|
||||
Spec: PostgresSpec{
|
||||
ClusterName: "testcluster42",
|
||||
PostgresqlParam: PostgresqlParam{PgVersion: "17"},
|
||||
PostgresqlParam: PostgresqlParam{PgVersion: "18"},
|
||||
Volume: Volume{Size: "10Gi"},
|
||||
TeamID: "acid",
|
||||
AllowedSourceRanges: []string{"185.85.220.0/22"},
|
||||
|
|
@ -803,3 +665,47 @@ func TestPostgresqlClone(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowedSourceRangesPattern(t *testing.T) {
|
||||
// pattern used in CRD validation for allowedSourceRanges
|
||||
pattern := `^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])|(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9]))$`
|
||||
re := regexp.MustCompile(pattern)
|
||||
|
||||
valid := []string{
|
||||
// IPv4
|
||||
"192.168.1.0/24",
|
||||
"0.0.0.0/0",
|
||||
"127.0.0.1/32",
|
||||
"10.0.0.0/8",
|
||||
"185.85.220.0/22",
|
||||
// IPv6
|
||||
"fd01::/48",
|
||||
"::1/128",
|
||||
"::/0",
|
||||
"2001:db8::/32",
|
||||
"fe80::1/64",
|
||||
"2001:0db8:85a3:0000:0000:8a2e:0370:7334/128",
|
||||
}
|
||||
|
||||
invalid := []string{
|
||||
"999.999.999.999/24",
|
||||
"192.168.1.0/33",
|
||||
"192.168.1.0",
|
||||
"not-an-ip",
|
||||
"fd01::/129",
|
||||
"::gggg/64",
|
||||
"",
|
||||
}
|
||||
|
||||
for _, cidr := range valid {
|
||||
if !re.MatchString(cidr) {
|
||||
t.Errorf("expected %q to match allowedSourceRanges pattern", cidr)
|
||||
}
|
||||
}
|
||||
|
||||
for _, cidr := range invalid {
|
||||
if re.MatchString(cidr) {
|
||||
t.Errorf("expected %q NOT to match allowedSourceRanges pattern", cidr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2025 Compose, Zalando SE
|
||||
Copyright 2026 Compose, Zalando SE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -163,6 +163,16 @@ func (in *KubernetesMetaConfiguration) DeepCopyInto(out *KubernetesMetaConfigura
|
|||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.PodTerminateGracePeriod != nil {
|
||||
in, out := &in.PodTerminateGracePeriod, &out.PodTerminateGracePeriod
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
if in.LivenessProbe != nil {
|
||||
in, out := &in.LivenessProbe, &out.LivenessProbe
|
||||
*out = new(corev1.Probe)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.SpiloAllowPrivilegeEscalation != nil {
|
||||
in, out := &in.SpiloAllowPrivilegeEscalation, &out.SpiloAllowPrivilegeEscalation
|
||||
*out = new(bool)
|
||||
|
|
@ -275,6 +285,11 @@ func (in *KubernetesMetaConfiguration) DeepCopyInto(out *KubernetesMetaConfigura
|
|||
}
|
||||
}
|
||||
out.PodEnvironmentConfigMap = in.PodEnvironmentConfigMap
|
||||
if in.MasterPodMoveTimeout != nil {
|
||||
in, out := &in.MasterPodMoveTimeout, &out.MasterPodMoveTimeout
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
if in.PersistentVolumeClaimRetentionPolicy != nil {
|
||||
in, out := &in.PersistentVolumeClaimRetentionPolicy, &out.PersistentVolumeClaimRetentionPolicy
|
||||
*out = make(map[string]string, len(*in))
|
||||
|
|
@ -423,13 +438,33 @@ func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData
|
|||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.EnableCRDValidation != nil {
|
||||
in, out := &in.EnableCRDValidation, &out.EnableCRDValidation
|
||||
if in.CRDCategories != nil {
|
||||
in, out := &in.CRDCategories, &out.CRDCategories
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.KubernetesUseConfigMaps != nil {
|
||||
in, out := &in.KubernetesUseConfigMaps, &out.KubernetesUseConfigMaps
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.CRDCategories != nil {
|
||||
in, out := &in.CRDCategories, &out.CRDCategories
|
||||
if in.ResyncPeriod != nil {
|
||||
in, out := &in.ResyncPeriod, &out.ResyncPeriod
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
if in.RepairPeriod != nil {
|
||||
in, out := &in.RepairPeriod, &out.RepairPeriod
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
if in.EnableMaintenanceWindows != nil {
|
||||
in, out := &in.EnableMaintenanceWindows, &out.EnableMaintenanceWindows
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.MaintenanceWindows != nil {
|
||||
in, out := &in.MaintenanceWindows, &out.MaintenanceWindows
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
|
|
@ -456,14 +491,14 @@ func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData
|
|||
in.MajorVersionUpgrade.DeepCopyInto(&out.MajorVersionUpgrade)
|
||||
in.Kubernetes.DeepCopyInto(&out.Kubernetes)
|
||||
out.PostgresPodResources = in.PostgresPodResources
|
||||
out.Timeouts = in.Timeouts
|
||||
in.Timeouts.DeepCopyInto(&out.Timeouts)
|
||||
in.LoadBalancer.DeepCopyInto(&out.LoadBalancer)
|
||||
out.AWSGCP = in.AWSGCP
|
||||
out.OperatorDebug = in.OperatorDebug
|
||||
in.OperatorDebug.DeepCopyInto(&out.OperatorDebug)
|
||||
in.TeamsAPI.DeepCopyInto(&out.TeamsAPI)
|
||||
out.LoggingRESTAPI = in.LoggingRESTAPI
|
||||
out.Scalyr = in.Scalyr
|
||||
out.LogicalBackup = in.LogicalBackup
|
||||
in.LogicalBackup.DeepCopyInto(&out.LogicalBackup)
|
||||
in.ConnectionPooler.DeepCopyInto(&out.ConnectionPooler)
|
||||
in.Patroni.DeepCopyInto(&out.Patroni)
|
||||
return
|
||||
|
|
@ -515,6 +550,16 @@ func (in *OperatorConfigurationList) DeepCopyObject() runtime.Object {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OperatorDebugConfiguration) DeepCopyInto(out *OperatorDebugConfiguration) {
|
||||
*out = *in
|
||||
if in.DebugLogging != nil {
|
||||
in, out := &in.DebugLogging, &out.DebugLogging
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.EnableDBAccess != nil {
|
||||
in, out := &in.EnableDBAccess, &out.EnableDBAccess
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -531,6 +576,21 @@ func (in *OperatorDebugConfiguration) DeepCopy() *OperatorDebugConfiguration {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OperatorLogicalBackupConfiguration) DeepCopyInto(out *OperatorLogicalBackupConfiguration) {
|
||||
*out = *in
|
||||
if in.SuccessfulJobsHistoryLimit != nil {
|
||||
in, out := &in.SuccessfulJobsHistoryLimit, &out.SuccessfulJobsHistoryLimit
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.FailedJobsHistoryLimit != nil {
|
||||
in, out := &in.FailedJobsHistoryLimit, &out.FailedJobsHistoryLimit
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.TTLSecondsAfterFinished != nil {
|
||||
in, out := &in.TTLSecondsAfterFinished, &out.TTLSecondsAfterFinished
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -547,6 +607,46 @@ func (in *OperatorLogicalBackupConfiguration) DeepCopy() *OperatorLogicalBackupC
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OperatorTimeouts) DeepCopyInto(out *OperatorTimeouts) {
|
||||
*out = *in
|
||||
if in.ResourceCheckInterval != nil {
|
||||
in, out := &in.ResourceCheckInterval, &out.ResourceCheckInterval
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
if in.ResourceCheckTimeout != nil {
|
||||
in, out := &in.ResourceCheckTimeout, &out.ResourceCheckTimeout
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
if in.PodLabelWaitTimeout != nil {
|
||||
in, out := &in.PodLabelWaitTimeout, &out.PodLabelWaitTimeout
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
if in.PodDeletionWaitTimeout != nil {
|
||||
in, out := &in.PodDeletionWaitTimeout, &out.PodDeletionWaitTimeout
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
if in.ReadyWaitInterval != nil {
|
||||
in, out := &in.ReadyWaitInterval, &out.ReadyWaitInterval
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
if in.ReadyWaitTimeout != nil {
|
||||
in, out := &in.ReadyWaitTimeout, &out.ReadyWaitTimeout
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
if in.PatroniAPICheckInterval != nil {
|
||||
in, out := &in.PatroniAPICheckInterval, &out.PatroniAPICheckInterval
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
if in.PatroniAPICheckTimeout != nil {
|
||||
in, out := &in.PatroniAPICheckTimeout, &out.PatroniAPICheckTimeout
|
||||
*out = new(metav1.Duration)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -708,16 +808,46 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) {
|
|||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.UseLoadBalancer != nil {
|
||||
in, out := &in.UseLoadBalancer, &out.UseLoadBalancer
|
||||
if in.EnableMasterNodePort != nil {
|
||||
in, out := &in.EnableMasterNodePort, &out.EnableMasterNodePort
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.ReplicaLoadBalancer != nil {
|
||||
in, out := &in.ReplicaLoadBalancer, &out.ReplicaLoadBalancer
|
||||
if in.MasterNodePort != nil {
|
||||
in, out := &in.MasterNodePort, &out.MasterNodePort
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.EnableMasterPoolerNodePort != nil {
|
||||
in, out := &in.EnableMasterPoolerNodePort, &out.EnableMasterPoolerNodePort
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.MasterPoolerNodePort != nil {
|
||||
in, out := &in.MasterPoolerNodePort, &out.MasterPoolerNodePort
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.EnableReplicaNodePort != nil {
|
||||
in, out := &in.EnableReplicaNodePort, &out.EnableReplicaNodePort
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.ReplicaNodePort != nil {
|
||||
in, out := &in.ReplicaNodePort, &out.ReplicaNodePort
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.EnableReplicaPoolerNodePort != nil {
|
||||
in, out := &in.EnableReplicaPoolerNodePort, &out.EnableReplicaPoolerNodePort
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.ReplicaPoolerNodePort != nil {
|
||||
in, out := &in.ReplicaPoolerNodePort, &out.ReplicaPoolerNodePort
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.AllowedSourceRanges != nil {
|
||||
in, out := &in.AllowedSourceRanges, &out.AllowedSourceRanges
|
||||
*out = make([]string, len(*in))
|
||||
|
|
@ -789,6 +919,13 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) {
|
|||
*out = new(corev1.NodeAffinity)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.TopologySpreadConstraints != nil {
|
||||
in, out := &in.TopologySpreadConstraints, &out.TopologySpreadConstraints
|
||||
*out = make([]corev1.TopologySpreadConstraint, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Tolerations != nil {
|
||||
in, out := &in.Tolerations, &out.Tolerations
|
||||
*out = make([]corev1.Toleration, len(*in))
|
||||
|
|
@ -796,6 +933,11 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) {
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.LivenessProbe != nil {
|
||||
in, out := &in.LivenessProbe, &out.LivenessProbe
|
||||
*out = new(corev1.Probe)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Sidecars != nil {
|
||||
in, out := &in.Sidecars, &out.Sidecars
|
||||
*out = make([]Sidecar, len(*in))
|
||||
|
|
@ -874,9 +1016,9 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) {
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.InitContainersOld != nil {
|
||||
in, out := &in.InitContainersOld, &out.InitContainersOld
|
||||
*out = make([]corev1.Container, len(*in))
|
||||
if in.EnvFrom != nil {
|
||||
in, out := &in.EnvFrom, &out.EnvFrom
|
||||
*out = make([]corev1.EnvFromSource, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
|
|
@ -975,14 +1117,14 @@ func (in *PostgresTeamSpec) DeepCopyInto(out *PostgresTeamSpec) {
|
|||
*out = *in
|
||||
if in.AdditionalSuperuserTeams != nil {
|
||||
in, out := &in.AdditionalSuperuserTeams, &out.AdditionalSuperuserTeams
|
||||
*out = make(map[string][]string, len(*in))
|
||||
*out = make(map[string]SuperUserTeams, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal []string
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
in, out := &val, &outVal
|
||||
*out = make([]string, len(*in))
|
||||
*out = make(SuperUserTeams, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
|
|
@ -990,14 +1132,14 @@ func (in *PostgresTeamSpec) DeepCopyInto(out *PostgresTeamSpec) {
|
|||
}
|
||||
if in.AdditionalTeams != nil {
|
||||
in, out := &in.AdditionalTeams, &out.AdditionalTeams
|
||||
*out = make(map[string][]string, len(*in))
|
||||
*out = make(map[string]Teams, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal []string
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
in, out := &val, &outVal
|
||||
*out = make([]string, len(*in))
|
||||
*out = make(Teams, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
|
|
@ -1005,14 +1147,14 @@ func (in *PostgresTeamSpec) DeepCopyInto(out *PostgresTeamSpec) {
|
|||
}
|
||||
if in.AdditionalMembers != nil {
|
||||
in, out := &in.AdditionalMembers, &out.AdditionalMembers
|
||||
*out = make(map[string][]string, len(*in))
|
||||
*out = make(map[string]Users, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal []string
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
in, out := &val, &outVal
|
||||
*out = make([]string, len(*in))
|
||||
*out = make(Users, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
|
|
@ -1400,6 +1542,26 @@ func (in *StreamTable) DeepCopy() *StreamTable {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in SuperUserTeams) DeepCopyInto(out *SuperUserTeams) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(SuperUserTeams, len(*in))
|
||||
copy(*out, *in)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SuperUserTeams.
|
||||
func (in SuperUserTeams) DeepCopy() SuperUserTeams {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SuperUserTeams)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TLSDescription) DeepCopyInto(out *TLSDescription) {
|
||||
*out = *in
|
||||
|
|
@ -1416,6 +1578,26 @@ func (in *TLSDescription) DeepCopy() *TLSDescription {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in Teams) DeepCopyInto(out *Teams) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(Teams, len(*in))
|
||||
copy(*out, *in)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Teams.
|
||||
func (in Teams) DeepCopy() Teams {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Teams)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TeamsAPIConfiguration) DeepCopyInto(out *TeamsAPIConfiguration) {
|
||||
*out = *in
|
||||
|
|
@ -1469,6 +1651,26 @@ func (in UserFlags) DeepCopy() UserFlags {
|
|||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in Users) DeepCopyInto(out *Users) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(Users, len(*in))
|
||||
copy(*out, *in)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Users.
|
||||
func (in Users) DeepCopy() Users {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Users)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Volume) DeepCopyInto(out *Volume) {
|
||||
*out = *in
|
||||
|
|
@ -1484,12 +1686,12 @@ func (in *Volume) DeepCopyInto(out *Volume) {
|
|||
}
|
||||
if in.Iops != nil {
|
||||
in, out := &in.Iops, &out.Iops
|
||||
*out = new(int64)
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.Throughput != nil {
|
||||
in, out := &in.Throughput, &out.Throughput
|
||||
*out = new(int64)
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
|
|
|
|||
|
|
@ -9,14 +9,16 @@ import (
|
|||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// FabricEventStream defines FabricEventStream Custom Resource Definition Object.
|
||||
// +k8s:deepcopy-gen=true
|
||||
type FabricEventStream struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
metav1.ObjectMeta `json:"metadata"`
|
||||
|
||||
Spec FabricEventStreamSpec `json:"spec"`
|
||||
}
|
||||
|
||||
// FabricEventStreamSpec defines the specification for the FabricEventStream TPR.
|
||||
// +k8s:deepcopy-gen=true
|
||||
type FabricEventStreamSpec struct {
|
||||
ApplicationId string `json:"applicationId"`
|
||||
EventStreams []EventStream `json:"eventStreams"`
|
||||
|
|
@ -25,6 +27,7 @@ type FabricEventStreamSpec struct {
|
|||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// FabricEventStreamList defines a list of FabricEventStreams .
|
||||
// +k8s:deepcopy-gen=true
|
||||
type FabricEventStreamList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata"`
|
||||
|
|
@ -33,6 +36,7 @@ type FabricEventStreamList struct {
|
|||
}
|
||||
|
||||
// EventStream defines the source, flow and sink of the event stream
|
||||
// +k8s:deepcopy-gen=true
|
||||
type EventStream struct {
|
||||
EventStreamFlow EventStreamFlow `json:"flow"`
|
||||
EventStreamSink EventStreamSink `json:"sink"`
|
||||
|
|
@ -41,12 +45,14 @@ type EventStream struct {
|
|||
}
|
||||
|
||||
// EventStreamFlow defines the flow characteristics of the event stream
|
||||
// +k8s:deepcopy-gen=true
|
||||
type EventStreamFlow struct {
|
||||
Type string `json:"type"`
|
||||
PayloadColumn *string `json:"payloadColumn,omitempty"`
|
||||
}
|
||||
|
||||
// EventStreamSink defines the target of the event stream
|
||||
// +k8s:deepcopy-gen=true
|
||||
type EventStreamSink struct {
|
||||
Type string `json:"type"`
|
||||
EventType string `json:"eventType,omitempty"`
|
||||
|
|
@ -54,12 +60,14 @@ type EventStreamSink struct {
|
|||
}
|
||||
|
||||
// EventStreamRecovery defines the target of dead letter queue
|
||||
// +k8s:deepcopy-gen=true
|
||||
type EventStreamRecovery struct {
|
||||
Type string `json:"type"`
|
||||
Sink *EventStreamSink `json:"sink"`
|
||||
}
|
||||
|
||||
// EventStreamSource defines the source of the event stream and connection for FES operator
|
||||
// +k8s:deepcopy-gen=true
|
||||
type EventStreamSource struct {
|
||||
Type string `json:"type"`
|
||||
Schema string `json:"schema,omitempty" defaults:"public"`
|
||||
|
|
@ -69,12 +77,14 @@ type EventStreamSource struct {
|
|||
}
|
||||
|
||||
// EventStreamTable defines the name and ID column to be used for streaming
|
||||
// +k8s:deepcopy-gen=true
|
||||
type EventStreamTable struct {
|
||||
Name string `json:"name"`
|
||||
IDColumn *string `json:"idColumn,omitempty"`
|
||||
}
|
||||
|
||||
// Connection to be used for allowing the FES operator to connect to a database
|
||||
// +k8s:deepcopy-gen=true
|
||||
type Connection struct {
|
||||
Url string `json:"jdbcUrl"`
|
||||
SlotName string `json:"slotName"`
|
||||
|
|
@ -84,6 +94,7 @@ type Connection struct {
|
|||
}
|
||||
|
||||
// DBAuth specifies the credentials to be used for connecting with the database
|
||||
// +k8s:deepcopy-gen=true
|
||||
type DBAuth struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name,omitempty"`
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2021 Compose, Zalando SE
|
||||
Copyright 2026 Compose, Zalando SE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -38,11 +39,11 @@ func (in *Connection) DeepCopyInto(out *Connection) {
|
|||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
in.DBAuth.DeepCopyInto(&out.DBAuth)
|
||||
out.DBAuth = in.DBAuth
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Connection.
|
||||
func (in *Connection) DeepCopy() *Connection {
|
||||
if in == nil {
|
||||
return nil
|
||||
|
|
@ -58,7 +59,7 @@ func (in *DBAuth) DeepCopyInto(out *DBAuth) {
|
|||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBAuth.
|
||||
func (in *DBAuth) DeepCopy() *DBAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
|
|
@ -72,13 +73,13 @@ func (in *DBAuth) DeepCopy() *DBAuth {
|
|||
func (in *EventStream) DeepCopyInto(out *EventStream) {
|
||||
*out = *in
|
||||
in.EventStreamFlow.DeepCopyInto(&out.EventStreamFlow)
|
||||
in.EventStreamRecovery.DeepCopyInto(&out.EventStreamRecovery)
|
||||
in.EventStreamSink.DeepCopyInto(&out.EventStreamSink)
|
||||
in.EventStreamSource.DeepCopyInto(&out.EventStreamSource)
|
||||
in.EventStreamRecovery.DeepCopyInto(&out.EventStreamRecovery)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventStream.
|
||||
func (in *EventStream) DeepCopy() *EventStream {
|
||||
if in == nil {
|
||||
return nil
|
||||
|
|
@ -99,7 +100,7 @@ func (in *EventStreamFlow) DeepCopyInto(out *EventStreamFlow) {
|
|||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventStreamFlow.
|
||||
func (in *EventStreamFlow) DeepCopy() *EventStreamFlow {
|
||||
if in == nil {
|
||||
return nil
|
||||
|
|
@ -120,7 +121,7 @@ func (in *EventStreamRecovery) DeepCopyInto(out *EventStreamRecovery) {
|
|||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventStreamRecovery.
|
||||
func (in *EventStreamRecovery) DeepCopy() *EventStreamRecovery {
|
||||
if in == nil {
|
||||
return nil
|
||||
|
|
@ -141,7 +142,7 @@ func (in *EventStreamSink) DeepCopyInto(out *EventStreamSink) {
|
|||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventStreamSink.
|
||||
func (in *EventStreamSink) DeepCopy() *EventStreamSink {
|
||||
if in == nil {
|
||||
return nil
|
||||
|
|
@ -154,17 +155,17 @@ func (in *EventStreamSink) DeepCopy() *EventStreamSink {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EventStreamSource) DeepCopyInto(out *EventStreamSource) {
|
||||
*out = *in
|
||||
in.Connection.DeepCopyInto(&out.Connection)
|
||||
in.EventStreamTable.DeepCopyInto(&out.EventStreamTable)
|
||||
if in.Filter != nil {
|
||||
in, out := &in.Filter, &out.Filter
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
in.EventStreamTable.DeepCopyInto(&out.EventStreamTable)
|
||||
in.Connection.DeepCopyInto(&out.Connection)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventStreamSource.
|
||||
func (in *EventStreamSource) DeepCopy() *EventStreamSource {
|
||||
if in == nil {
|
||||
return nil
|
||||
|
|
@ -185,7 +186,7 @@ func (in *EventStreamTable) DeepCopyInto(out *EventStreamTable) {
|
|||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventStreamTable.
|
||||
func (in *EventStreamTable) DeepCopy() *EventStreamTable {
|
||||
if in == nil {
|
||||
return nil
|
||||
|
|
@ -195,30 +196,6 @@ func (in *EventStreamTable) DeepCopy() *EventStreamTable {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *FabricEventStreamSpec) DeepCopyInto(out *FabricEventStreamSpec) {
|
||||
*out = *in
|
||||
if in.EventStreams != nil {
|
||||
in, out := &in.EventStreams, &out.EventStreams
|
||||
*out = make([]EventStream, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FabricEventStreamSpec.
|
||||
func (in *FabricEventStreamSpec) DeepCopy() *FabricEventStreamSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(FabricEventStreamSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *FabricEventStream) DeepCopyInto(out *FabricEventStream) {
|
||||
*out = *in
|
||||
|
|
@ -278,3 +255,26 @@ func (in *FabricEventStreamList) DeepCopyObject() runtime.Object {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *FabricEventStreamSpec) DeepCopyInto(out *FabricEventStreamSpec) {
|
||||
*out = *in
|
||||
if in.EventStreams != nil {
|
||||
in, out := &in.EventStreams, &out.EventStreams
|
||||
*out = make([]EventStream, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FabricEventStreamSpec.
|
||||
func (in *FabricEventStreamSpec) DeepCopy() *FabricEventStreamSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(FabricEventStreamSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package cluster
|
|||
// Postgres CustomResourceDefinition object i.e. Spilo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
|
@ -32,6 +33,7 @@ import (
|
|||
v1 "k8s.io/api/core/v1"
|
||||
policyv1 "k8s.io/api/policy/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/rest"
|
||||
|
|
@ -70,7 +72,7 @@ type kubeResources struct {
|
|||
CriticalOpPodDisruptionBudget *policyv1.PodDisruptionBudget
|
||||
LogicalBackupJob *batchv1.CronJob
|
||||
Streams map[string]*zalandov1.FabricEventStream
|
||||
//Pods are treated separately
|
||||
// Pods are treated separately
|
||||
}
|
||||
|
||||
// Cluster describes postgresql cluster
|
||||
|
|
@ -90,12 +92,13 @@ type Cluster struct {
|
|||
mu sync.Mutex
|
||||
userSyncStrategy spec.UserSyncer
|
||||
deleteOptions metav1.DeleteOptions
|
||||
podEventsStore cache.Store
|
||||
podEventsQueue *cache.FIFO
|
||||
replicationSlots map[string]interface{}
|
||||
|
||||
teamsAPIClient teams.Interface
|
||||
oauthTokenGetter OAuthTokenGetter
|
||||
KubeClient k8sutil.KubernetesClient //TODO: move clients to the better place?
|
||||
KubeClient k8sutil.KubernetesClient // TODO: move clients to the better place?
|
||||
currentProcess Process
|
||||
processMu sync.RWMutex // protects the current operation for reporting, no need to hold the master mutex
|
||||
specMu sync.RWMutex // protects the spec for reporting, no need to hold the master mutex
|
||||
|
|
@ -123,17 +126,20 @@ type compareLogicalBackupJobResult struct {
|
|||
func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec acidv1.Postgresql, logger *logrus.Entry, eventRecorder record.EventRecorder) *Cluster {
|
||||
deletePropagationPolicy := metav1.DeletePropagationOrphan
|
||||
|
||||
podEventsQueue := cache.NewFIFO(func(obj interface{}) (string, error) {
|
||||
keyFn := func(obj interface{}) (string, error) {
|
||||
e, ok := obj.(PodEvent)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("could not cast to PodEvent")
|
||||
return "", fmt.Errorf("could not cast to pod event")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s-%s", e.PodName, e.ResourceVersion), nil
|
||||
})
|
||||
}
|
||||
podEventsStore := cache.NewStore(keyFn)
|
||||
podEventsQueue := cache.NewFIFO(keyFn)
|
||||
|
||||
passwordEncryption, ok := pgSpec.Spec.PostgresqlParam.Parameters["password_encryption"]
|
||||
if !ok {
|
||||
passwordEncryption = "md5"
|
||||
passwordEncryption = "scram-sha-256"
|
||||
}
|
||||
|
||||
cluster := &Cluster{
|
||||
|
|
@ -149,13 +155,15 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec acidv1.Postgres
|
|||
PatroniEndpoints: make(map[string]*v1.Endpoints),
|
||||
PatroniConfigMaps: make(map[string]*v1.ConfigMap),
|
||||
VolumeClaims: make(map[types.UID]*v1.PersistentVolumeClaim),
|
||||
Streams: make(map[string]*zalandov1.FabricEventStream)},
|
||||
Streams: make(map[string]*zalandov1.FabricEventStream),
|
||||
},
|
||||
userSyncStrategy: users.DefaultUserSyncStrategy{
|
||||
PasswordEncryption: passwordEncryption,
|
||||
RoleDeletionSuffix: cfg.OpConfig.RoleDeletionSuffix,
|
||||
AdditionalOwnerRoles: cfg.OpConfig.AdditionalOwnerRoles,
|
||||
},
|
||||
deleteOptions: metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy},
|
||||
podEventsStore: podEventsStore,
|
||||
podEventsQueue: podEventsQueue,
|
||||
KubeClient: kubeClient,
|
||||
currentMajorVersion: 0,
|
||||
|
|
@ -168,7 +176,7 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec acidv1.Postgres
|
|||
cluster.eventRecorder = eventRecorder
|
||||
|
||||
cluster.EBSVolumes = make(map[string]volumes.VolumeProperties)
|
||||
if cfg.OpConfig.StorageResizeMode != "pvc" || cfg.OpConfig.EnableEBSGp3Migration {
|
||||
if cfg.OpConfig.StorageResizeMode != "pvc" {
|
||||
cluster.VolumeResizer = &volumes.EBSVolumeResizer{AWSRegion: cfg.OpConfig.AWSRegion}
|
||||
}
|
||||
|
||||
|
|
@ -271,25 +279,29 @@ func (c *Cluster) Create() (err error) {
|
|||
)
|
||||
|
||||
defer func() {
|
||||
var (
|
||||
pgUpdatedStatus *acidv1.Postgresql
|
||||
errStatus error
|
||||
)
|
||||
if err == nil {
|
||||
pgUpdatedStatus, errStatus = c.KubeClient.SetPostgresCRDStatus(c.clusterName(), acidv1.ClusterStatusRunning) //TODO: are you sure it's running?
|
||||
} else {
|
||||
currentStatus := c.Status.DeepCopy()
|
||||
pg := c.Postgresql.DeepCopy()
|
||||
pg.Status.PostgresClusterStatus = acidv1.ClusterStatusRunning
|
||||
|
||||
if err != nil {
|
||||
c.logger.Warningf("cluster created failed: %v", err)
|
||||
pgUpdatedStatus, errStatus = c.KubeClient.SetPostgresCRDStatus(c.clusterName(), acidv1.ClusterStatusAddFailed)
|
||||
pg.Status.PostgresClusterStatus = acidv1.ClusterStatusAddFailed
|
||||
}
|
||||
if errStatus != nil {
|
||||
c.logger.Warningf("could not set cluster status: %v", errStatus)
|
||||
}
|
||||
if pgUpdatedStatus != nil {
|
||||
|
||||
if !equality.Semantic.DeepEqual(currentStatus, pg.Status) {
|
||||
pgUpdatedStatus, err := c.KubeClient.SetPostgresCRDStatus(c.clusterName(), pg)
|
||||
if err != nil {
|
||||
c.logger.Warningf("could not set cluster status: %v", err)
|
||||
return
|
||||
}
|
||||
c.setSpec(pgUpdatedStatus)
|
||||
}
|
||||
}()
|
||||
|
||||
pgCreateStatus, err = c.KubeClient.SetPostgresCRDStatus(c.clusterName(), acidv1.ClusterStatusCreating)
|
||||
pg := c.Postgresql.DeepCopy()
|
||||
pg.Status.PostgresClusterStatus = acidv1.ClusterStatusCreating
|
||||
|
||||
pgCreateStatus, err = c.KubeClient.SetPostgresCRDStatus(c.clusterName(), pg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not set cluster status: %v", err)
|
||||
}
|
||||
|
|
@ -380,7 +392,7 @@ func (c *Cluster) Create() (err error) {
|
|||
|
||||
// create database objects unless we are running without pods or disabled
|
||||
// that feature explicitly
|
||||
if !(c.databaseAccessDisabled() || c.getNumberOfInstances(&c.Spec) <= 0 || c.Spec.StandbyCluster != nil) {
|
||||
if !(c.databaseAccessDisabled() || c.getNumberOfInstances(&c.Spec) <= 0 || isStandbyCluster(&c.Spec)) {
|
||||
c.logger.Infof("Create roles")
|
||||
if err = c.createRoles(); err != nil {
|
||||
return fmt.Errorf("could not create users: %v", err)
|
||||
|
|
@ -440,7 +452,7 @@ func (c *Cluster) compareStatefulSetWith(statefulSet *appsv1.StatefulSet) *compa
|
|||
var match, needsRollUpdate, needsReplace bool
|
||||
|
||||
match = true
|
||||
//TODO: improve me
|
||||
// TODO: improve me
|
||||
if *c.Statefulset.Spec.Replicas != *statefulSet.Spec.Replicas {
|
||||
match = false
|
||||
reasons = append(reasons, "new statefulset's number of replicas does not match the current one")
|
||||
|
|
@ -499,6 +511,11 @@ func (c *Cluster) compareStatefulSetWith(statefulSet *appsv1.StatefulSet) *compa
|
|||
needsRollUpdate = true
|
||||
reasons = append(reasons, "new statefulset's pod affinity does not match the current one")
|
||||
}
|
||||
if !reflect.DeepEqual(c.Statefulset.Spec.Template.Spec.TopologySpreadConstraints, statefulSet.Spec.Template.Spec.TopologySpreadConstraints) {
|
||||
needsReplace = true
|
||||
needsRollUpdate = true
|
||||
reasons = append(reasons, "new statefulset's pod topologySpreadConstraints does not match the current one")
|
||||
}
|
||||
if len(c.Statefulset.Spec.Template.Spec.Tolerations) != len(statefulSet.Spec.Template.Spec.Tolerations) {
|
||||
needsReplace = true
|
||||
needsRollUpdate = true
|
||||
|
|
@ -613,6 +630,8 @@ func (c *Cluster) compareContainers(description string, setA, setB []v1.Containe
|
|||
func(a, b v1.Container) bool { return a.Name != b.Name }),
|
||||
newCheck("new %s's %s (index %d) readiness probe does not match the current one",
|
||||
func(a, b v1.Container) bool { return !reflect.DeepEqual(a.ReadinessProbe, b.ReadinessProbe) }),
|
||||
newCheck("new %s's %s (index %d) liveness probe does not match the current one",
|
||||
func(a, b v1.Container) bool { return !reflect.DeepEqual(a.LivenessProbe, b.LivenessProbe) }),
|
||||
newCheck("new %s's %s (index %d) ports do not match the current one",
|
||||
func(a, b v1.Container) bool { return !comparePorts(a.Ports, b.Ports) }),
|
||||
newCheck("new %s's %s (index %d) resources do not match the current ones",
|
||||
|
|
@ -672,7 +691,6 @@ func compareResourcesAssumeFirstNotNil(a *v1.ResourceRequirements, b *v1.Resourc
|
|||
}
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
func compareEnv(a, b []v1.EnvVar) bool {
|
||||
|
|
@ -707,9 +725,7 @@ func compareEnv(a, b []v1.EnvVar) bool {
|
|||
}
|
||||
|
||||
func compareSpiloConfiguration(configa, configb string) bool {
|
||||
var (
|
||||
oa, ob spiloConfiguration
|
||||
)
|
||||
var oa, ob spiloConfiguration
|
||||
|
||||
var err error
|
||||
err = json.Unmarshal([]byte(configa), &oa)
|
||||
|
|
@ -818,7 +834,6 @@ func (c *Cluster) compareAnnotations(old, new map[string]string, removedList *[]
|
|||
}
|
||||
|
||||
return reason != "", reason
|
||||
|
||||
}
|
||||
|
||||
func (c *Cluster) compareServices(old, new *v1.Service) (bool, string) {
|
||||
|
|
@ -849,6 +864,14 @@ func (c *Cluster) compareServices(old, new *v1.Service) (bool, string) {
|
|||
return false, "new service's ExternalTrafficPolicy does not match the current one"
|
||||
}
|
||||
|
||||
if len(old.Spec.Ports) > 0 && len(new.Spec.Ports) > 0 {
|
||||
// we need to check whether the new port is not zero (=user-defined)
|
||||
// and only overwrite if it is
|
||||
if new.Spec.Ports[0].NodePort != 0 && old.Spec.Ports[0].NodePort != new.Spec.Ports[0].NodePort {
|
||||
return false, "new service's NodePort does not match the current one"
|
||||
}
|
||||
}
|
||||
|
||||
return true, ""
|
||||
}
|
||||
|
||||
|
|
@ -883,6 +906,16 @@ func (c *Cluster) compareLogicalBackupJob(cur, new *batchv1.CronJob) *compareLog
|
|||
reasons = append(reasons, fmt.Sprintf("new job's env PG_VERSION %q does not match the current one %q", newPgVersion, curPgVersion))
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cur.Labels, new.Labels) {
|
||||
match = false
|
||||
reasons = append(reasons, "new job's labels do not match the current ones")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cur.Spec.JobTemplate.Labels, new.Spec.JobTemplate.Labels) {
|
||||
match = false
|
||||
reasons = append(reasons, "new job's template labels do not match the current ones")
|
||||
}
|
||||
|
||||
needsReplace := false
|
||||
contReasons := make([]string, 0)
|
||||
needsReplace, contReasons = c.compareContainers("cronjob container", cur.Spec.JobTemplate.Spec.Template.Spec.Containers, new.Spec.JobTemplate.Spec.Template.Spec.Containers, needsReplace, contReasons)
|
||||
|
|
@ -891,11 +924,26 @@ func (c *Cluster) compareLogicalBackupJob(cur, new *batchv1.CronJob) *compareLog
|
|||
reasons = append(reasons, fmt.Sprintf("logical backup container specs do not match: %v", strings.Join(contReasons, `', '`)))
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cur.Spec.SuccessfulJobsHistoryLimit, new.Spec.SuccessfulJobsHistoryLimit) {
|
||||
match = false
|
||||
reasons = append(reasons, fmt.Sprintf("new job's successfulJobsHistoryLimit %v does not match the current one %v", new.Spec.SuccessfulJobsHistoryLimit, cur.Spec.SuccessfulJobsHistoryLimit))
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cur.Spec.FailedJobsHistoryLimit, new.Spec.FailedJobsHistoryLimit) {
|
||||
match = false
|
||||
reasons = append(reasons, fmt.Sprintf("new job's failedJobsHistoryLimit %v does not match the current one %v", new.Spec.FailedJobsHistoryLimit, cur.Spec.FailedJobsHistoryLimit))
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cur.Spec.JobTemplate.Spec.TTLSecondsAfterFinished, new.Spec.JobTemplate.Spec.TTLSecondsAfterFinished) {
|
||||
match = false
|
||||
reasons = append(reasons, fmt.Sprintf("new job's TTLSecondsAfterFinished %v does not match the current one %v", new.Spec.JobTemplate.Spec.TTLSecondsAfterFinished, cur.Spec.JobTemplate.Spec.TTLSecondsAfterFinished))
|
||||
}
|
||||
|
||||
return &compareLogicalBackupJobResult{match: match, reasons: reasons, deletedPodAnnotations: deletedPodAnnotations}
|
||||
}
|
||||
|
||||
func (c *Cluster) comparePodDisruptionBudget(cur, new *policyv1.PodDisruptionBudget) (bool, string) {
|
||||
//TODO: improve comparison
|
||||
// TODO: improve comparison
|
||||
if !reflect.DeepEqual(new.Spec, cur.Spec) {
|
||||
return false, "new PDB's spec does not match the current one"
|
||||
}
|
||||
|
|
@ -944,8 +992,17 @@ func (c *Cluster) removeFinalizer() error {
|
|||
}
|
||||
|
||||
c.logger.Infof("removing finalizer %s", finalizerName)
|
||||
finalizers := util.RemoveString(c.ObjectMeta.Finalizers, finalizerName)
|
||||
newSpec, err := c.KubeClient.SetFinalizer(c.clusterName(), c.DeepCopy(), finalizers)
|
||||
|
||||
// Fetch the latest version of the object to avoid resourceVersion conflicts
|
||||
clusterName := c.clusterName()
|
||||
latestPg, err := c.KubeClient.PostgresqlsGetter.Postgresqls(clusterName.Namespace).Get(
|
||||
context.TODO(), clusterName.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error fetching latest postgresql for finalizer removal: %v", err)
|
||||
}
|
||||
|
||||
finalizers := util.RemoveString(latestPg.ObjectMeta.Finalizers, finalizerName)
|
||||
newSpec, err := c.KubeClient.SetFinalizer(clusterName, latestPg, finalizers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error removing finalizer: %v", err)
|
||||
}
|
||||
|
|
@ -977,28 +1034,33 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
|
|||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.KubeClient.SetPostgresCRDStatus(c.clusterName(), acidv1.ClusterStatusUpdating)
|
||||
newSpec.Status.PostgresClusterStatus = acidv1.ClusterStatusUpdating
|
||||
|
||||
if !isInMaintenanceWindow(newSpec.Spec.MaintenanceWindows) {
|
||||
newSpec, err := c.KubeClient.SetPostgresCRDStatus(c.clusterName(), newSpec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not set cluster status to updating: %w", err)
|
||||
}
|
||||
|
||||
if !c.isInMaintenanceWindow(newSpec.Spec.MaintenanceWindows) {
|
||||
// do not apply any major version related changes yet
|
||||
newSpec.Spec.PostgresqlParam.PgVersion = oldSpec.Spec.PostgresqlParam.PgVersion
|
||||
}
|
||||
c.setSpec(newSpec)
|
||||
|
||||
defer func() {
|
||||
var (
|
||||
pgUpdatedStatus *acidv1.Postgresql
|
||||
err error
|
||||
)
|
||||
currentStatus := newSpec.Status.DeepCopy()
|
||||
newSpec.Status.PostgresClusterStatus = acidv1.ClusterStatusRunning
|
||||
|
||||
if updateFailed {
|
||||
pgUpdatedStatus, err = c.KubeClient.SetPostgresCRDStatus(c.clusterName(), acidv1.ClusterStatusUpdateFailed)
|
||||
} else {
|
||||
pgUpdatedStatus, err = c.KubeClient.SetPostgresCRDStatus(c.clusterName(), acidv1.ClusterStatusRunning)
|
||||
newSpec.Status.PostgresClusterStatus = acidv1.ClusterStatusUpdateFailed
|
||||
}
|
||||
if err != nil {
|
||||
c.logger.Warningf("could not set cluster status: %v", err)
|
||||
}
|
||||
if pgUpdatedStatus != nil {
|
||||
|
||||
if !equality.Semantic.DeepEqual(currentStatus, newSpec.Status) {
|
||||
pgUpdatedStatus, err := c.KubeClient.SetPostgresCRDStatus(c.clusterName(), newSpec)
|
||||
if err != nil {
|
||||
c.logger.Warningf("could not set cluster status: %v", err)
|
||||
return
|
||||
}
|
||||
c.setSpec(pgUpdatedStatus)
|
||||
}
|
||||
}()
|
||||
|
|
@ -1022,7 +1084,7 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
|
|||
|
||||
// Patroni service and endpoints / config maps
|
||||
if err := c.syncPatroniResources(); err != nil {
|
||||
c.logger.Errorf("could not sync services: %v", err)
|
||||
c.logger.Errorf("could not sync Patroni resources: %v", err)
|
||||
updateFailed = true
|
||||
}
|
||||
|
||||
|
|
@ -1063,7 +1125,7 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
|
|||
}
|
||||
|
||||
c.logger.Debug("syncing secrets")
|
||||
//TODO: mind the secrets of the deleted/new users
|
||||
// TODO: mind the secrets of the deleted/new users
|
||||
if err := c.syncSecrets(); err != nil {
|
||||
c.logger.Errorf("could not sync secrets: %v", err)
|
||||
updateFailed = true
|
||||
|
|
@ -1078,6 +1140,12 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
|
|||
c.logger.Infof("Storage resize is disabled (storage_resize_mode is off). Skipping volume size sync.")
|
||||
}
|
||||
|
||||
// Pod service account (IRSA annotation sync)
|
||||
if err := c.syncPodServiceAccount(); err != nil {
|
||||
c.logger.Errorf("could not sync pod service account: %v", err)
|
||||
updateFailed = true
|
||||
}
|
||||
|
||||
// Statefulset
|
||||
func() {
|
||||
if err := c.syncStatefulSet(); err != nil {
|
||||
|
|
@ -1101,7 +1169,6 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
|
|||
|
||||
// logical backup job
|
||||
func() {
|
||||
|
||||
// create if it did not exist
|
||||
if !oldSpec.Spec.EnableLogicalBackup && newSpec.Spec.EnableLogicalBackup {
|
||||
c.logger.Debug("creating backup cron job")
|
||||
|
|
@ -1129,7 +1196,6 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
|
|||
updateFailed = true
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
// Roles and Databases
|
||||
|
|
@ -1206,7 +1272,7 @@ func syncResources(a, b *v1.ResourceRequirements) bool {
|
|||
// before the pods, it will be re-created by the current master pod and will remain, obstructing the
|
||||
// creation of the new cluster with the same name. Therefore, the endpoints should be deleted last.
|
||||
func (c *Cluster) Delete() error {
|
||||
var anyErrors = false
|
||||
anyErrors := false
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.eventRecorder.Event(c.GetReference(), v1.EventTypeNormal, "Delete", "Started deletion of cluster resources")
|
||||
|
|
@ -1283,7 +1349,7 @@ func (c *Cluster) Delete() error {
|
|||
// If we are done deleting our various resources we remove the finalizer to let K8S finally delete the Postgres CR
|
||||
if anyErrors {
|
||||
c.eventRecorder.Event(c.GetReference(), v1.EventTypeWarning, "Delete", "some resources could be successfully deleted yet")
|
||||
return fmt.Errorf("some error(s) occured when deleting resources, NOT removing finalizer yet")
|
||||
return fmt.Errorf("some error(s) occurred when deleting resources, NOT removing finalizer yet")
|
||||
}
|
||||
if err := c.removeFinalizer(); err != nil {
|
||||
return fmt.Errorf("done cleaning up, but error when removing finalizer: %v", err)
|
||||
|
|
@ -1297,11 +1363,13 @@ func (c *Cluster) NeedsRepair() (bool, acidv1.PostgresStatus) {
|
|||
c.specMu.RLock()
|
||||
defer c.specMu.RUnlock()
|
||||
return !c.Status.Success(), c.Status
|
||||
|
||||
}
|
||||
|
||||
// ReceivePodEvent is called back by the controller in order to add the cluster's pod event to the queue.
|
||||
func (c *Cluster) ReceivePodEvent(event PodEvent) {
|
||||
if err := c.podEventsStore.Add(event); err != nil {
|
||||
c.logger.Errorf("error when receiving pod event for lookup: %v", err)
|
||||
}
|
||||
if err := c.podEventsQueue.Add(event); err != nil {
|
||||
c.logger.Errorf("error when receiving pod events: %v", err)
|
||||
}
|
||||
|
|
@ -1342,7 +1410,19 @@ func (c *Cluster) processPodEventQueue(stopCh <-chan struct{}) {
|
|||
case <-stopCh:
|
||||
return
|
||||
default:
|
||||
if _, err := c.podEventsQueue.Pop(cache.PopProcessFunc(c.processPodEvent)); err != nil {
|
||||
_, err := c.podEventsQueue.Pop(cache.PopProcessFunc(func(obj interface{}, isInInitialList bool) error {
|
||||
event, ok := obj.(PodEvent)
|
||||
if !ok {
|
||||
c.logger.Errorf("could not cast to pod event")
|
||||
return nil // skip event to keep processing
|
||||
}
|
||||
c.processPodEvent(event, isInInitialList)
|
||||
if err := c.podEventsStore.Delete(obj); err != nil {
|
||||
c.logger.Errorf("failed to delete key from lookup store: %v", err)
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
if err != nil {
|
||||
c.logger.Errorf("error when processing pod event queue %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -1406,7 +1486,6 @@ func (c *Cluster) initSystemUsers() {
|
|||
}
|
||||
|
||||
func (c *Cluster) initPreparedDatabaseRoles() error {
|
||||
|
||||
if c.Spec.PreparedDatabases != nil && len(c.Spec.PreparedDatabases) == 0 { // TODO: add option to disable creating such a default DB
|
||||
c.Spec.PreparedDatabases = map[string]acidv1.PreparedDatabase{strings.Replace(c.Name, "-", "_", -1): {}}
|
||||
}
|
||||
|
|
@ -1472,10 +1551,9 @@ func (c *Cluster) initPreparedDatabaseRoles() error {
|
|||
}
|
||||
|
||||
func (c *Cluster) initDefaultRoles(defaultRoles map[string]string, admin, prefix, searchPath, secretNamespace string) error {
|
||||
|
||||
for defaultRole, inherits := range defaultRoles {
|
||||
namespace := c.Namespace
|
||||
//if namespaced secrets are allowed
|
||||
// if namespaced secrets are allowed
|
||||
if secretNamespace != "" {
|
||||
if c.Config.OpConfig.EnableCrossNamespaceSecret {
|
||||
namespace = secretNamespace
|
||||
|
|
@ -1543,7 +1621,7 @@ func (c *Cluster) initRobotUsers() error {
|
|||
}
|
||||
}
|
||||
|
||||
//if namespaced secrets are allowed
|
||||
// if namespaced secrets are allowed
|
||||
if c.Config.OpConfig.EnableCrossNamespaceSecret {
|
||||
if strings.Contains(username, ".") {
|
||||
splits := strings.Split(username, ".")
|
||||
|
|
@ -1594,7 +1672,6 @@ func (c *Cluster) initAdditionalOwnerRoles() {
|
|||
|
||||
func (c *Cluster) initTeamMembers(teamID string, isPostgresSuperuserTeam bool) error {
|
||||
teamMembers, err := c.getTeamMembers(teamID)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get list of team members for team %q: %v", teamID, err)
|
||||
}
|
||||
|
|
@ -1633,7 +1710,6 @@ func (c *Cluster) initTeamMembers(teamID string, isPostgresSuperuserTeam bool) e
|
|||
}
|
||||
|
||||
func (c *Cluster) initHumanUsers() error {
|
||||
|
||||
var clusterIsOwnedBySuperuserTeam bool
|
||||
superuserTeams := []string{}
|
||||
|
||||
|
|
@ -1774,7 +1850,20 @@ func (c *Cluster) GetSwitchoverSchedule() string {
|
|||
func (c *Cluster) getSwitchoverScheduleAtTime(now time.Time) string {
|
||||
var possibleSwitchover, schedule time.Time
|
||||
|
||||
for _, window := range c.Spec.MaintenanceWindows {
|
||||
maintenanceWindows := c.Spec.MaintenanceWindows
|
||||
if len(maintenanceWindows) == 0 {
|
||||
maintenanceWindows = make([]acidv1.MaintenanceWindow, 0, len(c.OpConfig.MaintenanceWindows))
|
||||
for _, windowStr := range c.OpConfig.MaintenanceWindows {
|
||||
var window acidv1.MaintenanceWindow
|
||||
if err := window.UnmarshalJSON([]byte(windowStr)); err != nil {
|
||||
c.logger.Errorf("could not parse default maintenance window %q: %v", windowStr, err)
|
||||
continue
|
||||
}
|
||||
maintenanceWindows = append(maintenanceWindows, window)
|
||||
}
|
||||
}
|
||||
|
||||
for _, window := range maintenanceWindows {
|
||||
// in the best case it is possible today
|
||||
possibleSwitchover = time.Date(now.Year(), now.Month(), now.Day(), window.StartTime.Hour(), window.StartTime.Minute(), 0, 0, time.UTC)
|
||||
if window.Everyday {
|
||||
|
|
@ -1796,6 +1885,11 @@ func (c *Cluster) getSwitchoverScheduleAtTime(now time.Time) string {
|
|||
schedule = possibleSwitchover
|
||||
}
|
||||
}
|
||||
|
||||
if schedule.IsZero() {
|
||||
return ""
|
||||
}
|
||||
|
||||
return schedule.Format("2006-01-02T15:04+00")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ const (
|
|||
replicationUserName = "standby"
|
||||
poolerUserName = "pooler"
|
||||
adminUserName = "admin"
|
||||
exampleSpiloConfig = `{"postgresql":{"bin_dir":"/usr/lib/postgresql/12/bin","parameters":{"autovacuum_analyze_scale_factor":"0.1"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"]},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"dcs":{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"postgresql":{"parameters":{"max_connections":"100","max_locks_per_transaction":"64","max_worker_processes":"4"}}}}}`
|
||||
spiloConfigDiff = `{"postgresql":{"bin_dir":"/usr/lib/postgresql/12/bin","parameters":{"autovacuum_analyze_scale_factor":"0.1"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"]},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"dcs":{"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"postgresql":{"parameters":{"max_locks_per_transaction":"64","max_worker_processes":"4"}}}}}`
|
||||
exampleSpiloConfig = `{"postgresql":{"bin_dir":"/usr/lib/postgresql/12/bin","parameters":{"autovacuum_analyze_scale_factor":"0.1"},"pg_hba":["hostssl all all 0.0.0.0/0 scram-sha-256","host all all 0.0.0.0/0 scram-sha-256"]},"bootstrap":{"initdb":[{"auth-host":"scram-sha-256"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"dcs":{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"postgresql":{"parameters":{"max_connections":"100","max_locks_per_transaction":"64","max_worker_processes":"4"}}}}}`
|
||||
spiloConfigDiff = `{"postgresql":{"bin_dir":"/usr/lib/postgresql/12/bin","parameters":{"autovacuum_analyze_scale_factor":"0.1"},"pg_hba":["hostssl all all 0.0.0.0/0 scram-sha-256","host all all 0.0.0.0/0 scram-sha-256"]},"bootstrap":{"initdb":[{"auth-host":"scram-sha-256"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"dcs":{"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"postgresql":{"parameters":{"max_locks_per_transaction":"64","max_worker_processes":"4"}}}}}`
|
||||
)
|
||||
|
||||
var logger = logrus.New().WithField("test", "cluster")
|
||||
|
|
@ -95,6 +95,7 @@ func TestCreate(t *testing.T) {
|
|||
|
||||
client := k8sutil.KubernetesClient{
|
||||
DeploymentsGetter: clientSet.AppsV1(),
|
||||
ConfigMapsGetter: clientSet.CoreV1(),
|
||||
CronJobsGetter: clientSet.BatchV1(),
|
||||
EndpointsGetter: clientSet.CoreV1(),
|
||||
PersistentVolumeClaimsGetter: clientSet.CoreV1(),
|
||||
|
|
@ -138,7 +139,8 @@ func TestCreate(t *testing.T) {
|
|||
var cluster = New(
|
||||
Config{
|
||||
OpConfig: config.Config{
|
||||
PodManagementPolicy: "ordered_ready",
|
||||
PodManagementPolicy: "ordered_ready",
|
||||
PodTerminateGracePeriod: &metav1.Duration{Duration: 600 * time.Second},
|
||||
Resources: config.Resources{
|
||||
ClusterLabels: map[string]string{"application": "spilo"},
|
||||
ClusterNameLabel: "cluster-name",
|
||||
|
|
@ -147,8 +149,8 @@ func TestCreate(t *testing.T) {
|
|||
DefaultMemoryRequest: "300Mi",
|
||||
DefaultMemoryLimit: "300Mi",
|
||||
PodRoleLabel: "spilo-role",
|
||||
ResourceCheckInterval: time.Duration(3),
|
||||
ResourceCheckTimeout: time.Duration(10),
|
||||
ResourceCheckInterval: &metav1.Duration{Duration: 3 * time.Second},
|
||||
ResourceCheckTimeout: &metav1.Duration{Duration: 10 * time.Minute},
|
||||
},
|
||||
EnableFinalizers: util.True(),
|
||||
},
|
||||
|
|
@ -227,6 +229,63 @@ func TestStatefulSetUpdateWithEnv(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestStatefulSetUpdateWithEnvFrom(t *testing.T) {
|
||||
oldSpec := &acidv1.PostgresSpec{
|
||||
TeamID: "myapp", NumberOfInstances: 1,
|
||||
Resources: &acidv1.Resources{
|
||||
ResourceRequests: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("1"), Memory: k8sutil.StringToPointer("10")},
|
||||
ResourceLimits: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("1"), Memory: k8sutil.StringToPointer("10")},
|
||||
},
|
||||
Volume: acidv1.Volume{
|
||||
Size: "1G",
|
||||
},
|
||||
}
|
||||
oldSS, err := cl.generateStatefulSet(oldSpec)
|
||||
if err != nil {
|
||||
t.Errorf("in %s no StatefulSet created %v", t.Name(), err)
|
||||
}
|
||||
|
||||
newSpec := oldSpec.DeepCopy()
|
||||
newSS, err := cl.generateStatefulSet(newSpec)
|
||||
if err != nil {
|
||||
t.Errorf("in %s no StatefulSet created %v", t.Name(), err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(oldSS, newSS) {
|
||||
t.Errorf("in %s StatefulSet's must be equal", t.Name())
|
||||
}
|
||||
|
||||
newSpec.EnvFrom = []v1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "test-configmap",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
SecretRef: &v1.SecretEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "test-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
newSS, err = cl.generateStatefulSet(newSpec)
|
||||
if err != nil {
|
||||
t.Errorf("in %s no StatefulSet created %v", t.Name(), err)
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(oldSS, newSS) {
|
||||
t.Errorf("in %s StatefulSet's must be not equal", t.Name())
|
||||
}
|
||||
|
||||
postgresContainer := newSS.Spec.Template.Spec.Containers[0]
|
||||
if !reflect.DeepEqual(postgresContainer.EnvFrom, newSpec.EnvFrom) {
|
||||
t.Errorf("expected envFrom %v, got %v", newSpec.EnvFrom, postgresContainer.EnvFrom)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitRobotUsers(t *testing.T) {
|
||||
tests := []struct {
|
||||
testCase string
|
||||
|
|
@ -680,8 +739,7 @@ func TestServiceAnnotations(t *testing.T) {
|
|||
operatorAnnotations: make(map[string]string),
|
||||
serviceAnnotations: make(map[string]string),
|
||||
expect: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg.test.db.example.com,test-stg.acid.db.example.com",
|
||||
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg.test.db.example.com,test-stg.acid.db.example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -702,8 +760,7 @@ func TestServiceAnnotations(t *testing.T) {
|
|||
operatorAnnotations: make(map[string]string),
|
||||
serviceAnnotations: make(map[string]string),
|
||||
expect: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg.test.db.example.com,test-stg.acid.db.example.com",
|
||||
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg.test.db.example.com,test-stg.acid.db.example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -714,8 +771,7 @@ func TestServiceAnnotations(t *testing.T) {
|
|||
operatorAnnotations: make(map[string]string),
|
||||
serviceAnnotations: map[string]string{"foo": "bar"},
|
||||
expect: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg.test.db.example.com,test-stg.acid.db.example.com",
|
||||
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg.test.db.example.com,test-stg.acid.db.example.com",
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
|
|
@ -737,8 +793,7 @@ func TestServiceAnnotations(t *testing.T) {
|
|||
operatorAnnotations: map[string]string{"foo": "bar"},
|
||||
serviceAnnotations: make(map[string]string),
|
||||
expect: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg.test.db.example.com,test-stg.acid.db.example.com",
|
||||
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg.test.db.example.com,test-stg.acid.db.example.com",
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
|
|
@ -780,8 +835,7 @@ func TestServiceAnnotations(t *testing.T) {
|
|||
"external-dns.alpha.kubernetes.io/hostname": "wrong.external-dns-name.example.com",
|
||||
},
|
||||
expect: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg.test.db.example.com,test-stg.acid.db.example.com",
|
||||
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg.test.db.example.com,test-stg.acid.db.example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -792,8 +846,7 @@ func TestServiceAnnotations(t *testing.T) {
|
|||
serviceAnnotations: make(map[string]string),
|
||||
operatorAnnotations: make(map[string]string),
|
||||
expect: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg.test.db.example.com,test-stg.acid.db.example.com",
|
||||
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg.test.db.example.com,test-stg.acid.db.example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -835,8 +888,7 @@ func TestServiceAnnotations(t *testing.T) {
|
|||
operatorAnnotations: make(map[string]string),
|
||||
serviceAnnotations: make(map[string]string),
|
||||
expect: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg-repl.test.db.example.com,test-stg-repl.acid.db.example.com",
|
||||
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg-repl.test.db.example.com,test-stg-repl.acid.db.example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -857,8 +909,7 @@ func TestServiceAnnotations(t *testing.T) {
|
|||
operatorAnnotations: make(map[string]string),
|
||||
serviceAnnotations: make(map[string]string),
|
||||
expect: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg-repl.test.db.example.com,test-stg-repl.acid.db.example.com",
|
||||
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg-repl.test.db.example.com,test-stg-repl.acid.db.example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -869,8 +920,7 @@ func TestServiceAnnotations(t *testing.T) {
|
|||
operatorAnnotations: make(map[string]string),
|
||||
serviceAnnotations: map[string]string{"foo": "bar"},
|
||||
expect: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg-repl.test.db.example.com,test-stg-repl.acid.db.example.com",
|
||||
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg-repl.test.db.example.com,test-stg-repl.acid.db.example.com",
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
|
|
@ -892,8 +942,7 @@ func TestServiceAnnotations(t *testing.T) {
|
|||
operatorAnnotations: map[string]string{"foo": "bar"},
|
||||
serviceAnnotations: make(map[string]string),
|
||||
expect: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg-repl.test.db.example.com,test-stg-repl.acid.db.example.com",
|
||||
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg-repl.test.db.example.com,test-stg-repl.acid.db.example.com",
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
|
|
@ -935,8 +984,7 @@ func TestServiceAnnotations(t *testing.T) {
|
|||
"external-dns.alpha.kubernetes.io/hostname": "wrong.external-dns-name.example.com",
|
||||
},
|
||||
expect: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg-repl.test.db.example.com,test-stg-repl.acid.db.example.com",
|
||||
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg-repl.test.db.example.com,test-stg-repl.acid.db.example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -947,8 +995,7 @@ func TestServiceAnnotations(t *testing.T) {
|
|||
serviceAnnotations: make(map[string]string),
|
||||
operatorAnnotations: make(map[string]string),
|
||||
expect: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg-repl.test.db.example.com,test-stg-repl.acid.db.example.com",
|
||||
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
|
||||
"external-dns.alpha.kubernetes.io/hostname": "acid-test-stg-repl.test.db.example.com,test-stg-repl.acid.db.example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -1198,11 +1245,11 @@ func TestCompareSpiloConfiguration(t *testing.T) {
|
|||
ExpectedResult bool
|
||||
}{
|
||||
{
|
||||
`{"postgresql":{"bin_dir":"/usr/lib/postgresql/12/bin","parameters":{"autovacuum_analyze_scale_factor":"0.1"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"]},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"dcs":{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"postgresql":{"parameters":{"max_connections":"100","max_locks_per_transaction":"64","max_worker_processes":"4"}}}}}`,
|
||||
`{"postgresql":{"bin_dir":"/usr/lib/postgresql/12/bin","parameters":{"autovacuum_analyze_scale_factor":"0.1"},"pg_hba":["hostssl all all 0.0.0.0/0 scram-sha-256","host all all 0.0.0.0/0 scram-sha-256"]},"bootstrap":{"initdb":[{"auth-host":"scram-sha-256"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"dcs":{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"postgresql":{"parameters":{"max_connections":"100","max_locks_per_transaction":"64","max_worker_processes":"4"}}}}}`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
`{"postgresql":{"bin_dir":"/usr/lib/postgresql/12/bin","parameters":{"autovacuum_analyze_scale_factor":"0.1"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"]},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"dcs":{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"postgresql":{"parameters":{"max_connections":"200","max_locks_per_transaction":"64","max_worker_processes":"4"}}}}}`,
|
||||
`{"postgresql":{"bin_dir":"/usr/lib/postgresql/12/bin","parameters":{"autovacuum_analyze_scale_factor":"0.1"},"pg_hba":["hostssl all all 0.0.0.0/0 scram-sha-256","host all all 0.0.0.0/0 scram-sha-256"]},"bootstrap":{"initdb":[{"auth-host":"scram-sha-256"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"dcs":{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"postgresql":{"parameters":{"max_connections":"200","max_locks_per_transaction":"64","max_worker_processes":"4"}}}}}`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
|
|
@ -1346,7 +1393,8 @@ func newService(
|
|||
svcType v1.ServiceType,
|
||||
sourceRanges []string,
|
||||
selector map[string]string,
|
||||
policy v1.ServiceExternalTrafficPolicyType) *v1.Service {
|
||||
policy v1.ServiceExternalTrafficPolicyType,
|
||||
nodePort *int32) *v1.Service {
|
||||
svc := &v1.Service{
|
||||
Spec: v1.ServiceSpec{
|
||||
Selector: selector,
|
||||
|
|
@ -1356,6 +1404,16 @@ func newService(
|
|||
},
|
||||
}
|
||||
svc.Annotations = annotations
|
||||
|
||||
if nodePort != nil {
|
||||
svc.Spec.Ports = []v1.ServicePort{
|
||||
{
|
||||
Name: "port",
|
||||
NodePort: *nodePort,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
|
|
@ -1377,12 +1435,12 @@ func TestCompareServices(t *testing.T) {
|
|||
serviceWithOwnerReference := newService(
|
||||
map[string]string{
|
||||
constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
|
||||
constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
|
||||
},
|
||||
v1.ServiceTypeClusterIP,
|
||||
[]string{"128.141.0.0/16", "137.138.0.0/16"},
|
||||
nil,
|
||||
defaultPolicy,
|
||||
nil,
|
||||
)
|
||||
|
||||
ownerRef := metav1.OwnerReference{
|
||||
|
|
@ -1394,6 +1452,9 @@ func TestCompareServices(t *testing.T) {
|
|||
|
||||
serviceWithOwnerReference.ObjectMeta.OwnerReferences = append(serviceWithOwnerReference.ObjectMeta.OwnerReferences, ownerRef)
|
||||
|
||||
portZero := int32(0)
|
||||
portNotZero := int32(1337)
|
||||
|
||||
tests := []struct {
|
||||
about string
|
||||
current *v1.Service
|
||||
|
|
@ -1406,19 +1467,17 @@ func TestCompareServices(t *testing.T) {
|
|||
current: newService(
|
||||
map[string]string{
|
||||
constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
|
||||
constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
|
||||
},
|
||||
v1.ServiceTypeClusterIP,
|
||||
[]string{"128.141.0.0/16", "137.138.0.0/16"},
|
||||
nil, defaultPolicy),
|
||||
nil, defaultPolicy, nil),
|
||||
new: newService(
|
||||
map[string]string{
|
||||
constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
|
||||
constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
|
||||
},
|
||||
v1.ServiceTypeClusterIP,
|
||||
[]string{"128.141.0.0/16", "137.138.0.0/16"},
|
||||
nil, defaultPolicy),
|
||||
nil, defaultPolicy, nil),
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
|
|
@ -1426,19 +1485,17 @@ func TestCompareServices(t *testing.T) {
|
|||
current: newService(
|
||||
map[string]string{
|
||||
constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
|
||||
constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
|
||||
},
|
||||
v1.ServiceTypeClusterIP,
|
||||
[]string{"128.141.0.0/16", "137.138.0.0/16"},
|
||||
nil, defaultPolicy),
|
||||
nil, defaultPolicy, nil),
|
||||
new: newService(
|
||||
map[string]string{
|
||||
constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
|
||||
constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
|
||||
},
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
[]string{"128.141.0.0/16", "137.138.0.0/16"},
|
||||
nil, defaultPolicy),
|
||||
nil, defaultPolicy, nil),
|
||||
match: false,
|
||||
reason: `new service's type "LoadBalancer" does not match the current one "ClusterIP"`,
|
||||
},
|
||||
|
|
@ -1447,19 +1504,17 @@ func TestCompareServices(t *testing.T) {
|
|||
current: newService(
|
||||
map[string]string{
|
||||
constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
|
||||
constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
|
||||
},
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
[]string{"128.141.0.0/16", "137.138.0.0/16"},
|
||||
nil, defaultPolicy),
|
||||
nil, defaultPolicy, nil),
|
||||
new: newService(
|
||||
map[string]string{
|
||||
constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
|
||||
constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
|
||||
},
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
[]string{"185.249.56.0/22"},
|
||||
nil, defaultPolicy),
|
||||
nil, defaultPolicy, nil),
|
||||
match: false,
|
||||
reason: `new service's LoadBalancerSourceRange does not match the current one`,
|
||||
},
|
||||
|
|
@ -1468,19 +1523,17 @@ func TestCompareServices(t *testing.T) {
|
|||
current: newService(
|
||||
map[string]string{
|
||||
constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
|
||||
constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
|
||||
},
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
[]string{"128.141.0.0/16", "137.138.0.0/16"},
|
||||
nil, defaultPolicy),
|
||||
nil, defaultPolicy, nil),
|
||||
new: newService(
|
||||
map[string]string{
|
||||
constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
|
||||
constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
|
||||
},
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
[]string{},
|
||||
nil, defaultPolicy),
|
||||
nil, defaultPolicy, nil),
|
||||
match: false,
|
||||
reason: `new service's LoadBalancerSourceRange does not match the current one`,
|
||||
},
|
||||
|
|
@ -1489,11 +1542,10 @@ func TestCompareServices(t *testing.T) {
|
|||
current: newService(
|
||||
map[string]string{
|
||||
constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
|
||||
constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
|
||||
},
|
||||
v1.ServiceTypeClusterIP,
|
||||
[]string{"128.141.0.0/16", "137.138.0.0/16"},
|
||||
nil, defaultPolicy),
|
||||
nil, defaultPolicy, nil),
|
||||
new: serviceWithOwnerReference,
|
||||
match: false,
|
||||
},
|
||||
|
|
@ -1503,12 +1555,12 @@ func TestCompareServices(t *testing.T) {
|
|||
map[string]string{},
|
||||
v1.ServiceTypeClusterIP,
|
||||
[]string{},
|
||||
nil, defaultPolicy),
|
||||
nil, defaultPolicy, nil),
|
||||
new: newService(
|
||||
map[string]string{},
|
||||
v1.ServiceTypeClusterIP,
|
||||
[]string{},
|
||||
map[string]string{"cluster-name": "clstr", "spilo-role": "master"}, defaultPolicy),
|
||||
map[string]string{"cluster-name": "clstr", "spilo-role": "master"}, defaultPolicy, nil),
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
|
|
@ -1517,14 +1569,42 @@ func TestCompareServices(t *testing.T) {
|
|||
map[string]string{},
|
||||
v1.ServiceTypeClusterIP,
|
||||
[]string{},
|
||||
nil, defaultPolicy),
|
||||
nil, defaultPolicy, nil),
|
||||
new: newService(
|
||||
map[string]string{},
|
||||
v1.ServiceTypeClusterIP,
|
||||
[]string{},
|
||||
nil, v1.ServiceExternalTrafficPolicyTypeLocal),
|
||||
nil, v1.ServiceExternalTrafficPolicyTypeLocal, nil),
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
about: "services differ on node port",
|
||||
current: newService(
|
||||
map[string]string{},
|
||||
v1.ServiceTypeNodePort,
|
||||
[]string{},
|
||||
nil, defaultPolicy, &portZero),
|
||||
new: newService(
|
||||
map[string]string{},
|
||||
v1.ServiceTypeNodePort,
|
||||
[]string{},
|
||||
nil, defaultPolicy, &portNotZero),
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
about: "services do not differ on node port when requesting 0",
|
||||
current: newService(
|
||||
map[string]string{},
|
||||
v1.ServiceTypeNodePort,
|
||||
[]string{},
|
||||
nil, defaultPolicy, &portNotZero),
|
||||
new: newService(
|
||||
map[string]string{},
|
||||
v1.ServiceTypeNodePort,
|
||||
[]string{},
|
||||
nil, defaultPolicy, &portZero),
|
||||
match: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
@ -1546,12 +1626,21 @@ func TestCompareServices(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
var (
|
||||
defaultSuccessfulJobsHistoryLimit = int32(3)
|
||||
defaultFailedJobsHistoryLimit = int32(3)
|
||||
defaultTTLSecondsAfterFinished = int32(86400)
|
||||
)
|
||||
|
||||
func newCronJob(image, schedule string, vars []v1.EnvVar, mounts []v1.VolumeMount) *batchv1.CronJob {
|
||||
cron := &batchv1.CronJob{
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: schedule,
|
||||
Schedule: schedule,
|
||||
SuccessfulJobsHistoryLimit: &defaultSuccessfulJobsHistoryLimit,
|
||||
FailedJobsHistoryLimit: &defaultFailedJobsHistoryLimit,
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
Spec: batchv1.JobSpec{
|
||||
TTLSecondsAfterFinished: &defaultTTLSecondsAfterFinished,
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
|
|
@ -1603,8 +1692,8 @@ func newCronJob(image, schedule string, vars []v1.EnvVar, mounts []v1.VolumeMoun
|
|||
|
||||
func TestCompareLogicalBackupJob(t *testing.T) {
|
||||
|
||||
img1 := "registry.opensource.zalan.do/acid/logical-backup:v1.0"
|
||||
img2 := "registry.opensource.zalan.do/acid/logical-backup:v2.0"
|
||||
img1 := "ghcr.io/zalando/postgres-operator/logical-backup:v1.15.1"
|
||||
img2 := "ghcr.io/zalando/postgres-operator/logical-backup:v2.0.0"
|
||||
|
||||
clientSet := fake.NewSimpleClientset()
|
||||
acidClientSet := fakeacidv1.NewSimpleClientset()
|
||||
|
|
@ -2125,10 +2214,13 @@ func TestGetSwitchoverSchedule(t *testing.T) {
|
|||
pastWindowTimeStart := pastTimeStart.Format("15:04")
|
||||
pastWindowTimeEnd := now.Add(-1 * time.Hour).Format("15:04")
|
||||
|
||||
defaultWindowStr := fmt.Sprintf("%s-%s", futureWindowTimeStart, futureWindowTimeEnd)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
windows []acidv1.MaintenanceWindow
|
||||
expected string
|
||||
name string
|
||||
windows []acidv1.MaintenanceWindow
|
||||
defaultWindows []string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "everyday maintenance windows is later today",
|
||||
|
|
@ -2190,11 +2282,40 @@ func TestGetSwitchoverSchedule(t *testing.T) {
|
|||
},
|
||||
expected: pastTimeStart.AddDate(0, 0, 1).Format("2006-01-02T15:04+00"),
|
||||
},
|
||||
{
|
||||
name: "fallback to operator default window when spec is empty",
|
||||
windows: []acidv1.MaintenanceWindow{},
|
||||
defaultWindows: []string{defaultWindowStr},
|
||||
expected: futureTimeStart.Format("2006-01-02T15:04+00"),
|
||||
},
|
||||
{
|
||||
name: "no windows defined returns empty string",
|
||||
windows: []acidv1.MaintenanceWindow{},
|
||||
defaultWindows: nil,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "choose the earliest window from multiple in spec",
|
||||
windows: []acidv1.MaintenanceWindow{
|
||||
{
|
||||
Weekday: now.AddDate(0, 0, 2).Weekday(),
|
||||
StartTime: mustParseTime(futureWindowTimeStart),
|
||||
EndTime: mustParseTime(futureWindowTimeEnd),
|
||||
},
|
||||
{
|
||||
Weekday: now.AddDate(0, 0, 1).Weekday(),
|
||||
StartTime: mustParseTime(pastWindowTimeStart),
|
||||
EndTime: mustParseTime(pastWindowTimeEnd),
|
||||
},
|
||||
},
|
||||
expected: pastTimeStart.AddDate(0, 0, 1).Format("2006-01-02T15:04+00"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cluster.Spec.MaintenanceWindows = tt.windows
|
||||
cluster.OpConfig.MaintenanceWindows = tt.defaultWindows
|
||||
schedule := cluster.getSwitchoverScheduleAtTime(now)
|
||||
if schedule != tt.expected {
|
||||
t.Errorf("Expected GetSwitchoverSchedule to return %s, returned: %s", tt.expected, schedule)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ var poolerRunAsGroup = int64(101)
|
|||
|
||||
// ConnectionPoolerObjects K8s objects that are belong to connection pooler
|
||||
type ConnectionPoolerObjects struct {
|
||||
AuthSecret *v1.Secret
|
||||
Deployment *appsv1.Deployment
|
||||
Service *v1.Service
|
||||
Name string
|
||||
|
|
@ -167,6 +168,38 @@ func (c *Cluster) createConnectionPooler(LookupFunction InstallFunction) (SyncRe
|
|||
return reason, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) generateUserlist() string {
|
||||
var sb strings.Builder
|
||||
|
||||
poolerAdminUser := c.systemUsers[constants.ConnectionPoolerUserKeyName]
|
||||
fmt.Fprintf(&sb, "\"%s\" \"%s\"\n", poolerAdminUser.Name, poolerAdminUser.Password)
|
||||
|
||||
for roleName, infraRole := range c.InfrastructureRoles {
|
||||
if infraRole.Password != "" {
|
||||
fmt.Fprintf(&sb, "\"%s\" \"%s\"\n", roleName, infraRole.Password)
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (c *Cluster) generateConnectionPoolerAuthSecret(connectionPooler *ConnectionPoolerObjects) *v1.Secret {
|
||||
return &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: c.connectionPoolerLabels(connectionPooler.Role, true).MatchLabels,
|
||||
Name: fmt.Sprintf("%s-u", connectionPooler.Name),
|
||||
Namespace: connectionPooler.Namespace,
|
||||
Annotations: c.annotationsSet(nil),
|
||||
OwnerReferences: c.ownerReferences(),
|
||||
},
|
||||
Type: v1.SecretTypeOpaque,
|
||||
// Secret data must be bytes. Kubernetes handles the encoding.
|
||||
StringData: map[string]string{
|
||||
"userlist.txt": c.generateUserlist(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Generate pool size related environment variables.
|
||||
//
|
||||
// MAX_DB_CONN would specify the global maximum for connections to a target
|
||||
|
|
@ -259,7 +292,7 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) (
|
|||
if connectionPoolerSpec == nil {
|
||||
connectionPoolerSpec = &acidv1.ConnectionPooler{}
|
||||
}
|
||||
gracePeriod := int64(c.OpConfig.PodTerminateGracePeriod.Seconds())
|
||||
gracePeriod := int64(util.CoalesceDuration(c.OpConfig.PodTerminateGracePeriod, "5m").Seconds())
|
||||
resources, err := c.generateResourceRequirements(
|
||||
connectionPoolerSpec.Resources,
|
||||
makeDefaultConnectionPoolerResources(&c.OpConfig),
|
||||
|
|
@ -320,6 +353,18 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) (
|
|||
}
|
||||
envVars = append(envVars, c.getConnectionPoolerEnvVars()...)
|
||||
|
||||
infraRolesList := make([]string, 0)
|
||||
for infraRoleName := range c.InfrastructureRoles {
|
||||
infraRolesList = append(infraRolesList, infraRoleName)
|
||||
}
|
||||
|
||||
if len(infraRolesList) > 0 {
|
||||
envVars = append(envVars, v1.EnvVar{
|
||||
Name: "INFRASTRUCTURE_ROLES",
|
||||
Value: strings.Join(infraRolesList, ","),
|
||||
})
|
||||
}
|
||||
|
||||
poolerContainer := v1.Container{
|
||||
Name: connectionPoolerContainer,
|
||||
Image: effectiveDockerImage,
|
||||
|
|
@ -343,12 +388,29 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) (
|
|||
},
|
||||
}
|
||||
|
||||
var poolerVolumes []v1.Volume
|
||||
var volumeMounts []v1.VolumeMount
|
||||
|
||||
// mount secret volume with userlist.txt for pgBouncer to authenticate users
|
||||
poolerVolumes = append(poolerVolumes, v1.Volume{
|
||||
Name: fmt.Sprintf("%s-u", c.connectionPoolerName(role)),
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Secret: &v1.SecretVolumeSource{
|
||||
SecretName: fmt.Sprintf("%s-u", c.connectionPoolerName(role)),
|
||||
},
|
||||
},
|
||||
})
|
||||
volumeMounts = append(volumeMounts, v1.VolumeMount{
|
||||
Name: fmt.Sprintf("%s-u", c.connectionPoolerName(role)),
|
||||
MountPath: "/etc/pgbouncer/userlist.txt",
|
||||
SubPath: "userlist.txt",
|
||||
ReadOnly: true,
|
||||
})
|
||||
|
||||
// If the cluster has custom TLS certificates configured, we do the following:
|
||||
// 1. Add environment variables to tell pgBouncer where to find the TLS certificates
|
||||
// 2. Reference the secret in a volume
|
||||
// 3. Mount the volume to the container at /tls
|
||||
var poolerVolumes []v1.Volume
|
||||
var volumeMounts []v1.VolumeMount
|
||||
if spec.TLS != nil && spec.TLS.SecretName != "" {
|
||||
getPoolerTLSEnv := func(k string) string {
|
||||
keyName := ""
|
||||
|
|
@ -504,7 +566,9 @@ func (c *Cluster) generateConnectionPoolerService(connectionPooler *ConnectionPo
|
|||
},
|
||||
}
|
||||
|
||||
if c.shouldCreateLoadBalancerForPoolerService(poolerRole, spec) {
|
||||
if ok, port := c.shouldCreateNodePortForPoolerService(poolerRole, spec); ok {
|
||||
c.configureNodePortService(&serviceSpec, port)
|
||||
} else if c.shouldCreateLoadBalancerForPoolerService(poolerRole, spec) {
|
||||
c.configureLoadBalanceService(&serviceSpec, spec.AllowedSourceRanges)
|
||||
}
|
||||
|
||||
|
|
@ -532,11 +596,9 @@ func (c *Cluster) generatePoolerServiceAnnotations(role PostgresRole, spec *acid
|
|||
var dnsString string
|
||||
annotations := c.getCustomServiceAnnotations(role, spec)
|
||||
|
||||
if c.shouldCreateLoadBalancerForPoolerService(role, spec) {
|
||||
// set ELB Timeout annotation with default value
|
||||
if _, ok := annotations[constants.ElbTimeoutAnnotationName]; !ok {
|
||||
annotations[constants.ElbTimeoutAnnotationName] = constants.ElbTimeoutAnnotationValue
|
||||
}
|
||||
nodePort, _ := c.shouldCreateNodePortForPoolerService(role, spec)
|
||||
|
||||
if !nodePort && c.shouldCreateLoadBalancerForPoolerService(role, spec) {
|
||||
// -repl suffix will be added by replicaDNSName
|
||||
clusterNameWithPoolerSuffix := c.connectionPoolerName(Master)
|
||||
if role == Master {
|
||||
|
|
@ -577,6 +639,37 @@ func (c *Cluster) shouldCreateLoadBalancerForPoolerService(role PostgresRole, sp
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Cluster) shouldCreateNodePortForPoolerService(role PostgresRole, spec *acidv1.PostgresSpec) (bool, int32) {
|
||||
switch role {
|
||||
case Replica:
|
||||
// if the value is explicitly set in a Postgresql manifest, follow this setting
|
||||
if spec.EnableReplicaPoolerNodePort != nil {
|
||||
port := int32(0)
|
||||
if spec.ReplicaPoolerNodePort != nil {
|
||||
port = *spec.ReplicaPoolerNodePort
|
||||
}
|
||||
|
||||
return *spec.EnableReplicaPoolerNodePort, port
|
||||
}
|
||||
|
||||
// otherwise, follow the operator configuration
|
||||
return c.OpConfig.EnableReplicaPoolerNodePort, 0
|
||||
case Master:
|
||||
if spec.EnableMasterPoolerNodePort != nil {
|
||||
port := int32(0)
|
||||
if spec.MasterPoolerNodePort != nil {
|
||||
port = *spec.MasterPoolerNodePort
|
||||
}
|
||||
|
||||
return *spec.EnableMasterPoolerNodePort, port
|
||||
}
|
||||
|
||||
return c.OpConfig.EnableMasterPoolerNodePort, 0
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown role %v", role))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cluster) listPoolerPods(listOptions metav1.ListOptions) ([]v1.Pod, error) {
|
||||
pods, err := c.KubeClient.Pods(c.Namespace).List(context.TODO(), listOptions)
|
||||
if err != nil {
|
||||
|
|
@ -639,12 +732,31 @@ func (c *Cluster) deleteConnectionPooler(role PostgresRole) (err error) {
|
|||
c.logger.Infof("connection pooler service %s has been deleted for role %s", service.Name, role)
|
||||
}
|
||||
|
||||
// Repeat the same for the auth secret
|
||||
authSecret := c.ConnectionPooler[role].AuthSecret
|
||||
if authSecret == nil {
|
||||
c.logger.Debug("no connection pooler auth secret to delete")
|
||||
} else {
|
||||
err := c.KubeClient.
|
||||
Secrets(c.Namespace).
|
||||
Delete(context.TODO(), authSecret.Name, metav1.DeleteOptions{})
|
||||
|
||||
if k8sutil.ResourceNotFound(err) {
|
||||
c.logger.Debugf("connection pooler auth secret %s for role %s has already been deleted", authSecret.Name, role)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("could not delete connection pooler auth secret: %v", err)
|
||||
}
|
||||
|
||||
c.logger.Infof("connection pooler auth secret %s has been deleted for role %s", authSecret.Name, role)
|
||||
}
|
||||
|
||||
c.ConnectionPooler[role].AuthSecret = nil
|
||||
c.ConnectionPooler[role].Deployment = nil
|
||||
c.ConnectionPooler[role].Service = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// delete connection pooler
|
||||
// delete connection pooler secret
|
||||
func (c *Cluster) deleteConnectionPoolerSecret() (err error) {
|
||||
// Repeat the same for the secret object
|
||||
secretName := c.credentialSecretName(c.OpConfig.ConnectionPooler.User)
|
||||
|
|
@ -660,6 +772,7 @@ func (c *Cluster) deleteConnectionPoolerSecret() (err error) {
|
|||
return fmt.Errorf("could not delete pooler secret: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -912,7 +1025,7 @@ func (c *Cluster) syncConnectionPooler(oldSpec, newSpec *acidv1.Postgresql, Look
|
|||
|
||||
// in this case also do not forget to install lookup function
|
||||
// skip installation in standby clusters, since they are read-only
|
||||
if !c.ConnectionPooler[role].LookupFunction && c.Spec.StandbyCluster == nil {
|
||||
if !c.ConnectionPooler[role].LookupFunction && !isStandbyCluster(&newSpec.Spec) {
|
||||
connectionPooler := c.Spec.ConnectionPooler
|
||||
specSchema := ""
|
||||
specUser := ""
|
||||
|
|
@ -975,11 +1088,42 @@ func (c *Cluster) syncConnectionPoolerWorker(oldSpec, newSpec *acidv1.Postgresql
|
|||
pods []v1.Pod
|
||||
service *v1.Service
|
||||
newService *v1.Service
|
||||
authSecret *v1.Secret
|
||||
newAuthSecret *v1.Secret
|
||||
err error
|
||||
)
|
||||
|
||||
updatedPodAnnotations := map[string]*string{}
|
||||
syncReason := make([]string, 0)
|
||||
|
||||
// create extra secret for connection pooler authentication
|
||||
newAuthSecret = c.generateConnectionPoolerAuthSecret(c.ConnectionPooler[role])
|
||||
if authSecret, err = c.KubeClient.Secrets(c.Namespace).Get(context.TODO(), fmt.Sprintf("%s-u", c.connectionPoolerName(role)), metav1.GetOptions{}); err == nil {
|
||||
c.ConnectionPooler[role].AuthSecret = authSecret
|
||||
// make sure existing annotations are preserved
|
||||
newAuthSecret.Annotations = c.annotationsSet(authSecret.Annotations)
|
||||
authSecret, err = c.KubeClient.Secrets(authSecret.Namespace).Update(context.TODO(), newAuthSecret, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return NoSync, fmt.Errorf("could not update connection pooler auth secret: %v", err)
|
||||
}
|
||||
c.ConnectionPooler[role].AuthSecret = authSecret
|
||||
} else if !k8sutil.ResourceNotFound(err) {
|
||||
return NoSync, fmt.Errorf("could not get auth secret for connection pooler to sync: %v", err)
|
||||
}
|
||||
|
||||
if k8sutil.ResourceNotFound(err) {
|
||||
c.logger.Warningf("auth secret %s for connection pooler is not found, create it", fmt.Sprintf("%s-u", c.connectionPoolerName(role)))
|
||||
authSecret, err = c.KubeClient.
|
||||
Secrets(newAuthSecret.Namespace).
|
||||
Create(context.TODO(), newAuthSecret, metav1.CreateOptions{})
|
||||
|
||||
if err != nil {
|
||||
return NoSync, err
|
||||
}
|
||||
c.ConnectionPooler[role].AuthSecret = authSecret
|
||||
}
|
||||
|
||||
// next the pooler deployment
|
||||
deployment, err = c.KubeClient.
|
||||
Deployments(c.Namespace).
|
||||
Get(context.TODO(), c.connectionPoolerName(role), metav1.GetOptions{})
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue