Use ConfigMap to store operator's config
This commit is contained in:
parent
47e3e29a56
commit
da438aab3a
30
cmd/main.go
30
cmd/main.go
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.bus.zalan.do/acid/postgres-operator/pkg/controller"
|
"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/config"
|
||||||
"github.bus.zalan.do/acid/postgres-operator/pkg/util/k8sutil"
|
"github.bus.zalan.do/acid/postgres-operator/pkg/util/k8sutil"
|
||||||
)
|
)
|
||||||
|
|
@ -16,9 +17,9 @@ import (
|
||||||
var (
|
var (
|
||||||
KubeConfigFile string
|
KubeConfigFile string
|
||||||
podNamespace string
|
podNamespace string
|
||||||
|
configMapName spec.NamespacedName
|
||||||
OutOfCluster bool
|
OutOfCluster bool
|
||||||
version string
|
version string
|
||||||
cfg *config.Config
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -27,11 +28,14 @@ func init() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
podNamespace = os.Getenv("MY_POD_NAMESPACE")
|
podNamespace = os.Getenv("MY_POD_NAMESPACE")
|
||||||
if len(podNamespace) == 0 {
|
if podNamespace == "" {
|
||||||
podNamespace = "default"
|
podNamespace = "default"
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg = config.LoadFromEnv()
|
configMap := os.Getenv("CONFIG_MAP_NAME")
|
||||||
|
if configMap != "" {
|
||||||
|
configMapName.Decode(configMap)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ControllerConfig() *controller.Config {
|
func ControllerConfig() *controller.Config {
|
||||||
|
|
@ -48,16 +52,15 @@ func ControllerConfig() *controller.Config {
|
||||||
restClient, err := k8sutil.KubernetesRestClient(restConfig)
|
restClient, err := k8sutil.KubernetesRestClient(restConfig)
|
||||||
|
|
||||||
return &controller.Config{
|
return &controller.Config{
|
||||||
PodNamespace: podNamespace, //TODO: move to config.Config
|
|
||||||
KubeClient: client,
|
KubeClient: client,
|
||||||
RestClient: restClient,
|
RestClient: restClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
configMapData := make(map[string]string)
|
||||||
log.SetOutput(os.Stdout)
|
log.SetOutput(os.Stdout)
|
||||||
log.Printf("Spilo operator %s\n", version)
|
log.Printf("Spilo operator %s\n", version)
|
||||||
log.Printf("Config: %s", cfg.MustMarshal())
|
|
||||||
|
|
||||||
sigs := make(chan os.Signal, 1)
|
sigs := make(chan os.Signal, 1)
|
||||||
stop := make(chan struct{})
|
stop := make(chan struct{})
|
||||||
|
|
@ -67,6 +70,23 @@ func main() {
|
||||||
|
|
||||||
controllerConfig := ControllerConfig()
|
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 := controller.New(controllerConfig, cfg)
|
||||||
c.Run(stop, wg)
|
c.Run(stop, wg)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
hash: 9cb22736d27cb74bb0f674c5e57954c8fd14e4b5cae0b2e22ead9f10728f91f8
|
hash: de07f359cf295197b1663e28cb7a9652c1bb4b9ddca7e70e4e605410b43e4e5f
|
||||||
updated: 2017-04-10T15:33:31.332520218+02:00
|
updated: 2017-04-19T11:22:12.486616201+02:00
|
||||||
imports:
|
imports:
|
||||||
- name: cloud.google.com/go
|
- name: cloud.google.com/go
|
||||||
version: 3b1ae45394a234c385be014e9a488f2bb6eef821
|
version: 3b1ae45394a234c385be014e9a488f2bb6eef821
|
||||||
|
|
@ -75,12 +75,10 @@ imports:
|
||||||
version: 2eee05ed794112d45db504eb05aa693efd2b8b09
|
version: 2eee05ed794112d45db504eb05aa693efd2b8b09
|
||||||
- name: github.com/juju/ratelimit
|
- name: github.com/juju/ratelimit
|
||||||
version: 77ed1c8a01217656d2080ad51981f6e99adaa177
|
version: 77ed1c8a01217656d2080ad51981f6e99adaa177
|
||||||
- name: github.com/kelseyhightower/envconfig
|
|
||||||
version: 8bf4bbfc795e2c7c8a5ea47b707453ed019e2ad4
|
|
||||||
- name: github.com/kr/text
|
- name: github.com/kr/text
|
||||||
version: 7cafcd837844e784b526369c9bce262804aebc60
|
version: 7cafcd837844e784b526369c9bce262804aebc60
|
||||||
- name: github.com/lib/pq
|
- name: github.com/lib/pq
|
||||||
version: 0477eb88c5ca4009cb281f13c90633375b6a9987
|
version: 2704adc878c21e1329f46f6e56a1c387d788ff94
|
||||||
subpackages:
|
subpackages:
|
||||||
- oid
|
- oid
|
||||||
- name: github.com/mailru/easyjson
|
- name: github.com/mailru/easyjson
|
||||||
|
|
|
||||||
|
|
@ -27,5 +27,4 @@ import:
|
||||||
- rest
|
- rest
|
||||||
- tools/cache
|
- tools/cache
|
||||||
- tools/clientcmd
|
- tools/clientcmd
|
||||||
- package: github.com/kelseyhightower/envconfig
|
|
||||||
- package: github.com/motomux/pretty
|
- 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
|
- name: postgres-operator
|
||||||
image: pierone.example.com/acid/postgres-operator:0.1
|
image: pierone.example.com/acid/postgres-operator:0.1
|
||||||
env:
|
env:
|
||||||
- name: MY_POD_NAMESPACE #TODO: use PGOP_ prefix
|
- name: MY_POD_NAMESPACE
|
||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: metadata.namespace
|
fieldPath: metadata.namespace
|
||||||
- name: PGOP_SERVICE_ACCOUNT_NAME
|
- name: CONFIG_MAP_NAME
|
||||||
valueFrom:
|
value: "postgres-operator"
|
||||||
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"
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ apiVersion: "acid.zalan.do/v1"
|
||||||
kind: "Postgresql"
|
kind: "Postgresql"
|
||||||
|
|
||||||
metadata:
|
metadata:
|
||||||
name: testcluster
|
name: acid-testcluster
|
||||||
spec:
|
spec:
|
||||||
teamId: "ACID"
|
teamId: "ACID"
|
||||||
volume:
|
volume:
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ func (c *Cluster) recreatePods() error {
|
||||||
|
|
||||||
var masterPod v1.Pod
|
var masterPod v1.Pod
|
||||||
for _, pod := range pods.Items {
|
for _, pod := range pods.Items {
|
||||||
role := util.PodSpiloRole(&pod)
|
role := c.PodSpiloRole(&pod)
|
||||||
|
|
||||||
if role == constants.PodRoleMaster {
|
if role == constants.PodRoleMaster {
|
||||||
masterPod = pod
|
masterPod = pod
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ func (c *Cluster) waitForPodLabel(podEvents chan spec.PodEvent) error {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case podEvent := <-podEvents:
|
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 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
|
// 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.
|
// to promote, therefore, the new pod could be a master as well.
|
||||||
|
|
@ -156,10 +156,14 @@ func (c *Cluster) waitPodLabelsReady() error {
|
||||||
LabelSelector: ls.String(),
|
LabelSelector: ls.String(),
|
||||||
}
|
}
|
||||||
masterListOption := v1.ListOptions{
|
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{
|
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)
|
pods, err := c.KubeClient.Pods(namespace).List(listOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -203,10 +207,10 @@ func (c *Cluster) waitStatefulsetPodsReady() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cluster) labelsSet() labels.Set {
|
func (c *Cluster) labelsSet() labels.Set {
|
||||||
return labels.Set{
|
lbls := c.OpConfig.ClusterLabels
|
||||||
constants.ApplicationNameLabel: constants.ApplicationNameLabelValue,
|
lbls[c.OpConfig.ClusterNameLabel] = c.Metadata.Name
|
||||||
constants.ClusterNameLabel: c.Metadata.Name,
|
|
||||||
}
|
return labels.Set(lbls)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cluster) dnsName() string {
|
func (c *Cluster) dnsName() string {
|
||||||
|
|
@ -228,7 +232,7 @@ func (c *Cluster) credentialSecretName(username string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cluster) deleteEtcdKey() error {
|
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
|
//TODO: retry multiple times
|
||||||
resp, err := c.EtcdClient.Delete(context.Background(),
|
resp, err := c.EtcdClient.Delete(context.Background(),
|
||||||
|
|
@ -245,3 +249,7 @@ func (c *Cluster) deleteEtcdKey() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cluster) PodSpiloRole(pod *v1.Pod) string {
|
||||||
|
return pod.Labels[c.OpConfig.PodRoleLabel]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
PodNamespace string
|
|
||||||
KubeClient *kubernetes.Clientset
|
KubeClient *kubernetes.Clientset
|
||||||
RestClient *rest.RESTClient
|
RestClient *rest.RESTClient
|
||||||
EtcdClient etcdclient.KeysAPI
|
EtcdClient etcdclient.KeysAPI
|
||||||
|
|
@ -63,7 +62,6 @@ func (c *Controller) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) {
|
||||||
|
|
||||||
c.initController()
|
c.initController()
|
||||||
|
|
||||||
c.logger.Infof("'%s' namespace will be watched", c.PodNamespace)
|
|
||||||
go c.runInformers(stopCh)
|
go c.runInformers(stopCh)
|
||||||
|
|
||||||
c.logger.Info("Started working in background")
|
c.logger.Info("Started working in background")
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ func (c *Controller) podListFunc(options api.ListOptions) (runtime.Object, error
|
||||||
TimeoutSeconds: options.TimeoutSeconds,
|
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) {
|
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,
|
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{}) {
|
func (c *Controller) podAdd(obj interface{}) {
|
||||||
|
|
@ -62,7 +62,7 @@ func (c *Controller) podAdd(obj interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
podEvent := spec.PodEvent{
|
podEvent := spec.PodEvent{
|
||||||
ClusterName: util.PodClusterName(pod),
|
ClusterName: c.PodClusterName(pod),
|
||||||
PodName: util.NameFromMeta(pod.ObjectMeta),
|
PodName: util.NameFromMeta(pod.ObjectMeta),
|
||||||
CurPod: pod,
|
CurPod: pod,
|
||||||
EventType: spec.PodEventAdd,
|
EventType: spec.PodEventAdd,
|
||||||
|
|
@ -83,7 +83,7 @@ func (c *Controller) podUpdate(prev, cur interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
podEvent := spec.PodEvent{
|
podEvent := spec.PodEvent{
|
||||||
ClusterName: util.PodClusterName(curPod),
|
ClusterName: c.PodClusterName(curPod),
|
||||||
PodName: util.NameFromMeta(curPod.ObjectMeta),
|
PodName: util.NameFromMeta(curPod.ObjectMeta),
|
||||||
PrevPod: prevPod,
|
PrevPod: prevPod,
|
||||||
CurPod: curPod,
|
CurPod: curPod,
|
||||||
|
|
@ -100,7 +100,7 @@ func (c *Controller) podDelete(obj interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
podEvent := spec.PodEvent{
|
podEvent := spec.PodEvent{
|
||||||
ClusterName: util.PodClusterName(pod),
|
ClusterName: c.PodClusterName(pod),
|
||||||
PodName: util.NameFromMeta(pod.ObjectMeta),
|
PodName: util.NameFromMeta(pod.ObjectMeta),
|
||||||
CurPod: pod,
|
CurPod: pod,
|
||||||
EventType: spec.PodEventDelete,
|
EventType: spec.PodEventDelete,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import (
|
||||||
func (c *Controller) clusterListFunc(options api.ListOptions) (runtime.Object, error) {
|
func (c *Controller) clusterListFunc(options api.ListOptions) (runtime.Object, error) {
|
||||||
c.logger.Info("Getting list of currently running clusters")
|
c.logger.Info("Getting list of currently running clusters")
|
||||||
object, err := c.RestClient.Get().
|
object, err := c.RestClient.Get().
|
||||||
Namespace(c.PodNamespace).
|
Namespace(c.opConfig.Namespace).
|
||||||
Resource(constants.ResourceName).
|
Resource(constants.ResourceName).
|
||||||
VersionedParams(&options, api.ParameterCodec).
|
VersionedParams(&options, api.ParameterCodec).
|
||||||
FieldsSelectorParam(fields.Everything()).
|
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) {
|
func (c *Controller) clusterWatchFunc(options api.ListOptions) (watch.Interface, error) {
|
||||||
return c.RestClient.Get().
|
return c.RestClient.Get().
|
||||||
Prefix("watch").
|
Prefix("watch").
|
||||||
Namespace(c.PodNamespace).
|
Namespace(c.opConfig.Namespace).
|
||||||
Resource(constants.ResourceName).
|
Resource(constants.ResourceName).
|
||||||
VersionedParams(&options, api.ParameterCodec).
|
VersionedParams(&options, api.ParameterCodec).
|
||||||
FieldsSelectorParam(fields.Everything()).
|
FieldsSelectorParam(fields.Everything()).
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ func (c *Controller) createTPR() error {
|
||||||
|
|
||||||
restClient := c.RestClient
|
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) {
|
func (c *Controller) getInfrastructureRoles() (result map[string]spec.PgUser, err error) {
|
||||||
|
|
@ -125,3 +125,14 @@ Users:
|
||||||
|
|
||||||
return result, nil
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
|
"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
|
||||||
"github.com/kelseyhightower/envconfig"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TPR struct {
|
type TPR struct {
|
||||||
ReadyWaitInterval time.Duration `split_words:"true" default:"3s"`
|
ReadyWaitInterval time.Duration `name:"ready_wait_interval" default:"4s"`
|
||||||
ReadyWaitTimeout time.Duration `split_words:"true" default:"30s"`
|
ReadyWaitTimeout time.Duration `name:"ready_wait_timeout" default:"30s"`
|
||||||
ResyncPeriod time.Duration `split_words:"true" default:"5m"`
|
ResyncPeriod time.Duration `name:"resync_period" default:"5m"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Resources struct {
|
type Resources struct {
|
||||||
ResyncPeriodPod time.Duration `split_words:"true" default:"5m"`
|
ResyncPeriodPod time.Duration `name:"resync_period_pod" default:"5m"`
|
||||||
ResourceCheckInterval time.Duration `split_words:"true" default:"3s"`
|
ResourceCheckInterval time.Duration `name:"resource_check_interval" default:"3s"`
|
||||||
ResourceCheckTimeout time.Duration `split_words:"true" default:"10m"`
|
ResourceCheckTimeout time.Duration `name:"resource_check_timeout" default:"10m"`
|
||||||
PodLabelWaitTimeout time.Duration `split_words:"true" default:"10m"`
|
PodLabelWaitTimeout time.Duration `name:"pod_label_wait_timeout" default:"10m"`
|
||||||
PodDeletionWaitTimeout time.Duration `split_words:"true" 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 {
|
type Auth struct {
|
||||||
PamRoleName string `split_words:"true" default:"zalandos"`
|
PamRoleName string `name:"pam_rol_name" default:"zalandos"`
|
||||||
PamConfiguration string `split_words:"true" default:"https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"`
|
PamConfiguration string `name:"pam_configuration" default:"https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"`
|
||||||
TeamsAPIUrl string `envconfig:"teams_api_url" default:"https://teams.example.com/api/"`
|
TeamsAPIUrl string `name:"teams_api_url" default:"https://teams.example.com/api/"`
|
||||||
OAuthTokenSecretName spec.NamespacedName `envconfig:"oauth_token_secret_name" default:"postgresql-operator"`
|
OAuthTokenSecretName spec.NamespacedName `name:"oauth_token_secret_name" default:"postgresql-operator"`
|
||||||
InfrastructureRolesSecretName spec.NamespacedName `split_words:"true"`
|
InfrastructureRolesSecretName spec.NamespacedName `name:"infrastructure_roles_secret_name"`
|
||||||
SuperUsername string `split_words:"true" default:"postgres"`
|
SuperUsername string `name:"super_username" default:"postgres"`
|
||||||
ReplicationUsername string `split_words:"true" default:"replication"`
|
ReplicationUsername string `name:"replication_username" default:"replication"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
TPR
|
TPR
|
||||||
Resources
|
Resources
|
||||||
Auth
|
Auth
|
||||||
EtcdHost string `split_words:"true" default:"etcd-client.default.svc.cluster.local:2379"`
|
Namespace string `name:"namespace"`
|
||||||
DockerImage string `split_words:"true" default:"registry.opensource.zalan.do/acid/spilo-9.6:1.2-p12"`
|
EtcdHost string `name:"etcd_host" default:"etcd-client.default.svc.cluster.local:2379"`
|
||||||
ServiceAccountName string `split_words:"true" default:"operator"`
|
DockerImage string `name:"docker_image" default:"registry.opensource.zalan.do/acid/spilo-9.6:1.2-p12"`
|
||||||
DbHostedZone string `split_words:"true" default:"db.example.com"`
|
ServiceAccountName string `name:"service_account_name" default:"operator"`
|
||||||
EtcdScope string `split_words:"true" default:"service"`
|
DbHostedZone string `name:"db_hosted_zone" default:"db.example.com"`
|
||||||
WALES3Bucket string `envconfig:"wal_s3_bucket"`
|
EtcdScope string `name:"etcd_scope" default:"service"`
|
||||||
KubeIAMRole string `envconfig:"kube_iam_role"`
|
WALES3Bucket string `name:"wal_s3_bucket"`
|
||||||
DebugLogging bool `split_words:"true" default:"false"`
|
KubeIAMRole string `name:"kube_iam_role"`
|
||||||
DNSNameFormat string `envconfig:"dns_name_format" default:"%s.%s.%s"`
|
DebugLogging bool `name:"debug_logging" default:"false"`
|
||||||
}
|
DNSNameFormat string `name:"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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Config) MustMarshal() string {
|
func (c Config) MustMarshal() string {
|
||||||
|
|
@ -70,3 +59,23 @@ func (c Config) MustMarshal() string {
|
||||||
|
|
||||||
return string(b)
|
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
|
||||||
|
}
|
||||||
|
|
@ -14,8 +14,4 @@ const (
|
||||||
ResourceName = TPRName + "s"
|
ResourceName = TPRName + "s"
|
||||||
PodRoleMaster = "master"
|
PodRoleMaster = "master"
|
||||||
PodRoleReplica = "replica"
|
PodRoleReplica = "replica"
|
||||||
ApplicationNameLabel = "application"
|
|
||||||
ApplicationNameLabelValue = "spilo"
|
|
||||||
SpiloRoleLabel = "spilo-role"
|
|
||||||
ClusterNameLabel = "version"
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"k8s.io/client-go/pkg/api/v1"
|
"k8s.io/client-go/pkg/api/v1"
|
||||||
|
|
||||||
"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
|
"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
|
||||||
"github.bus.zalan.do/acid/postgres-operator/pkg/util/constants"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var passwordChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
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 {
|
func PGUserPassword(user spec.PgUser) string {
|
||||||
s := md5.Sum([]byte(user.Password + user.Name))
|
s := md5.Sum([]byte(user.Password + user.Name))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue