mirror of
https://github.com/zalando/postgres-operator.git
synced 2026-10-01 08:07:04 +02:00
Get config from environment variables;
ignore pg major version change; get rid of resources package;
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
)
|
||||
|
||||
type TPR struct {
|
||||
ReadyWaitInterval time.Duration `split_words:"true",default:"3s"`
|
||||
ReadyWaitTimeout time.Duration `split_words:"true",default:"30s"`
|
||||
ResyncPeriod time.Duration `split_words:"true",default:"5m"`
|
||||
}
|
||||
|
||||
type Resources struct {
|
||||
ResyncPeriodPod time.Duration `split_words:"true",default:"5m"`
|
||||
ResourceCheckInterval time.Duration `split_words:"true",default:"3s"`
|
||||
ResourceCheckTimeout time.Duration `split_words:"true",default:"10m"`
|
||||
PodLabelWaitTimeout time.Duration `split_words:"true",default:"10m"`
|
||||
PodDeletionWaitTimeout time.Duration `split_words:"true",default:"10m"`
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
PamRoleName string `split_words:"true",default:"zalandos"`
|
||||
PamConfiguration string `split_words:"true",default:"https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"`
|
||||
TeamsAPIUrl string `envconfig:"teams_api_url",default:"https://teams.example.com/api/"`
|
||||
OAuthTokenSecretName string `envconfig:"oauth_token_secret_name",default:"postgresql-operator"`
|
||||
SuperUsername string `split_words:"true",default:"postgres"`
|
||||
ReplicationUsername string `split_words:"true",default:"replication"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
TPR
|
||||
Resources
|
||||
Auth
|
||||
EtcdHost string `split_words:"true",default:"etcd-client.default.svc.cluster.local:2379"`
|
||||
DockerImage string `split_words:"true",default:"registry.opensource.zalan.do/acid/spilo-9.6:1.2-p12"`
|
||||
ServiceAccountName string `split_words:"true",default:"operator"`
|
||||
DbHostedZone string `split_words:"true",default:"db.example.com"`
|
||||
}
|
||||
|
||||
func LoadFromEnv() *Config {
|
||||
var cfg Config
|
||||
err := envconfig.Process("PGOP", &cfg)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Can't read config: %v", err))
|
||||
}
|
||||
|
||||
return &cfg
|
||||
}
|
||||
@@ -1,41 +1,14 @@
|
||||
package constants
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
TPRName = "postgresql"
|
||||
TPRVendor = "acid.zalan.do"
|
||||
TPRDescription = "Managed PostgreSQL clusters"
|
||||
TPRReadyWaitInterval = 3 * time.Second
|
||||
TPRReadyWaitTimeout = 30 * time.Second
|
||||
TPRApiVersion = "v1"
|
||||
ResourceCheckInterval = 3 * time.Second
|
||||
ResourceCheckTimeout = 10 * time.Minute
|
||||
|
||||
PodLabelWaitTimeout = 10 * time.Minute
|
||||
PodDeletionWaitTimeout = 10 * time.Minute
|
||||
|
||||
ResourceName = TPRName + "s"
|
||||
ResyncPeriodTPR = 5 * time.Minute
|
||||
ResyncPeriodPod = 5 * time.Minute
|
||||
|
||||
SuperuserName = "postgres"
|
||||
ReplicationUsername = "replication"
|
||||
|
||||
//TODO: move to the operator spec
|
||||
EtcdHost = "etcd-client.default.svc.cluster.local:2379"
|
||||
SpiloImage = "registry.opensource.zalan.do/acid/spilo-9.6:1.2-p12"
|
||||
PamRoleName = "zalandos"
|
||||
PamConfiguration = "https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"
|
||||
PasswordLength = 64
|
||||
TeamsAPIUrl = "https://teams.example.com/api/"
|
||||
UserSecretTemplate = "%s.%s.credentials.%s.%s" // Username, ClusterName, TPRName, TPRVendor
|
||||
|
||||
OAuthTokenSecretName = "postgresql-operator"
|
||||
ServiceAccountName = "operator"
|
||||
//Constants
|
||||
TPRName = "postgresql"
|
||||
TPRVendor = "acid.zalan.do"
|
||||
TPRDescription = "Managed PostgreSQL clusters"
|
||||
TPRApiVersion = "v1"
|
||||
DataVolumeName = "pgdata"
|
||||
PasswordLength = 64
|
||||
UserSecretTemplate = "%s.%s.credentials.%s.%s" // Username, ClusterName, TPRName, TPRVendor
|
||||
ZalandoDnsNameAnnotation = "zalando.org/dnsname"
|
||||
|
||||
// TODO: move DbHostedZone to operator configuration
|
||||
DbHostedZone = "db.example.com"
|
||||
ResourceName = TPRName + "s"
|
||||
)
|
||||
|
||||
@@ -1,289 +0,0 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/client-go/pkg/api/resource"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
"k8s.io/client-go/pkg/apis/apps/v1beta1"
|
||||
extv1beta "k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
"k8s.io/client-go/pkg/labels"
|
||||
"k8s.io/client-go/pkg/util/intstr"
|
||||
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/util"
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/util/constants"
|
||||
)
|
||||
|
||||
func credentialSecretName(clusterName, username string) string {
|
||||
return fmt.Sprintf(
|
||||
constants.UserSecretTemplate,
|
||||
username,
|
||||
clusterName,
|
||||
constants.TPRName,
|
||||
constants.TPRVendor)
|
||||
}
|
||||
|
||||
func labelsSet(clusterName string) labels.Set {
|
||||
return labels.Set{
|
||||
"application": "spilo",
|
||||
"spilo-cluster": clusterName,
|
||||
}
|
||||
}
|
||||
|
||||
func ResourceList(resources spec.Resources) *v1.ResourceList {
|
||||
resourceList := v1.ResourceList{}
|
||||
if resources.Cpu != "" {
|
||||
resourceList[v1.ResourceCPU] = resource.MustParse(resources.Cpu)
|
||||
}
|
||||
|
||||
if resources.Memory != "" {
|
||||
resourceList[v1.ResourceMemory] = resource.MustParse(resources.Memory)
|
||||
}
|
||||
|
||||
return &resourceList
|
||||
}
|
||||
|
||||
func PodTemplate(cluster spec.ClusterName, resourceList *v1.ResourceList, pgVersion string, dockerImage, etcdHost string) *v1.PodTemplateSpec {
|
||||
envVars := []v1.EnvVar{
|
||||
{
|
||||
Name: "SCOPE",
|
||||
Value: cluster.Name,
|
||||
},
|
||||
{
|
||||
Name: "PGROOT",
|
||||
Value: "/home/postgres/pgdata/pgroot",
|
||||
},
|
||||
{
|
||||
Name: "ETCD_HOST",
|
||||
Value: etcdHost,
|
||||
},
|
||||
{
|
||||
Name: "POD_IP",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
FieldRef: &v1.ObjectFieldSelector{
|
||||
APIVersion: "v1",
|
||||
FieldPath: "status.podIP",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "POD_NAMESPACE",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
FieldRef: &v1.ObjectFieldSelector{
|
||||
APIVersion: "v1",
|
||||
FieldPath: "metadata.namespace",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "PGPASSWORD_SUPERUSER",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: credentialSecretName(cluster.Name, constants.SuperuserName),
|
||||
},
|
||||
Key: "password",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "PGPASSWORD_STANDBY",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: credentialSecretName(cluster.Name, constants.ReplicationUsername),
|
||||
},
|
||||
Key: "password",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "PAM_OAUTH2", //TODO: get from the operator tpr spec
|
||||
Value: constants.PamConfiguration, //space before uid is obligatory
|
||||
},
|
||||
{
|
||||
Name: "SPILO_CONFIGURATION", //TODO: get from the operator tpr spec
|
||||
Value: fmt.Sprintf(`
|
||||
postgresql:
|
||||
bin_dir: /usr/lib/postgresql/%s/bin
|
||||
bootstrap:
|
||||
initdb:
|
||||
- auth-host: md5
|
||||
- auth-local: trust
|
||||
users:
|
||||
%s:
|
||||
password: NULL
|
||||
options:
|
||||
- createdb
|
||||
- nologin
|
||||
pg_hba:
|
||||
- hostnossl all all all reject
|
||||
- hostssl all +%s all pam
|
||||
- hostssl all all all md5`, pgVersion, constants.PamRoleName, constants.PamRoleName),
|
||||
},
|
||||
}
|
||||
|
||||
container := v1.Container{
|
||||
Name: cluster.Name,
|
||||
Image: dockerImage,
|
||||
ImagePullPolicy: v1.PullAlways,
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: *resourceList,
|
||||
},
|
||||
Ports: []v1.ContainerPort{
|
||||
{
|
||||
ContainerPort: 8008,
|
||||
Protocol: v1.ProtocolTCP,
|
||||
},
|
||||
{
|
||||
ContainerPort: 5432,
|
||||
Protocol: v1.ProtocolTCP,
|
||||
},
|
||||
{
|
||||
ContainerPort: 8080,
|
||||
Protocol: v1.ProtocolTCP,
|
||||
},
|
||||
},
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{
|
||||
Name: constants.DataVolumeName,
|
||||
MountPath: "/home/postgres/pgdata", //TODO: fetch from manifesto
|
||||
},
|
||||
},
|
||||
Env: envVars,
|
||||
}
|
||||
terminateGracePeriodSeconds := int64(30)
|
||||
|
||||
podSpec := v1.PodSpec{
|
||||
ServiceAccountName: constants.ServiceAccountName,
|
||||
TerminationGracePeriodSeconds: &terminateGracePeriodSeconds,
|
||||
Containers: []v1.Container{container},
|
||||
}
|
||||
|
||||
template := v1.PodTemplateSpec{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Labels: labelsSet(cluster.Name),
|
||||
Namespace: cluster.Namespace,
|
||||
Annotations: map[string]string{"pod.alpha.kubernetes.io/initialized": "true"},
|
||||
},
|
||||
Spec: podSpec,
|
||||
}
|
||||
|
||||
return &template
|
||||
}
|
||||
|
||||
func VolumeClaimTemplate(volumeSize, volumeStorageClass string) *v1.PersistentVolumeClaim {
|
||||
metadata := v1.ObjectMeta{
|
||||
Name: constants.DataVolumeName,
|
||||
}
|
||||
if volumeStorageClass != "" {
|
||||
// TODO: check if storage class exists
|
||||
metadata.Annotations = map[string]string{"volume.beta.kubernetes.io/storage-class": volumeStorageClass}
|
||||
} else {
|
||||
metadata.Annotations = map[string]string{"volume.alpha.kubernetes.io/storage-class": "default"}
|
||||
}
|
||||
|
||||
volumeClaim := &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: metadata,
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceStorage: resource.MustParse(volumeSize),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return volumeClaim
|
||||
}
|
||||
|
||||
func StatefulSet(cluster spec.ClusterName, podTemplate *v1.PodTemplateSpec,
|
||||
persistenVolumeClaim *v1.PersistentVolumeClaim, numberOfInstances int32) *v1beta1.StatefulSet {
|
||||
statefulSet := &v1beta1.StatefulSet{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: cluster.Name,
|
||||
Namespace: cluster.Namespace,
|
||||
Labels: labelsSet(cluster.Name),
|
||||
},
|
||||
Spec: v1beta1.StatefulSetSpec{
|
||||
Replicas: &numberOfInstances,
|
||||
ServiceName: cluster.Name,
|
||||
Template: *podTemplate,
|
||||
VolumeClaimTemplates: []v1.PersistentVolumeClaim{*persistenVolumeClaim},
|
||||
},
|
||||
}
|
||||
|
||||
return statefulSet
|
||||
}
|
||||
|
||||
func UserSecrets(cluster spec.ClusterName, pgUsers map[string]spec.PgUser) (secrets map[string]*v1.Secret, err error) {
|
||||
secrets = make(map[string]*v1.Secret, len(pgUsers))
|
||||
namespace := cluster.Namespace
|
||||
for username, pgUser := range pgUsers {
|
||||
//Skip users with no password i.e. human users (they'll be authenticated using pam)
|
||||
if pgUser.Password == "" {
|
||||
continue
|
||||
}
|
||||
secret := v1.Secret{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: credentialSecretName(cluster.Name, username),
|
||||
Namespace: namespace,
|
||||
Labels: labelsSet(cluster.Name),
|
||||
},
|
||||
Type: v1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
"username": []byte(pgUser.Name),
|
||||
"password": []byte(pgUser.Password),
|
||||
},
|
||||
}
|
||||
secrets[username] = &secret
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Service(cluster spec.ClusterName, teamName string, allowedSourceRanges []string) *v1.Service {
|
||||
service := &v1.Service{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: cluster.Name,
|
||||
Namespace: cluster.Namespace,
|
||||
Labels: labelsSet(cluster.Name),
|
||||
Annotations: map[string]string{
|
||||
constants.ZalandoDnsNameAnnotation: util.ClusterDNSName(cluster.Name, teamName, constants.DbHostedZone),
|
||||
},
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Type: v1.ServiceTypeLoadBalancer,
|
||||
Ports: []v1.ServicePort{{Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}}},
|
||||
LoadBalancerSourceRanges: allowedSourceRanges,
|
||||
},
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
func Endpoint(cluster spec.ClusterName) *v1.Endpoints {
|
||||
endpoints := &v1.Endpoints{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: cluster.Name,
|
||||
Namespace: cluster.Namespace,
|
||||
Labels: labelsSet(cluster.Name),
|
||||
},
|
||||
}
|
||||
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func ThirdPartyResource(TPRName string) *extv1beta.ThirdPartyResource {
|
||||
return &extv1beta.ThirdPartyResource{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
//ThirdPartyResources are cluster-wide
|
||||
Name: TPRName,
|
||||
},
|
||||
Versions: []extv1beta.APIVersion{
|
||||
{Name: constants.TPRApiVersion},
|
||||
},
|
||||
Description: constants.TPRDescription,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user