mirror of
https://github.com/zalando/postgres-operator.git
synced 2026-10-06 06:55:21 +02:00
merge with master
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do"
|
||||
acidzalando "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do"
|
||||
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
@@ -103,7 +103,936 @@ var OperatorConfigCRDResourceColumns = []apiextv1beta1.CustomResourceColumnDefin
|
||||
},
|
||||
}
|
||||
|
||||
func buildCRD(name, kind, plural, short string, columns []apiextv1beta1.CustomResourceColumnDefinition) *apiextv1beta1.CustomResourceDefinition {
|
||||
var min0 = 0.0
|
||||
var min1 = 1.0
|
||||
var minDisable = -1.0
|
||||
|
||||
// PostgresCRDResourceValidation to check applied manifest parameters
|
||||
var PostgresCRDResourceValidation = apiextv1beta1.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Required: []string{"kind", "apiVersion", "spec"},
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"kind": {
|
||||
Type: "string",
|
||||
Enum: []apiextv1beta1.JSON{
|
||||
{
|
||||
Raw: []byte(`"postgresql"`),
|
||||
},
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
Type: "string",
|
||||
Enum: []apiextv1beta1.JSON{
|
||||
{
|
||||
Raw: []byte(`"acid.zalan.do/v1"`),
|
||||
},
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
Type: "object",
|
||||
Required: []string{"numberOfInstances", "teamId", "postgresql"},
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"allowedSourceRanges": {
|
||||
Type: "array",
|
||||
Nullable: true,
|
||||
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
Pattern: "^(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\/(\\d|[1-2]\\d|3[0-2])$",
|
||||
},
|
||||
},
|
||||
},
|
||||
"clone": {
|
||||
Type: "object",
|
||||
Required: []string{"cluster"},
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"cluster": {
|
||||
Type: "string",
|
||||
},
|
||||
"s3_endpoint": {
|
||||
Type: "string",
|
||||
},
|
||||
"s3_access_key_id": {
|
||||
Type: "string",
|
||||
},
|
||||
"s3_secret_access_key": {
|
||||
Type: "string",
|
||||
},
|
||||
"s3_force_path_style": {
|
||||
Type: "string",
|
||||
},
|
||||
"s3_wal_path": {
|
||||
Type: "string",
|
||||
},
|
||||
"timestamp": {
|
||||
Type: "string",
|
||||
Description: "Date-time format that specifies a timezone as an offset relative to UTC e.g. 1996-12-19T16:39:57-08:00",
|
||||
Pattern: "^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\\.[0-9]+)?(([Zz])|([+-]([01][0-9]|2[0-3]):[0-5][0-9]))$",
|
||||
},
|
||||
"uid": {
|
||||
Type: "string",
|
||||
Format: "uuid",
|
||||
},
|
||||
},
|
||||
},
|
||||
"databases": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
Description: "User names specified here as database owners must be declared in the users key of the spec key",
|
||||
},
|
||||
},
|
||||
},
|
||||
"dockerImage": {
|
||||
Type: "string",
|
||||
},
|
||||
"enableLogicalBackup": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"enableMasterLoadBalancer": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"enableReplicaLoadBalancer": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"enableShmVolume": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"init_containers": {
|
||||
Type: "array",
|
||||
Description: "Deprecated",
|
||||
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Allows: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"initContainers": {
|
||||
Type: "array",
|
||||
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Allows: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"logicalBackupSchedule": {
|
||||
Type: "string",
|
||||
Pattern: "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$",
|
||||
},
|
||||
"maintenanceWindows": {
|
||||
Type: "array",
|
||||
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
Pattern: "^\\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))-((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))\\ *$",
|
||||
},
|
||||
},
|
||||
},
|
||||
"numberOfInstances": {
|
||||
Type: "integer",
|
||||
Minimum: &min0,
|
||||
},
|
||||
"patroni": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"initdb": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"pg_hba": {
|
||||
Type: "array",
|
||||
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"slots": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"ttl": {
|
||||
Type: "integer",
|
||||
},
|
||||
"loop_wait": {
|
||||
Type: "integer",
|
||||
},
|
||||
"retry_timeout": {
|
||||
Type: "integer",
|
||||
},
|
||||
"maximum_lag_on_failover": {
|
||||
Type: "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
"podAnnotations": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"pod_priority_class_name": {
|
||||
Type: "string",
|
||||
Description: "Deprecated",
|
||||
},
|
||||
"podPriorityClassName": {
|
||||
Type: "string",
|
||||
},
|
||||
"postgresql": {
|
||||
Type: "object",
|
||||
Required: []string{"version"},
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"version": {
|
||||
Type: "string",
|
||||
Enum: []apiextv1beta1.JSON{
|
||||
{
|
||||
Raw: []byte(`"9.3"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"9.4"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"9.5"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"9.6"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"10"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"11"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"12"`),
|
||||
},
|
||||
},
|
||||
},
|
||||
"parameters": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"replicaLoadBalancer": {
|
||||
Type: "boolean",
|
||||
Description: "Deprecated",
|
||||
},
|
||||
"resources": {
|
||||
Type: "object",
|
||||
Required: []string{"requests", "limits"},
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"limits": {
|
||||
Type: "object",
|
||||
Required: []string{"cpu", "memory"},
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"cpu": {
|
||||
Type: "string",
|
||||
Description: "Decimal natural followed by m, or decimal natural followed by dot followed by up to three decimal digits (precision used by Kubernetes). Must be greater than 0",
|
||||
Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
|
||||
},
|
||||
"memory": {
|
||||
Type: "string",
|
||||
Description: "Plain integer or fixed-point integer using one of these suffixes: E, P, T, G, M, k (with or without a tailing i). Must be greater than 0",
|
||||
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
|
||||
},
|
||||
},
|
||||
},
|
||||
"requests": {
|
||||
Type: "object",
|
||||
Required: []string{"cpu", "memory"},
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"cpu": {
|
||||
Type: "string",
|
||||
Description: "Decimal natural followed by m, or decimal natural followed by dot followed by up to three decimal digits (precision used by Kubernetes). Must be greater than 0",
|
||||
Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
|
||||
},
|
||||
"memory": {
|
||||
Type: "string",
|
||||
Description: "Plain integer or fixed-point integer using one of these suffixes: E, P, T, G, M, k (with or without a tailing i). Must be greater than 0",
|
||||
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"sidecars": {
|
||||
Type: "array",
|
||||
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Allows: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"spiloFSGroup": {
|
||||
Type: "integer",
|
||||
},
|
||||
"standby": {
|
||||
Type: "object",
|
||||
Required: []string{"s3_wal_path"},
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"s3_wal_path": {
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"teamId": {
|
||||
Type: "string",
|
||||
},
|
||||
"tolerations": {
|
||||
Type: "array",
|
||||
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Required: []string{"key", "operator", "effect"},
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"key": {
|
||||
Type: "string",
|
||||
},
|
||||
"operator": {
|
||||
Type: "string",
|
||||
Enum: []apiextv1beta1.JSON{
|
||||
{
|
||||
Raw: []byte(`"Equal"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"Exists"`),
|
||||
},
|
||||
},
|
||||
},
|
||||
"value": {
|
||||
Type: "string",
|
||||
},
|
||||
"effect": {
|
||||
Type: "string",
|
||||
Enum: []apiextv1beta1.JSON{
|
||||
{
|
||||
Raw: []byte(`"NoExecute"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"NoSchedule"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"PreferNoSchedule"`),
|
||||
},
|
||||
},
|
||||
},
|
||||
"tolerationSeconds": {
|
||||
Type: "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"useLoadBalancer": {
|
||||
Type: "boolean",
|
||||
Description: "Deprecated",
|
||||
},
|
||||
"users": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "array",
|
||||
Description: "Role flags specified here must not contradict each other",
|
||||
Nullable: true,
|
||||
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
Enum: []apiextv1beta1.JSON{
|
||||
{
|
||||
Raw: []byte(`"bypassrls"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"BYPASSRLS"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"nobypassrls"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"NOBYPASSRLS"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"createdb"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"CREATEDB"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"nocreatedb"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"NOCREATEDB"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"createrole"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"CREATEROLE"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"nocreaterole"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"NOCREATEROLE"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"inherit"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"INHERIT"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"noinherit"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"NOINHERIT"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"login"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"LOGIN"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"nologin"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"NOLOGIN"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"replication"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"REPLICATION"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"noreplication"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"NOREPLICATION"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"superuser"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"SUPERUSER"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"nosuperuser"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"NOSUPERUSER"`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"volume": {
|
||||
Type: "object",
|
||||
Required: []string{"size"},
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"size": {
|
||||
Type: "string",
|
||||
Description: "Value must not be zero",
|
||||
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
|
||||
},
|
||||
"storageClass": {
|
||||
Type: "string",
|
||||
},
|
||||
"subPath": {
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// OperatorConfigCRDResourceValidation to check applied manifest parameters
|
||||
var OperatorConfigCRDResourceValidation = apiextv1beta1.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "object",
|
||||
Required: []string{"kind", "apiVersion", "configuration"},
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"kind": {
|
||||
Type: "string",
|
||||
Enum: []apiextv1beta1.JSON{
|
||||
{
|
||||
Raw: []byte(`"OperatorConfiguration"`),
|
||||
},
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
Type: "string",
|
||||
Enum: []apiextv1beta1.JSON{
|
||||
{
|
||||
Raw: []byte(`"acid.zalan.do/v1"`),
|
||||
},
|
||||
},
|
||||
},
|
||||
"configuration": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"docker_image": {
|
||||
Type: "string",
|
||||
},
|
||||
"enable_crd_validation": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"enable_shm_volume": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"etcd_host": {
|
||||
Type: "string",
|
||||
},
|
||||
"max_instances": {
|
||||
Type: "integer",
|
||||
Description: "-1 = disabled",
|
||||
Minimum: &minDisable,
|
||||
},
|
||||
"min_instances": {
|
||||
Type: "integer",
|
||||
Description: "-1 = disabled",
|
||||
Minimum: &minDisable,
|
||||
},
|
||||
"resync_period": {
|
||||
Type: "string",
|
||||
},
|
||||
"repair_period": {
|
||||
Type: "string",
|
||||
},
|
||||
"set_memory_request_to_limit": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"sidecar_docker_images": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"workers": {
|
||||
Type: "integer",
|
||||
Minimum: &min1,
|
||||
},
|
||||
"users": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"replication_username": {
|
||||
Type: "string",
|
||||
},
|
||||
"super_username": {
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"kubernetes": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"cluster_domain": {
|
||||
Type: "string",
|
||||
},
|
||||
"cluster_labels": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"cluster_name_label": {
|
||||
Type: "string",
|
||||
},
|
||||
"custom_pod_annotations": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"enable_init_containers": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"enable_pod_antiaffinity": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"enable_pod_disruption_budget": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"enable_sidecars": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"infrastructure_roles_secret_name": {
|
||||
Type: "string",
|
||||
},
|
||||
"inherited_labels": {
|
||||
Type: "array",
|
||||
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"master_pod_move_timeout": {
|
||||
Type: "string",
|
||||
},
|
||||
"node_readiness_label": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"oauth_token_secret_name": {
|
||||
Type: "string",
|
||||
},
|
||||
"pdb_name_format": {
|
||||
Type: "string",
|
||||
},
|
||||
"pod_antiaffinity_topology_key": {
|
||||
Type: "string",
|
||||
},
|
||||
"pod_environment_configmap": {
|
||||
Type: "string",
|
||||
},
|
||||
"pod_management_policy": {
|
||||
Type: "string",
|
||||
Enum: []apiextv1beta1.JSON{
|
||||
{
|
||||
Raw: []byte(`"ordered_ready"`),
|
||||
},
|
||||
{
|
||||
Raw: []byte(`"parallel"`),
|
||||
},
|
||||
},
|
||||
},
|
||||
"pod_priority_class_name": {
|
||||
Type: "string",
|
||||
},
|
||||
"pod_role_label": {
|
||||
Type: "string",
|
||||
},
|
||||
"pod_service_account_definition": {
|
||||
Type: "string",
|
||||
},
|
||||
"pod_service_account_name": {
|
||||
Type: "string",
|
||||
},
|
||||
"pod_service_account_role_binding_definition": {
|
||||
Type: "string",
|
||||
},
|
||||
"pod_terminate_grace_period": {
|
||||
Type: "string",
|
||||
},
|
||||
"secret_name_template": {
|
||||
Type: "string",
|
||||
},
|
||||
"spilo_fsgroup": {
|
||||
Type: "integer",
|
||||
},
|
||||
"spilo_privileged": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"toleration": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"watched_namespace": {
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"postgres_pod_resources": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"default_cpu_limit": {
|
||||
Type: "string",
|
||||
Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
|
||||
},
|
||||
"default_cpu_request": {
|
||||
Type: "string",
|
||||
Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
|
||||
},
|
||||
"default_memory_limit": {
|
||||
Type: "string",
|
||||
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
|
||||
},
|
||||
"default_memory_request": {
|
||||
Type: "string",
|
||||
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeouts": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"pod_label_wait_timeout": {
|
||||
Type: "string",
|
||||
},
|
||||
"pod_deletion_wait_timeout": {
|
||||
Type: "string",
|
||||
},
|
||||
"ready_wait_interval": {
|
||||
Type: "string",
|
||||
},
|
||||
"ready_wait_timeout": {
|
||||
Type: "string",
|
||||
},
|
||||
"resource_check_interval": {
|
||||
Type: "string",
|
||||
},
|
||||
"resource_check_timeout": {
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"load_balancer": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"custom_service_annotations": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"db_hosted_zone": {
|
||||
Type: "string",
|
||||
},
|
||||
"enable_master_load_balancer": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"enable_replica_load_balancer": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"master_dns_name_format": {
|
||||
Type: "string",
|
||||
},
|
||||
"replica_dns_name_format": {
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"aws_or_gcp": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"additional_secret_mount": {
|
||||
Type: "string",
|
||||
},
|
||||
"additional_secret_mount_path": {
|
||||
Type: "string",
|
||||
},
|
||||
"aws_region": {
|
||||
Type: "string",
|
||||
},
|
||||
"kube_iam_role": {
|
||||
Type: "string",
|
||||
},
|
||||
"log_s3_bucket": {
|
||||
Type: "string",
|
||||
},
|
||||
"wal_s3_bucket": {
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"logical_backup": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"logical_backup_docker_image": {
|
||||
Type: "string",
|
||||
},
|
||||
"logical_backup_s3_access_key_id": {
|
||||
Type: "string",
|
||||
},
|
||||
"logical_backup_s3_bucket": {
|
||||
Type: "string",
|
||||
},
|
||||
"logical_backup_s3_endpoint": {
|
||||
Type: "string",
|
||||
},
|
||||
"logical_backup_s3_secret_access_key": {
|
||||
Type: "string",
|
||||
},
|
||||
"logical_backup_s3_sse": {
|
||||
Type: "string",
|
||||
},
|
||||
"logical_backup_schedule": {
|
||||
Type: "string",
|
||||
Pattern: "^(\\d+|\\*)(/\\d+)?(\\s+(\\d+|\\*)(/\\d+)?){4}$",
|
||||
},
|
||||
},
|
||||
},
|
||||
"debug": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"debug_logging": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"enable_database_access": {
|
||||
Type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
"teams_api": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"enable_admin_role_for_users": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"enable_team_superuser": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"enable_teams_api": {
|
||||
Type: "boolean",
|
||||
},
|
||||
"pam_configuration": {
|
||||
Type: "string",
|
||||
},
|
||||
"pam_role_name": {
|
||||
Type: "string",
|
||||
},
|
||||
"postgres_superuser_teams": {
|
||||
Type: "array",
|
||||
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"protected_role_names": {
|
||||
Type: "array",
|
||||
Items: &apiextv1beta1.JSONSchemaPropsOrArray{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"team_admin_role": {
|
||||
Type: "string",
|
||||
},
|
||||
"team_api_role_configuration": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"teams_api_url": {
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"logging_rest_api": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"api_port": {
|
||||
Type: "integer",
|
||||
},
|
||||
"cluster_history_entries": {
|
||||
Type: "integer",
|
||||
},
|
||||
"ring_log_lines": {
|
||||
Type: "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
"scalyr": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextv1beta1.JSONSchemaProps{
|
||||
"scalyr_api_key": {
|
||||
Type: "string",
|
||||
},
|
||||
"scalyr_cpu_limit": {
|
||||
Type: "string",
|
||||
Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
|
||||
},
|
||||
"scalyr_cpu_request": {
|
||||
Type: "string",
|
||||
Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
|
||||
},
|
||||
"scalyr_image": {
|
||||
Type: "string",
|
||||
},
|
||||
"scalyr_memory_limit": {
|
||||
Type: "string",
|
||||
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
|
||||
},
|
||||
"scalyr_memory_request": {
|
||||
Type: "string",
|
||||
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
|
||||
},
|
||||
"scalyr_server_url": {
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": {
|
||||
Type: "object",
|
||||
AdditionalProperties: &apiextv1beta1.JSONSchemaPropsOrBool{
|
||||
Schema: &apiextv1beta1.JSONSchemaProps{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func buildCRD(name, kind, plural, short string, columns []apiextv1beta1.CustomResourceColumnDefinition, validation apiextv1beta1.CustomResourceValidation) *apiextv1beta1.CustomResourceDefinition {
|
||||
return &apiextv1beta1.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
@@ -121,24 +1050,39 @@ func buildCRD(name, kind, plural, short string, columns []apiextv1beta1.CustomRe
|
||||
Status: &apiextv1beta1.CustomResourceSubresourceStatus{},
|
||||
},
|
||||
AdditionalPrinterColumns: columns,
|
||||
Validation: &validation,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// PostgresCRD returns CustomResourceDefinition built from PostgresCRDResource
|
||||
func PostgresCRD() *apiextv1beta1.CustomResourceDefinition {
|
||||
func PostgresCRD(enableValidation *bool) *apiextv1beta1.CustomResourceDefinition {
|
||||
postgresCRDvalidation := apiextv1beta1.CustomResourceValidation{}
|
||||
|
||||
if enableValidation != nil && *enableValidation {
|
||||
postgresCRDvalidation = PostgresCRDResourceValidation
|
||||
}
|
||||
|
||||
return buildCRD(PostgresCRDResouceName,
|
||||
PostgresCRDResourceKind,
|
||||
PostgresCRDResourcePlural,
|
||||
PostgresCRDResourceShort,
|
||||
PostgresCRDResourceColumns)
|
||||
PostgresCRDResourceColumns,
|
||||
postgresCRDvalidation)
|
||||
}
|
||||
|
||||
// ConfigurationCRD returns CustomResourceDefinition built from OperatorConfigCRDResource
|
||||
func ConfigurationCRD() *apiextv1beta1.CustomResourceDefinition {
|
||||
func ConfigurationCRD(enableValidation *bool) *apiextv1beta1.CustomResourceDefinition {
|
||||
opconfigCRDvalidation := apiextv1beta1.CustomResourceValidation{}
|
||||
|
||||
if enableValidation != nil && *enableValidation {
|
||||
opconfigCRDvalidation = OperatorConfigCRDResourceValidation
|
||||
}
|
||||
|
||||
return buildCRD(OperatorConfigCRDResourceName,
|
||||
OperatorConfigCRDResouceKind,
|
||||
OperatorConfigCRDResourcePlural,
|
||||
OperatorConfigCRDResourceShort,
|
||||
OperatorConfigCRDResourceColumns)
|
||||
OperatorConfigCRDResourceColumns,
|
||||
opconfigCRDvalidation)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ type KubernetesMetaConfiguration struct {
|
||||
WatchedNamespace string `json:"watched_namespace,omitempty"`
|
||||
PDBNameFormat config.StringTemplate `json:"pdb_name_format,omitempty"`
|
||||
EnablePodDisruptionBudget *bool `json:"enable_pod_disruption_budget,omitempty"`
|
||||
EnableInitContainers *bool `json:"enable_init_containers,omitempty"`
|
||||
EnableSidecars *bool `json:"enable_sidecars,omitempty"`
|
||||
SecretNameTemplate config.StringTemplate `json:"secret_name_template,omitempty"`
|
||||
ClusterDomain string `json:"cluster_domain"`
|
||||
OAuthTokenSecretName spec.NamespacedName `json:"oauth_token_secret_name,omitempty"`
|
||||
@@ -59,6 +61,7 @@ type KubernetesMetaConfiguration struct {
|
||||
InheritedLabels []string `json:"inherited_labels,omitempty"`
|
||||
ClusterNameLabel string `json:"cluster_name_label,omitempty"`
|
||||
NodeReadinessLabel map[string]string `json:"node_readiness_label,omitempty"`
|
||||
CustomPodAnnotations map[string]string `json:"custom_pod_annotations,omitempty"`
|
||||
// TODO: use a proper toleration structure?
|
||||
PodToleration map[string]string `json:"toleration,omitempty"`
|
||||
// TODO: use namespacedname
|
||||
@@ -115,7 +118,7 @@ type OperatorDebugConfiguration struct {
|
||||
EnableDBAccess bool `json:"enable_database_access,omitempty"`
|
||||
}
|
||||
|
||||
// TeamsAPIConfiguration defines the configration of TeamsAPI
|
||||
// TeamsAPIConfiguration defines the configuration of TeamsAPI
|
||||
type TeamsAPIConfiguration struct {
|
||||
EnableTeamsAPI bool `json:"enable_teams_api,omitempty"`
|
||||
TeamsAPIUrl string `json:"teams_api_url,omitempty"`
|
||||
@@ -147,8 +150,20 @@ type ScalyrConfiguration struct {
|
||||
ScalyrMemoryLimit string `json:"scalyr_memory_limit,omitempty"`
|
||||
}
|
||||
|
||||
// OperatorLogicalBackupConfiguration defines configuration for logical backup
|
||||
type OperatorLogicalBackupConfiguration struct {
|
||||
Schedule string `json:"logical_backup_schedule,omitempty"`
|
||||
DockerImage string `json:"logical_backup_docker_image,omitempty"`
|
||||
S3Bucket string `json:"logical_backup_s3_bucket,omitempty"`
|
||||
S3Endpoint string `json:"logical_backup_s3_endpoint,omitempty"`
|
||||
S3AccessKeyID string `json:"logical_backup_s3_access_key_id,omitempty"`
|
||||
S3SecretAccessKey string `json:"logical_backup_s3_secret_access_key,omitempty"`
|
||||
S3SSE string `json:"logical_backup_s3_sse,omitempty"`
|
||||
}
|
||||
|
||||
// OperatorConfigurationData defines the operation config
|
||||
type OperatorConfigurationData struct {
|
||||
EnableCRDValidation *bool `json:"enable_crd_validation,omitempty"`
|
||||
EtcdHost string `json:"etcd_host,omitempty"`
|
||||
DockerImage string `json:"docker_image,omitempty"`
|
||||
Workers uint32 `json:"workers,omitempty"`
|
||||
@@ -172,19 +187,5 @@ type OperatorConfigurationData struct {
|
||||
LogicalBackup OperatorLogicalBackupConfiguration `json:"logical_backup"`
|
||||
}
|
||||
|
||||
// OperatorConfigurationUsers defines configration for super user
|
||||
type OperatorConfigurationUsers struct {
|
||||
SuperUserName string `json:"superuser_name,omitempty"`
|
||||
Replication string `json:"replication_user_name,omitempty"`
|
||||
ProtectedRoles []string `json:"protected_roles,omitempty"`
|
||||
TeamAPIRoleConfiguration map[string]string `json:"team_api_role_configuration,omitempty"`
|
||||
}
|
||||
|
||||
//Duration shortens this frequently used name
|
||||
type Duration time.Duration
|
||||
|
||||
type OperatorLogicalBackupConfiguration struct {
|
||||
Schedule string `json:"logical_backup_schedule,omitempty"`
|
||||
DockerImage string `json:"logical_backup_docker_image,omitempty"`
|
||||
S3Bucket string `json:"logical_backup_s3_bucket,omitempty"`
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ type PostgresSpec struct {
|
||||
EnableLogicalBackup bool `json:"enableLogicalBackup,omitempty"`
|
||||
LogicalBackupSchedule string `json:"logicalBackupSchedule,omitempty"`
|
||||
StandbyCluster *StandbyDescription `json:"standby"`
|
||||
PodAnnotations map[string]string `json:"podAnnotations"`
|
||||
|
||||
// deprecated json tags
|
||||
InitContainersOld []v1.Container `json:"init_containers,omitempty"`
|
||||
|
||||
@@ -180,7 +180,7 @@ var unmarshalCluster = []struct {
|
||||
"name": "acid-testcluster1"
|
||||
},
|
||||
"spec": {
|
||||
"teamId": "ACID",
|
||||
"teamId": "acid",
|
||||
"pod_priority_class_name": "spilo-pod-priority",
|
||||
"volume": {
|
||||
"size": "5Gi",
|
||||
@@ -290,7 +290,7 @@ var unmarshalCluster = []struct {
|
||||
ResourceLimits: ResourceDescription{CPU: "300m", Memory: "3000Mi"},
|
||||
},
|
||||
|
||||
TeamID: "ACID",
|
||||
TeamID: "acid",
|
||||
AllowedSourceRanges: []string{"127.0.0.1/32"},
|
||||
NumberOfInstances: 2,
|
||||
Users: map[string]UserFlags{"zalando": {"superuser", "createdb"}},
|
||||
@@ -319,7 +319,7 @@ var unmarshalCluster = []struct {
|
||||
},
|
||||
Error: "",
|
||||
},
|
||||
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},
|
||||
// example with teamId set in input
|
||||
{
|
||||
@@ -437,6 +437,16 @@ var postgresqlList = []struct {
|
||||
PostgresqlList{},
|
||||
errors.New("unexpected end of JSON input")}}
|
||||
|
||||
var annotations = []struct {
|
||||
in []byte
|
||||
annotations map[string]string
|
||||
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"}}}`),
|
||||
annotations: map[string]string{"foo": "bar"},
|
||||
err: nil},
|
||||
}
|
||||
|
||||
func mustParseTime(s string) metav1.Time {
|
||||
v, err := time.Parse("15:04", s)
|
||||
if err != nil {
|
||||
@@ -482,6 +492,25 @@ func TestWeekdayTime(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestClusterAnnotations(t *testing.T) {
|
||||
for _, tt := range annotations {
|
||||
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 annotations: expected %v got %v", tt.err, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
for k, v := range cluster.Spec.PodAnnotations {
|
||||
found, expected := v, tt.annotations[k]
|
||||
if found != expected {
|
||||
t.Errorf("Didn't find correct value for key %v in for podAnnotations: Expected %v found %v", k, expected, found)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClusterName(t *testing.T) {
|
||||
for _, tt := range clusterNames {
|
||||
name, err := extractClusterName(tt.in, tt.inTeam)
|
||||
|
||||
@@ -81,6 +81,16 @@ func (in *KubernetesMetaConfiguration) DeepCopyInto(out *KubernetesMetaConfigura
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.EnableInitContainers != nil {
|
||||
in, out := &in.EnableInitContainers, &out.EnableInitContainers
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.EnableSidecars != nil {
|
||||
in, out := &in.EnableSidecars, &out.EnableSidecars
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
out.OAuthTokenSecretName = in.OAuthTokenSecretName
|
||||
out.InfrastructureRolesSecretName = in.InfrastructureRolesSecretName
|
||||
if in.ClusterLabels != nil {
|
||||
@@ -102,6 +112,13 @@ func (in *KubernetesMetaConfiguration) DeepCopyInto(out *KubernetesMetaConfigura
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.CustomPodAnnotations != nil {
|
||||
in, out := &in.CustomPodAnnotations, &out.CustomPodAnnotations
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.PodToleration != nil {
|
||||
in, out := &in.PodToleration, &out.PodToleration
|
||||
*out = make(map[string]string, len(*in))
|
||||
@@ -209,6 +226,11 @@ func (in *OperatorConfiguration) DeepCopyObject() runtime.Object {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData) {
|
||||
*out = *in
|
||||
if in.EnableCRDValidation != nil {
|
||||
in, out := &in.EnableCRDValidation, &out.EnableCRDValidation
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.ShmVolume != nil {
|
||||
in, out := &in.ShmVolume, &out.ShmVolume
|
||||
*out = new(bool)
|
||||
@@ -278,34 +300,6 @@ func (in *OperatorConfigurationList) DeepCopyObject() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OperatorConfigurationUsers) DeepCopyInto(out *OperatorConfigurationUsers) {
|
||||
*out = *in
|
||||
if in.ProtectedRoles != nil {
|
||||
in, out := &in.ProtectedRoles, &out.ProtectedRoles
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.TeamAPIRoleConfiguration != nil {
|
||||
in, out := &in.TeamAPIRoleConfiguration, &out.TeamAPIRoleConfiguration
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorConfigurationUsers.
|
||||
func (in *OperatorConfigurationUsers) DeepCopy() *OperatorConfigurationUsers {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OperatorConfigurationUsers)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OperatorDebugConfiguration) DeepCopyInto(out *OperatorDebugConfiguration) {
|
||||
*out = *in
|
||||
@@ -520,6 +514,13 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) {
|
||||
*out = new(StandbyDescription)
|
||||
**out = **in
|
||||
}
|
||||
if in.PodAnnotations != nil {
|
||||
in, out := &in.PodAnnotations, &out.PodAnnotations
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.InitContainersOld != nil {
|
||||
in, out := &in.InitContainersOld, &out.InitContainersOld
|
||||
*out = make([]corev1.Container, len(*in))
|
||||
|
||||
@@ -232,6 +232,10 @@ func (c *Cluster) Create() error {
|
||||
|
||||
c.setStatus(acidv1.ClusterStatusCreating)
|
||||
|
||||
if err = c.validateResources(&c.Spec); err != nil {
|
||||
return fmt.Errorf("insufficient resource limits specified: %v", err)
|
||||
}
|
||||
|
||||
for _, role := range []PostgresRole{Master, Replica} {
|
||||
|
||||
if c.Endpoints[role] != nil {
|
||||
@@ -499,6 +503,44 @@ func compareResourcesAssumeFirstNotNil(a *v1.ResourceRequirements, b *v1.Resourc
|
||||
|
||||
}
|
||||
|
||||
func (c *Cluster) validateResources(spec *acidv1.PostgresSpec) error {
|
||||
|
||||
// setting limits too low can cause unnecessary evictions / OOM kills
|
||||
const (
|
||||
cpuMinLimit = "256m"
|
||||
memoryMinLimit = "256Mi"
|
||||
)
|
||||
|
||||
var (
|
||||
isSmaller bool
|
||||
err error
|
||||
)
|
||||
|
||||
cpuLimit := spec.Resources.ResourceLimits.CPU
|
||||
if cpuLimit != "" {
|
||||
isSmaller, err = util.IsSmallerQuantity(cpuLimit, cpuMinLimit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error validating CPU limit: %v", err)
|
||||
}
|
||||
if isSmaller {
|
||||
return fmt.Errorf("defined CPU limit %s is below required minimum %s to properly run postgresql resource", cpuLimit, cpuMinLimit)
|
||||
}
|
||||
}
|
||||
|
||||
memoryLimit := spec.Resources.ResourceLimits.Memory
|
||||
if memoryLimit != "" {
|
||||
isSmaller, err = util.IsSmallerQuantity(memoryLimit, memoryMinLimit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error validating memory limit: %v", err)
|
||||
}
|
||||
if isSmaller {
|
||||
return fmt.Errorf("defined memory limit %s is below required minimum %s to properly run postgresql resource", memoryLimit, memoryMinLimit)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update changes Kubernetes objects according to the new specification. Unlike the sync case, the missing object
|
||||
// (i.e. service) is treated as an error
|
||||
// logical backup cron jobs are an exception: a user-initiated Update can enable a logical backup job
|
||||
@@ -509,6 +551,7 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
oldStatus := c.Status
|
||||
c.setStatus(acidv1.ClusterStatusUpdating)
|
||||
c.setSpec(newSpec)
|
||||
|
||||
@@ -520,6 +563,22 @@ 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
|
||||
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
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/zalando/postgres-operator/pkg/util/config"
|
||||
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
|
||||
"github.com/zalando/postgres-operator/pkg/util/teams"
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -328,3 +328,57 @@ func TestShouldDeleteSecret(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodAnnotations(t *testing.T) {
|
||||
testName := "TestPodAnnotations"
|
||||
tests := []struct {
|
||||
subTest string
|
||||
operator map[string]string
|
||||
database map[string]string
|
||||
merged map[string]string
|
||||
}{
|
||||
{
|
||||
subTest: "No Annotations",
|
||||
operator: make(map[string]string),
|
||||
database: make(map[string]string),
|
||||
merged: make(map[string]string),
|
||||
},
|
||||
{
|
||||
subTest: "Operator Config Annotations",
|
||||
operator: map[string]string{"foo": "bar"},
|
||||
database: make(map[string]string),
|
||||
merged: map[string]string{"foo": "bar"},
|
||||
},
|
||||
{
|
||||
subTest: "Database Config Annotations",
|
||||
operator: make(map[string]string),
|
||||
database: map[string]string{"foo": "bar"},
|
||||
merged: map[string]string{"foo": "bar"},
|
||||
},
|
||||
{
|
||||
subTest: "Database Config overrides Operator Config Annotations",
|
||||
operator: map[string]string{"foo": "bar", "global": "foo"},
|
||||
database: map[string]string{"foo": "baz", "local": "foo"},
|
||||
merged: map[string]string{"foo": "baz", "global": "foo", "local": "foo"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
cl.OpConfig.CustomPodAnnotations = tt.operator
|
||||
cl.Postgresql.Spec.PodAnnotations = tt.database
|
||||
|
||||
annotations := cl.generatePodAnnotations(&cl.Postgresql.Spec)
|
||||
for k, v := range annotations {
|
||||
if observed, expected := v, tt.merged[k]; observed != expected {
|
||||
t.Errorf("%v expects annotation value %v for key %v, but found %v",
|
||||
testName+"/"+tt.subTest, expected, observed, k)
|
||||
}
|
||||
}
|
||||
for k, v := range tt.merged {
|
||||
if observed, expected := annotations[k], v; observed != expected {
|
||||
t.Errorf("%v expects annotation value %v for key %v, but found %v",
|
||||
testName+"/"+tt.subTest, expected, observed, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+74
-13
@@ -430,6 +430,7 @@ func mountShmVolumeNeeded(opConfig config.Config, pgSpec *acidv1.PostgresSpec) *
|
||||
func generatePodTemplate(
|
||||
namespace string,
|
||||
labels labels.Set,
|
||||
annotations map[string]string,
|
||||
spiloContainer *v1.Container,
|
||||
initContainers []v1.Container,
|
||||
sidecarContainers []v1.Container,
|
||||
@@ -485,13 +486,17 @@ func generatePodTemplate(
|
||||
|
||||
template := v1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: labels,
|
||||
Namespace: namespace,
|
||||
Labels: labels,
|
||||
Namespace: namespace,
|
||||
Annotations: annotations,
|
||||
},
|
||||
Spec: podSpec,
|
||||
}
|
||||
if kubeIAMRole != "" {
|
||||
template.Annotations = map[string]string{constants.KubeIAmAnnotation: kubeIAMRole}
|
||||
if template.Annotations == nil {
|
||||
template.Annotations = make(map[string]string)
|
||||
}
|
||||
template.Annotations[constants.KubeIAmAnnotation] = kubeIAMRole
|
||||
}
|
||||
|
||||
return &template, nil
|
||||
@@ -715,6 +720,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
|
||||
|
||||
var (
|
||||
err error
|
||||
initContainers []v1.Container
|
||||
sidecarContainers []v1.Container
|
||||
podTemplate *v1.PodTemplateSpec
|
||||
volumeClaimTemplate *v1.PersistentVolumeClaim
|
||||
@@ -735,7 +741,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
|
||||
limit = c.OpConfig.DefaultMemoryLimit
|
||||
}
|
||||
|
||||
isSmaller, err := util.RequestIsSmallerThanLimit(request, limit)
|
||||
isSmaller, err := util.IsSmallerQuantity(request, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -762,7 +768,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
|
||||
limit = c.OpConfig.DefaultMemoryLimit
|
||||
}
|
||||
|
||||
isSmaller, err := util.RequestIsSmallerThanLimit(sidecarRequest, sidecarLimit)
|
||||
isSmaller, err := util.IsSmallerQuantity(sidecarRequest, sidecarLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -781,6 +787,13 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
|
||||
return nil, fmt.Errorf("could not generate resource requirements: %v", err)
|
||||
}
|
||||
|
||||
if spec.InitContainers != nil && len(spec.InitContainers) > 0 {
|
||||
if c.OpConfig.EnableInitContainers != nil && !(*c.OpConfig.EnableInitContainers) {
|
||||
c.logger.Warningf("initContainers specified but disabled in configuration - next statefulset creation would fail")
|
||||
}
|
||||
initContainers = spec.InitContainers
|
||||
}
|
||||
|
||||
customPodEnvVarsList := make([]v1.EnvVar, 0)
|
||||
|
||||
if c.OpConfig.PodEnvironmentConfigMap != "" {
|
||||
@@ -867,9 +880,14 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
|
||||
}
|
||||
|
||||
// generate sidecar containers
|
||||
if sidecarContainers, err = generateSidecarContainers(sideCars, volumeMounts, defaultResources,
|
||||
c.OpConfig.SuperUsername, c.credentialSecretName(c.OpConfig.SuperUsername), c.logger); err != nil {
|
||||
return nil, fmt.Errorf("could not generate sidecar containers: %v", err)
|
||||
if sideCars != nil && len(sideCars) > 0 {
|
||||
if c.OpConfig.EnableSidecars != nil && !(*c.OpConfig.EnableSidecars) {
|
||||
c.logger.Warningf("sidecars specified but disabled in configuration - next statefulset creation would fail")
|
||||
}
|
||||
if sidecarContainers, err = generateSidecarContainers(sideCars, volumeMounts, defaultResources,
|
||||
c.OpConfig.SuperUsername, c.credentialSecretName(c.OpConfig.SuperUsername), c.logger); err != nil {
|
||||
return nil, fmt.Errorf("could not generate sidecar containers: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
tolerationSpec := tolerations(&spec.Tolerations, c.OpConfig.PodToleration)
|
||||
@@ -881,12 +899,15 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
|
||||
effectiveFSGroup = spec.SpiloFSGroup
|
||||
}
|
||||
|
||||
annotations := c.generatePodAnnotations(spec)
|
||||
|
||||
// generate pod template for the statefulset, based on the spilo container and sidecars
|
||||
if podTemplate, err = generatePodTemplate(
|
||||
c.Namespace,
|
||||
c.labelsSet(true),
|
||||
annotations,
|
||||
spiloContainer,
|
||||
spec.InitContainers,
|
||||
initContainers,
|
||||
sidecarContainers,
|
||||
&tolerationSpec,
|
||||
effectiveFSGroup,
|
||||
@@ -949,6 +970,24 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
|
||||
return statefulSet, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) generatePodAnnotations(spec *acidv1.PostgresSpec) map[string]string {
|
||||
annotations := make(map[string]string)
|
||||
for k, v := range c.OpConfig.CustomPodAnnotations {
|
||||
annotations[k] = v
|
||||
}
|
||||
if spec != nil || spec.PodAnnotations != nil {
|
||||
for k, v := range spec.PodAnnotations {
|
||||
annotations[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(annotations) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return annotations
|
||||
}
|
||||
|
||||
func generateScalyrSidecarSpec(clusterName, APIKey, serverURL, dockerImage string,
|
||||
containerResources *acidv1.Resources, logger *logrus.Entry) *acidv1.Sidecar {
|
||||
if APIKey == "" || dockerImage == "" {
|
||||
@@ -1393,7 +1432,7 @@ func (c *Cluster) generatePodDisruptionBudget() *policybeta1.PodDisruptionBudget
|
||||
pdbEnabled := c.OpConfig.EnablePodDisruptionBudget
|
||||
|
||||
// if PodDisruptionBudget is disabled or if there are no DB pods, set the budget to 0.
|
||||
if (pdbEnabled != nil && !*pdbEnabled) || c.Spec.NumberOfInstances <= 0 {
|
||||
if (pdbEnabled != nil && !(*pdbEnabled)) || c.Spec.NumberOfInstances <= 0 {
|
||||
minAvailable = intstr.FromInt(0)
|
||||
}
|
||||
|
||||
@@ -1469,10 +1508,13 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1beta1.CronJob, error) {
|
||||
},
|
||||
}}
|
||||
|
||||
annotations := c.generatePodAnnotations(&c.Spec)
|
||||
|
||||
// re-use the method that generates DB pod templates
|
||||
if podTemplate, err = generatePodTemplate(
|
||||
c.Namespace,
|
||||
labels,
|
||||
annotations,
|
||||
logicalBackupContainer,
|
||||
[]v1.Container{},
|
||||
[]v1.Container{},
|
||||
@@ -1486,8 +1528,8 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1beta1.CronJob, error) {
|
||||
util.False(),
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
""); err != nil {
|
||||
c.OpConfig.AdditionalSecretMount,
|
||||
c.OpConfig.AdditionalSecretMountPath); err != nil {
|
||||
return nil, fmt.Errorf("could not generate pod template for logical backup pod: %v", err)
|
||||
}
|
||||
|
||||
@@ -1535,6 +1577,10 @@ func (c *Cluster) generateLogicalBackupPodEnvVars() []v1.EnvVar {
|
||||
Name: "SCOPE",
|
||||
Value: c.Name,
|
||||
},
|
||||
{
|
||||
Name: "CLUSTER_NAME_LABEL",
|
||||
Value: c.OpConfig.ClusterNameLabel,
|
||||
},
|
||||
{
|
||||
Name: "POD_NAMESPACE",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
@@ -1549,6 +1595,14 @@ func (c *Cluster) generateLogicalBackupPodEnvVars() []v1.EnvVar {
|
||||
Name: "LOGICAL_BACKUP_S3_BUCKET",
|
||||
Value: c.OpConfig.LogicalBackup.LogicalBackupS3Bucket,
|
||||
},
|
||||
{
|
||||
Name: "LOGICAL_BACKUP_S3_ENDPOINT",
|
||||
Value: c.OpConfig.LogicalBackup.LogicalBackupS3Endpoint,
|
||||
},
|
||||
{
|
||||
Name: "LOGICAL_BACKUP_S3_SSE",
|
||||
Value: c.OpConfig.LogicalBackup.LogicalBackupS3SSE,
|
||||
},
|
||||
{
|
||||
Name: "LOGICAL_BACKUP_S3_BUCKET_SCOPE_SUFFIX",
|
||||
Value: getBucketScopeSuffix(string(c.Postgresql.GetUID())),
|
||||
@@ -1587,8 +1641,15 @@ func (c *Cluster) generateLogicalBackupPodEnvVars() []v1.EnvVar {
|
||||
},
|
||||
}
|
||||
|
||||
c.logger.Debugf("Generated logical backup env vars %v", envVars)
|
||||
if c.OpConfig.LogicalBackup.LogicalBackupS3AccessKeyID != "" {
|
||||
envVars = append(envVars, v1.EnvVar{Name: "AWS_ACCESS_KEY_ID", Value: c.OpConfig.LogicalBackup.LogicalBackupS3AccessKeyID})
|
||||
}
|
||||
|
||||
if c.OpConfig.LogicalBackup.LogicalBackupS3SecretAccessKey != "" {
|
||||
envVars = append(envVars, v1.EnvVar{Name: "AWS_SECRET_ACCESS_KEY", Value: c.OpConfig.LogicalBackup.LogicalBackupS3SecretAccessKey})
|
||||
}
|
||||
|
||||
c.logger.Debugf("Generated logical backup env vars %v", envVars)
|
||||
return envVars
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package cluster
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
"testing"
|
||||
|
||||
|
||||
+47
-10
@@ -13,7 +13,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/zalando/postgres-operator/pkg/util"
|
||||
"github.com/zalando/postgres-operator/pkg/util/constants"
|
||||
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
|
||||
"github.com/zalando/postgres-operator/pkg/util/retryutil"
|
||||
)
|
||||
@@ -66,6 +65,17 @@ func (c *Cluster) listResources() error {
|
||||
|
||||
func (c *Cluster) createStatefulSet() (*appsv1.StatefulSet, error) {
|
||||
c.setProcessName("creating statefulset")
|
||||
// check if it's allowed that spec contains initContainers
|
||||
if c.Spec.InitContainers != nil && len(c.Spec.InitContainers) > 0 &&
|
||||
c.OpConfig.EnableInitContainers != nil && !(*c.OpConfig.EnableInitContainers) {
|
||||
return nil, fmt.Errorf("initContainers specified but disabled in configuration")
|
||||
}
|
||||
// check if it's allowed that spec contains sidecars
|
||||
if c.Spec.Sidecars != nil && len(c.Spec.Sidecars) > 0 &&
|
||||
c.OpConfig.EnableSidecars != nil && !(*c.OpConfig.EnableSidecars) {
|
||||
return nil, fmt.Errorf("sidecar containers specified but disabled in configuration")
|
||||
}
|
||||
|
||||
statefulSetSpec, err := c.generateStatefulSet(&c.Spec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not generate statefulset: %v", err)
|
||||
@@ -278,7 +288,8 @@ func (c *Cluster) replaceStatefulSet(newStatefulSet *appsv1.StatefulSet) error {
|
||||
oldStatefulset := c.Statefulset
|
||||
|
||||
options := metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy}
|
||||
if err := c.KubeClient.StatefulSets(oldStatefulset.Namespace).Delete(oldStatefulset.Name, &options); err != nil {
|
||||
err := c.KubeClient.StatefulSets(oldStatefulset.Namespace).Delete(oldStatefulset.Name, &options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not delete statefulset %q: %v", statefulSetName, err)
|
||||
}
|
||||
// make sure we clear the stored statefulset status if the subsequent create fails.
|
||||
@@ -286,11 +297,16 @@ func (c *Cluster) replaceStatefulSet(newStatefulSet *appsv1.StatefulSet) error {
|
||||
// wait until the statefulset is truly deleted
|
||||
c.logger.Debugf("waiting for the statefulset to be deleted")
|
||||
|
||||
err := retryutil.Retry(constants.StatefulsetDeletionInterval, constants.StatefulsetDeletionTimeout,
|
||||
err = retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout,
|
||||
func() (bool, error) {
|
||||
_, err := c.KubeClient.StatefulSets(oldStatefulset.Namespace).Get(oldStatefulset.Name, metav1.GetOptions{})
|
||||
|
||||
return err != nil, nil
|
||||
_, err2 := c.KubeClient.StatefulSets(oldStatefulset.Namespace).Get(oldStatefulset.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 statefulset: %v", err)
|
||||
@@ -380,13 +396,27 @@ func (c *Cluster) updateService(role PostgresRole, newService *v1.Service) error
|
||||
return fmt.Errorf("could not delete service %q: %v", serviceName, err)
|
||||
}
|
||||
|
||||
c.Endpoints[role] = nil
|
||||
svc, err := c.KubeClient.Services(serviceName.Namespace).Create(newService)
|
||||
// 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 create service %q: %v", serviceName, err)
|
||||
return fmt.Errorf("could not delete service %q: %v", serviceName, err)
|
||||
}
|
||||
|
||||
c.Services[role] = svc
|
||||
// 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)
|
||||
@@ -398,6 +428,13 @@ func (c *Cluster) updateService(role PostgresRole, newService *v1.Service) error
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
oldStatus := c.Status
|
||||
c.setSpec(newSpec)
|
||||
|
||||
defer func() {
|
||||
@@ -35,6 +36,16 @@ 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 {
|
||||
err = fmt.Errorf("could not init users: %v", err)
|
||||
return err
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
policybeta1 "k8s.io/api/policy/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
@@ -111,7 +111,7 @@ func (c *Controller) initOperatorConfig() {
|
||||
|
||||
if c.opConfig.SetMemoryRequestToLimit {
|
||||
|
||||
isSmaller, err := util.RequestIsSmallerThanLimit(c.opConfig.DefaultMemoryRequest, c.opConfig.DefaultMemoryLimit)
|
||||
isSmaller, err := util.IsSmallerQuantity(c.opConfig.DefaultMemoryRequest, c.opConfig.DefaultMemoryLimit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -120,7 +120,7 @@ func (c *Controller) initOperatorConfig() {
|
||||
c.opConfig.DefaultMemoryRequest = c.opConfig.DefaultMemoryLimit
|
||||
}
|
||||
|
||||
isSmaller, err = util.RequestIsSmallerThanLimit(c.opConfig.ScalyrMemoryRequest, c.opConfig.ScalyrMemoryLimit)
|
||||
isSmaller, err = util.IsSmallerQuantity(c.opConfig.ScalyrMemoryRequest, c.opConfig.ScalyrMemoryLimit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -240,7 +240,7 @@ func (c *Controller) initController() {
|
||||
c.initClients()
|
||||
|
||||
if configObjectName := os.Getenv("POSTGRES_OPERATOR_CONFIGURATION_OBJECT"); configObjectName != "" {
|
||||
if err := c.createConfigurationCRD(); err != nil {
|
||||
if err := c.createConfigurationCRD(c.opConfig.EnableCRDValidation); err != nil {
|
||||
c.logger.Fatalf("could not register Operator Configuration CustomResourceDefinition: %v", err)
|
||||
}
|
||||
if cfg, err := c.readOperatorConfigurationFromCRD(spec.GetOperatorNamespace(), configObjectName); err != nil {
|
||||
@@ -256,7 +256,7 @@ func (c *Controller) initController() {
|
||||
|
||||
c.modifyConfigFromEnvironment()
|
||||
|
||||
if err := c.createPostgresCRD(); err != nil {
|
||||
if err := c.createPostgresCRD(c.opConfig.EnableCRDValidation); err != nil {
|
||||
c.logger.Fatalf("could not register Postgres CustomResourceDefinition: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||
result := &config.Config{}
|
||||
|
||||
// general config
|
||||
result.EnableCRDValidation = fromCRD.EnableCRDValidation
|
||||
result.EtcdHost = fromCRD.EtcdHost
|
||||
result.DockerImage = fromCRD.DockerImage
|
||||
result.Workers = fromCRD.Workers
|
||||
@@ -41,6 +42,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||
result.ReplicationUsername = fromCRD.PostgresUsersConfiguration.ReplicationUsername
|
||||
|
||||
// kubernetes config
|
||||
result.CustomPodAnnotations = fromCRD.Kubernetes.CustomPodAnnotations
|
||||
result.PodServiceAccountName = fromCRD.Kubernetes.PodServiceAccountName
|
||||
result.PodServiceAccountDefinition = fromCRD.Kubernetes.PodServiceAccountDefinition
|
||||
result.PodServiceAccountRoleBindingDefinition = fromCRD.Kubernetes.PodServiceAccountRoleBindingDefinition
|
||||
@@ -52,6 +54,8 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||
result.WatchedNamespace = fromCRD.Kubernetes.WatchedNamespace
|
||||
result.PDBNameFormat = fromCRD.Kubernetes.PDBNameFormat
|
||||
result.EnablePodDisruptionBudget = fromCRD.Kubernetes.EnablePodDisruptionBudget
|
||||
result.EnableInitContainers = fromCRD.Kubernetes.EnableInitContainers
|
||||
result.EnableSidecars = fromCRD.Kubernetes.EnableSidecars
|
||||
result.SecretNameTemplate = fromCRD.Kubernetes.SecretNameTemplate
|
||||
result.OAuthTokenSecretName = fromCRD.Kubernetes.OAuthTokenSecretName
|
||||
result.InfrastructureRolesSecretName = fromCRD.Kubernetes.InfrastructureRolesSecretName
|
||||
@@ -100,6 +104,10 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||
result.LogicalBackupSchedule = fromCRD.LogicalBackup.Schedule
|
||||
result.LogicalBackupDockerImage = fromCRD.LogicalBackup.DockerImage
|
||||
result.LogicalBackupS3Bucket = fromCRD.LogicalBackup.S3Bucket
|
||||
result.LogicalBackupS3Endpoint = fromCRD.LogicalBackup.S3Endpoint
|
||||
result.LogicalBackupS3AccessKeyID = fromCRD.LogicalBackup.S3AccessKeyID
|
||||
result.LogicalBackupS3SecretAccessKey = fromCRD.LogicalBackup.S3SecretAccessKey
|
||||
result.LogicalBackupS3SSE = fromCRD.LogicalBackup.S3SSE
|
||||
|
||||
// debug config
|
||||
result.DebugLogging = fromCRD.OperatorDebug.DebugLogging
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
@@ -91,12 +91,12 @@ func (c *Controller) createOperatorCRD(crd *apiextv1beta1.CustomResourceDefiniti
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Controller) createPostgresCRD() error {
|
||||
return c.createOperatorCRD(acidv1.PostgresCRD())
|
||||
func (c *Controller) createPostgresCRD(enableValidation *bool) error {
|
||||
return c.createOperatorCRD(acidv1.PostgresCRD(enableValidation))
|
||||
}
|
||||
|
||||
func (c *Controller) createConfigurationCRD() error {
|
||||
return c.createOperatorCRD(acidv1.ConfigurationCRD())
|
||||
func (c *Controller) createConfigurationCRD(enableValidation *bool) error {
|
||||
return c.createOperatorCRD(acidv1.ConfigurationCRD(enableValidation))
|
||||
}
|
||||
|
||||
func readDecodedRole(s string) (*spec.PgUser, error) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/zalando/postgres-operator/pkg/spec"
|
||||
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,10 +12,11 @@ import (
|
||||
|
||||
// CRD describes CustomResourceDefinition specific configuration parameters
|
||||
type CRD struct {
|
||||
ReadyWaitInterval time.Duration `name:"ready_wait_interval" default:"4s"`
|
||||
ReadyWaitTimeout time.Duration `name:"ready_wait_timeout" default:"30s"`
|
||||
ResyncPeriod time.Duration `name:"resync_period" default:"30m"`
|
||||
RepairPeriod time.Duration `name:"repair_period" default:"5m"`
|
||||
ReadyWaitInterval time.Duration `name:"ready_wait_interval" default:"4s"`
|
||||
ReadyWaitTimeout time.Duration `name:"ready_wait_timeout" default:"30s"`
|
||||
ResyncPeriod time.Duration `name:"resync_period" default:"30m"`
|
||||
RepairPeriod time.Duration `name:"repair_period" default:"5m"`
|
||||
EnableCRDValidation *bool `name:"enable_crd_validation" default:"true"`
|
||||
}
|
||||
|
||||
// Resources describes kubernetes resource specific configuration parameters
|
||||
@@ -68,11 +69,15 @@ type Scalyr struct {
|
||||
ScalyrMemoryLimit string `name:"scalyr_memory_limit" default:"1Gi"`
|
||||
}
|
||||
|
||||
// LogicalBackup
|
||||
// LogicalBackup defines configuration for logical backup
|
||||
type LogicalBackup struct {
|
||||
LogicalBackupSchedule string `name:"logical_backup_schedule" default:"30 00 * * *"`
|
||||
LogicalBackupDockerImage string `name:"logical_backup_docker_image" default:"registry.opensource.zalan.do/acid/logical-backup"`
|
||||
LogicalBackupS3Bucket string `name:"logical_backup_s3_bucket" default:""`
|
||||
LogicalBackupSchedule string `name:"logical_backup_schedule" default:"30 00 * * *"`
|
||||
LogicalBackupDockerImage string `name:"logical_backup_docker_image" default:"registry.opensource.zalan.do/acid/logical-backup"`
|
||||
LogicalBackupS3Bucket string `name:"logical_backup_s3_bucket" default:""`
|
||||
LogicalBackupS3Endpoint string `name:"logical_backup_s3_endpoint" default:""`
|
||||
LogicalBackupS3AccessKeyID string `name:"logical_backup_s3_access_key_id" default:""`
|
||||
LogicalBackupS3SecretAccessKey string `name:"logical_backup_s3_secret_access_key" default:""`
|
||||
LogicalBackupS3SSE string `name:"logical_backup_s3_sse" default:"AES256"`
|
||||
}
|
||||
|
||||
// Config describes operator config
|
||||
@@ -85,7 +90,7 @@ type Config struct {
|
||||
|
||||
WatchedNamespace string `name:"watched_namespace"` // special values: "*" means 'watch all namespaces', the empty string "" means 'watch a namespace where operator is deployed to'
|
||||
EtcdHost string `name:"etcd_host" default:""` // special values: the empty string "" means Patroni will use K8s as a DCS
|
||||
DockerImage string `name:"docker_image" default:"registry.opensource.zalan.do/acid/spilo-11:1.6-p1"`
|
||||
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"`
|
||||
// default name `operator` enables backward compatibility with the older ServiceAccountName field
|
||||
PodServiceAccountName string `name:"pod_service_account_name" default:"operator"`
|
||||
@@ -109,6 +114,7 @@ type Config struct {
|
||||
EnableMasterLoadBalancer bool `name:"enable_master_load_balancer" default:"true"`
|
||||
EnableReplicaLoadBalancer bool `name:"enable_replica_load_balancer" default:"false"`
|
||||
CustomServiceAnnotations map[string]string `name:"custom_service_annotations"`
|
||||
CustomPodAnnotations map[string]string `name:"custom_pod_annotations"`
|
||||
EnablePodAntiAffinity bool `name:"enable_pod_antiaffinity" default:"false"`
|
||||
PodAntiAffinityTopologyKey string `name:"pod_antiaffinity_topology_key" default:"kubernetes.io/hostname"`
|
||||
// deprecated and kept for backward compatibility
|
||||
@@ -117,6 +123,8 @@ type Config struct {
|
||||
ReplicaDNSNameFormat StringTemplate `name:"replica_dns_name_format" default:"{cluster}-repl.{team}.{hostedzone}"`
|
||||
PDBNameFormat StringTemplate `name:"pdb_name_format" default:"postgres-{cluster}-pdb"`
|
||||
EnablePodDisruptionBudget *bool `name:"enable_pod_disruption_budget" default:"true"`
|
||||
EnableInitContainers *bool `name:"enable_init_containers" default:"true"`
|
||||
EnableSidecars *bool `name:"enable_sidecars" default:"true"`
|
||||
Workers uint32 `name:"workers" default:"4"`
|
||||
APIPort int `name:"api_port" default:"8080"`
|
||||
RingLogLines int `name:"ring_log_lines" default:"100"`
|
||||
|
||||
@@ -4,11 +4,9 @@ import "time"
|
||||
|
||||
// General kubernetes-related constants
|
||||
const (
|
||||
PostgresContainerName = "postgres"
|
||||
PostgresContainerIdx = 0
|
||||
K8sAPIPath = "/apis"
|
||||
StatefulsetDeletionInterval = 1 * time.Second
|
||||
StatefulsetDeletionTimeout = 30 * time.Second
|
||||
PostgresContainerName = "postgres"
|
||||
PostgresContainerIdx = 0
|
||||
K8sAPIPath = "/apis"
|
||||
|
||||
QueueResyncPeriodPod = 5 * time.Minute
|
||||
QueueResyncPeriodTPR = 5 * time.Minute
|
||||
|
||||
@@ -24,7 +24,7 @@ var teamsAPItc = []struct {
|
||||
{`{
|
||||
"dn": "cn=100100,ou=official,ou=foobar,dc=zalando,dc=net",
|
||||
"id": "acid",
|
||||
"id_name": "ACID",
|
||||
"id_name": "acid",
|
||||
"team_id": "111222",
|
||||
"type": "official",
|
||||
"name": "Acid team name",
|
||||
@@ -70,7 +70,7 @@ var teamsAPItc = []struct {
|
||||
&Team{
|
||||
Dn: "cn=100100,ou=official,ou=foobar,dc=zalando,dc=net",
|
||||
ID: "acid",
|
||||
TeamName: "ACID",
|
||||
TeamName: "acid",
|
||||
TeamID: "111222",
|
||||
Type: "official",
|
||||
FullName: "Acid team name",
|
||||
|
||||
+4
-4
@@ -141,17 +141,17 @@ func Coalesce(val, defaultVal string) string {
|
||||
return val
|
||||
}
|
||||
|
||||
// RequestIsSmallerThanLimit : ...
|
||||
func RequestIsSmallerThanLimit(requestStr, limitStr string) (bool, error) {
|
||||
// IsSmallerQuantity : checks if first resource is of a smaller quantity than the second
|
||||
func IsSmallerQuantity(requestStr, limitStr string) (bool, error) {
|
||||
|
||||
request, err := resource.ParseQuantity(requestStr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not parse memory request %v : %v", requestStr, err)
|
||||
return false, fmt.Errorf("could not parse request %v : %v", requestStr, err)
|
||||
}
|
||||
|
||||
limit, err2 := resource.ParseQuantity(limitStr)
|
||||
if err2 != nil {
|
||||
return false, fmt.Errorf("could not parse memory limit %v : %v", limitStr, err2)
|
||||
return false, fmt.Errorf("could not parse limit %v : %v", limitStr, err2)
|
||||
}
|
||||
|
||||
return request.Cmp(limit) == -1, nil
|
||||
|
||||
@@ -69,7 +69,7 @@ var substringMatch = []struct {
|
||||
{regexp.MustCompile(`aaaa (\d+) bbbb`), "aaaa 123 bbbb", nil},
|
||||
}
|
||||
|
||||
var requestIsSmallerThanLimitTests = []struct {
|
||||
var requestIsSmallerQuantityTests = []struct {
|
||||
request string
|
||||
limit string
|
||||
out bool
|
||||
@@ -155,14 +155,14 @@ func TestMapContains(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestIsSmallerThanLimit(t *testing.T) {
|
||||
for _, tt := range requestIsSmallerThanLimitTests {
|
||||
res, err := RequestIsSmallerThanLimit(tt.request, tt.limit)
|
||||
func TestIsSmallerQuantity(t *testing.T) {
|
||||
for _, tt := range requestIsSmallerQuantityTests {
|
||||
res, err := IsSmallerQuantity(tt.request, tt.limit)
|
||||
if err != nil {
|
||||
t.Errorf("RequestIsSmallerThanLimit returned unexpected error: %#v", err)
|
||||
t.Errorf("IsSmallerQuantity returned unexpected error: %#v", err)
|
||||
}
|
||||
if res != tt.out {
|
||||
t.Errorf("RequestIsSmallerThanLimit expected: %#v, got: %#v", tt.out, res)
|
||||
t.Errorf("IsSmallerQuantity expected: %#v, got: %#v", tt.out, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user