odoo-dev 0.1.0__py3-none-any.whl
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.
- odoo_dev/__init__.py +3 -0
- odoo_dev/cli.py +36 -0
- odoo_dev/commands/__init__.py +1 -0
- odoo_dev/commands/db.py +332 -0
- odoo_dev/commands/docker.py +196 -0
- odoo_dev/commands/run.py +432 -0
- odoo_dev/commands/setup.py +364 -0
- odoo_dev/config.py +101 -0
- odoo_dev/utils/__init__.py +1 -0
- odoo_dev/utils/console.py +25 -0
- odoo_dev-0.1.0.dist-info/METADATA +133 -0
- odoo_dev-0.1.0.dist-info/RECORD +14 -0
- odoo_dev-0.1.0.dist-info/WHEEL +4 -0
- odoo_dev-0.1.0.dist-info/entry_points.txt +2 -0
odoo_dev/__init__.py
ADDED
odoo_dev/cli.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Main CLI entry point for odoo-dev."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from odoo_dev.commands import db, docker, run, setup
|
|
6
|
+
|
|
7
|
+
app = typer.Typer(
|
|
8
|
+
name="odoo-dev",
|
|
9
|
+
help="Odoo Development Environment Helper",
|
|
10
|
+
no_args_is_help=True,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# Command groups
|
|
14
|
+
app.add_typer(db.app, name="db")
|
|
15
|
+
app.add_typer(docker.app, name="docker")
|
|
16
|
+
|
|
17
|
+
# Setup commands
|
|
18
|
+
app.command()(setup.setup)
|
|
19
|
+
app.command(name="setup-venv")(setup.setup_venv)
|
|
20
|
+
app.command()(setup.vscode)
|
|
21
|
+
|
|
22
|
+
# Local runtime commands (the defaults)
|
|
23
|
+
app.command()(run.run)
|
|
24
|
+
app.command()(run.shell)
|
|
25
|
+
app.command()(run.update)
|
|
26
|
+
app.command()(run.test)
|
|
27
|
+
app.command()(run.scaffold)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main() -> None:
|
|
31
|
+
"""Entry point for the CLI."""
|
|
32
|
+
app()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI commands for odoo-dev."""
|
odoo_dev/commands/db.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"""Database management commands."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from odoo_dev.config import load_config
|
|
10
|
+
from odoo_dev.utils.console import error, success, warning
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(help="Database operations")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command()
|
|
16
|
+
def restore(
|
|
17
|
+
backup_file: Annotated[
|
|
18
|
+
Path, typer.Argument(help="Path to backup file (.zip, .sql, .dump)")
|
|
19
|
+
],
|
|
20
|
+
db_name: Annotated[
|
|
21
|
+
str | None,
|
|
22
|
+
typer.Argument(help="Database name (default: derived from filename)"),
|
|
23
|
+
] = None,
|
|
24
|
+
no_neutralize: Annotated[
|
|
25
|
+
bool, typer.Option("--no-neutralize", help="Skip neutralization")
|
|
26
|
+
] = False,
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Restore database from backup file."""
|
|
29
|
+
cfg = load_config()
|
|
30
|
+
|
|
31
|
+
# Resolve to absolute path
|
|
32
|
+
backup_file = backup_file.resolve()
|
|
33
|
+
|
|
34
|
+
if not backup_file.exists():
|
|
35
|
+
error(f"Backup file not found: {backup_file}")
|
|
36
|
+
raise typer.Exit(1)
|
|
37
|
+
|
|
38
|
+
# Derive db name from filename if not provided
|
|
39
|
+
if db_name is None:
|
|
40
|
+
db_name = backup_file.stem
|
|
41
|
+
|
|
42
|
+
# Check config exists
|
|
43
|
+
if not cfg.config_file.exists():
|
|
44
|
+
error(f"Config file not found: {cfg.config_file}")
|
|
45
|
+
error("Run 'odoo-dev setup' first to create the configuration.")
|
|
46
|
+
raise typer.Exit(1)
|
|
47
|
+
|
|
48
|
+
neutralize = not no_neutralize
|
|
49
|
+
file_ext = backup_file.suffix.lower()
|
|
50
|
+
|
|
51
|
+
success(f"Restoring database {db_name} from {backup_file}...")
|
|
52
|
+
success(f"Format: {file_ext}")
|
|
53
|
+
success(f"Neutralize: {neutralize}")
|
|
54
|
+
|
|
55
|
+
# Parse database connection from config
|
|
56
|
+
db_config = _parse_db_config(cfg.config_file)
|
|
57
|
+
|
|
58
|
+
# Check if database exists
|
|
59
|
+
if _database_exists(db_name, db_config):
|
|
60
|
+
warning(f"Database {db_name} already exists.")
|
|
61
|
+
if not typer.confirm(
|
|
62
|
+
"Do you want to drop it and restore from backup?", default=False
|
|
63
|
+
):
|
|
64
|
+
success("Restore cancelled.")
|
|
65
|
+
raise typer.Exit(0)
|
|
66
|
+
|
|
67
|
+
# Activate venv if needed
|
|
68
|
+
venv_activate = cfg.venv_path / "bin" / "activate"
|
|
69
|
+
if not venv_activate.exists():
|
|
70
|
+
error(f"Virtual environment not found at {cfg.venv_path}")
|
|
71
|
+
error("Run 'odoo-dev setup-venv' first.")
|
|
72
|
+
raise typer.Exit(1)
|
|
73
|
+
|
|
74
|
+
# Drop existing database
|
|
75
|
+
warning(f"Dropping existing database {db_name}...")
|
|
76
|
+
_run_odoo_cmd(cfg, ["db", "drop", db_name], check=False)
|
|
77
|
+
|
|
78
|
+
# Restore based on file type
|
|
79
|
+
neutralize_flag = ["-n"] if neutralize else []
|
|
80
|
+
|
|
81
|
+
if file_ext == ".zip":
|
|
82
|
+
success("Using odoo db load method...")
|
|
83
|
+
result = _run_odoo_cmd(
|
|
84
|
+
cfg, ["db", "load", *neutralize_flag, db_name, str(backup_file)]
|
|
85
|
+
)
|
|
86
|
+
elif file_ext == ".dump":
|
|
87
|
+
success("Using pg_restore method...")
|
|
88
|
+
_create_database(db_name, db_config)
|
|
89
|
+
_pg_restore(backup_file, db_name, db_config)
|
|
90
|
+
if neutralize:
|
|
91
|
+
_run_neutralize(cfg, db_name, db_config)
|
|
92
|
+
elif file_ext == ".sql":
|
|
93
|
+
success("Using psql method...")
|
|
94
|
+
_create_database(db_name, db_config)
|
|
95
|
+
_psql_restore(backup_file, db_name, db_config)
|
|
96
|
+
if neutralize:
|
|
97
|
+
_run_neutralize(cfg, db_name, db_config)
|
|
98
|
+
else:
|
|
99
|
+
error(f"Unsupported backup format: {file_ext}")
|
|
100
|
+
error("Supported formats: .zip, .sql, .dump")
|
|
101
|
+
raise typer.Exit(1)
|
|
102
|
+
|
|
103
|
+
success(f"Database {db_name} restored successfully!")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@app.command()
|
|
107
|
+
def drop(
|
|
108
|
+
db_name: Annotated[str, typer.Argument(help="Database name to drop")],
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Drop a database."""
|
|
111
|
+
cfg = load_config()
|
|
112
|
+
|
|
113
|
+
warning(f"You are about to drop database {db_name}. This cannot be undone.")
|
|
114
|
+
if not typer.confirm("Continue?", default=False):
|
|
115
|
+
success("Cancelled.")
|
|
116
|
+
raise typer.Exit(0)
|
|
117
|
+
|
|
118
|
+
success(f"Dropping database {db_name}...")
|
|
119
|
+
_run_odoo_cmd(cfg, ["db", "drop", db_name])
|
|
120
|
+
success(f"Database {db_name} dropped.")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@app.command(name="list")
|
|
124
|
+
def list_dbs() -> None:
|
|
125
|
+
"""List all databases."""
|
|
126
|
+
cfg = load_config()
|
|
127
|
+
success("Listing databases...")
|
|
128
|
+
_run_odoo_cmd(cfg, ["db", "list"])
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@app.command()
|
|
132
|
+
def neutralize(
|
|
133
|
+
db_name: Annotated[str, typer.Argument(help="Database name to neutralize")],
|
|
134
|
+
) -> None:
|
|
135
|
+
"""Neutralize a database (disable emails, crons, etc.)."""
|
|
136
|
+
cfg = load_config()
|
|
137
|
+
|
|
138
|
+
success(f"Neutralizing database {db_name}...")
|
|
139
|
+
_run_odoo_cmd(cfg, ["neutralize", "-d", db_name])
|
|
140
|
+
success(f"Database {db_name} neutralized.")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _parse_db_config(config_file: Path) -> dict[str, str]:
|
|
144
|
+
"""Parse database connection info from odoo.conf."""
|
|
145
|
+
config: dict[str, str] = {
|
|
146
|
+
"host": "localhost",
|
|
147
|
+
"port": "5432",
|
|
148
|
+
"user": "odoo",
|
|
149
|
+
"password": "",
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for line in config_file.read_text().splitlines():
|
|
153
|
+
line = line.strip()
|
|
154
|
+
if line.startswith("db_host"):
|
|
155
|
+
config["host"] = line.split("=", 1)[1].strip()
|
|
156
|
+
elif line.startswith("db_port"):
|
|
157
|
+
config["port"] = line.split("=", 1)[1].strip()
|
|
158
|
+
elif line.startswith("db_user"):
|
|
159
|
+
config["user"] = line.split("=", 1)[1].strip()
|
|
160
|
+
elif line.startswith("db_password"):
|
|
161
|
+
config["password"] = line.split("=", 1)[1].strip()
|
|
162
|
+
|
|
163
|
+
return config
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _database_exists(db_name: str, db_config: dict[str, str]) -> bool:
|
|
167
|
+
"""Check if a database exists."""
|
|
168
|
+
env = subprocess.os.environ.copy()
|
|
169
|
+
env["PGPASSWORD"] = db_config["password"]
|
|
170
|
+
|
|
171
|
+
result = subprocess.run(
|
|
172
|
+
[
|
|
173
|
+
"psql",
|
|
174
|
+
"-h",
|
|
175
|
+
db_config["host"],
|
|
176
|
+
"-p",
|
|
177
|
+
db_config["port"],
|
|
178
|
+
"-U",
|
|
179
|
+
db_config["user"],
|
|
180
|
+
"-lqt",
|
|
181
|
+
],
|
|
182
|
+
capture_output=True,
|
|
183
|
+
text=True,
|
|
184
|
+
env=env,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
for line in result.stdout.splitlines():
|
|
188
|
+
if db_name == line.split("|")[0].strip():
|
|
189
|
+
return True
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _create_database(db_name: str, db_config: dict[str, str]) -> None:
|
|
194
|
+
"""Create a new database."""
|
|
195
|
+
env = subprocess.os.environ.copy()
|
|
196
|
+
env["PGPASSWORD"] = db_config["password"]
|
|
197
|
+
|
|
198
|
+
subprocess.run(
|
|
199
|
+
[
|
|
200
|
+
"createdb",
|
|
201
|
+
"-h",
|
|
202
|
+
db_config["host"],
|
|
203
|
+
"-p",
|
|
204
|
+
db_config["port"],
|
|
205
|
+
"-U",
|
|
206
|
+
db_config["user"],
|
|
207
|
+
db_name,
|
|
208
|
+
],
|
|
209
|
+
env=env,
|
|
210
|
+
check=True,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _pg_restore(backup_file: Path, db_name: str, db_config: dict[str, str]) -> None:
|
|
215
|
+
"""Restore using pg_restore."""
|
|
216
|
+
env = subprocess.os.environ.copy()
|
|
217
|
+
env["PGPASSWORD"] = db_config["password"]
|
|
218
|
+
|
|
219
|
+
subprocess.run(
|
|
220
|
+
[
|
|
221
|
+
"pg_restore",
|
|
222
|
+
"-h",
|
|
223
|
+
db_config["host"],
|
|
224
|
+
"-p",
|
|
225
|
+
db_config["port"],
|
|
226
|
+
"-U",
|
|
227
|
+
db_config["user"],
|
|
228
|
+
"-d",
|
|
229
|
+
db_name,
|
|
230
|
+
"--no-owner",
|
|
231
|
+
str(backup_file),
|
|
232
|
+
],
|
|
233
|
+
env=env,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _psql_restore(backup_file: Path, db_name: str, db_config: dict[str, str]) -> None:
|
|
238
|
+
"""Restore using psql."""
|
|
239
|
+
env = subprocess.os.environ.copy()
|
|
240
|
+
env["PGPASSWORD"] = db_config["password"]
|
|
241
|
+
|
|
242
|
+
subprocess.run(
|
|
243
|
+
[
|
|
244
|
+
"psql",
|
|
245
|
+
"-h",
|
|
246
|
+
db_config["host"],
|
|
247
|
+
"-p",
|
|
248
|
+
db_config["port"],
|
|
249
|
+
"-U",
|
|
250
|
+
db_config["user"],
|
|
251
|
+
"-d",
|
|
252
|
+
db_name,
|
|
253
|
+
"-f",
|
|
254
|
+
str(backup_file),
|
|
255
|
+
],
|
|
256
|
+
env=env,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _run_neutralize(
|
|
261
|
+
cfg: "ProjectConfig", db_name: str, db_config: dict[str, str]
|
|
262
|
+
) -> None:
|
|
263
|
+
"""Run Odoo neutralization and re-init db params."""
|
|
264
|
+
from odoo_dev.config import ProjectConfig
|
|
265
|
+
|
|
266
|
+
# Re-init database parameters
|
|
267
|
+
_reinit_db_params(db_name, db_config)
|
|
268
|
+
|
|
269
|
+
# Run odoo neutralize
|
|
270
|
+
_run_odoo_cmd(cfg, ["neutralize", "-d", db_name])
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _reinit_db_params(db_name: str, db_config: dict[str, str]) -> None:
|
|
274
|
+
"""Re-initialize database parameters after restore."""
|
|
275
|
+
env = subprocess.os.environ.copy()
|
|
276
|
+
env["PGPASSWORD"] = db_config["password"]
|
|
277
|
+
|
|
278
|
+
sql = """
|
|
279
|
+
DO $$
|
|
280
|
+
DECLARE
|
|
281
|
+
new_secret TEXT := gen_random_uuid()::text;
|
|
282
|
+
new_uuid TEXT := gen_random_uuid()::text;
|
|
283
|
+
BEGIN
|
|
284
|
+
DELETE FROM ir_config_parameter WHERE key IN (
|
|
285
|
+
'database.secret',
|
|
286
|
+
'database.uuid',
|
|
287
|
+
'database.create_date',
|
|
288
|
+
'web.base.url',
|
|
289
|
+
'base.login_cooldown_after',
|
|
290
|
+
'base.login_cooldown_duration'
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
INSERT INTO ir_config_parameter (key, value, create_uid, create_date, write_uid, write_date) VALUES
|
|
294
|
+
('database.secret', new_secret, 1, LOCALTIMESTAMP, 1, LOCALTIMESTAMP),
|
|
295
|
+
('database.uuid', new_uuid, 1, LOCALTIMESTAMP, 1, LOCALTIMESTAMP),
|
|
296
|
+
('database.create_date', LOCALTIMESTAMP::text, 1, LOCALTIMESTAMP, 1, LOCALTIMESTAMP),
|
|
297
|
+
('web.base.url', 'http://localhost:8069', 1, LOCALTIMESTAMP, 1, LOCALTIMESTAMP),
|
|
298
|
+
('base.login_cooldown_after', '10', 1, LOCALTIMESTAMP, 1, LOCALTIMESTAMP),
|
|
299
|
+
('base.login_cooldown_duration', '60', 1, LOCALTIMESTAMP, 1, LOCALTIMESTAMP);
|
|
300
|
+
END $$;
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
subprocess.run(
|
|
304
|
+
[
|
|
305
|
+
"psql",
|
|
306
|
+
"-h",
|
|
307
|
+
db_config["host"],
|
|
308
|
+
"-p",
|
|
309
|
+
db_config["port"],
|
|
310
|
+
"-U",
|
|
311
|
+
db_config["user"],
|
|
312
|
+
"-d",
|
|
313
|
+
db_name,
|
|
314
|
+
"-c",
|
|
315
|
+
sql,
|
|
316
|
+
],
|
|
317
|
+
env=env,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _run_odoo_cmd(
|
|
322
|
+
cfg: "ProjectConfig", args: list[str], check: bool = True
|
|
323
|
+
) -> subprocess.CompletedProcess:
|
|
324
|
+
"""Run an odoo-bin command in the project's venv."""
|
|
325
|
+
from odoo_dev.config import ProjectConfig
|
|
326
|
+
|
|
327
|
+
# Build command to run in venv
|
|
328
|
+
venv_python = cfg.venv_path / "bin" / "python"
|
|
329
|
+
|
|
330
|
+
cmd = [str(venv_python), str(cfg.odoo_bin), "-c", str(cfg.config_file), *args]
|
|
331
|
+
|
|
332
|
+
return subprocess.run(cmd, check=check)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Docker container management commands (optional)."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from odoo_dev.config import load_config
|
|
8
|
+
from odoo_dev.utils.console import error, success
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="Docker container management (optional)")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.command()
|
|
14
|
+
def start() -> None:
|
|
15
|
+
"""Start Odoo and PostgreSQL containers."""
|
|
16
|
+
cfg = load_config()
|
|
17
|
+
success(f"Starting Odoo ({cfg.odoo_version}) with Python {cfg.python_version}...")
|
|
18
|
+
|
|
19
|
+
# Check if odoo.conf exists
|
|
20
|
+
if not cfg.docker_config_file.exists():
|
|
21
|
+
error(f"Config file not found at {cfg.docker_config_file}")
|
|
22
|
+
error("Run 'odoo-dev setup' first to create the configuration.")
|
|
23
|
+
raise typer.Exit(1)
|
|
24
|
+
|
|
25
|
+
result = subprocess.run(
|
|
26
|
+
["docker", "compose", "--project-name", cfg.project_name, "up", "-d"],
|
|
27
|
+
cwd=cfg.script_dir,
|
|
28
|
+
)
|
|
29
|
+
if result.returncode != 0:
|
|
30
|
+
error("Failed to start containers. Check the logs for more information.")
|
|
31
|
+
raise typer.Exit(1)
|
|
32
|
+
|
|
33
|
+
success("Odoo is starting up...")
|
|
34
|
+
success("Web interface: http://localhost:8069")
|
|
35
|
+
success("Debug port: localhost:5678")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@app.command()
|
|
39
|
+
def stop() -> None:
|
|
40
|
+
"""Stop all containers."""
|
|
41
|
+
cfg = load_config()
|
|
42
|
+
success("Stopping containers...")
|
|
43
|
+
subprocess.run(
|
|
44
|
+
["docker", "compose", "--project-name", cfg.project_name, "down"],
|
|
45
|
+
cwd=cfg.script_dir,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@app.command()
|
|
50
|
+
def restart() -> None:
|
|
51
|
+
"""Restart all containers."""
|
|
52
|
+
stop()
|
|
53
|
+
start()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@app.command()
|
|
57
|
+
def logs() -> None:
|
|
58
|
+
"""Show logs from the Odoo container."""
|
|
59
|
+
cfg = load_config()
|
|
60
|
+
success("Showing logs from Odoo container...")
|
|
61
|
+
subprocess.run(
|
|
62
|
+
["docker", "compose", "--project-name", cfg.project_name, "logs", "-f", "odoo"],
|
|
63
|
+
cwd=cfg.script_dir,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.command()
|
|
68
|
+
def build(
|
|
69
|
+
community: bool = typer.Option(
|
|
70
|
+
False, "--community", help="Build for Community edition only"
|
|
71
|
+
),
|
|
72
|
+
) -> None:
|
|
73
|
+
"""Rebuild the Odoo Docker image."""
|
|
74
|
+
import shutil
|
|
75
|
+
|
|
76
|
+
cfg = load_config()
|
|
77
|
+
success(f"Rebuilding Odoo image ({cfg.project_name}-odoo:{cfg.odoo_version})...")
|
|
78
|
+
|
|
79
|
+
# Create temporary build context
|
|
80
|
+
build_context = cfg.project_dir / f".odoo-build-{subprocess.os.getpid()}"
|
|
81
|
+
build_context.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
# Copy necessary files to build context
|
|
85
|
+
shutil.copy(cfg.script_dir / "Dockerfile", build_context)
|
|
86
|
+
shutil.copy(cfg.script_dir / "docker-entrypoint.sh", build_context)
|
|
87
|
+
shutil.copy(cfg.docker_config_file, build_context / "odoo.conf")
|
|
88
|
+
|
|
89
|
+
# Copy requirements files
|
|
90
|
+
if (cfg.project_dir / "requirements.txt").exists():
|
|
91
|
+
shutil.copy(cfg.project_dir / "requirements.txt", build_context)
|
|
92
|
+
if (cfg.project_dir / "odoo" / "requirements.txt").exists():
|
|
93
|
+
shutil.copy(
|
|
94
|
+
cfg.project_dir / "odoo" / "requirements.txt",
|
|
95
|
+
build_context / "odoo-requirements.txt",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Create minimal docker-compose for build
|
|
99
|
+
compose_content = """name: ${PROJECT_NAME:-odoo}
|
|
100
|
+
|
|
101
|
+
services:
|
|
102
|
+
odoo:
|
|
103
|
+
image: ${PROJECT_NAME:-odoo-deploy}-odoo:${ODOO_VERSION:-17.0}
|
|
104
|
+
build:
|
|
105
|
+
context: .
|
|
106
|
+
dockerfile: Dockerfile
|
|
107
|
+
args:
|
|
108
|
+
- ODOO_VERSION=${ODOO_VERSION:-17.0}
|
|
109
|
+
- PYTHON_VERSION=${PYTHON_VERSION:-3.12}
|
|
110
|
+
platforms:
|
|
111
|
+
- linux/amd64
|
|
112
|
+
"""
|
|
113
|
+
(build_context / "docker-compose.yml").write_text(compose_content)
|
|
114
|
+
|
|
115
|
+
# Build the image
|
|
116
|
+
env = subprocess.os.environ.copy()
|
|
117
|
+
env["PROJECT_NAME"] = cfg.project_name
|
|
118
|
+
env["ODOO_VERSION"] = cfg.odoo_version
|
|
119
|
+
env["PYTHON_VERSION"] = cfg.python_version
|
|
120
|
+
|
|
121
|
+
result = subprocess.run(
|
|
122
|
+
[
|
|
123
|
+
"docker",
|
|
124
|
+
"compose",
|
|
125
|
+
"build",
|
|
126
|
+
"--build-arg",
|
|
127
|
+
f"ODOO_VERSION={cfg.odoo_version}",
|
|
128
|
+
"--build-arg",
|
|
129
|
+
f"PYTHON_VERSION={cfg.python_version}",
|
|
130
|
+
"odoo",
|
|
131
|
+
],
|
|
132
|
+
cwd=build_context,
|
|
133
|
+
env=env,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if result.returncode != 0:
|
|
137
|
+
error("Failed to build Docker image.")
|
|
138
|
+
raise typer.Exit(1)
|
|
139
|
+
|
|
140
|
+
success("Docker image built successfully!")
|
|
141
|
+
|
|
142
|
+
finally:
|
|
143
|
+
shutil.rmtree(build_context, ignore_errors=True)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@app.command()
|
|
147
|
+
def shell(
|
|
148
|
+
db_name: str = typer.Argument(..., help="Database name"),
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Open an Odoo shell in the Docker container."""
|
|
151
|
+
cfg = load_config()
|
|
152
|
+
|
|
153
|
+
success(f"Opening Docker shell with database {db_name}...")
|
|
154
|
+
|
|
155
|
+
subprocess.run(
|
|
156
|
+
[
|
|
157
|
+
"docker",
|
|
158
|
+
"compose",
|
|
159
|
+
"--project-name",
|
|
160
|
+
cfg.project_name,
|
|
161
|
+
"exec",
|
|
162
|
+
"odoo",
|
|
163
|
+
"python",
|
|
164
|
+
"/opt/project/odoo/odoo-bin",
|
|
165
|
+
"shell",
|
|
166
|
+
"-d",
|
|
167
|
+
db_name,
|
|
168
|
+
"-c",
|
|
169
|
+
"/etc/odoo/odoo.conf",
|
|
170
|
+
"--no-http",
|
|
171
|
+
],
|
|
172
|
+
cwd=cfg.script_dir,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@app.command()
|
|
177
|
+
def psql() -> None:
|
|
178
|
+
"""Open a PostgreSQL shell in the Docker container."""
|
|
179
|
+
cfg = load_config()
|
|
180
|
+
|
|
181
|
+
success("Opening PostgreSQL shell...")
|
|
182
|
+
subprocess.run(
|
|
183
|
+
[
|
|
184
|
+
"docker",
|
|
185
|
+
"compose",
|
|
186
|
+
"--project-name",
|
|
187
|
+
cfg.project_name,
|
|
188
|
+
"exec",
|
|
189
|
+
"db",
|
|
190
|
+
"psql",
|
|
191
|
+
"-U",
|
|
192
|
+
"odoo",
|
|
193
|
+
"postgres",
|
|
194
|
+
],
|
|
195
|
+
cwd=cfg.script_dir,
|
|
196
|
+
)
|