merge with master and resolve conflicts
This commit is contained in:
		
						commit
						1a8862e545
					
				
							
								
								
									
										2
									
								
								LICENSE
								
								
								
								
							
							
						
						
									
										2
									
								
								LICENSE
								
								
								
								
							| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
The MIT License (MIT)
 | 
					The MIT License (MIT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Copyright (c) 2019 Zalando SE
 | 
					Copyright (c) 2020 Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					# Patterns to ignore when building packages.
 | 
				
			||||||
 | 
					# This supports shell glob matching, relative path matching, and
 | 
				
			||||||
 | 
					# negation (prefixed with !). Only one pattern per line.
 | 
				
			||||||
 | 
					.DS_Store
 | 
				
			||||||
 | 
					# Common VCS dirs
 | 
				
			||||||
 | 
					.git/
 | 
				
			||||||
 | 
					.gitignore
 | 
				
			||||||
 | 
					.bzr/
 | 
				
			||||||
 | 
					.bzrignore
 | 
				
			||||||
 | 
					.hg/
 | 
				
			||||||
 | 
					.hgignore
 | 
				
			||||||
 | 
					.svn/
 | 
				
			||||||
 | 
					# Common backup files
 | 
				
			||||||
 | 
					*.swp
 | 
				
			||||||
 | 
					*.bak
 | 
				
			||||||
 | 
					*.tmp
 | 
				
			||||||
 | 
					*~
 | 
				
			||||||
 | 
					# Various IDEs
 | 
				
			||||||
 | 
					.project
 | 
				
			||||||
 | 
					.idea/
 | 
				
			||||||
 | 
					*.tmproj
 | 
				
			||||||
 | 
					.vscode/
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					apiVersion: v1
 | 
				
			||||||
 | 
					name: postgres-operator-ui
 | 
				
			||||||
 | 
					version: 0.1.0
 | 
				
			||||||
 | 
					appVersion: 1.3.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:
 | 
				
			||||||
 | 
					- postgres
 | 
				
			||||||
 | 
					- operator
 | 
				
			||||||
 | 
					- ui
 | 
				
			||||||
 | 
					- cloud-native
 | 
				
			||||||
 | 
					- patroni
 | 
				
			||||||
 | 
					- spilo
 | 
				
			||||||
 | 
					maintainers:
 | 
				
			||||||
 | 
					- name: Zalando
 | 
				
			||||||
 | 
					  email: opensource@zalando.de
 | 
				
			||||||
 | 
					- name: siku4
 | 
				
			||||||
 | 
					  email: sk@sik-net.de
 | 
				
			||||||
 | 
					sources:
 | 
				
			||||||
 | 
					- https://github.com/zalando/postgres-operator
 | 
				
			||||||
 | 
					engine: gotpl
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					To verify that postgres-operator has started, run:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  kubectl --namespace={{ .Release.Namespace }} get pods -l "app.kubernetes.io/name={{ template "postgres-operator-ui.name" . }}"
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					{{/* vim: set filetype=mustache: */}}
 | 
				
			||||||
 | 
					{{/*
 | 
				
			||||||
 | 
					Expand the name of the chart.
 | 
				
			||||||
 | 
					*/}}
 | 
				
			||||||
 | 
					{{- define "postgres-operator-ui.name" -}}
 | 
				
			||||||
 | 
					{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
 | 
				
			||||||
 | 
					{{- end -}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{/*
 | 
				
			||||||
 | 
					Create a default fully qualified app name.
 | 
				
			||||||
 | 
					We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
 | 
				
			||||||
 | 
					If release name contains chart name it will be used as a full name.
 | 
				
			||||||
 | 
					*/}}
 | 
				
			||||||
 | 
					{{- define "postgres-operator-ui.fullname" -}}
 | 
				
			||||||
 | 
					{{- if .Values.fullnameOverride -}}
 | 
				
			||||||
 | 
					{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
 | 
				
			||||||
 | 
					{{- else -}}
 | 
				
			||||||
 | 
					{{- $name := default .Chart.Name .Values.nameOverride -}}
 | 
				
			||||||
 | 
					{{- if contains $name .Release.Name -}}
 | 
				
			||||||
 | 
					{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
 | 
				
			||||||
 | 
					{{- else -}}
 | 
				
			||||||
 | 
					{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
 | 
				
			||||||
 | 
					{{- end -}}
 | 
				
			||||||
 | 
					{{- end -}}
 | 
				
			||||||
 | 
					{{- end -}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{/*
 | 
				
			||||||
 | 
					Create a service account name.
 | 
				
			||||||
 | 
					*/}}
 | 
				
			||||||
 | 
					{{- define "postgres-operator-ui.serviceAccountName" -}}
 | 
				
			||||||
 | 
					{{ default (include "postgres-operator-ui.fullname" .) .Values.serviceAccount.name }}
 | 
				
			||||||
 | 
					{{- end -}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{/*
 | 
				
			||||||
 | 
					Create chart name and version as used by the chart label.
 | 
				
			||||||
 | 
					*/}}
 | 
				
			||||||
 | 
					{{- define "postgres-operator-ui.chart" -}}
 | 
				
			||||||
 | 
					{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
 | 
				
			||||||
 | 
					{{- end -}}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,52 @@
 | 
				
			||||||
 | 
					{{ if .Values.rbac.create }}
 | 
				
			||||||
 | 
					apiVersion: rbac.authorization.k8s.io/v1
 | 
				
			||||||
 | 
					kind: ClusterRole
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  name: {{ include "postgres-operator-ui.serviceAccountName" . }}
 | 
				
			||||||
 | 
					  labels:
 | 
				
			||||||
 | 
					    app.kubernetes.io/name: {{ template "postgres-operator-ui.name" . }}
 | 
				
			||||||
 | 
					    helm.sh/chart: {{ template "postgres-operator-ui.chart" . }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
				
			||||||
 | 
					rules:
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - acid.zalan.do
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - postgresqls
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - create
 | 
				
			||||||
 | 
					  - delete
 | 
				
			||||||
 | 
					  - get
 | 
				
			||||||
 | 
					  - list
 | 
				
			||||||
 | 
					  - patch
 | 
				
			||||||
 | 
					  - update
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - ""
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - pods
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - get
 | 
				
			||||||
 | 
					  - list
 | 
				
			||||||
 | 
					  - watch
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - ""
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - services
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - get
 | 
				
			||||||
 | 
					  - list
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - apps
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - statefulsets
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - get
 | 
				
			||||||
 | 
					  - list
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - ""
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - namespaces
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - get
 | 
				
			||||||
 | 
					  - list
 | 
				
			||||||
 | 
					{{ end }}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,19 @@
 | 
				
			||||||
 | 
					{{ if .Values.rbac.create }}
 | 
				
			||||||
 | 
					apiVersion: rbac.authorization.k8s.io/v1
 | 
				
			||||||
 | 
					kind: ClusterRoleBinding
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  name: {{ include "postgres-operator-ui.serviceAccountName" . }}
 | 
				
			||||||
 | 
					  labels:
 | 
				
			||||||
 | 
					    app.kubernetes.io/name: {{ template "postgres-operator-ui.name" . }}
 | 
				
			||||||
 | 
					    helm.sh/chart: {{ template "postgres-operator-ui.chart" . }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
				
			||||||
 | 
					roleRef:
 | 
				
			||||||
 | 
					  apiGroup: rbac.authorization.k8s.io
 | 
				
			||||||
 | 
					  kind: ClusterRole
 | 
				
			||||||
 | 
					  name: {{ include "postgres-operator-ui.serviceAccountName" . }}
 | 
				
			||||||
 | 
					subjects:
 | 
				
			||||||
 | 
					- kind: ServiceAccount
 | 
				
			||||||
 | 
					  name: {{ include "postgres-operator-ui.serviceAccountName" . }}
 | 
				
			||||||
 | 
					  namespace: {{ .Release.Namespace }}
 | 
				
			||||||
 | 
					{{ end }}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,69 @@
 | 
				
			||||||
 | 
					apiVersion: "apps/v1"
 | 
				
			||||||
 | 
					kind: "Deployment"
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  labels:
 | 
				
			||||||
 | 
					    app.kubernetes.io/name: {{ template "postgres-operator-ui.name" . }}
 | 
				
			||||||
 | 
					    helm.sh/chart: {{ template "postgres-operator-ui.chart" . }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
				
			||||||
 | 
					  name: {{ template "postgres-operator-ui.fullname" . }}
 | 
				
			||||||
 | 
					spec:
 | 
				
			||||||
 | 
					  replicas: 1
 | 
				
			||||||
 | 
					  selector:
 | 
				
			||||||
 | 
					    matchLabels:
 | 
				
			||||||
 | 
					      app.kubernetes.io/name: {{ template "postgres-operator-ui.name" . }}
 | 
				
			||||||
 | 
					      app.kubernetes.io/instance: {{ .Release.Name }}
 | 
				
			||||||
 | 
					  template:
 | 
				
			||||||
 | 
					    metadata:
 | 
				
			||||||
 | 
					      labels:
 | 
				
			||||||
 | 
					        app.kubernetes.io/name: {{ template "postgres-operator-ui.name" . }}
 | 
				
			||||||
 | 
					        app.kubernetes.io/instance: {{ .Release.Name }}
 | 
				
			||||||
 | 
					        team: "acid" # Parameterize?
 | 
				
			||||||
 | 
					    spec:
 | 
				
			||||||
 | 
					      serviceAccountName: {{ include "postgres-operator-ui.serviceAccountName" . }}
 | 
				
			||||||
 | 
					      containers:
 | 
				
			||||||
 | 
					        - name: "service"
 | 
				
			||||||
 | 
					          image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}"
 | 
				
			||||||
 | 
					          imagePullPolicy: {{ .Values.image.pullPolicy }}
 | 
				
			||||||
 | 
					          ports:
 | 
				
			||||||
 | 
					            - containerPort: 8081
 | 
				
			||||||
 | 
					              protocol: "TCP"
 | 
				
			||||||
 | 
					          readinessProbe:
 | 
				
			||||||
 | 
					            httpGet:
 | 
				
			||||||
 | 
					              path: "/health"
 | 
				
			||||||
 | 
					              port: 8081
 | 
				
			||||||
 | 
					            initialDelaySeconds: 5
 | 
				
			||||||
 | 
					            timeoutSeconds: 1
 | 
				
			||||||
 | 
					          resources:
 | 
				
			||||||
 | 
					            {{- toYaml .Values.resources | nindent 12 }}
 | 
				
			||||||
 | 
					          env:
 | 
				
			||||||
 | 
					            - name: "APP_URL"
 | 
				
			||||||
 | 
					              value: "http://localhost:8081"
 | 
				
			||||||
 | 
					            - name: "OPERATOR_API_URL"
 | 
				
			||||||
 | 
					              value: {{ .Values.envs.operatorApiUrl }}
 | 
				
			||||||
 | 
					            - name: "TARGET_NAMESPACE"
 | 
				
			||||||
 | 
					              value: {{ .Values.envs.targetNamespace }}
 | 
				
			||||||
 | 
					            - name: "TEAMS"
 | 
				
			||||||
 | 
					              value: |-
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                  "acid"
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            - name: "OPERATOR_UI_CONFIG"
 | 
				
			||||||
 | 
					              value: |-
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  "docs_link":"https://postgres-operator.readthedocs.io/en/latest/",
 | 
				
			||||||
 | 
					                  "dns_format_string": "{1}-{0}.{2}",
 | 
				
			||||||
 | 
					                  "databases_visible": true,
 | 
				
			||||||
 | 
					                  "master_load_balancer_visible": true,
 | 
				
			||||||
 | 
					                  "nat_gateways_visible": false,
 | 
				
			||||||
 | 
					                  "replica_load_balancer_visible": true,
 | 
				
			||||||
 | 
					                  "resources_visible": true,
 | 
				
			||||||
 | 
					                  "users_visible": true,
 | 
				
			||||||
 | 
					                  "postgresql_versions": [
 | 
				
			||||||
 | 
					                    "12",
 | 
				
			||||||
 | 
					                    "11",
 | 
				
			||||||
 | 
					                    "10",
 | 
				
			||||||
 | 
					                    "9.6",
 | 
				
			||||||
 | 
					                    "9.5"
 | 
				
			||||||
 | 
					                  ]
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					{{- if .Values.ingress.enabled -}}
 | 
				
			||||||
 | 
					{{- $fullName := include "postgres-operator-ui.fullname" . -}}
 | 
				
			||||||
 | 
					{{- $svcPort := .Values.service.port -}}
 | 
				
			||||||
 | 
					{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
 | 
				
			||||||
 | 
					apiVersion: networking.k8s.io/v1beta1
 | 
				
			||||||
 | 
					{{- else -}}
 | 
				
			||||||
 | 
					apiVersion: extensions/v1beta1
 | 
				
			||||||
 | 
					{{- end }}
 | 
				
			||||||
 | 
					kind: Ingress
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  name: {{ $fullName }}
 | 
				
			||||||
 | 
					  labels:
 | 
				
			||||||
 | 
					    app.kubernetes.io/name: {{ template "postgres-operator-ui.name" . }}
 | 
				
			||||||
 | 
					    helm.sh/chart: {{ template "postgres-operator-ui.chart" . }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
				
			||||||
 | 
					  {{- with .Values.ingress.annotations }}
 | 
				
			||||||
 | 
					  annotations:
 | 
				
			||||||
 | 
					    {{- toYaml . | nindent 4 }}
 | 
				
			||||||
 | 
					  {{- end }}
 | 
				
			||||||
 | 
					spec:
 | 
				
			||||||
 | 
					{{- if .Values.ingress.tls }}
 | 
				
			||||||
 | 
					  tls:
 | 
				
			||||||
 | 
					  {{- range .Values.ingress.tls }}
 | 
				
			||||||
 | 
					    - hosts:
 | 
				
			||||||
 | 
					      {{- range .hosts }}
 | 
				
			||||||
 | 
					        - {{ . | quote }}
 | 
				
			||||||
 | 
					      {{- end }}
 | 
				
			||||||
 | 
					      secretName: {{ .secretName }}
 | 
				
			||||||
 | 
					  {{- end }}
 | 
				
			||||||
 | 
					{{- end }}
 | 
				
			||||||
 | 
					  rules:
 | 
				
			||||||
 | 
					  {{- range .Values.ingress.hosts }}
 | 
				
			||||||
 | 
					    - host: {{ .host | quote }}
 | 
				
			||||||
 | 
					      http:
 | 
				
			||||||
 | 
					        paths:
 | 
				
			||||||
 | 
					        {{- range .paths }}
 | 
				
			||||||
 | 
					          - path: {{ . }}
 | 
				
			||||||
 | 
					            backend:
 | 
				
			||||||
 | 
					              serviceName: {{ $fullName }}
 | 
				
			||||||
 | 
					              servicePort: {{ $svcPort }}
 | 
				
			||||||
 | 
					        {{- end }}
 | 
				
			||||||
 | 
					  {{- end }}
 | 
				
			||||||
 | 
					{{- end }}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					apiVersion: v1
 | 
				
			||||||
 | 
					kind: Service
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  labels:
 | 
				
			||||||
 | 
					    app.kubernetes.io/name: {{ template "postgres-operator-ui.name" . }}
 | 
				
			||||||
 | 
					    helm.sh/chart: {{ template "postgres-operator-ui.chart" . }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
				
			||||||
 | 
					  name: {{ template "postgres-operator-ui.fullname" . }}
 | 
				
			||||||
 | 
					spec:
 | 
				
			||||||
 | 
					  ports:
 | 
				
			||||||
 | 
					    - port: {{ .Values.service.port }}
 | 
				
			||||||
 | 
					      targetPort: 8081
 | 
				
			||||||
 | 
					      protocol: TCP
 | 
				
			||||||
 | 
					  selector:
 | 
				
			||||||
 | 
					    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/name: {{ template "postgres-operator-ui.name" . }}
 | 
				
			||||||
 | 
					  type: {{ .Values.service.type }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					{{ if .Values.serviceAccount.create }}
 | 
				
			||||||
 | 
					apiVersion: v1
 | 
				
			||||||
 | 
					kind: ServiceAccount
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  name: {{ include "postgres-operator-ui.serviceAccountName" . }}
 | 
				
			||||||
 | 
					  labels:
 | 
				
			||||||
 | 
					    app.kubernetes.io/name: {{ template "postgres-operator-ui.name" . }}
 | 
				
			||||||
 | 
					    helm.sh/chart: {{ template "postgres-operator-ui.chart" . }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
				
			||||||
 | 
					{{ end }}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,58 @@
 | 
				
			||||||
 | 
					# Default values for postgres-operator-ui.
 | 
				
			||||||
 | 
					# This is a YAML-formatted file.
 | 
				
			||||||
 | 
					# Declare variables to be passed into your templates.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					replicaCount: 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# configure ui image
 | 
				
			||||||
 | 
					image:
 | 
				
			||||||
 | 
					  registry: registry.opensource.zalan.do
 | 
				
			||||||
 | 
					  repository: acid/postgres-operator-ui
 | 
				
			||||||
 | 
					  tag: v1.2.0
 | 
				
			||||||
 | 
					  pullPolicy: "IfNotPresent"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rbac:
 | 
				
			||||||
 | 
					  # Specifies whether RBAC resources should be created
 | 
				
			||||||
 | 
					  create: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					serviceAccount:
 | 
				
			||||||
 | 
					  # Specifies whether a ServiceAccount should be created
 | 
				
			||||||
 | 
					  create: true
 | 
				
			||||||
 | 
					  # The name of the ServiceAccount to use.
 | 
				
			||||||
 | 
					  # If not set and create is true, a name is generated using the fullname template
 | 
				
			||||||
 | 
					  name:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# configure UI pod resources
 | 
				
			||||||
 | 
					resources:
 | 
				
			||||||
 | 
					  limits:
 | 
				
			||||||
 | 
					    cpu: 300m
 | 
				
			||||||
 | 
					    memory: 3000Mi
 | 
				
			||||||
 | 
					  requests:
 | 
				
			||||||
 | 
					    cpu: 100m
 | 
				
			||||||
 | 
					    memory: 100Mi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# configure UI ENVs
 | 
				
			||||||
 | 
					envs:
 | 
				
			||||||
 | 
					  # IMPORTANT: While operator chart and UI chart are idendependent, this is the interface between
 | 
				
			||||||
 | 
					  # UI and operator API. Insert the service name of the operator API here!
 | 
				
			||||||
 | 
					  operatorApiUrl: "http://postgres-operator:8080"
 | 
				
			||||||
 | 
					  targetNamespace: "default"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# configure UI service
 | 
				
			||||||
 | 
					service:
 | 
				
			||||||
 | 
					  type: "ClusterIP"
 | 
				
			||||||
 | 
					  port: "8080"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# configure UI ingress. If needed: "enabled: true"
 | 
				
			||||||
 | 
					ingress:
 | 
				
			||||||
 | 
					  enabled: false
 | 
				
			||||||
 | 
					  annotations: {}
 | 
				
			||||||
 | 
					    # kubernetes.io/ingress.class: nginx
 | 
				
			||||||
 | 
					    # kubernetes.io/tls-acme: "true"
 | 
				
			||||||
 | 
					  hosts:
 | 
				
			||||||
 | 
					    - host: ui.example.org
 | 
				
			||||||
 | 
					      paths: [""]
 | 
				
			||||||
 | 
					  tls: []
 | 
				
			||||||
 | 
					  #  - secretName: ui-tls
 | 
				
			||||||
 | 
					  #    hosts:
 | 
				
			||||||
 | 
					  #      - ui.exmaple.org
 | 
				
			||||||
| 
						 | 
					@ -179,6 +179,12 @@ spec:
 | 
				
			||||||
                default_memory_request:
 | 
					                default_memory_request:
 | 
				
			||||||
                  type: string
 | 
					                  type: string
 | 
				
			||||||
                  pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
 | 
					                  pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
 | 
				
			||||||
 | 
					                min_cpu_limit:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
 | 
				
			||||||
 | 
					                min_memory_limit:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
 | 
				
			||||||
            timeouts:
 | 
					            timeouts:
 | 
				
			||||||
              type: object
 | 
					              type: object
 | 
				
			||||||
              properties:
 | 
					              properties:
 | 
				
			||||||
| 
						 | 
					@ -237,6 +243,8 @@ spec:
 | 
				
			||||||
                  type: string
 | 
					                  type: string
 | 
				
			||||||
                logical_backup_s3_endpoint:
 | 
					                logical_backup_s3_endpoint:
 | 
				
			||||||
                  type: string
 | 
					                  type: string
 | 
				
			||||||
 | 
					                logical_backup_s3_region:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
                logical_backup_s3_secret_access_key:
 | 
					                logical_backup_s3_secret_access_key:
 | 
				
			||||||
                  type: string
 | 
					                  type: string
 | 
				
			||||||
                logical_backup_s3_sse:
 | 
					                logical_backup_s3_sse:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,53 @@
 | 
				
			||||||
 | 
					{{ if .Values.rbac.create }}
 | 
				
			||||||
 | 
					apiVersion: rbac.authorization.k8s.io/v1
 | 
				
			||||||
 | 
					kind: ClusterRole
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  name: postgres-pod
 | 
				
			||||||
 | 
					  labels:
 | 
				
			||||||
 | 
					    app.kubernetes.io/name: {{ template "postgres-operator.name" . }}
 | 
				
			||||||
 | 
					    helm.sh/chart: {{ template "postgres-operator.chart" . }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
				
			||||||
 | 
					rules:
 | 
				
			||||||
 | 
					# Patroni needs to watch and manage endpoints
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - ""
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - endpoints
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - create
 | 
				
			||||||
 | 
					  - delete
 | 
				
			||||||
 | 
					  - deletecollection
 | 
				
			||||||
 | 
					  - get
 | 
				
			||||||
 | 
					  - list
 | 
				
			||||||
 | 
					  - patch
 | 
				
			||||||
 | 
					  - update
 | 
				
			||||||
 | 
					  - watch
 | 
				
			||||||
 | 
					# Patroni needs to watch pods
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - ""
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - pods
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - get
 | 
				
			||||||
 | 
					  - list
 | 
				
			||||||
 | 
					  - patch
 | 
				
			||||||
 | 
					  - update
 | 
				
			||||||
 | 
					  - watch
 | 
				
			||||||
 | 
					# to let Patroni create a headless service
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - ""
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - services
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - create
 | 
				
			||||||
 | 
					# to run privileged pods
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - extensions
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - podsecuritypolicies
 | 
				
			||||||
 | 
					  resourceNames:
 | 
				
			||||||
 | 
					  - privileged
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - use
 | 
				
			||||||
 | 
					{{ end }}
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
{{ if .Values.rbac.create }}
 | 
					{{ if .Values.rbac.create }}
 | 
				
			||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
 | 
					apiVersion: rbac.authorization.k8s.io/v1
 | 
				
			||||||
kind: ClusterRole
 | 
					kind: ClusterRole
 | 
				
			||||||
metadata:
 | 
					metadata:
 | 
				
			||||||
  name: {{ include "postgres-operator.serviceAccountName" . }}
 | 
					  name: {{ include "postgres-operator.serviceAccountName" . }}
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@ metadata:
 | 
				
			||||||
    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
					    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
				
			||||||
    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
					    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
				
			||||||
rules:
 | 
					rules:
 | 
				
			||||||
 | 
					# all verbs allowed for custom operator resources
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - acid.zalan.do
 | 
					  - acid.zalan.do
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
| 
						 | 
					@ -16,7 +17,15 @@ rules:
 | 
				
			||||||
  - postgresqls/status
 | 
					  - postgresqls/status
 | 
				
			||||||
  - operatorconfigurations
 | 
					  - operatorconfigurations
 | 
				
			||||||
  verbs:
 | 
					  verbs:
 | 
				
			||||||
  - "*"
 | 
					  - create
 | 
				
			||||||
 | 
					  - delete
 | 
				
			||||||
 | 
					  - deletecollection
 | 
				
			||||||
 | 
					  - get
 | 
				
			||||||
 | 
					  - list
 | 
				
			||||||
 | 
					  - patch
 | 
				
			||||||
 | 
					  - update
 | 
				
			||||||
 | 
					  - watch
 | 
				
			||||||
 | 
					# to create or get/update CRDs when starting up
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - apiextensions.k8s.io
 | 
					  - apiextensions.k8s.io
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
| 
						 | 
					@ -26,12 +35,14 @@ rules:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - patch
 | 
					  - patch
 | 
				
			||||||
  - update
 | 
					  - update
 | 
				
			||||||
 | 
					# to read configuration from ConfigMaps
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - ""
 | 
					  - ""
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
  - configmaps
 | 
					  - configmaps
 | 
				
			||||||
  verbs:
 | 
					  verbs:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
 | 
					# to manage endpoints which are also used by Patroni
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - ""
 | 
					  - ""
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
| 
						 | 
					@ -43,7 +54,9 @@ rules:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - list
 | 
					  - list
 | 
				
			||||||
  - patch
 | 
					  - patch
 | 
				
			||||||
  - watch  # needed if zalando-postgres-operator account is used for pods as well
 | 
					  - update
 | 
				
			||||||
 | 
					  - watch
 | 
				
			||||||
 | 
					# to CRUD secrets for database access
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - ""
 | 
					  - ""
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
| 
						 | 
					@ -53,6 +66,7 @@ rules:
 | 
				
			||||||
  - update
 | 
					  - update
 | 
				
			||||||
  - delete
 | 
					  - delete
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
 | 
					# to check nodes for node readiness label
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - ""
 | 
					  - ""
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
| 
						 | 
					@ -61,6 +75,7 @@ rules:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - list
 | 
					  - list
 | 
				
			||||||
  - watch
 | 
					  - watch
 | 
				
			||||||
 | 
					# to read or delete existing PVCs. Creation via StatefulSet
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - ""
 | 
					  - ""
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
| 
						 | 
					@ -69,6 +84,7 @@ rules:
 | 
				
			||||||
  - delete
 | 
					  - delete
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - list
 | 
					  - list
 | 
				
			||||||
 | 
					 # to read existing PVs. Creation should be done via dynamic provisioning
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - ""
 | 
					  - ""
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
| 
						 | 
					@ -77,6 +93,7 @@ rules:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - list
 | 
					  - list
 | 
				
			||||||
  - update  # only for resizing AWS volumes
 | 
					  - update  # only for resizing AWS volumes
 | 
				
			||||||
 | 
					# to watch Spilo pods and do rolling updates. Creation via StatefulSet
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - ""
 | 
					  - ""
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
| 
						 | 
					@ -86,13 +103,16 @@ rules:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - list
 | 
					  - list
 | 
				
			||||||
  - watch
 | 
					  - watch
 | 
				
			||||||
 | 
					  - update
 | 
				
			||||||
  - patch
 | 
					  - patch
 | 
				
			||||||
 | 
					# to resize the filesystem in Spilo pods when increasing volume size
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - ""
 | 
					  - ""
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
  - pods/exec
 | 
					  - pods/exec
 | 
				
			||||||
  verbs:
 | 
					  verbs:
 | 
				
			||||||
  - create
 | 
					  - create
 | 
				
			||||||
 | 
					# to CRUD services to point to Postgres cluster instances
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - ""
 | 
					  - ""
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
| 
						 | 
					@ -102,6 +122,8 @@ rules:
 | 
				
			||||||
  - delete
 | 
					  - delete
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - patch
 | 
					  - patch
 | 
				
			||||||
 | 
					  - update
 | 
				
			||||||
 | 
					# to CRUD the StatefulSet which controls the Postgres cluster instances
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - apps
 | 
					  - apps
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
| 
						 | 
					@ -112,12 +134,26 @@ rules:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - list
 | 
					  - list
 | 
				
			||||||
  - patch
 | 
					  - patch
 | 
				
			||||||
 | 
					# to CRUD cron jobs for logical backups
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - batch
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - cronjobs
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - create
 | 
				
			||||||
 | 
					  - delete
 | 
				
			||||||
 | 
					  - get
 | 
				
			||||||
 | 
					  - list
 | 
				
			||||||
 | 
					  - patch
 | 
				
			||||||
 | 
					  - update
 | 
				
			||||||
 | 
					# to get namespaces operator resources can run in
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - ""
 | 
					  - ""
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
  - namespaces
 | 
					  - namespaces
 | 
				
			||||||
  verbs:
 | 
					  verbs:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
 | 
					# to define PDBs. Update happens via delete/create
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - policy
 | 
					  - policy
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
| 
						 | 
					@ -126,6 +162,7 @@ rules:
 | 
				
			||||||
  - create
 | 
					  - create
 | 
				
			||||||
  - delete
 | 
					  - delete
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
 | 
					# to create ServiceAccounts in each namespace the operator watches
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - ""
 | 
					  - ""
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
| 
						 | 
					@ -133,30 +170,21 @@ rules:
 | 
				
			||||||
  verbs:
 | 
					  verbs:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - create
 | 
					  - create
 | 
				
			||||||
 | 
					# to create role bindings to the postgres-pod service account
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - "rbac.authorization.k8s.io"
 | 
					  - rbac.authorization.k8s.io
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
  - rolebindings
 | 
					  - rolebindings
 | 
				
			||||||
  verbs:
 | 
					  verbs:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - create
 | 
					  - create
 | 
				
			||||||
 | 
					# to grant privilege to run privileged pods
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - "rbac.authorization.k8s.io"
 | 
					  - extensions
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
  - clusterroles
 | 
					  - podsecuritypolicies
 | 
				
			||||||
  verbs:
 | 
					 | 
				
			||||||
  - bind
 | 
					 | 
				
			||||||
  resourceNames:
 | 
					  resourceNames:
 | 
				
			||||||
  - {{ include "postgres-operator.serviceAccountName" . }}
 | 
					  - privileged
 | 
				
			||||||
- apiGroups:
 | 
					 | 
				
			||||||
  - batch
 | 
					 | 
				
			||||||
  resources:
 | 
					 | 
				
			||||||
  - cronjobs  # enables logical backups
 | 
					 | 
				
			||||||
  verbs:
 | 
					  verbs:
 | 
				
			||||||
  - create
 | 
					  - use
 | 
				
			||||||
  - delete
 | 
					 | 
				
			||||||
  - get
 | 
					 | 
				
			||||||
  - list
 | 
					 | 
				
			||||||
  - patch
 | 
					 | 
				
			||||||
  - update
 | 
					 | 
				
			||||||
{{ end }}
 | 
					{{ end }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,8 +14,6 @@ roleRef:
 | 
				
			||||||
  name: {{ include "postgres-operator.serviceAccountName" . }}
 | 
					  name: {{ include "postgres-operator.serviceAccountName" . }}
 | 
				
			||||||
subjects:
 | 
					subjects:
 | 
				
			||||||
- kind: ServiceAccount
 | 
					- kind: ServiceAccount
 | 
				
			||||||
# note: the cluster role binding needs to be defined
 | 
					 | 
				
			||||||
# for every namespace the operator service account lives in.
 | 
					 | 
				
			||||||
  name: {{ include "postgres-operator.serviceAccountName" . }}
 | 
					  name: {{ include "postgres-operator.serviceAccountName" . }}
 | 
				
			||||||
  namespace: {{ .Release.Namespace }}
 | 
					  namespace: {{ .Release.Namespace }}
 | 
				
			||||||
{{ end }}
 | 
					{{ end }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,6 @@ metadata:
 | 
				
			||||||
    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
					    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
				
			||||||
    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
					    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
				
			||||||
data:
 | 
					data:
 | 
				
			||||||
  pod_service_account_name: {{ include "postgres-operator.serviceAccountName" . }}
 | 
					 | 
				
			||||||
{{ toYaml .Values.configGeneral | indent 2 }}
 | 
					{{ toYaml .Values.configGeneral | indent 2 }}
 | 
				
			||||||
{{ toYaml .Values.configUsers | indent 2 }}
 | 
					{{ toYaml .Values.configUsers | indent 2 }}
 | 
				
			||||||
{{ toYaml .Values.configKubernetes | indent 2 }}
 | 
					{{ toYaml .Values.configKubernetes | indent 2 }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,6 @@ configuration:
 | 
				
			||||||
{{ toYaml .Values.configUsers | indent 4 }}
 | 
					{{ toYaml .Values.configUsers | indent 4 }}
 | 
				
			||||||
  kubernetes:
 | 
					  kubernetes:
 | 
				
			||||||
    oauth_token_secret_name: {{ template "postgres-operator.fullname" . }}
 | 
					    oauth_token_secret_name: {{ template "postgres-operator.fullname" . }}
 | 
				
			||||||
    pod_service_account_name: {{ include "postgres-operator.serviceAccountName" . }}
 | 
					 | 
				
			||||||
{{ toYaml .Values.configKubernetes | indent 4 }}
 | 
					{{ toYaml .Values.configKubernetes | indent 4 }}
 | 
				
			||||||
  postgres_pod_resources:
 | 
					  postgres_pod_resources:
 | 
				
			||||||
{{ toYaml .Values.configPostgresPodResources | indent 4 }}
 | 
					{{ toYaml .Values.configPostgresPodResources | indent 4 }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					apiVersion: v1
 | 
				
			||||||
 | 
					kind: Service
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  labels:
 | 
				
			||||||
 | 
					    app.kubernetes.io/name: {{ template "postgres-operator.name" . }}
 | 
				
			||||||
 | 
					    helm.sh/chart: {{ template "postgres-operator.chart" . }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
				
			||||||
 | 
					  name: {{ template "postgres-operator.fullname" . }}
 | 
				
			||||||
 | 
					spec:
 | 
				
			||||||
 | 
					  type: ClusterIP
 | 
				
			||||||
 | 
					  ports:
 | 
				
			||||||
 | 
					  - port: 8080
 | 
				
			||||||
 | 
					    protocol: TCP
 | 
				
			||||||
 | 
					    targetPort: 8080
 | 
				
			||||||
 | 
					  selector:
 | 
				
			||||||
 | 
					    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
				
			||||||
 | 
					    app.kubernetes.io/name: {{ template "postgres-operator.name" . }}
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
image:
 | 
					image:
 | 
				
			||||||
  registry: registry.opensource.zalan.do
 | 
					  registry: registry.opensource.zalan.do
 | 
				
			||||||
  repository: acid/postgres-operator
 | 
					  repository: acid/postgres-operator
 | 
				
			||||||
  tag: v1.3.0
 | 
					  tag: v1.3.1
 | 
				
			||||||
  pullPolicy: "IfNotPresent"
 | 
					  pullPolicy: "IfNotPresent"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Optionally specify an array of imagePullSecrets.
 | 
					# Optionally specify an array of imagePullSecrets.
 | 
				
			||||||
| 
						 | 
					@ -100,6 +100,8 @@ configKubernetes:
 | 
				
			||||||
  pod_management_policy: "ordered_ready"
 | 
					  pod_management_policy: "ordered_ready"
 | 
				
			||||||
  # label assigned to the Postgres pods (and services/endpoints)
 | 
					  # label assigned to the Postgres pods (and services/endpoints)
 | 
				
			||||||
  pod_role_label: spilo-role
 | 
					  pod_role_label: spilo-role
 | 
				
			||||||
 | 
					  # name of service account to be used by postgres cluster pods
 | 
				
			||||||
 | 
					  pod_service_account_name: "postgres-pod"
 | 
				
			||||||
  # Postgres pods are terminated forcefully after this timeout
 | 
					  # Postgres pods are terminated forcefully after this timeout
 | 
				
			||||||
  pod_terminate_grace_period: 5m
 | 
					  pod_terminate_grace_period: 5m
 | 
				
			||||||
  # template for database user secrets generated by the operator
 | 
					  # template for database user secrets generated by the operator
 | 
				
			||||||
| 
						 | 
					@ -115,13 +117,17 @@ configKubernetes:
 | 
				
			||||||
# configure resource requests for the Postgres pods
 | 
					# configure resource requests for the Postgres pods
 | 
				
			||||||
configPostgresPodResources:
 | 
					configPostgresPodResources:
 | 
				
			||||||
  # CPU limits for the postgres containers
 | 
					  # CPU limits for the postgres containers
 | 
				
			||||||
  default_cpu_limit: "3"
 | 
					  default_cpu_limit: "1"
 | 
				
			||||||
  # cpu request value for the postgres containers
 | 
					  # CPU request value for the postgres containers
 | 
				
			||||||
  default_cpu_request: 100m
 | 
					  default_cpu_request: 100m
 | 
				
			||||||
  # memory limits for the postgres containers
 | 
					  # memory limits for the postgres containers
 | 
				
			||||||
  default_memory_limit: 1Gi
 | 
					  default_memory_limit: 500Mi
 | 
				
			||||||
  # memory request value for the postgres containers
 | 
					  # memory request value for the postgres containers
 | 
				
			||||||
  default_memory_request: 100Mi
 | 
					  default_memory_request: 100Mi
 | 
				
			||||||
 | 
					  # hard CPU minimum required to properly run a Postgres cluster
 | 
				
			||||||
 | 
					  min_cpu_limit: 250m
 | 
				
			||||||
 | 
					  # hard memory minimum required to properly run a Postgres cluster
 | 
				
			||||||
 | 
					  min_memory_limit: 250Mi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# timeouts related to some operator actions
 | 
					# timeouts related to some operator actions
 | 
				
			||||||
configTimeouts:
 | 
					configTimeouts:
 | 
				
			||||||
| 
						 | 
					@ -200,6 +206,8 @@ configLogicalBackup:
 | 
				
			||||||
  logical_backup_s3_access_key_id: ""
 | 
					  logical_backup_s3_access_key_id: ""
 | 
				
			||||||
  # S3 bucket to store backup results
 | 
					  # S3 bucket to store backup results
 | 
				
			||||||
  logical_backup_s3_bucket: "my-bucket-url"
 | 
					  logical_backup_s3_bucket: "my-bucket-url"
 | 
				
			||||||
 | 
					  # S3 region of bucket
 | 
				
			||||||
 | 
					  logical_backup_s3_region: ""
 | 
				
			||||||
  # S3 endpoint url when not using AWS
 | 
					  # S3 endpoint url when not using AWS
 | 
				
			||||||
  logical_backup_s3_endpoint: ""
 | 
					  logical_backup_s3_endpoint: ""
 | 
				
			||||||
  # S3 Secret Access Key
 | 
					  # S3 Secret Access Key
 | 
				
			||||||
| 
						 | 
					@ -251,7 +259,7 @@ configScalyr:
 | 
				
			||||||
  # CPU rquest value for the Scalyr sidecar
 | 
					  # CPU rquest value for the Scalyr sidecar
 | 
				
			||||||
  scalyr_cpu_request: 100m
 | 
					  scalyr_cpu_request: 100m
 | 
				
			||||||
  # Memory limit value for the Scalyr sidecar
 | 
					  # Memory limit value for the Scalyr sidecar
 | 
				
			||||||
  scalyr_memory_limit: 1Gi
 | 
					  scalyr_memory_limit: 500Mi
 | 
				
			||||||
  # Memory request value for the Scalyr sidecar
 | 
					  # Memory request value for the Scalyr sidecar
 | 
				
			||||||
  scalyr_memory_request: 50Mi
 | 
					  scalyr_memory_request: 50Mi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -272,13 +280,13 @@ serviceAccount:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
priorityClassName: ""
 | 
					priorityClassName: ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
resources: {}
 | 
					resources:
 | 
				
			||||||
  # limits:
 | 
					  limits:
 | 
				
			||||||
  #   cpu: 100m
 | 
					    cpu: 500m
 | 
				
			||||||
  #   memory: 300Mi
 | 
					    memory: 500Mi
 | 
				
			||||||
  # requests:
 | 
					  requests:
 | 
				
			||||||
  #   cpu: 100m
 | 
					    cpu: 100m
 | 
				
			||||||
  #   memory: 300Mi
 | 
					    memory: 250Mi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Affinity for pod assignment
 | 
					# Affinity for pod assignment
 | 
				
			||||||
# Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
 | 
					# Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
image:
 | 
					image:
 | 
				
			||||||
  registry: registry.opensource.zalan.do
 | 
					  registry: registry.opensource.zalan.do
 | 
				
			||||||
  repository: acid/postgres-operator
 | 
					  repository: acid/postgres-operator
 | 
				
			||||||
  tag: v1.3.0
 | 
					  tag: v1.3.1
 | 
				
			||||||
  pullPolicy: "IfNotPresent"
 | 
					  pullPolicy: "IfNotPresent"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Optionally specify an array of imagePullSecrets.
 | 
					# Optionally specify an array of imagePullSecrets.
 | 
				
			||||||
| 
						 | 
					@ -55,7 +55,7 @@ configKubernetes:
 | 
				
			||||||
  # additional labels assigned to the cluster objects
 | 
					  # additional labels assigned to the cluster objects
 | 
				
			||||||
  cluster_labels: application:spilo
 | 
					  cluster_labels: application:spilo
 | 
				
			||||||
  # label assigned to Kubernetes objects created by the operator
 | 
					  # label assigned to Kubernetes objects created by the operator
 | 
				
			||||||
  cluster_name_label: version
 | 
					  cluster_name_label: cluster-name
 | 
				
			||||||
  # annotations attached to each database pod
 | 
					  # annotations attached to each database pod
 | 
				
			||||||
  # custom_pod_annotations: "keya:valuea,keyb:valueb"
 | 
					  # custom_pod_annotations: "keya:valuea,keyb:valueb"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -93,6 +93,8 @@ configKubernetes:
 | 
				
			||||||
  pod_management_policy: "ordered_ready"
 | 
					  pod_management_policy: "ordered_ready"
 | 
				
			||||||
  # label assigned to the Postgres pods (and services/endpoints)
 | 
					  # label assigned to the Postgres pods (and services/endpoints)
 | 
				
			||||||
  pod_role_label: spilo-role
 | 
					  pod_role_label: spilo-role
 | 
				
			||||||
 | 
					  # name of service account to be used by postgres cluster pods
 | 
				
			||||||
 | 
					  pod_service_account_name: "postgres-pod"
 | 
				
			||||||
  # Postgres pods are terminated forcefully after this timeout
 | 
					  # Postgres pods are terminated forcefully after this timeout
 | 
				
			||||||
  pod_terminate_grace_period: 5m
 | 
					  pod_terminate_grace_period: 5m
 | 
				
			||||||
  # template for database user secrets generated by the operator
 | 
					  # template for database user secrets generated by the operator
 | 
				
			||||||
| 
						 | 
					@ -108,13 +110,17 @@ configKubernetes:
 | 
				
			||||||
# configure resource requests for the Postgres pods
 | 
					# configure resource requests for the Postgres pods
 | 
				
			||||||
configPostgresPodResources:
 | 
					configPostgresPodResources:
 | 
				
			||||||
  # CPU limits for the postgres containers
 | 
					  # CPU limits for the postgres containers
 | 
				
			||||||
  default_cpu_limit: "3"
 | 
					  default_cpu_limit: "1"
 | 
				
			||||||
  # cpu request value for the postgres containers
 | 
					  # CPU request value for the postgres containers
 | 
				
			||||||
  default_cpu_request: 100m
 | 
					  default_cpu_request: 100m
 | 
				
			||||||
  # memory limits for the postgres containers
 | 
					  # memory limits for the postgres containers
 | 
				
			||||||
  default_memory_limit: 1Gi
 | 
					  default_memory_limit: 500Mi
 | 
				
			||||||
  # memory request value for the postgres containers
 | 
					  # memory request value for the postgres containers
 | 
				
			||||||
  default_memory_request: 100Mi
 | 
					  default_memory_request: 100Mi
 | 
				
			||||||
 | 
					  # hard CPU minimum required to properly run a Postgres cluster
 | 
				
			||||||
 | 
					  min_cpu_limit: 250m
 | 
				
			||||||
 | 
					  # hard memory minimum required to properly run a Postgres cluster
 | 
				
			||||||
 | 
					  min_memory_limit: 250Mi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# timeouts related to some operator actions
 | 
					# timeouts related to some operator actions
 | 
				
			||||||
configTimeouts:
 | 
					configTimeouts:
 | 
				
			||||||
| 
						 | 
					@ -191,6 +197,8 @@ configLogicalBackup:
 | 
				
			||||||
  logical_backup_s3_access_key_id: ""
 | 
					  logical_backup_s3_access_key_id: ""
 | 
				
			||||||
  # S3 bucket to store backup results
 | 
					  # S3 bucket to store backup results
 | 
				
			||||||
  logical_backup_s3_bucket: "my-bucket-url"
 | 
					  logical_backup_s3_bucket: "my-bucket-url"
 | 
				
			||||||
 | 
					  # S3 region of bucket
 | 
				
			||||||
 | 
					  logical_backup_s3_region: ""
 | 
				
			||||||
  # S3 endpoint url when not using AWS
 | 
					  # S3 endpoint url when not using AWS
 | 
				
			||||||
  logical_backup_s3_endpoint: ""
 | 
					  logical_backup_s3_endpoint: ""
 | 
				
			||||||
  # S3 Secret Access Key
 | 
					  # S3 Secret Access Key
 | 
				
			||||||
| 
						 | 
					@ -248,13 +256,13 @@ serviceAccount:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
priorityClassName: ""
 | 
					priorityClassName: ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
resources: {}
 | 
					resources:
 | 
				
			||||||
  # limits:
 | 
					  limits:
 | 
				
			||||||
  #   cpu: 100m
 | 
					    cpu: 500m
 | 
				
			||||||
  #   memory: 300Mi
 | 
					    memory: 500Mi
 | 
				
			||||||
  # requests:
 | 
					  requests:
 | 
				
			||||||
  #   cpu: 100m
 | 
					    cpu: 100m
 | 
				
			||||||
  #   memory: 300Mi
 | 
					    memory: 250Mi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Affinity for pod assignment
 | 
					# Affinity for pod assignment
 | 
				
			||||||
# Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
 | 
					# Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,6 +19,7 @@ RUN apt-get update     \
 | 
				
			||||||
    && curl --silent https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
 | 
					    && curl --silent https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
 | 
				
			||||||
    && apt-get update \
 | 
					    && apt-get update \
 | 
				
			||||||
    && apt-get install --no-install-recommends -y  \
 | 
					    && apt-get install --no-install-recommends -y  \
 | 
				
			||||||
 | 
					        postgresql-client-12  \
 | 
				
			||||||
        postgresql-client-11  \
 | 
					        postgresql-client-11  \
 | 
				
			||||||
        postgresql-client-10  \
 | 
					        postgresql-client-10  \
 | 
				
			||||||
        postgresql-client-9.6 \
 | 
					        postgresql-client-9.6 \
 | 
				
			||||||
| 
						 | 
					@ -28,6 +29,6 @@ RUN apt-get update     \
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY dump.sh ./
 | 
					COPY dump.sh ./
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV PG_DIR=/usr/lib/postgresql/
 | 
					ENV PG_DIR=/usr/lib/postgresql
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENTRYPOINT ["/dump.sh"]
 | 
					ENTRYPOINT ["/dump.sh"]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,12 +6,10 @@ set -o nounset
 | 
				
			||||||
set -o pipefail
 | 
					set -o pipefail
 | 
				
			||||||
IFS=$'\n\t'
 | 
					IFS=$'\n\t'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# make script trace visible via `kubectl logs`
 | 
					 | 
				
			||||||
set -o xtrace
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ALL_DB_SIZE_QUERY="select sum(pg_database_size(datname)::numeric) from pg_database;"
 | 
					ALL_DB_SIZE_QUERY="select sum(pg_database_size(datname)::numeric) from pg_database;"
 | 
				
			||||||
PG_BIN=$PG_DIR/$PG_VERSION/bin
 | 
					PG_BIN=$PG_DIR/$PG_VERSION/bin
 | 
				
			||||||
DUMP_SIZE_COEFF=5
 | 
					DUMP_SIZE_COEFF=5
 | 
				
			||||||
 | 
					ERRORCOUNT=0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
 | 
					TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
 | 
				
			||||||
K8S_API_URL=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1
 | 
					K8S_API_URL=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1
 | 
				
			||||||
| 
						 | 
					@ -42,9 +40,10 @@ function aws_upload {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [[ ! -z "$EXPECTED_SIZE" ]] && args+=("--expected-size=$EXPECTED_SIZE")
 | 
					    [[ ! -z "$EXPECTED_SIZE" ]] && args+=("--expected-size=$EXPECTED_SIZE")
 | 
				
			||||||
    [[ ! -z "$LOGICAL_BACKUP_S3_ENDPOINT" ]] && args+=("--endpoint-url=$LOGICAL_BACKUP_S3_ENDPOINT")
 | 
					    [[ ! -z "$LOGICAL_BACKUP_S3_ENDPOINT" ]] && args+=("--endpoint-url=$LOGICAL_BACKUP_S3_ENDPOINT")
 | 
				
			||||||
    [[ ! "$LOGICAL_BACKUP_S3_SSE" == "" ]] && args+=("--sse=$LOGICAL_BACKUP_S3_SSE")
 | 
					    [[ ! -z "$LOGICAL_BACKUP_S3_REGION" ]] && args+=("--region=$LOGICAL_BACKUP_S3_REGION")
 | 
				
			||||||
 | 
					    [[ ! -z "$LOGICAL_BACKUP_S3_SSE" ]] && args+=("--sse=$LOGICAL_BACKUP_S3_SSE")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    aws s3 cp - "$PATH_TO_BACKUP" "${args[@]//\'/}" --debug
 | 
					    aws s3 cp - "$PATH_TO_BACKUP" "${args[@]//\'/}"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function get_pods {
 | 
					function get_pods {
 | 
				
			||||||
| 
						 | 
					@ -93,4 +92,9 @@ for search in "${search_strategy[@]}"; do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
done
 | 
					done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set -x
 | 
				
			||||||
dump | compress | aws_upload $(($(estimate_size) / DUMP_SIZE_COEFF))
 | 
					dump | compress | aws_upload $(($(estimate_size) / DUMP_SIZE_COEFF))
 | 
				
			||||||
 | 
					[[ ${PIPESTATUS[0]} != 0 || ${PIPESTATUS[1]} != 0 || ${PIPESTATUS[2]} != 0 ]] && (( ERRORCOUNT += 1 ))
 | 
				
			||||||
 | 
					set +x
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exit $ERRORCOUNT
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,6 +47,12 @@ patching the CRD manifest:
 | 
				
			||||||
zk8 patch crd postgresqls.acid.zalan.do -p '{"spec":{"validation": null}}'
 | 
					zk8 patch crd postgresqls.acid.zalan.do -p '{"spec":{"validation": null}}'
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Non-default cluster domain
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If your cluster uses a DNS domain other than the default `cluster.local`, this
 | 
				
			||||||
 | 
					needs to be set in the operator configuration (`cluster_domain` variable). This
 | 
				
			||||||
 | 
					is used by the operator to connect to the clusters after creation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Namespaces
 | 
					## Namespaces
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Select the namespace to deploy to
 | 
					### Select the namespace to deploy to
 | 
				
			||||||
| 
						 | 
					@ -89,36 +95,13 @@ lacks access rights to any of them (except K8s system namespaces like
 | 
				
			||||||
'list pods' execute at the cluster scope and fail at the first violation of
 | 
					'list pods' execute at the cluster scope and fail at the first violation of
 | 
				
			||||||
access rights.
 | 
					access rights.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The watched namespace also needs to have a (possibly different) service account
 | 
					 | 
				
			||||||
in the case database pods need to talk to the K8s API (e.g. when using
 | 
					 | 
				
			||||||
K8s-native configuration of Patroni). The operator checks that the
 | 
					 | 
				
			||||||
`pod_service_account_name` exists in the target namespace, and, if not, deploys
 | 
					 | 
				
			||||||
there the `pod_service_account_definition` from the operator
 | 
					 | 
				
			||||||
[`Config`](../pkg/util/config/config.go) with the default value of:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```yaml
 | 
					 | 
				
			||||||
apiVersion: v1
 | 
					 | 
				
			||||||
kind: ServiceAccount
 | 
					 | 
				
			||||||
metadata:
 | 
					 | 
				
			||||||
 name: operator
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
In this definition, the operator overwrites the account's name to match
 | 
					 | 
				
			||||||
`pod_service_account_name` and the `default` namespace to match the target
 | 
					 | 
				
			||||||
namespace. The operator performs **no** further syncing of this account.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Non-default cluster domain
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
If your cluster uses a DNS domain other than the default `cluster.local`, this
 | 
					 | 
				
			||||||
needs to be set in the operator configuration (`cluster_domain` variable). This
 | 
					 | 
				
			||||||
is used by the operator to connect to the clusters after creation.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Role-based access control for the operator
 | 
					## Role-based access control for the operator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The manifest [`operator-service-account-rbac.yaml`](../manifests/operator-service-account-rbac.yaml)
 | 
					The manifest [`operator-service-account-rbac.yaml`](../manifests/operator-service-account-rbac.yaml)
 | 
				
			||||||
defines the service account, cluster roles and bindings needed for the operator
 | 
					defines the service account, cluster roles and bindings needed for the operator
 | 
				
			||||||
to function under access control restrictions. To deploy the operator with this
 | 
					to function under access control restrictions. The file also includes a cluster
 | 
				
			||||||
RBAC policy use:
 | 
					role `postgres-pod` with privileges for Patroni to watch and manage pods and
 | 
				
			||||||
 | 
					endpoints. To deploy the operator with this RBAC policies use:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
kubectl create -f manifests/configmap.yaml
 | 
					kubectl create -f manifests/configmap.yaml
 | 
				
			||||||
| 
						 | 
					@ -127,14 +110,14 @@ kubectl create -f manifests/postgres-operator.yaml
 | 
				
			||||||
kubectl create -f manifests/minimal-postgres-manifest.yaml
 | 
					kubectl create -f manifests/minimal-postgres-manifest.yaml
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Service account and cluster roles
 | 
					### Namespaced service account and role binding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Note that the service account is named `zalando-postgres-operator`. You may have
 | 
					For each namespace the operator watches it creates (or reads) a service account
 | 
				
			||||||
to change the `service_account_name` in the operator ConfigMap and
 | 
					and role binding to be used by the Postgres Pods. The service account is bound
 | 
				
			||||||
`serviceAccountName` in the `postgres-operator` deployment appropriately. This
 | 
					to the `postgres-pod` cluster role. The name and definitions of these resources
 | 
				
			||||||
is done intentionally to avoid breaking those setups that already work with the
 | 
					can be [configured](reference/operator_parameters.md#kubernetes-resources).
 | 
				
			||||||
default `operator` account. In the future the operator should ideally be run
 | 
					Note, that the operator performs **no** further syncing of namespaced service
 | 
				
			||||||
under the `zalando-postgres-operator` service account.
 | 
					accounts and role bindings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Give K8s users access to create/list `postgresqls`
 | 
					### Give K8s users access to create/list `postgresqls`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -376,6 +359,17 @@ cluster manifest. In the case any of these variables are omitted from the
 | 
				
			||||||
manifest, the operator configuration settings `enable_master_load_balancer` and
 | 
					manifest, the operator configuration settings `enable_master_load_balancer` and
 | 
				
			||||||
`enable_replica_load_balancer` apply. Note that the operator settings affect
 | 
					`enable_replica_load_balancer` apply. Note that the operator settings affect
 | 
				
			||||||
all Postgresql services running in all namespaces watched by the operator.
 | 
					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:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `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". This value can be overwritten with the operator
 | 
				
			||||||
 | 
					  config parameter `custom_service_annotations` or the  cluster parameter
 | 
				
			||||||
 | 
					  `serviceAnnotations`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
To limit the range of IP addresses that can reach a load balancer, specify the
 | 
					To limit the range of IP addresses that can reach a load balancer, specify the
 | 
				
			||||||
desired ranges in the `allowedSourceRanges` field (applies to both master and
 | 
					desired ranges in the `allowedSourceRanges` field (applies to both master and
 | 
				
			||||||
| 
						 | 
					@ -486,37 +480,71 @@ A secret can be pre-provisioned in different ways:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Setting up the Postgres Operator UI
 | 
					## Setting up the Postgres Operator UI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
With the v1.2 release the Postgres Operator is shipped with a browser-based
 | 
					Since the v1.2 release the Postgres Operator is shipped with a browser-based
 | 
				
			||||||
configuration user interface (UI) that simplifies managing Postgres clusters
 | 
					configuration user interface (UI) that simplifies managing Postgres clusters
 | 
				
			||||||
with the operator. The UI runs with Node.js and comes with it's own Docker
 | 
					with the operator.
 | 
				
			||||||
image.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Run NPM to continuously compile `tags/js` code. Basically, it creates an
 | 
					### Building the UI image
 | 
				
			||||||
`app.js` file in: `static/build/app.js`
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					The UI runs with Node.js and comes with it's own Docker
 | 
				
			||||||
(cd ui/app && npm start)
 | 
					image. However, installing Node.js to build the operator UI is not required. It
 | 
				
			||||||
```
 | 
					is handled via Docker containers when running:
 | 
				
			||||||
 | 
					 | 
				
			||||||
To build the Docker image open a shell and change to the `ui` folder. Then run:
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
docker build -t registry.opensource.zalan.do/acid/postgres-operator-ui:v1.2.0 .
 | 
					make docker
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Apply all manifests for the `ui/manifests` folder to deploy the Postgres
 | 
					### Configure endpoints and options
 | 
				
			||||||
Operator UI on K8s. For local tests you don't need the Ingress resource.
 | 
					
 | 
				
			||||||
 | 
					The UI talks to the K8s API server as well as the Postgres Operator [REST API](developer.md#debugging-the-operator).
 | 
				
			||||||
 | 
					K8s API server URLs are loaded from the machine's kubeconfig environment by
 | 
				
			||||||
 | 
					default. Alternatively, a list can also be passed when starting the Python
 | 
				
			||||||
 | 
					application with the `--cluster` option.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The Operator API endpoint can be configured via the `OPERATOR_API_URL`
 | 
				
			||||||
 | 
					environment variables in the [deployment manifest](../ui/manifests/deployment.yaml#L40).
 | 
				
			||||||
 | 
					You can also expose the operator API through a [service](../manifests/api-service.yaml).
 | 
				
			||||||
 | 
					Some displayed options can be disabled from UI using simple flags under the
 | 
				
			||||||
 | 
					`OPERATOR_UI_CONFIG` field in the deployment.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Deploy the UI on K8s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Now, apply all manifests from the `ui/manifests` folder to deploy the Postgres
 | 
				
			||||||
 | 
					Operator UI on K8s. Replace the image tag in the deployment manifest if you
 | 
				
			||||||
 | 
					want to test the image you've built with `make docker`. Make sure the pods for
 | 
				
			||||||
 | 
					the operator and the UI are both running.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
kubectl apply -f ui/manifests
 | 
					sed -e "s/\(image\:.*\:\).*$/\1$TAG/" manifests/deployment.yaml | kubectl apply -f manifests/
 | 
				
			||||||
 | 
					kubectl get all -l application=postgres-operator-ui
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Make sure the pods for the operator and the UI are both running. For local
 | 
					### Local testing
 | 
				
			||||||
testing you need to apply proxying and port forwarding so that the UI can talk
 | 
					
 | 
				
			||||||
to the K8s and Postgres Operator REST API. You can use the provided
 | 
					For local testing you need to apply K8s proxying and operator pod port
 | 
				
			||||||
`run_local.sh` script for this. Make sure it uses the correct URL to your K8s
 | 
					forwarding so that the UI can talk to the K8s and Postgres Operator REST API.
 | 
				
			||||||
API server, e.g. for minikube it would be `https://192.168.99.100:8443`.
 | 
					The Ingress resource is not needed. You can use the provided `run_local.sh`
 | 
				
			||||||
 | 
					script for this. Make sure that:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Python dependencies are installed on your machine
 | 
				
			||||||
 | 
					* the K8s API server URL is set for kubectl commands, e.g. for minikube it would usually be `https://192.168.99.100:8443`.
 | 
				
			||||||
 | 
					* the pod label selectors for port forwarding are correct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When testing with minikube you have to build the image in its docker environment
 | 
				
			||||||
 | 
					(running `make docker` doesn't do it for you). From the `ui` directory execute:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
 | 
					# compile and build operator UI
 | 
				
			||||||
 | 
					make docker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# build in image in minikube docker env
 | 
				
			||||||
 | 
					eval $(minikube docker-env)
 | 
				
			||||||
 | 
					docker build -t registry.opensource.zalan.do/acid/postgres-operator-ui:v1.3.0 .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# apply UI manifests next to a running Postgres Operator
 | 
				
			||||||
 | 
					kubectl apply -f manifests/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# install python dependencies to run UI locally
 | 
				
			||||||
 | 
					pip3 install -r requirements
 | 
				
			||||||
./run_local.sh
 | 
					./run_local.sh
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,9 +31,13 @@ status page.
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Usually, the startup should only take up to 1 minute. If you feel the process
 | 
					Usually, the startup should only take up to 1 minute. If you feel the process
 | 
				
			||||||
got stuck click on the "Logs" button to inspect the operator logs. From the
 | 
					got stuck click on the "Logs" button to inspect the operator logs. If the logs
 | 
				
			||||||
"Status" field in the top menu you can also retrieve the logs and queue of each
 | 
					look fine, but the UI seems to got stuck, check if you are have configured the
 | 
				
			||||||
worker the operator is using. The number of concurrent workers can be
 | 
					same [cluster name label](../ui/manifests/deployment.yaml#L45) like for the
 | 
				
			||||||
 | 
					[operator](../manifests/configmap.yaml#L13).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					From the "Status" field in the top menu you can also retrieve the logs and queue
 | 
				
			||||||
 | 
					of each worker the operator is using. The number of concurrent workers can be
 | 
				
			||||||
[configured](reference/operator_parameters.md#general).
 | 
					[configured](reference/operator_parameters.md#general).
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,6 +52,7 @@ cd postgres-operator
 | 
				
			||||||
kubectl create -f manifests/configmap.yaml  # configuration
 | 
					kubectl create -f manifests/configmap.yaml  # configuration
 | 
				
			||||||
kubectl create -f manifests/operator-service-account-rbac.yaml  # identity and permissions
 | 
					kubectl create -f manifests/operator-service-account-rbac.yaml  # identity and permissions
 | 
				
			||||||
kubectl create -f manifests/postgres-operator.yaml  # deployment
 | 
					kubectl create -f manifests/postgres-operator.yaml  # deployment
 | 
				
			||||||
 | 
					kubectl create -f manifests/api-service.yaml  # operator API to be used by UI
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
There is a [Kustomization](https://github.com/kubernetes-sigs/kustomize)
 | 
					There is a [Kustomization](https://github.com/kubernetes-sigs/kustomize)
 | 
				
			||||||
| 
						 | 
					@ -104,7 +105,7 @@ kubectl create -f https://operatorhub.io/install/postgres-operator.yaml
 | 
				
			||||||
This installs the operator in the `operators` namespace. More information can be
 | 
					This installs the operator in the `operators` namespace. More information can be
 | 
				
			||||||
found on [operatorhub.io](https://operatorhub.io/operator/postgres-operator).
 | 
					found on [operatorhub.io](https://operatorhub.io/operator/postgres-operator).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Create a Postgres cluster
 | 
					## Check if Postgres Operator is running
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Starting the operator may take a few seconds. Check if the operator pod is
 | 
					Starting the operator may take a few seconds. Check if the operator pod is
 | 
				
			||||||
running before applying a Postgres cluster manifest.
 | 
					running before applying a Postgres cluster manifest.
 | 
				
			||||||
| 
						 | 
					@ -115,7 +116,61 @@ kubectl get pod -l name=postgres-operator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# if you've created the operator using helm chart
 | 
					# if you've created the operator using helm chart
 | 
				
			||||||
kubectl get pod -l app.kubernetes.io/name=postgres-operator
 | 
					kubectl get pod -l app.kubernetes.io/name=postgres-operator
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If the operator doesn't get into `Running` state, either check the latest K8s
 | 
				
			||||||
 | 
					events of the deployment or pod with `kubectl describe` or inspect the operator
 | 
				
			||||||
 | 
					logs:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					kubectl logs "$(kubectl get pod -l name=postgres-operator --output='name')"
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Deploy the operator UI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In the following paragraphs we describe how to access and manage PostgreSQL
 | 
				
			||||||
 | 
					clusters from the command line with kubectl. But it can also be done from the
 | 
				
			||||||
 | 
					browser-based [Postgres Operator UI](operator-ui.md). Before deploying the UI
 | 
				
			||||||
 | 
					make sure the operator is running and its REST API is reachable through a
 | 
				
			||||||
 | 
					[K8s service](../manifests/api-service.yaml). The URL to this API must be
 | 
				
			||||||
 | 
					configured in the [deployment manifest](../ui/manifests/deployment.yaml#L43)
 | 
				
			||||||
 | 
					of the UI.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To deploy the UI simply apply all its manifests files or use the UI helm chart:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# manual deployment
 | 
				
			||||||
 | 
					kubectl apply -f ui/manifests/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# or helm chart
 | 
				
			||||||
 | 
					helm install postgres-operator-ui ./charts/postgres-operator-ui
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Like with the operator, check if the UI pod gets into `Running` state:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# if you've created the operator using yaml manifests
 | 
				
			||||||
 | 
					kubectl get pod -l name=postgres-operator-ui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# if you've created the operator using helm chart
 | 
				
			||||||
 | 
					kubectl get pod -l app.kubernetes.io/name=postgres-operator-ui
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can now access the web interface by port forwarding the UI pod (mind the
 | 
				
			||||||
 | 
					label selector) and enter `localhost:8081` in your browser:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					kubectl port-forward "$(kubectl get pod -l name=postgres-operator-ui --output='name')" 8081
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Available option are explained in detail in the [UI docs](operator-ui.md).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Create a Postgres cluster
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If the operator pod is running it listens to new events regarding `postgresql`
 | 
				
			||||||
 | 
					resources. Now, it's time to submit your first Postgres cluster manifest.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
# create a Postgres cluster
 | 
					# create a Postgres cluster
 | 
				
			||||||
kubectl create -f manifests/minimal-postgres-manifest.yaml
 | 
					kubectl create -f manifests/minimal-postgres-manifest.yaml
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -122,6 +122,11 @@ These parameters are grouped directly under  the `spec` key in the manifest.
 | 
				
			||||||
  A map of key value pairs that gets attached as [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)
 | 
					  A map of key value pairs that gets attached as [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)
 | 
				
			||||||
  to each pod created for the database.
 | 
					  to each pod created for the database.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **serviceAnnotations**
 | 
				
			||||||
 | 
					  A map of key value pairs that gets attached as [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)
 | 
				
			||||||
 | 
					  to the services created for the database cluster. Check the
 | 
				
			||||||
 | 
					  [administrator docs](https://github.com/zalando/postgres-operator/blob/master/docs/administrator.md#load-balancers-and-allowed-ip-ranges)
 | 
				
			||||||
 | 
					  for more information regarding default values and overwrite rules.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **enableShmVolume**
 | 
					* **enableShmVolume**
 | 
				
			||||||
  Start a database pod without limitations on shm memory. By default Docker
 | 
					  Start a database pod without limitations on shm memory. By default Docker
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -152,21 +152,22 @@ configuration they are grouped under the `kubernetes` key.
 | 
				
			||||||
  service account used by Patroni running on individual Pods to communicate
 | 
					  service account used by Patroni running on individual Pods to communicate
 | 
				
			||||||
  with the operator. Required even if native Kubernetes support in Patroni is
 | 
					  with the operator. Required even if native Kubernetes support in Patroni is
 | 
				
			||||||
  not used, because Patroni keeps pod labels in sync with the instance role.
 | 
					  not used, because Patroni keeps pod labels in sync with the instance role.
 | 
				
			||||||
  The default is `operator`.
 | 
					  The default is `postgres-pod`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **pod_service_account_definition**
 | 
					* **pod_service_account_definition**
 | 
				
			||||||
  The operator tries to create the pod Service Account in the namespace that
 | 
					  On Postgres cluster creation the operator tries to create the service account
 | 
				
			||||||
  doesn't define such an account using the YAML definition provided by this
 | 
					  for the Postgres pods if it does not exist in the namespace. The internal
 | 
				
			||||||
  option. If not defined, a simple definition that contains only the name will
 | 
					  default service account definition (defines only the name) can be overwritten
 | 
				
			||||||
  be used. The default is empty.
 | 
					  with this parameter. Make sure to provide a valid YAML or JSON string. The
 | 
				
			||||||
 | 
					  default is empty.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **pod_service_account_role_binding_definition**
 | 
					* **pod_service_account_role_binding_definition**
 | 
				
			||||||
  This definition must bind pod service account to a role with permission
 | 
					  This definition must bind the pod service account to a role with permission
 | 
				
			||||||
  sufficient for the pods to start and for Patroni to access K8s endpoints;
 | 
					  sufficient for the pods to start and for Patroni to access K8s endpoints;
 | 
				
			||||||
  service account on its own lacks any such rights starting with K8s v1.8. If
 | 
					  service account on its own lacks any such rights starting with K8s v1.8. If
 | 
				
			||||||
  not explicitly defined by the user, a simple definition that binds the
 | 
					  not explicitly defined by the user, a simple definition that binds the
 | 
				
			||||||
  account to the operator's own 'zalando-postgres-operator' cluster role will
 | 
					  account to the 'postgres-pod' [cluster role](../../manifests/operator-service-account-rbac.yaml#L198)
 | 
				
			||||||
  be used. The default is empty.
 | 
					  will be used. The default is empty.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **pod_terminate_grace_period**
 | 
					* **pod_terminate_grace_period**
 | 
				
			||||||
  Postgres pods are [terminated forcefully](https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods)
 | 
					  Postgres pods are [terminated forcefully](https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods)
 | 
				
			||||||
| 
						 | 
					@ -318,11 +319,19 @@ CRD-based configuration.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **default_cpu_limit**
 | 
					* **default_cpu_limit**
 | 
				
			||||||
  CPU limits for the Postgres containers, unless overridden by cluster-specific
 | 
					  CPU limits for the Postgres containers, unless overridden by cluster-specific
 | 
				
			||||||
  settings. The default is `3`.
 | 
					  settings. The default is `1`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **default_memory_limit**
 | 
					* **default_memory_limit**
 | 
				
			||||||
  memory limits for the Postgres containers, unless overridden by cluster-specific
 | 
					  memory limits for the Postgres containers, unless overridden by cluster-specific
 | 
				
			||||||
  settings. The default is `1Gi`.
 | 
					  settings. The default is `500Mi`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **min_cpu_limit**
 | 
				
			||||||
 | 
					  hard CPU minimum what we consider to be required to properly run Postgres
 | 
				
			||||||
 | 
					  clusters with Patroni on Kubernetes. The default is `250m`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **min_memory_limit**
 | 
				
			||||||
 | 
					  hard memory minimum what we consider to be required to properly run Postgres
 | 
				
			||||||
 | 
					  clusters with Patroni on Kubernetes. The default is `250Mi`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Operator timeouts
 | 
					## Operator timeouts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -380,8 +389,9 @@ In the CRD-based configuration they are grouped under the `load_balancer` key.
 | 
				
			||||||
  `false`.
 | 
					  `false`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **custom_service_annotations**
 | 
					* **custom_service_annotations**
 | 
				
			||||||
  when load balancing is enabled, LoadBalancer service is created and
 | 
					  This key/value map provides a list of annotations that get attached to each
 | 
				
			||||||
  this parameter takes service annotations that are applied to service.
 | 
					  service of a cluster created by the operator. If the annotation key is also
 | 
				
			||||||
 | 
					  provided by the cluster definition, the manifest value is used.
 | 
				
			||||||
  Optional.
 | 
					  Optional.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **master_dns_name_format** defines the DNS name string template for the
 | 
					* **master_dns_name_format** defines the DNS name string template for the
 | 
				
			||||||
| 
						 | 
					@ -453,8 +463,11 @@ grouped under the `logical_backup` key.
 | 
				
			||||||
  S3 bucket to store backup results. The bucket has to be present and
 | 
					  S3 bucket to store backup results. The bucket has to be present and
 | 
				
			||||||
  accessible by Postgres pods. Default: empty.
 | 
					  accessible by Postgres pods. Default: empty.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **logical_backup_s3_region**
 | 
				
			||||||
 | 
					  Specifies the region of the bucket which is required with some non-AWS S3 storage services. The default is empty.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **logical_backup_s3_endpoint**
 | 
					* **logical_backup_s3_endpoint**
 | 
				
			||||||
  When using non-AWS S3 storage, endpoint can be set as a ENV variable.
 | 
					  When using non-AWS S3 storage, endpoint can be set as a ENV variable. The default is empty.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **logical_backup_s3_sse**
 | 
					* **logical_backup_s3_sse**
 | 
				
			||||||
  Specify server side encription that S3 storage is using. If empty string
 | 
					  Specify server side encription that S3 storage is using. If empty string
 | 
				
			||||||
| 
						 | 
					@ -579,4 +592,4 @@ scalyr sidecar. In the CRD-based configuration they are grouped under the
 | 
				
			||||||
  CPU limit value for the Scalyr sidecar. The default is `1`.
 | 
					  CPU limit value for the Scalyr sidecar. The default is `1`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **scalyr_memory_limit**
 | 
					* **scalyr_memory_limit**
 | 
				
			||||||
  Memory limit value for the Scalyr sidecar. The default is `1Gi`.
 | 
					  Memory limit value for the Scalyr sidecar. The default is `500Mi`.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										12
									
								
								docs/user.md
								
								
								
								
							
							
						
						
									
										12
									
								
								docs/user.md
								
								
								
								
							| 
						 | 
					@ -65,7 +65,7 @@ our test cluster.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
# get name of master pod of acid-minimal-cluster
 | 
					# get name of master pod of acid-minimal-cluster
 | 
				
			||||||
export PGMASTER=$(kubectl get pods -o jsonpath={.items..metadata.name} -l application=spilo,version=acid-minimal-cluster,spilo-role=master)
 | 
					export PGMASTER=$(kubectl get pods -o jsonpath={.items..metadata.name} -l application=spilo,cluster-name=acid-minimal-cluster,spilo-role=master)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# set up port forward
 | 
					# set up port forward
 | 
				
			||||||
kubectl port-forward $PGMASTER 6432:5432
 | 
					kubectl port-forward $PGMASTER 6432:5432
 | 
				
			||||||
| 
						 | 
					@ -232,11 +232,11 @@ spec:
 | 
				
			||||||
      memory: 300Mi
 | 
					      memory: 300Mi
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The minimum limit to properly run the `postgresql` resource is `256m` for `cpu`
 | 
					The minimum limits to properly run the `postgresql` resource are configured to
 | 
				
			||||||
and `256Mi` for `memory`. If a lower value is set in the manifest the operator
 | 
					`250m` for `cpu` and `250Mi` for `memory`. If a lower value is set in the
 | 
				
			||||||
will cancel ADD or UPDATE events on this resource with an error. If no
 | 
					manifest the operator will raise the limits to the configured minimum values.
 | 
				
			||||||
resources are defined in the manifest the operator will obtain the configured
 | 
					If no resources are defined in the manifest they will be obtained from the
 | 
				
			||||||
[default requests](reference/operator_parameters.md#kubernetes-resource-requests).
 | 
					configured [default requests](reference/operator_parameters.md#kubernetes-resource-requests).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Use taints and tolerations for dedicated PostgreSQL nodes
 | 
					## Use taints and tolerations for dedicated PostgreSQL nodes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,3 +44,4 @@ The current tests are all bundled in [`test_e2e.py`](tests/test_e2e.py):
 | 
				
			||||||
* taint-based eviction of Postgres pods
 | 
					* taint-based eviction of Postgres pods
 | 
				
			||||||
* invoking logical backup cron job
 | 
					* invoking logical backup cron job
 | 
				
			||||||
* uniqueness of master pod
 | 
					* uniqueness of master pod
 | 
				
			||||||
 | 
					* custom service annotations
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,6 +58,106 @@ class EndToEndTestCase(unittest.TestCase):
 | 
				
			||||||
        k8s.create_with_kubectl("manifests/minimal-postgres-manifest.yaml")
 | 
					        k8s.create_with_kubectl("manifests/minimal-postgres-manifest.yaml")
 | 
				
			||||||
        k8s.wait_for_pod_start('spilo-role=master')
 | 
					        k8s.wait_for_pod_start('spilo-role=master')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @timeout_decorator.timeout(TEST_TIMEOUT_SEC)
 | 
				
			||||||
 | 
					    def test_enable_load_balancer(self):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Test if services are updated when enabling/disabling load balancers
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        k8s = self.k8s
 | 
				
			||||||
 | 
					        cluster_label = 'cluster-name=acid-minimal-cluster'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # enable load balancer services
 | 
				
			||||||
 | 
					        pg_patch_enable_lbs = {
 | 
				
			||||||
 | 
					            "spec": {
 | 
				
			||||||
 | 
					                "enableMasterLoadBalancer": True,
 | 
				
			||||||
 | 
					                "enableReplicaLoadBalancer": True
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        k8s.api.custom_objects_api.patch_namespaced_custom_object(
 | 
				
			||||||
 | 
					            "acid.zalan.do", "v1", "default", "postgresqls", "acid-minimal-cluster", pg_patch_enable_lbs)
 | 
				
			||||||
 | 
					        # wait for service recreation
 | 
				
			||||||
 | 
					        time.sleep(60)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        master_svc_type = k8s.get_service_type(cluster_label + ',spilo-role=master')
 | 
				
			||||||
 | 
					        self.assertEqual(master_svc_type, 'LoadBalancer',
 | 
				
			||||||
 | 
					                         "Expected LoadBalancer service type for master, found {}".format(master_svc_type))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        repl_svc_type = k8s.get_service_type(cluster_label + ',spilo-role=replica')
 | 
				
			||||||
 | 
					        self.assertEqual(repl_svc_type, 'LoadBalancer',
 | 
				
			||||||
 | 
					                         "Expected LoadBalancer service type for replica, found {}".format(repl_svc_type))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # disable load balancer services again
 | 
				
			||||||
 | 
					        pg_patch_disable_lbs = {
 | 
				
			||||||
 | 
					            "spec": {
 | 
				
			||||||
 | 
					                "enableMasterLoadBalancer": False,
 | 
				
			||||||
 | 
					                "enableReplicaLoadBalancer": False
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        k8s.api.custom_objects_api.patch_namespaced_custom_object(
 | 
				
			||||||
 | 
					            "acid.zalan.do", "v1", "default", "postgresqls", "acid-minimal-cluster", pg_patch_disable_lbs)
 | 
				
			||||||
 | 
					        # wait for service recreation
 | 
				
			||||||
 | 
					        time.sleep(60)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        master_svc_type = k8s.get_service_type(cluster_label + ',spilo-role=master')
 | 
				
			||||||
 | 
					        self.assertEqual(master_svc_type, 'ClusterIP',
 | 
				
			||||||
 | 
					                         "Expected ClusterIP service type for master, found {}".format(master_svc_type))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        repl_svc_type = k8s.get_service_type(cluster_label + ',spilo-role=replica')
 | 
				
			||||||
 | 
					        self.assertEqual(repl_svc_type, 'ClusterIP',
 | 
				
			||||||
 | 
					                         "Expected ClusterIP service type for replica, found {}".format(repl_svc_type))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @timeout_decorator.timeout(TEST_TIMEOUT_SEC)
 | 
				
			||||||
 | 
					    def test_min_resource_limits(self):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Lower resource limits below configured minimum and let operator fix it
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        k8s = self.k8s
 | 
				
			||||||
 | 
					        cluster_label = 'cluster-name=acid-minimal-cluster'
 | 
				
			||||||
 | 
					        _, failover_targets = k8s.get_pg_nodes(cluster_label)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # configure minimum boundaries for CPU and memory limits
 | 
				
			||||||
 | 
					        minCPULimit = '500m'
 | 
				
			||||||
 | 
					        minMemoryLimit = '500Mi'
 | 
				
			||||||
 | 
					        patch_min_resource_limits = {
 | 
				
			||||||
 | 
					            "data": {
 | 
				
			||||||
 | 
					                "min_cpu_limit": minCPULimit,
 | 
				
			||||||
 | 
					                "min_memory_limit": minMemoryLimit
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        k8s.update_config(patch_min_resource_limits)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # lower resource limits below minimum
 | 
				
			||||||
 | 
					        pg_patch_resources = {
 | 
				
			||||||
 | 
					            "spec": {
 | 
				
			||||||
 | 
					                "resources": {
 | 
				
			||||||
 | 
					                    "requests": {
 | 
				
			||||||
 | 
					                        "cpu": "10m",
 | 
				
			||||||
 | 
					                        "memory": "50Mi"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "limits": {
 | 
				
			||||||
 | 
					                        "cpu": "200m",
 | 
				
			||||||
 | 
					                        "memory": "200Mi"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        k8s.api.custom_objects_api.patch_namespaced_custom_object(
 | 
				
			||||||
 | 
					            "acid.zalan.do", "v1", "default", "postgresqls", "acid-minimal-cluster", pg_patch_resources)
 | 
				
			||||||
 | 
					        k8s.wait_for_master_failover(failover_targets)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pods = k8s.api.core_v1.list_namespaced_pod(
 | 
				
			||||||
 | 
					            'default', label_selector='spilo-role=master,' + cluster_label).items
 | 
				
			||||||
 | 
					        self.assert_master_is_unique()
 | 
				
			||||||
 | 
					        masterPod = pods[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(masterPod.spec.containers[0].resources.limits['cpu'], minCPULimit,
 | 
				
			||||||
 | 
					                         "Expected CPU limit {}, found {}"
 | 
				
			||||||
 | 
					                         .format(minCPULimit, masterPod.spec.containers[0].resources.limits['cpu']))
 | 
				
			||||||
 | 
					        self.assertEqual(masterPod.spec.containers[0].resources.limits['memory'], minMemoryLimit,
 | 
				
			||||||
 | 
					                         "Expected memory limit {}, found {}"
 | 
				
			||||||
 | 
					                         .format(minMemoryLimit, masterPod.spec.containers[0].resources.limits['memory']))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @timeout_decorator.timeout(TEST_TIMEOUT_SEC)
 | 
					    @timeout_decorator.timeout(TEST_TIMEOUT_SEC)
 | 
				
			||||||
    def test_multi_namespace_support(self):
 | 
					    def test_multi_namespace_support(self):
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
| 
						 | 
					@ -72,16 +172,15 @@ class EndToEndTestCase(unittest.TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        k8s.create_with_kubectl("manifests/complete-postgres-manifest.yaml")
 | 
					        k8s.create_with_kubectl("manifests/complete-postgres-manifest.yaml")
 | 
				
			||||||
        k8s.wait_for_pod_start("spilo-role=master", self.namespace)
 | 
					        k8s.wait_for_pod_start("spilo-role=master", self.namespace)
 | 
				
			||||||
        self.assert_master_is_unique(self.namespace, version="acid-test-cluster")
 | 
					        self.assert_master_is_unique(self.namespace, "acid-test-cluster")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @timeout_decorator.timeout(TEST_TIMEOUT_SEC)
 | 
					    @timeout_decorator.timeout(TEST_TIMEOUT_SEC)
 | 
				
			||||||
    def test_scaling(self):
 | 
					    def test_scaling(self):
 | 
				
			||||||
        """
 | 
					        '''
 | 
				
			||||||
           Scale up from 2 to 3 and back to 2 pods by updating the Postgres manifest at runtime.
 | 
					           Scale up from 2 to 3 and back to 2 pods by updating the Postgres manifest at runtime.
 | 
				
			||||||
        """
 | 
					        '''
 | 
				
			||||||
 | 
					 | 
				
			||||||
        k8s = self.k8s
 | 
					        k8s = self.k8s
 | 
				
			||||||
        labels = "version=acid-minimal-cluster"
 | 
					        labels = "cluster-name=acid-minimal-cluster"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        k8s.wait_for_pg_to_scale(3)
 | 
					        k8s.wait_for_pg_to_scale(3)
 | 
				
			||||||
        self.assertEqual(3, k8s.count_pods_with_label(labels))
 | 
					        self.assertEqual(3, k8s.count_pods_with_label(labels))
 | 
				
			||||||
| 
						 | 
					@ -93,11 +192,11 @@ class EndToEndTestCase(unittest.TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @timeout_decorator.timeout(TEST_TIMEOUT_SEC)
 | 
					    @timeout_decorator.timeout(TEST_TIMEOUT_SEC)
 | 
				
			||||||
    def test_taint_based_eviction(self):
 | 
					    def test_taint_based_eviction(self):
 | 
				
			||||||
        """
 | 
					        '''
 | 
				
			||||||
           Add taint "postgres=:NoExecute" to node with master. This must cause a failover.
 | 
					           Add taint "postgres=:NoExecute" to node with master. This must cause a failover.
 | 
				
			||||||
        """
 | 
					        '''
 | 
				
			||||||
        k8s = self.k8s
 | 
					        k8s = self.k8s
 | 
				
			||||||
        cluster_label = 'version=acid-minimal-cluster'
 | 
					        cluster_label = 'cluster-name=acid-minimal-cluster'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # get nodes of master and replica(s) (expected target of new master)
 | 
					        # get nodes of master and replica(s) (expected target of new master)
 | 
				
			||||||
        current_master_node, failover_targets = k8s.get_pg_nodes(cluster_label)
 | 
					        current_master_node, failover_targets = k8s.get_pg_nodes(cluster_label)
 | 
				
			||||||
| 
						 | 
					@ -126,7 +225,7 @@ class EndToEndTestCase(unittest.TestCase):
 | 
				
			||||||
        # patch node and test if master is failing over to one of the expected nodes
 | 
					        # patch node and test if master is failing over to one of the expected nodes
 | 
				
			||||||
        k8s.api.core_v1.patch_node(current_master_node, body)
 | 
					        k8s.api.core_v1.patch_node(current_master_node, body)
 | 
				
			||||||
        k8s.wait_for_master_failover(failover_targets)
 | 
					        k8s.wait_for_master_failover(failover_targets)
 | 
				
			||||||
        k8s.wait_for_pod_start('spilo-role=replica')
 | 
					        k8s.wait_for_pod_start('spilo-role=replica,' + cluster_label)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        new_master_node, new_replica_nodes = k8s.get_pg_nodes(cluster_label)
 | 
					        new_master_node, new_replica_nodes = k8s.get_pg_nodes(cluster_label)
 | 
				
			||||||
        self.assertNotEqual(current_master_node, new_master_node,
 | 
					        self.assertNotEqual(current_master_node, new_master_node,
 | 
				
			||||||
| 
						 | 
					@ -145,7 +244,7 @@ class EndToEndTestCase(unittest.TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @timeout_decorator.timeout(TEST_TIMEOUT_SEC)
 | 
					    @timeout_decorator.timeout(TEST_TIMEOUT_SEC)
 | 
				
			||||||
    def test_logical_backup_cron_job(self):
 | 
					    def test_logical_backup_cron_job(self):
 | 
				
			||||||
        """
 | 
					        '''
 | 
				
			||||||
        Ensure we can (a) create the cron job at user request for a specific PG cluster
 | 
					        Ensure we can (a) create the cron job at user request for a specific PG cluster
 | 
				
			||||||
                      (b) update the cluster-wide image for the logical backup pod
 | 
					                      (b) update the cluster-wide image for the logical backup pod
 | 
				
			||||||
                      (c) delete the job at user request
 | 
					                      (c) delete the job at user request
 | 
				
			||||||
| 
						 | 
					@ -153,7 +252,7 @@ class EndToEndTestCase(unittest.TestCase):
 | 
				
			||||||
        Limitations:
 | 
					        Limitations:
 | 
				
			||||||
        (a) Does not run the actual batch job because there is no S3 mock to upload backups to
 | 
					        (a) Does not run the actual batch job because there is no S3 mock to upload backups to
 | 
				
			||||||
        (b) Assumes 'acid-minimal-cluster' exists as defined in setUp
 | 
					        (b) Assumes 'acid-minimal-cluster' exists as defined in setUp
 | 
				
			||||||
        """
 | 
					        '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        k8s = self.k8s
 | 
					        k8s = self.k8s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -207,14 +306,54 @@ class EndToEndTestCase(unittest.TestCase):
 | 
				
			||||||
        self.assertEqual(0, len(jobs),
 | 
					        self.assertEqual(0, len(jobs),
 | 
				
			||||||
                         "Expected 0 logical backup jobs, found {}".format(len(jobs)))
 | 
					                         "Expected 0 logical backup jobs, found {}".format(len(jobs)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def assert_master_is_unique(self, namespace='default', version="acid-minimal-cluster"):
 | 
					    @timeout_decorator.timeout(TEST_TIMEOUT_SEC)
 | 
				
			||||||
        """
 | 
					    def test_service_annotations(self):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Create a Postgres cluster with service annotations and check them.
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        k8s = self.k8s
 | 
				
			||||||
 | 
					        patch_custom_service_annotations = {
 | 
				
			||||||
 | 
					            "data": {
 | 
				
			||||||
 | 
					                "custom_service_annotations": "foo:bar",
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        k8s.update_config(patch_custom_service_annotations)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pg_patch_custom_annotations = {
 | 
				
			||||||
 | 
					            "spec": {
 | 
				
			||||||
 | 
					                "serviceAnnotations": {
 | 
				
			||||||
 | 
					                    "annotation.key": "value"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        k8s.api.custom_objects_api.patch_namespaced_custom_object(
 | 
				
			||||||
 | 
					            "acid.zalan.do", "v1", "default", "postgresqls", "acid-minimal-cluster", pg_patch_custom_annotations)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        annotations = {
 | 
				
			||||||
 | 
					            "annotation.key": "value",
 | 
				
			||||||
 | 
					            "foo": "bar",
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        self.assertTrue(k8s.check_service_annotations(
 | 
				
			||||||
 | 
					            "cluster-name=acid-service-annotations,spilo-role=master", annotations))
 | 
				
			||||||
 | 
					        self.assertTrue(k8s.check_service_annotations(
 | 
				
			||||||
 | 
					            "cluster-name=acid-service-annotations,spilo-role=replica", annotations))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # clean up
 | 
				
			||||||
 | 
					        unpatch_custom_service_annotations = {
 | 
				
			||||||
 | 
					            "data": {
 | 
				
			||||||
 | 
					                "custom_service_annotations": "",
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        k8s.update_config(unpatch_custom_service_annotations)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def assert_master_is_unique(self, namespace='default', clusterName="acid-minimal-cluster"):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
           Check that there is a single pod in the k8s cluster with the label "spilo-role=master"
 | 
					           Check that there is a single pod in the k8s cluster with the label "spilo-role=master"
 | 
				
			||||||
           To be called manually after operations that affect pods
 | 
					           To be called manually after operations that affect pods
 | 
				
			||||||
        """
 | 
					        '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        k8s = self.k8s
 | 
					        k8s = self.k8s
 | 
				
			||||||
        labels = 'spilo-role=master,version=' + version
 | 
					        labels = 'spilo-role=master,cluster-name=' + clusterName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        num_of_master_pods = k8s.count_pods_with_label(labels, namespace)
 | 
					        num_of_master_pods = k8s.count_pods_with_label(labels, namespace)
 | 
				
			||||||
        self.assertEqual(num_of_master_pods, 1, "Expected 1 master pod, found {}".format(num_of_master_pods))
 | 
					        self.assertEqual(num_of_master_pods, 1, "Expected 1 master pod, found {}".format(num_of_master_pods))
 | 
				
			||||||
| 
						 | 
					@ -272,6 +411,23 @@ class K8s:
 | 
				
			||||||
                pod_phase = pods[0].status.phase
 | 
					                pod_phase = pods[0].status.phase
 | 
				
			||||||
            time.sleep(self.RETRY_TIMEOUT_SEC)
 | 
					            time.sleep(self.RETRY_TIMEOUT_SEC)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_service_type(self, svc_labels, namespace='default'):
 | 
				
			||||||
 | 
					        svc_type = ''
 | 
				
			||||||
 | 
					        svcs = self.api.core_v1.list_namespaced_service(namespace, label_selector=svc_labels, limit=1).items
 | 
				
			||||||
 | 
					        for svc in svcs:
 | 
				
			||||||
 | 
					            svc_type = svc.spec.type
 | 
				
			||||||
 | 
					        return svc_type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def check_service_annotations(self, svc_labels, annotations, namespace='default'):
 | 
				
			||||||
 | 
					        svcs = self.api.core_v1.list_namespaced_service(namespace, label_selector=svc_labels, limit=1).items
 | 
				
			||||||
 | 
					        for svc in svcs:
 | 
				
			||||||
 | 
					            if len(svc.metadata.annotations) != len(annotations):
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					            for key in svc.metadata.annotations:
 | 
				
			||||||
 | 
					                if svc.metadata.annotations[key] != annotations[key]:
 | 
				
			||||||
 | 
					                    return False
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def wait_for_pg_to_scale(self, number_of_instances, namespace='default'):
 | 
					    def wait_for_pg_to_scale(self, number_of_instances, namespace='default'):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        body = {
 | 
					        body = {
 | 
				
			||||||
| 
						 | 
					@ -282,7 +438,7 @@ class K8s:
 | 
				
			||||||
        _ = self.api.custom_objects_api.patch_namespaced_custom_object(
 | 
					        _ = self.api.custom_objects_api.patch_namespaced_custom_object(
 | 
				
			||||||
            "acid.zalan.do", "v1", namespace, "postgresqls", "acid-minimal-cluster", body)
 | 
					            "acid.zalan.do", "v1", namespace, "postgresqls", "acid-minimal-cluster", body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        labels = 'version=acid-minimal-cluster'
 | 
					        labels = 'cluster-name=acid-minimal-cluster'
 | 
				
			||||||
        while self.count_pods_with_label(labels) != number_of_instances:
 | 
					        while self.count_pods_with_label(labels) != number_of_instances:
 | 
				
			||||||
            time.sleep(self.RETRY_TIMEOUT_SEC)
 | 
					            time.sleep(self.RETRY_TIMEOUT_SEC)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -292,7 +448,7 @@ class K8s:
 | 
				
			||||||
    def wait_for_master_failover(self, expected_master_nodes, namespace='default'):
 | 
					    def wait_for_master_failover(self, expected_master_nodes, namespace='default'):
 | 
				
			||||||
        pod_phase = 'Failing over'
 | 
					        pod_phase = 'Failing over'
 | 
				
			||||||
        new_master_node = ''
 | 
					        new_master_node = ''
 | 
				
			||||||
        labels = 'spilo-role=master,version=acid-minimal-cluster'
 | 
					        labels = 'spilo-role=master,cluster-name=acid-minimal-cluster'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        while (pod_phase != 'Running') or (new_master_node not in expected_master_nodes):
 | 
					        while (pod_phase != 'Running') or (new_master_node not in expected_master_nodes):
 | 
				
			||||||
            pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector=labels).items
 | 
					            pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector=labels).items
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					apiVersion: v1
 | 
				
			||||||
 | 
					kind: Service
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  name: postgres-operator
 | 
				
			||||||
 | 
					spec:
 | 
				
			||||||
 | 
					  type: ClusterIP
 | 
				
			||||||
 | 
					  ports:
 | 
				
			||||||
 | 
					  - port: 8080
 | 
				
			||||||
 | 
					    protocol: TCP
 | 
				
			||||||
 | 
					    targetPort: 8080
 | 
				
			||||||
 | 
					  selector:
 | 
				
			||||||
 | 
					    name: postgres-operator
 | 
				
			||||||
| 
						 | 
					@ -43,6 +43,8 @@ spec:
 | 
				
			||||||
#  spiloFSGroup: 103
 | 
					#  spiloFSGroup: 103
 | 
				
			||||||
#  podAnnotations:
 | 
					#  podAnnotations:
 | 
				
			||||||
#    annotation.key: value
 | 
					#    annotation.key: value
 | 
				
			||||||
 | 
					#  serviceAnnotations:
 | 
				
			||||||
 | 
					#    annotation.key: value
 | 
				
			||||||
#  podPriorityClassName: "spilo-pod-priority"
 | 
					#  podPriorityClassName: "spilo-pod-priority"
 | 
				
			||||||
#  tolerations:
 | 
					#  tolerations:
 | 
				
			||||||
#  - key: postgres
 | 
					#  - key: postgres
 | 
				
			||||||
| 
						 | 
					@ -53,8 +55,8 @@ spec:
 | 
				
			||||||
      cpu: 10m
 | 
					      cpu: 10m
 | 
				
			||||||
      memory: 100Mi
 | 
					      memory: 100Mi
 | 
				
			||||||
    limits:
 | 
					    limits:
 | 
				
			||||||
      cpu: 300m
 | 
					      cpu: 500m
 | 
				
			||||||
      memory: 300Mi
 | 
					      memory: 500Mi
 | 
				
			||||||
  patroni:
 | 
					  patroni:
 | 
				
			||||||
    initdb:
 | 
					    initdb:
 | 
				
			||||||
      encoding: "UTF8"
 | 
					      encoding: "UTF8"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,14 +10,14 @@ data:
 | 
				
			||||||
  cluster_domain: cluster.local
 | 
					  cluster_domain: cluster.local
 | 
				
			||||||
  cluster_history_entries: "1000"
 | 
					  cluster_history_entries: "1000"
 | 
				
			||||||
  cluster_labels: application:spilo
 | 
					  cluster_labels: application:spilo
 | 
				
			||||||
  cluster_name_label: version
 | 
					  cluster_name_label: cluster-name
 | 
				
			||||||
  # custom_service_annotations: "keyx:valuez,keya:valuea"
 | 
					  # custom_service_annotations: "keyx:valuez,keya:valuea"
 | 
				
			||||||
  # custom_pod_annotations: "keya:valuea,keyb:valueb"
 | 
					  # custom_pod_annotations: "keya:valuea,keyb:valueb"
 | 
				
			||||||
  db_hosted_zone: db.example.com
 | 
					  db_hosted_zone: db.example.com
 | 
				
			||||||
  debug_logging: "true"
 | 
					  debug_logging: "true"
 | 
				
			||||||
  # default_cpu_limit: "3"
 | 
					  # default_cpu_limit: "1"
 | 
				
			||||||
  # default_cpu_request: 100m
 | 
					  # default_cpu_request: 100m
 | 
				
			||||||
  # default_memory_limit: 1Gi
 | 
					  # default_memory_limit: 500Mi
 | 
				
			||||||
  # default_memory_request: 100Mi
 | 
					  # default_memory_request: 100Mi
 | 
				
			||||||
  docker_image: registry.opensource.zalan.do/acid/spilo-cdp-12:1.6-p16
 | 
					  docker_image: registry.opensource.zalan.do/acid/spilo-cdp-12:1.6-p16
 | 
				
			||||||
  # enable_admin_role_for_users: "true"
 | 
					  # enable_admin_role_for_users: "true"
 | 
				
			||||||
| 
						 | 
					@ -40,6 +40,7 @@ data:
 | 
				
			||||||
  # logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup"
 | 
					  # logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup"
 | 
				
			||||||
  # logical_backup_s3_access_key_id: ""
 | 
					  # logical_backup_s3_access_key_id: ""
 | 
				
			||||||
  # logical_backup_s3_bucket: "my-bucket-url"
 | 
					  # logical_backup_s3_bucket: "my-bucket-url"
 | 
				
			||||||
 | 
					  # logical_backup_s3_region: ""
 | 
				
			||||||
  # logical_backup_s3_endpoint: ""
 | 
					  # logical_backup_s3_endpoint: ""
 | 
				
			||||||
  # logical_backup_s3_secret_access_key: ""
 | 
					  # logical_backup_s3_secret_access_key: ""
 | 
				
			||||||
  # logical_backup_s3_sse: "AES256"
 | 
					  # logical_backup_s3_sse: "AES256"
 | 
				
			||||||
| 
						 | 
					@ -48,6 +49,8 @@ data:
 | 
				
			||||||
  # master_pod_move_timeout: 10m
 | 
					  # master_pod_move_timeout: 10m
 | 
				
			||||||
  # max_instances: "-1"
 | 
					  # max_instances: "-1"
 | 
				
			||||||
  # min_instances: "-1"
 | 
					  # min_instances: "-1"
 | 
				
			||||||
 | 
					  # min_cpu_limit: 250m
 | 
				
			||||||
 | 
					  # min_memory_limit: 250Mi
 | 
				
			||||||
  # node_readiness_label: ""
 | 
					  # node_readiness_label: ""
 | 
				
			||||||
  # oauth_token_secret_name: postgresql-operator
 | 
					  # oauth_token_secret_name: postgresql-operator
 | 
				
			||||||
  # pam_configuration: |
 | 
					  # pam_configuration: |
 | 
				
			||||||
| 
						 | 
					@ -60,7 +63,7 @@ data:
 | 
				
			||||||
  pod_label_wait_timeout: 10m
 | 
					  pod_label_wait_timeout: 10m
 | 
				
			||||||
  pod_management_policy: "ordered_ready"
 | 
					  pod_management_policy: "ordered_ready"
 | 
				
			||||||
  pod_role_label: spilo-role
 | 
					  pod_role_label: spilo-role
 | 
				
			||||||
  pod_service_account_name: "zalando-postgres-operator"
 | 
					  pod_service_account_name: "postgres-pod"
 | 
				
			||||||
  pod_terminate_grace_period: 5m
 | 
					  pod_terminate_grace_period: 5m
 | 
				
			||||||
  # postgres_superuser_teams: "postgres_superusers"
 | 
					  # postgres_superuser_teams: "postgres_superusers"
 | 
				
			||||||
  # protected_role_names: "admin"
 | 
					  # protected_role_names: "admin"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,3 +4,4 @@ resources:
 | 
				
			||||||
- configmap.yaml
 | 
					- configmap.yaml
 | 
				
			||||||
- operator-service-account-rbac.yaml
 | 
					- operator-service-account-rbac.yaml
 | 
				
			||||||
- postgres-operator.yaml
 | 
					- postgres-operator.yaml
 | 
				
			||||||
 | 
					- api-service.yaml
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,14 +1,14 @@
 | 
				
			||||||
apiVersion: v1
 | 
					apiVersion: v1
 | 
				
			||||||
kind: ServiceAccount
 | 
					kind: ServiceAccount
 | 
				
			||||||
metadata:
 | 
					metadata:
 | 
				
			||||||
  name: zalando-postgres-operator
 | 
					  name: postgres-operator
 | 
				
			||||||
  namespace: default
 | 
					  namespace: default
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
 | 
					apiVersion: rbac.authorization.k8s.io/v1
 | 
				
			||||||
kind: ClusterRole
 | 
					kind: ClusterRole
 | 
				
			||||||
metadata:
 | 
					metadata:
 | 
				
			||||||
  name: zalando-postgres-operator
 | 
					  name: postgres-operator
 | 
				
			||||||
rules:
 | 
					rules:
 | 
				
			||||||
# all verbs allowed for custom operator resources
 | 
					# all verbs allowed for custom operator resources
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,14 @@ rules:
 | 
				
			||||||
  - postgresqls/status
 | 
					  - postgresqls/status
 | 
				
			||||||
  - operatorconfigurations
 | 
					  - operatorconfigurations
 | 
				
			||||||
  verbs:
 | 
					  verbs:
 | 
				
			||||||
  - "*"
 | 
					  - create
 | 
				
			||||||
 | 
					  - delete
 | 
				
			||||||
 | 
					  - deletecollection
 | 
				
			||||||
 | 
					  - get
 | 
				
			||||||
 | 
					  - list
 | 
				
			||||||
 | 
					  - patch
 | 
				
			||||||
 | 
					  - update
 | 
				
			||||||
 | 
					  - watch
 | 
				
			||||||
# to create or get/update CRDs when starting up
 | 
					# to create or get/update CRDs when starting up
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - apiextensions.k8s.io
 | 
					  - apiextensions.k8s.io
 | 
				
			||||||
| 
						 | 
					@ -48,7 +55,8 @@ rules:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - list
 | 
					  - list
 | 
				
			||||||
  - patch
 | 
					  - patch
 | 
				
			||||||
  - watch  # needed if zalando-postgres-operator account is used for pods as well
 | 
					  - update
 | 
				
			||||||
 | 
					  - watch
 | 
				
			||||||
# to CRUD secrets for database access
 | 
					# to CRUD secrets for database access
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - ""
 | 
					  - ""
 | 
				
			||||||
| 
						 | 
					@ -96,6 +104,7 @@ rules:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - list
 | 
					  - list
 | 
				
			||||||
  - watch
 | 
					  - watch
 | 
				
			||||||
 | 
					  - update
 | 
				
			||||||
  - patch
 | 
					  - patch
 | 
				
			||||||
# to resize the filesystem in Spilo pods when increasing volume size
 | 
					# to resize the filesystem in Spilo pods when increasing volume size
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
| 
						 | 
					@ -114,6 +123,7 @@ rules:
 | 
				
			||||||
  - delete
 | 
					  - delete
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - patch
 | 
					  - patch
 | 
				
			||||||
 | 
					  - update
 | 
				
			||||||
# to CRUD the StatefulSet which controls the Postgres cluster instances
 | 
					# to CRUD the StatefulSet which controls the Postgres cluster instances
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - apps
 | 
					  - apps
 | 
				
			||||||
| 
						 | 
					@ -125,6 +135,18 @@ rules:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - list
 | 
					  - list
 | 
				
			||||||
  - patch
 | 
					  - patch
 | 
				
			||||||
 | 
					# to CRUD cron jobs for logical backups
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - batch
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - cronjobs
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - create
 | 
				
			||||||
 | 
					  - delete
 | 
				
			||||||
 | 
					  - get
 | 
				
			||||||
 | 
					  - list
 | 
				
			||||||
 | 
					  - patch
 | 
				
			||||||
 | 
					  - update
 | 
				
			||||||
# to get namespaces operator resources can run in
 | 
					# to get namespaces operator resources can run in
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - ""
 | 
					  - ""
 | 
				
			||||||
| 
						 | 
					@ -149,39 +171,82 @@ rules:
 | 
				
			||||||
  verbs:
 | 
					  verbs:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - create
 | 
					  - create
 | 
				
			||||||
# to create role bindings to the operator service account
 | 
					# to create role bindings to the postgres-pod service account
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - "rbac.authorization.k8s.io"
 | 
					  - rbac.authorization.k8s.io
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
  - rolebindings
 | 
					  - rolebindings
 | 
				
			||||||
  verbs:
 | 
					  verbs:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - create
 | 
					  - create
 | 
				
			||||||
# to CRUD cron jobs for logical backups
 | 
					# to grant privilege to run privileged pods
 | 
				
			||||||
- apiGroups:
 | 
					- apiGroups:
 | 
				
			||||||
  - batch
 | 
					  - extensions
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
  - cronjobs
 | 
					  - podsecuritypolicies
 | 
				
			||||||
 | 
					  resourceNames:
 | 
				
			||||||
 | 
					  - privileged
 | 
				
			||||||
  verbs:
 | 
					  verbs:
 | 
				
			||||||
  - create
 | 
					  - use
 | 
				
			||||||
  - delete
 | 
					 | 
				
			||||||
  - get
 | 
					 | 
				
			||||||
  - list
 | 
					 | 
				
			||||||
  - patch
 | 
					 | 
				
			||||||
  - update
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
apiVersion: rbac.authorization.k8s.io/v1
 | 
					apiVersion: rbac.authorization.k8s.io/v1
 | 
				
			||||||
kind: ClusterRoleBinding
 | 
					kind: ClusterRoleBinding
 | 
				
			||||||
metadata:
 | 
					metadata:
 | 
				
			||||||
  name: zalando-postgres-operator
 | 
					  name: postgres-operator
 | 
				
			||||||
roleRef:
 | 
					roleRef:
 | 
				
			||||||
  apiGroup: rbac.authorization.k8s.io
 | 
					  apiGroup: rbac.authorization.k8s.io
 | 
				
			||||||
  kind: ClusterRole
 | 
					  kind: ClusterRole
 | 
				
			||||||
  name: zalando-postgres-operator
 | 
					  name: postgres-operator
 | 
				
			||||||
subjects:
 | 
					subjects:
 | 
				
			||||||
- kind: ServiceAccount
 | 
					- kind: ServiceAccount
 | 
				
			||||||
# note: the cluster role binding needs to be defined
 | 
					  name: postgres-operator
 | 
				
			||||||
# for every namespace the operator service account lives in.
 | 
					 | 
				
			||||||
  name: zalando-postgres-operator
 | 
					 | 
				
			||||||
  namespace: default
 | 
					  namespace: default
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					apiVersion: rbac.authorization.k8s.io/v1
 | 
				
			||||||
 | 
					kind: ClusterRole
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  name: postgres-pod
 | 
				
			||||||
 | 
					rules:
 | 
				
			||||||
 | 
					# Patroni needs to watch and manage endpoints
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - ""
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - endpoints
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - create
 | 
				
			||||||
 | 
					  - delete
 | 
				
			||||||
 | 
					  - deletecollection
 | 
				
			||||||
 | 
					  - get
 | 
				
			||||||
 | 
					  - list
 | 
				
			||||||
 | 
					  - patch
 | 
				
			||||||
 | 
					  - update
 | 
				
			||||||
 | 
					  - watch
 | 
				
			||||||
 | 
					# Patroni needs to watch pods
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - ""
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - pods
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - get
 | 
				
			||||||
 | 
					  - list
 | 
				
			||||||
 | 
					  - patch
 | 
				
			||||||
 | 
					  - update
 | 
				
			||||||
 | 
					  - watch
 | 
				
			||||||
 | 
					# to let Patroni create a headless service
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - ""
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - services
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - create
 | 
				
			||||||
 | 
					# to run privileged pods
 | 
				
			||||||
 | 
					- apiGroups:
 | 
				
			||||||
 | 
					  - extensions
 | 
				
			||||||
 | 
					  resources:
 | 
				
			||||||
 | 
					  - podsecuritypolicies
 | 
				
			||||||
 | 
					  resourceNames:
 | 
				
			||||||
 | 
					  - privileged
 | 
				
			||||||
 | 
					  verbs:
 | 
				
			||||||
 | 
					  - use
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -155,6 +155,12 @@ spec:
 | 
				
			||||||
                default_memory_request:
 | 
					                default_memory_request:
 | 
				
			||||||
                  type: string
 | 
					                  type: string
 | 
				
			||||||
                  pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
 | 
					                  pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
 | 
				
			||||||
 | 
					                min_cpu_limit:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
 | 
				
			||||||
 | 
					                min_memory_limit:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
 | 
				
			||||||
            timeouts:
 | 
					            timeouts:
 | 
				
			||||||
              type: object
 | 
					              type: object
 | 
				
			||||||
              properties:
 | 
					              properties:
 | 
				
			||||||
| 
						 | 
					@ -213,6 +219,8 @@ spec:
 | 
				
			||||||
                  type: string
 | 
					                  type: string
 | 
				
			||||||
                logical_backup_s3_endpoint:
 | 
					                logical_backup_s3_endpoint:
 | 
				
			||||||
                  type: string
 | 
					                  type: string
 | 
				
			||||||
 | 
					                logical_backup_s3_region:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
                logical_backup_s3_secret_access_key:
 | 
					                logical_backup_s3_secret_access_key:
 | 
				
			||||||
                  type: string
 | 
					                  type: string
 | 
				
			||||||
                logical_backup_s3_sse:
 | 
					                logical_backup_s3_sse:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,17 +12,17 @@ spec:
 | 
				
			||||||
      labels:
 | 
					      labels:
 | 
				
			||||||
        name: postgres-operator
 | 
					        name: postgres-operator
 | 
				
			||||||
    spec:
 | 
					    spec:
 | 
				
			||||||
      serviceAccountName: zalando-postgres-operator
 | 
					      serviceAccountName: postgres-operator
 | 
				
			||||||
      containers:
 | 
					      containers:
 | 
				
			||||||
      - name: postgres-operator
 | 
					      - name: postgres-operator
 | 
				
			||||||
        image: registry.opensource.zalan.do/acid/postgres-operator:v1.3.0
 | 
					        image: registry.opensource.zalan.do/acid/postgres-operator:v1.3.1
 | 
				
			||||||
        imagePullPolicy: IfNotPresent
 | 
					        imagePullPolicy: IfNotPresent
 | 
				
			||||||
        resources:
 | 
					        resources:
 | 
				
			||||||
          requests:
 | 
					          requests:
 | 
				
			||||||
            cpu: 500m
 | 
					            cpu: 100m
 | 
				
			||||||
            memory: 250Mi
 | 
					            memory: 250Mi
 | 
				
			||||||
          limits:
 | 
					          limits:
 | 
				
			||||||
            cpu: 2000m
 | 
					            cpu: 500m
 | 
				
			||||||
            memory: 500Mi
 | 
					            memory: 500Mi
 | 
				
			||||||
        securityContext:
 | 
					        securityContext:
 | 
				
			||||||
          runAsUser: 1000
 | 
					          runAsUser: 1000
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,7 +45,7 @@ configuration:
 | 
				
			||||||
    # pod_priority_class_name: ""
 | 
					    # pod_priority_class_name: ""
 | 
				
			||||||
    pod_role_label: spilo-role
 | 
					    pod_role_label: spilo-role
 | 
				
			||||||
    # pod_service_account_definition: ""
 | 
					    # pod_service_account_definition: ""
 | 
				
			||||||
    pod_service_account_name: zalando-postgres-operator
 | 
					    pod_service_account_name: postgres-pod
 | 
				
			||||||
    # pod_service_account_role_binding_definition: ""
 | 
					    # pod_service_account_role_binding_definition: ""
 | 
				
			||||||
    pod_terminate_grace_period: 5m
 | 
					    pod_terminate_grace_period: 5m
 | 
				
			||||||
    secret_name_template: "{username}.{cluster}.credentials.{tprkind}.{tprgroup}"
 | 
					    secret_name_template: "{username}.{cluster}.credentials.{tprkind}.{tprgroup}"
 | 
				
			||||||
| 
						 | 
					@ -54,10 +54,12 @@ configuration:
 | 
				
			||||||
    # toleration: {}
 | 
					    # toleration: {}
 | 
				
			||||||
    # watched_namespace: ""
 | 
					    # watched_namespace: ""
 | 
				
			||||||
  postgres_pod_resources:
 | 
					  postgres_pod_resources:
 | 
				
			||||||
    default_cpu_limit: "3"
 | 
					    default_cpu_limit: "1"
 | 
				
			||||||
    default_cpu_request: 100m
 | 
					    default_cpu_request: 100m
 | 
				
			||||||
    default_memory_limit: 1Gi
 | 
					    default_memory_limit: 500Mi
 | 
				
			||||||
    default_memory_request: 100Mi
 | 
					    default_memory_request: 100Mi
 | 
				
			||||||
 | 
					    # min_cpu_limit: 250m
 | 
				
			||||||
 | 
					    # min_memory_limit: 250Mi
 | 
				
			||||||
  timeouts:
 | 
					  timeouts:
 | 
				
			||||||
    pod_label_wait_timeout: 10m
 | 
					    pod_label_wait_timeout: 10m
 | 
				
			||||||
    pod_deletion_wait_timeout: 10m
 | 
					    pod_deletion_wait_timeout: 10m
 | 
				
			||||||
| 
						 | 
					@ -86,6 +88,7 @@ configuration:
 | 
				
			||||||
    # logical_backup_s3_access_key_id: ""
 | 
					    # logical_backup_s3_access_key_id: ""
 | 
				
			||||||
    logical_backup_s3_bucket: "my-bucket-url"
 | 
					    logical_backup_s3_bucket: "my-bucket-url"
 | 
				
			||||||
    # logical_backup_s3_endpoint: ""
 | 
					    # logical_backup_s3_endpoint: ""
 | 
				
			||||||
 | 
					    # logical_backup_s3_region: ""
 | 
				
			||||||
    # logical_backup_s3_secret_access_key: ""
 | 
					    # logical_backup_s3_secret_access_key: ""
 | 
				
			||||||
    logical_backup_s3_sse: "AES256"
 | 
					    logical_backup_s3_sse: "AES256"
 | 
				
			||||||
    logical_backup_schedule: "30 00 * * *"
 | 
					    logical_backup_schedule: "30 00 * * *"
 | 
				
			||||||
| 
						 | 
					@ -115,6 +118,6 @@ configuration:
 | 
				
			||||||
    scalyr_cpu_limit: "1"
 | 
					    scalyr_cpu_limit: "1"
 | 
				
			||||||
    scalyr_cpu_request: 100m
 | 
					    scalyr_cpu_request: 100m
 | 
				
			||||||
    # scalyr_image: ""
 | 
					    # scalyr_image: ""
 | 
				
			||||||
    scalyr_memory_limit: 1Gi
 | 
					    scalyr_memory_limit: 500Mi
 | 
				
			||||||
    scalyr_memory_request: 50Mi
 | 
					    scalyr_memory_request: 50Mi
 | 
				
			||||||
    # scalyr_server_url: ""
 | 
					    # scalyr_server_url: ""
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -230,6 +230,10 @@ spec:
 | 
				
			||||||
                      pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
 | 
					                      pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
 | 
				
			||||||
                      # Note: the value specified here must not be zero or be higher
 | 
					                      # Note: the value specified here must not be zero or be higher
 | 
				
			||||||
                      # than the corresponding limit.
 | 
					                      # than the corresponding limit.
 | 
				
			||||||
 | 
					            serviceAnnotations:
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
 | 
					              additionalProperties:
 | 
				
			||||||
 | 
					                type: string
 | 
				
			||||||
            sidecars:
 | 
					            sidecars:
 | 
				
			||||||
              type: array
 | 
					              type: array
 | 
				
			||||||
              nullable: true
 | 
					              nullable: true
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,14 @@ rules:
 | 
				
			||||||
  - postgresqls
 | 
					  - postgresqls
 | 
				
			||||||
  - postgresqls/status
 | 
					  - postgresqls/status
 | 
				
			||||||
  verbs:
 | 
					  verbs:
 | 
				
			||||||
  - "*"
 | 
					  - create
 | 
				
			||||||
 | 
					  - delete
 | 
				
			||||||
 | 
					  - deletecollection
 | 
				
			||||||
 | 
					  - get
 | 
				
			||||||
 | 
					  - list
 | 
				
			||||||
 | 
					  - patch
 | 
				
			||||||
 | 
					  - update
 | 
				
			||||||
 | 
					  - watch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
apiVersion: rbac.authorization.k8s.io/v1
 | 
					apiVersion: rbac.authorization.k8s.io/v1
 | 
				
			||||||
| 
						 | 
					@ -48,4 +55,3 @@ rules:
 | 
				
			||||||
  - get
 | 
					  - get
 | 
				
			||||||
  - list
 | 
					  - list
 | 
				
			||||||
  - watch
 | 
					  - watch
 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -383,6 +383,14 @@ var PostgresCRDResourceValidation = apiextv1beta1.CustomResourceValidation{
 | 
				
			||||||
							},
 | 
												},
 | 
				
			||||||
						},
 | 
											},
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
 | 
										"serviceAnnotations": {
 | 
				
			||||||
 | 
											Type: "object",
 | 
				
			||||||
 | 
											AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
 | 
				
			||||||
 | 
												Schema: &apiextv1beta1.JSONSchemaProps{
 | 
				
			||||||
 | 
													Type: "string",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
					"sidecars": {
 | 
										"sidecars": {
 | 
				
			||||||
						Type: "array",
 | 
											Type: "array",
 | 
				
			||||||
						Items: &apiextv1beta1.JSONSchemaPropsOrArray{
 | 
											Items: &apiextv1beta1.JSONSchemaPropsOrArray{
 | 
				
			||||||
| 
						 | 
					@ -810,6 +818,14 @@ var OperatorConfigCRDResourceValidation = apiextv1beta1.CustomResourceValidation
 | 
				
			||||||
								Type:    "string",
 | 
													Type:    "string",
 | 
				
			||||||
								Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
 | 
													Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
 | 
				
			||||||
							},
 | 
												},
 | 
				
			||||||
 | 
												"min_cpu_limit": {
 | 
				
			||||||
 | 
													Type:    "string",
 | 
				
			||||||
 | 
													Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												"min_memory_limit": {
 | 
				
			||||||
 | 
													Type:    "string",
 | 
				
			||||||
 | 
													Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
						},
 | 
											},
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
					"timeouts": {
 | 
										"timeouts": {
 | 
				
			||||||
| 
						 | 
					@ -901,6 +917,9 @@ var OperatorConfigCRDResourceValidation = apiextv1beta1.CustomResourceValidation
 | 
				
			||||||
							"logical_backup_s3_endpoint": {
 | 
												"logical_backup_s3_endpoint": {
 | 
				
			||||||
								Type: "string",
 | 
													Type: "string",
 | 
				
			||||||
							},
 | 
												},
 | 
				
			||||||
 | 
												"logical_backup_s3_region": {
 | 
				
			||||||
 | 
													Type: "string",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
							"logical_backup_s3_secret_access_key": {
 | 
												"logical_backup_s3_secret_access_key": {
 | 
				
			||||||
								Type: "string",
 | 
													Type: "string",
 | 
				
			||||||
							},
 | 
												},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -67,7 +67,7 @@ type KubernetesMetaConfiguration struct {
 | 
				
			||||||
	// TODO: use namespacedname
 | 
						// TODO: use namespacedname
 | 
				
			||||||
	PodEnvironmentConfigMap    string        `json:"pod_environment_configmap,omitempty"`
 | 
						PodEnvironmentConfigMap    string        `json:"pod_environment_configmap,omitempty"`
 | 
				
			||||||
	PodPriorityClassName       string        `json:"pod_priority_class_name,omitempty"`
 | 
						PodPriorityClassName       string        `json:"pod_priority_class_name,omitempty"`
 | 
				
			||||||
	MasterPodMoveTimeout       time.Duration `json:"master_pod_move_timeout,omitempty"`
 | 
						MasterPodMoveTimeout       Duration      `json:"master_pod_move_timeout,omitempty"`
 | 
				
			||||||
	EnablePodAntiAffinity      bool          `json:"enable_pod_antiaffinity,omitempty"`
 | 
						EnablePodAntiAffinity      bool          `json:"enable_pod_antiaffinity,omitempty"`
 | 
				
			||||||
	PodAntiAffinityTopologyKey string        `json:"pod_antiaffinity_topology_key,omitempty"`
 | 
						PodAntiAffinityTopologyKey string        `json:"pod_antiaffinity_topology_key,omitempty"`
 | 
				
			||||||
	PodManagementPolicy        string        `json:"pod_management_policy,omitempty"`
 | 
						PodManagementPolicy        string        `json:"pod_management_policy,omitempty"`
 | 
				
			||||||
| 
						 | 
					@ -79,6 +79,8 @@ type PostgresPodResourcesDefaults struct {
 | 
				
			||||||
	DefaultMemoryRequest string `json:"default_memory_request,omitempty"`
 | 
						DefaultMemoryRequest string `json:"default_memory_request,omitempty"`
 | 
				
			||||||
	DefaultCPULimit      string `json:"default_cpu_limit,omitempty"`
 | 
						DefaultCPULimit      string `json:"default_cpu_limit,omitempty"`
 | 
				
			||||||
	DefaultMemoryLimit   string `json:"default_memory_limit,omitempty"`
 | 
						DefaultMemoryLimit   string `json:"default_memory_limit,omitempty"`
 | 
				
			||||||
 | 
						MinCPULimit          string `json:"min_cpu_limit,omitempty"`
 | 
				
			||||||
 | 
						MinMemoryLimit       string `json:"min_memory_limit,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// OperatorTimeouts defines the timeout of ResourceCheck, PodWait, ReadyWait
 | 
					// OperatorTimeouts defines the timeout of ResourceCheck, PodWait, ReadyWait
 | 
				
			||||||
| 
						 | 
					@ -155,6 +157,7 @@ type OperatorLogicalBackupConfiguration struct {
 | 
				
			||||||
	Schedule          string `json:"logical_backup_schedule,omitempty"`
 | 
						Schedule          string `json:"logical_backup_schedule,omitempty"`
 | 
				
			||||||
	DockerImage       string `json:"logical_backup_docker_image,omitempty"`
 | 
						DockerImage       string `json:"logical_backup_docker_image,omitempty"`
 | 
				
			||||||
	S3Bucket          string `json:"logical_backup_s3_bucket,omitempty"`
 | 
						S3Bucket          string `json:"logical_backup_s3_bucket,omitempty"`
 | 
				
			||||||
 | 
						S3Region          string `json:"logical_backup_s3_region,omitempty"`
 | 
				
			||||||
	S3Endpoint        string `json:"logical_backup_s3_endpoint,omitempty"`
 | 
						S3Endpoint        string `json:"logical_backup_s3_endpoint,omitempty"`
 | 
				
			||||||
	S3AccessKeyID     string `json:"logical_backup_s3_access_key_id,omitempty"`
 | 
						S3AccessKeyID     string `json:"logical_backup_s3_access_key_id,omitempty"`
 | 
				
			||||||
	S3SecretAccessKey string `json:"logical_backup_s3_secret_access_key,omitempty"`
 | 
						S3SecretAccessKey string `json:"logical_backup_s3_secret_access_key,omitempty"`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -61,6 +61,7 @@ type PostgresSpec struct {
 | 
				
			||||||
	LogicalBackupSchedule string                      `json:"logicalBackupSchedule,omitempty"`
 | 
						LogicalBackupSchedule string                      `json:"logicalBackupSchedule,omitempty"`
 | 
				
			||||||
	StandbyCluster        *StandbyDescription         `json:"standby"`
 | 
						StandbyCluster        *StandbyDescription         `json:"standby"`
 | 
				
			||||||
	PodAnnotations        map[string]string           `json:"podAnnotations"`
 | 
						PodAnnotations        map[string]string           `json:"podAnnotations"`
 | 
				
			||||||
 | 
						ServiceAnnotations    map[string]string           `json:"serviceAnnotations"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// deprecated json tags
 | 
						// deprecated json tags
 | 
				
			||||||
	InitContainersOld       []v1.Container `json:"init_containers,omitempty"`
 | 
						InitContainersOld       []v1.Container `json:"init_containers,omitempty"`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,127 +13,139 @@ import (
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var parseTimeTests = []struct {
 | 
					var parseTimeTests = []struct {
 | 
				
			||||||
 | 
						about string
 | 
				
			||||||
	in    string
 | 
						in    string
 | 
				
			||||||
	out   metav1.Time
 | 
						out   metav1.Time
 | 
				
			||||||
	err   error
 | 
						err   error
 | 
				
			||||||
}{
 | 
					}{
 | 
				
			||||||
	{"16:08", mustParseTime("16:08"), nil},
 | 
						{"parse common time with minutes", "16:08", mustParseTime("16:08"), nil},
 | 
				
			||||||
	{"11:00", mustParseTime("11:00"), nil},
 | 
						{"parse time with zeroed minutes", "11:00", mustParseTime("11:00"), nil},
 | 
				
			||||||
	{"23:59", mustParseTime("23:59"), nil},
 | 
						{"parse corner case last minute of the day", "23:59", mustParseTime("23:59"), nil},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	{"26:09", metav1.Now(), errors.New(`parsing time "26:09": hour out of range`)},
 | 
						{"expect error as hour is out of range", "26:09", metav1.Now(), errors.New(`parsing time "26:09": hour out of range`)},
 | 
				
			||||||
	{"23:69", metav1.Now(), errors.New(`parsing time "23:69": minute out of range`)},
 | 
						{"expect error as minute is out of range", "23:69", metav1.Now(), errors.New(`parsing time "23:69": minute out of range`)},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var parseWeekdayTests = []struct {
 | 
					var parseWeekdayTests = []struct {
 | 
				
			||||||
 | 
						about string
 | 
				
			||||||
	in    string
 | 
						in    string
 | 
				
			||||||
	out   time.Weekday
 | 
						out   time.Weekday
 | 
				
			||||||
	err   error
 | 
						err   error
 | 
				
			||||||
}{
 | 
					}{
 | 
				
			||||||
	{"Wed", time.Wednesday, nil},
 | 
						{"parse common weekday", "Wed", time.Wednesday, nil},
 | 
				
			||||||
	{"Sunday", time.Weekday(0), errors.New("incorrect weekday")},
 | 
						{"expect error as weekday is invalid", "Sunday", time.Weekday(0), errors.New("incorrect weekday")},
 | 
				
			||||||
	{"", time.Weekday(0), errors.New("incorrect weekday")},
 | 
						{"expect error as weekday is empty", "", time.Weekday(0), errors.New("incorrect weekday")},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var clusterNames = []struct {
 | 
					var clusterNames = []struct {
 | 
				
			||||||
 | 
						about       string
 | 
				
			||||||
	in          string
 | 
						in          string
 | 
				
			||||||
	inTeam      string
 | 
						inTeam      string
 | 
				
			||||||
	clusterName string
 | 
						clusterName string
 | 
				
			||||||
	err         error
 | 
						err         error
 | 
				
			||||||
}{
 | 
					}{
 | 
				
			||||||
	{"acid-test", "acid", "test", nil},
 | 
						{"common team and cluster name", "acid-test", "acid", "test", nil},
 | 
				
			||||||
	{"test-my-name", "test", "my-name", nil},
 | 
						{"cluster name with hyphen", "test-my-name", "test", "my-name", nil},
 | 
				
			||||||
	{"my-team-another-test", "my-team", "another-test", nil},
 | 
						{"cluster and team name with hyphen", "my-team-another-test", "my-team", "another-test", nil},
 | 
				
			||||||
	{"------strange-team-cluster", "-----", "strange-team-cluster",
 | 
						{"expect error as cluster name is just hyphens", "------strange-team-cluster", "-----", "strange-team-cluster",
 | 
				
			||||||
		errors.New(`name must confirm to DNS-1035, regex used for validation is "^[a-z]([-a-z0-9]*[a-z0-9])?$"`)},
 | 
							errors.New(`name must confirm to DNS-1035, regex used for validation is "^[a-z]([-a-z0-9]*[a-z0-9])?$"`)},
 | 
				
			||||||
	{"fooobar-fooobarfooobarfooobarfooobarfooobarfooobarfooobarfooobar", "fooobar", "",
 | 
						{"expect error as cluster name is too long", "fooobar-fooobarfooobarfooobarfooobarfooobarfooobarfooobarfooobar", "fooobar", "",
 | 
				
			||||||
		errors.New("name cannot be longer than 58 characters")},
 | 
							errors.New("name cannot be longer than 58 characters")},
 | 
				
			||||||
	{"acid-test", "test", "", errors.New("name must match {TEAM}-{NAME} format")},
 | 
						{"expect error as cluster name does not match {TEAM}-{NAME} format", "acid-test", "test", "", errors.New("name must match {TEAM}-{NAME} format")},
 | 
				
			||||||
	{"-test", "", "", errors.New("team name is empty")},
 | 
						{"expect error as team and cluster name are empty", "-test", "", "", errors.New("team name is empty")},
 | 
				
			||||||
	{"-test", "-", "", errors.New("name must match {TEAM}-{NAME} format")},
 | 
						{"expect error as cluster name is empty and team name is a hyphen", "-test", "-", "", errors.New("name must match {TEAM}-{NAME} format")},
 | 
				
			||||||
	{"", "-", "", errors.New("cluster name must match {TEAM}-{NAME} format. Got cluster name '', team name '-'")},
 | 
						{"expect error as cluster name is empty, team name is a hyphen and cluster name is empty", "", "-", "", errors.New("cluster name must match {TEAM}-{NAME} format. Got cluster name '', team name '-'")},
 | 
				
			||||||
	{"-", "-", "", errors.New("cluster name must match {TEAM}-{NAME} format. Got cluster name '-', team name '-'")},
 | 
						{"expect error as cluster and team name are hyphens", "-", "-", "", errors.New("cluster name must match {TEAM}-{NAME} format. Got cluster name '-', team name '-'")},
 | 
				
			||||||
	// user may specify the team part of the full cluster name differently from the team name returned by the Teams API
 | 
						// user may specify the team part of the full cluster name differently from the team name returned by the Teams API
 | 
				
			||||||
	// in the case the actual Teams API name is long enough, this will fail the check
 | 
						// in the case the actual Teams API name is long enough, this will fail the check
 | 
				
			||||||
	{"foo-bar", "qwerty", "", errors.New("cluster name must match {TEAM}-{NAME} format. Got cluster name 'foo-bar', team name 'qwerty'")},
 | 
						{"expect error as team name does not match", "foo-bar", "qwerty", "", errors.New("cluster name must match {TEAM}-{NAME} format. Got cluster name 'foo-bar', team name 'qwerty'")},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var cloneClusterDescriptions = []struct {
 | 
					var cloneClusterDescriptions = []struct {
 | 
				
			||||||
 | 
						about string
 | 
				
			||||||
	in    *CloneDescription
 | 
						in    *CloneDescription
 | 
				
			||||||
	err   error
 | 
						err   error
 | 
				
			||||||
}{
 | 
					}{
 | 
				
			||||||
	{&CloneDescription{"foo+bar", "", "NotEmpty", "", "", "", "", nil}, nil},
 | 
						{"cluster name invalid but EndTimeSet is not empty", &CloneDescription{"foo+bar", "", "NotEmpty", "", "", "", "", nil}, nil},
 | 
				
			||||||
	{&CloneDescription{"foo+bar", "", "", "", "", "", "", nil},
 | 
						{"expect error as cluster name does not match DNS-1035", &CloneDescription{"foo+bar", "", "", "", "", "", "", nil},
 | 
				
			||||||
		errors.New(`clone cluster name must confirm to DNS-1035, regex used for validation is "^[a-z]([-a-z0-9]*[a-z0-9])?$"`)},
 | 
							errors.New(`clone cluster name must confirm to DNS-1035, regex used for validation is "^[a-z]([-a-z0-9]*[a-z0-9])?$"`)},
 | 
				
			||||||
	{&CloneDescription{"foobar123456789012345678901234567890123456789012345678901234567890", "", "", "", "", "", "", nil},
 | 
						{"expect error as cluster name is too long", &CloneDescription{"foobar123456789012345678901234567890123456789012345678901234567890", "", "", "", "", "", "", nil},
 | 
				
			||||||
		errors.New("clone cluster name must be no longer than 63 characters")},
 | 
							errors.New("clone cluster name must be no longer than 63 characters")},
 | 
				
			||||||
	{&CloneDescription{"foobar", "", "", "", "", "", "", nil}, nil},
 | 
						{"common cluster name", &CloneDescription{"foobar", "", "", "", "", "", "", nil}, nil},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var maintenanceWindows = []struct {
 | 
					var maintenanceWindows = []struct {
 | 
				
			||||||
 | 
						about string
 | 
				
			||||||
	in    []byte
 | 
						in    []byte
 | 
				
			||||||
	out   MaintenanceWindow
 | 
						out   MaintenanceWindow
 | 
				
			||||||
	err   error
 | 
						err   error
 | 
				
			||||||
}{{[]byte(`"Tue:10:00-20:00"`),
 | 
					}{{"regular scenario",
 | 
				
			||||||
 | 
						[]byte(`"Tue:10:00-20:00"`),
 | 
				
			||||||
	MaintenanceWindow{
 | 
						MaintenanceWindow{
 | 
				
			||||||
		Everyday:  false,
 | 
							Everyday:  false,
 | 
				
			||||||
		Weekday:   time.Tuesday,
 | 
							Weekday:   time.Tuesday,
 | 
				
			||||||
		StartTime: mustParseTime("10:00"),
 | 
							StartTime: mustParseTime("10:00"),
 | 
				
			||||||
		EndTime:   mustParseTime("20:00"),
 | 
							EndTime:   mustParseTime("20:00"),
 | 
				
			||||||
	}, nil},
 | 
						}, nil},
 | 
				
			||||||
	{[]byte(`"Mon:10:00-10:00"`),
 | 
						{"starts and ends at the same time",
 | 
				
			||||||
 | 
							[]byte(`"Mon:10:00-10:00"`),
 | 
				
			||||||
		MaintenanceWindow{
 | 
							MaintenanceWindow{
 | 
				
			||||||
			Everyday:  false,
 | 
								Everyday:  false,
 | 
				
			||||||
			Weekday:   time.Monday,
 | 
								Weekday:   time.Monday,
 | 
				
			||||||
			StartTime: mustParseTime("10:00"),
 | 
								StartTime: mustParseTime("10:00"),
 | 
				
			||||||
			EndTime:   mustParseTime("10:00"),
 | 
								EndTime:   mustParseTime("10:00"),
 | 
				
			||||||
		}, nil},
 | 
							}, nil},
 | 
				
			||||||
	{[]byte(`"Sun:00:00-00:00"`),
 | 
						{"starts and ends 00:00 on sunday",
 | 
				
			||||||
 | 
							[]byte(`"Sun:00:00-00:00"`),
 | 
				
			||||||
		MaintenanceWindow{
 | 
							MaintenanceWindow{
 | 
				
			||||||
			Everyday:  false,
 | 
								Everyday:  false,
 | 
				
			||||||
			Weekday:   time.Sunday,
 | 
								Weekday:   time.Sunday,
 | 
				
			||||||
			StartTime: mustParseTime("00:00"),
 | 
								StartTime: mustParseTime("00:00"),
 | 
				
			||||||
			EndTime:   mustParseTime("00:00"),
 | 
								EndTime:   mustParseTime("00:00"),
 | 
				
			||||||
		}, nil},
 | 
							}, nil},
 | 
				
			||||||
	{[]byte(`"01:00-10:00"`),
 | 
						{"without day indication should define to sunday",
 | 
				
			||||||
 | 
							[]byte(`"01:00-10:00"`),
 | 
				
			||||||
		MaintenanceWindow{
 | 
							MaintenanceWindow{
 | 
				
			||||||
			Everyday:  true,
 | 
								Everyday:  true,
 | 
				
			||||||
			Weekday:   time.Sunday,
 | 
								Weekday:   time.Sunday,
 | 
				
			||||||
			StartTime: mustParseTime("01:00"),
 | 
								StartTime: mustParseTime("01:00"),
 | 
				
			||||||
			EndTime:   mustParseTime("10:00"),
 | 
								EndTime:   mustParseTime("10:00"),
 | 
				
			||||||
		}, nil},
 | 
							}, nil},
 | 
				
			||||||
	{[]byte(`"Mon:12:00-11:00"`), MaintenanceWindow{}, errors.New(`'From' time must be prior to the 'To' time`)},
 | 
						{"expect error as 'From' is later than 'To'", []byte(`"Mon:12:00-11:00"`), MaintenanceWindow{}, errors.New(`'From' time must be prior to the 'To' time`)},
 | 
				
			||||||
	{[]byte(`"Wed:33:00-00:00"`), MaintenanceWindow{}, errors.New(`could not parse start time: parsing time "33:00": hour out of range`)},
 | 
						{"expect error as 'From' is later than 'To' with 00:00 corner case", []byte(`"Mon:10:00-00:00"`), MaintenanceWindow{}, errors.New(`'From' time must be prior to the 'To' time`)},
 | 
				
			||||||
	{[]byte(`"Wed:00:00-26:00"`), MaintenanceWindow{}, errors.New(`could not parse end time: parsing time "26:00": hour out of range`)},
 | 
						{"expect error as 'From' time is not valid", []byte(`"Wed:33:00-00:00"`), MaintenanceWindow{}, errors.New(`could not parse start time: parsing time "33:00": hour out of range`)},
 | 
				
			||||||
	{[]byte(`"Sunday:00:00-00:00"`), MaintenanceWindow{}, errors.New(`could not parse weekday: incorrect weekday`)},
 | 
						{"expect error as 'To' time is not valid", []byte(`"Wed:00:00-26:00"`), MaintenanceWindow{}, errors.New(`could not parse end time: parsing time "26:00": hour out of range`)},
 | 
				
			||||||
	{[]byte(`":00:00-10:00"`), MaintenanceWindow{}, errors.New(`could not parse weekday: incorrect weekday`)},
 | 
						{"expect error as weekday is not valid", []byte(`"Sunday:00:00-00:00"`), MaintenanceWindow{}, errors.New(`could not parse weekday: incorrect weekday`)},
 | 
				
			||||||
	{[]byte(`"Mon:10:00-00:00"`), MaintenanceWindow{}, errors.New(`'From' time must be prior to the 'To' time`)},
 | 
						{"expect error as weekday is empty", []byte(`":00:00-10:00"`), MaintenanceWindow{}, errors.New(`could not parse weekday: incorrect weekday`)},
 | 
				
			||||||
	{[]byte(`"Mon:00:00:00-10:00:00"`), MaintenanceWindow{}, errors.New(`incorrect maintenance window format`)},
 | 
						{"expect error as maintenance window set seconds", []byte(`"Mon:00:00:00-10:00:00"`), MaintenanceWindow{}, errors.New(`incorrect maintenance window format`)},
 | 
				
			||||||
	{[]byte(`"Mon:00:00"`), MaintenanceWindow{}, errors.New("incorrect maintenance window format")},
 | 
						{"expect error as 'To' time set seconds", []byte(`"Mon:00:00-00:00:00"`), MaintenanceWindow{}, errors.New("could not parse end time: incorrect time format")},
 | 
				
			||||||
	{[]byte(`"Mon:00:00-00:00:00"`), MaintenanceWindow{}, errors.New("could not parse end time: incorrect time format")}}
 | 
						{"expect error as 'To' time is missing", []byte(`"Mon:00:00"`), MaintenanceWindow{}, errors.New("incorrect maintenance window format")}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var postgresStatus = []struct {
 | 
					var postgresStatus = []struct {
 | 
				
			||||||
 | 
						about string
 | 
				
			||||||
	in    []byte
 | 
						in    []byte
 | 
				
			||||||
	out   PostgresStatus
 | 
						out   PostgresStatus
 | 
				
			||||||
	err   error
 | 
						err   error
 | 
				
			||||||
}{
 | 
					}{
 | 
				
			||||||
	{[]byte(`{"PostgresClusterStatus":"Running"}`),
 | 
						{"cluster running", []byte(`{"PostgresClusterStatus":"Running"}`),
 | 
				
			||||||
		PostgresStatus{PostgresClusterStatus: ClusterStatusRunning}, nil},
 | 
							PostgresStatus{PostgresClusterStatus: ClusterStatusRunning}, nil},
 | 
				
			||||||
	{[]byte(`{"PostgresClusterStatus":""}`),
 | 
						{"cluster status undefined", []byte(`{"PostgresClusterStatus":""}`),
 | 
				
			||||||
		PostgresStatus{PostgresClusterStatus: ClusterStatusUnknown}, nil},
 | 
							PostgresStatus{PostgresClusterStatus: ClusterStatusUnknown}, nil},
 | 
				
			||||||
	{[]byte(`"Running"`),
 | 
						{"cluster running without full JSON format", []byte(`"Running"`),
 | 
				
			||||||
		PostgresStatus{PostgresClusterStatus: ClusterStatusRunning}, nil},
 | 
							PostgresStatus{PostgresClusterStatus: ClusterStatusRunning}, nil},
 | 
				
			||||||
	{[]byte(`""`),
 | 
						{"cluster status empty", []byte(`""`),
 | 
				
			||||||
		PostgresStatus{PostgresClusterStatus: ClusterStatusUnknown}, nil}}
 | 
							PostgresStatus{PostgresClusterStatus: ClusterStatusUnknown}, nil}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var tmp postgresqlCopy
 | 
				
			||||||
var unmarshalCluster = []struct {
 | 
					var unmarshalCluster = []struct {
 | 
				
			||||||
 | 
						about   string
 | 
				
			||||||
	in      []byte
 | 
						in      []byte
 | 
				
			||||||
	out     Postgresql
 | 
						out     Postgresql
 | 
				
			||||||
	marshal []byte
 | 
						marshal []byte
 | 
				
			||||||
	err     error
 | 
						err     error
 | 
				
			||||||
}{
 | 
					}{
 | 
				
			||||||
	// example with simple status field
 | 
					 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
							about: "example with simple status field",
 | 
				
			||||||
		in: []byte(`{
 | 
							in: []byte(`{
 | 
				
			||||||
	  "kind": "Postgresql","apiVersion": "acid.zalan.do/v1",
 | 
						  "kind": "Postgresql","apiVersion": "acid.zalan.do/v1",
 | 
				
			||||||
	  "metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": 100}}`),
 | 
						  "metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": 100}}`),
 | 
				
			||||||
| 
						 | 
					@ -147,12 +159,14 @@ var unmarshalCluster = []struct {
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			Status: PostgresStatus{PostgresClusterStatus: ClusterStatusInvalid},
 | 
								Status: PostgresStatus{PostgresClusterStatus: ClusterStatusInvalid},
 | 
				
			||||||
			// This error message can vary between Go versions, so compute it for the current version.
 | 
								// This error message can vary between Go versions, so compute it for the current version.
 | 
				
			||||||
			Error: json.Unmarshal([]byte(`{"teamId": 0}`), &PostgresSpec{}).Error(),
 | 
								Error: json.Unmarshal([]byte(`{
 | 
				
			||||||
 | 
									"kind": "Postgresql","apiVersion": "acid.zalan.do/v1",
 | 
				
			||||||
 | 
									"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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":"Invalid"}`),
 | 
							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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":"Invalid"}`),
 | 
				
			||||||
		err:     nil},
 | 
							err:     nil},
 | 
				
			||||||
	// example with /status subresource
 | 
					 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
							about: "example with /status subresource",
 | 
				
			||||||
		in: []byte(`{
 | 
							in: []byte(`{
 | 
				
			||||||
	  "kind": "Postgresql","apiVersion": "acid.zalan.do/v1",
 | 
						  "kind": "Postgresql","apiVersion": "acid.zalan.do/v1",
 | 
				
			||||||
	  "metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": 100}}`),
 | 
						  "metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": 100}}`),
 | 
				
			||||||
| 
						 | 
					@ -166,13 +180,14 @@ var unmarshalCluster = []struct {
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			Status: PostgresStatus{PostgresClusterStatus: ClusterStatusInvalid},
 | 
								Status: PostgresStatus{PostgresClusterStatus: ClusterStatusInvalid},
 | 
				
			||||||
			// This error message can vary between Go versions, so compute it for the current version.
 | 
								// This error message can vary between Go versions, so compute it for the current version.
 | 
				
			||||||
			Error: json.Unmarshal([]byte(`{"teamId": 0}`), &PostgresSpec{}).Error(),
 | 
								Error: json.Unmarshal([]byte(`{
 | 
				
			||||||
 | 
									"kind": "Postgresql","apiVersion": "acid.zalan.do/v1",
 | 
				
			||||||
 | 
									"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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":{"PostgresClusterStatus":"Invalid"}}`),
 | 
							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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":{"PostgresClusterStatus":"Invalid"}}`),
 | 
				
			||||||
		err:     nil},
 | 
							err:     nil},
 | 
				
			||||||
	// example with detailed input manifest
 | 
					 | 
				
			||||||
	// and deprecated pod_priority_class_name -> podPriorityClassName
 | 
					 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
							about: "example with detailed input manifest and deprecated pod_priority_class_name -> podPriorityClassName",
 | 
				
			||||||
		in: []byte(`{
 | 
							in: []byte(`{
 | 
				
			||||||
	  "kind": "Postgresql",
 | 
						  "kind": "Postgresql",
 | 
				
			||||||
	  "apiVersion": "acid.zalan.do/v1",
 | 
						  "apiVersion": "acid.zalan.do/v1",
 | 
				
			||||||
| 
						 | 
					@ -321,8 +336,8 @@ var unmarshalCluster = []struct {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"9.6","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":""}}`),
 | 
							marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"9.6","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},
 | 
				
			||||||
	// example with teamId set in input
 | 
					 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
							about: "example with teamId set in input",
 | 
				
			||||||
		in:    []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "teapot-testcluster1"}, "spec": {"teamId": "acid"}}`),
 | 
							in:    []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "teapot-testcluster1"}, "spec": {"teamId": "acid"}}`),
 | 
				
			||||||
		out: Postgresql{
 | 
							out: Postgresql{
 | 
				
			||||||
			TypeMeta: metav1.TypeMeta{
 | 
								TypeMeta: metav1.TypeMeta{
 | 
				
			||||||
| 
						 | 
					@ -338,8 +353,8 @@ var unmarshalCluster = []struct {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"teapot-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} ,"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":{"PostgresClusterStatus":"Invalid"}}`),
 | 
							marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"teapot-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} ,"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":{"PostgresClusterStatus":"Invalid"}}`),
 | 
				
			||||||
		err:     nil},
 | 
							err:     nil},
 | 
				
			||||||
	// clone example
 | 
					 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
							about: "example with clone",
 | 
				
			||||||
		in:    []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": "acid", "clone": {"cluster": "team-batman"}}}`),
 | 
							in:    []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": "acid", "clone": {"cluster": "team-batman"}}}`),
 | 
				
			||||||
		out: Postgresql{
 | 
							out: Postgresql{
 | 
				
			||||||
			TypeMeta: metav1.TypeMeta{
 | 
								TypeMeta: metav1.TypeMeta{
 | 
				
			||||||
| 
						 | 
					@ -360,8 +375,8 @@ var unmarshalCluster = []struct {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{"cluster":"team-batman"}},"status":{"PostgresClusterStatus":""}}`),
 | 
							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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{"cluster":"team-batman"}},"status":{"PostgresClusterStatus":""}}`),
 | 
				
			||||||
		err:     nil},
 | 
							err:     nil},
 | 
				
			||||||
	// standby example
 | 
					 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
							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/"}}}`),
 | 
							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/"}}}`),
 | 
				
			||||||
		out: Postgresql{
 | 
							out: Postgresql{
 | 
				
			||||||
			TypeMeta: metav1.TypeMeta{
 | 
								TypeMeta: metav1.TypeMeta{
 | 
				
			||||||
| 
						 | 
					@ -382,24 +397,28 @@ var unmarshalCluster = []struct {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"standby":{"s3_wal_path":"s3://custom/path/to/bucket/"}},"status":{"PostgresClusterStatus":""}}`),
 | 
							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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"standby":{"s3_wal_path":"s3://custom/path/to/bucket/"}},"status":{"PostgresClusterStatus":""}}`),
 | 
				
			||||||
		err:     nil},
 | 
							err:     nil},
 | 
				
			||||||
	// erroneous examples
 | 
					 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
							about:   "expect error on malformatted JSON",
 | 
				
			||||||
		in:      []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`),
 | 
							in:      []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`),
 | 
				
			||||||
		out:     Postgresql{},
 | 
							out:     Postgresql{},
 | 
				
			||||||
		marshal: []byte{},
 | 
							marshal: []byte{},
 | 
				
			||||||
		err:     errors.New("unexpected end of JSON input")},
 | 
							err:     errors.New("unexpected end of JSON input")},
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
							about:   "expect error on JSON with field's value malformatted",
 | 
				
			||||||
		in:      []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster","creationTimestamp":qaz},"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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":{"PostgresClusterStatus":"Invalid"}}`),
 | 
							in:      []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster","creationTimestamp":qaz},"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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":{"PostgresClusterStatus":"Invalid"}}`),
 | 
				
			||||||
		out:     Postgresql{},
 | 
							out:     Postgresql{},
 | 
				
			||||||
		marshal: []byte{},
 | 
							marshal: []byte{},
 | 
				
			||||||
		err:     errors.New("invalid character 'q' looking for beginning of value")}}
 | 
							err:     errors.New("invalid character 'q' looking for beginning of value"),
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var postgresqlList = []struct {
 | 
					var postgresqlList = []struct {
 | 
				
			||||||
 | 
						about string
 | 
				
			||||||
	in    []byte
 | 
						in    []byte
 | 
				
			||||||
	out   PostgresqlList
 | 
						out   PostgresqlList
 | 
				
			||||||
	err   error
 | 
						err   error
 | 
				
			||||||
}{
 | 
					}{
 | 
				
			||||||
	{[]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":"9.6"},"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":"9.6"},"teamId":"acid","volume":{"size":"10Gi"}},"status":{"PostgresClusterStatus":"Running"}}],"kind":"List","metadata":{},"resourceVersion":"","selfLink":""}`),
 | 
				
			||||||
		PostgresqlList{
 | 
							PostgresqlList{
 | 
				
			||||||
			TypeMeta: metav1.TypeMeta{
 | 
								TypeMeta: metav1.TypeMeta{
 | 
				
			||||||
				Kind:       "List",
 | 
									Kind:       "List",
 | 
				
			||||||
| 
						 | 
					@ -433,20 +452,88 @@ var postgresqlList = []struct {
 | 
				
			||||||
			}},
 | 
								}},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		nil},
 | 
							nil},
 | 
				
			||||||
	{[]byte(`{"apiVersion":"v1","items":[{"apiVersion":"acid.zalan.do/v1","kind":"Postgresql","metadata":{"labels":{"team":"acid"},"name":"acid-testcluster42","namespace"`),
 | 
						{"expect error on malformatted JSON", []byte(`{"apiVersion":"v1","items":[{"apiVersion":"acid.zalan.do/v1","kind":"Postgresql","metadata":{"labels":{"team":"acid"},"name":"acid-testcluster42","namespace"`),
 | 
				
			||||||
		PostgresqlList{},
 | 
							PostgresqlList{},
 | 
				
			||||||
		errors.New("unexpected end of JSON input")}}
 | 
							errors.New("unexpected end of JSON input")}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var annotations = []struct {
 | 
					var podAnnotations = []struct {
 | 
				
			||||||
 | 
						about       string
 | 
				
			||||||
	in          []byte
 | 
						in          []byte
 | 
				
			||||||
	annotations map[string]string
 | 
						annotations map[string]string
 | 
				
			||||||
	err         error
 | 
						err         error
 | 
				
			||||||
}{{
 | 
					}{{
 | 
				
			||||||
	in:          []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "acid-testcluster1"}, "spec": {"podAnnotations": {"foo": "bar"},"teamId": "acid", "clone": {"cluster": "team-batman"}}}`),
 | 
						about: "common annotations",
 | 
				
			||||||
 | 
						in: []byte(`{
 | 
				
			||||||
 | 
							"kind": "Postgresql",
 | 
				
			||||||
 | 
							"apiVersion": "acid.zalan.do/v1",
 | 
				
			||||||
 | 
							"metadata": {
 | 
				
			||||||
 | 
								"name": "acid-testcluster1"
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"spec": {
 | 
				
			||||||
 | 
								"podAnnotations": {
 | 
				
			||||||
 | 
									"foo": "bar"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"teamId": "acid",
 | 
				
			||||||
 | 
								"clone": {
 | 
				
			||||||
 | 
									"cluster": "team-batman"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}`),
 | 
				
			||||||
	annotations: map[string]string{"foo": "bar"},
 | 
						annotations: map[string]string{"foo": "bar"},
 | 
				
			||||||
	err:         nil},
 | 
						err:         nil},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var serviceAnnotations = []struct {
 | 
				
			||||||
 | 
						about       string
 | 
				
			||||||
 | 
						in          []byte
 | 
				
			||||||
 | 
						annotations map[string]string
 | 
				
			||||||
 | 
						err         error
 | 
				
			||||||
 | 
					}{
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							about: "common single annotation",
 | 
				
			||||||
 | 
							in: []byte(`{
 | 
				
			||||||
 | 
								"kind": "Postgresql",
 | 
				
			||||||
 | 
								"apiVersion": "acid.zalan.do/v1",
 | 
				
			||||||
 | 
								"metadata": {
 | 
				
			||||||
 | 
									"name": "acid-testcluster1"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"spec": {
 | 
				
			||||||
 | 
									"serviceAnnotations": {
 | 
				
			||||||
 | 
										"foo": "bar"
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									"teamId": "acid",
 | 
				
			||||||
 | 
									"clone": {
 | 
				
			||||||
 | 
										"cluster": "team-batman"
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}`),
 | 
				
			||||||
 | 
							annotations: map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
							err:         nil,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							about: "common two annotations",
 | 
				
			||||||
 | 
							in: []byte(`{
 | 
				
			||||||
 | 
								"kind": "Postgresql",
 | 
				
			||||||
 | 
								"apiVersion": "acid.zalan.do/v1",
 | 
				
			||||||
 | 
								"metadata": {
 | 
				
			||||||
 | 
									"name": "acid-testcluster1"
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"spec": {
 | 
				
			||||||
 | 
									"serviceAnnotations": {
 | 
				
			||||||
 | 
										"foo": "bar",
 | 
				
			||||||
 | 
										"post": "gres"
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									"teamId": "acid",
 | 
				
			||||||
 | 
									"clone": {
 | 
				
			||||||
 | 
										"cluster": "team-batman"
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}`),
 | 
				
			||||||
 | 
							annotations: map[string]string{"foo": "bar", "post": "gres"},
 | 
				
			||||||
 | 
							err:         nil,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func mustParseTime(s string) metav1.Time {
 | 
					func mustParseTime(s string) metav1.Time {
 | 
				
			||||||
	v, err := time.Parse("15:04", s)
 | 
						v, err := time.Parse("15:04", s)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -458,12 +545,13 @@ func mustParseTime(s string) metav1.Time {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestParseTime(t *testing.T) {
 | 
					func TestParseTime(t *testing.T) {
 | 
				
			||||||
	for _, tt := range parseTimeTests {
 | 
						for _, tt := range parseTimeTests {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
			aTime, err := parseTime(tt.in)
 | 
								aTime, err := parseTime(tt.in)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				if tt.err == nil || err.Error() != tt.err.Error() {
 | 
									if tt.err == nil || err.Error() != tt.err.Error() {
 | 
				
			||||||
					t.Errorf("ParseTime expected error: %v, got: %v", tt.err, err)
 | 
										t.Errorf("ParseTime expected error: %v, got: %v", tt.err, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			continue
 | 
									return
 | 
				
			||||||
			} else if tt.err != nil {
 | 
								} else if tt.err != nil {
 | 
				
			||||||
				t.Errorf("Expected error: %v", tt.err)
 | 
									t.Errorf("Expected error: %v", tt.err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					@ -471,17 +559,19 @@ func TestParseTime(t *testing.T) {
 | 
				
			||||||
			if aTime != tt.out {
 | 
								if aTime != tt.out {
 | 
				
			||||||
				t.Errorf("Expected time: %v, got: %v", tt.out, aTime)
 | 
									t.Errorf("Expected time: %v, got: %v", tt.out, aTime)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestWeekdayTime(t *testing.T) {
 | 
					func TestWeekdayTime(t *testing.T) {
 | 
				
			||||||
	for _, tt := range parseWeekdayTests {
 | 
						for _, tt := range parseWeekdayTests {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
			aTime, err := parseWeekday(tt.in)
 | 
								aTime, err := parseWeekday(tt.in)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				if tt.err == nil || err.Error() != tt.err.Error() {
 | 
									if tt.err == nil || err.Error() != tt.err.Error() {
 | 
				
			||||||
					t.Errorf("ParseWeekday expected error: %v, got: %v", tt.err, err)
 | 
										t.Errorf("ParseWeekday expected error: %v, got: %v", tt.err, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			continue
 | 
									return
 | 
				
			||||||
			} else if tt.err != nil {
 | 
								} else if tt.err != nil {
 | 
				
			||||||
				t.Errorf("Expected error: %v", tt.err)
 | 
									t.Errorf("Expected error: %v", tt.err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					@ -489,18 +579,20 @@ func TestWeekdayTime(t *testing.T) {
 | 
				
			||||||
			if aTime != tt.out {
 | 
								if aTime != tt.out {
 | 
				
			||||||
				t.Errorf("Expected weekday: %v, got: %v", tt.out, aTime)
 | 
									t.Errorf("Expected weekday: %v, got: %v", tt.out, aTime)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestClusterAnnotations(t *testing.T) {
 | 
					func TestPodAnnotations(t *testing.T) {
 | 
				
			||||||
	for _, tt := range annotations {
 | 
						for _, tt := range podAnnotations {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
			var cluster Postgresql
 | 
								var cluster Postgresql
 | 
				
			||||||
			err := cluster.UnmarshalJSON(tt.in)
 | 
								err := cluster.UnmarshalJSON(tt.in)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				if tt.err == nil || err.Error() != tt.err.Error() {
 | 
									if tt.err == nil || err.Error() != tt.err.Error() {
 | 
				
			||||||
				t.Errorf("Unable to marshal cluster with annotations: expected %v got %v", tt.err, err)
 | 
										t.Errorf("Unable to marshal cluster with podAnnotations: expected %v got %v", tt.err, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			continue
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			for k, v := range cluster.Spec.PodAnnotations {
 | 
								for k, v := range cluster.Spec.PodAnnotations {
 | 
				
			||||||
				found, expected := v, tt.annotations[k]
 | 
									found, expected := v, tt.annotations[k]
 | 
				
			||||||
| 
						 | 
					@ -508,28 +600,53 @@ func TestClusterAnnotations(t *testing.T) {
 | 
				
			||||||
					t.Errorf("Didn't find correct value for key %v in for podAnnotations: Expected %v found %v", k, expected, found)
 | 
										t.Errorf("Didn't find correct value for key %v in for podAnnotations: Expected %v found %v", k, expected, found)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestServiceAnnotations(t *testing.T) {
 | 
				
			||||||
 | 
						for _, tt := range serviceAnnotations {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
 | 
								var cluster Postgresql
 | 
				
			||||||
 | 
								err := cluster.UnmarshalJSON(tt.in)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									if tt.err == nil || err.Error() != tt.err.Error() {
 | 
				
			||||||
 | 
										t.Errorf("Unable to marshal cluster with serviceAnnotations: expected %v got %v", tt.err, err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								for k, v := range cluster.Spec.ServiceAnnotations {
 | 
				
			||||||
 | 
									found, expected := v, tt.annotations[k]
 | 
				
			||||||
 | 
									if found != expected {
 | 
				
			||||||
 | 
										t.Errorf("Didn't find correct value for key %v in for serviceAnnotations: Expected %v found %v", k, expected, found)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestClusterName(t *testing.T) {
 | 
					func TestClusterName(t *testing.T) {
 | 
				
			||||||
	for _, tt := range clusterNames {
 | 
						for _, tt := range clusterNames {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
			name, err := extractClusterName(tt.in, tt.inTeam)
 | 
								name, err := extractClusterName(tt.in, tt.inTeam)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				if tt.err == nil || err.Error() != tt.err.Error() {
 | 
									if tt.err == nil || err.Error() != tt.err.Error() {
 | 
				
			||||||
					t.Errorf("extractClusterName expected error: %v, got: %v", tt.err, err)
 | 
										t.Errorf("extractClusterName expected error: %v, got: %v", tt.err, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			continue
 | 
									return
 | 
				
			||||||
			} else if tt.err != nil {
 | 
								} else if tt.err != nil {
 | 
				
			||||||
				t.Errorf("Expected error: %v", tt.err)
 | 
									t.Errorf("Expected error: %v", tt.err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if name != tt.clusterName {
 | 
								if name != tt.clusterName {
 | 
				
			||||||
				t.Errorf("Expected cluserName: %q, got: %q", tt.clusterName, name)
 | 
									t.Errorf("Expected cluserName: %q, got: %q", tt.clusterName, name)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestCloneClusterDescription(t *testing.T) {
 | 
					func TestCloneClusterDescription(t *testing.T) {
 | 
				
			||||||
	for _, tt := range cloneClusterDescriptions {
 | 
						for _, tt := range cloneClusterDescriptions {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
			if err := validateCloneClusterDescription(tt.in); err != nil {
 | 
								if err := validateCloneClusterDescription(tt.in); err != nil {
 | 
				
			||||||
				if tt.err == nil || err.Error() != tt.err.Error() {
 | 
									if tt.err == nil || err.Error() != tt.err.Error() {
 | 
				
			||||||
					t.Errorf("testCloneClusterDescription expected error: %v, got: %v", tt.err, err)
 | 
										t.Errorf("testCloneClusterDescription expected error: %v, got: %v", tt.err, err)
 | 
				
			||||||
| 
						 | 
					@ -537,18 +654,20 @@ func TestCloneClusterDescription(t *testing.T) {
 | 
				
			||||||
			} else if tt.err != nil {
 | 
								} else if tt.err != nil {
 | 
				
			||||||
				t.Errorf("Expected error: %v", tt.err)
 | 
									t.Errorf("Expected error: %v", tt.err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestUnmarshalMaintenanceWindow(t *testing.T) {
 | 
					func TestUnmarshalMaintenanceWindow(t *testing.T) {
 | 
				
			||||||
	for _, tt := range maintenanceWindows {
 | 
						for _, tt := range maintenanceWindows {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
			var m MaintenanceWindow
 | 
								var m MaintenanceWindow
 | 
				
			||||||
			err := m.UnmarshalJSON(tt.in)
 | 
								err := m.UnmarshalJSON(tt.in)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				if tt.err == nil || err.Error() != tt.err.Error() {
 | 
									if tt.err == nil || err.Error() != tt.err.Error() {
 | 
				
			||||||
					t.Errorf("MaintenanceWindow unmarshal expected error: %v, got %v", tt.err, err)
 | 
										t.Errorf("MaintenanceWindow unmarshal expected error: %v, got %v", tt.err, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			continue
 | 
									return
 | 
				
			||||||
			} else if tt.err != nil {
 | 
								} else if tt.err != nil {
 | 
				
			||||||
				t.Errorf("Expected error: %v", tt.err)
 | 
									t.Errorf("Expected error: %v", tt.err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					@ -556,13 +675,15 @@ func TestUnmarshalMaintenanceWindow(t *testing.T) {
 | 
				
			||||||
			if !reflect.DeepEqual(m, tt.out) {
 | 
								if !reflect.DeepEqual(m, tt.out) {
 | 
				
			||||||
				t.Errorf("Expected maintenance window: %#v, got: %#v", tt.out, m)
 | 
									t.Errorf("Expected maintenance window: %#v, got: %#v", tt.out, m)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestMarshalMaintenanceWindow(t *testing.T) {
 | 
					func TestMarshalMaintenanceWindow(t *testing.T) {
 | 
				
			||||||
	for _, tt := range maintenanceWindows {
 | 
						for _, tt := range maintenanceWindows {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
			if tt.err != nil {
 | 
								if tt.err != nil {
 | 
				
			||||||
			continue
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			s, err := tt.out.MarshalJSON()
 | 
								s, err := tt.out.MarshalJSON()
 | 
				
			||||||
| 
						 | 
					@ -573,37 +694,40 @@ func TestMarshalMaintenanceWindow(t *testing.T) {
 | 
				
			||||||
			if !bytes.Equal(s, tt.in) {
 | 
								if !bytes.Equal(s, tt.in) {
 | 
				
			||||||
				t.Errorf("Expected Marshal: %q, got: %q", string(tt.in), string(s))
 | 
									t.Errorf("Expected Marshal: %q, got: %q", string(tt.in), string(s))
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestUnmarshalPostgresStatus(t *testing.T) {
 | 
					func TestUnmarshalPostgresStatus(t *testing.T) {
 | 
				
			||||||
	for _, tt := range postgresStatus {
 | 
						for _, tt := range postgresStatus {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			var ps PostgresStatus
 | 
								var ps PostgresStatus
 | 
				
			||||||
			err := ps.UnmarshalJSON(tt.in)
 | 
								err := ps.UnmarshalJSON(tt.in)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				if tt.err == nil || err.Error() != tt.err.Error() {
 | 
									if tt.err == nil || err.Error() != tt.err.Error() {
 | 
				
			||||||
					t.Errorf("CR status unmarshal expected error: %v, got %v", tt.err, err)
 | 
										t.Errorf("CR status unmarshal expected error: %v, got %v", tt.err, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			continue
 | 
									return
 | 
				
			||||||
			//} else if tt.err != nil {
 | 
					 | 
				
			||||||
			//t.Errorf("Expected error: %v", tt.err)
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !reflect.DeepEqual(ps, tt.out) {
 | 
								if !reflect.DeepEqual(ps, tt.out) {
 | 
				
			||||||
				t.Errorf("Expected status: %#v, got: %#v", tt.out, ps)
 | 
									t.Errorf("Expected status: %#v, got: %#v", tt.out, ps)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestPostgresUnmarshal(t *testing.T) {
 | 
					func TestPostgresUnmarshal(t *testing.T) {
 | 
				
			||||||
	for _, tt := range unmarshalCluster {
 | 
						for _, tt := range unmarshalCluster {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
			var cluster Postgresql
 | 
								var cluster Postgresql
 | 
				
			||||||
			err := cluster.UnmarshalJSON(tt.in)
 | 
								err := cluster.UnmarshalJSON(tt.in)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				if tt.err == nil || err.Error() != tt.err.Error() {
 | 
									if tt.err == nil || err.Error() != tt.err.Error() {
 | 
				
			||||||
					t.Errorf("Unmarshal expected error: %v, got: %v", tt.err, err)
 | 
										t.Errorf("Unmarshal expected error: %v, got: %v", tt.err, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			continue
 | 
									return
 | 
				
			||||||
			} else if tt.err != nil {
 | 
								} else if tt.err != nil {
 | 
				
			||||||
				t.Errorf("Expected error: %v", tt.err)
 | 
									t.Errorf("Expected error: %v", tt.err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					@ -611,13 +735,16 @@ func TestPostgresUnmarshal(t *testing.T) {
 | 
				
			||||||
			if !reflect.DeepEqual(cluster, tt.out) {
 | 
								if !reflect.DeepEqual(cluster, tt.out) {
 | 
				
			||||||
				t.Errorf("Expected Postgresql: %#v, got %#v", tt.out, cluster)
 | 
									t.Errorf("Expected Postgresql: %#v, got %#v", tt.out, cluster)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestMarshal(t *testing.T) {
 | 
					func TestMarshal(t *testing.T) {
 | 
				
			||||||
	for _, tt := range unmarshalCluster {
 | 
						for _, tt := range unmarshalCluster {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if tt.err != nil {
 | 
								if tt.err != nil {
 | 
				
			||||||
			continue
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Unmarshal and marshal example to capture api changes
 | 
								// Unmarshal and marshal example to capture api changes
 | 
				
			||||||
| 
						 | 
					@ -627,7 +754,7 @@ func TestMarshal(t *testing.T) {
 | 
				
			||||||
				if tt.err == nil || err.Error() != tt.err.Error() {
 | 
									if tt.err == nil || err.Error() != tt.err.Error() {
 | 
				
			||||||
					t.Errorf("Backwards compatibility unmarshal expected error: %v, got: %v", tt.err, err)
 | 
										t.Errorf("Backwards compatibility unmarshal expected error: %v, got: %v", tt.err, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			continue
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			expected, err := json.Marshal(cluster)
 | 
								expected, err := json.Marshal(cluster)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -641,11 +768,14 @@ func TestMarshal(t *testing.T) {
 | 
				
			||||||
			if !bytes.Equal(m, expected) {
 | 
								if !bytes.Equal(m, expected) {
 | 
				
			||||||
				t.Errorf("Marshal Postgresql \nexpected: %q, \ngot:      %q", string(expected), string(m))
 | 
									t.Errorf("Marshal Postgresql \nexpected: %q, \ngot:      %q", string(expected), string(m))
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestPostgresMeta(t *testing.T) {
 | 
					func TestPostgresMeta(t *testing.T) {
 | 
				
			||||||
	for _, tt := range unmarshalCluster {
 | 
						for _, tt := range unmarshalCluster {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if a := tt.out.GetObjectKind(); a != &tt.out.TypeMeta {
 | 
								if a := tt.out.GetObjectKind(); a != &tt.out.TypeMeta {
 | 
				
			||||||
				t.Errorf("GetObjectKindMeta \nexpected: %v, \ngot:       %v", tt.out.TypeMeta, a)
 | 
									t.Errorf("GetObjectKindMeta \nexpected: %v, \ngot:       %v", tt.out.TypeMeta, a)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					@ -653,13 +783,15 @@ func TestPostgresMeta(t *testing.T) {
 | 
				
			||||||
			if a := tt.out.GetObjectMeta(); reflect.DeepEqual(a, tt.out.ObjectMeta) {
 | 
								if a := tt.out.GetObjectMeta(); reflect.DeepEqual(a, tt.out.ObjectMeta) {
 | 
				
			||||||
				t.Errorf("GetObjectMeta \nexpected: %v, \ngot:       %v", tt.out.ObjectMeta, a)
 | 
									t.Errorf("GetObjectMeta \nexpected: %v, \ngot:       %v", tt.out.ObjectMeta, a)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestPostgresListMeta(t *testing.T) {
 | 
					func TestPostgresListMeta(t *testing.T) {
 | 
				
			||||||
	for _, tt := range postgresqlList {
 | 
						for _, tt := range postgresqlList {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
			if tt.err != nil {
 | 
								if tt.err != nil {
 | 
				
			||||||
			continue
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if a := tt.out.GetObjectKind(); a != &tt.out.TypeMeta {
 | 
								if a := tt.out.GetObjectKind(); a != &tt.out.TypeMeta {
 | 
				
			||||||
| 
						 | 
					@ -671,17 +803,19 @@ func TestPostgresListMeta(t *testing.T) {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestPostgresqlClone(t *testing.T) {
 | 
					func TestPostgresqlClone(t *testing.T) {
 | 
				
			||||||
	for _, tt := range unmarshalCluster {
 | 
						for _, tt := range unmarshalCluster {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
			cp := &tt.out
 | 
								cp := &tt.out
 | 
				
			||||||
			cp.Error = ""
 | 
								cp.Error = ""
 | 
				
			||||||
			clone := cp.Clone()
 | 
								clone := cp.Clone()
 | 
				
			||||||
			if !reflect.DeepEqual(clone, cp) {
 | 
								if !reflect.DeepEqual(clone, cp) {
 | 
				
			||||||
				t.Errorf("TestPostgresqlClone expected: \n%#v\n, got \n%#v", cp, clone)
 | 
									t.Errorf("TestPostgresqlClone expected: \n%#v\n, got \n%#v", cp, clone)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
// +build !ignore_autogenerated
 | 
					// +build !ignore_autogenerated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					@ -521,6 +521,13 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) {
 | 
				
			||||||
			(*out)[key] = val
 | 
								(*out)[key] = val
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if in.ServiceAnnotations != nil {
 | 
				
			||||||
 | 
							in, out := &in.ServiceAnnotations, &out.ServiceAnnotations
 | 
				
			||||||
 | 
							*out = make(map[string]string, len(*in))
 | 
				
			||||||
 | 
							for key, val := range *in {
 | 
				
			||||||
 | 
								(*out)[key] = val
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if in.InitContainersOld != nil {
 | 
						if in.InitContainersOld != nil {
 | 
				
			||||||
		in, out := &in.InitContainersOld, &out.InitContainersOld
 | 
							in, out := &in.InitContainersOld, &out.InitContainersOld
 | 
				
			||||||
		*out = make([]corev1.Container, len(*in))
 | 
							*out = make([]corev1.Container, len(*in))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,7 @@ import (
 | 
				
			||||||
	"github.com/zalando/postgres-operator/pkg/util/patroni"
 | 
						"github.com/zalando/postgres-operator/pkg/util/patroni"
 | 
				
			||||||
	"github.com/zalando/postgres-operator/pkg/util/teams"
 | 
						"github.com/zalando/postgres-operator/pkg/util/teams"
 | 
				
			||||||
	"github.com/zalando/postgres-operator/pkg/util/users"
 | 
						"github.com/zalando/postgres-operator/pkg/util/users"
 | 
				
			||||||
	rbacv1beta1 "k8s.io/api/rbac/v1beta1"
 | 
						rbacv1 "k8s.io/api/rbac/v1"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,7 @@ type Config struct {
 | 
				
			||||||
	RestConfig                   *rest.Config
 | 
						RestConfig                   *rest.Config
 | 
				
			||||||
	InfrastructureRoles          map[string]spec.PgUser // inherited from the controller
 | 
						InfrastructureRoles          map[string]spec.PgUser // inherited from the controller
 | 
				
			||||||
	PodServiceAccount            *v1.ServiceAccount
 | 
						PodServiceAccount            *v1.ServiceAccount
 | 
				
			||||||
	PodServiceAccountRoleBinding *rbacv1beta1.RoleBinding
 | 
						PodServiceAccountRoleBinding *rbacv1.RoleBinding
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type kubeResources struct {
 | 
					type kubeResources struct {
 | 
				
			||||||
| 
						 | 
					@ -232,8 +232,8 @@ func (c *Cluster) Create() error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.setStatus(acidv1.ClusterStatusCreating)
 | 
						c.setStatus(acidv1.ClusterStatusCreating)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = c.validateResources(&c.Spec); err != nil {
 | 
						if err = c.enforceMinResourceLimits(&c.Spec); err != nil {
 | 
				
			||||||
		return fmt.Errorf("insufficient resource limits specified: %v", err)
 | 
							return fmt.Errorf("could not enforce minimum resource limits: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, role := range []PostgresRole{Master, Replica} {
 | 
						for _, role := range []PostgresRole{Master, Replica} {
 | 
				
			||||||
| 
						 | 
					@ -503,38 +503,38 @@ func compareResourcesAssumeFirstNotNil(a *v1.ResourceRequirements, b *v1.Resourc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Cluster) validateResources(spec *acidv1.PostgresSpec) error {
 | 
					func (c *Cluster) enforceMinResourceLimits(spec *acidv1.PostgresSpec) error {
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// setting limits too low can cause unnecessary evictions / OOM kills
 | 
					 | 
				
			||||||
	const (
 | 
					 | 
				
			||||||
		cpuMinLimit    = "256m"
 | 
					 | 
				
			||||||
		memoryMinLimit = "256Mi"
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
		isSmaller bool
 | 
							isSmaller bool
 | 
				
			||||||
		err       error
 | 
							err       error
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// setting limits too low can cause unnecessary evictions / OOM kills
 | 
				
			||||||
 | 
						minCPULimit := c.OpConfig.MinCPULimit
 | 
				
			||||||
 | 
						minMemoryLimit := c.OpConfig.MinMemoryLimit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cpuLimit := spec.Resources.ResourceLimits.CPU
 | 
						cpuLimit := spec.Resources.ResourceLimits.CPU
 | 
				
			||||||
	if cpuLimit != "" {
 | 
						if cpuLimit != "" {
 | 
				
			||||||
		isSmaller, err = util.IsSmallerQuantity(cpuLimit, cpuMinLimit)
 | 
							isSmaller, err = util.IsSmallerQuantity(cpuLimit, minCPULimit)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return fmt.Errorf("error validating CPU limit: %v", err)
 | 
								return fmt.Errorf("could not compare defined CPU limit %s with configured minimum value %s: %v", cpuLimit, minCPULimit, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if isSmaller {
 | 
							if isSmaller {
 | 
				
			||||||
			return fmt.Errorf("defined CPU limit %s is below required minimum %s to properly run postgresql resource", cpuLimit, cpuMinLimit)
 | 
								c.logger.Warningf("defined CPU limit %s is below required minimum %s and will be set to it", cpuLimit, minCPULimit)
 | 
				
			||||||
 | 
								spec.Resources.ResourceLimits.CPU = minCPULimit
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	memoryLimit := spec.Resources.ResourceLimits.Memory
 | 
						memoryLimit := spec.Resources.ResourceLimits.Memory
 | 
				
			||||||
	if memoryLimit != "" {
 | 
						if memoryLimit != "" {
 | 
				
			||||||
		isSmaller, err = util.IsSmallerQuantity(memoryLimit, memoryMinLimit)
 | 
							isSmaller, err = util.IsSmallerQuantity(memoryLimit, minMemoryLimit)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return fmt.Errorf("error validating memory limit: %v", err)
 | 
								return fmt.Errorf("could not compare defined memory limit %s with configured minimum value %s: %v", memoryLimit, minMemoryLimit, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if isSmaller {
 | 
							if isSmaller {
 | 
				
			||||||
			return fmt.Errorf("defined memory limit %s is below required minimum %s to properly run postgresql resource", memoryLimit, memoryMinLimit)
 | 
								c.logger.Warningf("defined memory limit %s is below required minimum %s and will be set to it", memoryLimit, minMemoryLimit)
 | 
				
			||||||
 | 
								spec.Resources.ResourceLimits.Memory = minMemoryLimit
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -551,7 +551,6 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
 | 
				
			||||||
	c.mu.Lock()
 | 
						c.mu.Lock()
 | 
				
			||||||
	defer c.mu.Unlock()
 | 
						defer c.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	oldStatus := c.Status
 | 
					 | 
				
			||||||
	c.setStatus(acidv1.ClusterStatusUpdating)
 | 
						c.setStatus(acidv1.ClusterStatusUpdating)
 | 
				
			||||||
	c.setSpec(newSpec)
 | 
						c.setSpec(newSpec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -563,22 +562,6 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := c.validateResources(&newSpec.Spec); err != nil {
 | 
					 | 
				
			||||||
		err = fmt.Errorf("insufficient resource limits specified: %v", err)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// cancel update only when (already too low) pod resources were edited
 | 
					 | 
				
			||||||
		// if cluster was successfully running before the update, continue but log a warning
 | 
					 | 
				
			||||||
		isCPULimitSmaller, err2 := util.IsSmallerQuantity(newSpec.Spec.Resources.ResourceLimits.CPU, oldSpec.Spec.Resources.ResourceLimits.CPU)
 | 
					 | 
				
			||||||
		isMemoryLimitSmaller, err3 := util.IsSmallerQuantity(newSpec.Spec.Resources.ResourceLimits.Memory, oldSpec.Spec.Resources.ResourceLimits.Memory)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if oldStatus.Running() && !isCPULimitSmaller && !isMemoryLimitSmaller && err2 == nil && err3 == nil {
 | 
					 | 
				
			||||||
			c.logger.Warning(err)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			updateFailed = true
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if oldSpec.Spec.PgVersion != newSpec.Spec.PgVersion { // PG versions comparison
 | 
						if oldSpec.Spec.PgVersion != newSpec.Spec.PgVersion { // PG versions comparison
 | 
				
			||||||
		c.logger.Warningf("postgresql version change(%q -> %q) has no effect", oldSpec.Spec.PgVersion, newSpec.Spec.PgVersion)
 | 
							c.logger.Warningf("postgresql version change(%q -> %q) has no effect", oldSpec.Spec.PgVersion, newSpec.Spec.PgVersion)
 | 
				
			||||||
		//we need that hack to generate statefulset with the old version
 | 
							//we need that hack to generate statefulset with the old version
 | 
				
			||||||
| 
						 | 
					@ -624,6 +607,12 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Statefulset
 | 
						// Statefulset
 | 
				
			||||||
	func() {
 | 
						func() {
 | 
				
			||||||
 | 
							if err := c.enforceMinResourceLimits(&c.Spec); err != nil {
 | 
				
			||||||
 | 
								c.logger.Errorf("could not sync resources: %v", err)
 | 
				
			||||||
 | 
								updateFailed = true
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		oldSs, err := c.generateStatefulSet(&oldSpec.Spec)
 | 
							oldSs, err := c.generateStatefulSet(&oldSpec.Spec)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			c.logger.Errorf("could not generate old statefulset spec: %v", err)
 | 
								c.logger.Errorf("could not generate old statefulset spec: %v", err)
 | 
				
			||||||
| 
						 | 
					@ -631,6 +620,9 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// update newSpec to for latter comparison with oldSpec
 | 
				
			||||||
 | 
							c.enforceMinResourceLimits(&newSpec.Spec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		newSs, err := c.generateStatefulSet(&newSpec.Spec)
 | 
							newSs, err := c.generateStatefulSet(&newSpec.Spec)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			c.logger.Errorf("could not generate new statefulset spec: %v", err)
 | 
								c.logger.Errorf("could not generate new statefulset spec: %v", err)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -355,6 +355,12 @@ func TestPodAnnotations(t *testing.T) {
 | 
				
			||||||
			database: map[string]string{"foo": "bar"},
 | 
								database: map[string]string{"foo": "bar"},
 | 
				
			||||||
			merged:   map[string]string{"foo": "bar"},
 | 
								merged:   map[string]string{"foo": "bar"},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								subTest:  "Both Annotations",
 | 
				
			||||||
 | 
								operator: map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
								database: map[string]string{"post": "gres"},
 | 
				
			||||||
 | 
								merged:   map[string]string{"foo": "bar", "post": "gres"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			subTest:  "Database Config overrides Operator Config Annotations",
 | 
								subTest:  "Database Config overrides Operator Config Annotations",
 | 
				
			||||||
			operator: map[string]string{"foo": "bar", "global": "foo"},
 | 
								operator: map[string]string{"foo": "bar", "global": "foo"},
 | 
				
			||||||
| 
						 | 
					@ -382,3 +388,319 @@ func TestPodAnnotations(t *testing.T) {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestServiceAnnotations(t *testing.T) {
 | 
				
			||||||
 | 
						enabled := true
 | 
				
			||||||
 | 
						disabled := false
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							about                         string
 | 
				
			||||||
 | 
							role                          PostgresRole
 | 
				
			||||||
 | 
							enableMasterLoadBalancerSpec  *bool
 | 
				
			||||||
 | 
							enableMasterLoadBalancerOC    bool
 | 
				
			||||||
 | 
							enableReplicaLoadBalancerSpec *bool
 | 
				
			||||||
 | 
							enableReplicaLoadBalancerOC   bool
 | 
				
			||||||
 | 
							operatorAnnotations           map[string]string
 | 
				
			||||||
 | 
							clusterAnnotations            map[string]string
 | 
				
			||||||
 | 
							expect                        map[string]string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							//MASTER
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                        "Master with no annotations and EnableMasterLoadBalancer disabled on spec and OperatorConfig",
 | 
				
			||||||
 | 
								role:                         "master",
 | 
				
			||||||
 | 
								enableMasterLoadBalancerSpec: &disabled,
 | 
				
			||||||
 | 
								enableMasterLoadBalancerOC:   false,
 | 
				
			||||||
 | 
								operatorAnnotations:          make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations:           make(map[string]string),
 | 
				
			||||||
 | 
								expect:                       make(map[string]string),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                        "Master with no annotations and EnableMasterLoadBalancer enabled on spec",
 | 
				
			||||||
 | 
								role:                         "master",
 | 
				
			||||||
 | 
								enableMasterLoadBalancerSpec: &enabled,
 | 
				
			||||||
 | 
								enableMasterLoadBalancerOC:   false,
 | 
				
			||||||
 | 
								operatorAnnotations:          make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations:           make(map[string]string),
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                        "Master with no annotations and EnableMasterLoadBalancer enabled only on operator config",
 | 
				
			||||||
 | 
								role:                         "master",
 | 
				
			||||||
 | 
								enableMasterLoadBalancerSpec: &disabled,
 | 
				
			||||||
 | 
								enableMasterLoadBalancerOC:   true,
 | 
				
			||||||
 | 
								operatorAnnotations:          make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations:           make(map[string]string),
 | 
				
			||||||
 | 
								expect:                       make(map[string]string),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                      "Master with no annotations and EnableMasterLoadBalancer defined only on operator config",
 | 
				
			||||||
 | 
								role:                       "master",
 | 
				
			||||||
 | 
								enableMasterLoadBalancerOC: true,
 | 
				
			||||||
 | 
								operatorAnnotations:        make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations:         make(map[string]string),
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                      "Master with cluster annotations and load balancer enabled",
 | 
				
			||||||
 | 
								role:                       "master",
 | 
				
			||||||
 | 
								enableMasterLoadBalancerOC: true,
 | 
				
			||||||
 | 
								operatorAnnotations:        make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations:         map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
 | 
				
			||||||
 | 
									"foo": "bar",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                        "Master with cluster annotations and load balancer disabled",
 | 
				
			||||||
 | 
								role:                         "master",
 | 
				
			||||||
 | 
								enableMasterLoadBalancerSpec: &disabled,
 | 
				
			||||||
 | 
								enableMasterLoadBalancerOC:   true,
 | 
				
			||||||
 | 
								operatorAnnotations:          make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations:           map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
								expect:                       map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                      "Master with operator annotations and load balancer enabled",
 | 
				
			||||||
 | 
								role:                       "master",
 | 
				
			||||||
 | 
								enableMasterLoadBalancerOC: true,
 | 
				
			||||||
 | 
								operatorAnnotations:        map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
								clusterAnnotations:         make(map[string]string),
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
 | 
				
			||||||
 | 
									"foo": "bar",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                      "Master with operator annotations override default annotations",
 | 
				
			||||||
 | 
								role:                       "master",
 | 
				
			||||||
 | 
								enableMasterLoadBalancerOC: true,
 | 
				
			||||||
 | 
								operatorAnnotations: map[string]string{
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "1800",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								clusterAnnotations: make(map[string]string),
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "1800",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                      "Master with cluster annotations override default annotations",
 | 
				
			||||||
 | 
								role:                       "master",
 | 
				
			||||||
 | 
								enableMasterLoadBalancerOC: true,
 | 
				
			||||||
 | 
								operatorAnnotations:        make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations: map[string]string{
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "1800",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "1800",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                      "Master with cluster annotations do not override external-dns annotations",
 | 
				
			||||||
 | 
								role:                       "master",
 | 
				
			||||||
 | 
								enableMasterLoadBalancerOC: true,
 | 
				
			||||||
 | 
								operatorAnnotations:        make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname": "wrong.external-dns-name.example.com",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                      "Master with operator annotations do not override external-dns annotations",
 | 
				
			||||||
 | 
								role:                       "master",
 | 
				
			||||||
 | 
								enableMasterLoadBalancerOC: true,
 | 
				
			||||||
 | 
								clusterAnnotations:         make(map[string]string),
 | 
				
			||||||
 | 
								operatorAnnotations: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname": "wrong.external-dns-name.example.com",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// REPLICA
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                         "Replica with no annotations and EnableReplicaLoadBalancer disabled on spec and OperatorConfig",
 | 
				
			||||||
 | 
								role:                          "replica",
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerSpec: &disabled,
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerOC:   false,
 | 
				
			||||||
 | 
								operatorAnnotations:           make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations:            make(map[string]string),
 | 
				
			||||||
 | 
								expect:                        make(map[string]string),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                         "Replica with no annotations and EnableReplicaLoadBalancer enabled on spec",
 | 
				
			||||||
 | 
								role:                          "replica",
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerSpec: &enabled,
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerOC:   false,
 | 
				
			||||||
 | 
								operatorAnnotations:           make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations:            make(map[string]string),
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test-repl.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                         "Replica with no annotations and EnableReplicaLoadBalancer enabled only on operator config",
 | 
				
			||||||
 | 
								role:                          "replica",
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerSpec: &disabled,
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerOC:   true,
 | 
				
			||||||
 | 
								operatorAnnotations:           make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations:            make(map[string]string),
 | 
				
			||||||
 | 
								expect:                        make(map[string]string),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                       "Replica with no annotations and EnableReplicaLoadBalancer defined only on operator config",
 | 
				
			||||||
 | 
								role:                        "replica",
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerOC: true,
 | 
				
			||||||
 | 
								operatorAnnotations:         make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations:          make(map[string]string),
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test-repl.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                       "Replica with cluster annotations and load balancer enabled",
 | 
				
			||||||
 | 
								role:                        "replica",
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerOC: true,
 | 
				
			||||||
 | 
								operatorAnnotations:         make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations:          map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test-repl.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
 | 
				
			||||||
 | 
									"foo": "bar",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                         "Replica with cluster annotations and load balancer disabled",
 | 
				
			||||||
 | 
								role:                          "replica",
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerSpec: &disabled,
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerOC:   true,
 | 
				
			||||||
 | 
								operatorAnnotations:           make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations:            map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
								expect:                        map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                       "Replica with operator annotations and load balancer enabled",
 | 
				
			||||||
 | 
								role:                        "replica",
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerOC: true,
 | 
				
			||||||
 | 
								operatorAnnotations:         map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
								clusterAnnotations:          make(map[string]string),
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test-repl.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
 | 
				
			||||||
 | 
									"foo": "bar",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                       "Replica with operator annotations override default annotations",
 | 
				
			||||||
 | 
								role:                        "replica",
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerOC: true,
 | 
				
			||||||
 | 
								operatorAnnotations: map[string]string{
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "1800",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								clusterAnnotations: make(map[string]string),
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test-repl.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "1800",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                       "Replica with cluster annotations override default annotations",
 | 
				
			||||||
 | 
								role:                        "replica",
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerOC: true,
 | 
				
			||||||
 | 
								operatorAnnotations:         make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations: map[string]string{
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "1800",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test-repl.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "1800",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                       "Replica with cluster annotations do not override external-dns annotations",
 | 
				
			||||||
 | 
								role:                        "replica",
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerOC: true,
 | 
				
			||||||
 | 
								operatorAnnotations:         make(map[string]string),
 | 
				
			||||||
 | 
								clusterAnnotations: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname": "wrong.external-dns-name.example.com",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test-repl.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                       "Replica with operator annotations do not override external-dns annotations",
 | 
				
			||||||
 | 
								role:                        "replica",
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerOC: true,
 | 
				
			||||||
 | 
								clusterAnnotations:          make(map[string]string),
 | 
				
			||||||
 | 
								operatorAnnotations: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname": "wrong.external-dns-name.example.com",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expect: map[string]string{
 | 
				
			||||||
 | 
									"external-dns.alpha.kubernetes.io/hostname":                            "test-repl.acid.db.example.com",
 | 
				
			||||||
 | 
									"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// COMMON
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                       "cluster annotations append to operator annotations",
 | 
				
			||||||
 | 
								role:                        "replica",
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerOC: false,
 | 
				
			||||||
 | 
								operatorAnnotations:         map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
								clusterAnnotations:          map[string]string{"post": "gres"},
 | 
				
			||||||
 | 
								expect:                      map[string]string{"foo": "bar", "post": "gres"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about:                       "cluster annotations override operator annotations",
 | 
				
			||||||
 | 
								role:                        "replica",
 | 
				
			||||||
 | 
								enableReplicaLoadBalancerOC: false,
 | 
				
			||||||
 | 
								operatorAnnotations:         map[string]string{"foo": "bar", "post": "gres"},
 | 
				
			||||||
 | 
								clusterAnnotations:          map[string]string{"post": "greSQL"},
 | 
				
			||||||
 | 
								expect:                      map[string]string{"foo": "bar", "post": "greSQL"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
 | 
								cl.OpConfig.CustomServiceAnnotations = tt.operatorAnnotations
 | 
				
			||||||
 | 
								cl.OpConfig.EnableMasterLoadBalancer = tt.enableMasterLoadBalancerOC
 | 
				
			||||||
 | 
								cl.OpConfig.EnableReplicaLoadBalancer = tt.enableReplicaLoadBalancerOC
 | 
				
			||||||
 | 
								cl.OpConfig.MasterDNSNameFormat = "{cluster}.{team}.{hostedzone}"
 | 
				
			||||||
 | 
								cl.OpConfig.ReplicaDNSNameFormat = "{cluster}-repl.{team}.{hostedzone}"
 | 
				
			||||||
 | 
								cl.OpConfig.DbHostedZone = "db.example.com"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								cl.Postgresql.Spec.ClusterName = "test"
 | 
				
			||||||
 | 
								cl.Postgresql.Spec.TeamID = "acid"
 | 
				
			||||||
 | 
								cl.Postgresql.Spec.ServiceAnnotations = tt.clusterAnnotations
 | 
				
			||||||
 | 
								cl.Postgresql.Spec.EnableMasterLoadBalancer = tt.enableMasterLoadBalancerSpec
 | 
				
			||||||
 | 
								cl.Postgresql.Spec.EnableReplicaLoadBalancer = tt.enableReplicaLoadBalancerSpec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								got := cl.generateServiceAnnotations(tt.role, &cl.Postgresql.Spec)
 | 
				
			||||||
 | 
								if len(tt.expect) != len(got) {
 | 
				
			||||||
 | 
									t.Errorf("expected %d annotation(s), got %d", len(tt.expect), len(got))
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								for k, v := range got {
 | 
				
			||||||
 | 
									if tt.expect[k] != v {
 | 
				
			||||||
 | 
										t.Errorf("expected annotation '%v' with value '%v', got value '%v'", k, tt.expect[k], v)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1051,6 +1051,7 @@ func (c *Cluster) getNumberOfInstances(spec *acidv1.PostgresSpec) int32 {
 | 
				
			||||||
	/* Limit the max number of pods to one, if this is standby-cluster */
 | 
						/* Limit the max number of pods to one, if this is standby-cluster */
 | 
				
			||||||
	if spec.StandbyCluster != nil {
 | 
						if spec.StandbyCluster != nil {
 | 
				
			||||||
		c.logger.Info("Standby cluster can have maximum of 1 pod")
 | 
							c.logger.Info("Standby cluster can have maximum of 1 pod")
 | 
				
			||||||
 | 
							min = 1
 | 
				
			||||||
		max = 1
 | 
							max = 1
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if max >= 0 && newcur > max {
 | 
						if max >= 0 && newcur > max {
 | 
				
			||||||
| 
						 | 
					@ -1236,14 +1237,6 @@ func (c *Cluster) shouldCreateLoadBalancerForService(role PostgresRole, spec *ac
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Cluster) generateService(role PostgresRole, spec *acidv1.PostgresSpec) *v1.Service {
 | 
					func (c *Cluster) generateService(role PostgresRole, spec *acidv1.PostgresSpec) *v1.Service {
 | 
				
			||||||
	var dnsName string
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if role == Master {
 | 
					 | 
				
			||||||
		dnsName = c.masterDNSName()
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		dnsName = c.replicaDNSName()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	serviceSpec := v1.ServiceSpec{
 | 
						serviceSpec := v1.ServiceSpec{
 | 
				
			||||||
		Ports: []v1.ServicePort{{Name: "postgresql", Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}}},
 | 
							Ports: []v1.ServicePort{{Name: "postgresql", Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}}},
 | 
				
			||||||
		Type:  v1.ServiceTypeClusterIP,
 | 
							Type:  v1.ServiceTypeClusterIP,
 | 
				
			||||||
| 
						 | 
					@ -1253,8 +1246,6 @@ func (c *Cluster) generateService(role PostgresRole, spec *acidv1.PostgresSpec)
 | 
				
			||||||
		serviceSpec.Selector = c.roleLabelsSet(false, role)
 | 
							serviceSpec.Selector = c.roleLabelsSet(false, role)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var annotations map[string]string
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if c.shouldCreateLoadBalancerForService(role, spec) {
 | 
						if c.shouldCreateLoadBalancerForService(role, spec) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// spec.AllowedSourceRanges evaluates to the empty slice of zero length
 | 
							// spec.AllowedSourceRanges evaluates to the empty slice of zero length
 | 
				
			||||||
| 
						 | 
					@ -1268,18 +1259,6 @@ func (c *Cluster) generateService(role PostgresRole, spec *acidv1.PostgresSpec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		c.logger.Debugf("final load balancer source ranges as seen in a service spec (not necessarily applied): %q", serviceSpec.LoadBalancerSourceRanges)
 | 
							c.logger.Debugf("final load balancer source ranges as seen in a service spec (not necessarily applied): %q", serviceSpec.LoadBalancerSourceRanges)
 | 
				
			||||||
		serviceSpec.Type = v1.ServiceTypeLoadBalancer
 | 
							serviceSpec.Type = v1.ServiceTypeLoadBalancer
 | 
				
			||||||
 | 
					 | 
				
			||||||
		annotations = map[string]string{
 | 
					 | 
				
			||||||
			constants.ZalandoDNSNameAnnotation: dnsName,
 | 
					 | 
				
			||||||
			constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if len(c.OpConfig.CustomServiceAnnotations) != 0 {
 | 
					 | 
				
			||||||
			c.logger.Debugf("There are custom annotations defined, creating them.")
 | 
					 | 
				
			||||||
			for customAnnotationKey, customAnnotationValue := range c.OpConfig.CustomServiceAnnotations {
 | 
					 | 
				
			||||||
				annotations[customAnnotationKey] = customAnnotationValue
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else if role == Replica {
 | 
						} else if role == Replica {
 | 
				
			||||||
		// before PR #258, the replica service was only created if allocated a LB
 | 
							// before PR #258, the replica service was only created if allocated a LB
 | 
				
			||||||
		// now we always create the service but warn if the LB is absent
 | 
							// now we always create the service but warn if the LB is absent
 | 
				
			||||||
| 
						 | 
					@ -1291,7 +1270,7 @@ func (c *Cluster) generateService(role PostgresRole, spec *acidv1.PostgresSpec)
 | 
				
			||||||
			Name:        c.serviceName(role),
 | 
								Name:        c.serviceName(role),
 | 
				
			||||||
			Namespace:   c.Namespace,
 | 
								Namespace:   c.Namespace,
 | 
				
			||||||
			Labels:      c.roleLabelsSet(true, role),
 | 
								Labels:      c.roleLabelsSet(true, role),
 | 
				
			||||||
			Annotations: annotations,
 | 
								Annotations: c.generateServiceAnnotations(role, spec),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		Spec: serviceSpec,
 | 
							Spec: serviceSpec,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -1299,6 +1278,42 @@ func (c *Cluster) generateService(role PostgresRole, spec *acidv1.PostgresSpec)
 | 
				
			||||||
	return service
 | 
						return service
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Cluster) generateServiceAnnotations(role PostgresRole, spec *acidv1.PostgresSpec) map[string]string {
 | 
				
			||||||
 | 
						annotations := make(map[string]string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for k, v := range c.OpConfig.CustomServiceAnnotations {
 | 
				
			||||||
 | 
							annotations[k] = v
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if spec != nil || spec.ServiceAnnotations != nil {
 | 
				
			||||||
 | 
							for k, v := range spec.ServiceAnnotations {
 | 
				
			||||||
 | 
								annotations[k] = v
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.shouldCreateLoadBalancerForService(role, spec) {
 | 
				
			||||||
 | 
							var dnsName string
 | 
				
			||||||
 | 
							if role == Master {
 | 
				
			||||||
 | 
								dnsName = c.masterDNSName()
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								dnsName = c.replicaDNSName()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Just set ELB Timeout annotation with default value, if it does not
 | 
				
			||||||
 | 
							// have a cutom value
 | 
				
			||||||
 | 
							if _, ok := annotations[constants.ElbTimeoutAnnotationName]; !ok {
 | 
				
			||||||
 | 
								annotations[constants.ElbTimeoutAnnotationName] = constants.ElbTimeoutAnnotationValue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// External DNS name annotation is not customizable
 | 
				
			||||||
 | 
							annotations[constants.ZalandoDNSNameAnnotation] = dnsName
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(annotations) == 0 {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return annotations
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Cluster) generateEndpoint(role PostgresRole, subsets []v1.EndpointSubset) *v1.Endpoints {
 | 
					func (c *Cluster) generateEndpoint(role PostgresRole, subsets []v1.EndpointSubset) *v1.Endpoints {
 | 
				
			||||||
	endpoints := &v1.Endpoints{
 | 
						endpoints := &v1.Endpoints{
 | 
				
			||||||
		ObjectMeta: metav1.ObjectMeta{
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
| 
						 | 
					@ -1490,7 +1505,7 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1beta1.CronJob, error) {
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	labels := map[string]string{
 | 
						labels := map[string]string{
 | 
				
			||||||
		"version":     c.Name,
 | 
							c.OpConfig.ClusterNameLabel: c.Name,
 | 
				
			||||||
		"application":               "spilo-logical-backup",
 | 
							"application":               "spilo-logical-backup",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	podAffinityTerm := v1.PodAffinityTerm{
 | 
						podAffinityTerm := v1.PodAffinityTerm{
 | 
				
			||||||
| 
						 | 
					@ -1595,6 +1610,10 @@ func (c *Cluster) generateLogicalBackupPodEnvVars() []v1.EnvVar {
 | 
				
			||||||
			Name:  "LOGICAL_BACKUP_S3_BUCKET",
 | 
								Name:  "LOGICAL_BACKUP_S3_BUCKET",
 | 
				
			||||||
			Value: c.OpConfig.LogicalBackup.LogicalBackupS3Bucket,
 | 
								Value: c.OpConfig.LogicalBackup.LogicalBackupS3Bucket,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name:  "LOGICAL_BACKUP_S3_REGION",
 | 
				
			||||||
 | 
								Value: c.OpConfig.LogicalBackup.LogicalBackupS3Region,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			Name:  "LOGICAL_BACKUP_S3_ENDPOINT",
 | 
								Name:  "LOGICAL_BACKUP_S3_ENDPOINT",
 | 
				
			||||||
			Value: c.OpConfig.LogicalBackup.LogicalBackupS3Endpoint,
 | 
								Value: c.OpConfig.LogicalBackup.LogicalBackupS3Endpoint,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -366,6 +366,11 @@ func (c *Cluster) createService(role PostgresRole) (*v1.Service, error) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Cluster) updateService(role PostgresRole, newService *v1.Service) error {
 | 
					func (c *Cluster) updateService(role PostgresRole, newService *v1.Service) error {
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							svc *v1.Service
 | 
				
			||||||
 | 
							err error
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.setProcessName("updating %v service", role)
 | 
						c.setProcessName("updating %v service", role)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if c.Services[role] == nil {
 | 
						if c.Services[role] == nil {
 | 
				
			||||||
| 
						 | 
					@ -373,70 +378,6 @@ func (c *Cluster) updateService(role PostgresRole, newService *v1.Service) error
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	serviceName := util.NameFromMeta(c.Services[role].ObjectMeta)
 | 
						serviceName := util.NameFromMeta(c.Services[role].ObjectMeta)
 | 
				
			||||||
	endpointName := util.NameFromMeta(c.Endpoints[role].ObjectMeta)
 | 
					 | 
				
			||||||
	// TODO: check if it possible to change the service type with a patch in future versions of Kubernetes
 | 
					 | 
				
			||||||
	if newService.Spec.Type != c.Services[role].Spec.Type {
 | 
					 | 
				
			||||||
		// service type has changed, need to replace the service completely.
 | 
					 | 
				
			||||||
		// we cannot use just patch the current service, since it may contain attributes incompatible with the new type.
 | 
					 | 
				
			||||||
		var (
 | 
					 | 
				
			||||||
			currentEndpoint *v1.Endpoints
 | 
					 | 
				
			||||||
			err             error
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if role == Master {
 | 
					 | 
				
			||||||
			// for the master service we need to re-create the endpoint as well. Get the up-to-date version of
 | 
					 | 
				
			||||||
			// the addresses stored in it before the service is deleted (deletion of the service removes the endpoint)
 | 
					 | 
				
			||||||
			currentEndpoint, err = c.KubeClient.Endpoints(c.Namespace).Get(c.endpointName(role), metav1.GetOptions{})
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return fmt.Errorf("could not get current cluster %s endpoints: %v", role, err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		err = c.KubeClient.Services(serviceName.Namespace).Delete(serviceName.Name, c.deleteOptions)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return fmt.Errorf("could not delete service %q: %v", serviceName, err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// wait until the service is truly deleted
 | 
					 | 
				
			||||||
		c.logger.Debugf("waiting for service to be deleted")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		err = retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout,
 | 
					 | 
				
			||||||
			func() (bool, error) {
 | 
					 | 
				
			||||||
				_, err2 := c.KubeClient.Services(serviceName.Namespace).Get(serviceName.Name, metav1.GetOptions{})
 | 
					 | 
				
			||||||
				if err2 == nil {
 | 
					 | 
				
			||||||
					return false, nil
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				if k8sutil.ResourceNotFound(err2) {
 | 
					 | 
				
			||||||
					return true, nil
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				return false, err2
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return fmt.Errorf("could not delete service %q: %v", serviceName, err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// make sure we clear the stored service and endpoint status if the subsequent create fails.
 | 
					 | 
				
			||||||
		c.Services[role] = nil
 | 
					 | 
				
			||||||
		c.Endpoints[role] = nil
 | 
					 | 
				
			||||||
		if role == Master {
 | 
					 | 
				
			||||||
			// create the new endpoint using the addresses obtained from the previous one
 | 
					 | 
				
			||||||
			endpointSpec := c.generateEndpoint(role, currentEndpoint.Subsets)
 | 
					 | 
				
			||||||
			ep, err := c.KubeClient.Endpoints(endpointSpec.Namespace).Create(endpointSpec)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return fmt.Errorf("could not create endpoint %q: %v", endpointName, err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			c.Endpoints[role] = ep
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		svc, err := c.KubeClient.Services(serviceName.Namespace).Create(newService)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return fmt.Errorf("could not create service %q: %v", serviceName, err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		c.Services[role] = svc
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// update the service annotation in order to propagate ELB notation.
 | 
						// update the service annotation in order to propagate ELB notation.
 | 
				
			||||||
	if len(newService.ObjectMeta.Annotations) > 0 {
 | 
						if len(newService.ObjectMeta.Annotations) > 0 {
 | 
				
			||||||
| 
						 | 
					@ -454,19 +395,31 @@ func (c *Cluster) updateService(role PostgresRole, newService *v1.Service) error
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// now, patch the service spec, but when disabling LoadBalancers do update instead
 | 
				
			||||||
 | 
						// patch does not work because of LoadBalancerSourceRanges field (even if set to nil)
 | 
				
			||||||
 | 
						oldServiceType := c.Services[role].Spec.Type
 | 
				
			||||||
 | 
						newServiceType := newService.Spec.Type
 | 
				
			||||||
 | 
						if newServiceType == "ClusterIP" && newServiceType != oldServiceType {
 | 
				
			||||||
 | 
							newService.ResourceVersion = c.Services[role].ResourceVersion
 | 
				
			||||||
 | 
							newService.Spec.ClusterIP = c.Services[role].Spec.ClusterIP
 | 
				
			||||||
 | 
							svc, err = c.KubeClient.Services(serviceName.Namespace).Update(newService)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("could not update service %q: %v", serviceName, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
		patchData, err := specPatch(newService.Spec)
 | 
							patchData, err := specPatch(newService.Spec)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return fmt.Errorf("could not form patch for the service %q: %v", serviceName, err)
 | 
								return fmt.Errorf("could not form patch for the service %q: %v", serviceName, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// update the service spec
 | 
							svc, err = c.KubeClient.Services(serviceName.Namespace).Patch(
 | 
				
			||||||
	svc, err := c.KubeClient.Services(serviceName.Namespace).Patch(
 | 
					 | 
				
			||||||
			serviceName.Name,
 | 
								serviceName.Name,
 | 
				
			||||||
			types.MergePatchType,
 | 
								types.MergePatchType,
 | 
				
			||||||
			patchData, "")
 | 
								patchData, "")
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return fmt.Errorf("could not patch service %q: %v", serviceName, err)
 | 
								return fmt.Errorf("could not patch service %q: %v", serviceName, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	c.Services[role] = svc
 | 
						c.Services[role] = svc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,6 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
 | 
				
			||||||
	c.mu.Lock()
 | 
						c.mu.Lock()
 | 
				
			||||||
	defer c.mu.Unlock()
 | 
						defer c.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	oldStatus := c.Status
 | 
					 | 
				
			||||||
	c.setSpec(newSpec)
 | 
						c.setSpec(newSpec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	defer func() {
 | 
						defer func() {
 | 
				
			||||||
| 
						 | 
					@ -36,16 +35,6 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = c.validateResources(&c.Spec); err != nil {
 | 
					 | 
				
			||||||
		err = fmt.Errorf("insufficient resource limits specified: %v", err)
 | 
					 | 
				
			||||||
		if oldStatus.Running() {
 | 
					 | 
				
			||||||
			c.logger.Warning(err)
 | 
					 | 
				
			||||||
			err = nil
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = c.initUsers(); err != nil {
 | 
						if err = c.initUsers(); err != nil {
 | 
				
			||||||
		err = fmt.Errorf("could not init users: %v", err)
 | 
							err = fmt.Errorf("could not init users: %v", err)
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
| 
						 | 
					@ -77,6 +66,11 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = c.enforceMinResourceLimits(&c.Spec); err != nil {
 | 
				
			||||||
 | 
							err = fmt.Errorf("could not enforce minimum resource limits: %v", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.logger.Debugf("syncing statefulsets")
 | 
						c.logger.Debugf("syncing statefulsets")
 | 
				
			||||||
	if err = c.syncStatefulSet(); err != nil {
 | 
						if err = c.syncStatefulSet(); err != nil {
 | 
				
			||||||
		if !k8sutil.ResourceAlreadyExists(err) {
 | 
							if !k8sutil.ResourceAlreadyExists(err) {
 | 
				
			||||||
| 
						 | 
					@ -128,7 +122,7 @@ func (c *Cluster) syncServices() error {
 | 
				
			||||||
		c.logger.Debugf("syncing %s service", role)
 | 
							c.logger.Debugf("syncing %s service", role)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := c.syncEndpoint(role); err != nil {
 | 
							if err := c.syncEndpoint(role); err != nil {
 | 
				
			||||||
			return fmt.Errorf("could not sync %s endpont: %v", role, err)
 | 
								return fmt.Errorf("could not sync %s endpoint: %v", role, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := c.syncService(role); err != nil {
 | 
							if err := c.syncService(role); err != nil {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,7 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
	v1 "k8s.io/api/core/v1"
 | 
						v1 "k8s.io/api/core/v1"
 | 
				
			||||||
	rbacv1beta1 "k8s.io/api/rbac/v1beta1"
 | 
						rbacv1 "k8s.io/api/rbac/v1"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/types"
 | 
						"k8s.io/apimachinery/pkg/types"
 | 
				
			||||||
	"k8s.io/client-go/kubernetes/scheme"
 | 
						"k8s.io/client-go/kubernetes/scheme"
 | 
				
			||||||
| 
						 | 
					@ -57,7 +57,7 @@ type Controller struct {
 | 
				
			||||||
	workerLogs map[uint32]ringlog.RingLogger
 | 
						workerLogs map[uint32]ringlog.RingLogger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	PodServiceAccount            *v1.ServiceAccount
 | 
						PodServiceAccount            *v1.ServiceAccount
 | 
				
			||||||
	PodServiceAccountRoleBinding *rbacv1beta1.RoleBinding
 | 
						PodServiceAccountRoleBinding *rbacv1.RoleBinding
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewController creates a new controller
 | 
					// NewController creates a new controller
 | 
				
			||||||
| 
						 | 
					@ -161,10 +161,11 @@ func (c *Controller) initPodServiceAccount() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if c.opConfig.PodServiceAccountDefinition == "" {
 | 
						if c.opConfig.PodServiceAccountDefinition == "" {
 | 
				
			||||||
		c.opConfig.PodServiceAccountDefinition = `
 | 
							c.opConfig.PodServiceAccountDefinition = `
 | 
				
			||||||
		{ "apiVersion": "v1",
 | 
							{
 | 
				
			||||||
 | 
								"apiVersion": "v1",
 | 
				
			||||||
			"kind": "ServiceAccount",
 | 
								"kind": "ServiceAccount",
 | 
				
			||||||
			"metadata": {
 | 
								"metadata": {
 | 
				
			||||||
				 "name": "operator"
 | 
									"name": "postgres-pod"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}`
 | 
							}`
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -175,13 +176,13 @@ func (c *Controller) initPodServiceAccount() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch {
 | 
						switch {
 | 
				
			||||||
	case err != nil:
 | 
						case err != nil:
 | 
				
			||||||
		panic(fmt.Errorf("Unable to parse pod service account definition from the operator config map: %v", err))
 | 
							panic(fmt.Errorf("Unable to parse pod service account definition from the operator configuration: %v", err))
 | 
				
			||||||
	case groupVersionKind.Kind != "ServiceAccount":
 | 
						case groupVersionKind.Kind != "ServiceAccount":
 | 
				
			||||||
		panic(fmt.Errorf("pod service account definition in the operator config map defines another type of resource: %v", groupVersionKind.Kind))
 | 
							panic(fmt.Errorf("pod service account definition in the operator configuration defines another type of resource: %v", groupVersionKind.Kind))
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		c.PodServiceAccount = obj.(*v1.ServiceAccount)
 | 
							c.PodServiceAccount = obj.(*v1.ServiceAccount)
 | 
				
			||||||
		if c.PodServiceAccount.Name != c.opConfig.PodServiceAccountName {
 | 
							if c.PodServiceAccount.Name != c.opConfig.PodServiceAccountName {
 | 
				
			||||||
			c.logger.Warnf("in the operator config map, the pod service account name %v does not match the name %v given in the account definition; using the former for consistency", c.opConfig.PodServiceAccountName, c.PodServiceAccount.Name)
 | 
								c.logger.Warnf("in the operator configuration, the pod service account name %v does not match the name %v given in the account definition; using the former for consistency", c.opConfig.PodServiceAccountName, c.PodServiceAccount.Name)
 | 
				
			||||||
			c.PodServiceAccount.Name = c.opConfig.PodServiceAccountName
 | 
								c.PodServiceAccount.Name = c.opConfig.PodServiceAccountName
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		c.PodServiceAccount.Namespace = ""
 | 
							c.PodServiceAccount.Namespace = ""
 | 
				
			||||||
| 
						 | 
					@ -198,7 +199,7 @@ func (c *Controller) initRoleBinding() {
 | 
				
			||||||
	if c.opConfig.PodServiceAccountRoleBindingDefinition == "" {
 | 
						if c.opConfig.PodServiceAccountRoleBindingDefinition == "" {
 | 
				
			||||||
		c.opConfig.PodServiceAccountRoleBindingDefinition = fmt.Sprintf(`
 | 
							c.opConfig.PodServiceAccountRoleBindingDefinition = fmt.Sprintf(`
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			"apiVersion": "rbac.authorization.k8s.io/v1beta1",
 | 
								"apiVersion": "rbac.authorization.k8s.io/v1",
 | 
				
			||||||
			"kind": "RoleBinding",
 | 
								"kind": "RoleBinding",
 | 
				
			||||||
			"metadata": {
 | 
								"metadata": {
 | 
				
			||||||
				   "name": "%s"
 | 
									   "name": "%s"
 | 
				
			||||||
| 
						 | 
					@ -223,11 +224,11 @@ func (c *Controller) initRoleBinding() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch {
 | 
						switch {
 | 
				
			||||||
	case err != nil:
 | 
						case err != nil:
 | 
				
			||||||
		panic(fmt.Errorf("Unable to parse the definition of the role binding for the pod service account definition from the operator config map: %v", err))
 | 
							panic(fmt.Errorf("unable to parse the definition of the role binding for the pod service account definition from the operator configuration: %v", err))
 | 
				
			||||||
	case groupVersionKind.Kind != "RoleBinding":
 | 
						case groupVersionKind.Kind != "RoleBinding":
 | 
				
			||||||
		panic(fmt.Errorf("role binding definition in the operator config map defines another type of resource: %v", groupVersionKind.Kind))
 | 
							panic(fmt.Errorf("role binding definition in the operator configuration defines another type of resource: %v", groupVersionKind.Kind))
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		c.PodServiceAccountRoleBinding = obj.(*rbacv1beta1.RoleBinding)
 | 
							c.PodServiceAccountRoleBinding = obj.(*rbacv1.RoleBinding)
 | 
				
			||||||
		c.PodServiceAccountRoleBinding.Namespace = ""
 | 
							c.PodServiceAccountRoleBinding.Namespace = ""
 | 
				
			||||||
		c.logger.Info("successfully parsed")
 | 
							c.logger.Info("successfully parsed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -66,7 +66,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
 | 
				
			||||||
	result.NodeReadinessLabel = fromCRD.Kubernetes.NodeReadinessLabel
 | 
						result.NodeReadinessLabel = fromCRD.Kubernetes.NodeReadinessLabel
 | 
				
			||||||
	result.PodPriorityClassName = fromCRD.Kubernetes.PodPriorityClassName
 | 
						result.PodPriorityClassName = fromCRD.Kubernetes.PodPriorityClassName
 | 
				
			||||||
	result.PodManagementPolicy = fromCRD.Kubernetes.PodManagementPolicy
 | 
						result.PodManagementPolicy = fromCRD.Kubernetes.PodManagementPolicy
 | 
				
			||||||
	result.MasterPodMoveTimeout = fromCRD.Kubernetes.MasterPodMoveTimeout
 | 
						result.MasterPodMoveTimeout = time.Duration(fromCRD.Kubernetes.MasterPodMoveTimeout)
 | 
				
			||||||
	result.EnablePodAntiAffinity = fromCRD.Kubernetes.EnablePodAntiAffinity
 | 
						result.EnablePodAntiAffinity = fromCRD.Kubernetes.EnablePodAntiAffinity
 | 
				
			||||||
	result.PodAntiAffinityTopologyKey = fromCRD.Kubernetes.PodAntiAffinityTopologyKey
 | 
						result.PodAntiAffinityTopologyKey = fromCRD.Kubernetes.PodAntiAffinityTopologyKey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -75,6 +75,8 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
 | 
				
			||||||
	result.DefaultMemoryRequest = fromCRD.PostgresPodResources.DefaultMemoryRequest
 | 
						result.DefaultMemoryRequest = fromCRD.PostgresPodResources.DefaultMemoryRequest
 | 
				
			||||||
	result.DefaultCPULimit = fromCRD.PostgresPodResources.DefaultCPULimit
 | 
						result.DefaultCPULimit = fromCRD.PostgresPodResources.DefaultCPULimit
 | 
				
			||||||
	result.DefaultMemoryLimit = fromCRD.PostgresPodResources.DefaultMemoryLimit
 | 
						result.DefaultMemoryLimit = fromCRD.PostgresPodResources.DefaultMemoryLimit
 | 
				
			||||||
 | 
						result.MinCPULimit = fromCRD.PostgresPodResources.MinCPULimit
 | 
				
			||||||
 | 
						result.MinMemoryLimit = fromCRD.PostgresPodResources.MinMemoryLimit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// timeout config
 | 
						// timeout config
 | 
				
			||||||
	result.ResourceCheckInterval = time.Duration(fromCRD.Timeouts.ResourceCheckInterval)
 | 
						result.ResourceCheckInterval = time.Duration(fromCRD.Timeouts.ResourceCheckInterval)
 | 
				
			||||||
| 
						 | 
					@ -104,6 +106,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
 | 
				
			||||||
	result.LogicalBackupSchedule = fromCRD.LogicalBackup.Schedule
 | 
						result.LogicalBackupSchedule = fromCRD.LogicalBackup.Schedule
 | 
				
			||||||
	result.LogicalBackupDockerImage = fromCRD.LogicalBackup.DockerImage
 | 
						result.LogicalBackupDockerImage = fromCRD.LogicalBackup.DockerImage
 | 
				
			||||||
	result.LogicalBackupS3Bucket = fromCRD.LogicalBackup.S3Bucket
 | 
						result.LogicalBackupS3Bucket = fromCRD.LogicalBackup.S3Bucket
 | 
				
			||||||
 | 
						result.LogicalBackupS3Region = fromCRD.LogicalBackup.S3Region
 | 
				
			||||||
	result.LogicalBackupS3Endpoint = fromCRD.LogicalBackup.S3Endpoint
 | 
						result.LogicalBackupS3Endpoint = fromCRD.LogicalBackup.S3Endpoint
 | 
				
			||||||
	result.LogicalBackupS3AccessKeyID = fromCRD.LogicalBackup.S3AccessKeyID
 | 
						result.LogicalBackupS3AccessKeyID = fromCRD.LogicalBackup.S3AccessKeyID
 | 
				
			||||||
	result.LogicalBackupS3SecretAccessKey = fromCRD.LogicalBackup.S3SecretAccessKey
 | 
						result.LogicalBackupS3SecretAccessKey = fromCRD.LogicalBackup.S3SecretAccessKey
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Copyright 2019 Compose, Zalando SE
 | 
					Copyright 2020 Compose, Zalando SE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,8 +37,10 @@ type Resources struct {
 | 
				
			||||||
	PodToleration           map[string]string `name:"toleration" default:""`
 | 
						PodToleration           map[string]string `name:"toleration" default:""`
 | 
				
			||||||
	DefaultCPURequest       string            `name:"default_cpu_request" default:"100m"`
 | 
						DefaultCPURequest       string            `name:"default_cpu_request" default:"100m"`
 | 
				
			||||||
	DefaultMemoryRequest    string            `name:"default_memory_request" default:"100Mi"`
 | 
						DefaultMemoryRequest    string            `name:"default_memory_request" default:"100Mi"`
 | 
				
			||||||
	DefaultCPULimit         string            `name:"default_cpu_limit" default:"3"`
 | 
						DefaultCPULimit         string            `name:"default_cpu_limit" default:"1"`
 | 
				
			||||||
	DefaultMemoryLimit      string            `name:"default_memory_limit" default:"1Gi"`
 | 
						DefaultMemoryLimit      string            `name:"default_memory_limit" default:"500Mi"`
 | 
				
			||||||
 | 
						MinCPULimit             string            `name:"min_cpu_limit" default:"250m"`
 | 
				
			||||||
 | 
						MinMemoryLimit          string            `name:"min_memory_limit" default:"250Mi"`
 | 
				
			||||||
	PodEnvironmentConfigMap string            `name:"pod_environment_configmap" default:""`
 | 
						PodEnvironmentConfigMap string            `name:"pod_environment_configmap" default:""`
 | 
				
			||||||
	NodeReadinessLabel      map[string]string `name:"node_readiness_label" default:""`
 | 
						NodeReadinessLabel      map[string]string `name:"node_readiness_label" default:""`
 | 
				
			||||||
	MaxInstances            int32             `name:"max_instances" default:"-1"`
 | 
						MaxInstances            int32             `name:"max_instances" default:"-1"`
 | 
				
			||||||
| 
						 | 
					@ -66,7 +68,7 @@ type Scalyr struct {
 | 
				
			||||||
	ScalyrCPURequest    string `name:"scalyr_cpu_request" default:"100m"`
 | 
						ScalyrCPURequest    string `name:"scalyr_cpu_request" default:"100m"`
 | 
				
			||||||
	ScalyrMemoryRequest string `name:"scalyr_memory_request" default:"50Mi"`
 | 
						ScalyrMemoryRequest string `name:"scalyr_memory_request" default:"50Mi"`
 | 
				
			||||||
	ScalyrCPULimit      string `name:"scalyr_cpu_limit" default:"1"`
 | 
						ScalyrCPULimit      string `name:"scalyr_cpu_limit" default:"1"`
 | 
				
			||||||
	ScalyrMemoryLimit   string `name:"scalyr_memory_limit" default:"1Gi"`
 | 
						ScalyrMemoryLimit   string `name:"scalyr_memory_limit" default:"500Mi"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// LogicalBackup defines configuration for logical backup
 | 
					// LogicalBackup defines configuration for logical backup
 | 
				
			||||||
| 
						 | 
					@ -74,6 +76,7 @@ type LogicalBackup struct {
 | 
				
			||||||
	LogicalBackupSchedule          string `name:"logical_backup_schedule" default:"30 00 * * *"`
 | 
						LogicalBackupSchedule          string `name:"logical_backup_schedule" default:"30 00 * * *"`
 | 
				
			||||||
	LogicalBackupDockerImage       string `name:"logical_backup_docker_image" default:"registry.opensource.zalan.do/acid/logical-backup"`
 | 
						LogicalBackupDockerImage       string `name:"logical_backup_docker_image" default:"registry.opensource.zalan.do/acid/logical-backup"`
 | 
				
			||||||
	LogicalBackupS3Bucket          string `name:"logical_backup_s3_bucket" default:""`
 | 
						LogicalBackupS3Bucket          string `name:"logical_backup_s3_bucket" default:""`
 | 
				
			||||||
 | 
						LogicalBackupS3Region          string `name:"logical_backup_s3_region" default:""`
 | 
				
			||||||
	LogicalBackupS3Endpoint        string `name:"logical_backup_s3_endpoint" default:""`
 | 
						LogicalBackupS3Endpoint        string `name:"logical_backup_s3_endpoint" default:""`
 | 
				
			||||||
	LogicalBackupS3AccessKeyID     string `name:"logical_backup_s3_access_key_id" default:""`
 | 
						LogicalBackupS3AccessKeyID     string `name:"logical_backup_s3_access_key_id" default:""`
 | 
				
			||||||
	LogicalBackupS3SecretAccessKey string `name:"logical_backup_s3_secret_access_key" default:""`
 | 
						LogicalBackupS3SecretAccessKey string `name:"logical_backup_s3_secret_access_key" default:""`
 | 
				
			||||||
| 
						 | 
					@ -93,7 +96,7 @@ type Config struct {
 | 
				
			||||||
	DockerImage      string            `name:"docker_image" default:"registry.opensource.zalan.do/acid/spilo-cdp-12:1.6-p16"`
 | 
						DockerImage      string            `name:"docker_image" default:"registry.opensource.zalan.do/acid/spilo-cdp-12:1.6-p16"`
 | 
				
			||||||
	Sidecars         map[string]string `name:"sidecar_docker_images"`
 | 
						Sidecars         map[string]string `name:"sidecar_docker_images"`
 | 
				
			||||||
	// default name `operator` enables backward compatibility with the older ServiceAccountName field
 | 
						// default name `operator` enables backward compatibility with the older ServiceAccountName field
 | 
				
			||||||
	PodServiceAccountName string `name:"pod_service_account_name" default:"operator"`
 | 
						PodServiceAccountName string `name:"pod_service_account_name" default:"postgres-pod"`
 | 
				
			||||||
	// value of this string must be valid JSON or YAML; see initPodServiceAccount
 | 
						// value of this string must be valid JSON or YAML; see initPodServiceAccount
 | 
				
			||||||
	PodServiceAccountDefinition            string            `name:"pod_service_account_definition" default:""`
 | 
						PodServiceAccountDefinition            string            `name:"pod_service_account_definition" default:""`
 | 
				
			||||||
	PodServiceAccountRoleBindingDefinition string            `name:"pod_service_account_role_binding_definition" default:""`
 | 
						PodServiceAccountRoleBindingDefinition string            `name:"pod_service_account_role_binding_definition" default:""`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,6 @@ import (
 | 
				
			||||||
	batchv1beta1 "k8s.io/api/batch/v1beta1"
 | 
						batchv1beta1 "k8s.io/api/batch/v1beta1"
 | 
				
			||||||
	clientbatchv1beta1 "k8s.io/client-go/kubernetes/typed/batch/v1beta1"
 | 
						clientbatchv1beta1 "k8s.io/client-go/kubernetes/typed/batch/v1beta1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/zalando/postgres-operator/pkg/util/constants"
 | 
					 | 
				
			||||||
	v1 "k8s.io/api/core/v1"
 | 
						v1 "k8s.io/api/core/v1"
 | 
				
			||||||
	policybeta1 "k8s.io/api/policy/v1beta1"
 | 
						policybeta1 "k8s.io/api/policy/v1beta1"
 | 
				
			||||||
	apiextclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 | 
						apiextclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 | 
				
			||||||
| 
						 | 
					@ -19,7 +18,7 @@ import (
 | 
				
			||||||
	appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
 | 
						appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
 | 
				
			||||||
	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
 | 
						corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
 | 
				
			||||||
	policyv1beta1 "k8s.io/client-go/kubernetes/typed/policy/v1beta1"
 | 
						policyv1beta1 "k8s.io/client-go/kubernetes/typed/policy/v1beta1"
 | 
				
			||||||
	rbacv1beta1 "k8s.io/client-go/kubernetes/typed/rbac/v1beta1"
 | 
						rbacv1 "k8s.io/client-go/kubernetes/typed/rbac/v1"
 | 
				
			||||||
	"k8s.io/client-go/rest"
 | 
						"k8s.io/client-go/rest"
 | 
				
			||||||
	"k8s.io/client-go/tools/clientcmd"
 | 
						"k8s.io/client-go/tools/clientcmd"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,7 +39,7 @@ type KubernetesClient struct {
 | 
				
			||||||
	corev1.NamespacesGetter
 | 
						corev1.NamespacesGetter
 | 
				
			||||||
	corev1.ServiceAccountsGetter
 | 
						corev1.ServiceAccountsGetter
 | 
				
			||||||
	appsv1.StatefulSetsGetter
 | 
						appsv1.StatefulSetsGetter
 | 
				
			||||||
	rbacv1beta1.RoleBindingsGetter
 | 
						rbacv1.RoleBindingsGetter
 | 
				
			||||||
	policyv1beta1.PodDisruptionBudgetsGetter
 | 
						policyv1beta1.PodDisruptionBudgetsGetter
 | 
				
			||||||
	apiextbeta1.CustomResourceDefinitionsGetter
 | 
						apiextbeta1.CustomResourceDefinitionsGetter
 | 
				
			||||||
	clientbatchv1beta1.CronJobsGetter
 | 
						clientbatchv1beta1.CronJobsGetter
 | 
				
			||||||
| 
						 | 
					@ -104,7 +103,7 @@ func NewFromConfig(cfg *rest.Config) (KubernetesClient, error) {
 | 
				
			||||||
	kubeClient.StatefulSetsGetter = client.AppsV1()
 | 
						kubeClient.StatefulSetsGetter = client.AppsV1()
 | 
				
			||||||
	kubeClient.PodDisruptionBudgetsGetter = client.PolicyV1beta1()
 | 
						kubeClient.PodDisruptionBudgetsGetter = client.PolicyV1beta1()
 | 
				
			||||||
	kubeClient.RESTClient = client.CoreV1().RESTClient()
 | 
						kubeClient.RESTClient = client.CoreV1().RESTClient()
 | 
				
			||||||
	kubeClient.RoleBindingsGetter = client.RbacV1beta1()
 | 
						kubeClient.RoleBindingsGetter = client.RbacV1()
 | 
				
			||||||
	kubeClient.CronJobsGetter = client.BatchV1beta1()
 | 
						kubeClient.CronJobsGetter = client.BatchV1beta1()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	apiextClient, err := apiextclient.NewForConfig(cfg)
 | 
						apiextClient, err := apiextclient.NewForConfig(cfg)
 | 
				
			||||||
| 
						 | 
					@ -136,21 +135,37 @@ func SameService(cur, new *v1.Service) (match bool, reason string) {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	oldDNSAnnotation := cur.Annotations[constants.ZalandoDNSNameAnnotation]
 | 
						match = true
 | 
				
			||||||
	newDNSAnnotation := new.Annotations[constants.ZalandoDNSNameAnnotation]
 | 
					 | 
				
			||||||
	oldELBAnnotation := cur.Annotations[constants.ElbTimeoutAnnotationName]
 | 
					 | 
				
			||||||
	newELBAnnotation := new.Annotations[constants.ElbTimeoutAnnotationName]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if oldDNSAnnotation != newDNSAnnotation {
 | 
						reasonPrefix := "new service's annotations doesn't match the current one:"
 | 
				
			||||||
		return false, fmt.Sprintf("new service's %q annotation value %q doesn't match the current one %q",
 | 
						for ann := range cur.Annotations {
 | 
				
			||||||
			constants.ZalandoDNSNameAnnotation, newDNSAnnotation, oldDNSAnnotation)
 | 
							if _, ok := new.Annotations[ann]; !ok {
 | 
				
			||||||
 | 
								match = false
 | 
				
			||||||
 | 
								if len(reason) == 0 {
 | 
				
			||||||
 | 
									reason = reasonPrefix
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								reason += fmt.Sprintf(" Removed '%s'.", ann)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	if oldELBAnnotation != newELBAnnotation {
 | 
					 | 
				
			||||||
		return false, fmt.Sprintf("new service's %q annotation value %q doesn't match the current one %q",
 | 
					 | 
				
			||||||
			constants.ElbTimeoutAnnotationName, oldELBAnnotation, newELBAnnotation)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return true, ""
 | 
						for ann := range new.Annotations {
 | 
				
			||||||
 | 
							v, ok := cur.Annotations[ann]
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								if len(reason) == 0 {
 | 
				
			||||||
 | 
									reason = reasonPrefix
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								reason += fmt.Sprintf(" Added '%s' with value '%s'.", ann, new.Annotations[ann])
 | 
				
			||||||
 | 
								match = false
 | 
				
			||||||
 | 
							} else if v != new.Annotations[ann] {
 | 
				
			||||||
 | 
								if len(reason) == 0 {
 | 
				
			||||||
 | 
									reason = reasonPrefix
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								reason += fmt.Sprintf(" '%s' changed from '%s' to '%s'.", ann, v, new.Annotations[ann])
 | 
				
			||||||
 | 
								match = false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return match, reason
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SamePDB compares the PodDisruptionBudgets
 | 
					// SamePDB compares the PodDisruptionBudgets
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,311 @@
 | 
				
			||||||
 | 
					package k8sutil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/zalando/postgres-operator/pkg/util/constants"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						v1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func newsService(ann map[string]string, svcT v1.ServiceType, lbSr []string) *v1.Service {
 | 
				
			||||||
 | 
						svc := &v1.Service{
 | 
				
			||||||
 | 
							Spec: v1.ServiceSpec{
 | 
				
			||||||
 | 
								Type:                     svcT,
 | 
				
			||||||
 | 
								LoadBalancerSourceRanges: lbSr,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						svc.Annotations = ann
 | 
				
			||||||
 | 
						return svc
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSameService(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							about   string
 | 
				
			||||||
 | 
							current *v1.Service
 | 
				
			||||||
 | 
							new     *v1.Service
 | 
				
			||||||
 | 
							reason  string
 | 
				
			||||||
 | 
							match   bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about: "two equal services",
 | 
				
			||||||
 | 
								current: newsService(
 | 
				
			||||||
 | 
									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"}),
 | 
				
			||||||
 | 
								new: newsService(
 | 
				
			||||||
 | 
									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"}),
 | 
				
			||||||
 | 
								match: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about: "services differ on service type",
 | 
				
			||||||
 | 
								current: newsService(
 | 
				
			||||||
 | 
									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"}),
 | 
				
			||||||
 | 
								new: newsService(
 | 
				
			||||||
 | 
									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"}),
 | 
				
			||||||
 | 
								match:  false,
 | 
				
			||||||
 | 
								reason: `new service's type "LoadBalancer" doesn't match the current one "ClusterIP"`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about: "services differ on lb source ranges",
 | 
				
			||||||
 | 
								current: newsService(
 | 
				
			||||||
 | 
									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"}),
 | 
				
			||||||
 | 
								new: newsService(
 | 
				
			||||||
 | 
									map[string]string{
 | 
				
			||||||
 | 
										constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
 | 
				
			||||||
 | 
										constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{"185.249.56.0/22"}),
 | 
				
			||||||
 | 
								match:  false,
 | 
				
			||||||
 | 
								reason: `new service's LoadBalancerSourceRange doesn't match the current one`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about: "new service doesn't have lb source ranges",
 | 
				
			||||||
 | 
								current: newsService(
 | 
				
			||||||
 | 
									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"}),
 | 
				
			||||||
 | 
								new: newsService(
 | 
				
			||||||
 | 
									map[string]string{
 | 
				
			||||||
 | 
										constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
 | 
				
			||||||
 | 
										constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{}),
 | 
				
			||||||
 | 
								match:  false,
 | 
				
			||||||
 | 
								reason: `new service's LoadBalancerSourceRange doesn't match the current one`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about: "services differ on DNS annotation",
 | 
				
			||||||
 | 
								current: newsService(
 | 
				
			||||||
 | 
									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"}),
 | 
				
			||||||
 | 
								new: newsService(
 | 
				
			||||||
 | 
									map[string]string{
 | 
				
			||||||
 | 
										constants.ZalandoDNSNameAnnotation: "new_clstr.acid.zalan.do",
 | 
				
			||||||
 | 
										constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{"128.141.0.0/16", "137.138.0.0/16"}),
 | 
				
			||||||
 | 
								match:  false,
 | 
				
			||||||
 | 
								reason: `new service's annotations doesn't match the current one: 'external-dns.alpha.kubernetes.io/hostname' changed from 'clstr.acid.zalan.do' to 'new_clstr.acid.zalan.do'.`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about: "services differ on AWS ELB annotation",
 | 
				
			||||||
 | 
								current: newsService(
 | 
				
			||||||
 | 
									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"}),
 | 
				
			||||||
 | 
								new: newsService(
 | 
				
			||||||
 | 
									map[string]string{
 | 
				
			||||||
 | 
										constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
 | 
				
			||||||
 | 
										constants.ElbTimeoutAnnotationName: "1800",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{"128.141.0.0/16", "137.138.0.0/16"}),
 | 
				
			||||||
 | 
								match:  false,
 | 
				
			||||||
 | 
								reason: `new service's annotations doesn't match the current one: 'service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout' changed from '3600' to '1800'.`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about: "service changes existing annotation",
 | 
				
			||||||
 | 
								current: newsService(
 | 
				
			||||||
 | 
									map[string]string{
 | 
				
			||||||
 | 
										constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
 | 
				
			||||||
 | 
										constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
 | 
				
			||||||
 | 
										"foo":                              "bar",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{"128.141.0.0/16", "137.138.0.0/16"}),
 | 
				
			||||||
 | 
								new: newsService(
 | 
				
			||||||
 | 
									map[string]string{
 | 
				
			||||||
 | 
										constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
 | 
				
			||||||
 | 
										constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
 | 
				
			||||||
 | 
										"foo":                              "baz",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{"128.141.0.0/16", "137.138.0.0/16"}),
 | 
				
			||||||
 | 
								match:  false,
 | 
				
			||||||
 | 
								reason: `new service's annotations doesn't match the current one: 'foo' changed from 'bar' to 'baz'.`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about: "service changes multiple existing annotations",
 | 
				
			||||||
 | 
								current: newsService(
 | 
				
			||||||
 | 
									map[string]string{
 | 
				
			||||||
 | 
										constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
 | 
				
			||||||
 | 
										constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
 | 
				
			||||||
 | 
										"foo":                              "bar",
 | 
				
			||||||
 | 
										"bar":                              "foo",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{"128.141.0.0/16", "137.138.0.0/16"}),
 | 
				
			||||||
 | 
								new: newsService(
 | 
				
			||||||
 | 
									map[string]string{
 | 
				
			||||||
 | 
										constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
 | 
				
			||||||
 | 
										constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
 | 
				
			||||||
 | 
										"foo":                              "baz",
 | 
				
			||||||
 | 
										"bar":                              "fooz",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{"128.141.0.0/16", "137.138.0.0/16"}),
 | 
				
			||||||
 | 
								match: false,
 | 
				
			||||||
 | 
								// Test just the prefix to avoid flakiness and map sorting
 | 
				
			||||||
 | 
								reason: `new service's annotations doesn't match the current one:`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about: "service adds a new custom annotation",
 | 
				
			||||||
 | 
								current: newsService(
 | 
				
			||||||
 | 
									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"}),
 | 
				
			||||||
 | 
								new: newsService(
 | 
				
			||||||
 | 
									map[string]string{
 | 
				
			||||||
 | 
										constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
 | 
				
			||||||
 | 
										constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
 | 
				
			||||||
 | 
										"foo":                              "bar",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{"128.141.0.0/16", "137.138.0.0/16"}),
 | 
				
			||||||
 | 
								match:  false,
 | 
				
			||||||
 | 
								reason: `new service's annotations doesn't match the current one: Added 'foo' with value 'bar'.`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about: "service removes a custom annotation",
 | 
				
			||||||
 | 
								current: newsService(
 | 
				
			||||||
 | 
									map[string]string{
 | 
				
			||||||
 | 
										constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
 | 
				
			||||||
 | 
										constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
 | 
				
			||||||
 | 
										"foo":                              "bar",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{"128.141.0.0/16", "137.138.0.0/16"}),
 | 
				
			||||||
 | 
								new: newsService(
 | 
				
			||||||
 | 
									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"}),
 | 
				
			||||||
 | 
								match:  false,
 | 
				
			||||||
 | 
								reason: `new service's annotations doesn't match the current one: Removed 'foo'.`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about: "service removes a custom annotation and adds a new one",
 | 
				
			||||||
 | 
								current: newsService(
 | 
				
			||||||
 | 
									map[string]string{
 | 
				
			||||||
 | 
										constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
 | 
				
			||||||
 | 
										constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
 | 
				
			||||||
 | 
										"foo":                              "bar",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{"128.141.0.0/16", "137.138.0.0/16"}),
 | 
				
			||||||
 | 
								new: newsService(
 | 
				
			||||||
 | 
									map[string]string{
 | 
				
			||||||
 | 
										constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
 | 
				
			||||||
 | 
										constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
 | 
				
			||||||
 | 
										"bar":                              "foo",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{"128.141.0.0/16", "137.138.0.0/16"}),
 | 
				
			||||||
 | 
								match:  false,
 | 
				
			||||||
 | 
								reason: `new service's annotations doesn't match the current one: Removed 'foo'. Added 'bar' with value 'foo'.`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about: "service removes a custom annotation, adds a new one and change another",
 | 
				
			||||||
 | 
								current: newsService(
 | 
				
			||||||
 | 
									map[string]string{
 | 
				
			||||||
 | 
										constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
 | 
				
			||||||
 | 
										constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
 | 
				
			||||||
 | 
										"foo":                              "bar",
 | 
				
			||||||
 | 
										"zalan":                            "do",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{"128.141.0.0/16", "137.138.0.0/16"}),
 | 
				
			||||||
 | 
								new: newsService(
 | 
				
			||||||
 | 
									map[string]string{
 | 
				
			||||||
 | 
										constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do",
 | 
				
			||||||
 | 
										constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue,
 | 
				
			||||||
 | 
										"bar":                              "foo",
 | 
				
			||||||
 | 
										"zalan":                            "do.com",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{"128.141.0.0/16", "137.138.0.0/16"}),
 | 
				
			||||||
 | 
								match: false,
 | 
				
			||||||
 | 
								// Test just the prefix to avoid flakiness and map sorting
 | 
				
			||||||
 | 
								reason: `new service's annotations doesn't match the current one: Removed 'foo'.`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								about: "service add annotations",
 | 
				
			||||||
 | 
								current: newsService(
 | 
				
			||||||
 | 
									map[string]string{},
 | 
				
			||||||
 | 
									v1.ServiceTypeLoadBalancer,
 | 
				
			||||||
 | 
									[]string{"128.141.0.0/16", "137.138.0.0/16"}),
 | 
				
			||||||
 | 
								new: newsService(
 | 
				
			||||||
 | 
									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"}),
 | 
				
			||||||
 | 
								match: false,
 | 
				
			||||||
 | 
								// Test just the prefix to avoid flakiness and map sorting
 | 
				
			||||||
 | 
								reason: `new service's annotations doesn't match the current one: Added `,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.about, func(t *testing.T) {
 | 
				
			||||||
 | 
								match, reason := SameService(tt.current, tt.new)
 | 
				
			||||||
 | 
								if match && !tt.match {
 | 
				
			||||||
 | 
									t.Errorf("expected services to do not match: '%q' and '%q'", tt.current, tt.new)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !match && tt.match {
 | 
				
			||||||
 | 
									t.Errorf("expected services to be the same: '%q' and '%q'", tt.current, tt.new)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !match && !tt.match {
 | 
				
			||||||
 | 
									if !strings.HasPrefix(reason, tt.reason) {
 | 
				
			||||||
 | 
										t.Errorf("expected reason prefix '%s', found '%s'", tt.reason, reason)
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								ui/Makefile
								
								
								
								
							
							
						
						
									
										20
									
								
								ui/Makefile
								
								
								
								
							| 
						 | 
					@ -1,17 +1,6 @@
 | 
				
			||||||
.PHONY: clean test appjs docker push mock
 | 
					.PHONY: clean test appjs docker push mock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
BINARY ?= postgres-operator-ui
 | 
					IMAGE            ?= registry.opensource.zalan.do/acid/postgres-operator-ui
 | 
				
			||||||
BUILD_FLAGS ?= -v
 | 
					 | 
				
			||||||
CGO_ENABLED ?= 0
 | 
					 | 
				
			||||||
ifeq ($(RACE),1)
 | 
					 | 
				
			||||||
	BUILD_FLAGS += -race -a
 | 
					 | 
				
			||||||
    CGO_ENABLED=1
 | 
					 | 
				
			||||||
endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
LOCAL_BUILD_FLAGS ?= $(BUILD_FLAGS)
 | 
					 | 
				
			||||||
LDFLAGS ?= -X=main.version=$(VERSION)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
IMAGE            ?= registry.opensource.zalan.do/acid/$(BINARY)
 | 
					 | 
				
			||||||
VERSION          ?= $(shell git describe --tags --always --dirty)
 | 
					VERSION          ?= $(shell git describe --tags --always --dirty)
 | 
				
			||||||
TAG              ?= $(VERSION)
 | 
					TAG              ?= $(VERSION)
 | 
				
			||||||
GITHEAD          = $(shell git rev-parse --short HEAD)
 | 
					GITHEAD          = $(shell git rev-parse --short HEAD)
 | 
				
			||||||
| 
						 | 
					@ -32,8 +21,11 @@ appjs:
 | 
				
			||||||
	docker run $(TTYFLAGS) -u $$(id -u) -v $$(pwd):/workdir -w /workdir/app node:10.1.0-alpine npm run build
 | 
						docker run $(TTYFLAGS) -u $$(id -u) -v $$(pwd):/workdir -w /workdir/app node:10.1.0-alpine npm run build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
docker: appjs
 | 
					docker: appjs
 | 
				
			||||||
	docker build --build-arg "VERSION=$(VERSION)" -t "$(IMAGE):$(TAG)" .
 | 
						echo `(env)`
 | 
				
			||||||
	@echo 'Docker image $(IMAGE):$(TAG) can now be used.'
 | 
						echo "Tag ${TAG}"
 | 
				
			||||||
 | 
						echo "Version ${VERSION}"
 | 
				
			||||||
 | 
						echo "git describe $(shell git describe --tags --always --dirty)"
 | 
				
			||||||
 | 
						docker build --rm -t "$(IMAGE):$(TAG)" -f Dockerfile .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
push: docker
 | 
					push: docker
 | 
				
			||||||
	docker push "$(IMAGE):$(TAG)"
 | 
						docker push "$(IMAGE):$(TAG)"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "name": "postgres-operator-ui",
 | 
					  "name": "postgres-operator-ui",
 | 
				
			||||||
  "version": "1.0.0",
 | 
					  "version": "1.3.0",
 | 
				
			||||||
  "description": "PostgreSQL Operator UI",
 | 
					  "description": "PostgreSQL Operator UI",
 | 
				
			||||||
  "main": "src/app.js",
 | 
					  "main": "src/app.js",
 | 
				
			||||||
  "config": {
 | 
					  "config": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -408,7 +408,7 @@ new
 | 
				
			||||||
                          ref='cpuLimit'
 | 
					                          ref='cpuLimit'
 | 
				
			||||||
                          type='number'
 | 
					                          type='number'
 | 
				
			||||||
                          placeholder='{ cpu.state.limit.initialValue }'
 | 
					                          placeholder='{ cpu.state.limit.initialValue }'
 | 
				
			||||||
                          min='1'
 | 
					                          min='250'
 | 
				
			||||||
                          required
 | 
					                          required
 | 
				
			||||||
                          value='{ cpu.state.limit.state }'
 | 
					                          value='{ cpu.state.limit.state }'
 | 
				
			||||||
                          onchange='{ cpu.state.limit.edit }'
 | 
					                          onchange='{ cpu.state.limit.edit }'
 | 
				
			||||||
| 
						 | 
					@ -434,7 +434,7 @@ new
 | 
				
			||||||
                          onkeyup='{ memory.state.request.edit }'
 | 
					                          onkeyup='{ memory.state.request.edit }'
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                        .input-group-addon
 | 
					                        .input-group-addon
 | 
				
			||||||
                          .input-units Gi
 | 
					                          .input-units Mi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                      .input-group
 | 
					                      .input-group
 | 
				
			||||||
                        .input-group-addon.resource-type Limit
 | 
					                        .input-group-addon.resource-type Limit
 | 
				
			||||||
| 
						 | 
					@ -442,14 +442,14 @@ new
 | 
				
			||||||
                          ref='memoryLimit'
 | 
					                          ref='memoryLimit'
 | 
				
			||||||
                          type='number'
 | 
					                          type='number'
 | 
				
			||||||
                          placeholder='{ memory.state.limit.initialValue }'
 | 
					                          placeholder='{ memory.state.limit.initialValue }'
 | 
				
			||||||
                          min='1'
 | 
					                          min='250'
 | 
				
			||||||
                          required
 | 
					                          required
 | 
				
			||||||
                          value='{ memory.state.limit.state }'
 | 
					                          value='{ memory.state.limit.state }'
 | 
				
			||||||
                          onchange='{ memory.state.limit.edit }'
 | 
					                          onchange='{ memory.state.limit.edit }'
 | 
				
			||||||
                          onkeyup='{ memory.state.limit.edit }'
 | 
					                          onkeyup='{ memory.state.limit.edit }'
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                        .input-group-addon
 | 
					                        .input-group-addon
 | 
				
			||||||
                          .input-units Gi
 | 
					                          .input-units Mi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      .col-lg-3
 | 
					      .col-lg-3
 | 
				
			||||||
        help-general(config='{ opts.config }')
 | 
					        help-general(config='{ opts.config }')
 | 
				
			||||||
| 
						 | 
					@ -519,10 +519,10 @@ new
 | 
				
			||||||
        resources:
 | 
					        resources:
 | 
				
			||||||
          requests:
 | 
					          requests:
 | 
				
			||||||
            cpu: {{ cpu.state.request.state }}m
 | 
					            cpu: {{ cpu.state.request.state }}m
 | 
				
			||||||
            memory: {{ memory.state.request.state }}Gi
 | 
					            memory: {{ memory.state.request.state }}Mi
 | 
				
			||||||
          limits:
 | 
					          limits:
 | 
				
			||||||
            cpu: {{ cpu.state.limit.state }}m
 | 
					            cpu: {{ cpu.state.limit.state }}m
 | 
				
			||||||
            memory: {{ memory.state.limit.state }}Gi{{#if restoring}}
 | 
					            memory: {{ memory.state.limit.state }}Mi{{#if restoring}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        clone:
 | 
					        clone:
 | 
				
			||||||
          cluster: "{{ backup.state.name.state }}"
 | 
					          cluster: "{{ backup.state.name.state }}"
 | 
				
			||||||
| 
						 | 
					@ -786,8 +786,8 @@ new
 | 
				
			||||||
      return instance
 | 
					      return instance
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.cpu = DynamicResource({ request: 100, limit: 1000 })
 | 
					    this.cpu = DynamicResource({ request: 100, limit: 500 })
 | 
				
			||||||
    this.memory = DynamicResource({ request: 1, limit: 1 })
 | 
					    this.memory = DynamicResource({ request: 100, limit: 500 })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.backup = DynamicSet({
 | 
					    this.backup = DynamicSet({
 | 
				
			||||||
      type: () => 'empty',
 | 
					      type: () => 'empty',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -76,6 +76,9 @@ postgresql
 | 
				
			||||||
        .alert.alert-danger(if='{ progress.requestStatus !== "OK" }') Create request failed
 | 
					        .alert.alert-danger(if='{ progress.requestStatus !== "OK" }') Create request failed
 | 
				
			||||||
        .alert.alert-success(if='{ progress.requestStatus === "OK" }') Create request successful ({ new Date(progress.createdTimestamp).toLocaleString() })
 | 
					        .alert.alert-success(if='{ progress.requestStatus === "OK" }') Create request successful ({ new Date(progress.createdTimestamp).toLocaleString() })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .alert.alert-info(if='{ !progress.postgresql }') PostgreSQL cluster manifest pending
 | 
				
			||||||
 | 
					        .alert.alert-success(if='{ progress.postgresql }') PostgreSQL cluster manifest created
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .alert.alert-info(if='{ !progress.statefulSet }') StatefulSet pending
 | 
					        .alert.alert-info(if='{ !progress.statefulSet }') StatefulSet pending
 | 
				
			||||||
        .alert.alert-success(if='{ progress.statefulSet }') StatefulSet created
 | 
					        .alert.alert-success(if='{ progress.statefulSet }') StatefulSet created
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,12 +45,14 @@ postgresqls
 | 
				
			||||||
        thead
 | 
					        thead
 | 
				
			||||||
          tr
 | 
					          tr
 | 
				
			||||||
            th(style='width: 120px') Team
 | 
					            th(style='width: 120px') Team
 | 
				
			||||||
 | 
					            th(style='width: 130px') Namespace
 | 
				
			||||||
 | 
					            th Name
 | 
				
			||||||
            th(style='width: 50px') Pods
 | 
					            th(style='width: 50px') Pods
 | 
				
			||||||
            th(style='width: 140px') CPU
 | 
					            th(style='width: 140px') CPU
 | 
				
			||||||
            th(style='width: 130px') Memory
 | 
					            th(style='width: 130px') Memory
 | 
				
			||||||
            th(style='width: 100px') Size
 | 
					            th(style='width: 100px') Size
 | 
				
			||||||
            th(style='width: 130px') Namespace
 | 
					            th(style='width: 120px') Cost/Month
 | 
				
			||||||
            th Name
 | 
					            th(stlye='width: 120px')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tbody
 | 
					        tbody
 | 
				
			||||||
          tr(
 | 
					          tr(
 | 
				
			||||||
| 
						 | 
					@ -58,19 +60,21 @@ postgresqls
 | 
				
			||||||
            hidden='{ !namespaced_name.toLowerCase().includes(filter.state.toLowerCase()) }'
 | 
					            hidden='{ !namespaced_name.toLowerCase().includes(filter.state.toLowerCase()) }'
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
            td { team }
 | 
					            td { team }
 | 
				
			||||||
            td { nodes }
 | 
					 | 
				
			||||||
            td { cpu } / { cpu_limit }
 | 
					 | 
				
			||||||
            td { memory } / { memory_limit }
 | 
					 | 
				
			||||||
            td { volume_size }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            td(style='white-space: pre')
 | 
					            td(style='white-space: pre')
 | 
				
			||||||
              | { namespace }
 | 
					              | { namespace }
 | 
				
			||||||
 | 
					 | 
				
			||||||
            td
 | 
					            td
 | 
				
			||||||
              a(
 | 
					              a(
 | 
				
			||||||
                href='/#/status/{ cluster_path(this) }'
 | 
					                href='/#/status/{ cluster_path(this) }'
 | 
				
			||||||
              )
 | 
					              )
 | 
				
			||||||
                | { name }
 | 
					                | { name }
 | 
				
			||||||
 | 
					            td { nodes }
 | 
				
			||||||
 | 
					            td { cpu } / { cpu_limit }
 | 
				
			||||||
 | 
					            td { memory } / { memory_limit }
 | 
				
			||||||
 | 
					            td { volume_size }
 | 
				
			||||||
 | 
					            td { calcCosts(nodes, cpu, memory, volume_size) }$
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            td
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              .btn-group.pull-right(
 | 
					              .btn-group.pull-right(
 | 
				
			||||||
                aria-label='Cluster { qname } actions'
 | 
					                aria-label='Cluster { qname } actions'
 | 
				
			||||||
| 
						 | 
					@ -124,12 +128,14 @@ postgresqls
 | 
				
			||||||
        thead
 | 
					        thead
 | 
				
			||||||
          tr
 | 
					          tr
 | 
				
			||||||
            th(style='width: 120px') Team
 | 
					            th(style='width: 120px') Team
 | 
				
			||||||
 | 
					            th(style='width: 130px') Namespace
 | 
				
			||||||
 | 
					            th Name
 | 
				
			||||||
            th(style='width: 50px') Pods
 | 
					            th(style='width: 50px') Pods
 | 
				
			||||||
            th(style='width: 140px') CPU
 | 
					            th(style='width: 140px') CPU
 | 
				
			||||||
            th(style='width: 130px') Memory
 | 
					            th(style='width: 130px') Memory
 | 
				
			||||||
            th(style='width: 100px') Size
 | 
					            th(style='width: 100px') Size
 | 
				
			||||||
            th(style='width: 130px') Namespace
 | 
					            th(style='width: 120px') Cost/Month
 | 
				
			||||||
            th Name
 | 
					            th(stlye='width: 120px')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tbody
 | 
					        tbody
 | 
				
			||||||
          tr(
 | 
					          tr(
 | 
				
			||||||
| 
						 | 
					@ -137,20 +143,20 @@ postgresqls
 | 
				
			||||||
            hidden='{ !namespaced_name.toLowerCase().includes(filter.state.toLowerCase()) }'
 | 
					            hidden='{ !namespaced_name.toLowerCase().includes(filter.state.toLowerCase()) }'
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
            td { team }
 | 
					            td { team }
 | 
				
			||||||
            td { nodes }
 | 
					 | 
				
			||||||
            td { cpu } / { cpu_limit }
 | 
					 | 
				
			||||||
            td { memory } / { memory_limit }
 | 
					 | 
				
			||||||
            td { volume_size }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            td(style='white-space: pre')
 | 
					            td(style='white-space: pre')
 | 
				
			||||||
              | { namespace }
 | 
					              | { namespace }
 | 
				
			||||||
 | 
					 | 
				
			||||||
            td
 | 
					            td
 | 
				
			||||||
 | 
					 | 
				
			||||||
              a(
 | 
					              a(
 | 
				
			||||||
                href='/#/status/{ cluster_path(this) }'
 | 
					                href='/#/status/{ cluster_path(this) }'
 | 
				
			||||||
              )
 | 
					              )
 | 
				
			||||||
                | { name }
 | 
					                | { name }
 | 
				
			||||||
 | 
					            td { nodes }
 | 
				
			||||||
 | 
					            td { cpu } / { cpu_limit }
 | 
				
			||||||
 | 
					            td { memory } / { memory_limit }
 | 
				
			||||||
 | 
					            td { volume_size }
 | 
				
			||||||
 | 
					            td { calcCosts(nodes, cpu, memory, volume_size) }$
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            td
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              .btn-group.pull-right(
 | 
					              .btn-group.pull-right(
 | 
				
			||||||
                aria-label='Cluster { qname } actions'
 | 
					                aria-label='Cluster { qname } actions'
 | 
				
			||||||
| 
						 | 
					@ -223,6 +229,45 @@ postgresqls
 | 
				
			||||||
      + '/' + encodeURI(cluster.name)
 | 
					      + '/' + encodeURI(cluster.name)
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const calcCosts = this.calcCosts = (nodes, cpu, memory, disk) => {
 | 
				
			||||||
 | 
					        costs = nodes * (toCores(cpu) * opts.config.cost_core + toMemory(memory) * opts.config.cost_memory + toDisk(disk) * opts.config.cost_ebs)
 | 
				
			||||||
 | 
					        return costs.toFixed(2)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const toDisk = this.toDisk = value => {
 | 
				
			||||||
 | 
					      if(value.endsWith("Gi")) {
 | 
				
			||||||
 | 
					        value = value.substring(0, value.length-2)
 | 
				
			||||||
 | 
					        value = Number(value)
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      return value
 | 
				
			||||||
 | 
					    }    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const toMemory = this.toMemory = value => {
 | 
				
			||||||
 | 
					      if (value.endsWith("Mi")) {
 | 
				
			||||||
 | 
					        value = value.substring(0, value.length-2)
 | 
				
			||||||
 | 
					        value = Number(value) / 1000.
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      else if(value.endsWith("Gi")) {
 | 
				
			||||||
 | 
					        value = value.substring(0, value.length-2)
 | 
				
			||||||
 | 
					        value = Number(value)
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return value
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const toCores = this.toCores = value => {
 | 
				
			||||||
 | 
					      if (value.endsWith("m")) {
 | 
				
			||||||
 | 
					        value = value.substring(0, value.length-1)
 | 
				
			||||||
 | 
					        value = Number(value) / 1000.
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return value
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.on('mount', () =>
 | 
					    this.on('mount', () =>
 | 
				
			||||||
      jQuery
 | 
					      jQuery
 | 
				
			||||||
      .get('/postgresqls')
 | 
					      .get('/postgresqls')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,23 +4,23 @@ metadata:
 | 
				
			||||||
  name: "postgres-operator-ui"
 | 
					  name: "postgres-operator-ui"
 | 
				
			||||||
  namespace: "default"
 | 
					  namespace: "default"
 | 
				
			||||||
  labels:
 | 
					  labels:
 | 
				
			||||||
    application: "postgres-operator-ui"
 | 
					    name: "postgres-operator-ui"
 | 
				
			||||||
    team: "acid"
 | 
					    team: "acid"
 | 
				
			||||||
spec:
 | 
					spec:
 | 
				
			||||||
  replicas: 1
 | 
					  replicas: 1
 | 
				
			||||||
  selector:
 | 
					  selector:
 | 
				
			||||||
    matchLabels:
 | 
					    matchLabels:
 | 
				
			||||||
      application: "postgres-operator-ui"
 | 
					      name: "postgres-operator-ui"
 | 
				
			||||||
  template:
 | 
					  template:
 | 
				
			||||||
    metadata:
 | 
					    metadata:
 | 
				
			||||||
      labels:
 | 
					      labels:
 | 
				
			||||||
        application: "postgres-operator-ui"
 | 
					        name: "postgres-operator-ui"
 | 
				
			||||||
        team: "acid"
 | 
					        team: "acid"
 | 
				
			||||||
    spec:
 | 
					    spec:
 | 
				
			||||||
      serviceAccountName: postgres-operator-ui
 | 
					      serviceAccountName: postgres-operator-ui
 | 
				
			||||||
      containers:
 | 
					      containers:
 | 
				
			||||||
        - name: "service"
 | 
					        - name: "service"
 | 
				
			||||||
          image: registry.opensource.zalan.do/acid/postgres-operator-ui:v1.2.0
 | 
					          image: registry.opensource.zalan.do/acid/postgres-operator-ui:v1.3.0
 | 
				
			||||||
          ports:
 | 
					          ports:
 | 
				
			||||||
            - containerPort: 8081
 | 
					            - containerPort: 8081
 | 
				
			||||||
              protocol: "TCP"
 | 
					              protocol: "TCP"
 | 
				
			||||||
| 
						 | 
					@ -32,8 +32,8 @@ spec:
 | 
				
			||||||
            timeoutSeconds: 1
 | 
					            timeoutSeconds: 1
 | 
				
			||||||
          resources:
 | 
					          resources:
 | 
				
			||||||
            limits:
 | 
					            limits:
 | 
				
			||||||
              cpu: "300m"
 | 
					              cpu: "200m"
 | 
				
			||||||
              memory: "3000Mi"
 | 
					              memory: "200Mi"
 | 
				
			||||||
            requests:
 | 
					            requests:
 | 
				
			||||||
              cpu: "100m"
 | 
					              cpu: "100m"
 | 
				
			||||||
              memory: "100Mi"
 | 
					              memory: "100Mi"
 | 
				
			||||||
| 
						 | 
					@ -41,7 +41,9 @@ spec:
 | 
				
			||||||
            - name: "APP_URL"
 | 
					            - name: "APP_URL"
 | 
				
			||||||
              value: "http://localhost:8081"
 | 
					              value: "http://localhost:8081"
 | 
				
			||||||
            - name: "OPERATOR_API_URL"
 | 
					            - name: "OPERATOR_API_URL"
 | 
				
			||||||
              value: "http://localhost:8080"
 | 
					              value: "http://postgres-operator:8080"
 | 
				
			||||||
 | 
					            - name: "OPERATOR_CLUSTER_NAME_LABEL"
 | 
				
			||||||
 | 
					              value: "cluster-name"
 | 
				
			||||||
            - name: "TARGET_NAMESPACE"
 | 
					            - name: "TARGET_NAMESPACE"
 | 
				
			||||||
              value: "default"
 | 
					              value: "default"
 | 
				
			||||||
            - name: "TEAMS"
 | 
					            - name: "TEAMS"
 | 
				
			||||||
| 
						 | 
					@ -60,9 +62,14 @@ spec:
 | 
				
			||||||
                  "replica_load_balancer_visible": true,
 | 
					                  "replica_load_balancer_visible": true,
 | 
				
			||||||
                  "resources_visible": true,
 | 
					                  "resources_visible": true,
 | 
				
			||||||
                  "users_visible": true,
 | 
					                  "users_visible": true,
 | 
				
			||||||
 | 
					                  "cost_ebs": 0.119,
 | 
				
			||||||
 | 
					                  "cost_core": 0.0575,
 | 
				
			||||||
 | 
					                  "cost_memory": 0.014375,
 | 
				
			||||||
                  "postgresql_versions": [
 | 
					                  "postgresql_versions": [
 | 
				
			||||||
 | 
					                    "12",
 | 
				
			||||||
                    "11",
 | 
					                    "11",
 | 
				
			||||||
                    "10",
 | 
					                    "10",
 | 
				
			||||||
                    "9.6"
 | 
					                    "9.6",
 | 
				
			||||||
 | 
					                    "9.5"
 | 
				
			||||||
                  ]
 | 
					                  ]
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@ metadata:
 | 
				
			||||||
  namespace: default
 | 
					  namespace: default
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
 | 
					apiVersion: rbac.authorization.k8s.io/v1
 | 
				
			||||||
kind: ClusterRole
 | 
					kind: ClusterRole
 | 
				
			||||||
metadata:
 | 
					metadata:
 | 
				
			||||||
  name: postgres-operator-ui
 | 
					  name: postgres-operator-ui
 | 
				
			||||||
| 
						 | 
					@ -61,7 +61,5 @@ roleRef:
 | 
				
			||||||
  name: postgres-operator-ui
 | 
					  name: postgres-operator-ui
 | 
				
			||||||
subjects:
 | 
					subjects:
 | 
				
			||||||
- kind: ServiceAccount
 | 
					- kind: ServiceAccount
 | 
				
			||||||
# note: the cluster role binding needs to be defined
 | 
					 | 
				
			||||||
# for every namespace the operator-ui service account lives in.
 | 
					 | 
				
			||||||
  name: postgres-operator-ui
 | 
					  name: postgres-operator-ui
 | 
				
			||||||
  namespace: default
 | 
					  namespace: default
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -76,6 +76,7 @@ ACCESS_TOKEN_URL = getenv('ACCESS_TOKEN_URL')
 | 
				
			||||||
TOKENINFO_URL = getenv('OAUTH2_TOKEN_INFO_URL')
 | 
					TOKENINFO_URL = getenv('OAUTH2_TOKEN_INFO_URL')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
OPERATOR_API_URL = getenv('OPERATOR_API_URL', 'http://postgres-operator')
 | 
					OPERATOR_API_URL = getenv('OPERATOR_API_URL', 'http://postgres-operator')
 | 
				
			||||||
 | 
					OPERATOR_CLUSTER_NAME_LABEL = getenv('OPERATOR_CLUSTER_NAME_LABEL', 'cluster-name')
 | 
				
			||||||
OPERATOR_UI_CONFIG = getenv('OPERATOR_UI_CONFIG', '{}')
 | 
					OPERATOR_UI_CONFIG = getenv('OPERATOR_UI_CONFIG', '{}')
 | 
				
			||||||
OPERATOR_UI_MAINTENANCE_CHECK = getenv('OPERATOR_UI_MAINTENANCE_CHECK', '{}')
 | 
					OPERATOR_UI_MAINTENANCE_CHECK = getenv('OPERATOR_UI_MAINTENANCE_CHECK', '{}')
 | 
				
			||||||
READ_ONLY_MODE = getenv('READ_ONLY_MODE', False) in [True, 'true']
 | 
					READ_ONLY_MODE = getenv('READ_ONLY_MODE', False) in [True, 'true']
 | 
				
			||||||
| 
						 | 
					@ -84,6 +85,13 @@ SUPERUSER_TEAM = getenv('SUPERUSER_TEAM', 'acid')
 | 
				
			||||||
TARGET_NAMESPACE = getenv('TARGET_NAMESPACE')
 | 
					TARGET_NAMESPACE = getenv('TARGET_NAMESPACE')
 | 
				
			||||||
GOOGLE_ANALYTICS = getenv('GOOGLE_ANALYTICS', False)
 | 
					GOOGLE_ANALYTICS = getenv('GOOGLE_ANALYTICS', False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# storage pricing, i.e. https://aws.amazon.com/ebs/pricing/
 | 
				
			||||||
 | 
					COST_EBS = float(getenv('COST_EBS', 0.119))  # GB per month
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# compute costs, i.e. https://www.ec2instances.info/?region=eu-central-1&selected=m5.2xlarge
 | 
				
			||||||
 | 
					COST_CORE = 30.5 * 24 * float(getenv('COST_CORE', 0.0575))  # Core per hour m5.2xlarge / 8.
 | 
				
			||||||
 | 
					COST_MEMORY = 30.5 * 24 * float(getenv('COST_MEMORY', 0.014375))  # Memory GB m5.2xlarge / 32.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WALE_S3_ENDPOINT = getenv(
 | 
					WALE_S3_ENDPOINT = getenv(
 | 
				
			||||||
    'WALE_S3_ENDPOINT',
 | 
					    'WALE_S3_ENDPOINT',
 | 
				
			||||||
    'https+path://s3-eu-central-1.amazonaws.com:443',
 | 
					    'https+path://s3-eu-central-1.amazonaws.com:443',
 | 
				
			||||||
| 
						 | 
					@ -293,6 +301,9 @@ DEFAULT_UI_CONFIG = {
 | 
				
			||||||
    'dns_format_string': '{0}.{1}.{2}',
 | 
					    'dns_format_string': '{0}.{1}.{2}',
 | 
				
			||||||
    'pgui_link': '',
 | 
					    'pgui_link': '',
 | 
				
			||||||
    'static_network_whitelist': {},
 | 
					    'static_network_whitelist': {},
 | 
				
			||||||
 | 
					    'cost_ebs': COST_EBS,
 | 
				
			||||||
 | 
					    'cost_core': COST_CORE,
 | 
				
			||||||
 | 
					    'cost_memory': COST_MEMORY
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1003,6 +1014,7 @@ def main(port, secret_key, debug, clusters: list):
 | 
				
			||||||
    logger.info(f'App URL: {APP_URL}')
 | 
					    logger.info(f'App URL: {APP_URL}')
 | 
				
			||||||
    logger.info(f'Authorize URL: {AUTHORIZE_URL}')
 | 
					    logger.info(f'Authorize URL: {AUTHORIZE_URL}')
 | 
				
			||||||
    logger.info(f'Operator API URL: {OPERATOR_API_URL}')
 | 
					    logger.info(f'Operator API URL: {OPERATOR_API_URL}')
 | 
				
			||||||
 | 
					    logger.info(f'Operator cluster name label: {OPERATOR_CLUSTER_NAME_LABEL}')
 | 
				
			||||||
    logger.info(f'Readonly mode: {"enabled" if READ_ONLY_MODE else "disabled"}')  # noqa
 | 
					    logger.info(f'Readonly mode: {"enabled" if READ_ONLY_MODE else "disabled"}')  # noqa
 | 
				
			||||||
    logger.info(f'Spilo S3 backup bucket: {SPILO_S3_BACKUP_BUCKET}')
 | 
					    logger.info(f'Spilo S3 backup bucket: {SPILO_S3_BACKUP_BUCKET}')
 | 
				
			||||||
    logger.info(f'Spilo S3 backup prefix: {SPILO_S3_BACKUP_PREFIX}')
 | 
					    logger.info(f'Spilo S3 backup prefix: {SPILO_S3_BACKUP_PREFIX}')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ from datetime import datetime, timezone
 | 
				
			||||||
from furl import furl
 | 
					from furl import furl
 | 
				
			||||||
from json import dumps
 | 
					from json import dumps
 | 
				
			||||||
from logging import getLogger
 | 
					from logging import getLogger
 | 
				
			||||||
from os import environ
 | 
					from os import environ, getenv
 | 
				
			||||||
from requests import Session
 | 
					from requests import Session
 | 
				
			||||||
from urllib.parse import urljoin
 | 
					from urllib.parse import urljoin
 | 
				
			||||||
from uuid import UUID
 | 
					from uuid import UUID
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,8 @@ logger = getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
session = Session()
 | 
					session = Session()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					OPERATOR_CLUSTER_NAME_LABEL = getenv('OPERATOR_CLUSTER_NAME_LABEL', 'cluster-name')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def request(cluster, path, **kwargs):
 | 
					def request(cluster, path, **kwargs):
 | 
				
			||||||
    if 'timeout' not in kwargs:
 | 
					    if 'timeout' not in kwargs:
 | 
				
			||||||
| 
						 | 
					@ -137,7 +139,7 @@ def read_pods(cluster, namespace, spilo_cluster):
 | 
				
			||||||
        cluster=cluster,
 | 
					        cluster=cluster,
 | 
				
			||||||
        resource_type='pods',
 | 
					        resource_type='pods',
 | 
				
			||||||
        namespace=namespace,
 | 
					        namespace=namespace,
 | 
				
			||||||
        label_selector={'version': spilo_cluster},
 | 
					        label_selector={OPERATOR_CLUSTER_NAME_LABEL: spilo_cluster},
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,14 +1,15 @@
 | 
				
			||||||
Flask-OAuthlib==0.9.5
 | 
					Flask-OAuthlib==0.9.5
 | 
				
			||||||
Flask==1.0.2
 | 
					Flask==1.1.1
 | 
				
			||||||
backoff==1.5.0
 | 
					backoff==1.8.1
 | 
				
			||||||
boto3==1.5.14
 | 
					boto3==1.10.4
 | 
				
			||||||
boto==2.48.0
 | 
					boto==2.49.0
 | 
				
			||||||
click==6.7
 | 
					click==6.7
 | 
				
			||||||
furl==1.0.1
 | 
					furl==1.0.2
 | 
				
			||||||
gevent==1.2.2
 | 
					gevent==1.2.2
 | 
				
			||||||
jq==0.1.6
 | 
					jq==0.1.6
 | 
				
			||||||
json_delta>=2.0
 | 
					json_delta>=2.0
 | 
				
			||||||
kubernetes==3.0.0
 | 
					kubernetes==3.0.0
 | 
				
			||||||
requests==2.20.1
 | 
					requests==2.22.0
 | 
				
			||||||
stups-tokens>=1.1.19
 | 
					stups-tokens>=1.1.19
 | 
				
			||||||
wal_e==1.1.0
 | 
					wal_e==1.1.0
 | 
				
			||||||
 | 
					werkzeug==0.16.1
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,10 +19,15 @@ default_operator_ui_config='{
 | 
				
			||||||
  "nat_gateways_visible": false,
 | 
					  "nat_gateways_visible": false,
 | 
				
			||||||
  "resources_visible": true,
 | 
					  "resources_visible": true,
 | 
				
			||||||
  "users_visible": true,
 | 
					  "users_visible": true,
 | 
				
			||||||
 | 
					  "cost_ebs": 0.119,
 | 
				
			||||||
 | 
					  "cost_core": 0.0575,
 | 
				
			||||||
 | 
					  "cost_memory": 0.014375,
 | 
				
			||||||
  "postgresql_versions": [
 | 
					  "postgresql_versions": [
 | 
				
			||||||
 | 
					    "12",
 | 
				
			||||||
    "11",
 | 
					    "11",
 | 
				
			||||||
    "10",
 | 
					    "10",
 | 
				
			||||||
    "9.6"
 | 
					    "9.6",
 | 
				
			||||||
 | 
					    "9.5"
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "static_network_whitelist": {
 | 
					  "static_network_whitelist": {
 | 
				
			||||||
    "localhost": ["172.0.0.1/32"]
 | 
					    "localhost": ["172.0.0.1/32"]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue