diff --git a/packages/apps/postgres/templates/init-script.yaml b/packages/apps/postgres/templates/init-script.yaml index 1dfb4960a..03da82eb2 100644 --- a/packages/apps/postgres/templates/init-script.yaml +++ b/packages/apps/postgres/templates/init-script.yaml @@ -204,13 +204,18 @@ stringData: echo "== assign roles to users" psql -v ON_ERROR_STOP=1 --echo-all <<\EOT {{- range $database, $d := .Values.databases }} + {{- /* roles, roles.admin and roles.readonly are all optional: a database + declared without them grants nothing and revokes every managed role. */}} + {{- $roles := default dict (default dict $d).roles }} + {{- $admin := default list $roles.admin }} + {{- $readonly := default list $roles.readonly }} {{- range $user, $u := $.Values.users }} - {{- if has $user $d.roles.admin }} + {{- if has $user $admin }} GRANT "{{ $database }}_admin" TO "{{ $user }}"; {{- else }} REVOKE "{{ $database }}_admin" FROM "{{ $user }}"; {{- end }} - {{- if has $user $d.roles.readonly }} + {{- if has $user $readonly }} GRANT "{{ $database }}_readonly" TO "{{ $user }}"; {{- else }} REVOKE "{{ $database }}_readonly" FROM "{{ $user }}"; diff --git a/packages/apps/postgres/tests/database_roles_test.yaml b/packages/apps/postgres/tests/database_roles_test.yaml new file mode 100644 index 000000000..8c65451a6 --- /dev/null +++ b/packages/apps/postgres/tests/database_roles_test.yaml @@ -0,0 +1,97 @@ +suite: databases render without roles + +# `databases..roles`, `roles.admin` and `roles.readonly` are all optional +# in the schema. The init script used to dereference `$d.roles.admin` for every +# database, so a database declared with only `extensions` (or with nothing at +# all) failed the whole render with "nil pointer evaluating interface {}.admin". +# A missing list must read as an empty list: no user is granted the role, and +# every managed role is revoked from every user, exactly as `roles: {}` does. + +templates: + - templates/init-script.yaml + +release: + name: pg-test + namespace: tenant-test + +set: + _cluster: + scheduling: {} + users: + app: + password: secret + +tests: + - it: "a database with only extensions renders and revokes both managed roles" + set: + databases: + events: + extensions: + - hstore + documentSelector: + path: metadata.name + value: pg-test-init-script + asserts: + - matchRegex: + path: stringData["init.sh"] + pattern: 'REVOKE "events_admin" FROM "app";' + - matchRegex: + path: stringData["init.sh"] + pattern: 'REVOKE "events_readonly" FROM "app";' + - notMatchRegex: + path: stringData["init.sh"] + pattern: 'GRANT "events_(admin|readonly)" TO' + - matchRegex: + path: stringData["init.sh"] + pattern: 'CREATE EXTENSION IF NOT EXISTS hstore;' + + - it: "a database with an empty body renders the same as roles: {}" + set: + databases: + events: {} + documentSelector: + path: metadata.name + value: pg-test-init-script + asserts: + - matchRegex: + path: stringData["init.sh"] + pattern: 'REVOKE "events_admin" FROM "app";' + - matchRegex: + path: stringData["init.sh"] + pattern: 'REVOKE "events_readonly" FROM "app";' + + - it: "roles with only admin grants admin and revokes readonly" + set: + databases: + events: + roles: + admin: + - app + documentSelector: + path: metadata.name + value: pg-test-init-script + asserts: + - matchRegex: + path: stringData["init.sh"] + pattern: 'GRANT "events_admin" TO "app";' + - matchRegex: + path: stringData["init.sh"] + pattern: 'REVOKE "events_readonly" FROM "app";' + + - it: "roles with only readonly grants readonly and revokes admin" + set: + databases: + events: + roles: + readonly: + - app + documentSelector: + path: metadata.name + value: pg-test-init-script + asserts: + - matchRegex: + path: stringData["init.sh"] + pattern: 'REVOKE "events_admin" FROM "app";' + - matchRegex: + path: stringData["init.sh"] + pattern: 'GRANT "events_readonly" TO "app";'