odoo-installer 0.1.1__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.
- odoo_installer-0.1.1/.gitignore +26 -0
- odoo_installer-0.1.1/CHANGELOG.md +103 -0
- odoo_installer-0.1.1/DEVELOPMENT.md +465 -0
- odoo_installer-0.1.1/LICENSE +21 -0
- odoo_installer-0.1.1/PKG-INFO +130 -0
- odoo_installer-0.1.1/README.md +92 -0
- odoo_installer-0.1.1/pyproject.toml +110 -0
- odoo_installer-0.1.1/src/odoo_installer/__init__.py +5 -0
- odoo_installer-0.1.1/src/odoo_installer/__main__.py +8 -0
- odoo_installer-0.1.1/src/odoo_installer/adapters/__init__.py +5 -0
- odoo_installer-0.1.1/src/odoo_installer/adapters/docker.py +189 -0
- odoo_installer-0.1.1/src/odoo_installer/adapters/filesystem.py +88 -0
- odoo_installer-0.1.1/src/odoo_installer/adapters/git.py +88 -0
- odoo_installer-0.1.1/src/odoo_installer/adapters/github.py +91 -0
- odoo_installer-0.1.1/src/odoo_installer/adapters/system.py +114 -0
- odoo_installer-0.1.1/src/odoo_installer/cli/__init__.py +0 -0
- odoo_installer-0.1.1/src/odoo_installer/cli/common.py +51 -0
- odoo_installer-0.1.1/src/odoo_installer/cli/config.py +105 -0
- odoo_installer-0.1.1/src/odoo_installer/cli/db.py +140 -0
- odoo_installer-0.1.1/src/odoo_installer/cli/deps.py +54 -0
- odoo_installer-0.1.1/src/odoo_installer/cli/doctor.py +36 -0
- odoo_installer-0.1.1/src/odoo_installer/cli/install.py +45 -0
- odoo_installer-0.1.1/src/odoo_installer/cli/instance.py +267 -0
- odoo_installer-0.1.1/src/odoo_installer/cli/main.py +65 -0
- odoo_installer-0.1.1/src/odoo_installer/cli/module.py +399 -0
- odoo_installer-0.1.1/src/odoo_installer/cli/test.py +180 -0
- odoo_installer-0.1.1/src/odoo_installer/config.py +152 -0
- odoo_installer-0.1.1/src/odoo_installer/console.py +157 -0
- odoo_installer-0.1.1/src/odoo_installer/constants.py +27 -0
- odoo_installer-0.1.1/src/odoo_installer/core/__init__.py +1 -0
- odoo_installer-0.1.1/src/odoo_installer/core/dbms.py +168 -0
- odoo_installer-0.1.1/src/odoo_installer/core/instances.py +511 -0
- odoo_installer-0.1.1/src/odoo_installer/core/modules.py +572 -0
- odoo_installer-0.1.1/src/odoo_installer/core/plan.py +37 -0
- odoo_installer-0.1.1/src/odoo_installer/core/prereqs.py +209 -0
- odoo_installer-0.1.1/src/odoo_installer/core/runner.py +41 -0
- odoo_installer-0.1.1/src/odoo_installer/core/stack.py +83 -0
- odoo_installer-0.1.1/src/odoo_installer/core/tester.py +166 -0
- odoo_installer-0.1.1/src/odoo_installer/exceptions.py +53 -0
- odoo_installer-0.1.1/src/odoo_installer/py.typed +0 -0
- odoo_installer-0.1.1/src/odoo_installer/schemas.py +145 -0
- odoo_installer-0.1.1/src/odoo_installer/templates/.env.j2 +7 -0
- odoo_installer-0.1.1/src/odoo_installer/templates/docker-compose.yml.j2 +41 -0
- odoo_installer-0.1.1/src/odoo_installer/templates/odoo.conf.j2 +11 -0
- odoo_installer-0.1.1/tests/unit/fakes.py +277 -0
- odoo_installer-0.1.1/tests/unit/test_adopt.py +146 -0
- odoo_installer-0.1.1/tests/unit/test_cli.py +47 -0
- odoo_installer-0.1.1/tests/unit/test_cli_config.py +124 -0
- odoo_installer-0.1.1/tests/unit/test_cli_db.py +184 -0
- odoo_installer-0.1.1/tests/unit/test_cli_doctor.py +93 -0
- odoo_installer-0.1.1/tests/unit/test_cli_install.py +77 -0
- odoo_installer-0.1.1/tests/unit/test_cli_instance.py +174 -0
- odoo_installer-0.1.1/tests/unit/test_cli_module.py +304 -0
- odoo_installer-0.1.1/tests/unit/test_cli_test.py +175 -0
- odoo_installer-0.1.1/tests/unit/test_config.py +178 -0
- odoo_installer-0.1.1/tests/unit/test_dbms.py +96 -0
- odoo_installer-0.1.1/tests/unit/test_exceptions.py +26 -0
- odoo_installer-0.1.1/tests/unit/test_filesystem.py +118 -0
- odoo_installer-0.1.1/tests/unit/test_instances.py +277 -0
- odoo_installer-0.1.1/tests/unit/test_modules.py +397 -0
- odoo_installer-0.1.1/tests/unit/test_plan.py +67 -0
- odoo_installer-0.1.1/tests/unit/test_prereqs.py +117 -0
- odoo_installer-0.1.1/tests/unit/test_stack.py +62 -0
- odoo_installer-0.1.1/tests/unit/test_tester.py +151 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
|
|
11
|
+
# Tooling caches
|
|
12
|
+
.mypy_cache/
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
.coverage
|
|
16
|
+
coverage.xml
|
|
17
|
+
htmlcov/
|
|
18
|
+
|
|
19
|
+
# Editors / OS
|
|
20
|
+
.idea/
|
|
21
|
+
.vscode/
|
|
22
|
+
*.swp
|
|
23
|
+
.DS_Store
|
|
24
|
+
|
|
25
|
+
# Project
|
|
26
|
+
*.log
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.1] - 2026-09-01
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- DEVELOPMENT.md synced with the v0.1.0 implementation state: composition root
|
|
13
|
+
(`cli/deps.py`) and `core/plan.py` added to the package layout, real schema model
|
|
14
|
+
names, the `.env`/compose template contents in §4, `tested.toml` in the state model,
|
|
15
|
+
and the removed `--debug` claim.
|
|
16
|
+
- The deferred integration test layer and the CI docker job are now recorded as a v1.1
|
|
17
|
+
roadmap item with a v1.1 priority list.
|
|
18
|
+
- README install instructions no longer claim PyPI availability for the unpublished
|
|
19
|
+
0.1.0 release.
|
|
20
|
+
- Removed the stale `--debug` traceback claim from the exceptions module docstring.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- First publication to PyPI (`pip install odoo-installer`).
|
|
25
|
+
|
|
26
|
+
## [0.1.0] - 2026-08-31
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Project scaffold: src-layout package, Typer CLI entry point (`odoo-installer`, alias
|
|
31
|
+
`oii`), `--version` flag and `version` command, static constants for the Odoo 19.0
|
|
32
|
+
stack, and the typed error hierarchy.
|
|
33
|
+
- Developer tooling: ruff (format + lint), mypy (strict), pytest with coverage, and
|
|
34
|
+
unit tests for the CLI entry point.
|
|
35
|
+
- CI: lint/types and unit test matrix (Python 3.11–3.13) via GitHub Actions.
|
|
36
|
+
- `doctor` command: host checks for the docker engine and compose plugin, docker group
|
|
37
|
+
membership (read from `/etc/group`, not stale process groups), git, disk space at the
|
|
38
|
+
instances root, port availability on 8069–8099, and GitHub API reachability. Renders a
|
|
39
|
+
rich table or `--json`; exits with code 4 when a critical check fails.
|
|
40
|
+
- `config show|set|path` sub-app backed by validated, atomic TOML persistence
|
|
41
|
+
(`~/.config/odoo-installer/config.toml`); instance registry load/save helpers for M2.
|
|
42
|
+
- Host adapters (docker, system, github, filesystem) behind `Protocol` interfaces;
|
|
43
|
+
unit tests run fully offline against fakes.
|
|
44
|
+
- `install` command: plan-first host prerequisite installation (docker engine, compose
|
|
45
|
+
plugin, git) via pacman/apt with `--apply`; idempotent — satisfied hosts are no-ops.
|
|
46
|
+
- `instance` sub-app: `create` (dry-run plan → `--apply`, auto port allocation in the
|
|
47
|
+
configured range, jinja-rendered `docker-compose.yml`/`.env`/`odoo.conf`, generated
|
|
48
|
+
secrets persisted across re-runs, `docker compose up -d` + health wait),
|
|
49
|
+
`list`, `show`, `start`, `stop`, `restart`, and `remove` (dry-run by default;
|
|
50
|
+
execution requires `--apply --yes`; `--remove-data` destroys the pgdata volume).
|
|
51
|
+
- Instance state: registry (`registry.toml`) plus per-instance manifest
|
|
52
|
+
(`.odoo-installer.json`); `create` re-runs are idempotent and pin the recorded port.
|
|
53
|
+
- Docker adapter additions: `compose`, `wait_healthy` (with log capture on failure),
|
|
54
|
+
`logs`; system adapter package/service operations; filesystem adapter atomic
|
|
55
|
+
writes with permission modes (`.env` 0600, `odoo.conf` 0644 for container readability).
|
|
56
|
+
- `instance adopt <dir>`: register an existing compose stack (detected purely from
|
|
57
|
+
container labels — no compose file parsing) and manage it read-mostly: `start` uses
|
|
58
|
+
`docker compose start` so adopted stacks are never recreated, and no stack files are
|
|
59
|
+
rewritten (only the odoo-installer manifest is added).
|
|
60
|
+
- `db` sub-app: `list` (sizes via `pg_database_size`), `create` (idempotent),
|
|
61
|
+
`drop` and `reset` — executed through `psql` in the db container. Database names are
|
|
62
|
+
always explicit CLI arguments; `postgres`/`template0`/`template1` refuse to be
|
|
63
|
+
dropped; `drop`/`reset` are plan-first and execute only with `--apply --yes`.
|
|
64
|
+
- `module` sub-app for OCA repositories and modules:
|
|
65
|
+
`add` verifies the 19.0 branch via the GitHub API before cloning (never guesses —
|
|
66
|
+
a repo whose default branch is 18.0 but has 19.0 is handled), clones into the
|
|
67
|
+
instance's `repos/` (or the configured `repo_root` for adopted stacks), supports
|
|
68
|
+
`--sparse` and mounting existing checkouts unmutated (`--repo`), appends the compose
|
|
69
|
+
volume + `addons_path` with automatic backups and `docker compose config`
|
|
70
|
+
validation, and restarts web only for stacks the CLI created (adopted stacks get a
|
|
71
|
+
"restart with your own tooling" report instead);
|
|
72
|
+
`list` shows modules with per-database install states (`--db`, `--json`);
|
|
73
|
+
`search` queries the OCA GitHub org;
|
|
74
|
+
`install`/`upgrade` run `odoo -i/-u --stop-after-init --http-port=8071` inside the
|
|
75
|
+
web container against an explicit `--db` (scratch `oitest_*` recommended) and verify
|
|
76
|
+
the resulting `ir_module_module` states;
|
|
77
|
+
`remove` unmounts, optionally resets module states and purges the clone.
|
|
78
|
+
- `filesystem.write_text` now preserves an existing file's permission mode across
|
|
79
|
+
atomic replacement — edits of container-mounted configs (odoo.conf) no longer
|
|
80
|
+
accidentally become unreadable to the container's odoo user.
|
|
81
|
+
- `module test <name>`: installs the module on a throwaway `oitest_<module>`
|
|
82
|
+
database, runs `--test-enable --test-tags=/<module>` inside the web container,
|
|
83
|
+
captures the full log (XDG state dir for adopted stacks), and prints PASS/FAIL
|
|
84
|
+
(exit 3 on failure). A PASS is recorded in the installable-addons whitelist
|
|
85
|
+
(`~/.config/odoo-installer/tested.toml`) with repo, branch, commit and log path.
|
|
86
|
+
- Installable-addons whitelist enforcement: `module install`/`upgrade` refuse
|
|
87
|
+
untested modules unless `--allow-untested` is passed; `module list` shows a
|
|
88
|
+
Tested column.
|
|
89
|
+
- Shallow single-branch clones (`--depth 1 --branch 19.0`) for owned clones —
|
|
90
|
+
big repos like OCA/web now cost ~22 MB instead of hundreds.
|
|
91
|
+
- `config edit`: opens `config.toml` in `$EDITOR`, validates the result before
|
|
92
|
+
saving, and refuses invalid edits (nothing is written).
|
|
93
|
+
- `test suite`: batch-tests every module on an instance's addons_path (filter by
|
|
94
|
+
`--only <repo>` or `--modules m1,m2`), one scratch DB per module, sequential;
|
|
95
|
+
PASSes feed the whitelist; Markdown/JSON reports with `--output` (repeatable,
|
|
96
|
+
e.g. `--output report.md --output report.json`); rich summary; exit 3 on any
|
|
97
|
+
failure; `--keep-db` keeps scratch databases.
|
|
98
|
+
- Test failure classification: logs are parsed into failure kinds (test failure,
|
|
99
|
+
import error, not installable, addons_path, manifest, traceback, exit code).
|
|
100
|
+
- Test tooling: `tests/unit/test_filesystem.py` covers the filesystem adapter's
|
|
101
|
+
mode-preservation semantics directly; the docker/git/github/system adapters
|
|
102
|
+
(thin subprocess/network wrappers exercised live) are omitted from coverage,
|
|
103
|
+
which is pinned at ≥ 85%.
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
# DEVELOPMENT.md — `odoo-installer`
|
|
2
|
+
|
|
3
|
+
Development guide for **odoo-installer**: a professional, pip-installable Python CLI that
|
|
4
|
+
installs, configures, and manages **Odoo 19.0 Docker stacks** and their modules — including
|
|
5
|
+
correct-branch OCA module management and automated installability testing of every core and
|
|
6
|
+
OCA module.
|
|
7
|
+
|
|
8
|
+
This document is the source of truth for architecture, scope, conventions, and milestones.
|
|
9
|
+
Any change to an approved decision must be reflected here first.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Approved decisions
|
|
14
|
+
|
|
15
|
+
Locked in with the project owner before implementation:
|
|
16
|
+
|
|
17
|
+
| # | Decision | Choice |
|
|
18
|
+
|---|----------|--------|
|
|
19
|
+
| D1 | Runtime model | **Docker only.** The CLI never installs Odoo natively. It generates and manages `docker compose` stacks (web + db) and runs all Odoo commands inside containers. |
|
|
20
|
+
| D2 | Odoo version | **19.0 only** for v1. The version is a constant (`ODOO_VERSION = "19.0"`), not a user-facing matrix. Default image: `odoo:19.0` (Docker Hub, active; dated tags like `19.0-20260817` exist). |
|
|
21
|
+
| D3 | Host OS support | **Arch first** (pacman adapter, tested for real on this machine), **Debian/Ubuntu next** (apt adapter in a later milestone). |
|
|
22
|
+
| D4 | Language & packaging | Python `>= 3.11`, src layout, `hatchling` build backend, console script `odoo-installer` (+ short alias `oii`), developed and used from a local `.venv`. |
|
|
23
|
+
| D5 | CLI framework | **Typer + Rich** (typed commands, tables, shell completion). |
|
|
24
|
+
| D6 | Config | **TOML**: global `~/.config/odoo-installer/config.toml`, per-instance manifest `<stack>/.odoo-installer.json`, global registry `~/.config/odoo-installer/registry.toml`. |
|
|
25
|
+
| D7 | Safety | System-changing and destructive commands are **plan-first**: without `--apply` / `--yes` they print exactly what they would do and exit 0. Idempotent re-runs are a requirement. |
|
|
26
|
+
| D8 | Git access | Plain `git` via `subprocess` (no GitPython). GitHub metadata via **httpx**. |
|
|
27
|
+
|
|
28
|
+
### Non-goals for v1
|
|
29
|
+
|
|
30
|
+
- Native (systemd / non-Docker) Odoo installation.
|
|
31
|
+
- Odoo versions other than 19.0 (the code keeps the version a single constant so later support is cheap).
|
|
32
|
+
- GUI / TUI.
|
|
33
|
+
- Database backup/restore, SMTP setup wizard, reverse-proxy/TLS generation (future roadmap).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 2. Product scope — command surface
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
odoo-installer doctor [--json]
|
|
41
|
+
Host diagnostics: docker engine, compose plugin, git, disk space, port conflicts,
|
|
42
|
+
github.com reachability, user-in-docker-group. Exit code 4 when a critical check fails.
|
|
43
|
+
|
|
44
|
+
odoo-installer install [--apply]
|
|
45
|
+
Install HOST PREREQUISITES only (docker engine, compose plugin, git) via pacman/apt.
|
|
46
|
+
Never installs Odoo itself — that is what the stack is for.
|
|
47
|
+
|
|
48
|
+
odoo-installer instance create <name> [--dir PATH] [--http-port N] [--image TAG] [--pg-tag N] [--apply]
|
|
49
|
+
Render a complete compose stack (compose file, .env, config/odoo.conf), `up -d`,
|
|
50
|
+
wait for /web/health, register the instance.
|
|
51
|
+
odoo-installer instance list | show <name>
|
|
52
|
+
odoo-installer instance start|stop|restart <name>
|
|
53
|
+
odoo-installer instance remove <name> [--remove-data] [--yes]
|
|
54
|
+
remove defaults to keeping volumes/DBs; --remove-data destroys the pgdata volume.
|
|
55
|
+
odoo-installer instance adopt <dir>
|
|
56
|
+
Register an EXISTING compose stack (e.g. ~/Projects/odoo-docker) without rewriting it.
|
|
57
|
+
Adopted stacks are managed read-mostly: exec/psql/logs are allowed; file rewriting is not.
|
|
58
|
+
|
|
59
|
+
odoo-installer module add <oca-repo> [--modules m1,m2] [--sparse] [--repo PATH] [--apply]
|
|
60
|
+
Clone OCA/<repo> at the branch matching 19.0, mount it into the stack, rewrite
|
|
61
|
+
addons_path, restart web. --repo mounts an existing local checkout instead of cloning.
|
|
62
|
+
odoo-installer module list [--instance NAME] [--json]
|
|
63
|
+
odoo-installer module search <query>
|
|
64
|
+
GitHub API: find OCA repos and the modules they contain for 19.0.
|
|
65
|
+
odoo-installer module install <name...> [--db DB]
|
|
66
|
+
odoo-installer module upgrade <name...> [--db DB]
|
|
67
|
+
Run `odoo -d <db> -i/-u <name> --stop-after-init --http-port=8071` inside the web container.
|
|
68
|
+
odoo-installer module remove <name...> [--db DB] [--purge-repo] [--yes]
|
|
69
|
+
|
|
70
|
+
odoo-installer db list [--instance] | create <db> [--instance]
|
|
71
|
+
The database name is always an explicit positional argument, never a default.
|
|
72
|
+
odoo-installer db drop|reset <db> [--instance] [--yes] [--apply]
|
|
73
|
+
Executed through psql in the db container. drop/reset always require --yes.
|
|
74
|
+
|
|
75
|
+
odoo-installer module test <name> [--instance] [--keep-db]
|
|
76
|
+
Install the module on a throwaway DB (oitest_<module>_<ts>), run its tests
|
|
77
|
+
(--test-enable --test-tags /<module>), capture and parse the log, print PASS/FAIL.
|
|
78
|
+
odoo-installer test suite [--instance <name>] [--only <repo>] [--modules m1,m2]
|
|
79
|
+
[--output report.{md,json}] [--keep-db]
|
|
80
|
+
Batch over the modules of every repo on the stack's addons_path (filter by
|
|
81
|
+
repo or module list); one scratch DB (oitest_<module>) per module, sequential;
|
|
82
|
+
PASS results are recorded in tested.toml; Markdown/JSON report + rich summary
|
|
83
|
+
table. Exit 3 if any module fails.
|
|
84
|
+
|
|
85
|
+
odoo-installer config show | set <key> <value> | edit | path
|
|
86
|
+
odoo-installer version
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Manual, step-by-step usage is a first-class goal: every composite action
|
|
90
|
+
(`install`, `instance create`, `module add`) is decomposable into the individual commands
|
|
91
|
+
above, and every step is idempotent so users can drive installation manually.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 3. Architecture
|
|
96
|
+
|
|
97
|
+
### 3.1 Layering
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
┌─────────────────────────────────────────────────────────┐
|
|
101
|
+
│ cli/ Typer commands: parse → core → render (rich) │ thin, no logic
|
|
102
|
+
├─────────────────────────────────────────────────────────┤
|
|
103
|
+
│ core/ Business logic, pure Python, no direct I/O │ fully unit-testable
|
|
104
|
+
│ prereqs, stack, instances, modules, runner, tester, │
|
|
105
|
+
│ plan, dbms │
|
|
106
|
+
├─────────────────────────────────────────────────────────┤
|
|
107
|
+
│ adapters/ The ONLY code that touches the world │ behind Protocols
|
|
108
|
+
│ docker, git, github, system, filesystem │
|
|
109
|
+
├─────────────────────────────────────────────────────────┤
|
|
110
|
+
│ schemas.py pydantic models (GlobalConfig, Registry, │
|
|
111
|
+
│ InstanceManifest, RepoRecord, TestedModule, ...) │
|
|
112
|
+
│ config.py config resolution + persistence │
|
|
113
|
+
│ console.py rich output helpers, plan/dry-run rendering │
|
|
114
|
+
│ exceptions.py typed error hierarchy │
|
|
115
|
+
└─────────────────────────────────────────────────────────┘
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Rules:
|
|
119
|
+
|
|
120
|
+
1. **cli/ commands never import adapters.** Commands build inputs, call `core`, render
|
|
121
|
+
results. The single exception is the composition root `cli/deps.py`, which wires the
|
|
122
|
+
real adapters into a `Container` (rule 2).
|
|
123
|
+
2. **core/ depends on adapters only through `typing.Protocol` interfaces** (e.g.
|
|
124
|
+
`DockerLike`, `GitLike`, `SystemLike`), injected as constructor arguments. Tests pass
|
|
125
|
+
fakes; production wires real adapters in `cli/`.
|
|
126
|
+
3. **All external effects go through adapters** — no `subprocess`, `httpx`, or raw file
|
|
127
|
+
writes outside `adapters/` and `config.py`.
|
|
128
|
+
4. Every mutating core function returns a **plan object** (list of concrete steps) that the
|
|
129
|
+
CLI either renders (dry-run) or executes (`--apply`). This makes dry-run exact by
|
|
130
|
+
construction — the printed plan *is* the executed code path.
|
|
131
|
+
|
|
132
|
+
### 3.2 Package layout
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
src/odoo_installer/
|
|
136
|
+
├── __init__.py # __version__
|
|
137
|
+
├── __main__.py # python -m odoo_installer
|
|
138
|
+
├── constants.py # ODOO_VERSION="19.0", DEFAULT_IMAGE="odoo:19.0", ports, names
|
|
139
|
+
├── exceptions.py
|
|
140
|
+
├── schemas.py # GlobalConfig, Registry(Entry), InstanceManifest, RepoRecord, TestedModule/Registry
|
|
141
|
+
├── config.py # TOML load/merge/save, path resolution (platformdirs)
|
|
142
|
+
├── console.py # rich console, tables, plan renderer, --json output
|
|
143
|
+
├── cli/
|
|
144
|
+
│ ├── main.py # Typer app assembly, global callbacks, version, completion
|
|
145
|
+
│ ├── deps.py # composition root: builds Container with real adapters (rule 2)
|
|
146
|
+
│ ├── common.py # shared command helpers (instance resolution, tested-pass recording)
|
|
147
|
+
│ ├── doctor.py, install.py, instance.py, module.py, db.py, test.py, config.py
|
|
148
|
+
├── core/
|
|
149
|
+
│ ├── prereqs.py # host prerequisite checks + install plans (pacman/apt)
|
|
150
|
+
│ ├── stack.py # compose/odoo.conf/.env rendering, health wait, addons_path rewrite
|
|
151
|
+
│ ├── instances.py # registry + per-instance manifest CRUD
|
|
152
|
+
│ ├── modules.py # OCA repo resolution, cloning, module discovery
|
|
153
|
+
│ ├── runner.py # odoo command execution inside the web container
|
|
154
|
+
│ ├── tester.py # scratch-DB test runs, log parsing, report building
|
|
155
|
+
│ ├── plan.py # Step model + apply_steps executor (dry-run/--apply)
|
|
156
|
+
│ └── dbms.py # database list/create/drop/reset via psql
|
|
157
|
+
├── adapters/
|
|
158
|
+
│ ├── docker.py # `docker` / `docker compose` subprocess wrapper
|
|
159
|
+
│ ├── git.py # clone/fetch/checkout/sparse-checkout/rev-parse
|
|
160
|
+
│ ├── github.py # httpx: repo search, branch existence, rate-limit handling
|
|
161
|
+
│ ├── system.py # distro detect (Arch/Debian), pacman/apt command building
|
|
162
|
+
│ └── filesystem.py # paths, atomic file writes, dir scaffolding
|
|
163
|
+
├── templates/ # docker-compose.yml.j2, odoo.conf.j2, .env.j2 (jinja2)
|
|
164
|
+
└── py.typed
|
|
165
|
+
tests/
|
|
166
|
+
└── unit/ # fakes only, offline, < 5 s
|
|
167
|
+
└── fakes.py # FakeDocker, FakeGit, FakeGitHub, FakeSystem, FakeFs
|
|
168
|
+
|
|
169
|
+
`tests/integration/` (real git/docker, marker `integration`) is deferred to v1.1 — see §8.
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 3.3 Dependencies
|
|
173
|
+
|
|
174
|
+
Runtime: `typer`, `rich`, `pydantic>=2`, `httpx`, `jinja2`, `tomli-w` (reads use stdlib
|
|
175
|
+
`tomllib`), `platformdirs`.
|
|
176
|
+
Dev: `pytest`, `pytest-cov`, `pytest-mock`, `ruff`, `mypy`, `pre-commit`, `build`, `twine`.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 4. The generated Docker stack
|
|
181
|
+
|
|
182
|
+
Default instances root: `~/odoo-instances/`. One directory per instance:
|
|
183
|
+
|
|
184
|
+
```text
|
|
185
|
+
~/odoo-instances/<name>/
|
|
186
|
+
├── docker-compose.yml # rendered from templates/
|
|
187
|
+
├── .env # COMPOSE_PROJECT_NAME, ODOO_IMAGE, PG_TAG, HTTP_PORT, POSTGRES_PASSWORD, ADMIN_PASSWD
|
|
188
|
+
├── config/odoo.conf # addons_path rewritten by `module add`
|
|
189
|
+
├── addons/local/ # user's own modules (mounted as /mnt/extra-addons)
|
|
190
|
+
├── repos/<oca-repo>/ # OCA clones (each mounted as /mnt/oca/<repo>)
|
|
191
|
+
├── logs/ # captured test/install logs (test-<module>-<ts>.log)
|
|
192
|
+
└── .odoo-installer.json # instance manifest (see §5)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Reference compose shape (what the template must render):
|
|
196
|
+
|
|
197
|
+
```yaml
|
|
198
|
+
services:
|
|
199
|
+
web:
|
|
200
|
+
image: odoo:19.0 # .env: ODOO_IMAGE
|
|
201
|
+
restart: unless-stopped
|
|
202
|
+
depends_on: { db: { condition: service_healthy } }
|
|
203
|
+
ports: ["8069:8069"] # .env: HTTP_PORT
|
|
204
|
+
environment:
|
|
205
|
+
DB_HOST: db
|
|
206
|
+
DB_PORT: "5432"
|
|
207
|
+
DB_USER: odoo
|
|
208
|
+
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
|
209
|
+
volumes:
|
|
210
|
+
- ./config:/etc/odoo
|
|
211
|
+
- ./addons/local:/mnt/extra-addons
|
|
212
|
+
# module add appends one line per OCA repo: ./repos/<repo>:/mnt/oca/<repo>
|
|
213
|
+
command: ["odoo", "-c", "/etc/odoo/odoo.conf"]
|
|
214
|
+
healthcheck:
|
|
215
|
+
test: ["CMD-SHELL", "curl -f http://localhost:8069/web/health || exit 1"]
|
|
216
|
+
interval: 10s
|
|
217
|
+
timeout: 5s
|
|
218
|
+
retries: 30
|
|
219
|
+
start_period: 30s
|
|
220
|
+
db:
|
|
221
|
+
image: postgres:17 # .env: PG_TAG (configurable)
|
|
222
|
+
restart: unless-stopped
|
|
223
|
+
environment:
|
|
224
|
+
POSTGRES_DB: postgres
|
|
225
|
+
POSTGRES_USER: odoo
|
|
226
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
227
|
+
volumes: [pgdata:/var/lib/postgresql/data]
|
|
228
|
+
healthcheck:
|
|
229
|
+
test: ["CMD-SHELL", "pg_isready -U odoo"]
|
|
230
|
+
interval: 5s
|
|
231
|
+
timeout: 5s
|
|
232
|
+
retries: 30
|
|
233
|
+
volumes: { pgdata: }
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
- The db service is **not** published on the host; all DB access goes through the stack.
|
|
237
|
+
- `addons_path` in `odoo.conf` starts as `/mnt/extra-addons` and gains `/mnt/oca/<repo>`
|
|
238
|
+
entries when repos are added; `module add` rewrites the file and restarts `web`.
|
|
239
|
+
- Port allocation: first free port in 8069–8099 unless `--http-port` is given (the live
|
|
240
|
+
stack on this machine already owns 8069).
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 5. State model
|
|
245
|
+
|
|
246
|
+
| File | Scope | Content |
|
|
247
|
+
|------|-------|---------|
|
|
248
|
+
| `~/.config/odoo-installer/config.toml` | global user config | instance root, default ports, default pg tag, GitHub token env var name |
|
|
249
|
+
| `~/.config/odoo-installer/registry.toml` | instance registry | `name → {dir, http_port, created_at, adopted: bool}` |
|
|
250
|
+
| `~/.config/odoo-installer/tested.toml` | installable-addons whitelist | `module → {repo, branch, commit, db, log_path}`; written by `module test` / `test suite` PASSes; `module install`/`upgrade` refuse modules that are not listed unless `--allow-untested` |
|
|
251
|
+
| `<stack>/.odoo-installer.json` | per instance | schema_version, odoo_version, image, pg_tag, applied steps, added repos `{repo, url, branch, commit, modules, mount}`, adopted flag |
|
|
252
|
+
| `<stack>/repos/<repo>/` | OCA clones | git state is the truth for branch/commit; manifest records the last synced commit |
|
|
253
|
+
|
|
254
|
+
Config precedence: **CLI flags > instance manifest > global config.toml > constants.**
|
|
255
|
+
All config writes are atomic (write temp file + rename).
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## 6. OCA integration rules
|
|
260
|
+
|
|
261
|
+
The tool must behave exactly like the documented OCA workflow:
|
|
262
|
+
|
|
263
|
+
1. **Branch rule.** An OCA repo is always checked out at `origin/19.0` — verified to exist
|
|
264
|
+
via the GitHub API *before* cloning. The tool never guesses or falls back to `master`/`main`.
|
|
265
|
+
A missing `19.0` branch is a hard error naming the repo.
|
|
266
|
+
2. **Remotes.** Clones get `origin = https://github.com/OCA/<repo>.git`. If the user
|
|
267
|
+
supplies `--fork <user>`, `origin` is the fork and `upstream` is OCA (mirrors the
|
|
268
|
+
manual workflow).
|
|
269
|
+
3. **Sparse mode.** `--sparse` uses `git sparse-checkout` limited to the requested modules
|
|
270
|
+
(plus their manifest dirs) to keep clones small; default is a full clone.
|
|
271
|
+
4. **Existing checkouts.** `--repo <path>` mounts an existing local checkout instead of
|
|
272
|
+
cloning — this is how the tool coexists with the `~/dev/<repo>` + `~/dev/<repo>-deploy`
|
|
273
|
+
worktree pattern used on this machine: the CLI mounts whatever path it is told to and
|
|
274
|
+
never switches branches in a checkout it does not own.
|
|
275
|
+
5. **Module discovery.** A module = a directory containing `__manifest__.py`. Discovery
|
|
276
|
+
scans mounted repos; `module list` merges filesystem state with
|
|
277
|
+
`ir_module_module` state (via psql) and reports install state per DB.
|
|
278
|
+
6. **Install/upgrade semantics.** Runs inside the `web` container as
|
|
279
|
+
`odoo -d <db> -i/-u <module> --stop-after-init --http-port=8071` (alternate port
|
|
280
|
+
convention avoids clashing with the serving process — same as manual practice).
|
|
281
|
+
7. **Adopted stacks are read-mostly.** `instance adopt` never rewrites compose files;
|
|
282
|
+
it may only append addons mounts if explicitly confirmed, otherwise reports what the
|
|
283
|
+
user must add by hand.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## 7. Safety, idempotency, errors
|
|
288
|
+
|
|
289
|
+
- **Plan-first:** `install`, `instance create/remove`, `module add/remove`,
|
|
290
|
+
`db drop/reset`, and any host package operation print a numbered plan of the exact
|
|
291
|
+
commands and file writes, then require `--apply` (or `--yes` for prompts) to execute.
|
|
292
|
+
- **Idempotency:** every step checks current state first (package installed? repo already
|
|
293
|
+
cloned at right commit? addons_path already contains the entry?) and reports
|
|
294
|
+
`already satisfied` instead of redoing work. Re-running a completed install is a no-op.
|
|
295
|
+
- **Exit codes:** `0` success · `1` runtime error · `2` usage error (Typer default) ·
|
|
296
|
+
`3` test failures (test suite) · `4` doctor critical check failed.
|
|
297
|
+
- **Error hierarchy** (`exceptions.py`): `OdooInstallerError` base → `PrerequisiteError`,
|
|
298
|
+
`StackError`, `GitError`, `GitHubError`, `ConfigError`, `ModuleError`, `TestFailureError`.
|
|
299
|
+
The CLI renders user-facing messages and maps errors to the exit codes above
|
|
300
|
+
(no `--debug` flag exists in v1.0).
|
|
301
|
+
- **Live instance care:** commands that could touch the production `odoo` DB on this
|
|
302
|
+
machine require an explicit `--db` value (never a default) when the stack is adopted.
|
|
303
|
+
Scratch DBs used by `test` are named `oitest_*` and dropped afterwards unless `--keep-db`.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## 8. Testing strategy
|
|
308
|
+
|
|
309
|
+
Pyramid, enforced by CI:
|
|
310
|
+
|
|
311
|
+
| Level | Scope | Rules |
|
|
312
|
+
|-------|-------|-------|
|
|
313
|
+
| Unit (`tests/unit/`) | core + cli against `FakeDocker`, `FakeGit`, `FakeGitHub`, `FakeSystem`, `tmp_path` | offline, deterministic, < 5 s, no markers; every core function's plan generation AND execution paths covered |
|
|
314
|
+
| Integration (`tests/integration/`, marker `integration`, opt-in via `OII_INTEGRATION=1`) — **deferred to v1.1** | real `git clone` of a small OCA repo, real `docker compose up` on an ephemeral port, full `instance create → module add → module install → test module` cycle on a throwaway stack | planned: run locally and in a CI docker job; tear down everything in `finally` |
|
|
315
|
+
| Live smoke (manual, documented) | adopted `~/Projects/odoo-docker` stack | read-mostly commands + one scratch-DB module test; never mutates the `odoo` DB |
|
|
316
|
+
|
|
317
|
+
**Deferral note (recorded at v0.1.0):** the integration layer and its CI docker job
|
|
318
|
+
were deferred to v1.1 — the M4/M5 acceptance relied on the unit layer plus the
|
|
319
|
+
live-stack smoke instead. The `integration` marker remains registered in
|
|
320
|
+
`pyproject.toml`, but until the layer lands `pytest -m integration` selects zero tests
|
|
321
|
+
and exits 5, so it is **not** part of the v0.1.0 quality gates.
|
|
322
|
+
|
|
323
|
+
Quality gates (every milestone, all green before merge):
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
ruff format --check . && ruff check .
|
|
327
|
+
mypy src
|
|
328
|
+
pytest # unit
|
|
329
|
+
# v1.1: pytest -m integration # when docker/git available
|
|
330
|
+
pytest --cov=src/odoo_installer --cov-report=term-missing # keep ≥ 85% overall
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Log-parsing tests use inline recorded-style fixture logs (shaped from real 19.0 runs)
|
|
334
|
+
covering: pass, test failure, import error, missing manifest, "not installable",
|
|
335
|
+
addons-path warning.
|
|
336
|
+
|
|
337
|
+
CI (GitHub Actions): job `lint` (name `lint & types`; ruff format/check, mypy) and job
|
|
338
|
+
`unit` on Python 3.11/3.12/3.13. The docker-based `integration` job lands together with
|
|
339
|
+
the v1.1 integration layer; the workflow file documents this.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## 9. Packaging & release
|
|
344
|
+
|
|
345
|
+
```toml
|
|
346
|
+
[project]
|
|
347
|
+
name = "odoo-installer"
|
|
348
|
+
requires-python = ">=3.11"
|
|
349
|
+
dependencies = [ ... ] # see §3.3
|
|
350
|
+
[project.scripts]
|
|
351
|
+
odoo-installer = "odoo_installer.cli.main:app"
|
|
352
|
+
oii = "odoo_installer.cli.main:app"
|
|
353
|
+
[build-system]
|
|
354
|
+
requires = ["hatchling"]
|
|
355
|
+
build-backend = "hatchling.build"
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
- Developed with `pip install -e ".[dev]"` in `.venv`; used exactly like that on this machine.
|
|
359
|
+
- Versioning: SemVer, single source in `src/odoo_installer/__init__.py`
|
|
360
|
+
(`hatchling` reads it via `[tool.hatch.version]`).
|
|
361
|
+
- Release: `python -m build`, `twine check dist/*`, changelog in `CHANGELOG.md`
|
|
362
|
+
(Keep a Changelog). v1 target release: `0.1.0` at M6.
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## 10. Milestones
|
|
367
|
+
|
|
368
|
+
Each milestone ends with all quality gates green and a demo of the listed "done" behavior.
|
|
369
|
+
One logical change per commit; Conventional Commits (`feat:`, `fix:`, `chore:`, `docs:`,
|
|
370
|
+
`test:`) on branches `feat/<slug>` — this repo is not an OCA repo, so OCA commit prefixes
|
|
371
|
+
do not apply here.
|
|
372
|
+
|
|
373
|
+
### M0 — Scaffold
|
|
374
|
+
pyproject, src layout, `.venv`, Typer app with `--version`, ruff/mypy/pytest configured,
|
|
375
|
+
pre-commit, CI workflow, `python -m odoo_installer` works.
|
|
376
|
+
**Done when:** `pip install -e ".[dev]" && odoo-installer --version` succeeds; CI green.
|
|
377
|
+
|
|
378
|
+
### M1 — Config + doctor
|
|
379
|
+
`config.py` (TOML load/merge/save, precedence), `constants.py`, registry read/write,
|
|
380
|
+
`doctor` with all host checks, rich table + `--json`, exit code 4 semantics.
|
|
381
|
+
**Done when:** doctor reflects this machine's real state (docker present, 8069 busy);
|
|
382
|
+
unit tests cover config precedence and every check's pass/fail branch.
|
|
383
|
+
|
|
384
|
+
### M2 — Host prereqs + instance lifecycle
|
|
385
|
+
`install` (pacman adapter, plan-first, `--apply`), `instance create/list/show/start/stop/
|
|
386
|
+
restart/remove`, template rendering (compose/.env/odoo.conf), health wait, registry +
|
|
387
|
+
manifest writes, port auto-allocation.
|
|
388
|
+
**Done when:** a real instance `oitest` is created on this machine (dry-run → apply),
|
|
389
|
+
answers `/web/health`, `remove` leaves nothing behind; re-running `create` is a no-op.
|
|
390
|
+
|
|
391
|
+
### M3 — Adoption + databases
|
|
392
|
+
`instance adopt` (detect web/db services, ports, existing addons mounts), `db
|
|
393
|
+
list/create/drop/reset` via psql exec.
|
|
394
|
+
**Done when:** `~/Projects/odoo-docker` is adopted, `db list` matches `psql -l`, `db drop`
|
|
395
|
+
refuses without `--yes`.
|
|
396
|
+
|
|
397
|
+
### M4 — OCA modules
|
|
398
|
+
`module add` (branch verification, clone/sparse/existing-checkout, mount + addons_path
|
|
399
|
+
rewrite + restart), `module list/search/install/upgrade/remove`, GitHub search adapter.
|
|
400
|
+
**Done when:** an OCA module is added from GitHub at `origin/19.0`, installed into a
|
|
401
|
+
scratch DB inside the adopted stack, and listed with correct install state; integration
|
|
402
|
+
test runs the same flow against a throwaway stack.
|
|
403
|
+
|
|
404
|
+
### M5 — Test suite
|
|
405
|
+
`test module` (scratch DB, `--test-tags /<module>`, log capture to `logs/`, parser),
|
|
406
|
+
`test suite` with per-module scratch DBs, md/json reports, rich summary, exit code 3.
|
|
407
|
+
**Done when:** `test suite` on the adopted stack produces a correct PASS/FAIL report for
|
|
408
|
+
every module on the addons_path; fixture-log unit tests prove each failure class parses.
|
|
409
|
+
|
|
410
|
+
### M6 — Polish & release ✔ (v0.1.0, 2026-08-31)
|
|
411
|
+
README, `--help` UX pass, shell completion (`--install-completion`), error message audit,
|
|
412
|
+
coverage ≥ 85%, `python -m build` + `twine check`, CHANGELOG, tag `0.1.0`, optional
|
|
413
|
+
TestPyPI publish.
|
|
414
|
+
**Done:** coverage 90.7% (filesystem adapter unit-tested; subprocess adapters omitted with
|
|
415
|
+
rationale in `pyproject.toml`), wheel + sdist pass `twine check` and a clean-venv smoke
|
|
416
|
+
test, README/CHANGELOG finalized, `v0.1.0` tagged. TestPyPI publish left as an optional
|
|
417
|
+
follow-up; published to PyPI as 0.1.1 (2026-09-01).
|
|
418
|
+
|
|
419
|
+
**Deferral note:** the "integration test runs the same flow against a throwaway stack"
|
|
420
|
+
clauses in the M4/M5 acceptance criteria were satisfied via the unit layer + live-stack
|
|
421
|
+
smoke instead; the throwaway-stack integration test layer is deferred to v1.1 (see §8).
|
|
422
|
+
|
|
423
|
+
### v1.1 roadmap (priority order)
|
|
424
|
+
|
|
425
|
+
1. **Integration test layer** (`tests/integration/`, marker `integration`) and the CI
|
|
426
|
+
docker job — the biggest open gap: automated end-to-end
|
|
427
|
+
`instance create → module add → module install → test module` on a throwaway stack,
|
|
428
|
+
run locally and in CI.
|
|
429
|
+
2. `module upgrade-repos` — re-sync OCA clones to their recorded 19.0 branches.
|
|
430
|
+
3. apt adapter validation on a real Debian/Ubuntu machine (D3's "later milestone").
|
|
431
|
+
4. Publish to PyPI — ✔ done (0.1.1, 2026-09-01); README switched back to
|
|
432
|
+
`pip install odoo-installer`.
|
|
433
|
+
5. Backlog from §1 non-goals: DB backup/restore, SMTP setup wizard, reverse-proxy/TLS
|
|
434
|
+
generation.
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## 11. Local development setup
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
cd /home/volkan/Projects/dev/odoo-installer
|
|
442
|
+
python -m venv .venv
|
|
443
|
+
source .venv/bin/activate
|
|
444
|
+
pip install -U pip
|
|
445
|
+
pip install -e ".[dev]"
|
|
446
|
+
pre-commit install
|
|
447
|
+
pytest # unit suite
|
|
448
|
+
odoo-installer --version
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Notes for this machine: system Python is 3.14 (Arch); if a pinned dev dependency lags on
|
|
452
|
+
3.14, create the venv with an older interpreter rather than dropping the floor below 3.11.
|
|
453
|
+
The live Odoo 19.0 stack runs at `~/Projects/odoo-docker` (ports 8069) — port auto-allocation
|
|
454
|
+
and the adopted-stack care rules in §7 exist because of it.
|
|
455
|
+
|
|
456
|
+
## 12. Risks & mitigations
|
|
457
|
+
|
|
458
|
+
| Risk | Mitigation |
|
|
459
|
+
|------|------------|
|
|
460
|
+
| Python 3.14 + pinned deps lag | floor stays 3.11; pin dev deps; venv may use an older interpreter |
|
|
461
|
+
| Odoo weekly image tags drift | default `odoo:19.0`; `--image` override recorded in the manifest |
|
|
462
|
+
| OCA branch moves fast (ocabot bumps) | manifest stores last synced commit; `module upgrade-repos` (deferred to v1.1) re-syncs |
|
|
463
|
+
| Log parsing fragility | parser matched against recorded fixture logs from real runs; exit code is primary signal |
|
|
464
|
+
| Destructive ops on the live stack | plan-first + `--yes`, explicit `--db`, adopted stacks read-mostly, scratch DB naming `oitest_*` |
|
|
465
|
+
| GitHub rate limits | `GITHUB_TOKEN`/`GH_TOKEN` env support; graceful degradation to offline discovery |
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Volkan TASCI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|