Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ Every PostgresUser has a generated Kubernetes secret attached to it, which conta
|----------------------|---------------------|
| `DATABASE_NAME` | Name of the database, same as in `Postgres` CR, copied for convenience |
| `HOST` | PostgreSQL server host (including port number) |
| `URI_ARGS` | URI Args, same as in `Postgres` CR, copied for convenience |
| `PASSWORD` | Autogenerated password for user |
| `ROLE` | Autogenerated role with login enabled (user) |
| `LOGIN` | Same as `ROLE`. In case `POSTGRES_CLOUD_PROVIDER` is set to "Azure", `LOGIN` it will be set to `{role}@{serverName}`, serverName is extracted from `POSTGRES_USER` from operator's config. |
Expand All @@ -203,6 +204,10 @@ Every PostgresUser has a generated Kubernetes secret attached to it, which conta
| `HOSTNAME` | The PostgreSQL server hostname (without port) |
| `PORT` | The PostgreSQL server port |

| Functions | Meaning |
|----------------|-------------------------------------------------------------------|
| `mergeUriArgs` | Merge any provided uri args with any set in the `Postgres` CR |

### Multiple operator support

Run multiple operator instances by setting unique POSTGRES_INSTANCE values and using annotations in your CRs to assign them.
Expand Down
9 changes: 7 additions & 2 deletions internal/controller/postgresuser_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"maps"
"net"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -17,7 +18,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"

