Skip to content

Commit 2e2a005

Browse files
authored
Merge pull request #170 from opengisch/specific-roles
implement specific roles
2 parents e2c5cc7 + 4b4c73e commit 2e2a005

File tree

15 files changed

+360
-28
lines changed

15 files changed

+360
-28
lines changed

docs/docs/cli/role.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
usage: pum role [-h] {create,grant,revoke,drop}
1+
usage: pum role [-h] [--suffix SUFFIX] [--no-create-generic] {create,grant,revoke,drop}
22
### positional arguments:
33
- `{create,grant,revoke,drop}`: Action to perform
44
### options:
55
- `-h, --help`: show this help message and exit
6+
- `--suffix SUFFIX`: Create DB-specific roles by appending this suffix to each role name (e.g. 'lausanne' creates 'role_lausanne')
7+
- `--no-create-generic`: When using --suffix, skip creating the generic (base) roles and granting inheritance

docs/docs/roles.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,48 @@ roles:
5252

5353
- The `RoleManager` class can be initialized with a list of roles (as dicts or `Role` objects).
5454
- It checks that inherited roles exist and builds a mapping of role names to `Role` objects.
55-
- The `create` method can be used to create roles and grant permissions in the database.
55+
- The `create_roles` method creates roles and optionally grants permissions in the database.
5656
- Permissions are enforced by granting the specified actions on the listed schemas to the role.
5757

5858
**PermissionType Enum:**
5959

6060
- `read`: Grants `USAGE` and `SELECT` privileges.
6161
- `write`: Grants `INSERT`, `UPDATE`, and `DELETE` privileges.
6262

63+
## DB-Specific Roles
64+
65+
When you have several database instances of the same module in a single PostgreSQL cluster, the roles defined in the configuration would be shared across all databases. To isolate permissions per database, you can create **DB-specific roles** by providing a `suffix`.
66+
67+
For example, with a role `tww_user` and suffix `lausanne`:
68+
69+
1. A specific role `tww_user_lausanne` is created and granted the configured permissions.
70+
2. The generic role `tww_user` is also created (unless `create_generic=False` / `--no-create-generic`).
71+
3. The generic role is granted membership of the specific role, so that `tww_user` inherits `tww_user_lausanne`'s permissions.
72+
73+
This way, users assigned to `tww_user` automatically get access to the Lausanne database, and you can repeat the process for other databases (e.g. `tww_user_zurich`).
74+
75+
### CLI Usage
76+
77+
```bash
78+
# Create specific roles with suffix, plus generic roles with inheritance
79+
pum -p mydb role create --suffix lausanne
80+
81+
# Create only the specific roles (no generic roles)
82+
pum -p mydb role create --suffix lausanne --no-create-generic
83+
```
84+
85+
### Python API
86+
87+
```python
88+
role_manager.create_roles(
89+
connection=conn,
90+
suffix="lausanne", # creates <role>_lausanne
91+
create_generic=True, # also create the base roles (default)
92+
grant=True, # grant configured permissions
93+
commit=True,
94+
)
95+
```
96+
6397
## Summary
6498

6599
- Define roles and permissions in your config YAML under the `roles` key.

pum/changelog.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
class Changelog:
2121
"""This class represent a changelog directory.
2222
The directory name is the version of the changelog.
23+
24+
.. versionadded:: 1.0.0
2325
"""
2426

2527
def __init__(self, dir):

pum/cli.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,18 @@ def formatter_class(prog):
238238
parser_role.add_argument(
239239
"action", choices=["create", "grant", "revoke", "drop"], help="Action to perform"
240240
)
241+
parser_role.add_argument(
242+
"--suffix",
243+
help="Create DB-specific roles by appending this suffix to each role name (e.g. 'lausanne' creates 'role_lausanne')",
244+
type=str,
245+
default=None,
246+
)
247+
parser_role.add_argument(
248+
"--no-create-generic",
249+
help="When using --suffix, skip creating the generic (base) roles and granting inheritance",
250+
action="store_true",
251+
default=False,
252+
)
241253

