Skip to content

Commit

Permalink
feat: Add ability to seperate port into its own secret
Browse files Browse the repository at this point in the history
  • Loading branch information
halkeye committed Oct 29, 2024
1 parent 51461ea commit a737b4a
Show file tree
Hide file tree
Showing 23 changed files with 1,718 additions and 62 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ These environment variables are embedded in [deploy/operator.yaml](deploy/operat
## Installation

This operator requires a Kubernetes Secret to be created in the same namespace as operator itself.
Secret should contain these keys: POSTGRES_HOST, POSTGRES_USER, POSTGRES_PASS, POSTGRES_URI_ARGS, POSTGRES_CLOUD_PROVIDER, POSTGRES_DEFAULT_DATABASE.
Secret should contain these keys: POSTGRES_HOST, POSTGRES_PORT (optional), POSTGRES_USER, POSTGRES_PASS, POSTGRES_URI_ARGS, POSTGRES_CLOUD_PROVIDER, POSTGRES_DEFAULT_DATABASE.
Example:

```yaml
Expand Down Expand Up @@ -193,12 +193,14 @@ meeting the specific needs of different applications.

Available context:

| Variable | Meaning |
|-------------|--------------------------|
| `.Host` | Database host |
| `.Role` | Generated user/role name |
| `.Database` | Referenced database name |
| `.Password` | Generated role password |
| Variable | Meaning |
|---------------|----------------------------|
| `.Host` | Database host and port |
| `.HostNoPort` | Database host without port |
| `.Port` | Database port |
| `.Role` | Generated user/role name |
| `.Database` | Referenced database name |
| `.Password` | Generated role password |

### Contribution

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/movetokube/postgres-operator
go 1.18

require (
github.com/caarlos0/env/v11 v11.0.1
github.com/go-logr/logr v0.1.0
github.com/go-openapi/spec v0.19.4
github.com/golang/mock v1.3.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ github.com/bugsnag/bugsnag-go v1.5.3/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqR
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/caarlos0/env/v11 v11.0.1 h1:A8dDt9Ub9ybqRSUF3fQc/TA/gTam2bKT4Pit+cwrsPs=
github.com/caarlos0/env/v11 v11.0.1/go.mod h1:2RC3HQu8BQqtEK3V4iHPxj0jOdWdbPpWJ6pOueeU1xM=
github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
Expand Down
45 changes: 27 additions & 18 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
package config

import (
"log"
"net/url"
"strconv"
"strings"
"sync"

"github.com/movetokube/postgres-operator/pkg/utils"
"github.com/caarlos0/env/v11"
)

type cfg struct {
PostgresHost string
PostgresUser string
PostgresPass string
PostgresUriArgs string
PostgresDefaultDb string
CloudProvider string
AnnotationFilter string
KeepSecretName bool
PostgresHost string `env:"POSTGRES_HOST,required"`
PostgresUser string `env:"POSTGRES_USER,required"`
PostgresPass string `env:"POSTGRES_PASS,required"`
PostgresPort uint32 `env:"POSTGRES_PORT" envDefault:"5432"`
PostgresUriArgs string `env:"POSTGRES_URI_ARGS,required"`
PostgresDefaultDb string `env:"POSTGRES_DEFAULT_DATABASE"`
CloudProvider string `env:"POSTGRES_CLOUD_PROVIDER"`
AnnotationFilter string `env:"POSTGRES_INSTANCE"`
KeepSecretName bool `env:"KEEP_SECRET_NAME"`
}

var doOnce sync.Once
Expand All @@ -25,15 +28,21 @@ var config *cfg
func Get() *cfg {
doOnce.Do(func() {
config = &cfg{}
config.PostgresHost = utils.MustGetEnv("POSTGRES_HOST")
config.PostgresUser = url.PathEscape(utils.MustGetEnv("POSTGRES_USER"))
config.PostgresPass = url.PathEscape(utils.MustGetEnv("POSTGRES_PASS"))
config.PostgresUriArgs = utils.MustGetEnv("POSTGRES_URI_ARGS")
config.PostgresDefaultDb = utils.GetEnv("POSTGRES_DEFAULT_DATABASE")
config.CloudProvider = utils.GetEnv("POSTGRES_CLOUD_PROVIDER")
config.AnnotationFilter = utils.GetEnv("POSTGRES_INSTANCE")
if value, err := strconv.ParseBool(utils.GetEnv("KEEP_SECRET_NAME")); err == nil {
config.KeepSecretName = value
if err := env.Parse(config); err != nil {
log.Fatal(err)
}
config.PostgresUser = url.PathEscape(config.PostgresUser)
config.PostgresPass = url.PathEscape(config.PostgresPass)
if strings.Contains(config.PostgresHost, ":") {
parts := strings.Split(config.PostgresHost, ":")
if len(parts) > 1 {
port, err := strconv.ParseInt(parts[1], 10, 32)
if err != nil {
log.Fatal(err)
}
config.PostgresPort = uint32(port)
config.PostgresHost = parts[0]
}
}
})
return config
Expand Down
10 changes: 5 additions & 5 deletions pkg/controller/postgres/postgres_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
"sigs.k8s.io/controller-runtime/pkg/source"
)

Expand All @@ -34,7 +34,7 @@ func Add(mgr manager.Manager) error {
// newReconciler returns a new reconcile.Reconciler
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
c := config.Get()
pg, err := postgres.NewPG(c.PostgresHost, c.PostgresUser, c.PostgresPass, c.PostgresUriArgs, c.PostgresDefaultDb, c.CloudProvider, log.WithName("postgres"))
pg, err := postgres.NewPG(c.PostgresHost, c.PostgresUser, c.PostgresPass, c.PostgresPort, c.PostgresUriArgs, c.PostgresDefaultDb, c.CloudProvider, log.WithName("postgres"))
if err != nil {
return nil
}
Expand Down Expand Up @@ -220,19 +220,19 @@ func (r *ReconcilePostgres) Reconcile(request reconcile.Request) (_ reconcile.Re
}

// Set privileges on schema
schemaPrivilegesReader := postgres.PostgresSchemaPrivileges{database, owner, reader, schema, readerPrivs, false}
schemaPrivilegesReader := postgres.PostgresSchemaPrivileges{DB: database, Creator: owner, Role: reader, Schema: schema, Privs: readerPrivs, CreateSchema: false}
err = r.pg.SetSchemaPrivileges(schemaPrivilegesReader, reqLogger)
if err != nil {
reqLogger.Error(err, fmt.Sprintf("Could not give %s permissions \"%s\"", reader, readerPrivs))
continue
}
schemaPrivilegesWriter := postgres.PostgresSchemaPrivileges{database, owner, writer, schema, readerPrivs, true}
schemaPrivilegesWriter := postgres.PostgresSchemaPrivileges{DB: database, Creator: owner, Role: writer, Schema: schema, Privs: readerPrivs, CreateSchema: true}
err = r.pg.SetSchemaPrivileges(schemaPrivilegesWriter, reqLogger)
if err != nil {
reqLogger.Error(err, fmt.Sprintf("Could not give %s permissions \"%s\"", writer, writerPrivs))
continue
}
schemaPrivilegesOwner := postgres.PostgresSchemaPrivileges{database, owner, owner, schema, readerPrivs, true}
schemaPrivilegesOwner := postgres.PostgresSchemaPrivileges{DB: database, Creator: owner, Role: owner, Schema: schema, Privs: readerPrivs, CreateSchema: true}
err = r.pg.SetSchemaPrivileges(schemaPrivilegesOwner, reqLogger)
if err != nil {
reqLogger.Error(err, fmt.Sprintf("Could not give %s permissions \"%s\"", writer, writerPrivs))
Expand Down
38 changes: 23 additions & 15 deletions pkg/controller/postgresuser/postgresuser_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
"sigs.k8s.io/controller-runtime/pkg/source"
)

Expand All @@ -45,7 +45,7 @@ func Add(mgr manager.Manager) error {
// newReconciler returns a new reconcile.Reconciler
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
c := config.Get()
pg, err := postgres.NewPG(c.PostgresHost, c.PostgresUser, c.PostgresPass, c.PostgresUriArgs, c.PostgresDefaultDb, c.CloudProvider, log.WithName("postgresuser"))
pg, err := postgres.NewPG(c.PostgresHost, c.PostgresUser, c.PostgresPass, c.PostgresPort, c.PostgresUriArgs, c.PostgresDefaultDb, c.CloudProvider, log.WithName("postgresuser"))
if err != nil {
return nil
}
Expand All @@ -55,6 +55,7 @@ func newReconciler(mgr manager.Manager) reconcile.Reconciler {
scheme: mgr.GetScheme(),
pg: pg,
pgHost: c.PostgresHost,
pgPort: c.PostgresPort,
instanceFilter: c.AnnotationFilter,
keepSecretName: c.KeepSecretName,
}
Expand Down Expand Up @@ -100,6 +101,7 @@ type ReconcilePostgresUser struct {
scheme *runtime.Scheme
pg postgres.PG
pgHost string
pgPort uint32
instanceFilter string
keepSecretName bool // use secret name as defined in PostgresUserSpec
}
Expand Down Expand Up @@ -280,9 +282,9 @@ func (r *ReconcilePostgresUser) addFinalizer(reqLogger logr.Logger, m *dbv1alpha
}

func (r *ReconcilePostgresUser) newSecretForCR(cr *dbv1alpha1.PostgresUser, role, password, login string) (*corev1.Secret, error) {
pgUserUrl := fmt.Sprintf("postgresql://%s:%s@%s/%s", role, password, r.pgHost, cr.Status.DatabaseName)
pgJDBCUrl := fmt.Sprintf("jdbc:postgresql://%s/%s", r.pgHost, cr.Status.DatabaseName)
pgDotnetUrl := fmt.Sprintf("User ID=%s;Password=%s;Host=%s;Port=5432;Database=%s;", role, password, r.pgHost, cr.Status.DatabaseName)
pgUserUrl := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s", role, password, r.pgHost, r.pgPort, cr.Status.DatabaseName)
pgJDBCUrl := fmt.Sprintf("jdbc:postgresql://%s:%d/%s", r.pgHost, r.pgPort, cr.Status.DatabaseName)
pgDotnetUrl := fmt.Sprintf("User ID=%s;Password=%s;Host=%s;Port=%d;Database=%s;", role, password, r.pgHost, r.pgPort, cr.Status.DatabaseName)
labels := map[string]string{
"app": cr.Name,
}
Expand All @@ -293,10 +295,13 @@ func (r *ReconcilePostgresUser) newSecretForCR(cr *dbv1alpha1.PostgresUser, role
}

templateData, err := renderTemplate(cr.Spec.SecretTemplate, templateContext{
Role: role,
Host: r.pgHost,
Database: cr.Status.DatabaseName,
Password: password,
Role: role,
Host: fmt.Sprintf("%s:%d", r.pgHost, r.pgPort),
HostNoPort: r.pgHost,
Port: r.pgPort,
Login: login,
Database: cr.Status.DatabaseName,
Password: password,
})
if err != nil {
return nil, fmt.Errorf("render templated keys: %w", err)
Expand All @@ -306,7 +311,7 @@ func (r *ReconcilePostgresUser) newSecretForCR(cr *dbv1alpha1.PostgresUser, role
"POSTGRES_URL": []byte(pgUserUrl),
"POSTGRES_JDBC_URL": []byte(pgJDBCUrl),
"POSTGRES_DOTNET_URL": []byte(pgDotnetUrl),
"HOST": []byte(r.pgHost),
"HOST": []byte(fmt.Sprintf("%s:%d", r.pgHost, r.pgPort)),
"DATABASE_NAME": []byte(cr.Status.DatabaseName),
"ROLE": []byte(role),
"PASSWORD": []byte(password),
Expand Down Expand Up @@ -364,7 +369,7 @@ func (r *ReconcilePostgresUser) getPostgresCR(instance *dbv1alpha1.PostgresUser)
return &database, nil
}

func (r *ReconcilePostgresUser) addOwnerRef(reqLogger logr.Logger, instance *dbv1alpha1.PostgresUser) error {
func (r *ReconcilePostgresUser) addOwnerRef(_ logr.Logger, instance *dbv1alpha1.PostgresUser) error {
// Search postgres database CR
pg, err := r.getPostgresCR(instance)
if err != nil {
Expand All @@ -381,10 +386,13 @@ func (r *ReconcilePostgresUser) addOwnerRef(reqLogger logr.Logger, instance *dbv
}

type templateContext struct {
Host string
Role string
Database string
Password string
Host string
HostNoPort string
Port uint32
Role string
Login string
Database string
Password string
}

func renderTemplate(data map[string]string, tc templateContext) (map[string][]byte, error) {
Expand Down
50 changes: 50 additions & 0 deletions pkg/controller/postgresuser/postgresuser_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package postgresuser

import (
"reflect"
"testing"

dbv1alpha1 "github.com/movetokube/postgres-operator/pkg/apis/db/v1alpha1"
)

func assertEqual(t *testing.T, a interface{}, b interface{}) {
t.Helper()

if a == b {
return
}

// debug.PrintStack()
t.Errorf("Received %v (type %v), expected %v (type %v)", a, reflect.TypeOf(a), b, reflect.TypeOf(b))
}

func TestReconcilePostgresUser_newSecretForCR(t *testing.T) {
rpu := &ReconcilePostgresUser{
pgHost: "localhost",
pgPort: 5432,
}

cr := &dbv1alpha1.PostgresUser{
Status: dbv1alpha1.PostgresUserStatus{
DatabaseName: "dbname",
},
Spec: dbv1alpha1.PostgresUserSpec{
SecretTemplate: map[string]string{
"all": "host={{.Host}} host_no_port={{.HostNoPort}} port={{.Port}} user={{.Role}} login={{.Login}} password={{.Password}} dbname={{.Database}}",
},
},
}

secret, err := rpu.newSecretForCR(cr, "role", "password", "login")
if err != nil {
t.Fatalf("could not patch object: (%v)", err)
}

if secret == nil {
t.Fatalf("no secret returned")
}

// keep old behavior of merging host and port
assertEqual(t, string(secret.Data["HOST"]), "localhost:5432")
assertEqual(t, string(secret.Data["all"]), "host=localhost:5432 host_no_port=localhost port=5432 user=role login=login password=password dbname=dbname")
}
14 changes: 7 additions & 7 deletions pkg/postgres/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ const (
GRANT_CREATE_TABLE = `GRANT CREATE ON SCHEMA "%s" TO "%s"`
GRANT_ALL_TABLES = `GRANT %s ON ALL TABLES IN SCHEMA "%s" TO "%s"`
DEFAULT_PRIVS_SCHEMA = `ALTER DEFAULT PRIVILEGES FOR ROLE "%s" IN SCHEMA "%s" GRANT %s ON TABLES TO "%s"`
REVOKE_CONNECT = `REVOKE CONNECT ON DATABASE "%s" FROM public`
TERMINATE_BACKEND = `SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '%s' AND pid <> pg_backend_pid()`
GET_DB_OWNER = `SELECT pg_catalog.pg_get_userbyid(d.datdba) FROM pg_catalog.pg_database d WHERE d.datname = '%s'`
REVOKE_CONNECT = `REVOKE CONNECT ON DATABASE "%s" FROM public`
TERMINATE_BACKEND = `SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '%s' AND pid <> pg_backend_pid()`
GET_DB_OWNER = `SELECT pg_catalog.pg_get_userbyid(d.datdba) FROM pg_catalog.pg_database d WHERE d.datname = '%s'`
GRANT_CREATE_SCHEMA = `GRANT CREATE ON DATABASE "%s" TO "%s"`
)

Expand All @@ -45,7 +45,7 @@ func (c *pg) CreateDB(dbname, role string) error {
}

func (c *pg) CreateSchema(db, role, schema string, logger logr.Logger) error {
tmpDb, err := GetConnection(c.user, c.pass, c.host, db, c.args, logger)
tmpDb, err := GetConnection(c.user, c.pass, c.host, c.port, db, c.args, logger)
if err != nil {
return err
}
Expand Down Expand Up @@ -82,7 +82,7 @@ func (c *pg) DropDatabase(database string, logger logr.Logger) error {
}

func (c *pg) CreateExtension(db, extension string, logger logr.Logger) error {
tmpDb, err := GetConnection(c.user, c.pass, c.host, db, c.args, logger)
tmpDb, err := GetConnection(c.user, c.pass, c.host, c.port, db, c.args, logger)
if err != nil {
return err
}
Expand All @@ -96,7 +96,7 @@ func (c *pg) CreateExtension(db, extension string, logger logr.Logger) error {
}

func (c *pg) SetSchemaPrivileges(schemaPrivileges PostgresSchemaPrivileges, logger logr.Logger) error {
tmpDb, err := GetConnection(c.user, c.pass, c.host, schemaPrivileges.DB, c.args, logger)
tmpDb, err := GetConnection(c.user, c.pass, c.host, c.port, schemaPrivileges.DB, c.args, logger)
if err != nil {
return err
}
Expand Down Expand Up @@ -125,7 +125,7 @@ func (c *pg) SetSchemaPrivileges(schemaPrivileges PostgresSchemaPrivileges, logg
_, err = tmpDb.Exec(fmt.Sprintf(GRANT_CREATE_TABLE, schemaPrivileges.Schema, schemaPrivileges.Role))
if err != nil {
return err
}
}
}

return nil
Expand Down
10 changes: 5 additions & 5 deletions pkg/postgres/gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ func (c *gcppg) CreateDB(dbname, role string) error {
}

func (c *gcppg) DropRole(role, newOwner, database string, logger logr.Logger) error {
tmpDb, err := GetConnection(c.user, c.pass, c.host, database, c.args, logger)

tmpDb, err := GetConnection(c.user, c.pass, c.host, c.port, database, c.args, logger)
q := fmt.Sprintf(GET_DB_OWNER, database)
logger.Info("Checking master role: "+ q)
logger.Info("Checking master role: " + q)
rows, err := tmpDb.Query(q)
if err != nil {
return err
Expand All @@ -68,9 +68,9 @@ func (c *gcppg) DropRole(role, newOwner, database string, logger logr.Logger) er
rows.Scan(&masterRole)
}

if( role != masterRole){
if role != masterRole {
q = fmt.Sprintf(DROP_ROLE, role)
logger.Info("GCP Drop Role: "+ q)
logger.Info("GCP Drop Role: " + q)
_, err = tmpDb.Exec(q)
// Check if error exists and if different from "ROLE NOT FOUND" => 42704
if err != nil && err.(*pq.Error).Code != "42704" {
Expand Down
Loading

0 comments on commit a737b4a

Please sign in to comment.