Allow cloning clusters from the operator. (#90)
Allow cloning clusters from the operator. The changes add a new JSON node `clone` with possible values `cluster` and `timestamp`. `cluster` is mandatory, and setting a non-empty `timestamp` triggers wal-e point in time recovery. Spilo and Patroni do the whole heavy-lifting, the operator just defines certain variables and gathers some data about how to connect to the host to clone or the target S3 bucket. As a minor change, set the image pull policy to IfNotPresent instead of Always to simplify local testing. Change the default replication username to standby.
This commit is contained in:
		
							parent
							
								
									a0a9e8f849
								
							
						
					
					
						commit
						8b85935a7a
					
				|  | @ -21,7 +21,7 @@ data: | ||||||
|   pod_label_wait_timeout: 10m |   pod_label_wait_timeout: 10m | ||||||
|   ready_wait_interval: 3s |   ready_wait_interval: 3s | ||||||
|   ready_wait_timeout: 30s |   ready_wait_timeout: 30s | ||||||
|   replication_username: replication |   replication_username: standby | ||||||
|   resource_check_interval: 3s |   resource_check_interval: 3s | ||||||
|   resource_check_timeout: 10m |   resource_check_timeout: 10m | ||||||
|   resync_period: 5m |   resync_period: 5m | ||||||
|  |  | ||||||
|  | @ -226,7 +226,10 @@ PATRONI_INITDB_PARAMS: | ||||||
| 	return string(result) | 	return string(result) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) generatePodTemplate(resourceRequirements *v1.ResourceRequirements, pgParameters *spec.PostgresqlParam, patroniParameters *spec.Patroni) *v1.PodTemplateSpec { | func (c *Cluster) generatePodTemplate(resourceRequirements *v1.ResourceRequirements, | ||||||
|  | 	pgParameters *spec.PostgresqlParam, | ||||||
|  | 	patroniParameters *spec.Patroni, | ||||||
|  | 	cloneDescription *spec.CloneDescription) *v1.PodTemplateSpec { | ||||||
| 	spiloConfiguration := c.generateSpiloJSONConfiguration(pgParameters, patroniParameters) | 	spiloConfiguration := c.generateSpiloJSONConfiguration(pgParameters, patroniParameters) | ||||||
| 
 | 
 | ||||||
| 	envVars := []v1.EnvVar{ | 	envVars := []v1.EnvVar{ | ||||||
|  | @ -301,11 +304,17 @@ func (c *Cluster) generatePodTemplate(resourceRequirements *v1.ResourceRequireme | ||||||
| 	if c.OpConfig.WALES3Bucket != "" { | 	if c.OpConfig.WALES3Bucket != "" { | ||||||
| 		envVars = append(envVars, v1.EnvVar{Name: "WAL_S3_BUCKET", Value: c.OpConfig.WALES3Bucket}) | 		envVars = append(envVars, v1.EnvVar{Name: "WAL_S3_BUCKET", Value: c.OpConfig.WALES3Bucket}) | ||||||
| 	} | 	} | ||||||
|  | 	if cloneDescription.ClusterName != "" { | ||||||
|  | 		cloneVars := c.generateCloneEnvironment(cloneDescription) | ||||||
|  | 		for _, v := range cloneVars { | ||||||
|  | 			envVars = append(envVars, v) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	privilegedMode := bool(true) | 	privilegedMode := bool(true) | ||||||
| 	container := v1.Container{ | 	container := v1.Container{ | ||||||
| 		Name:            c.containerName(), | 		Name:            c.containerName(), | ||||||
| 		Image:           c.OpConfig.DockerImage, | 		Image:           c.OpConfig.DockerImage, | ||||||
| 		ImagePullPolicy: v1.PullAlways, | 		ImagePullPolicy: v1.PullIfNotPresent, | ||||||
| 		Resources:       *resourceRequirements, | 		Resources:       *resourceRequirements, | ||||||
| 		Ports: []v1.ContainerPort{ | 		Ports: []v1.ContainerPort{ | ||||||
| 			{ | 			{ | ||||||
|  | @ -357,13 +366,13 @@ func (c *Cluster) generatePodTemplate(resourceRequirements *v1.ResourceRequireme | ||||||
| func (c *Cluster) generateStatefulSet(spec spec.PostgresSpec) (*v1beta1.StatefulSet, error) { | func (c *Cluster) generateStatefulSet(spec spec.PostgresSpec) (*v1beta1.StatefulSet, error) { | ||||||
| 	resourceRequirements, err := c.resourceRequirements(spec.Resources) | 	resourceRequirements, err := c.resourceRequirements(spec.Resources) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, fmt.Errorf("could not generate resource requirements: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	podTemplate := c.generatePodTemplate(resourceRequirements, &spec.PostgresqlParam, &spec.Patroni) | 	podTemplate := c.generatePodTemplate(resourceRequirements, &spec.PostgresqlParam, &spec.Patroni, &spec.Clone) | ||||||
| 	volumeClaimTemplate, err := generatePersistentVolumeClaimTemplate(spec.Volume.Size, spec.Volume.StorageClass) | 	volumeClaimTemplate, err := generatePersistentVolumeClaimTemplate(spec.Volume.Size, spec.Volume.StorageClass) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, fmt.Errorf("could not generate volume claim template: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	statefulSet := &v1beta1.StatefulSet{ | 	statefulSet := &v1beta1.StatefulSet{ | ||||||
|  | @ -523,3 +532,50 @@ func (c *Cluster) generateMasterEndpoints(subsets []v1.EndpointSubset) *v1.Endpo | ||||||
| 
 | 
 | ||||||
| 	return endpoints | 	return endpoints | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (c *Cluster) generateCloneEnvironment(description *spec.CloneDescription) []v1.EnvVar { | ||||||
|  | 	result := make([]v1.EnvVar, 0) | ||||||
|  | 	if description.ClusterName == "" { | ||||||
|  | 		return result | ||||||
|  | 	} | ||||||
|  | 	cluster := description.ClusterName | ||||||
|  | 	result = append(result, v1.EnvVar{Name: "CLONE_SCOPE", Value: cluster}) | ||||||
|  | 	if description.EndTimestamp == "" { | ||||||
|  | 		// cloning with basebackup, make a connection string to the cluster to clone from
 | ||||||
|  | 		host, port := c.getClusterServiceConnectionParameters(cluster) | ||||||
|  | 		// TODO: make some/all of those constants
 | ||||||
|  | 		result = append(result, v1.EnvVar{Name: "CLONE_METHOD", Value: "CLONE_WITH_BASEBACKUP"}) | ||||||
|  | 		result = append(result, v1.EnvVar{Name: "CLONE_HOST", Value: host}) | ||||||
|  | 		result = append(result, v1.EnvVar{Name: "CLONE_PORT", Value: port}) | ||||||
|  | 		// TODO: assume replication user name is the same for all clusters, fetch it from secrets otherwise
 | ||||||
|  | 		result = append(result, v1.EnvVar{Name: "CLONE_USER", Value: c.OpConfig.ReplicationUsername}) | ||||||
|  | 		result = append(result, | ||||||
|  | 			v1.EnvVar{Name: "CLONE_PASSWORD", | ||||||
|  | 				ValueFrom: &v1.EnvVarSource{ | ||||||
|  | 					SecretKeyRef: &v1.SecretKeySelector{ | ||||||
|  | 						LocalObjectReference: v1.LocalObjectReference{ | ||||||
|  | 							Name: c.credentialSecretNameForCluster(c.OpConfig.ReplicationUsername, | ||||||
|  | 								description.ClusterName), | ||||||
|  | 						}, | ||||||
|  | 						Key: "password", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}) | ||||||
|  | 	} else { | ||||||
|  | 		// cloning with S3, find out the bucket to clone
 | ||||||
|  | 		clone_wal_s3_bucket := c.OpConfig.WALES3Bucket | ||||||
|  | 		result = append(result, v1.EnvVar{Name: "CLONE_METHOD", Value: "CLONE_WITH_WALE"}) | ||||||
|  | 		result = append(result, v1.EnvVar{Name: "CLONE_WAL_S3_BUCKET", Value: clone_wal_s3_bucket}) | ||||||
|  | 		result = append(result, v1.EnvVar{Name: "CLONE_TARGET_TIME", Value: description.EndTimestamp}) | ||||||
|  | 	} | ||||||
|  | 	return result | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // getClusterServiceConnectionParameters fetches cluster host name and port
 | ||||||
|  | // TODO: perhaps we need to query the service (i.e. if non-standard port is used?)
 | ||||||
|  | // TODO: handle clusters in different namespaces
 | ||||||
|  | func (c *Cluster) getClusterServiceConnectionParameters(clusterName string) (host string, port string) { | ||||||
|  | 	host = clusterName | ||||||
|  | 	port = "5432" | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -304,11 +304,15 @@ func (c *Cluster) replicaDNSName() string { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) credentialSecretName(username string) string { | func (c *Cluster) credentialSecretName(username string) string { | ||||||
|  | 	return c.credentialSecretNameForCluster(username, c.Name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *Cluster) credentialSecretNameForCluster(username string, clusterName string) string { | ||||||
| 	// secret  must consist of lower case alphanumeric characters, '-' or '.',
 | 	// secret  must consist of lower case alphanumeric characters, '-' or '.',
 | ||||||
| 	// and must start and end with an alphanumeric character
 | 	// and must start and end with an alphanumeric character
 | ||||||
| 	return fmt.Sprintf(constants.UserSecretTemplate, | 	return fmt.Sprintf(constants.UserSecretTemplate, | ||||||
| 		strings.Replace(username, "_", "-", -1), | 		strings.Replace(username, "_", "-", -1), | ||||||
| 		c.Name) | 		clusterName) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) podSpiloRole(pod *v1.Pod) string { | func (c *Cluster) podSpiloRole(pod *v1.Pod) string { | ||||||
|  |  | ||||||
|  | @ -51,6 +51,12 @@ type Patroni struct { | ||||||
| 	MaximumLagOnFailover float32           `json:"maximum_lag_on_failover"` // float32 because https://github.com/kubernetes/kubernetes/issues/30213
 | 	MaximumLagOnFailover float32           `json:"maximum_lag_on_failover"` // float32 because https://github.com/kubernetes/kubernetes/issues/30213
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // CloneDescription describes which cluster the new should clone and up to which point in time
 | ||||||
|  | type CloneDescription struct { | ||||||
|  | 	ClusterName  string `json:"cluster,omitempty"` | ||||||
|  | 	EndTimestamp string `json:"timestamp,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type userFlags []string | type userFlags []string | ||||||
| 
 | 
 | ||||||
| // PostgresStatus contains status of the PostgreSQL cluster (running, creation failed etc.)
 | // PostgresStatus contains status of the PostgreSQL cluster (running, creation failed etc.)
 | ||||||
|  | @ -86,12 +92,13 @@ type PostgresSpec struct { | ||||||
| 
 | 
 | ||||||
| 	TeamID              string   `json:"teamId"` | 	TeamID              string   `json:"teamId"` | ||||||
| 	AllowedSourceRanges []string `json:"allowedSourceRanges"` | 	AllowedSourceRanges []string `json:"allowedSourceRanges"` | ||||||
| 	// EnableLoadBalancer  is a pointer, since it is importat to know if that parameters is omitted from the manifest
 | 	// EnableLoadBalancer  is a pointer, since it is important to know if that parameters is omitted from the manifest
 | ||||||
| 	UseLoadBalancer     *bool                `json:"useLoadBalancer,omitempty"` | 	UseLoadBalancer     *bool                `json:"useLoadBalancer,omitempty"` | ||||||
| 	ReplicaLoadBalancer bool                 `json:"replicaLoadBalancer,omitempty"` | 	ReplicaLoadBalancer bool                 `json:"replicaLoadBalancer,omitempty"` | ||||||
| 	NumberOfInstances   int32                `json:"numberOfInstances"` | 	NumberOfInstances   int32                `json:"numberOfInstances"` | ||||||
| 	Users               map[string]userFlags `json:"users"` | 	Users               map[string]userFlags `json:"users"` | ||||||
| 	MaintenanceWindows  []MaintenanceWindow  `json:"maintenanceWindows,omitempty"` | 	MaintenanceWindows  []MaintenanceWindow  `json:"maintenanceWindows,omitempty"` | ||||||
|  | 	Clone               CloneDescription     `json:"clone"` | ||||||
| 	ClusterName         string               `json:"-"` | 	ClusterName         string               `json:"-"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -236,6 +243,15 @@ func (p *Postgresql) UnmarshalJSON(data []byte) error { | ||||||
| 		tmp2.Error = err | 		tmp2.Error = err | ||||||
| 		tmp2.Status = ClusterStatusInvalid | 		tmp2.Status = ClusterStatusInvalid | ||||||
| 	} | 	} | ||||||
|  | 	// The assumption below is that a cluster to clone, if any, belongs to the same team
 | ||||||
|  | 	if tmp2.Spec.Clone.ClusterName != "" { | ||||||
|  | 		_, err := extractClusterName(tmp2.Spec.Clone.ClusterName, tmp2.Spec.TeamID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			tmp2.Error = fmt.Errorf("%s for the cluster to clone", err) | ||||||
|  | 			tmp2.Spec.Clone = CloneDescription{} | ||||||
|  | 			tmp2.Status = ClusterStatusInvalid | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	*p = tmp2 | 	*p = tmp2 | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
|  |  | ||||||
|  | @ -119,7 +119,7 @@ var unmarshalCluster = []struct { | ||||||
| 			Field:  "teamId", | 			Field:  "teamId", | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	[]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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null},"status":"Invalid"}`), nil}, | 	[]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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":"Invalid"}`), nil}, | ||||||
| 	{[]byte(`{ | 	{[]byte(`{ | ||||||
|   "kind": "Postgresql", |   "kind": "Postgresql", | ||||||
|   "apiVersion": "acid.zalan.do/v1", |   "apiVersion": "acid.zalan.do/v1", | ||||||
|  | @ -160,6 +160,9 @@ var unmarshalCluster = []struct { | ||||||
|         "memory": "3000Mi" |         "memory": "3000Mi" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "clone" : { | ||||||
|  |      "cluster": "acid-batman" | ||||||
|  |      }, | ||||||
|     "patroni": { |     "patroni": { | ||||||
|       "initdb": { |       "initdb": { | ||||||
|         "encoding": "UTF8", |         "encoding": "UTF8", | ||||||
|  | @ -219,6 +222,7 @@ var unmarshalCluster = []struct { | ||||||
| 					ResourceRequest: ResourceDescription{CPU: "10m", Memory: "50Mi"}, | 					ResourceRequest: ResourceDescription{CPU: "10m", Memory: "50Mi"}, | ||||||
| 					ResourceLimits:  ResourceDescription{CPU: "300m", Memory: "3000Mi"}, | 					ResourceLimits:  ResourceDescription{CPU: "300m", Memory: "3000Mi"}, | ||||||
| 				}, | 				}, | ||||||
|  | 
 | ||||||
| 				TeamID:              "ACID", | 				TeamID:              "ACID", | ||||||
| 				AllowedSourceRanges: []string{"127.0.0.1/32"}, | 				AllowedSourceRanges: []string{"127.0.0.1/32"}, | ||||||
| 				NumberOfInstances:   2, | 				NumberOfInstances:   2, | ||||||
|  | @ -241,11 +245,14 @@ var unmarshalCluster = []struct { | ||||||
| 						EndTime:   mustParseTime("05:15"), | 						EndTime:   mustParseTime("05:15"), | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
|  | 				Clone: CloneDescription{ | ||||||
|  | 					ClusterName: "acid-batman", | ||||||
|  | 				}, | ||||||
| 				ClusterName: "testcluster1", | 				ClusterName: "testcluster1", | ||||||
| 			}, | 			}, | ||||||
| 			Error: nil, | 			Error: nil, | ||||||
| 		}, | 		}, | ||||||
| 		[]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"}},"volume":{"size":"5Gi","storageClass":"SSD"},"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},"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"]}}`), nil}, | 		[]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"}},"volume":{"size":"5Gi","storageClass":"SSD"},"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},"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"}}}`), nil}, | ||||||
| 	{ | 	{ | ||||||
| 		[]byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "teapot-testcluster1"}, "spec": {"teamId": "acid"}}`), | 		[]byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "teapot-testcluster1"}, "spec": {"teamId": "acid"}}`), | ||||||
| 		Postgresql{ | 		Postgresql{ | ||||||
|  | @ -260,12 +267,31 @@ var unmarshalCluster = []struct { | ||||||
| 			Status: ClusterStatusInvalid, | 			Status: ClusterStatusInvalid, | ||||||
| 			Error:  errors.New("name must match {TEAM}-{NAME} format"), | 			Error:  errors.New("name must match {TEAM}-{NAME} format"), | ||||||
| 		}, | 		}, | ||||||
| 		[]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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null},"status":"Invalid"}`), nil}, | 		[]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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":"Invalid"}`), nil}, | ||||||
|  | 	{ | ||||||
|  | 		in: []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": "acid", "clone": {"cluster": "team-batman"}}}`), | ||||||
|  | 		out: Postgresql{ | ||||||
|  | 			TypeMeta: metav1.TypeMeta{ | ||||||
|  | 				Kind:       "Postgresql", | ||||||
|  | 				APIVersion: "acid.zalan.do/v1", | ||||||
|  | 			}, | ||||||
|  | 			ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 				Name: "acid-testcluster1", | ||||||
|  | 			}, | ||||||
|  | 			Spec: PostgresSpec{ | ||||||
|  | 				TeamID:      "acid", | ||||||
|  | 				Clone:       CloneDescription{}, | ||||||
|  | 				ClusterName: "testcluster1", | ||||||
|  | 			}, | ||||||
|  | 			Status: ClusterStatusInvalid, | ||||||
|  | 			Error:  errors.New("name must match {TEAM}-{NAME} format for the cluster to clone"), | ||||||
|  | 		}, | ||||||
|  | 		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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":"Invalid"}`), err: nil}, | ||||||
| 	{[]byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`), | 	{[]byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`), | ||||||
| 		Postgresql{}, | 		Postgresql{}, | ||||||
| 		[]byte{}, | 		[]byte{}, | ||||||
| 		errors.New("unexpected end of JSON input")}, | 		errors.New("unexpected end of JSON input")}, | ||||||
| 	{[]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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null},"status":"Invalid"}`), | 	{[]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},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":"Invalid"}`), | ||||||
| 		Postgresql{}, | 		Postgresql{}, | ||||||
| 		[]byte{}, | 		[]byte{}, | ||||||
| 		errors.New("invalid character 'q' looking for beginning of value")}} | 		errors.New("invalid character 'q' looking for beginning of value")}} | ||||||
|  |  | ||||||
|  | @ -38,7 +38,7 @@ type Auth struct { | ||||||
| 	OAuthTokenSecretName          spec.NamespacedName `name:"oauth_token_secret_name" default:"postgresql-operator"` | 	OAuthTokenSecretName          spec.NamespacedName `name:"oauth_token_secret_name" default:"postgresql-operator"` | ||||||
| 	InfrastructureRolesSecretName spec.NamespacedName `name:"infrastructure_roles_secret_name"` | 	InfrastructureRolesSecretName spec.NamespacedName `name:"infrastructure_roles_secret_name"` | ||||||
| 	SuperUsername                 string              `name:"super_username" default:"postgres"` | 	SuperUsername                 string              `name:"super_username" default:"postgres"` | ||||||
| 	ReplicationUsername           string              `name:"replication_username" default:"replication"` | 	ReplicationUsername           string              `name:"replication_username" default:"standby"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Config describes operator config
 | // Config describes operator config
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue