Skip to content

Commit 639b50f

Browse files
authored
Merge branch 'master' into fix-postgresuser-getpostgrescr
2 parents 4b3b3f7 + 31c8947 commit 639b50f

15 files changed

+192
-58
lines changed

README.md

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,5 @@
11
# External PostgreSQL server operator for Kubernetes
22

3-
---------------------------------------------------------
4-
### IMPORTANT UPDATE
5-
6-
### Restoring pushes to DockerHub repository `movetokube/postgres-operator`
7-
8-
Some history about this:
9-
10-
About 10 days after announcing the decition to sunset free organisations in dockerhub and receiving heavily negative community feedback
11-
Docker revoked their decision, did a 180-degree turn and did not sunset free legacy organisations.
12-
13-
Thus, new images of this operator will be pushed to both `movetokube/postgres-operator` and `ghcr.io/movetokube/postgres-operator` for your convenience.
14-
15-
Starting with ext-postgres-operator Helm chart version **1.2.3** images will be pulled from ghcr by default, you can change this if you like.
16-
17-
Here's how to install it (please install with care according to your configuration):
18-
```shell
19-
helm repo add ext-postgres-operator https://movetokube.github.io/postgres-operator/
20-
helm upgrade --install -n operators ext-postgres-operator ext-postgres-operator/ext-postgres-operator --version 1.2.3
21-
```
22-
23-
----------------------------------------------------------
243
## Sponsors
254

265
Please consider sponsoring my work
@@ -39,6 +18,7 @@ None
3918
* Creates Kubernetes secret with postgres_uri in the same namespace as CR
4019
* Support for AWS RDS and Azure Database for PostgresSQL
4120
* Support for managing CRs in dynamically created namespaces
21+
* Template secret values
4222

4323
## Cloud specific configuration
4424