242254
# Parser for the "check" command
243255
parser_checker = subparsers.add_parser(
@@ -545,7 +557,13 @@ def cli() -> int: # noqa: PLR0912
545557
exit_code = 1
546558
else:
547559
if args.action == "create":
548-
config.role_manager().create_roles(connection=conn)
560+
config.role_manager().create_roles(
561+
connection=conn,
562+
suffix=args.suffix,
563+
create_generic=not args.no_create_generic,
564+
grant=True,
565+
commit=True,
566+
)
549567
elif args.action == "grant":
550568
config.role_manager().grant_permissions(connection=conn)
551569
elif args.action == "revoke":

pum/dumper.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414

1515

1616
class DumpFormat(Enum):
17+
"""Enumeration of supported dump formats.
18+
19+
.. versionadded:: 1.0.0
20+
"""
21+
1722
CUSTOM = "custom"
1823
PLAIN = "plain"
1924

@@ -26,7 +31,10 @@ def to_pg_dump_flag(self):
2631

2732

2833
class Dumper:
29-
"""This class is used to dump and restore a Postgres database."""
34+
"""This class is used to dump and restore a Postgres database.
35+
36+
.. versionadded:: 1.0.0
37+
"""
3038

3139
def __init__(self, pg_connection: str, dump_path: str):
3240
"""Initialize the Dumper.

pum/exceptions.py

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,96 @@
11
# Base exception for all PUM errors
22
class PumException(Exception):
3-
"""Base class for all exceptions raised by PUM."""
3+
"""Base class for all exceptions raised by PUM.
4+
5+
.. versionadded:: 1.0.0
6+
"""
47

58

69
class PumDependencyError(PumException):
7-
"""Exception when dependency are not resolved"""
10+
"""Exception when dependency are not resolved.
11+
12+
.. versionadded:: 1.0.0
13+
"""
814

915

1016
# --- Configuration and Validation Errors ---
1117

1218

1319
class PumConfigError(PumException):
14-
"""Exception raised for errors in the PUM configuration."""
20+
"""Exception raised for errors in the PUM configuration.
21+
22+
.. versionadded:: 1.0.0
23+
"""
1524

1625

1726
class PumInvalidChangelog(PumException):
18-
"""Exception raised for invalid changelog."""
27+
"""Exception raised for invalid changelog.
28+
29+
.. versionadded:: 1.0.0
30+
"""
1931

2032

2133
# --- Schema Migration Errors ---
2234
class PumSchemaMigrationError(PumException):
23-
"""Exception raised for errors related to schema migrations."""
35+
"""Exception raised for errors related to schema migrations.
36+
37+
.. versionadded:: 1.0.0
38+
"""
2439

2540

2641
class PumSchemaMigrationNoBaselineError(PumSchemaMigrationError):
27-
"""Exception raised when no baseline version is found in the migration table."""
42+
"""Exception raised when no baseline version is found in the migration table.
43+
44+
.. versionadded:: 1.0.0
45+
"""
2846

2947

3048
# --- Hook Errors ---
3149

3250

3351
class PumHookError(PumException):
34-
"""Exception raised for errors by an invalid hook."""
52+
"""Exception raised for errors by an invalid hook.
53+
54+
.. versionadded:: 1.0.0
55+
"""
3556

3657

3758
# --- Changelog/SQL Errors ---
3859

3960

4061
class PumSqlError(PumException):
41-
"""Exception raised for SQL-related errors in PUM."""
62+
"""Exception raised for SQL-related errors in PUM.
63+
64+
.. versionadded:: 1.0.0
65+
"""
4266

4367

4468
# --- Dump/Restore Errors (for dumper.py, if needed) ---
4569

4670

4771
class PgDumpCommandError(PumException):
48-
"""Exception raised for invalid pg_dump command."""
72+
"""Exception raised for invalid pg_dump command.
73+
74+
.. versionadded:: 1.0.0
75+
"""
4976

5077

5178
class PgDumpFailed(PumException):
52-
"""Exception raised when pg_dump fails."""
79+
"""Exception raised when pg_dump fails.
80+
81+
.. versionadded:: 1.0.0
82+
"""
5383

5484

5585
class PgRestoreCommandError(PumException):
56-
"""Exception raised for invalid pg_restore command."""
86+
"""Exception raised for invalid pg_restore command.
87+
88+
.. versionadded:: 1.0.0
89+
"""
5790

5891

5992
class PgRestoreFailed(PumException):
60-
"""Exception raised when pg_restore fails."""
93+
"""Exception raised when pg_restore fails.
94+
95+
.. versionadded:: 1.0.0
96+
"""

pum/feedback.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ class Feedback(abc.ABC):
1111
1212
This class provides methods for progress reporting and cancellation handling.
1313
Subclasses should implement the abstract methods to provide custom feedback mechanisms.
14+
15+
.. versionadded:: 1.3.0
1416
"""
1517

1618
def __init__(self) -> None:
@@ -89,6 +91,8 @@ class LogFeedback(Feedback):
8991
9092
This is the default feedback implementation that simply logs messages
9193
without any UI interaction.
94+
95+
.. versionadded:: 1.3.0
9296
"""
9397

9498
def report_progress(self, message: str, current: int = 0, total: int = 0) -> None:
@@ -106,6 +110,8 @@ class SilentFeedback(Feedback):
106110
"""Feedback implementation that does nothing.
107111
108112
This can be used when no feedback is desired.
113+
114+
.. versionadded:: 1.3.0
109115
"""
110116

111117
def report_progress(self, message: str, current: int = 0, total: int = 0) -> None:

pum/hook.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class HookBase(abc.ABC):
1919
This class defines the interface for application hooks that can be implemented in Python.
2020
It requires the implementation of the `run_hook` method, which will be called during the migration process.
2121
It can call the execute method to run SQL statements with the provided connection and parameters.
22+
23+
.. versionadded:: 1.0.0
2224
"""
2325

2426
def __init__(self) -> None:
@@ -69,7 +71,10 @@ def execute(
6971

7072
class HookHandler:
7173
"""Handler for application hooks.
72-
This class manages the execution of application hooks, which can be either SQL files or Python functions."""
74+
This class manages the execution of application hooks, which can be either SQL files or Python functions.
75+
76+
.. versionadded:: 1.0.0
77+
"""
7378

7479
def __init__(
7580
self,

pum/parameter.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class ParameterType(Enum):
1212
DECIMAL (str): Represents a decimal parameter type.
1313
PATH (str): Represents a path parameter type.
1414
15+
.. versionadded:: 1.0.0
1516
"""
1617

1718
BOOLEAN = "boolean"
@@ -26,7 +27,10 @@ def __str__(self) -> str:
2627

2728

2829
class ParameterDefinition:
29-
"""A class to define a migration parameter."""
30+
"""A class to define a migration parameter.
31+
32+
.. versionadded:: 1.0.0
33+
"""
3034

3135
def __init__(
3236
self,

pum/pum_config.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,10 @@ def _exception_chain_text(exc: BaseException) -> str:
100100

101101

102102
class PumConfig:
103-
"""A class to hold configuration settings."""
103+
"""A class to hold configuration settings.
104+
105+
.. versionadded:: 1.0.0
106+
"""
104107

105108
def __init__(
106109
self,

0 commit comments

Comments
 (0)