phlo-postgres 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/PKG-INFO +1 -1
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/README.md +2 -2
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/pyproject.toml +1 -1
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres/__init__.py +1 -1
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres/cli.py +32 -8
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres/service.yaml +2 -2
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres.egg-info/PKG-INFO +1 -1
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/tests/test_postgres_cli.py +32 -6
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/setup.cfg +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres/authorization.py +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres/cli_plugin.py +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres/exporter_service.yaml +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres/plugin.py +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres/publish_target.py +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres/resource.py +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres/settings.py +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres/volume_setup.yaml +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres.egg-info/SOURCES.txt +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres.egg-info/dependency_links.txt +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres.egg-info/entry_points.txt +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres.egg-info/requires.txt +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/src/phlo_postgres.egg-info/top_level.txt +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/tests/test_authorization.py +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/tests/test_integration_postgres.py +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/tests/test_postgres_plugin.py +0 -0
- {phlo_postgres-0.3.0 → phlo_postgres-0.3.2}/tests/test_resource.py +0 -0
|
@@ -18,7 +18,7 @@ phlo plugin install postgres
|
|
|
18
18
|
|
|
19
19
|
| Variable | Default | Description |
|
|
20
20
|
| ------------------------ | -------- | ---------------------- |
|
|
21
|
-
| `POSTGRES_PORT` | `
|
|
21
|
+
| `POSTGRES_PORT` | `10000` | PostgreSQL host port |
|
|
22
22
|
| `POSTGRES_USER` | `phlo` | Database username |
|
|
23
23
|
| `POSTGRES_PASSWORD` | `phlo` | Database password |
|
|
24
24
|
| `POSTGRES_DB` | `phlo` | Database name |
|
|
@@ -57,7 +57,7 @@ phlo services start --service postgres,postgres-exporter
|
|
|
57
57
|
|
|
58
58
|
## Endpoints
|
|
59
59
|
|
|
60
|
-
- **PostgreSQL**: `localhost:
|
|
60
|
+
- **PostgreSQL**: `localhost:10000`
|
|
61
61
|
- **Exporter Metrics**: `http://localhost:9187/metrics`
|
|
62
62
|
|
|
63
63
|
## Entry Points
|
|
@@ -23,13 +23,22 @@ from subprocess import TimeoutExpired
|
|
|
23
23
|
|
|
24
24
|
import click
|
|
25
25
|
|
|
26
|
+
from phlo.cli.authorization_wrappers import enforce_surface_mutation_authorization
|
|
26
27
|
from phlo.cli.commands.services.utils import (
|
|
27
|
-
|
|
28
|
+
ensure_compose_project,
|
|
28
29
|
require_container_backend as _require_selected_container_backend,
|
|
29
30
|
)
|
|
30
31
|
from phlo.cli.infrastructure.command import CommandError, run_command
|
|
31
32
|
from phlo.cli.infrastructure.compose import compose_base_cmd
|
|
32
33
|
from phlo.cli.infrastructure.utils import get_project_name
|
|
34
|
+
from phlo.cli.output import (
|
|
35
|
+
command_failed_error,
|
|
36
|
+
empty_file_error,
|
|
37
|
+
exclusive_options_error,
|
|
38
|
+
file_read_error,
|
|
39
|
+
)
|
|
40
|
+
from phlo.cli.output import missing_query_error
|
|
41
|
+
from phlo_postgres.authorization import get_postgres_cli_adapter
|
|
33
42
|
from phlo_postgres.settings import get_settings
|
|
34
43
|
|
|
35
44
|
|
|
@@ -56,18 +65,18 @@ def _read_sql(*, query: str | None, file: Path | None) -> str:
|
|
|
56
65
|
|
|
57
66
|
"""
|
|
58
67
|
if query and file:
|
|
59
|
-
raise
|
|
68
|
+
raise exclusive_options_error("an inline query", "--file")
|
|
60
69
|
if file is not None:
|
|
61
70
|
try:
|
|
62
71
|
sql = file.read_text(encoding="utf-8")
|
|
63
72
|
except OSError as exc:
|
|
64
|
-
raise
|
|
73
|
+
raise file_read_error(file) from exc
|
|
65
74
|
if sql.strip():
|
|
66
75
|
return sql
|
|
67
|
-
raise
|
|
76
|
+
raise empty_file_error(file)
|
|
68
77
|
if query and query.strip():
|
|
69
78
|
return query
|
|
70
|
-
raise
|
|
79
|
+
raise missing_query_error(command_hint='phlo postgres query "SELECT 1"')
|
|
71
80
|
|
|
72
81
|
|
|
73
82
|
def _require_container_backend() -> None:
|
|
@@ -93,7 +102,7 @@ def _postgres_exec_base(*, tty: bool) -> list[str]:
|
|
|
93
102
|
>>> # Returns: ['docker', 'compose', '-p', 'phlo', '-f', '...', 'exec', '-t', 'postgres']
|
|
94
103
|
|
|
95
104
|
"""
|
|
96
|
-
phlo_dir =
|
|
105
|
+
phlo_dir = ensure_compose_project()
|
|
97
106
|
project_name = get_project_name()
|
|
98
107
|
cmd = compose_base_cmd(phlo_dir=phlo_dir, project_name=project_name)
|
|
99
108
|
cmd.append("exec")
|
|
@@ -183,6 +192,7 @@ def postgres_group(ctx: click.Context, postgres_args: tuple[str, ...]) -> None:
|
|
|
183
192
|
return
|
|
184
193
|
|
|
185
194
|
_require_container_backend()
|
|
195
|
+
enforce_surface_mutation_authorization("postgres", get_postgres_cli_adapter)
|
|
186
196
|
user, database = _postgres_identity(user=None, database=None)
|
|
187
197
|
cmd = _postgres_exec_base(tty=True)
|
|
188
198
|
cmd.extend(["psql", "-U", user, "-d", database])
|
|
@@ -230,6 +240,7 @@ def postgres_query(
|
|
|
230
240
|
|
|
231
241
|
"""
|
|
232
242
|
_require_container_backend()
|
|
243
|
+
enforce_surface_mutation_authorization("postgres.query", get_postgres_cli_adapter)
|
|
233
244
|
sql = _read_sql(query=query, file=query_file)
|
|
234
245
|
resolved_user, resolved_db = _postgres_identity(user=user, database=database)
|
|
235
246
|
cmd = _postgres_exec_base(tty=False)
|
|
@@ -244,7 +255,12 @@ def postgres_query(
|
|
|
244
255
|
)
|
|
245
256
|
except CommandError as exc:
|
|
246
257
|
stderr = exc.stderr.strip()
|
|
247
|
-
raise
|
|
258
|
+
raise command_failed_error(
|
|
259
|
+
"psql",
|
|
260
|
+
exit_code=exc.returncode,
|
|
261
|
+
details=[stderr] if stderr else ["PostgreSQL did not complete the query."],
|
|
262
|
+
run="phlo services status postgres",
|
|
263
|
+
) from exc
|
|
248
264
|
except TimeoutExpired as exc:
|
|
249
265
|
raise click.ClickException(f"Query timed out after {timeout_seconds} seconds.") from exc
|
|
250
266
|
|
|
@@ -289,6 +305,7 @@ def postgres_dump(
|
|
|
289
305
|
|
|
290
306
|
"""
|
|
291
307
|
_require_container_backend()
|
|
308
|
+
enforce_surface_mutation_authorization("postgres.dump", get_postgres_cli_adapter)
|
|
292
309
|
resolved_user, resolved_db = _postgres_identity(user=user, database=database)
|
|
293
310
|
cmd = _postgres_exec_base(tty=False)
|
|
294
311
|
cmd.extend(["pg_dump", "-U", resolved_user, resolved_db])
|
|
@@ -302,7 +319,12 @@ def postgres_dump(
|
|
|
302
319
|
)
|
|
303
320
|
except CommandError as exc:
|
|
304
321
|
stderr = exc.stderr.strip()
|
|
305
|
-
raise
|
|
322
|
+
raise command_failed_error(
|
|
323
|
+
"pg_dump",
|
|
324
|
+
exit_code=exc.returncode,
|
|
325
|
+
details=[stderr] if stderr else ["PostgreSQL did not create the dump."],
|
|
326
|
+
run="phlo services status postgres",
|
|
327
|
+
) from exc
|
|
306
328
|
except TimeoutExpired as exc:
|
|
307
329
|
raise click.ClickException(f"Dump timed out after {timeout_seconds} seconds.") from exc
|
|
308
330
|
|
|
@@ -359,6 +381,7 @@ def postgres_restore(
|
|
|
359
381
|
|
|
360
382
|
"""
|
|
361
383
|
_require_container_backend()
|
|
384
|
+
enforce_surface_mutation_authorization("postgres.restore", get_postgres_cli_adapter)
|
|
362
385
|
resolved_user, resolved_db = _postgres_identity(user=user, database=database)
|
|
363
386
|
cmd = _postgres_exec_base(tty=False)
|
|
364
387
|
cmd.extend(["psql", "-U", resolved_user, "-d", resolved_db, "-v", "ON_ERROR_STOP=1"])
|
|
@@ -423,6 +446,7 @@ def postgres_vacuum(
|
|
|
423
446
|
|
|
424
447
|
"""
|
|
425
448
|
_require_container_backend()
|
|
449
|
+
enforce_surface_mutation_authorization("postgres.vacuum", get_postgres_cli_adapter)
|
|
426
450
|
resolved_user, resolved_db = _postgres_identity(user=user, database=database)
|
|
427
451
|
cmd = _postgres_exec_base(tty=False)
|
|
428
452
|
cmd.extend(["vacuumdb", "-U", resolved_user])
|
|
@@ -24,7 +24,7 @@ compose:
|
|
|
24
24
|
# SSL/TLS
|
|
25
25
|
POSTGRES_SSL_MODE: ${POSTGRES_SSL_MODE:-prefer}
|
|
26
26
|
ports:
|
|
27
|
-
- "${POSTGRES_PORT:-
|
|
27
|
+
- "${POSTGRES_PORT:-10000}:5432"
|
|
28
28
|
volumes:
|
|
29
29
|
- postgres-data:/var/lib/postgresql/data
|
|
30
30
|
healthcheck:
|
|
@@ -45,7 +45,7 @@ env_vars:
|
|
|
45
45
|
default: phlo
|
|
46
46
|
description: PostgreSQL database name
|
|
47
47
|
POSTGRES_PORT:
|
|
48
|
-
default:
|
|
48
|
+
default: 10000
|
|
49
49
|
description: PostgreSQL host port
|
|
50
50
|
# SSL/TLS
|
|
51
51
|
POSTGRES_SSL_MODE:
|
|
@@ -42,7 +42,9 @@ def test_postgres_query_runs_psql(monkeypatch) -> None:
|
|
|
42
42
|
]
|
|
43
43
|
return CompletedProcess(cmd, 0, stdout="1\n", stderr="")
|
|
44
44
|
|
|
45
|
-
monkeypatch.setattr(
|
|
45
|
+
monkeypatch.setattr(
|
|
46
|
+
"phlo_postgres.cli.ensure_compose_project", lambda: Path("/tmp/project/.phlo")
|
|
47
|
+
)
|
|
46
48
|
monkeypatch.setattr("phlo_postgres.cli.get_project_name", lambda: "demo")
|
|
47
49
|
monkeypatch.setattr(
|
|
48
50
|
"phlo_postgres.cli.compose_base_cmd",
|
|
@@ -56,6 +58,20 @@ def test_postgres_query_runs_psql(monkeypatch) -> None:
|
|
|
56
58
|
assert result.output == "1\n"
|
|
57
59
|
|
|
58
60
|
|
|
61
|
+
def test_postgres_query_requires_initialized_services(monkeypatch, tmp_path) -> None:
|
|
62
|
+
phlo_dir = tmp_path / ".phlo"
|
|
63
|
+
phlo_dir.mkdir()
|
|
64
|
+
monkeypatch.chdir(tmp_path)
|
|
65
|
+
|
|
66
|
+
result = CliRunner().invoke(postgres_group, ["query", "SELECT 1"])
|
|
67
|
+
|
|
68
|
+
assert result.exit_code != 0
|
|
69
|
+
assert "Error: Phlo services have not been initialized" in result.output
|
|
70
|
+
assert "Missing: .phlo/docker-compose.yml" in result.output
|
|
71
|
+
assert "Run: phlo services init" in result.output
|
|
72
|
+
assert "couldn't find env file" not in result.output
|
|
73
|
+
|
|
74
|
+
|
|
59
75
|
def test_postgres_dump_writes_gzip_file(monkeypatch, tmp_path) -> None:
|
|
60
76
|
output_file = tmp_path / "backup.sql.gz"
|
|
61
77
|
|
|
@@ -65,7 +81,9 @@ def test_postgres_dump_writes_gzip_file(monkeypatch, tmp_path) -> None:
|
|
|
65
81
|
assert cmd[-4:] == ["pg_dump", "-U", "phlo", "phlo"]
|
|
66
82
|
return CompletedProcess(cmd, 0, stdout="CREATE TABLE test ();", stderr="")
|
|
67
83
|
|
|
68
|
-
monkeypatch.setattr(
|
|
84
|
+
monkeypatch.setattr(
|
|
85
|
+
"phlo_postgres.cli.ensure_compose_project", lambda: Path("/tmp/project/.phlo")
|
|
86
|
+
)
|
|
69
87
|
monkeypatch.setattr("phlo_postgres.cli.get_project_name", lambda: "demo")
|
|
70
88
|
monkeypatch.setattr(
|
|
71
89
|
"phlo_postgres.cli.compose_base_cmd",
|
|
@@ -86,7 +104,9 @@ def test_postgres_restore_reads_file(monkeypatch, tmp_path) -> None:
|
|
|
86
104
|
input_file.write_text("SELECT 1;", encoding="utf-8")
|
|
87
105
|
captured: list[tuple[list[str], str]] = []
|
|
88
106
|
|
|
89
|
-
monkeypatch.setattr(
|
|
107
|
+
monkeypatch.setattr(
|
|
108
|
+
"phlo_postgres.cli.ensure_compose_project", lambda: Path("/tmp/project/.phlo")
|
|
109
|
+
)
|
|
90
110
|
monkeypatch.setattr("phlo_postgres.cli.get_project_name", lambda: "demo")
|
|
91
111
|
monkeypatch.setattr(
|
|
92
112
|
"phlo_postgres.cli.compose_base_cmd",
|
|
@@ -136,7 +156,9 @@ def test_postgres_vacuum_runs_vacuumdb(monkeypatch) -> None:
|
|
|
136
156
|
assert cmd[-5:] == ["vacuumdb", "-U", "phlo", "-z", "phlo"]
|
|
137
157
|
return CompletedProcess(cmd, 0, stdout="VACUUM\n", stderr="")
|
|
138
158
|
|
|
139
|
-
monkeypatch.setattr(
|
|
159
|
+
monkeypatch.setattr(
|
|
160
|
+
"phlo_postgres.cli.ensure_compose_project", lambda: Path("/tmp/project/.phlo")
|
|
161
|
+
)
|
|
140
162
|
monkeypatch.setattr("phlo_postgres.cli.get_project_name", lambda: "demo")
|
|
141
163
|
monkeypatch.setattr(
|
|
142
164
|
"phlo_postgres.cli.compose_base_cmd",
|
|
@@ -153,7 +175,9 @@ def test_postgres_vacuum_runs_vacuumdb(monkeypatch) -> None:
|
|
|
153
175
|
def test_postgres_shell_passthrough(monkeypatch) -> None:
|
|
154
176
|
captured: list[list[str]] = []
|
|
155
177
|
|
|
156
|
-
monkeypatch.setattr(
|
|
178
|
+
monkeypatch.setattr(
|
|
179
|
+
"phlo_postgres.cli.ensure_compose_project", lambda: Path("/tmp/project/.phlo")
|
|
180
|
+
)
|
|
157
181
|
monkeypatch.setattr("phlo_postgres.cli.get_project_name", lambda: "demo")
|
|
158
182
|
monkeypatch.setattr(
|
|
159
183
|
"phlo_postgres.cli.compose_base_cmd",
|
|
@@ -197,7 +221,9 @@ def test_postgres_query_timeout(monkeypatch) -> None:
|
|
|
197
221
|
return CompletedProcess(cmd, 0, stdout="", stderr="")
|
|
198
222
|
raise TimeoutExpired(cmd=cmd, timeout=30)
|
|
199
223
|
|
|
200
|
-
monkeypatch.setattr(
|
|
224
|
+
monkeypatch.setattr(
|
|
225
|
+
"phlo_postgres.cli.ensure_compose_project", lambda: Path("/tmp/project/.phlo")
|
|
226
|
+
)
|
|
201
227
|
monkeypatch.setattr("phlo_postgres.cli.get_project_name", lambda: "demo")
|
|
202
228
|
monkeypatch.setattr(
|
|
203
229
|
"phlo_postgres.cli.compose_base_cmd",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|