Skip to content

Commit

Permalink
refactor: Redo env search logic and add support for CloudNativePG
Browse files Browse the repository at this point in the history
  • Loading branch information
gabe565 committed Dec 8, 2023
1 parent ba0d427 commit 93241b2
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 115 deletions.
8 changes: 4 additions & 4 deletions internal/config/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ type Database interface {
Name() string
DefaultPort() uint16

PortEnvNames() []string
DatabaseEnvNames() []string
PortEnvNames() kubernetes.ConfigFinders
DatabaseEnvNames() kubernetes.ConfigFinders
ListDatabasesQuery() string
ListTablesQuery() string

UserEnvNames() []string
UserEnvNames() kubernetes.ConfigFinders
DefaultUser() string

DropDatabaseQuery(database string) string
AnalyzeQuery() string
PodLabels() []kubernetes.LabelQueryable
FilterPods(ctx context.Context, client kubernetes.KubeClient, pods []v1.Pod) ([]v1.Pod, error)
PasswordEnvNames(conf Global) []string
PasswordEnvNames(conf Global) kubernetes.ConfigFinders

ExecCommand(conf Exec) *command.Builder
DumpCommand(conf Dump) *command.Builder
Expand Down
22 changes: 13 additions & 9 deletions internal/database/mariadb/mariadb.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ func (MariaDB) Name() string {
return "mariadb"
}

func (MariaDB) PortEnvNames() []string {
return []string{"MARIADB_PORT_NUMBER", "MYSQL_PORT_NUMBER"}
func (MariaDB) PortEnvNames() kubernetes.ConfigFinders {
return kubernetes.ConfigFinders{kubernetes.ConfigFromEnv{"MARIADB_PORT_NUMBER", "MYSQL_PORT_NUMBER"}}
}

func (MariaDB) DefaultPort() uint16 {
return 3306
}

func (MariaDB) DatabaseEnvNames() []string {
return []string{"MARIADB_DATABASE", "MYSQL_DATABASE"}
func (MariaDB) DatabaseEnvNames() kubernetes.ConfigFinders {
return kubernetes.ConfigFinders{kubernetes.ConfigFromEnv{"MARIADB_DATABASE", "MYSQL_DATABASE"}}
}

func (MariaDB) ListDatabasesQuery() string {
Expand All @@ -38,8 +38,8 @@ func (MariaDB) ListTablesQuery() string {
return "show tables"
}

func (MariaDB) UserEnvNames() []string {
return []string{"MARIADB_USER", "MYSQL_USER"}
func (MariaDB) UserEnvNames() kubernetes.ConfigFinders {
return kubernetes.ConfigFinders{kubernetes.ConfigFromEnv{"MARIADB_USER", "MYSQL_USER"}}
}

func (MariaDB) DefaultUser() string {
Expand Down Expand Up @@ -77,11 +77,15 @@ func (MariaDB) FilterPods(ctx context.Context, client kubernetes.KubeClient, pod
return pods, nil
}

func (db MariaDB) PasswordEnvNames(c config.Global) []string {
func (db MariaDB) PasswordEnvNames(c config.Global) kubernetes.ConfigFinders {
if c.Username == db.DefaultUser() {
return []string{"MARIADB_ROOT_PASSWORD", "MYSQL_ROOT_PASSWORD"}
return kubernetes.ConfigFinders{
kubernetes.ConfigFromEnv{"MARIADB_ROOT_PASSWORD", "MYSQL_ROOT_PASSWORD"},
}
}
return kubernetes.ConfigFinders{
kubernetes.ConfigFromEnv{"MARIADB_PASSWORD", "MYSQL_PASSWORD"},
}
return []string{"MARIADB_PASSWORD", "MYSQL_PASSWORD"}
}

func (MariaDB) ExecCommand(conf config.Exec) *command.Builder {
Expand Down
8 changes: 4 additions & 4 deletions internal/database/mariadb/mariadb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,16 +166,16 @@ func TestMariaDB_PasswordEnvNames(t *testing.T) {
tests := []struct {
name string
args args
want []string
want kubernetes.ConfigFinders
}{
{"default", args{}, []string{"MARIADB_PASSWORD", "MYSQL_PASSWORD"}},
{"root", args{config.Global{Username: "root"}}, []string{"MARIADB_ROOT_PASSWORD", "MYSQL_ROOT_PASSWORD"}},
{"default", args{}, kubernetes.ConfigFinders{kubernetes.ConfigFromEnv{"MARIADB_PASSWORD", "MYSQL_PASSWORD"}}},
{"root", args{config.Global{Username: "root"}}, kubernetes.ConfigFinders{kubernetes.ConfigFromEnv{"MARIADB_ROOT_PASSWORD", "MYSQL_ROOT_PASSWORD"}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db := MariaDB{}
got := db.PasswordEnvNames(tt.args.c)
assert.Equal(t, got, tt.want)
assert.Equal(t, tt.want, got)
})
}
}
Expand Down
18 changes: 9 additions & 9 deletions internal/database/mongodb/mongodb.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ func (MongoDB) Name() string {
return "mongodb"
}

func (MongoDB) PortEnvNames() []string {
return []string{"MONGODB_PORT_NUMBER"}
func (MongoDB) PortEnvNames() kubernetes.ConfigFinders {
return kubernetes.ConfigFinders{kubernetes.ConfigFromEnv{"MONGODB_PORT_NUMBER"}}
}

func (MongoDB) DefaultPort() uint16 {
return 27017
}

func (MongoDB) DatabaseEnvNames() []string {
return []string{"MONGODB_EXTRA_DATABASES"}
func (MongoDB) DatabaseEnvNames() kubernetes.ConfigFinders {
return kubernetes.ConfigFinders{kubernetes.ConfigFromEnv{"MONGODB_EXTRA_DATABASES"}}
}

func (MongoDB) ListDatabasesQuery() string {
Expand All @@ -38,8 +38,8 @@ func (MongoDB) ListTablesQuery() string {
return "db.getCollectionNames().forEach(function(collection){ print(collection) })"
}

func (MongoDB) UserEnvNames() []string {
return []string{"MONGODB_EXTRA_USERNAMES", "MONGODB_ROOT_USER"}
func (MongoDB) UserEnvNames() kubernetes.ConfigFinders {
return kubernetes.ConfigFinders{kubernetes.ConfigFromEnv{"MONGODB_EXTRA_USERNAMES", "MONGODB_ROOT_USER"}}
}

func (MongoDB) DefaultUser() string {
Expand Down Expand Up @@ -73,11 +73,11 @@ func (MongoDB) FilterPods(ctx context.Context, client kubernetes.KubeClient, pod
return pods, nil
}

func (db MongoDB) PasswordEnvNames(c config.Global) []string {
func (db MongoDB) PasswordEnvNames(c config.Global) kubernetes.ConfigFinders {
if c.Username == db.DefaultUser() {
return []string{"MONGODB_ROOT_PASSWORD"}
return kubernetes.ConfigFinders{kubernetes.ConfigFromEnv{"MONGODB_ROOT_PASSWORD"}}
}
return []string{"MONGODB_EXTRA_PASSWORDS"}
return kubernetes.ConfigFinders{kubernetes.ConfigFromEnv{"MONGODB_EXTRA_PASSWORDS"}}
}

func (db MongoDB) AuthenticationDatabase(c config.Global) string {
Expand Down
8 changes: 4 additions & 4 deletions internal/database/mongodb/mongodb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,16 @@ func TestMongoDB_PasswordEnvNames(t *testing.T) {
tests := []struct {
name string
args args
want []string
want kubernetes.ConfigFinders
}{
{"default", args{}, []string{"MONGODB_EXTRA_PASSWORDS"}},
{"root", args{config.Global{Username: "root"}}, []string{"MONGODB_ROOT_PASSWORD"}},
{"default", args{}, kubernetes.ConfigFinders{kubernetes.ConfigFromEnv{"MONGODB_EXTRA_PASSWORDS"}}},
{"root", args{config.Global{Username: "root"}}, kubernetes.ConfigFinders{kubernetes.ConfigFromEnv{"MONGODB_ROOT_PASSWORD"}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db := MongoDB{}
got := db.PasswordEnvNames(tt.args.c)
assert.Equal(t, got, tt.want)
assert.Equal(t, tt.want, got)
})
}
}
Expand Down
44 changes: 35 additions & 9 deletions internal/database/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/clevyr/kubedb/internal/database/sqlformat"
"github.com/clevyr/kubedb/internal/kubernetes"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/selection"

v1 "k8s.io/api/core/v1"
)
Expand All @@ -25,16 +26,22 @@ func (Postgres) Name() string {
return "postgres"
}

func (Postgres) PortEnvNames() []string {
return []string{"POSTGRESQL_PORT_NUMBER"}
func (Postgres) PortEnvNames() kubernetes.ConfigFinders {
return kubernetes.ConfigFinders{
kubernetes.ConfigFromEnv{"POSTGRESQL_PORT_NUMBER"},
kubernetes.ConfigFromVolumeSecret{Name: "app-secret", Key: "port"},
}
}

func (Postgres) DefaultPort() uint16 {
return 5432
}

func (Postgres) DatabaseEnvNames() []string {
return []string{"POSTGRES_DATABASE", "POSTGRES_DB"}
func (Postgres) DatabaseEnvNames() kubernetes.ConfigFinders {
return kubernetes.ConfigFinders{
kubernetes.ConfigFromEnv{"POSTGRES_DATABASE", "POSTGRES_DB"},
kubernetes.ConfigFromVolumeSecret{Name: "app-secret", Key: "dbname"},
}
}

func (Postgres) ListDatabasesQuery() string {
Expand All @@ -45,8 +52,11 @@ func (Postgres) ListTablesQuery() string {
return "SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE'"
}

func (Postgres) UserEnvNames() []string {
return []string{"POSTGRES_USER", "PGPOOL_POSTGRES_USERNAME", "PGUSER_SUPERUSER"}
func (Postgres) UserEnvNames() kubernetes.ConfigFinders {
return kubernetes.ConfigFinders{
kubernetes.ConfigFromEnv{"POSTGRES_USER", "PGPOOL_POSTGRES_USERNAME", "PGUSER_SUPERUSER"},
kubernetes.ConfigFromVolumeSecret{Name: "app-secret", Key: "username"},
}
}

func (Postgres) DefaultUser() string {
Expand All @@ -71,6 +81,7 @@ func (Postgres) PodLabels() []kubernetes.LabelQueryable {
{Name: "app.kubernetes.io/name", Value: "postgresql-ha"},
{Name: "app.kubernetes.io/component", Value: "postgresql"},
},
kubernetes.LabelQuery{Name: "cnpg.io/cluster", Operator: selection.Exists},
kubernetes.LabelQuery{Name: "application", Value: "spilo"},
kubernetes.LabelQuery{Name: "app", Value: "postgresql"},
}
Expand Down Expand Up @@ -117,6 +128,16 @@ func (Postgres) FilterPods(ctx context.Context, client kubernetes.KubeClient, po
break
}
}
} else if _, ok := pods[0].Labels["cnpg.io/cluster"]; ok {
log.Debug("filtering CloudNativePG Pods for Leader")

for key, pod := range pods {
if role, ok := pod.Labels["cnpg.io/instanceRole"]; ok {
if role == "primary" {
return pods[key : key+1], nil
}
}
}
} else if pods[0].Labels["application"] == "spilo" {
log.Debug("querying Patroni for primary instance")
cmd := command.NewBuilder("patronictl", "list", "--format=json")
Expand Down Expand Up @@ -158,11 +179,16 @@ func (Postgres) FilterPods(ctx context.Context, client kubernetes.KubeClient, po
return pods, nil
}

func (db Postgres) PasswordEnvNames(c config.Global) []string {
func (db Postgres) PasswordEnvNames(c config.Global) kubernetes.ConfigFinders {
var searchEnvs kubernetes.ConfigFromEnv
if c.Username == db.DefaultUser() {
return []string{"POSTGRES_POSTGRES_PASSWORD", "POSTGRES_PASSWORD", "PGPOOL_POSTGRES_PASSWORD", "PGPASSWORD_SUPERUSER"}
searchEnvs = append(searchEnvs, "POSTGRES_POSTGRES_PASSWORD")
}
searchEnvs = append(searchEnvs, "POSTGRES_PASSWORD", "PGPOOL_POSTGRES_PASSWORD", "PGPASSWORD_SUPERUSER")
return kubernetes.ConfigFinders{
searchEnvs,
kubernetes.ConfigFromVolumeSecret{Name: "app-secret", Key: "password"},
}
return []string{"POSTGRES_PASSWORD", "PGPOOL_POSTGRES_PASSWORD", "PGPASSWORD_SUPERUSER"}
}

func (Postgres) ExecCommand(conf config.Exec) *command.Builder {
Expand Down
21 changes: 18 additions & 3 deletions internal/database/postgres/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,25 @@ func TestPostgres_PasswordEnvNames(t *testing.T) {
tests := []struct {
name string
args args
want []string
want kubernetes.ConfigFinders
}{
{"default", args{}, []string{"POSTGRES_PASSWORD", "PGPOOL_POSTGRES_PASSWORD", "PGPASSWORD_SUPERUSER"}},
{"postgres", args{config.Global{Username: "postgres"}}, []string{"POSTGRES_POSTGRES_PASSWORD", "POSTGRES_PASSWORD", "PGPOOL_POSTGRES_PASSWORD", "PGPASSWORD_SUPERUSER"}},
{"default", args{}, kubernetes.ConfigFinders{
kubernetes.ConfigFromEnv{
"POSTGRES_PASSWORD",
"PGPOOL_POSTGRES_PASSWORD",
"PGPASSWORD_SUPERUSER",
},
kubernetes.ConfigFromVolumeSecret{Name: "app-secret", Key: "password"},
}},
{"postgres", args{config.Global{Username: "postgres"}}, kubernetes.ConfigFinders{
kubernetes.ConfigFromEnv{
"POSTGRES_POSTGRES_PASSWORD",
"POSTGRES_PASSWORD",
"PGPOOL_POSTGRES_PASSWORD",
"PGPASSWORD_SUPERUSER",
},
kubernetes.ConfigFromVolumeSecret{Name: "app-secret", Key: "password"},
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
109 changes: 109 additions & 0 deletions internal/kubernetes/config_finder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package kubernetes

import (
"context"
"errors"
"fmt"
"strings"

corev1 "k8s.io/api/core/v1"
v1meta "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var (
ErrEnvVarNotFound = errors.New("env var not found")
ErrNoDiscoveryEnvs = errors.New("failed to find config")
)

type ConfigFinder interface {
GetValue(context.Context, KubeClient, corev1.Pod) (string, error)
}

type ConfigFinders []ConfigFinder

func (c ConfigFinders) Search(ctx context.Context, client KubeClient, pod corev1.Pod) (string, error) {
for _, search := range c {
found, err := search.GetValue(ctx, client, pod)
if err == nil {
return found, nil
}
}
return "", ErrNoDiscoveryEnvs
}

type ConfigFromEnv []string

func (e ConfigFromEnv) GetValue(ctx context.Context, client KubeClient, pod corev1.Pod) (string, error) {
if len(e) == 0 {
return "", ErrNoEnvNames
}

var err error
var envVar *corev1.EnvVar
for _, envName := range e {
envVar, err = FindEnvVar(pod, envName)
if err == nil {
break
}
}
if err != nil {
if errors.Is(err, ErrEnvVarNotFound) {
return "", fmt.Errorf("%w: %s", ErrNoDiscoveryEnvs, strings.Join(e, ", "))
}
return "", err
}

if envVar.ValueFrom != nil {
switch {
case envVar.ValueFrom.SecretKeyRef != nil:
secretKeyRef := envVar.ValueFrom.SecretKeyRef
secret, err := client.Secrets().Get(ctx, secretKeyRef.Name, v1meta.GetOptions{})
if err != nil {
return "", err
}
data, ok := secret.Data[secretKeyRef.Key]
if !ok {
return "", fmt.Errorf("%w: %v", ErrSecretDoesNotHaveKey, secretKeyRef)
}
return string(data), nil
case envVar.ValueFrom.ConfigMapKeyRef != nil:
configMapRef := envVar.ValueFrom.ConfigMapKeyRef
configMap, err := client.ConfigMaps().Get(ctx, configMapRef.Name, v1meta.GetOptions{})
if err != nil {
return "", err
}
data, ok := configMap.Data[configMapRef.Key]
if !ok {
return "", fmt.Errorf("%w: %v", ErrConfigMapDoesNotHaveKey, configMapRef)
}
return data, nil
}
}
return envVar.Value, nil
}

type ConfigFromVolumeSecret struct {
Name string
Key string
}

func (f ConfigFromVolumeSecret) GetValue(ctx context.Context, client KubeClient, pod corev1.Pod) (string, error) {
if f.Name == "" || f.Key == "" {
return "", ErrNoEnvNames
}

for _, volume := range pod.Spec.Volumes {
if volume.Name == f.Name && volume.Secret != nil {
secret, err := client.Secrets().Get(ctx, volume.Secret.SecretName, v1meta.GetOptions{})
if err != nil {
return "", err
}

if value, ok := secret.Data[f.Key]; ok {
return string(value), nil
}
}
}

return "", ErrNoDiscoveryEnvs
}
Loading

0 comments on commit 93241b2

Please sign in to comment.