@@ -173,6 +153,8 @@ spec:
173153
privileges: OWNER # Can be OWNER/READ/WRITE
174154
annotations: # Annotations to be propagated to the secrets metadata section (optional)
175155
foo: "bar"
156+
secretTemplate: # Output secrets can be customized using standard Go templates
157+
PQ_URL: "host={{.Host}} user={{.Role}} password={{.Password}} dbname={{.Database}}"
176158
```
177159

178160
This creates a user role `username-<hash>` and grants role `test-db-group`, `test-db-writer` or `test-db-reader` depending on `privileges` property. Its credentials are put in secret `my-secret-my-db-user` (unless `KEEP_SECRET_NAME` is enabled).
@@ -203,6 +185,21 @@ With the help of annotations it is possible to create annotation-based copies of
203185

204186
For more information and an example, see [kubernetes-replicator#pull-based-replication](https://github.com/mittwald/kubernetes-replicator#pull-based-replication)
205187

188+
#### Template Use Case
189+
190+
Users can specify the structure and content of secrets based on their unique requirements using standard
191+
[Go templates](https://pkg.go.dev/text/template#hdr-Actions). This flexibility allows for a more tailored approach to
192+
meeting the specific needs of different applications.
193+
194+
Available context:
195+
196+
| Variable | Meaning |
197+
|-------------|--------------------------|
198+
| `.Host` | Database host |
199+
| `.Role` | Generated user/role name |
200+
| `.Database` | Referenced database name |
201+
| `.Password` | Generated role password |
202+
206203
### Contribution
207204

208205
You can contribute to this project by opening a PR to merge to `master`, or one of the `vX.X.X` branches.

charts/ext-postgres-operator/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ type: application
1111
# This is the chart version. This version number should be incremented each time you make changes
1212
# to the chart and its templates, including the app version.
1313
# Versions are expected to follow Semantic Versioning (https://semver.org/)
14-
version: 1.2.3
14+
version: 1.2.6
1515

1616
# This is the version number of the application being deployed. This version number should be
1717
# incremented each time you make changes to the application. Versions are not expected to
1818
# follow Semantic Versioning. They should reflect the version the application is using.
1919
# It is recommended to use it with quotes.
20-
appVersion: "1.2.3"
20+
appVersion: "1.3.3"

charts/ext-postgres-operator/crds/db.movetokube.com_postgresusers_crd.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ spec:
3131
spec:
3232
description: PostgresUserSpec defines the desired state of PostgresUser
3333
properties:
34+
annotations:
35+
additionalProperties:
36+
type: string
37+
type: object
3438
database:
3539
type: string
3640
privileges:
@@ -39,6 +43,10 @@ spec:
3943
type: string
4044
secretName:
4145
type: string
46+
secretTemplate:
47+
additionalProperties:
48+
type: string
49+
type: object
4250
required:
4351
- database
4452
- role

charts/ext-postgres-operator/templates/operator.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ metadata:
55
labels:
66
{{- include "chart.labels" . | nindent 4 }}
77
namespace: {{ .Release.Namespace }}
8+
{{- with .Values.deploymentAnnotations }}
9+
annotations:
10+
{{- toYaml . | nindent 4 }}
11+
{{- end }}
812
spec:
913
replicas: {{ .Values.replicaCount }}
1014
selector:
@@ -51,7 +55,7 @@ spec:
5155
value: {{ include "chart.fullname" . }}
5256
{{- range $key, $value := .Values.env }}
5357
- name: {{ $key }}
54-
value: {{ $value }}
58+
value: {{ $value | quote }}
5559
{{- end }}
5660
{{- if .Values.volumeMounts }}
5761
volumeMounts:

charts/ext-postgres-operator/values.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ image:
88
repository: ghcr.io/movetokube/postgres-operator
99
pullPolicy: IfNotPresent
1010
# Overrides the image tag whose default is the chart appVersion.
11-
tag: "latest"
11+
tag: ""
1212

1313
# Override chart name, defaults to Chart.name
1414
nameOverride: ""
@@ -22,6 +22,8 @@ serviceAccount:
2222
# If not set and create is true, a name is generated using the fullname template
2323
name: ""
2424

25+
deploymentAnnotations: {}
26+
2527
podAnnotations: {}
2628

2729
# Additionnal labels to add to the pod.

deploy/crds/db.movetokube.com_postgresusers_crd.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ spec:
3131
spec:
3232
description: PostgresUserSpec defines the desired state of PostgresUser
3333
properties:
34+
annotations:
35+
additionalProperties:
36+
type: string
37+
type: object
3438
database:
3539
type: string
3640
privileges:
@@ -39,6 +43,10 @@ spec:
3943
type: string
4044
secretName:
4145
type: string
46+
secretTemplate:
47+
additionalProperties:
48+
type: string
49+
type: object
4250
required:
4351
- database
4452
- role

pkg/apis/db/v1alpha1/postgresuser_types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ type PostgresUserSpec struct {
1414
Database string `json:"database"`
1515
SecretName string `json:"secretName"`
1616
// +optional
17+
SecretTemplate map[string]string `json:"secretTemplate,omitempty"` // key-value, where key is secret field, value is go template
18+
// +optional
1719
Privileges string `json:"privileges"`
1820
// +optional
1921
Annotations map[string]string `json:"annotations,omitempty"`

pkg/apis/db/v1alpha1/zz_generated.deepcopy.go

Lines changed: 16 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/controller/postgres/postgres_controller.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,20 @@ func (r *ReconcilePostgres) Reconcile(request reconcile.Request) (_ reconcile.Re
220220
}
221221

222222
// Set privileges on schema
223-
err = r.pg.SetSchemaPrivileges(database, owner, reader, schema, readerPrivs, reqLogger)
223+
schemaPrivilegesReader := postgres.PostgresSchemaPrivileges{database, owner, reader, schema, readerPrivs, false}
224+
err = r.pg.SetSchemaPrivileges(schemaPrivilegesReader, reqLogger)
224225
if err != nil {
225226
reqLogger.Error(err, fmt.Sprintf("Could not give %s permissions \"%s\"", reader, readerPrivs))
226227
continue
227228
}
228-
err = r.pg.SetSchemaPrivileges(database, owner, writer, schema, writerPrivs, reqLogger)
229+
schemaPrivilegesWriter := postgres.PostgresSchemaPrivileges{database, owner, writer, schema, readerPrivs, true}
230+
err = r.pg.SetSchemaPrivileges(schemaPrivilegesWriter, reqLogger)
231+
if err != nil {
232+
reqLogger.Error(err, fmt.Sprintf("Could not give %s permissions \"%s\"", writer, writerPrivs))
233+
continue
234+
}
235+
schemaPrivilegesOwner := postgres.PostgresSchemaPrivileges{database, owner, owner, schema, readerPrivs, true}
236+
err = r.pg.SetSchemaPrivileges(schemaPrivilegesOwner, reqLogger)
229237
if err != nil {
230238
reqLogger.Error(err, fmt.Sprintf("Could not give %s permissions \"%s\"", writer, writerPrivs))
231239
continue

pkg/controller/postgres/postgres_controller_test.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -682,10 +682,10 @@ var _ = Describe("ReconcilePostgres", func() {
682682
// Expected method calls
683683
// customers schema
684684
pg.EXPECT().CreateSchema(name, name+"-group", "customers", gomock.Any()).Return(nil).Times(1)
685-
pg.EXPECT().SetSchemaPrivileges(name, name+"-group", gomock.Any(), "customers", gomock.Any(), gomock.Any()).Return(nil).Times(2)
685+
pg.EXPECT().SetSchemaPrivileges(name, name+"-group", gomock.Any(), "customers", gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(3)
686686
// stores schema
687687
pg.EXPECT().CreateSchema(name, name+"-group", "stores", gomock.Any()).Return(nil).Times(1)
688-
pg.EXPECT().SetSchemaPrivileges(name, name+"-group", gomock.Any(), "stores", gomock.Any(), gomock.Any()).Return(nil).Times(2)
688+
pg.EXPECT().SetSchemaPrivileges(name, name+"-group", gomock.Any(), "stores", gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(3)
689689
})
690690

691691
It("should update status", func() {
@@ -707,10 +707,12 @@ var _ = Describe("ReconcilePostgres", func() {
707707
// Expected method calls
708708
// customers schema errors
709709
pg.EXPECT().CreateSchema(name, name+"-group", "customers", gomock.Any()).Return(fmt.Errorf("Could not create schema")).Times(1)
710-
pg.EXPECT().SetSchemaPrivileges(name, name+"-group", gomock.Any(), "customers", gomock.Any(), gomock.Any()).Return(nil).Times(0)
710+
pg.EXPECT().SetSchemaPrivileges(name, name+"-group", gomock.Any(), "customers", gomock.Any(), gomock.Any() ,gomock.Any()).Return(nil).Times(0)
711711
// stores schema
712712
pg.EXPECT().CreateSchema(name, name+"-group", "stores", gomock.Any()).Return(nil).Times(1)
713-
pg.EXPECT().SetSchemaPrivileges(name, name+"-group", gomock.Any(), "stores", gomock.Any(), gomock.Any()).Return(nil).Times(2)
713+
pg.EXPECT().SetSchemaPrivileges(name, name+"-group", name+"-reader", "stores", gomock.Any(), false, gomock.Any()).Return(nil).Times(1)
714+
pg.EXPECT().SetSchemaPrivileges(name, name+"-group", name+"-writer", "stores", gomock.Any(), true, gomock.Any()).Return(nil).Times(1)
715+
pg.EXPECT().SetSchemaPrivileges(name, name+"-group", name+"-group", "stores", gomock.Any(), true, gomock.Any()).Return(nil).Times(1)
714716
})
715717

716718
It("should update status", func() {
@@ -751,7 +753,7 @@ var _ = Describe("ReconcilePostgres", func() {
751753
// Expected method calls
752754
// customers schema
753755
pg.EXPECT().CreateSchema(name, name+"-group", "customers", gomock.Any()).Return(nil).Times(1)
754-
pg.EXPECT().SetSchemaPrivileges(name, name+"-group", gomock.Any(), "customers", gomock.Any(), gomock.Any()).Return(nil).Times(2)
756+
pg.EXPECT().SetSchemaPrivileges(name, name+"-group", gomock.Any(), "customers", gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(3)
755757
// stores schema already exists
756758
pg.EXPECT().CreateSchema(name, name+"-group", "stores", gomock.Any()).Times(0)
757759
// Call reconcile

pkg/controller/postgresuser/postgresuser_controller.go

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package postgresuser
22

33
import (
4+
"bytes"
45
"context"
56
goerr "errors"
67
"fmt"
8+
"text/template"
79

810
"github.com/movetokube/postgres-operator/pkg/config"
911

@@ -160,7 +162,11 @@ func (r *ReconcilePostgresUser) Reconcile(request reconcile.Request) (reconcile.
160162

161163
// Creation logic
162164
var role, login string
163-
password := utils.GetRandomString(15)
165+
password, err := utils.GetSecureRandomString(15)
166+
167+
if err != nil {
168+
return r.requeue(instance, err)
169+
}
164170

165171
if instance.Status.PostgresRole == "" {
166172
// We need to get the Postgres CR to get the group role name
@@ -221,7 +227,10 @@ func (r *ReconcilePostgresUser) Reconcile(request reconcile.Request) (reconcile.
221227
return r.requeue(instance, err)
222228
}
223229

224-
secret := r.newSecretForCR(instance, role, password, login)
230+
secret, err := r.newSecretForCR(instance, role, password, login)
231+
if err != nil {
232+
return r.requeue(instance, err)
233+
}
225234

226235
// Set PostgresUser instance as the owner and controller
227236
if err := controllerutil.SetControllerReference(instance, secret, r.scheme); err != nil {
@@ -270,7 +279,7 @@ func (r *ReconcilePostgresUser) addFinalizer(reqLogger logr.Logger, m *dbv1alpha
270279
return nil
271280
}
272281

273-
func (r *ReconcilePostgresUser) newSecretForCR(cr *dbv1alpha1.PostgresUser, role, password, login string) *corev1.Secret {
282+
func (r *ReconcilePostgresUser) newSecretForCR(cr *dbv1alpha1.PostgresUser, role, password, login string) (*corev1.Secret, error) {
274283
pgUserUrl := fmt.Sprintf("postgresql://%s:%s@%s/%s", role, password, r.pgHost, cr.Status.DatabaseName)
275284
pgJDBCUrl := fmt.Sprintf("jdbc:postgresql://%s/%s", r.pgHost, cr.Status.DatabaseName)
276285
pgDotnetUrl := fmt.Sprintf("User ID=%s;Password=%s;Host=%s;Port=5432;Database=%s;", role, password, r.pgHost, cr.Status.DatabaseName)
@@ -283,24 +292,40 @@ func (r *ReconcilePostgresUser) newSecretForCR(cr *dbv1alpha1.PostgresUser, role
283292
name = cr.Spec.SecretName
284293
}
285294

295+
templateData, err := renderTemplate(cr.Spec.SecretTemplate, templateContext{
296+
Role: role,
297+
Host: r.pgHost,
298+
Database: cr.Status.DatabaseName,
299+
Password: password,
300+
})
301+
if err != nil {
302+
return nil, fmt.Errorf("render templated keys: %w", err)
303+
}
304+
305+
data := map[string][]byte{
306+
"POSTGRES_URL": []byte(pgUserUrl),
307+
"POSTGRES_JDBC_URL": []byte(pgJDBCUrl),
308+
"POSTGRES_DOTNET_URL": []byte(pgDotnetUrl),
309+
"HOST": []byte(r.pgHost),
310+
"DATABASE_NAME": []byte(cr.Status.DatabaseName),
311+
"ROLE": []byte(role),
312+
"PASSWORD": []byte(password),
313+
"LOGIN": []byte(login),
314+
}
315+
// templates may override standard keys
316+
for k, v := range templateData {
317+
data[k] = v
318+
}
319+
286320
return &corev1.Secret{
287321
ObjectMeta: metav1.ObjectMeta{
288322
Name: name,
289323
Namespace: cr.Namespace,
290324
Labels: labels,
291325
Annotations: annotations,
292326
},
293-
Data: map[string][]byte{
294-
"POSTGRES_URL": []byte(pgUserUrl),
295-
"POSTGRES_JDBC_URL": []byte(pgJDBCUrl),
296-
"POSTGRES_DOTNET_URL": []byte(pgDotnetUrl),
297-
"HOST": []byte(r.pgHost),
298-
"DATABASE_NAME": []byte(cr.Status.DatabaseName),
299-
"ROLE": []byte(role),
300-
"PASSWORD": []byte(password),
301-
"LOGIN": []byte(login),
302-
},
303-
}
327+
Data: data,
328+
}, nil
304329
}
305330

306331
func (r *ReconcilePostgresUser) requeue(cr *dbv1alpha1.PostgresUser, reason error) (reconcile.Result, error) {
@@ -354,3 +379,29 @@ func (r *ReconcilePostgresUser) addOwnerRef(reqLogger logr.Logger, instance *dbv
354379
err = r.client.Update(context.TODO(), instance)
355380
return err
356381
}
382+
383+
type templateContext struct {
384+
Host string
385+
Role string
386+
Database string
387+
Password string
388+
}
389+
390+
func renderTemplate(data map[string]string, tc templateContext) (map[string][]byte, error) {
391+
if len(data) == 0 {
392+
return nil, nil
393+
}
394+
var out = make(map[string][]byte, len(data))
395+
for key, templ := range data {
396+
parsed, err := template.New("").Parse(templ)
397+
if err != nil {
398+
return nil, fmt.Errorf("parse template %q: %w", key, err)
399+
}
400+
var content bytes.Buffer
401+
if err := parsed.Execute(&content, tc); err != nil {
402+
return nil, fmt.Errorf("execute template %q: %w", key, err)
403+
}
404+
out[key] = content.Bytes()
405+
}
406+
return out, nil
407+
}

0 commit comments

Comments
 (0)