postgres-dbman 0.1.0__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.
- postgres_dbman-0.1.0/PKG-INFO +155 -0
- postgres_dbman-0.1.0/README.md +143 -0
- postgres_dbman-0.1.0/pyproject.toml +46 -0
- postgres_dbman-0.1.0/setup.cfg +4 -0
- postgres_dbman-0.1.0/src/dbman/__init__.py +5 -0
- postgres_dbman-0.1.0/src/dbman/__main__.py +6 -0
- postgres_dbman-0.1.0/src/dbman/cli.py +292 -0
- postgres_dbman-0.1.0/src/dbman/core.py +1149 -0
- postgres_dbman-0.1.0/src/dbman/zfs_metadata.py +405 -0
- postgres_dbman-0.1.0/src/postgres_dbman.egg-info/PKG-INFO +155 -0
- postgres_dbman-0.1.0/src/postgres_dbman.egg-info/SOURCES.txt +13 -0
- postgres_dbman-0.1.0/src/postgres_dbman.egg-info/dependency_links.txt +1 -0
- postgres_dbman-0.1.0/src/postgres_dbman.egg-info/entry_points.txt +2 -0
- postgres_dbman-0.1.0/src/postgres_dbman.egg-info/requires.txt +5 -0
- postgres_dbman-0.1.0/src/postgres_dbman.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: postgres-dbman
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: docker>=7.1.0
|
|
8
|
+
Requires-Dist: pre-commit>=4.2.0
|
|
9
|
+
Requires-Dist: python-decouple>=3.8
|
|
10
|
+
Requires-Dist: ruff>=0.11.4
|
|
11
|
+
Requires-Dist: zstandard>=0.22.0
|
|
12
|
+
|
|
13
|
+
# DBMan
|
|
14
|
+
|
|
15
|
+
DBMan is an CLI tool to manage PostgreSQL databases running in separate Docker containers.
|
|
16
|
+
The content of the databases is obtained from pg_dumps of production databases.
|
|
17
|
+
|
|
18
|
+
It will assign each database to a unique ZFS dataset inside a predefined ZFS dataset.
|
|
19
|
+
It will allow snapshotting the dataset and restoring the dataset from a snapshot.
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
|
|
23
|
+
DBMan uses environment variables for configuration. Copy `.env.example` to `.env` and modify the values as needed:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# ZFS configuration
|
|
27
|
+
DBMAN_ZFS_DATASET=big/docker-postgres
|
|
28
|
+
DBMAN_ZFS_MOUNT_POINT=/mnt/big/docker-postgres
|
|
29
|
+
DBMAN_ZFS_COMPRESSION=lz4 # Default compression algorithm for new databases
|
|
30
|
+
|
|
31
|
+
# Docker configuration
|
|
32
|
+
DBMAN_DOCKER_IMAGE=postgres:latest
|
|
33
|
+
|
|
34
|
+
# PostgreSQL configuration
|
|
35
|
+
DBMAN_POSTGRES_USER=postgres
|
|
36
|
+
DBMAN_POSTGRES_PASSWORD=postgres
|
|
37
|
+
|
|
38
|
+
# Database configuration
|
|
39
|
+
DBMAN_SQLITE_DB=~/.dbman/dbman.db
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
You can also override the ZFS compression algorithm using the `-c` or `--compression` command line option:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv run dbman.py -c zstd create mydb dump.sql
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
1. You need `uv` to run dbman. More info about installing it can be found here
|
|
51
|
+
https://docs.astral.sh/uv/getting-started/installation/
|
|
52
|
+
|
|
53
|
+
2. Copy `.env.example` to `.env` and configure your environment variables.
|
|
54
|
+
|
|
55
|
+
3. Test it:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv run dbman.py
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
The tool can be used to:
|
|
64
|
+
|
|
65
|
+
- create new databases from pg_dumps - it will automatically create a new Docker container
|
|
66
|
+
with the data from the dump. It will allow naming the container and the database. It will
|
|
67
|
+
automatically assign a port to the container.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Create a database with default compression (inherited from parent ZFS dataset)
|
|
71
|
+
uv run dbman.py create mydb dump.sql
|
|
72
|
+
# or using the short command
|
|
73
|
+
uv run dbman.py c mydb dump.sql
|
|
74
|
+
|
|
75
|
+
# Create a database with specific compression
|
|
76
|
+
uv run dbman.py create -c zstd mydb dump.sql
|
|
77
|
+
# or using the short command
|
|
78
|
+
uv run dbman.py c -c zstd mydb dump.sql
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- list all databases and their statuses
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
uv run dbman.py list
|
|
85
|
+
# or using the short command
|
|
86
|
+
uv run dbman.py ls
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- create a snapshot of a database
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
uv run dbman.py snapshot mydb mysnapshot
|
|
93
|
+
# or using the short command
|
|
94
|
+
uv run dbman.py snap mydb mysnapshot
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- restore a database from a snapshot
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv run dbman.py restore mydb mysnapshot
|
|
101
|
+
# or using the short command
|
|
102
|
+
uv run dbman.py rest mydb mysnapshot
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- list all snapshots of a database
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uv run dbman.py list-snapshots mydb
|
|
109
|
+
# or using the short command
|
|
110
|
+
uv run dbman.py lss mydb
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- start a subshell with database environment variables
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
uv run dbman.py shell mydb
|
|
117
|
+
# or using the short command
|
|
118
|
+
uv run dbman.py sh mydb
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- print database environment variables
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Print variables
|
|
125
|
+
uv run dbman.py env mydb
|
|
126
|
+
# or using the short command
|
|
127
|
+
uv run dbman.py e mydb
|
|
128
|
+
|
|
129
|
+
# Export variables to the current shell
|
|
130
|
+
eval $(uv run dbman.py env --export mydb)
|
|
131
|
+
|
|
132
|
+
# Unset variables from the current shell
|
|
133
|
+
eval $(uv run dbman.py env --clean mydb)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- destroy a database
|
|
137
|
+
```bash
|
|
138
|
+
uv run dbman.py destroy mydb
|
|
139
|
+
# or using the short command
|
|
140
|
+
uv run dbman.py del mydb
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
For detailed usage information, run:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
uv run dbman.py --help
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Note
|
|
150
|
+
|
|
151
|
+
This tool was also used as a proof-of-concept in creating code by using AI. I started
|
|
152
|
+
with a docstring in dbman.py and asked `Cursor` to create an implementation for me.
|
|
153
|
+
It created a functional ~250 lines of code which mostly did what I wanted. When I tweaked
|
|
154
|
+
and improved it, I also extensively used AI to do the modifications, rather than coding it
|
|
155
|
+
myself.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# DBMan
|
|
2
|
+
|
|
3
|
+
DBMan is an CLI tool to manage PostgreSQL databases running in separate Docker containers.
|
|
4
|
+
The content of the databases is obtained from pg_dumps of production databases.
|
|
5
|
+
|
|
6
|
+
It will assign each database to a unique ZFS dataset inside a predefined ZFS dataset.
|
|
7
|
+
It will allow snapshotting the dataset and restoring the dataset from a snapshot.
|
|
8
|
+
|
|
9
|
+
## Configuration
|
|
10
|
+
|
|
11
|
+
DBMan uses environment variables for configuration. Copy `.env.example` to `.env` and modify the values as needed:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# ZFS configuration
|
|
15
|
+
DBMAN_ZFS_DATASET=big/docker-postgres
|
|
16
|
+
DBMAN_ZFS_MOUNT_POINT=/mnt/big/docker-postgres
|
|
17
|
+
DBMAN_ZFS_COMPRESSION=lz4 # Default compression algorithm for new databases
|
|
18
|
+
|
|
19
|
+
# Docker configuration
|
|
20
|
+
DBMAN_DOCKER_IMAGE=postgres:latest
|
|
21
|
+
|
|
22
|
+
# PostgreSQL configuration
|
|
23
|
+
DBMAN_POSTGRES_USER=postgres
|
|
24
|
+
DBMAN_POSTGRES_PASSWORD=postgres
|
|
25
|
+
|
|
26
|
+
# Database configuration
|
|
27
|
+
DBMAN_SQLITE_DB=~/.dbman/dbman.db
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
You can also override the ZFS compression algorithm using the `-c` or `--compression` command line option:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv run dbman.py -c zstd create mydb dump.sql
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
1. You need `uv` to run dbman. More info about installing it can be found here
|
|
39
|
+
https://docs.astral.sh/uv/getting-started/installation/
|
|
40
|
+
|
|
41
|
+
2. Copy `.env.example` to `.env` and configure your environment variables.
|
|
42
|
+
|
|
43
|
+
3. Test it:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv run dbman.py
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
The tool can be used to:
|
|
52
|
+
|
|
53
|
+
- create new databases from pg_dumps - it will automatically create a new Docker container
|
|
54
|
+
with the data from the dump. It will allow naming the container and the database. It will
|
|
55
|
+
automatically assign a port to the container.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Create a database with default compression (inherited from parent ZFS dataset)
|
|
59
|
+
uv run dbman.py create mydb dump.sql
|
|
60
|
+
# or using the short command
|
|
61
|
+
uv run dbman.py c mydb dump.sql
|
|
62
|
+
|
|
63
|
+
# Create a database with specific compression
|
|
64
|
+
uv run dbman.py create -c zstd mydb dump.sql
|
|
65
|
+
# or using the short command
|
|
66
|
+
uv run dbman.py c -c zstd mydb dump.sql
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- list all databases and their statuses
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
uv run dbman.py list
|
|
73
|
+
# or using the short command
|
|
74
|
+
uv run dbman.py ls
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- create a snapshot of a database
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uv run dbman.py snapshot mydb mysnapshot
|
|
81
|
+
# or using the short command
|
|
82
|
+
uv run dbman.py snap mydb mysnapshot
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- restore a database from a snapshot
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
uv run dbman.py restore mydb mysnapshot
|
|
89
|
+
# or using the short command
|
|
90
|
+
uv run dbman.py rest mydb mysnapshot
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- list all snapshots of a database
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
uv run dbman.py list-snapshots mydb
|
|
97
|
+
# or using the short command
|
|
98
|
+
uv run dbman.py lss mydb
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- start a subshell with database environment variables
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
uv run dbman.py shell mydb
|
|
105
|
+
# or using the short command
|
|
106
|
+
uv run dbman.py sh mydb
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- print database environment variables
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Print variables
|
|
113
|
+
uv run dbman.py env mydb
|
|
114
|
+
# or using the short command
|
|
115
|
+
uv run dbman.py e mydb
|
|
116
|
+
|
|
117
|
+
# Export variables to the current shell
|
|
118
|
+
eval $(uv run dbman.py env --export mydb)
|
|
119
|
+
|
|
120
|
+
# Unset variables from the current shell
|
|
121
|
+
eval $(uv run dbman.py env --clean mydb)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- destroy a database
|
|
125
|
+
```bash
|
|
126
|
+
uv run dbman.py destroy mydb
|
|
127
|
+
# or using the short command
|
|
128
|
+
uv run dbman.py del mydb
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
For detailed usage information, run:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
uv run dbman.py --help
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Note
|
|
138
|
+
|
|
139
|
+
This tool was also used as a proof-of-concept in creating code by using AI. I started
|
|
140
|
+
with a docstring in dbman.py and asked `Cursor` to create an implementation for me.
|
|
141
|
+
It created a functional ~250 lines of code which mostly did what I wanted. When I tweaked
|
|
142
|
+
and improved it, I also extensively used AI to do the modifications, rather than coding it
|
|
143
|
+
myself.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "postgres-dbman"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Add your description here"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"docker>=7.1.0",
|
|
13
|
+
"pre-commit>=4.2.0",
|
|
14
|
+
"python-decouple>=3.8",
|
|
15
|
+
"ruff>=0.11.4",
|
|
16
|
+
"zstandard>=0.22.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
dbman = "dbman.cli:main"
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
where = ["src"]
|
|
24
|
+
|
|
25
|
+
[tool.ruff]
|
|
26
|
+
line-length = 100
|
|
27
|
+
target-version = "py311"
|
|
28
|
+
|
|
29
|
+
[tool.ruff.lint]
|
|
30
|
+
select = [
|
|
31
|
+
"E", # pycodestyle errors
|
|
32
|
+
"W", # pycodestyle warnings
|
|
33
|
+
"F", # pyflakes
|
|
34
|
+
"I", # isort
|
|
35
|
+
"B", # flake8-bugbear
|
|
36
|
+
"C4", # flake8-comprehensions
|
|
37
|
+
"UP", # pyupgrade
|
|
38
|
+
"PL", # pylint
|
|
39
|
+
"RUF", # ruff-specific rules
|
|
40
|
+
]
|
|
41
|
+
ignore = []
|
|
42
|
+
|
|
43
|
+
[tool.ruff.format]
|
|
44
|
+
quote-style = "double"
|
|
45
|
+
indent-style = "space"
|
|
46
|
+
line-ending = "auto"
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""CLI entry point for DBMan."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
import traceback
|
|
9
|
+
|
|
10
|
+
from .core import DEFAULT_COMPRESSION, DBMan, format_output
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main() -> None: # noqa: PLR0915, PLR0912
|
|
16
|
+
parser = argparse.ArgumentParser(description="DBMan - PostgreSQL database management tool")
|
|
17
|
+
parser.add_argument(
|
|
18
|
+
"-f", "--force", action="store_true", help="Force recreation of ZFS datasets"
|
|
19
|
+
)
|
|
20
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose logging")
|
|
21
|
+
parser.add_argument("--json", action="store_true", help="Output in JSON format")
|
|
22
|
+
|
|
23
|
+
subparsers = parser.add_subparsers(dest="command", help="Commands", required=True)
|
|
24
|
+
|
|
25
|
+
# Create database command
|
|
26
|
+
create_parser = subparsers.add_parser("create", aliases=["c"], help="Create a new database")
|
|
27
|
+
create_parser.add_argument(
|
|
28
|
+
"name", nargs="?", help="Database name (optional if DBMAN_DB_NAME is set)"
|
|
29
|
+
)
|
|
30
|
+
create_parser.add_argument("dump_path", help="Path to pg_dump file")
|
|
31
|
+
create_parser.add_argument("--port", type=int, help="Port to expose (optional)")
|
|
32
|
+
create_parser.add_argument(
|
|
33
|
+
"-c",
|
|
34
|
+
"--compression",
|
|
35
|
+
default=DEFAULT_COMPRESSION,
|
|
36
|
+
help=f"ZFS compression algorithm (default: {DEFAULT_COMPRESSION})",
|
|
37
|
+
)
|
|
38
|
+
create_parser.add_argument(
|
|
39
|
+
"--postgres-config",
|
|
40
|
+
action="append",
|
|
41
|
+
help="PostgreSQL configuration option in format key=value (can be used multiple times)",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# List databases command
|
|
45
|
+
subparsers.add_parser("list", aliases=["ls"], help="List all databases")
|
|
46
|
+
|
|
47
|
+
# Snapshot commands
|
|
48
|
+
snapshot_parser = subparsers.add_parser("snapshot", aliases=["snap"], help="Create a snapshot")
|
|
49
|
+
snapshot_parser.add_argument(
|
|
50
|
+
"name", nargs="?", help="Database name (optional if DBMAN_DB_NAME is set)"
|
|
51
|
+
)
|
|
52
|
+
snapshot_parser.add_argument("snapshot_name", help="Snapshot name")
|
|
53
|
+
snapshot_parser.add_argument(
|
|
54
|
+
"--add",
|
|
55
|
+
action="store_true",
|
|
56
|
+
help="Add an existing ZFS snapshot to the database",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
restore_parser = subparsers.add_parser(
|
|
60
|
+
"restore", aliases=["rest"], help="Restore from snapshot"
|
|
61
|
+
)
|
|
62
|
+
restore_parser.add_argument(
|
|
63
|
+
"name", nargs="?", help="Database name (optional if DBMAN_DB_NAME is set)"
|
|
64
|
+
)
|
|
65
|
+
restore_parser.add_argument("snapshot_name", help="Snapshot name or number")
|
|
66
|
+
|
|
67
|
+
list_snapshots_parser = subparsers.add_parser(
|
|
68
|
+
"list-snapshots", aliases=["lss"], help="List snapshots"
|
|
69
|
+
)
|
|
70
|
+
list_snapshots_parser.add_argument("name", nargs="?", help="Database name (optional)")
|
|
71
|
+
|
|
72
|
+
# Shell command
|
|
73
|
+
shell_parser = subparsers.add_parser(
|
|
74
|
+
"shell", aliases=["sh"], help="Start a subshell with database environment variables"
|
|
75
|
+
)
|
|
76
|
+
shell_parser.add_argument(
|
|
77
|
+
"name", nargs="?", help="Database name (optional if DBMAN_DB_NAME is set)"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Env command
|
|
81
|
+
env_parser = subparsers.add_parser(
|
|
82
|
+
"env", aliases=["e"], help="Print database environment variables"
|
|
83
|
+
)
|
|
84
|
+
env_parser.add_argument(
|
|
85
|
+
"name", nargs="?", help="Database name (optional if DBMAN_DB_NAME is set)"
|
|
86
|
+
)
|
|
87
|
+
env_parser.add_argument(
|
|
88
|
+
"--export",
|
|
89
|
+
"-e",
|
|
90
|
+
action="store_true",
|
|
91
|
+
help="Export environment variables to the current shell",
|
|
92
|
+
)
|
|
93
|
+
env_parser.add_argument(
|
|
94
|
+
"--clean",
|
|
95
|
+
action="store_true",
|
|
96
|
+
help="Unset environment variables from the current shell",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Destroy command
|
|
100
|
+
destroy_parser = subparsers.add_parser(
|
|
101
|
+
"destroy", aliases=["del", "rm"], help="Destroy a database"
|
|
102
|
+
)
|
|
103
|
+
destroy_parser.add_argument(
|
|
104
|
+
"name", nargs="?", help="Database name (optional if DBMAN_DB_NAME is set)"
|
|
105
|
+
)
|
|
106
|
+
destroy_parser.add_argument(
|
|
107
|
+
"--keep-zfs-dataset",
|
|
108
|
+
action="store_true",
|
|
109
|
+
help="Keep the ZFS dataset (by default, the ZFS dataset is removed)",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Start command
|
|
113
|
+
start_parser = subparsers.add_parser("start", help="Start a stopped database")
|
|
114
|
+
start_parser.add_argument(
|
|
115
|
+
"name", nargs="?", help="Database name (optional if DBMAN_DB_NAME is set)"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Stop command
|
|
119
|
+
stop_parser = subparsers.add_parser("stop", help="Stop a running database")
|
|
120
|
+
stop_parser.add_argument(
|
|
121
|
+
"name", nargs="?", help="Database name (optional if DBMAN_DB_NAME is set)"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Restart command
|
|
125
|
+
restart_parser = subparsers.add_parser("restart", help="Restart a database")
|
|
126
|
+
restart_parser.add_argument(
|
|
127
|
+
"name", nargs="?", help="Database name (optional if DBMAN_DB_NAME is set)"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Cleanup command
|
|
131
|
+
cleanup_parser = subparsers.add_parser(
|
|
132
|
+
"cleanup", help="List orphaned mountpoints (directories without corresponding databases)"
|
|
133
|
+
)
|
|
134
|
+
cleanup_parser.add_argument(
|
|
135
|
+
"--one-line",
|
|
136
|
+
"-1",
|
|
137
|
+
action="store_true",
|
|
138
|
+
help="Output only mountpoint names on a single line separated by spaces",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Clone command
|
|
142
|
+
clone_parser = subparsers.add_parser("clone", help="Clone a database from a snapshot")
|
|
143
|
+
clone_parser.add_argument(
|
|
144
|
+
"source_name",
|
|
145
|
+
nargs="?",
|
|
146
|
+
help="Source database name (optional if DBMAN_DB_NAME is set)",
|
|
147
|
+
)
|
|
148
|
+
clone_parser.add_argument("snapshot_name", help="Snapshot name to clone from")
|
|
149
|
+
clone_parser.add_argument("new_name", help="Name for the new cloned database")
|
|
150
|
+
clone_parser.add_argument("--port", type=int, help="Port to expose (optional)")
|
|
151
|
+
|
|
152
|
+
# Export command
|
|
153
|
+
export_parser = subparsers.add_parser(
|
|
154
|
+
"export", aliases=["exp"], help="Export a database to a ZFS send file"
|
|
155
|
+
)
|
|
156
|
+
export_parser.add_argument(
|
|
157
|
+
"name", nargs="?", help="Database name (optional if DBMAN_DB_NAME is set)"
|
|
158
|
+
)
|
|
159
|
+
export_parser.add_argument(
|
|
160
|
+
"output_file",
|
|
161
|
+
nargs="?",
|
|
162
|
+
help="Output file path (optional; auto-generated from name and snapshot if omitted)",
|
|
163
|
+
)
|
|
164
|
+
export_parser.add_argument(
|
|
165
|
+
"--snapshot",
|
|
166
|
+
"-s",
|
|
167
|
+
dest="snapshot_name",
|
|
168
|
+
help="Snapshot name to export (optional; auto-created as export-<timestamp> if omitted)",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Import command
|
|
172
|
+
import_parser = subparsers.add_parser(
|
|
173
|
+
"import", aliases=["imp"], help="Import a database from a ZFS send file"
|
|
174
|
+
)
|
|
175
|
+
import_parser.add_argument("input_file", help="Path to the ZFS send file")
|
|
176
|
+
import_parser.add_argument(
|
|
177
|
+
"new_name",
|
|
178
|
+
nargs="?",
|
|
179
|
+
help="Name for the imported database (optional; detected from stream if omitted)",
|
|
180
|
+
)
|
|
181
|
+
import_parser.add_argument("--port", type=int, help="Port to expose (optional)")
|
|
182
|
+
import_parser.add_argument(
|
|
183
|
+
"--compression",
|
|
184
|
+
"-c",
|
|
185
|
+
help="ZFS compression algorithm for the received dataset (e.g. lz4, zstd)",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Delete older than command
|
|
189
|
+
delete_older_parser = subparsers.add_parser(
|
|
190
|
+
"delete_older_than",
|
|
191
|
+
aliases=["dot"],
|
|
192
|
+
help="List or delete databases older than specified days",
|
|
193
|
+
)
|
|
194
|
+
delete_older_parser.add_argument(
|
|
195
|
+
"days", type=int, help="Number of days - databases older than this will be listed/deleted"
|
|
196
|
+
)
|
|
197
|
+
delete_older_parser.add_argument(
|
|
198
|
+
"--do-it",
|
|
199
|
+
action="store_true",
|
|
200
|
+
help="Actually delete the databases (without this flag, only lists them)",
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
args = parser.parse_args()
|
|
204
|
+
|
|
205
|
+
# Set logging level based on verbose flag
|
|
206
|
+
if args.verbose:
|
|
207
|
+
logging.getLogger("dbman").setLevel(logging.DEBUG)
|
|
208
|
+
|
|
209
|
+
# Get database name from environment variable if not provided
|
|
210
|
+
def get_db_name(name: str | None, allow_none: bool = False) -> str:
|
|
211
|
+
if name is not None:
|
|
212
|
+
return name
|
|
213
|
+
env_name = os.environ.get("DBMAN_DB_NAME")
|
|
214
|
+
if env_name is None and not allow_none:
|
|
215
|
+
raise ValueError(
|
|
216
|
+
"Database name must be provided either as an argument or via DBMAN_DB_NAME "
|
|
217
|
+
"environment variable"
|
|
218
|
+
)
|
|
219
|
+
return env_name
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
dbman = DBMan(args.force)
|
|
223
|
+
|
|
224
|
+
if args.command in ["create", "c"]:
|
|
225
|
+
# Parse PostgreSQL configuration options
|
|
226
|
+
postgres_config = {}
|
|
227
|
+
if args.postgres_config:
|
|
228
|
+
for cfg in args.postgres_config:
|
|
229
|
+
try:
|
|
230
|
+
key, value = cfg.split("=", 1)
|
|
231
|
+
postgres_config[key] = value
|
|
232
|
+
except ValueError:
|
|
233
|
+
logger.error(f"Invalid configuration format: {cfg}")
|
|
234
|
+
sys.exit(1)
|
|
235
|
+
dbman.create_database(
|
|
236
|
+
get_db_name(args.name), args.dump_path, args.port, args.compression, postgres_config
|
|
237
|
+
)
|
|
238
|
+
elif args.command in ["list", "ls"]:
|
|
239
|
+
databases = dbman.list_databases()
|
|
240
|
+
format_output(databases, args.json)
|
|
241
|
+
elif args.command in ["snapshot", "snap"]:
|
|
242
|
+
if args.add:
|
|
243
|
+
dbman.add_snapshot(get_db_name(args.name), args.snapshot_name)
|
|
244
|
+
else:
|
|
245
|
+
dbman.create_snapshot(get_db_name(args.name), args.snapshot_name)
|
|
246
|
+
elif args.command in ["restore", "rest"]:
|
|
247
|
+
# only allow negative numbers for snapshot numbers
|
|
248
|
+
if re.match(r"^-\d+$", args.snapshot_name):
|
|
249
|
+
dbman.restore_snapshot(get_db_name(args.name), int(args.snapshot_name))
|
|
250
|
+
else:
|
|
251
|
+
dbman.restore_snapshot(get_db_name(args.name), args.snapshot_name)
|
|
252
|
+
elif args.command in ["list-snapshots", "lss"]:
|
|
253
|
+
snapshots = dbman.list_snapshots(get_db_name(args.name, allow_none=True))
|
|
254
|
+
format_output(snapshots, args.json)
|
|
255
|
+
elif args.command in ["shell", "sh"]:
|
|
256
|
+
dbman.shell(get_db_name(args.name))
|
|
257
|
+
elif args.command in ["env", "e"]:
|
|
258
|
+
dbman.env(get_db_name(args.name), args.export, args.clean)
|
|
259
|
+
elif args.command in ["destroy", "del", "rm"]:
|
|
260
|
+
dbman._destroy_database(get_db_name(args.name), keep_zfs=args.keep_zfs_dataset)
|
|
261
|
+
elif args.command == "start":
|
|
262
|
+
dbman.start(get_db_name(args.name))
|
|
263
|
+
elif args.command == "stop":
|
|
264
|
+
dbman.stop(get_db_name(args.name))
|
|
265
|
+
elif args.command == "restart":
|
|
266
|
+
dbman.restart(get_db_name(args.name))
|
|
267
|
+
elif args.command == "cleanup":
|
|
268
|
+
orphaned_mountpoints = dbman.cleanup()
|
|
269
|
+
if args.one_line:
|
|
270
|
+
# Output only mountpoint names on a single line
|
|
271
|
+
names = [mp["name"] for mp in orphaned_mountpoints]
|
|
272
|
+
print(" ".join(names))
|
|
273
|
+
else:
|
|
274
|
+
format_output(orphaned_mountpoints, args.json)
|
|
275
|
+
elif args.command == "clone":
|
|
276
|
+
dbman.clone_database(
|
|
277
|
+
get_db_name(args.source_name), args.snapshot_name, args.new_name, args.port
|
|
278
|
+
)
|
|
279
|
+
elif args.command in ["delete_older_than", "dot"]:
|
|
280
|
+
databases = dbman.delete_older_than(args.days, args.do_it)
|
|
281
|
+
format_output(databases, args.json)
|
|
282
|
+
elif args.command in ["export", "exp"]:
|
|
283
|
+
dbman.export_database(get_db_name(args.name), args.output_file, args.snapshot_name)
|
|
284
|
+
elif args.command in ["import", "imp"]:
|
|
285
|
+
dbman.import_database(args.input_file, args.new_name, args.port, args.compression)
|
|
286
|
+
else:
|
|
287
|
+
parser.print_help()
|
|
288
|
+
sys.exit(1)
|
|
289
|
+
except Exception as e:
|
|
290
|
+
logger.error(f"Error: {e!s}")
|
|
291
|
+
logger.debug(f"Stack trace: {traceback.format_exc()}")
|
|
292
|
+
sys.exit(1)
|