"github.com/go-logr/logr"
dbv1alpha1 "github.com/movetokube/postgres-operator/api/v1alpha1"
"github.com/movetokube/postgres-operator/pkg/config"
"github.com/movetokube/postgres-operator/pkg/postgres"
Expand All @@ -30,6 +30,7 @@ type PostgresUserReconciler struct {
Scheme *runtime.Scheme
pg postgres.PG
pgHost string
pgUriArgs string
instanceFilter string
keepSecretName bool // use secret name as defined in PostgresUserSpec
}
Expand All @@ -41,6 +42,7 @@ func NewPostgresUserReconciler(mgr manager.Manager, cfg *config.Cfg, pg postgres
Scheme: mgr.GetScheme(),
pg: pg,
pgHost: cfg.PostgresHost,
pgUriArgs: cfg.PostgresUriArgs,
instanceFilter: cfg.AnnotationFilter,
keepSecretName: cfg.KeepSecretName,
}
Expand Down Expand Up @@ -259,6 +261,7 @@ func (r *PostgresUserReconciler) newSecretForCR(reqLogger logr.Logger, cr *dbv1a
templateData, err := utils.RenderTemplate(cr.Spec.SecretTemplate, utils.TemplateContext{
Role: role,
Host: r.pgHost,
UriArgs: r.pgUriArgs,
Database: cr.Status.DatabaseName,
Password: password,
Hostname: hostname,
Expand All @@ -274,6 +277,7 @@ func (r *PostgresUserReconciler) newSecretForCR(reqLogger logr.Logger, cr *dbv1a
"POSTGRES_DOTNET_URL": []byte(pgDotnetUrl),
"HOST": []byte(r.pgHost),
"DATABASE_NAME": []byte(cr.Status.DatabaseName),
"URI_ARGS": []byte(r.pgUriArgs),
"ROLE": []byte(role),
"PASSWORD": []byte(password),
"LOGIN": []byte(login),
Expand Down Expand Up @@ -310,7 +314,8 @@ func (r *PostgresUserReconciler) addFinalizer(ctx context.Context, reqLogger log
}
return nil
}
func (r *PostgresUserReconciler) addOwnerRef(ctx context.Context, reqLogger logr.Logger, instance *dbv1alpha1.PostgresUser) error {

func (r *PostgresUserReconciler) addOwnerRef(ctx context.Context, _ logr.Logger, instance *dbv1alpha1.PostgresUser) error {
// Search postgres database CR
pg, err := r.getPostgresCR(ctx, instance)
if err != nil {
Expand Down
31 changes: 28 additions & 3 deletions internal/controller/postgresuser_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,12 +436,15 @@ var _ = Describe("PostgresUser Controller", func() {
BeforeEach(func() {
userWithTemplate := postgresUser.DeepCopy()
userWithTemplate.Spec.SecretTemplate = map[string]string{
"CUSTOM_KEY": "User: {{.Role}}, DB: {{.Database}}",
"PGPASSWORD": "{{.Password}}",
"CUSTOM_KEY": "User: {{.Role}}, DB: {{.Database}}",
"PGPASSWORD": "{{.Password}}",
"URIARGSFILTER": `postgres://foobar?{{ "sslmode=no-verify" | mergeUriArgs }}`,
"URIARGSFILTER_COMBINED": `postgres://foobar?{{ "logging=true" | mergeUriArgs }}`,
"URIARGSFILTER_EMPTYSTRING": `postgres://foobar?{{ "" | mergeUriArgs }}`,
}

initClient(postgresDB, userWithTemplate, false)
})

AfterEach(func() {
// Clean up any created secrets
secretList := &corev1.SecretList{}
Expand All @@ -458,6 +461,8 @@ var _ = Describe("PostgresUser Controller", func() {
pg.EXPECT().GrantRole(gomock.Any(), gomock.Any()).Return(nil)
pg.EXPECT().AlterDefaultLoginRole(gomock.Any(), gomock.Any()).Return(nil)

rp.pgUriArgs = "sslmode=disable"

// Call Reconcile
err := runReconcile(rp, ctx, req)
Expect(err).NotTo(HaveOccurred())
Expand Down Expand Up @@ -492,6 +497,10 @@ var _ = Describe("PostgresUser Controller", func() {
pgUrl := string(foundSecret.Data["POSTGRES_URL"])
Expect(pgUrl).To(ContainSubstring(actualRole))

// Check if URI_ARGS contains the uri args from the secret
uriArgs := string(foundSecret.Data["URI_ARGS"])
Expect(uriArgs).To(Equal("sslmode=disable"))

// Check if the template was applied using the data in the actual secret
// Directly check the custom keys we're expecting
Expect(foundSecret.Data).To(HaveKey("CUSTOM_KEY"))
Expand All @@ -503,6 +512,22 @@ var _ = Describe("PostgresUser Controller", func() {
Expect(foundSecret.Data).To(HaveKey("PGPASSWORD"))
pgPassword := string(foundSecret.Data["PGPASSWORD"])
Expect(pgPassword).NotTo(BeEmpty())

// Check that uri parameters are copied
Expect(foundSecret.Data).To(HaveKey("URIARGSFILTER"))
uriArgsFilter := string(foundSecret.Data["URIARGSFILTER"])
Expect(uriArgsFilter).To(Equal("postgres://foobar?sslmode=disable"))

// Check that uri parameters are merged with none in the templates
Expect(foundSecret.Data).To(HaveKey("URIARGSFILTER_EMPTYSTRING"))
uriArgsFilterEmptyString := string(foundSecret.Data["URIARGSFILTER_EMPTYSTRING"])
Expect(uriArgsFilterEmptyString).To(Equal("postgres://foobar?sslmode=disable"))

// Check that uri parameters are merged
Expect(foundSecret.Data).To(HaveKey("URIARGSFILTER_COMBINED"))
uriArgsFilterCombined := string(foundSecret.Data["URIARGSFILTER_COMBINED"])
Expect(uriArgsFilterCombined).To(Equal("postgres://foobar?logging=true&sslmode=disable"))

})
})
})
Expand Down
21 changes: 20 additions & 1 deletion pkg/utils/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package utils
import (
"bytes"
"fmt"
"net/url"
"text/template"
)

Expand All @@ -13,6 +14,7 @@ type TemplateContext struct {
Password string
Hostname string // Hostname is different from Host as it does not contain the port number.
Port string
UriArgs string
}

func RenderTemplate(data map[string]string, tc TemplateContext) (map[string][]byte, error) {
Expand All @@ -21,7 +23,24 @@ func RenderTemplate(data map[string]string, tc TemplateContext) (map[string][]by
}
var out = make(map[string][]byte, len(data))
for key, templ := range data {
parsed, err := template.New("").Parse(templ)
tmplObj := template.New("")
tmplObj.Funcs(template.FuncMap{
"mergeUriArgs": func(uriArgs string) (string, error) {
inputArgs, err := url.ParseQuery(uriArgs)
if err != nil {
return uriArgs, fmt.Errorf("unable to parse input uri args: %w", err)
}
pgArgs, err := url.ParseQuery(tc.UriArgs)
if err != nil {
return uriArgs, fmt.Errorf("unable to parse pg uri args: %w", err)
}
for argName, values := range pgArgs {
inputArgs.Set(argName, values[0])
}
return inputArgs.Encode(), nil
},
})
parsed, err := tmplObj.Parse(templ)
if err != nil {
return nil, fmt.Errorf("parse template %q: %w", key, err)
}
Expand Down