Use ConfigMap to store operator's config
This commit is contained in:
parent
47e3e29a56
commit
da438aab3a
34
cmd/main.go
34
cmd/main.go
|
|
@ -9,6 +9,7 @@ import (
|
|||
"syscall"
|
||||
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/controller"
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/util/config"
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/util/k8sutil"
|
||||
)
|
||||
|
|
@ -16,9 +17,9 @@ import (
|
|||
var (
|
||||
KubeConfigFile string
|
||||
podNamespace string
|
||||
configMapName spec.NamespacedName
|
||||
OutOfCluster bool
|
||||
version string
|
||||
cfg *config.Config
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -27,11 +28,14 @@ func init() {
|
|||
flag.Parse()
|
||||
|
||||
podNamespace = os.Getenv("MY_POD_NAMESPACE")
|
||||
if len(podNamespace) == 0 {
|
||||
if podNamespace == "" {
|
||||
podNamespace = "default"
|
||||
}
|
||||
|
||||
cfg = config.LoadFromEnv()
|
||||
configMap := os.Getenv("CONFIG_MAP_NAME")
|
||||
if configMap != "" {
|
||||
configMapName.Decode(configMap)
|
||||
}
|
||||
}
|
||||
|
||||
func ControllerConfig() *controller.Config {
|
||||
|
|
@ -48,16 +52,15 @@ func ControllerConfig() *controller.Config {
|
|||
restClient, err := k8sutil.KubernetesRestClient(restConfig)
|
||||
|
||||
return &controller.Config{
|
||||
PodNamespace: podNamespace, //TODO: move to config.Config
|
||||
KubeClient: client,
|
||||
RestClient: restClient,
|
||||
KubeClient: client,
|
||||
RestClient: restClient,
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
configMapData := make(map[string]string)
|
||||
log.SetOutput(os.Stdout)
|
||||
log.Printf("Spilo operator %s\n", version)
|
||||
log.Printf("Config: %s", cfg.MustMarshal())
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
stop := make(chan struct{})
|
||||
|
|
@ -67,6 +70,23 @@ func main() {
|
|||
|
||||
controllerConfig := ControllerConfig()
|
||||
|
||||
if configMapName != (spec.NamespacedName{}) {
|
||||
configMap, err := controllerConfig.KubeClient.ConfigMaps(configMapName.Namespace).Get(configMapName.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configMapData = configMap.Data
|
||||
} else {
|
||||
log.Printf("No ConfigMap specified. Loading default values")
|
||||
}
|
||||
if configMapData["namespace"] == "" { // Namespace in ConfigMap has priority over env var
|
||||
configMapData["namespace"] = podNamespace
|
||||
}
|
||||
cfg := config.NewFromMap(configMapData)
|
||||
|
||||
log.Printf("Config: %s", cfg.MustMarshal())
|
||||
|
||||
c := controller.New(controllerConfig, cfg)
|
||||
c.Run(stop, wg)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
hash: 9cb22736d27cb74bb0f674c5e57954c8fd14e4b5cae0b2e22ead9f10728f91f8
|
||||
updated: 2017-04-10T15:33:31.332520218+02:00
|
||||
hash: de07f359cf295197b1663e28cb7a9652c1bb4b9ddca7e70e4e605410b43e4e5f
|
||||
updated: 2017-04-19T11:22:12.486616201+02:00
|
||||
imports:
|
||||
- name: cloud.google.com/go
|
||||
version: 3b1ae45394a234c385be014e9a488f2bb6eef821
|
||||
|
|
@ -75,12 +75,10 @@ imports:
|
|||
version: 2eee05ed794112d45db504eb05aa693efd2b8b09
|
||||
- name: github.com/juju/ratelimit
|
||||
version: 77ed1c8a01217656d2080ad51981f6e99adaa177
|
||||
- name: github.com/kelseyhightower/envconfig
|
||||
version: 8bf4bbfc795e2c7c8a5ea47b707453ed019e2ad4
|
||||
- name: github.com/kr/text
|
||||
version: 7cafcd837844e784b526369c9bce262804aebc60
|
||||
- name: github.com/lib/pq
|
||||
version: 0477eb88c5ca4009cb281f13c90633375b6a9987
|
||||
version: 2704adc878c21e1329f46f6e56a1c387d788ff94
|
||||
subpackages:
|
||||
- oid
|
||||
- name: github.com/mailru/easyjson
|
||||
|
|
|
|||
|
|
@ -27,5 +27,4 @@ import:
|
|||
- rest
|
||||
- tools/cache
|
||||
- tools/clientcmd
|
||||
- package: github.com/kelseyhightower/envconfig
|
||||
- package: github.com/motomux/pretty
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: postgres-operator
|
||||
data:
|
||||
service_account_name: operator
|
||||
cluster_labels: application:spilo
|
||||
cluster_name_label: version
|
||||
pod_role_label: spilo-role
|
||||
db_hosted_zone: db.example.com
|
||||
dns_name_format: '%s.%s.staging.%s'
|
||||
docker_image: registry.opensource.zalan.do/acid/spilo-9.6:1.2-p12
|
||||
etcd_host: etcd-client.default.svc.cluster.local:2379
|
||||
infrastructure_roles_secret_name: postgresql-infrastructure-roles
|
||||
oauth_token_secret_name: postgresql-operator
|
||||
pam_configuration: |
|
||||
https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees
|
||||
pam_role_name: zalandos
|
||||
pod_deletion_wait_timeout: 10m
|
||||
pod_label_wait_timeout: 10m
|
||||
ready_wait_interval: 3s
|
||||
ready_wait_timeout: 30s
|
||||
replication_username: replication
|
||||
resource_check_interval: 3s
|
||||
resource_check_timeout: 10m
|
||||
resync_period: 5m
|
||||
resync_period_pod: 5m
|
||||
super_username: postgres
|
||||
teams_api_url: https://teams.example.com/api/
|
||||
|
|
@ -14,49 +14,9 @@ spec:
|
|||
- name: postgres-operator
|
||||
image: pierone.example.com/acid/postgres-operator:0.1
|
||||
env:
|
||||
- name: MY_POD_NAMESPACE #TODO: use PGOP_ prefix
|
||||
- name: MY_POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: PGOP_SERVICE_ACCOUNT_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.serviceAccountName
|
||||
- name: PGOP_READY_WAIT_INTERVAL
|
||||
value: "3s"
|
||||
- name: PGOP_READY_WAIT_TIMEOUT
|
||||
value: "30s"
|
||||
- name: PGOP_RESYNC_PERIOD
|
||||
value: "5m"
|
||||
- name: PGOP_RESYNC_PERIOD_POD
|
||||
value: "5m"
|
||||
- name: PGOP_RESOURCE_CHECK_INTERVAL
|
||||
value: "3s"
|
||||
- name: PGOP_RESOURCE_CHECK_TIMEOUT
|
||||
value: "10m"
|
||||
- name: PGOP_POD_LABEL_WAIT_TIMEOUT
|
||||
value: "10m"
|
||||
- name: PGOP_POD_DELETION_WAIT_TIMEOUT
|
||||
value: "10m"
|
||||
- name: PGOP_PAM_ROLE_NAME
|
||||
value: "zalandos"
|
||||
- name: PGOP_PAM_CONFIGURATION
|
||||
value: "https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"
|
||||
- name: PGOP_TEAMS_API_URL
|
||||
value: "https://teams.example.com/api/"
|
||||
- name: PGOP_OAUTH_TOKEN_SECRET_NAME
|
||||
value: "postgresql-operator"
|
||||
- name: PGOP_SUPER_USERNAME
|
||||
value: "postgres"
|
||||
- name: PGOP_REPLICATION_USERNAME
|
||||
value: "replication"
|
||||
- name: PGOP_ETCD_HOST
|
||||
value: "etcd-client.default.svc.cluster.local:2379"
|
||||
- name: PGOP_DOCKER_IMAGE
|
||||
value: "registry.opensource.zalan.do/acid/spilo-9.6:1.2-p12"
|
||||
- name: PGOP_DB_HOSTED_ZONE
|
||||
value: "db.example.com"
|
||||
- name: PGOP_DNS_NAME_FORMAT
|
||||
value: "%s.%s.staging.%s"
|
||||
- name: PGOP_INFRASTRUCTURE_ROLES_SECRET_NAME
|
||||
value: "postgresql-infrastructure-roles"
|
||||
- name: CONFIG_MAP_NAME
|
||||
value: "postgres-operator"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ apiVersion: "acid.zalan.do/v1"
|
|||
kind: "Postgresql"
|
||||
|
||||
metadata:
|
||||
name: testcluster
|
||||
name: acid-testcluster
|
||||
spec:
|
||||
teamId: "ACID"
|
||||
volume:
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ func (c *Cluster) recreatePods() error {
|
|||
|
||||
var masterPod v1.Pod
|
||||
for _, pod := range pods.Items {
|
||||
role := util.PodSpiloRole(&pod)
|
||||
role := c.PodSpiloRole(&pod)
|
||||
|
||||
if role == constants.PodRoleMaster {
|
||||
masterPod = pod
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ func (c *Cluster) waitForPodLabel(podEvents chan spec.PodEvent) error {
|
|||
for {
|
||||
select {
|
||||
case podEvent := <-podEvents:
|
||||
role := util.PodSpiloRole(podEvent.CurPod)
|
||||
role := c.PodSpiloRole(podEvent.CurPod)
|
||||
// We cannot assume any role of the newly created pod. Normally, for a multi-pod cluster
|
||||
// we should observe the 'replica' value, but it could be that some pods are not allowed
|
||||
// to promote, therefore, the new pod could be a master as well.
|
||||
|
|
@ -156,10 +156,14 @@ func (c *Cluster) waitPodLabelsReady() error {
|
|||
LabelSelector: ls.String(),
|
||||
}
|
||||
masterListOption := v1.ListOptions{
|
||||
LabelSelector: labels.Merge(ls, labels.Set{constants.SpiloRoleLabel: constants.PodRoleMaster}).String(),
|
||||
LabelSelector: labels.Merge(ls, labels.Set{
|
||||
c.OpConfig.PodRoleLabel: constants.PodRoleMaster,
|
||||
}).String(),
|
||||
}
|
||||
replicaListOption := v1.ListOptions{
|
||||
LabelSelector: labels.Merge(ls, labels.Set{constants.SpiloRoleLabel: constants.PodRoleReplica}).String(),
|
||||
LabelSelector: labels.Merge(ls, labels.Set{
|
||||
c.OpConfig.PodRoleLabel: constants.PodRoleReplica,
|
||||
}).String(),
|
||||
}
|
||||
pods, err := c.KubeClient.Pods(namespace).List(listOptions)
|
||||
if err != nil {
|
||||
|
|
@ -203,10 +207,10 @@ func (c *Cluster) waitStatefulsetPodsReady() error {
|
|||
}
|
||||
|
||||
func (c *Cluster) labelsSet() labels.Set {
|
||||
return labels.Set{
|
||||
constants.ApplicationNameLabel: constants.ApplicationNameLabelValue,
|
||||
constants.ClusterNameLabel: c.Metadata.Name,
|
||||
}
|
||||
lbls := c.OpConfig.ClusterLabels
|
||||
lbls[c.OpConfig.ClusterNameLabel] = c.Metadata.Name
|
||||
|
||||
return labels.Set(lbls)
|
||||
}
|
||||
|
||||
func (c *Cluster) dnsName() string {
|
||||
|
|
@ -228,7 +232,7 @@ func (c *Cluster) credentialSecretName(username string) string {
|
|||
}
|
||||
|
||||
func (c *Cluster) deleteEtcdKey() error {
|
||||
etcdKey := fmt.Sprintf("/service/%s", c.Metadata.Name)
|
||||
etcdKey := fmt.Sprintf("/%s/%s", c.OpConfig.EtcdScope, c.Metadata.Name)
|
||||
|
||||
//TODO: retry multiple times
|
||||
resp, err := c.EtcdClient.Delete(context.Background(),
|
||||
|
|
@ -245,3 +249,7 @@ func (c *Cluster) deleteEtcdKey() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cluster) PodSpiloRole(pod *v1.Pod) string {
|
||||
return pod.Labels[c.OpConfig.PodRoleLabel]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import (
|
|||
)
|
||||
|
||||
type Config struct {
|
||||
PodNamespace string
|
||||
KubeClient *kubernetes.Clientset
|
||||
RestClient *rest.RESTClient
|
||||
EtcdClient etcdclient.KeysAPI
|
||||
|
|
@ -63,7 +62,6 @@ func (c *Controller) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) {
|
|||
|
||||
c.initController()
|
||||
|
||||
c.logger.Infof("'%s' namespace will be watched", c.PodNamespace)
|
||||
go c.runInformers(stopCh)
|
||||
|
||||
c.logger.Info("Started working in background")
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ func (c *Controller) podListFunc(options api.ListOptions) (runtime.Object, error
|
|||
TimeoutSeconds: options.TimeoutSeconds,
|
||||
}
|
||||
|
||||
return c.KubeClient.CoreV1().Pods(c.PodNamespace).List(opts)
|
||||
return c.KubeClient.CoreV1().Pods(c.opConfig.Namespace).List(opts)
|
||||
}
|
||||
|
||||
func (c *Controller) podWatchFunc(options api.ListOptions) (watch.Interface, error) {
|
||||
|
|
@ -52,7 +52,7 @@ func (c *Controller) podWatchFunc(options api.ListOptions) (watch.Interface, err
|
|||
TimeoutSeconds: options.TimeoutSeconds,
|
||||
}
|
||||
|
||||
return c.KubeClient.CoreV1Client.Pods(c.PodNamespace).Watch(opts)
|
||||
return c.KubeClient.CoreV1Client.Pods(c.opConfig.Namespace).Watch(opts)
|
||||
}
|
||||
|
||||
func (c *Controller) podAdd(obj interface{}) {
|
||||
|
|
@ -62,7 +62,7 @@ func (c *Controller) podAdd(obj interface{}) {
|
|||
}
|
||||
|
||||
podEvent := spec.PodEvent{
|
||||
ClusterName: util.PodClusterName(pod),
|
||||
ClusterName: c.PodClusterName(pod),
|
||||
PodName: util.NameFromMeta(pod.ObjectMeta),
|
||||
CurPod: pod,
|
||||
EventType: spec.PodEventAdd,
|
||||
|
|
@ -83,7 +83,7 @@ func (c *Controller) podUpdate(prev, cur interface{}) {
|
|||
}
|
||||
|
||||
podEvent := spec.PodEvent{
|
||||
ClusterName: util.PodClusterName(curPod),
|
||||
ClusterName: c.PodClusterName(curPod),
|
||||
PodName: util.NameFromMeta(curPod.ObjectMeta),
|
||||
PrevPod: prevPod,
|
||||
CurPod: curPod,
|
||||
|
|
@ -100,7 +100,7 @@ func (c *Controller) podDelete(obj interface{}) {
|
|||
}
|
||||
|
||||
podEvent := spec.PodEvent{
|
||||
ClusterName: util.PodClusterName(pod),
|
||||
ClusterName: c.PodClusterName(pod),
|
||||
PodName: util.NameFromMeta(pod.ObjectMeta),
|
||||
CurPod: pod,
|
||||
EventType: spec.PodEventDelete,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
func (c *Controller) clusterListFunc(options api.ListOptions) (runtime.Object, error) {
|
||||
c.logger.Info("Getting list of currently running clusters")
|
||||
object, err := c.RestClient.Get().
|
||||
Namespace(c.PodNamespace).
|
||||
Namespace(c.opConfig.Namespace).
|
||||
Resource(constants.ResourceName).
|
||||
VersionedParams(&options, api.ParameterCodec).
|
||||
FieldsSelectorParam(fields.Everything()).
|
||||
|
|
@ -65,7 +65,7 @@ func (c *Controller) clusterListFunc(options api.ListOptions) (runtime.Object, e
|
|||
func (c *Controller) clusterWatchFunc(options api.ListOptions) (watch.Interface, error) {
|
||||
return c.RestClient.Get().
|
||||
Prefix("watch").
|
||||
Namespace(c.PodNamespace).
|
||||
Namespace(c.opConfig.Namespace).
|
||||
Resource(constants.ResourceName).
|
||||
VersionedParams(&options, api.ParameterCodec).
|
||||
FieldsSelectorParam(fields.Everything()).
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ func (c *Controller) createTPR() error {
|
|||
|
||||
restClient := c.RestClient
|
||||
|
||||
return k8sutil.WaitTPRReady(restClient, c.opConfig.TPR.ReadyWaitInterval, c.opConfig.TPR.ReadyWaitTimeout, c.PodNamespace)
|
||||
return k8sutil.WaitTPRReady(restClient, c.opConfig.TPR.ReadyWaitInterval, c.opConfig.TPR.ReadyWaitTimeout, c.opConfig.Namespace)
|
||||
}
|
||||
|
||||
func (c *Controller) getInfrastructureRoles() (result map[string]spec.PgUser, err error) {
|
||||
|
|
@ -125,3 +125,14 @@ Users:
|
|||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *Controller) PodClusterName(pod *v1.Pod) spec.NamespacedName {
|
||||
if name, ok := pod.Labels[c.opConfig.ClusterNameLabel]; ok {
|
||||
return spec.NamespacedName{
|
||||
Namespace: pod.Namespace,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
return spec.NamespacedName{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,64 +2,53 @@ package config
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
|
||||
"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"`
|
||||
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:"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"`
|
||||
ResyncPeriodPod time.Duration `name:"resync_period_pod" default:"5m"`
|
||||
ResourceCheckInterval time.Duration `name:"resource_check_interval" default:"3s"`
|
||||
ResourceCheckTimeout time.Duration `name:"resource_check_timeout" default:"10m"`
|
||||
PodLabelWaitTimeout time.Duration `name:"pod_label_wait_timeout" default:"10m"`
|
||||
PodDeletionWaitTimeout time.Duration `name:"pod_deletion_wait_timeout" default:"10m"`
|
||||
ClusterLabels map[string]string `name:"cluster_labels" default:"application:spilo"`
|
||||
ClusterNameLabel string `name:"cluster_name_label" default:"cluster-name"`
|
||||
PodRoleLabel string `name:"pod_role_label" default:"spilo-role"`
|
||||
}
|
||||
|
||||
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 spec.NamespacedName `envconfig:"oauth_token_secret_name" default:"postgresql-operator"`
|
||||
InfrastructureRolesSecretName spec.NamespacedName `split_words:"true"`
|
||||
SuperUsername string `split_words:"true" default:"postgres"`
|
||||
ReplicationUsername string `split_words:"true" default:"replication"`
|
||||
PamRoleName string `name:"pam_rol_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:"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"`
|
||||
EtcdScope string `split_words:"true" default:"service"`
|
||||
WALES3Bucket string `envconfig:"wal_s3_bucket"`
|
||||
KubeIAMRole string `envconfig:"kube_iam_role"`
|
||||
DebugLogging bool `split_words:"true" default:"false"`
|
||||
DNSNameFormat string `envconfig:"dns_name_format" default:"%s.%s.%s"`
|
||||
}
|
||||
|
||||
func LoadFromEnv() *Config {
|
||||
//TODO: maybe we should use ConfigMaps( https://kubernetes.io/docs/tasks/configure-pod-container/configmap/ ) instead?
|
||||
|
||||
var cfg Config
|
||||
err := envconfig.Process("PGOP", &cfg)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Can't read config: %v", err))
|
||||
}
|
||||
cfg.EtcdScope = strings.Trim(cfg.EtcdScope, "/")
|
||||
|
||||
return &cfg
|
||||
Namespace string `name:"namespace"`
|
||||
EtcdHost string `name:"etcd_host" default:"etcd-client.default.svc.cluster.local:2379"`
|
||||
DockerImage string `name:"docker_image" default:"registry.opensource.zalan.do/acid/spilo-9.6:1.2-p12"`
|
||||
ServiceAccountName string `name:"service_account_name" default:"operator"`
|
||||
DbHostedZone string `name:"db_hosted_zone" default:"db.example.com"`
|
||||
EtcdScope string `name:"etcd_scope" default:"service"`
|
||||
WALES3Bucket string `name:"wal_s3_bucket"`
|
||||
KubeIAMRole string `name:"kube_iam_role"`
|
||||
DebugLogging bool `name:"debug_logging" default:"false"`
|
||||
DNSNameFormat string `name:"dns_name_format" default:"%s.%s.%s"`
|
||||
}
|
||||
|
||||
func (c Config) MustMarshal() string {
|
||||
|
|
@ -70,3 +59,23 @@ func (c Config) MustMarshal() string {
|
|||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func NewFromMap(m map[string]string) *Config {
|
||||
cfg := Config{}
|
||||
fields, _ := structFields(&cfg)
|
||||
|
||||
for _, structField := range fields {
|
||||
key := strings.ToLower(structField.Name)
|
||||
value, ok := m[key]
|
||||
if !ok && structField.Default != "" {
|
||||
value = structField.Default
|
||||
}
|
||||
|
||||
err := processField(value, structField.Field)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return &cfg
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,164 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Decoder interface {
|
||||
Decode(value string) error
|
||||
}
|
||||
|
||||
type fieldInfo struct {
|
||||
Name string
|
||||
Default string
|
||||
Field reflect.Value
|
||||
}
|
||||
|
||||
func decoderFrom(field reflect.Value) (d Decoder) {
|
||||
// it may be impossible for a struct field to fail this check
|
||||
if !field.CanInterface() {
|
||||
return
|
||||
}
|
||||
|
||||
d, ok := field.Interface().(Decoder)
|
||||
if !ok && field.CanAddr() {
|
||||
d, ok = field.Addr().Interface().(Decoder)
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// taken from github.com/kelseyhightower/envconfig
|
||||
func structFields(spec interface{}) ([]fieldInfo, error) {
|
||||
s := reflect.ValueOf(spec).Elem()
|
||||
|
||||
// over allocate an info array, we will extend if needed later
|
||||
infos := make([]fieldInfo, 0, s.NumField())
|
||||
for i := 0; i < s.NumField(); i++ {
|
||||
f := s.Field(i)
|
||||
ftype := s.Type().Field(i)
|
||||
|
||||
fieldName := ftype.Tag.Get("name")
|
||||
if fieldName == "" {
|
||||
fieldName = strings.ToLower(ftype.Name)
|
||||
}
|
||||
|
||||
// Capture information about the config variable
|
||||
info := fieldInfo{
|
||||
Name: fieldName,
|
||||
Field: f,
|
||||
Default: ftype.Tag.Get("default"),
|
||||
}
|
||||
infos = append(infos, info)
|
||||
|
||||
if f.Kind() == reflect.Struct {
|
||||
// honor Decode if present
|
||||
if decoderFrom(f) == nil {
|
||||
embeddedPtr := f.Addr().Interface()
|
||||
embeddedInfos, err := structFields(embeddedPtr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infos = append(infos[:len(infos)-1], embeddedInfos...)
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func processField(value string, field reflect.Value) error {
|
||||
typ := field.Type()
|
||||
|
||||
decoder := decoderFrom(field)
|
||||
if decoder != nil {
|
||||
return decoder.Decode(value)
|
||||
}
|
||||
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
if field.IsNil() {
|
||||
field.Set(reflect.New(typ))
|
||||
}
|
||||
field = field.Elem()
|
||||
}
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.String:
|
||||
field.SetString(value)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
var (
|
||||
val int64
|
||||
err error
|
||||
)
|
||||
if field.Kind() == reflect.Int64 && typ.PkgPath() == "time" && typ.Name() == "Duration" {
|
||||
var d time.Duration
|
||||
d, err = time.ParseDuration(value)
|
||||
val = int64(d)
|
||||
} else {
|
||||
val, err = strconv.ParseInt(value, 0, typ.Bits())
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
field.SetInt(val)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
val, err := strconv.ParseUint(value, 0, typ.Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetUint(val)
|
||||
case reflect.Bool:
|
||||
val, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetBool(val)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
val, err := strconv.ParseFloat(value, typ.Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetFloat(val)
|
||||
case reflect.Slice:
|
||||
vals := strings.Split(value, ",")
|
||||
sl := reflect.MakeSlice(typ, len(vals), len(vals))
|
||||
for i, val := range vals {
|
||||
err := processField(val, sl.Index(i))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
field.Set(sl)
|
||||
case reflect.Map:
|
||||
pairs := strings.Split(value, ",")
|
||||
mp := reflect.MakeMap(typ)
|
||||
for _, pair := range pairs {
|
||||
kvpair := strings.Split(pair, ":")
|
||||
if len(kvpair) != 2 {
|
||||
return fmt.Errorf("invalid map item: %q", pair)
|
||||
}
|
||||
k := reflect.New(typ.Key()).Elem()
|
||||
err := processField(kvpair[0], k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := reflect.New(typ.Elem()).Elem()
|
||||
err = processField(kvpair[1], v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mp.SetMapIndex(k, v)
|
||||
}
|
||||
field.Set(mp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -2,20 +2,16 @@ package constants
|
|||
|
||||
const (
|
||||
//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"
|
||||
KubeIAmAnnotation = "iam.amazonaws.com/role"
|
||||
ResourceName = TPRName + "s"
|
||||
PodRoleMaster = "master"
|
||||
PodRoleReplica = "replica"
|
||||
ApplicationNameLabel = "application"
|
||||
ApplicationNameLabelValue = "spilo"
|
||||
SpiloRoleLabel = "spilo-role"
|
||||
ClusterNameLabel = "version"
|
||||
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"
|
||||
KubeIAmAnnotation = "iam.amazonaws.com/role"
|
||||
ResourceName = TPRName + "s"
|
||||
PodRoleMaster = "master"
|
||||
PodRoleReplica = "replica"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
"k8s.io/client-go/pkg/api/v1"
|
||||
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/util/constants"
|
||||
)
|
||||
|
||||
var passwordChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
|
|
@ -37,21 +36,6 @@ func NameFromMeta(meta v1.ObjectMeta) spec.NamespacedName {
|
|||
}
|
||||
}
|
||||
|
||||
func PodClusterName(pod *v1.Pod) spec.NamespacedName {
|
||||
if name, ok := pod.Labels[constants.ClusterNameLabel]; ok {
|
||||
return spec.NamespacedName{
|
||||
Namespace: pod.Namespace,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
return spec.NamespacedName{}
|
||||
}
|
||||
|
||||
func PodSpiloRole(pod *v1.Pod) string {
|
||||
return pod.Labels[constants.SpiloRoleLabel]
|
||||
}
|
||||
|
||||
func PGUserPassword(user spec.PgUser) string {
|
||||
s := md5.Sum([]byte(user.Password + user.Name))
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue