From 2c9c09ebfff33de7ea595763c1f06735d2869f7e Mon Sep 17 00:00:00 2001 From: Oleksii Kliukin Date: Mon, 11 Jun 2018 10:11:01 +0200 Subject: [PATCH] WIP: first commit --- pkg/cluster/cluster.go | 2 +- pkg/cluster/util.go | 2 +- pkg/controller/controller.go | 17 ++- pkg/controller/operator_config.go | 36 +++++++ pkg/controller/postgresql.go | 4 +- pkg/controller/util.go | 22 ++-- pkg/spec/postgresql.go | 2 +- pkg/util/config/config.go | 16 +-- pkg/util/config/crd_config.go | 165 ++++++++++++++++++++++++++++++ pkg/util/config/util.go | 10 +- pkg/util/constants/crd.go | 14 ++- 11 files changed, 258 insertions(+), 32 deletions(-) create mode 100644 pkg/controller/operator_config.go create mode 100644 pkg/util/config/crd_config.go diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 2662cd521..e162fc9a0 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -155,7 +155,7 @@ func (c *Cluster) setStatus(status spec.PostgresStatus) { _, err = c.KubeClient.CRDREST.Patch(types.MergePatchType). Namespace(c.Namespace). - Resource(constants.CRDResource). + Resource(constants.PostgresCRDResource). Name(c.Name). Body(request). DoRaw() diff --git a/pkg/cluster/util.go b/pkg/cluster/util.go index e7db26d82..ab8189d96 100644 --- a/pkg/cluster/util.go +++ b/pkg/cluster/util.go @@ -424,7 +424,7 @@ func (c *Cluster) credentialSecretNameForCluster(username string, clusterName st return c.OpConfig.SecretNameTemplate.Format( "username", strings.Replace(username, "_", "-", -1), "cluster", clusterName, - "tprkind", constants.CRDKind, + "tprkind", constants.PostgresCRDKind, "tprgroup", constants.CRDGroup) } diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 84a07811b..ee5ffb5ec 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -174,8 +174,21 @@ func (c *Controller) initController() { c.logger.Logger.Level = logrus.DebugLevel } - if err := c.createCRD(); err != nil { - c.logger.Fatalf("could not register CustomResourceDefinition: %v", err) + + if err := c.createPostgresCRD(); err != nil { + c.logger.Fatalf("could not register Postgres CustomResourceDefinition: %v", err) + } + + if err := c.createOperatorCRD(); err != nil { + c.logger.Fatalf("could not register Operator Configuration CustomResourceDefinition: %v", err) + } + + if configObjectName := os.Getenv("POSTGRES_OPERATOR_CONFIGURATION_OBJECT"); configObjectName != "" { + if config, err := c.readOperatorConfigurationFromCRD(configObjectName); err != nil { + c.logger.Fatalf("unable to read operator configuration: %v", err) + } else { + c.logger.Info("operator configuration: %#v", config) + } } if infraRoles, err := c.getInfrastructureRoles(&c.opConfig.InfrastructureRolesSecretName); err != nil { diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go new file mode 100644 index 000000000..6a36ef752 --- /dev/null +++ b/pkg/controller/operator_config.go @@ -0,0 +1,36 @@ +package controller + +import ( + "encoding/json" + "fmt" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/zalando-incubator/postgres-operator/pkg/util/constants" + + "github.com/zalando-incubator/postgres-operator/pkg/util/config" + "github.com/zalando-incubator/postgres-operator/pkg/spec" +) + + +func (c *Controller) readOperatorConfigurationFromCRD(configObjectName string) (*OperatorConfiguration, error){ + var ( + config OperatorConfiguration + ) + + req := c.KubeClient.CRDREST.Get(). + Name(configObjectName). + Namespace(c.opConfig.WatchedNamespace). + Resource(constants.OperatorConfigCRDResource). + VersionedParams(&metav1.ListOptions{ResourceVersion: "0"}, metav1.ParameterCodec) + + data, err := req.DoRaw(); + if err != nil { + return nil, fmt.Errorf("could not get operator configuration object %s: %v", configObjectName, err) + } + if err = json.Unmarshal(data, &config); err != nil { + return nil, fmt.Errorf("could not unmarshal operator configuration object %s, %v", configObjectName, err) + } + + return &config, nil +} \ No newline at end of file diff --git a/pkg/controller/postgresql.go b/pkg/controller/postgresql.go index cb689b70a..bf7fe8889 100644 --- a/pkg/controller/postgresql.go +++ b/pkg/controller/postgresql.go @@ -48,7 +48,7 @@ func (c *Controller) clusterListFunc(options metav1.ListOptions) (runtime.Object req := c.KubeClient.CRDREST. Get(). Namespace(c.opConfig.WatchedNamespace). - Resource(constants.CRDResource). + Resource(constants.PostgresCRDResource). VersionedParams(&options, metav1.ParameterCodec) b, err := req.DoRaw() @@ -117,7 +117,7 @@ func (c *Controller) clusterWatchFunc(options metav1.ListOptions) (watch.Interfa r, err := c.KubeClient.CRDREST. Get(). Namespace(c.opConfig.WatchedNamespace). - Resource(constants.CRDResource). + Resource(constants.PostgresCRDResource). VersionedParams(&options, metav1.ParameterCodec). FieldsSelectorParam(nil). Stream() diff --git a/pkg/controller/util.go b/pkg/controller/util.go index 5e46e93eb..11f6796b9 100644 --- a/pkg/controller/util.go +++ b/pkg/controller/util.go @@ -47,20 +47,20 @@ func (c *Controller) clusterWorkerID(clusterName spec.NamespacedName) uint32 { return c.clusterWorkers[clusterName] } -func (c *Controller) createCRD() error { +func (c *Controller) createZalandoCRD(plural, singular, short string) error { crd := &apiextv1beta1.CustomResourceDefinition{ ObjectMeta: metav1.ObjectMeta{ - Name: constants.CRDResource + "." + constants.CRDGroup, + Name: plural + "." + constants.CRDGroup, }, Spec: apiextv1beta1.CustomResourceDefinitionSpec{ Group: constants.CRDGroup, Version: constants.CRDApiVersion, Names: apiextv1beta1.CustomResourceDefinitionNames{ - Plural: constants.CRDResource, - Singular: constants.CRDKind, - ShortNames: []string{constants.CRDShort}, - Kind: constants.CRDKind, - ListKind: constants.CRDKind + "List", + Plural: plural, + Singular: singular, + ShortNames: []string{short}, + Kind: singular, + ListKind: singular + "List", }, Scope: apiextv1beta1.NamespaceScoped, }, @@ -98,6 +98,14 @@ func (c *Controller) createCRD() error { }) } +func (c *Controller) createPostgresCRD() error { + return c.createZalandoCRD(constants.PostgresCRDResource, constants.PostgresCRDKind, constants.PostgresCRDShort) +} + +func (c *Controller) createOperatorCRD() error { + return c.createZalandoCRD(constants.OperatorConfigCRDResource, constants.OperatorConfigCRDKind, constants.OperatorConfigCRDShort) +} + func readDecodedRole(s string) (*spec.PgUser, error) { var result spec.PgUser if err := yaml.Unmarshal([]byte(s), &result); err != nil { diff --git a/pkg/spec/postgresql.go b/pkg/spec/postgresql.go index bd5d06127..5b7a108d8 100644 --- a/pkg/spec/postgresql.go +++ b/pkg/spec/postgresql.go @@ -140,7 +140,7 @@ var ( ) // Clone makes a deepcopy of the Postgresql structure. The Error field is nulled-out, -// as there is no guaratee that the actual implementation of the error interface +// as there is no guarantee that the actual implementation of the error interface // will not contain any private fields not-reachable to deepcopy. This should be ok, // since Error is never read from a Kubernetes object. func (p *Postgresql) Clone() *Postgresql { diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 182bb077c..c3f135557 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -40,11 +40,11 @@ type Resources struct { // Auth describes authentication specific configuration parameters type Auth struct { - SecretNameTemplate stringTemplate `name:"secret_name_template" default:"{username}.{cluster}.credentials.{tprkind}.{tprgroup}"` - PamRoleName string `name:"pam_role_name" default:"zalandos"` - PamConfiguration string `name:"pam_configuration" default:"https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"` - TeamsAPIUrl string `name:"teams_api_url" default:"https://teams.example.com/api/"` - OAuthTokenSecretName spec.NamespacedName `name:"oauth_token_secret_name" default:"postgresql-operator"` + SecretNameTemplate StringTemplate `name:"secret_name_template" default:"{username}.{cluster}.credentials.{tprkind}.{tprgroup}"` + PamRoleName string `name:"pam_role_name" default:"zalandos"` + PamConfiguration string `name:"pam_configuration" default:"https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"` + TeamsAPIUrl string `name:"teams_api_url" default:"https://teams.example.com/api/"` + OAuthTokenSecretName spec.NamespacedName `name:"oauth_token_secret_name" default:"postgresql-operator"` InfrastructureRolesSecretName spec.NamespacedName `name:"infrastructure_roles_secret_name"` SuperUsername string `name:"super_username" default:"postgres"` ReplicationUsername string `name:"replication_username" default:"standby"` @@ -88,9 +88,9 @@ type Config struct { EnableReplicaLoadBalancer bool `name:"enable_replica_load_balancer" default:"false"` // deprecated and kept for backward compatibility EnableLoadBalancer *bool `name:"enable_load_balancer"` - MasterDNSNameFormat stringTemplate `name:"master_dns_name_format" default:"{cluster}.{team}.{hostedzone}"` - ReplicaDNSNameFormat stringTemplate `name:"replica_dns_name_format" default:"{cluster}-repl.{team}.{hostedzone}"` - PDBNameFormat stringTemplate `name:"pdb_name_format" default:"postgres-{cluster}-pdb"` + MasterDNSNameFormat StringTemplate `name:"master_dns_name_format" default:"{cluster}.{team}.{hostedzone}"` + ReplicaDNSNameFormat StringTemplate `name:"replica_dns_name_format" default:"{cluster}-repl.{team}.{hostedzone}"` + PDBNameFormat StringTemplate `name:"pdb_name_format" default:"postgres-{cluster}-pdb"` Workers uint32 `name:"workers" default:"4"` APIPort int `name:"api_port" default:"8080"` RingLogLines int `name:"ring_log_lines" default:"100"` diff --git a/pkg/util/config/crd_config.go b/pkg/util/config/crd_config.go new file mode 100644 index 000000000..a2ea6e7c9 --- /dev/null +++ b/pkg/util/config/crd_config.go @@ -0,0 +1,165 @@ +package config + +import ( + + "encoding/json" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/zalando-incubator/postgres-operator/pkg/spec" +) + +type OperatorConfiguration struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Configuration OperatorConfigurationData `json:"configuration"` + Error error `json:"-"` +} + + +type OperatorConfigurationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []OperatorConfiguration `json:"items"` +} + +type PostgresUsersConfiguration struct { + SuperUsername string `json:"super_username,omitempty"` + ReplicationUsername string `json:"replication_username,omitempty"` +} + +type KubernetesMetaConfiguration struct { + PodServiceAccountName string `json:"pod_service_account_name,omitempty"` + // TODO: change it to the proper json + PodServiceAccountDefinition string `json:"pod_service_account_definition,omitempty"` + PodTerminateGracePeriod time.Duration `json:"pod_terminate_grace_period,omitempty"` + WatchedNamespace string `json:"watched_namespace,omitempty"` + PDBNameFormat StringTemplate `json:"pdb_name_format,omitempty"` + SecretNameTemplate StringTemplate `json:"secret_name_template,omitempty"` + OAuthTokenSecretName spec.NamespacedName `json:"oauth_token_secret_name,omitempty"` + InfrastructureRolesSecretName spec.NamespacedName `json:"infrastructure_roles_secret_name,omitempty"` + PodRoleLabel string `json:"pod_role_label,omitempty"` + ClusterLabels map[string]string `json:"cluster_labels,omitempty"` + ClusterNameLabel string `json:"cluster_name_label,omitempty"` + NodeReadinessLabel map[string]string `json:"node_readiness_label,omitempty"` + // TODO: use a proper toleration structure? + PodToleration map[string]string `json:"toleration,omitempty"` + // TODO: use namespacedname + PodEnvironmentConfigMap string `json:"pod_environment_configmap,omitempty"` + +} + +type PostgresPodResourcesDefaults struct { + DefaultCPURequest string `json:"default_cpu_request,omitempty"` + DefaultMemoryRequest string `json:"default_memory_request,omitempty"` + DefaultCPULimit string `json:"default_cpu_limit,omitempty"` + DefaultMemoryLimit string `json:"default_memory_limit,omitempty"` +} + +type OperatorTimeouts struct { + ResourceCheckInterval time.Duration `json:"resource_check_interval,omitempty"` + ResourceCheckTimeout time.Duration `json:"resource_check_timeout,omitempty"` + PodLabelWaitTimeout time.Duration `json:"pod_label_wait_timeout,omitempty"` + PodDeletionWaitTimeout time.Duration `json:"pod_deletion_wait_timeout,omitempty"` + ReadyWaitInterval time.Duration `json:"ready_wait_interval,omitempty"` + ReadyWaitTimeout time.Duration `json:"ready_wait_timeout,omitempty"` +} + +type LoadBalancerConfiguration struct { + DbHostedZone string `json:"db_hosted_zone,omitempty"` + EnableMasterLoadBalancer bool `json:"enable_master_load_balancer,omitempty"` + EnableReplicaLoadBalancer bool `json:"enable_replica_load_balancer,omitempty"` + MasterDNSNameFormat StringTemplate `json:"master_dns_name_format,omitempty"` + ReplicaDNSNameFormat StringTemplate `json:"replica_dns_name_format,omitempty"` +} + +type AWSGCPConfiguration struct { + WALES3Bucket string `json:"wal_s3_bucket,omitempty"` + LogS3Bucket string `json:"log_s3_bucket,omitempty"` + KubeIAMRole string `json:"kube_iam_role,omitempty"` +} + +type OperatorDebugConfiguration struct { + DebugLogging bool `json:"debug_logging,omitempty"` + EnableDBAccess bool `json:"enable_database_access,omitempty"` +} + +type TeamsAPIConfiguration struct { + EnableTeamsAPI bool `json:"enable_teams_api,omitempty"` + TeamsAPIUrl string `json:"teams_api_url,omitempty"` + TeamAPIRoleConfiguration map[string]string `json:"team_api_role_configuration,omitempty"` + EnableTeamSuperuser bool `json:"enable_team_superuser,omitempty"` + TeamAdminRole string `json:"team_admin_role,omitempty"` + PamRoleName string `json:"pam_role_name,omitempty"` + PamConfiguration string `json:"pam_configuration,omitempty"` + ProtectedRoles []string `json:"protected_role_names,omitempty"` +} + +type LoggingRESTAPIConfiguration struct { + APIPort int `json:"api_port,omitempty"` + RingLogLines int `json:"ring_log_lines,omitempty"` + ClusterHistoryEntries int `json:"cluster_history_entries,omitempty"` +} + +type ScalyrConfiguration struct { + ScalyrAPIKey string `json:"scalyr_api_key,omitempty"` + ScalyrImage string `json:"scalyr_image,omitempty"` + ScalyrServerURL string `json:"scalyr_server_url,omitempty"` + ScalyrCPURequest string `json:"scalyr_cpu_request,omitempty"` + ScalyrMemoryRequest string `json:"scalyr_memory_request,omitempty"` + ScalyrCPULimit string `json:"scalyr_cpu_limit,omitempty"` + ScalyrMemoryLimit string `json:"scalyr_memory_limit,omitempty"` +} + +type OperatorConfigurationData struct { + EtcdHost string `json:"etcd_host,omitempty"` + DockerImage string `json:"docker_image,omitempty"` + Workers int `json:"workers,omitempty"` + MinInstances int32 `json:"min_instances,omitempty"` + MaxInstances int32 `json:"max_instances,omitempty"` + ResyncPeriod time.Duration `json:"resync_period,omitempty"` + PostgresUsersConfiguration PostgresUsersConfiguration `json:"users"` + Kubernetes KubernetesMetaConfiguration `json:"kubernetes"` + PostgresPodResources PostgresPodResourcesDefaults `json:"postgres_pod_resources"` + Timeouts OperatorTimeouts `json:"timeouts"` + LoadBalancer LoadBalancerConfiguration `json:"load_balancer"` + AWSGCP AWSGCPConfiguration `json:"aws_or_gcp"` + OperatorDebug OperatorDebugConfiguration `json:"debug"` + TeamsAPI TeamsAPIConfiguration `json:"teams_api"` + LoggingRESTAPI LoggingRESTAPIConfiguration `json:"logging_rest_api"` + Scalyr ScalyrConfiguration `json:"scalyr"` +} + + +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"` +} + +type OperatorConfigurationCopy OperatorConfiguration +type OperatorConfigurationListCopy OperatorConfigurationList + + +func (opc *OperatorConfiguration) UnmarshalJSON(data []byte) error { + var ref OperatorConfigurationCopy + if err := json.Unmarshal(data, &ref); err != nil { + return err + } + *opc = OperatorConfiguration(ref) + return nil +} + +func (opcl *OperatorConfigurationList) UnmarshalJSON(data []byte) error { + var ref OperatorConfigurationListCopy + if err := json.Unmarshal(data, &ref); err != nil { + return nil + } + *opcl = OperatorConfigurationList(ref) + return nil +} + diff --git a/pkg/util/config/util.go b/pkg/util/config/util.go index dbe411045..f3394a387 100644 --- a/pkg/util/config/util.go +++ b/pkg/util/config/util.go @@ -19,7 +19,7 @@ type fieldInfo struct { Field reflect.Value } -type stringTemplate string +type StringTemplate string func decoderFrom(field reflect.Value) (d decoder) { // it may be impossible for a struct field to fail this check @@ -222,13 +222,13 @@ func getMapPairsFromString(value string) (pairs []string, err error) { return } -func (f *stringTemplate) Decode(value string) error { - *f = stringTemplate(value) +func (f *StringTemplate) Decode(value string) error { + *f = StringTemplate(value) return nil } -func (f *stringTemplate) Format(a ...string) string { +func (f *StringTemplate) Format(a ...string) string { res := string(*f) for i := 0; i < len(a); i += 2 { @@ -238,6 +238,6 @@ func (f *stringTemplate) Format(a ...string) string { return res } -func (f stringTemplate) MarshalJSON() ([]byte, error) { +func (f StringTemplate) MarshalJSON() ([]byte, error) { return json.Marshal(string(f)) } diff --git a/pkg/util/constants/crd.go b/pkg/util/constants/crd.go index 94db5033a..f3522ba61 100644 --- a/pkg/util/constants/crd.go +++ b/pkg/util/constants/crd.go @@ -2,9 +2,13 @@ package constants // Different properties of the PostgreSQL Custom Resource Definition const ( - CRDKind = "postgresql" - CRDResource = "postgresqls" - CRDShort = "pg" - CRDGroup = "acid.zalan.do" - CRDApiVersion = "v1" + PostgresCRDKind = "postgresql" + PostgresCRDResource = "postgresqls" + PostgresCRDShort = "pg" + CRDGroup = "acid.zalan.do" + CRDApiVersion = "v1" + OperatorConfigCRDKind = "postgresql-operator-configuration" + OperatorConfigCRDResource = "postgresql-operator-configurations" + OperatorConfigCRDShort = "pgopconfig" + )