Skip to content

Commit a737b4a

Browse files
committed
feat: Add ability to seperate port into its own secret
1 parent 51461ea commit a737b4a

23 files changed

+1718
-62
lines changed

README.md

+9-7
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ These environment variables are embedded in [deploy/operator.yaml](deploy/operat
6565
## Installation
6666

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

7171
```yaml
@@ -193,12 +193,14 @@ meeting the specific needs of different applications.
193193

194194
Available context:
195195

196-
| Variable | Meaning |
197-
|-------------|--------------------------|
198-
| `.Host` | Database host |
199-
| `.Role` | Generated user/role name |
200-
| `.Database` | Referenced database name |
201-
| `.Password` | Generated role password |
196+
| Variable | Meaning |
197+
|---------------|----------------------------|
198+
| `.Host` | Database host and port |
199+
| `.HostNoPort` | Database host without port |
200+
| `.Port` | Database port |
201+
| `.Role` | Generated user/role name |
202+
| `.Database` | Referenced database name |
203+
| `.Password` | Generated role password |
202204

203205
### Contribution
204206

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/movetokube/postgres-operator
33
go 1.18
44

55
require (
6+
github.com/caarlos0/env/v11 v11.0.1
67
github.com/go-logr/logr v0.1.0
78
github.com/go-openapi/spec v0.19.4
89
github.com/golang/mock v1.3.1

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ github.com/bugsnag/bugsnag-go v1.5.3/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqR
119119
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
120120
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
121121
github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
122+
github.com/caarlos0/env/v11 v11.0.1 h1:A8dDt9Ub9ybqRSUF3fQc/TA/gTam2bKT4Pit+cwrsPs=
123+
github.com/caarlos0/env/v11 v11.0.1/go.mod h1:2RC3HQu8BQqtEK3V4iHPxj0jOdWdbPpWJ6pOueeU1xM=
122124
github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
123125
github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
124126
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=

pkg/config/config.go

+27-18
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
package config
22

33
import (
4+
"log"
45
"net/url"
56
"strconv"
7+
"strings"
68
"sync"
79

8-
"github.com/movetokube/postgres-operator/pkg/utils"
10+
"github.com/caarlos0/env/v11"
911
)
1012

1113
type cfg struct {
12-
PostgresHost string
13-
PostgresUser string
14-
PostgresPass string
15-
PostgresUriArgs string
16-
PostgresDefaultDb string
17-
CloudProvider string
18-
AnnotationFilter string
19-
KeepSecretName bool
14+
PostgresHost string `env:"POSTGRES_HOST,required"`
15+
PostgresUser string `env:"POSTGRES_USER,required"`
16+
PostgresPass string `env:"POSTGRES_PASS,required"`
17+
PostgresPort uint32 `env:"POSTGRES_PORT" envDefault:"5432"`
18+
PostgresUriArgs string `env:"POSTGRES_URI_ARGS,required"`
19+
PostgresDefaultDb string `env:"POSTGRES_DEFAULT_DATABASE"`
20+
CloudProvider string `env:"POSTGRES_CLOUD_PROVIDER"`
21+
AnnotationFilter string `env:"POSTGRES_INSTANCE"`
22+
KeepSecretName bool `env:"KEEP_SECRET_NAME"`
2023
}
2124

2225
var doOnce sync.Once
@@ -25,15 +28,21 @@ var config *cfg
2528
func Get() *cfg {
2629
doOnce.Do(func() {
2730
config = &cfg{}
28-
config.PostgresHost = utils.MustGetEnv("POSTGRES_HOST")
29-
config.PostgresUser = url.PathEscape(utils.MustGetEnv("POSTGRES_USER"))
30-
config.PostgresPass = url.PathEscape(utils.MustGetEnv("POSTGRES_PASS"))
31-
config.PostgresUriArgs = utils.MustGetEnv("POSTGRES_URI_ARGS")
32-
config.PostgresDefaultDb = utils.GetEnv("POSTGRES_DEFAULT_DATABASE")
33-
config.CloudProvider = utils.GetEnv("POSTGRES_CLOUD_PROVIDER")
34-
config.AnnotationFilter = utils.GetEnv("POSTGRES_INSTANCE")
35-
if value, err := strconv.ParseBool(utils.GetEnv("KEEP_SECRET_NAME")); err == nil {
36-
config.KeepSecretName = value
31+
if err := env.Parse(config); err != nil {
32+
log.Fatal(err)
33+
}
34+
config.PostgresUser = url.PathEscape(config.PostgresUser)
35+
config.PostgresPass = url.PathEscape(config.PostgresPass)
36+
if strings.Contains(config.PostgresHost, ":") {
37+
parts := strings.Split(config.PostgresHost, ":")
38+
if len(parts) > 1 {
39+
port, err := strconv.ParseInt(parts[1], 10, 32)
40+
if err != nil {
41+
log.Fatal(err)
42+
}
43+
config.PostgresPort = uint32(port)
44+
config.PostgresHost = parts[0]
45+
}
3746
}
3847
})
3948
return config

pkg/controller/postgres/postgres_controller.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ import (
1717
"sigs.k8s.io/controller-runtime/pkg/client"
1818
"sigs.k8s.io/controller-runtime/pkg/controller"
1919
"sigs.k8s.io/controller-runtime/pkg/handler"
20+
logf "sigs.k8s.io/controller-runtime/pkg/log"
2021
"sigs.k8s.io/controller-runtime/pkg/manager"
2122
"sigs.k8s.io/controller-runtime/pkg/reconcile"
22-
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
2323
"sigs.k8s.io/controller-runtime/pkg/source"
2424
)
2525

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

222222
// Set privileges on schema
223-
schemaPrivilegesReader := postgres.PostgresSchemaPrivileges{database, owner, reader, schema, readerPrivs, false}
223+
schemaPrivilegesReader := postgres.PostgresSchemaPrivileges{DB: database, Creator: owner, Role: reader, Schema: schema, Privs: readerPrivs, CreateSchema: false}
224224
err = r.pg.SetSchemaPrivileges(schemaPrivilegesReader, reqLogger)
225225
if err != nil {
226226
reqLogger.Error(err, fmt.Sprintf("Could not give %s permissions \"%s\"", reader, readerPrivs))
227227
continue
228228
}
229-
schemaPrivilegesWriter := postgres.PostgresSchemaPrivileges{database, owner, writer, schema, readerPrivs, true}
229+
schemaPrivilegesWriter := postgres.PostgresSchemaPrivileges{DB: database, Creator: owner, Role: writer, Schema: schema, Privs: readerPrivs, CreateSchema: true}
230230
err = r.pg.SetSchemaPrivileges(schemaPrivilegesWriter, reqLogger)
231231
if err != nil {
232232
reqLogger.Error(err, fmt.Sprintf("Could not give %s permissions \"%s\"", writer, writerPrivs))
233233
continue
234234
}
235-
schemaPrivilegesOwner := postgres.PostgresSchemaPrivileges{database, owner, owner, schema, readerPrivs, true}
235+
schemaPrivilegesOwner := postgres.PostgresSchemaPrivileges{DB: database, Creator: owner, Role: owner, Schema: schema, Privs: readerPrivs, CreateSchema: true}
236236
err = r.pg.SetSchemaPrivileges(schemaPrivilegesOwner, reqLogger)
237237
if err != nil {
238238
reqLogger.Error(err, fmt.Sprintf("Could not give %s permissions \"%s\"", writer, writerPrivs))

pkg/controller/postgresuser/postgresuser_controller.go

+23-15
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ import (
2323
"sigs.k8s.io/controller-runtime/pkg/controller"
2424
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
2525
"sigs.k8s.io/controller-runtime/pkg/handler"
26+
logf "sigs.k8s.io/controller-runtime/pkg/log"
2627
"sigs.k8s.io/controller-runtime/pkg/manager"
2728
"sigs.k8s.io/controller-runtime/pkg/reconcile"
28-
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
2929
"sigs.k8s.io/controller-runtime/pkg/source"
3030
)
3131

@@ -45,7 +45,7 @@ func Add(mgr manager.Manager) error {
4545
// newReconciler returns a new reconcile.Reconciler
4646
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
4747
c := config.Get()
48-
pg, err := postgres.NewPG(c.PostgresHost, c.PostgresUser, c.PostgresPass, c.PostgresUriArgs, c.PostgresDefaultDb, c.CloudProvider, log.WithName("postgresuser"))
48+
pg, err := postgres.NewPG(c.PostgresHost, c.PostgresUser, c.PostgresPass, c.PostgresPort, c.PostgresUriArgs, c.PostgresDefaultDb, c.CloudProvider, log.WithName("postgresuser"))
4949
if err != nil {
5050
return nil
5151
}
@@ -55,6 +55,7 @@ func newReconciler(mgr manager.Manager) reconcile.Reconciler {
5555
scheme: mgr.GetScheme(),
5656
pg: pg,
5757
pgHost: c.PostgresHost,
58+
pgPort: c.PostgresPort,
5859
instanceFilter: c.AnnotationFilter,
5960
keepSecretName: c.KeepSecretName,
6061
}
@@ -100,6 +101,7 @@ type ReconcilePostgresUser struct {
100101
scheme *runtime.Scheme
101102
pg postgres.PG
102103
pgHost string
104+
pgPort uint32
103105
instanceFilter string
104106
keepSecretName bool // use secret name as defined in PostgresUserSpec
105107
}
@@ -280,9 +282,9 @@ func (r *ReconcilePostgresUser) addFinalizer(reqLogger logr.Logger, m *dbv1alpha
280282
}
281283

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

295297
templateData, err := renderTemplate(cr.Spec.SecretTemplate, templateContext{
296-
Role: role,
297-
Host: r.pgHost,
298-
Database: cr.Status.DatabaseName,
299-
Password: password,
298+
Role: role,
299+
Host: fmt.Sprintf("%s:%d", r.pgHost, r.pgPort),
300+
HostNoPort: r.pgHost,
301+
Port: r.pgPort,
302+
Login: login,
303+
Database: cr.Status.DatabaseName,
304+
Password: password,
300305
})
301306
if err != nil {
302307
return nil, fmt.Errorf("render templated keys: %w", err)
@@ -306,7 +311,7 @@ func (r *ReconcilePostgresUser) newSecretForCR(cr *dbv1alpha1.PostgresUser, role
306311
"POSTGRES_URL": []byte(pgUserUrl),
307312
"POSTGRES_JDBC_URL": []byte(pgJDBCUrl),
308313
"POSTGRES_DOTNET_URL": []byte(pgDotnetUrl),
309-
"HOST": []byte(r.pgHost),
314+
"HOST": []byte(fmt.Sprintf("%s:%d", r.pgHost, r.pgPort)),
310315
"DATABASE_NAME": []byte(cr.Status.DatabaseName),
311316
"ROLE": []byte(role),
312317
"PASSWORD": []byte(password),
@@ -364,7 +369,7 @@ func (r *ReconcilePostgresUser) getPostgresCR(instance *dbv1alpha1.PostgresUser)
364369
return &database, nil
365370
}
366371

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

383388
type templateContext struct {
384-
Host string
385-
Role string
386-
Database string
387-
Password string
389+
Host string
390+
HostNoPort string
391+
Port uint32
392+
Role string
393+
Login string
394+
Database string
395+
Password string
388396
}
389397

390398
func renderTemplate(data map[string]string, tc templateContext) (map[string][]byte, error) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package postgresuser
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
dbv1alpha1 "github.com/movetokube/postgres-operator/pkg/apis/db/v1alpha1"
8+
)
9+
10+
func assertEqual(t *testing.T, a interface{}, b interface{}) {
11+
t.Helper()
12+
13+
if a == b {
14+
return
15+
}
16+
17+
// debug.PrintStack()
18+
t.Errorf("Received %v (type %v), expected %v (type %v)", a, reflect.TypeOf(a), b, reflect.TypeOf(b))
19+
}
20+
21+
func TestReconcilePostgresUser_newSecretForCR(t *testing.T) {
22+
rpu := &ReconcilePostgresUser{
23+
pgHost: "localhost",
24+
pgPort: 5432,
25+
}
26+
27+
cr := &dbv1alpha1.PostgresUser{
28+
Status: dbv1alpha1.PostgresUserStatus{
29+
DatabaseName: "dbname",
30+
},
31+
Spec: dbv1alpha1.PostgresUserSpec{
32+
SecretTemplate: map[string]string{
33+
"all": "host={{.Host}} host_no_port={{.HostNoPort}} port={{.Port}} user={{.Role}} login={{.Login}} password={{.Password}} dbname={{.Database}}",
34+
},
35+
},
36+
}
37+
38+
secret, err := rpu.newSecretForCR(cr, "role", "password", "login")
39+
if err != nil {
40+
t.Fatalf("could not patch object: (%v)", err)
41+
}
42+
43+
if secret == nil {
44+
t.Fatalf("no secret returned")
45+
}
46+
47+
// keep old behavior of merging host and port
48+
assertEqual(t, string(secret.Data["HOST"]), "localhost:5432")
49+
assertEqual(t, string(secret.Data["all"]), "host=localhost:5432 host_no_port=localhost port=5432 user=role login=login password=password dbname=dbname")
50+
}

pkg/postgres/database.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ const (
1717
GRANT_CREATE_TABLE = `GRANT CREATE ON SCHEMA "%s" TO "%s"`
1818
GRANT_ALL_TABLES = `GRANT %s ON ALL TABLES IN SCHEMA "%s" TO "%s"`
1919
DEFAULT_PRIVS_SCHEMA = `ALTER DEFAULT PRIVILEGES FOR ROLE "%s" IN SCHEMA "%s" GRANT %s ON TABLES TO "%s"`
20-
REVOKE_CONNECT = `REVOKE CONNECT ON DATABASE "%s" FROM public`
21-
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()`
22-
GET_DB_OWNER = `SELECT pg_catalog.pg_get_userbyid(d.datdba) FROM pg_catalog.pg_database d WHERE d.datname = '%s'`
20+
REVOKE_CONNECT = `REVOKE CONNECT ON DATABASE "%s" FROM public`
21+
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()`
22+
GET_DB_OWNER = `SELECT pg_catalog.pg_get_userbyid(d.datdba) FROM pg_catalog.pg_database d WHERE d.datname = '%s'`
2323
GRANT_CREATE_SCHEMA = `GRANT CREATE ON DATABASE "%s" TO "%s"`
2424
)
2525

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

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

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

9898
func (c *pg) SetSchemaPrivileges(schemaPrivileges PostgresSchemaPrivileges, logger logr.Logger) error {
99-
tmpDb, err := GetConnection(c.user, c.pass, c.host, schemaPrivileges.DB, c.args, logger)
99+
tmpDb, err := GetConnection(c.user, c.pass, c.host, c.port, schemaPrivileges.DB, c.args, logger)
100100
if err != nil {
101101
return err
102102
}
@@ -125,7 +125,7 @@ func (c *pg) SetSchemaPrivileges(schemaPrivileges PostgresSchemaPrivileges, logg
125125
_, err = tmpDb.Exec(fmt.Sprintf(GRANT_CREATE_TABLE, schemaPrivileges.Schema, schemaPrivileges.Role))
126126
if err != nil {
127127
return err
128-
}
128+
}
129129
}
130130

131131
return nil

pkg/postgres/gcp.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ func (c *gcppg) CreateDB(dbname, role string) error {
5555
}
5656

5757
func (c *gcppg) DropRole(role, newOwner, database string, logger logr.Logger) error {
58-
59-
tmpDb, err := GetConnection(c.user, c.pass, c.host, database, c.args, logger)
58+
59+
tmpDb, err := GetConnection(c.user, c.pass, c.host, c.port, database, c.args, logger)
6060
q := fmt.Sprintf(GET_DB_OWNER, database)
61-
logger.Info("Checking master role: "+ q)
61+
logger.Info("Checking master role: " + q)
6262
rows, err := tmpDb.Query(q)
6363
if err != nil {
6464
return err
@@ -68,9 +68,9 @@ func (c *gcppg) DropRole(role, newOwner, database string, logger logr.Logger) er
6868
rows.Scan(&masterRole)
6969
}
7070

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

0 commit comments

Comments
 (0)