gOdoo-cli 1.0.5__tar.gz → 1.0.6__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.
- godoo_cli-1.0.6/.agents/skills/godoo-release-management/SKILL.md +24 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/AGENTS.md +2 -9
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/PKG-INFO +1 -1
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/docs/hooks.md +56 -23
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/__about__.py +1 -1
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/runtime/init.py +45 -4
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/checksums.py +19 -7
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/hooks.py +46 -7
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_checksums.py +11 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_hooks.py +45 -3
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_init_hooks.py +149 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.agents/skills/godoo-changeset-review/SKILL.md +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.agents/skills/godoo-editor-validation/SKILL.md +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.agents/skills/godoo-runtime-validation/SKILL.md +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.agents/skills/godoo-source-workspaces/SKILL.md +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.codecovrc +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.dockerignore +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.env.sample +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.github/FUNDING.yml +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.github/workflows/README.md +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.github/workflows/quality.yml +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.github/workflows/version-bump.yml +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.github/workflows/version-publish.yml +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.gitignore +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.pre-commit-config.yaml +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.prettierrc.yml +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.vscode/extensions.json +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.vscode/settings.json +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/.vscode/tasks.json +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/LICENSE +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/README.md +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/addons/.gitkeep +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/addons/godoo_test_succ_mod/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/addons/godoo_test_succ_mod/__manifest__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/addons/godoo_test_succ_mod/models/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/addons/godoo_test_succ_mod/tests/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/addons/godoo_test_succ_mod/tests/data.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/addons/godoo_test_succ_mod/tests/test_succ.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/assets/odoo_logo.png +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/config/nginx.conf.template +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/config/postgresql.conf +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/docker/Dockerfile +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/docker/docker-compose.base.yml +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/docker/docker-compose.dev.yml +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/docker/docker-compose.traefik.yml +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/docker/postgres-init/10-vector.sql +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/docs/architecture.md +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/docs/downstream.md +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/docs/lifecycle.md +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/docs/workspace.md +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/makefile +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/odoo_manifest.yml +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/package-lock.json +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/package.json +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/pyproject.toml +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/scripts/check_docstring_style.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/scripts/migrations/development/shrink_database.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/__main__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/common.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/configuration.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/db/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/db/archive.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/db/cli.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/db/passwords.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/db/query.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/db/reset.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/odoo_bin/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/root.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/rpc/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/rpc/cli.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/rpc/config_parameters.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/rpc/importer.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/rpc/modules.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/rpc/translations.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/run.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/runtime/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/runtime/dependencies.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/runtime/launch.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/runtime/shell.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/runtime/shell_scripts/odoo_banner.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/runtime/shell_scripts/render_report.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/runtime/start.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/runtime/status.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/test/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/test/cli.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/test/load_data.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/test/run.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/upgrade.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/workspace/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/workspace/common.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/workspace/configure.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/workspace/image.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/workspace/storage.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/commands/workspace/sync.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/database/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/database/connection.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/database/postgres.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/database/settings.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/database/state.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/git/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/git/git_url.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/git/repository.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/helpers/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/helpers/hashing.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/helpers/modules_git.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/helpers/progress.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/helpers/system.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/models/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/models/godoo_git_repo.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/models/godoo_manifest.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/models/godoo_models.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/models/godoo_modules.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/archive.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/backup.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/baseline.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/dataset.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/filestore.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/lifecycle.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/locks.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/nginx.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/odoo.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/promotion.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/reset.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/restore.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/runtime/status.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/workspace/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/workspace/editor.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/workspace/git.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/workspace/image.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/workspace/manifest.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/workspace/operations.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/workspace/specifications.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/workspace/storage.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/src/godoo_cli/workspace/types.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/__init__.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/commands/test_db_archive_cli.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/commands/test_db_query_cli.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/commands/test_odoo_bin_commands.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/commands/test_rpc_commands.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/commands/test_runtime_cli.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/commands/test_runtime_dependencies.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/commands/test_runtime_shell_scripts.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/commands/test_runtime_start.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/commands/test_test_run.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/commands/test_upgrade.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/conftest.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/database/test_db_backup.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/database/test_db_query.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/database/test_db_reset.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/database/test_db_restore.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/git/test_git_url_uri.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/integration/test_container_contract.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/integration/test_odoo_integration.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_archive_crc.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_baseline_recovery.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_database_copy.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_dataset.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_filestore.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_filestore_copy.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_init_policy.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_lifecycle.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_lifecycle_service.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_nginx.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_nginx_cli.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_odoo_config_generation.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_odoo_preflight.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_odoo_version_compatibility.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_report_url.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_reset_baseline.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_runtime_models.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_runtime_operations.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/runtime/test_runtime_source_environment.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/test_logging.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/workspace/test_godoo_manifest.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/workspace/test_workspace.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/workspace/test_workspace_git.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/workspace/test_workspace_image.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/workspace/test_workspace_layout.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/workspace/test_workspace_runtime_env.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/tests/workspace/test_workspace_storage.py +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/thirdparty/.gitkeep +0 -0
- {godoo_cli-1.0.5 → godoo_cli-1.0.6}/uv.lock +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godoo-release-management
|
|
3
|
+
description:
|
|
4
|
+
Prepare, review, merge, and verify gOdoo releases using the repository's version-bump and publication workflows.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# gOdoo release management
|
|
8
|
+
|
|
9
|
+
Read `.github/workflows/README.md` and inspect the current release state before acting. Publish or merge only when
|
|
10
|
+
explicitly authorized. Keep the release PR short-lived and coordinate other merges to preserve its intended scope.
|
|
11
|
+
|
|
12
|
+
Dispatch `.github/workflows/version-bump.yml` against `main` with the selected bump type. The workflow creates
|
|
13
|
+
`release/<version>` and its release PR; leave version changes to it and keep the PR limited to release preparation. Do
|
|
14
|
+
not use long-lived `release` or `develop` branches for this workflow.
|
|
15
|
+
|
|
16
|
+
The release PR description becomes the release notes. Never replace existing release text or remove its changelog
|
|
17
|
+
summary. If additions are needed, replace the placeholder and preserve the generated changelog and remaining text.
|
|
18
|
+
Review the notes, release-preparation diff, release label, source repository, target branch, title, branch name, package
|
|
19
|
+
version, and available Quality results against the identities in `.github/workflows/README.md` before merging.
|
|
20
|
+
|
|
21
|
+
After the authorized merge, verify publication to PyPI, the `v<version>` tag at the release merge commit, and the GitHub
|
|
22
|
+
Release. If publication fails, inspect the failed step and existing PyPI, tag, and release state before retrying. Never
|
|
23
|
+
move an existing release tag to repair publication. Report the released version, revision, validation, and any
|
|
24
|
+
downstream adoption required.
|
|
@@ -10,8 +10,8 @@ coherent change.
|
|
|
10
10
|
detects or orchestrates containers.
|
|
11
11
|
- Load the matching skill under `.agents/skills/` for source-workspace or runtime work; load both only when a change
|
|
12
12
|
crosses those boundaries.
|
|
13
|
-
- Use `godoo-editor-validation` for VS Code diagnostics and development-flow checks,
|
|
14
|
-
whole-changeset reviews.
|
|
13
|
+
- Use `godoo-editor-validation` for VS Code diagnostics and development-flow checks, `godoo-changeset-review` for
|
|
14
|
+
whole-changeset reviews, and `godoo-release-management` for release preparation and publication.
|
|
15
15
|
- Keep shared Docker validation sequential.
|
|
16
16
|
|
|
17
17
|
## Branches and releases
|
|
@@ -24,13 +24,6 @@ coherent change.
|
|
|
24
24
|
Follow the existing merge strategy and commit conventions. Push and merge only within explicit authorization.
|
|
25
25
|
- After merge, return to an updated `main` or start the next topic branch from current `origin/main`. Prune stale remote
|
|
26
26
|
references and remove merged branches only after checking ancestry and worktree ownership; preserve unfinished work.
|
|
27
|
-
- Leave package version changes to `.github/workflows/version-bump.yml`, dispatched against `main` with the chosen bump
|
|
28
|
-
type. It creates `release/<version>` and the release PR; keep that PR limited to preparing the release.
|
|
29
|
-
- Review the release PR description and available Quality results before merging: its description becomes the release
|
|
30
|
-
notes. The release-labeled PR must target `main` and match the title, branch, and version identities documented in
|
|
31
|
-
`.github/workflows/README.md`. Publication uses the release merge commit for PyPI, the tag, and the GitHub Release.
|
|
32
|
-
- Keep release PRs short-lived and coordinate other merges to preserve the intended release scope. Do not use a
|
|
33
|
-
long-lived `release` or `develop` branch for this workflow, or move an existing release tag to repair publication.
|
|
34
27
|
|
|
35
28
|
## Code and CLI
|
|
36
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: gOdoo-cli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.6
|
|
4
4
|
Summary: Odoo lifecycle, Python dependency preflight, and shared Git workspaces.
|
|
5
5
|
Project-URL: repository, https://github.com/OpenJKSoftware/gOdoo
|
|
6
6
|
Author-email: Joshua Kreuder <Joshua_Kreuder@outlook.com>
|
|
@@ -7,18 +7,21 @@ Ordinary restores and major upgrades share the same initialization flow.
|
|
|
7
7
|
|
|
8
8
|
## Choose the right mechanism
|
|
9
9
|
|
|
10
|
-
| Work | Mechanism | Runs when |
|
|
11
|
-
| ----------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
12
|
-
| Versioned schema or data repair | Addon `migrations/<version>/pre-*.py`, `post-*.py`, or `end-*.py` | An eligible installed-to-manifest version change occurs. |
|
|
13
|
-
| External migration roots | Explicit `--upgrade-path` | Selected module updates invoke Odoo's loader. |
|
|
14
|
-
| Cursor-based work before registry loading | Explicit `--pre-upgrade-script` | An update selection exists; maps to native `--pre-upgrade-scripts`. |
|
|
15
|
-
| Choose modules to install/update | `prepare` hook | Each initialization attempt,
|
|
16
|
-
| Bootstrap or restore setup | `after_bootstrap` / `after_restore` hook | The matching initialization outcome applies. |
|
|
17
|
-
| Recurring environment policy | `after_reconcile` hook | Reconciliation succeeds and declaration filters match. |
|
|
10
|
+
| Work | Mechanism | Mechanism owner | Runs when |
|
|
11
|
+
| ----------------------------------------- | ----------------------------------------------------------------- | --------------- | ------------------------------------------------------------------- |
|
|
12
|
+
| Versioned schema or data repair | Addon `migrations/<version>/pre-*.py`, `post-*.py`, or `end-*.py` | Odoo | An eligible installed-to-manifest version change occurs. |
|
|
13
|
+
| External migration roots | Explicit `--upgrade-path` | Odoo | Selected module updates invoke Odoo's loader. |
|
|
14
|
+
| Cursor-based work before registry loading | Explicit `--pre-upgrade-script` | Odoo | An update selection exists; maps to native `--pre-upgrade-scripts`. |
|
|
15
|
+
| Choose modules to install/update | `prepare` hook | gOdoo | Each initialization attempt, unless completed with `once=True`. |
|
|
16
|
+
| Bootstrap or restore setup | `after_bootstrap` / `after_restore` hook | gOdoo | The matching initialization outcome applies. |
|
|
17
|
+
| Recurring environment policy | `after_reconcile` hook | gOdoo | Reconciliation succeeds and declaration filters match. |
|
|
18
|
+
|
|
19
|
+
Projects supply the migration scripts and hook implementations. gOdoo forwards the selected native migration options to
|
|
20
|
+
Odoo and runs the lifecycle hooks.
|
|
18
21
|
|
|
19
22
|
Native migration versions must be above the installed version and at or below the manifest version. Fresh installs need
|
|
20
23
|
their own install behavior. There is no transition directory, version-pair selector, implicit module list, or hook
|
|
21
|
-
implicit migration scheduler.
|
|
24
|
+
implicit migration scheduler. Hooks can opt into one-shot completion with `once=True`.
|
|
22
25
|
|
|
23
26
|
## Discover and select hooks
|
|
24
27
|
|
|
@@ -49,7 +52,7 @@ actual boolean. A missing predicate runs whenever eligible; an exception fails i
|
|
|
49
52
|
| `phase` | `prepare`, `after_bootstrap`, `after_restore`, or `after_reconcile`. |
|
|
50
53
|
| `on` | Tuple of `ready`, `bootstrapped`, and/or `restored`; defaults to all. Custom outcome filters are invalid for preparation. |
|
|
51
54
|
| `environments` | Optional tuple of exact `GODOO_ENVIRONMENT` values. Omitted means unrestricted; an allowlist never matches an unset environment. |
|
|
52
|
-
| `once` | Optional boolean, default false. Record successful
|
|
55
|
+
| `once` | Optional boolean, default false. Record successful completion for this hook in the database. |
|
|
53
56
|
|
|
54
57
|
Environment is a deployment input, never inferred from database contents.
|
|
55
58
|
|
|
@@ -67,10 +70,38 @@ def run(ctx):
|
|
|
67
70
|
ctx.request_modules(update=["project_base"], install=["helpdesk"])
|
|
68
71
|
```
|
|
69
72
|
|
|
70
|
-
Preparation runs without starting Odoo
|
|
71
|
-
`request_modules(update=..., install=...)`, with no `env`.
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
Preparation runs without starting Odoo. Its context exposes `environment` and
|
|
74
|
+
`request_modules(update=..., install=...)`, with no `env`. Pass module names as sequences of strings. Requests combine
|
|
75
|
+
with CLI and environment selections before dependency preflight and native reconciliation.
|
|
76
|
+
|
|
77
|
+
`install=[...]` in a recurring hook requests installation on each initialization. Modules already installed or queued
|
|
78
|
+
for upgrade are skipped; other requested modules are passed to Odoo for installation. A module uninstalled later is
|
|
79
|
+
requested again on the next initialization. Change or remove the request if that module should remain uninstalled.
|
|
80
|
+
Preparation must not mutate databases or external systems. No request means no implicit action; even `all` requires an
|
|
81
|
+
explicit request. Upgrade paths and pre-upgrade scripts remain explicit CLI/environment inputs.
|
|
82
|
+
|
|
83
|
+
A one-time preparation hook requests modules until reconciliation succeeds and every requested install is verified:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
# hooks/11_install_feature.py
|
|
87
|
+
from godoo_cli.runtime.hooks import Hook
|
|
88
|
+
|
|
89
|
+
HOOK = Hook(id="project.install_feature", phase="prepare", once=True)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def run(ctx):
|
|
93
|
+
ctx.request_modules(install=["helpdesk"])
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Run `godoo runtime start --hooks-dir /path/to/hooks` to initialize and reconcile before launching Odoo. A standalone
|
|
97
|
+
`runtime launch` only starts a prepared runtime and does not run hooks. Once completed, the hook is skipped even if an
|
|
98
|
+
operator later uninstalls the module. Use a new hook ID for a new installation request. List module names explicitly;
|
|
99
|
+
`install=["all"]` cannot be verified and is rejected in one-time preparation hooks.
|
|
100
|
+
|
|
101
|
+
One-time preparation runs after bootstrap or restore, when gOdoo can read the database completion record, but before
|
|
102
|
+
other database hooks and native reconciliation. Its dependencies are therefore checked after bootstrap or restore. If
|
|
103
|
+
reconciliation or verification fails, the hook runs again on retry using its current code. Native Odoo work and the
|
|
104
|
+
completion record are separate transactions, so an interruption after native work may cause a retry.
|
|
74
105
|
|
|
75
106
|
## Reconcile database state
|
|
76
107
|
|
|
@@ -154,15 +185,17 @@ retries; it is not a completion record.
|
|
|
154
185
|
|
|
155
186
|
Use `Hook(id="project.repair.2026_10", phase="after_reconcile", once=True)` for a transactional one-shot repair. IDs are
|
|
156
187
|
immutable: use a new ID for a new repair. The runner stores completion in `ir_config_parameter` under
|
|
157
|
-
`godoo.hooks.once.<identity-hash
|
|
158
|
-
database; environment-restricted hooks complete separately for each selected
|
|
159
|
-
carry or rewind the records with the database. Declaration filters still
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
188
|
+
`godoo.hooks.once.<identity-hash>`. For database hooks, it is stored in the same transaction as the action. Unrestricted
|
|
189
|
+
hooks complete globally in that database; environment-restricted hooks complete separately for each selected
|
|
190
|
+
environment. Restores and baseline resets carry or rewind the records with the database. Declaration filters still
|
|
191
|
+
apply, and duplicate IDs remain invalid.
|
|
192
|
+
|
|
193
|
+
Completed hooks skip `needs_run`. For database hooks, a false predicate records completion atomically without running
|
|
194
|
+
the action; an exception rolls back the action and its completion record. For preparation hooks, completion is recorded
|
|
195
|
+
after successful reconciliation and verification. A later hook's failure preserves earlier committed repairs. Hooks that
|
|
196
|
+
commit internally or change external systems still need their own idempotency: the runner cannot promise exactly-once
|
|
197
|
+
external effects. Keep recurring environment policy repeatable, with state predicates, so ordinary drift is repaired
|
|
198
|
+
after every controlled start.
|
|
166
199
|
|
|
167
200
|
| Attempt | Outcome visible to hooks |
|
|
168
201
|
| ---------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
@@ -13,9 +13,21 @@ import typer
|
|
|
13
13
|
|
|
14
14
|
from ...models import GodooConfig
|
|
15
15
|
from ...runtime.archive import load_runtime_archive, prepare_archive_with_original_filestore
|
|
16
|
-
from ...runtime.checksums import
|
|
16
|
+
from ...runtime.checksums import (
|
|
17
|
+
ModulePlan,
|
|
18
|
+
module_states,
|
|
19
|
+
pending_installs,
|
|
20
|
+
plan_module_changes,
|
|
21
|
+
verify_installed_modules,
|
|
22
|
+
)
|
|
17
23
|
from ...runtime.dataset import resolve_runtime_dataset
|
|
18
|
-
from ...runtime.hooks import
|
|
24
|
+
from ...runtime.hooks import (
|
|
25
|
+
complete_prepare_hooks,
|
|
26
|
+
discover_hooks,
|
|
27
|
+
has_eligible_hooks,
|
|
28
|
+
pending_prepare_hooks,
|
|
29
|
+
run_prepare_hooks,
|
|
30
|
+
)
|
|
19
31
|
from ...runtime.lifecycle import (
|
|
20
32
|
LifecycleBootstrapError,
|
|
21
33
|
deployment_init,
|
|
@@ -423,10 +435,35 @@ def initialize_odoo_runtime(resolve_config: Callable[[], GodooConfig], request:
|
|
|
423
435
|
plan: ModulePlan | None = None
|
|
424
436
|
selected_updates = split_lifecycle_values(update_modules)
|
|
425
437
|
selected_installs = split_lifecycle_values(install_modules)
|
|
438
|
+
requested_installs = list(selected_installs)
|
|
439
|
+
prepare_completion_keys: list[str] = []
|
|
426
440
|
|
|
427
441
|
def plan_reconciliation(conf: GodooConfig) -> None:
|
|
428
442
|
"""Select native work under the lifecycle lock before any database hook."""
|
|
429
|
-
nonlocal plan, selected_updates, selected_installs
|
|
443
|
+
nonlocal plan, selected_updates, selected_installs, requested_installs, prepare_completion_keys
|
|
444
|
+
once_hooks, prepare_completion_keys = pending_prepare_hooks(conf, hooks, environment)
|
|
445
|
+
if once_hooks:
|
|
446
|
+
once_modules = run_prepare_hooks(once_hooks, environment, once=True)
|
|
447
|
+
once_updates = split_lifecycle_values(list(once_modules.update))
|
|
448
|
+
once_installs = split_lifecycle_values(list(once_modules.install))
|
|
449
|
+
if "all" in once_installs:
|
|
450
|
+
message = "One-time preparation hooks must name install modules explicitly; 'all' cannot be verified."
|
|
451
|
+
raise ValueError(message)
|
|
452
|
+
selected_updates = list(dict.fromkeys([*selected_updates, *once_updates]))
|
|
453
|
+
selected_installs = list(dict.fromkeys([*selected_installs, *once_installs]))
|
|
454
|
+
requested_installs = list(selected_installs)
|
|
455
|
+
_validate_pre_upgrade_options(
|
|
456
|
+
runtime_seed is not None or (persisted is not None and persisted["outcome"] == "restored"),
|
|
457
|
+
pre_upgrade_scripts,
|
|
458
|
+
selected_updates,
|
|
459
|
+
has_eligible_hooks(hooks, phase="after_restore", outcome="restored", environment=environment),
|
|
460
|
+
)
|
|
461
|
+
preflight_reconcile_dependencies(
|
|
462
|
+
conf,
|
|
463
|
+
selected_updates,
|
|
464
|
+
selected_installs,
|
|
465
|
+
ignore_missing_installed_modules=bool(pre_upgrade_scripts),
|
|
466
|
+
)
|
|
430
467
|
if request.update_changed:
|
|
431
468
|
plan = plan_module_changes(
|
|
432
469
|
conf, selected_updates, selected_installs, pre_upgrade_scripts=bool(pre_upgrade_scripts)
|
|
@@ -452,6 +489,10 @@ def initialize_odoo_runtime(resolve_config: Callable[[], GodooConfig], request:
|
|
|
452
489
|
)
|
|
453
490
|
if result == 0 and plan is not None:
|
|
454
491
|
plan.finish(conf)
|
|
492
|
+
elif result == 0 and requested_installs:
|
|
493
|
+
verify_installed_modules(conf, requested_installs)
|
|
494
|
+
if result == 0:
|
|
495
|
+
complete_prepare_hooks(conf, prepare_completion_keys)
|
|
455
496
|
return result
|
|
456
497
|
|
|
457
498
|
try:
|
|
@@ -480,7 +521,7 @@ def initialize_odoo_runtime(resolve_config: Callable[[], GodooConfig], request:
|
|
|
480
521
|
except LifecycleBootstrapError as error:
|
|
481
522
|
LOGGER.exception("Native Odoo bootstrap failed during deployment initialization")
|
|
482
523
|
return error.return_code
|
|
483
|
-
except (ValueError, RuntimeError, OSError, psycopg2.Error):
|
|
524
|
+
except (TypeError, ValueError, RuntimeError, OSError, psycopg2.Error):
|
|
484
525
|
LOGGER.exception("Deployment initialization failed")
|
|
485
526
|
return 1
|
|
486
527
|
finally:
|
|
@@ -8,7 +8,7 @@ import logging
|
|
|
8
8
|
import os
|
|
9
9
|
import re
|
|
10
10
|
from collections.abc import Container, Sequence
|
|
11
|
-
from dataclasses import dataclass
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
14
|
from ..helpers.hashing import fingerprint, sha256_file
|
|
@@ -50,6 +50,21 @@ def pending_installs(states: dict[str, ModuleState], requested: list[str]) -> li
|
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
def verify_installed_modules(config: GodooConfig, requested: list[str]) -> None:
|
|
54
|
+
"""Require every explicitly requested module to be installed after reconciliation."""
|
|
55
|
+
states = module_states(config)
|
|
56
|
+
missing = _missing_installs(states, requested)
|
|
57
|
+
if missing:
|
|
58
|
+
message = f"Requested modules were not installed: {', '.join(missing)}"
|
|
59
|
+
raise RuntimeError(message)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _missing_installs(states: dict[str, ModuleState], requested: list[str]) -> list[str]:
|
|
63
|
+
return sorted(
|
|
64
|
+
name for name in requested if name != "all" and states.get(name, ModuleState("unknown")).state != "installed"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
53
68
|
def _tree_hash(path: Path, *, core: bool = False) -> str:
|
|
54
69
|
files = []
|
|
55
70
|
for directory, folders, names in os.walk(path):
|
|
@@ -141,6 +156,7 @@ class ModulePlan:
|
|
|
141
156
|
updates: list[str]
|
|
142
157
|
installs: list[str]
|
|
143
158
|
snapshot: dict[str, str]
|
|
159
|
+
requested_installs: list[str] = field(default_factory=list)
|
|
144
160
|
|
|
145
161
|
def finish(self, config: GodooConfig) -> None:
|
|
146
162
|
"""Verify native completion and unchanged sources before saving this snapshot."""
|
|
@@ -149,11 +165,7 @@ class ModulePlan:
|
|
|
149
165
|
if pending:
|
|
150
166
|
message = f"Native reconciliation left unfinished modules: {', '.join(pending)}"
|
|
151
167
|
raise RuntimeError(message)
|
|
152
|
-
missing_installs =
|
|
153
|
-
name
|
|
154
|
-
for name in self.installs
|
|
155
|
-
if name != "all" and states.get(name, ModuleState("unknown")).state != "installed"
|
|
156
|
-
)
|
|
168
|
+
missing_installs = _missing_installs(states, [*self.installs, *self.requested_installs])
|
|
157
169
|
if missing_installs:
|
|
158
170
|
message = f"Requested modules were not installed: {', '.join(missing_installs)}"
|
|
159
171
|
raise RuntimeError(message)
|
|
@@ -217,4 +229,4 @@ def plan_module_changes(
|
|
|
217
229
|
)
|
|
218
230
|
raise RuntimeError(message)
|
|
219
231
|
LOGGER.info("Source reconciliation selected updates=%s installs=%s", selected_updates, selected_installs)
|
|
220
|
-
return ModulePlan(selected_updates, selected_installs, snapshot)
|
|
232
|
+
return ModulePlan(selected_updates, selected_installs, snapshot, requested_installs=list(installs))
|
|
@@ -45,9 +45,6 @@ class Hook:
|
|
|
45
45
|
if type(self.once) is not bool:
|
|
46
46
|
message = "Hook once must be a boolean."
|
|
47
47
|
raise ValueError(message)
|
|
48
|
-
if self.phase == "prepare" and self.once:
|
|
49
|
-
message = "Preparation hooks cannot declare once=True."
|
|
50
|
-
raise ValueError(message)
|
|
51
48
|
if self.phase == "prepare" and self.on != OUTCOMES:
|
|
52
49
|
message = "Preparation hooks cannot filter by outcome."
|
|
53
50
|
raise ValueError(message)
|
|
@@ -131,10 +128,14 @@ def has_eligible_hooks(hooks: Iterable[HookDefinition], *, phase: str, outcome:
|
|
|
131
128
|
return any(_is_eligible(definition.hook, phase, outcome, environment) for definition in hooks)
|
|
132
129
|
|
|
133
130
|
|
|
134
|
-
def run_prepare_hooks(
|
|
131
|
+
def run_prepare_hooks(
|
|
132
|
+
hooks: Iterable[HookDefinition], environment: str | None, *, once: bool = False
|
|
133
|
+
) -> ModuleSelections:
|
|
135
134
|
"""Run eligible preparation hooks and collect their module requests."""
|
|
136
135
|
context = _PrepareContext(environment=environment)
|
|
137
136
|
for definition in hooks:
|
|
137
|
+
if definition.hook.once != once:
|
|
138
|
+
continue
|
|
138
139
|
if not _is_eligible(definition.hook, "prepare", None, environment):
|
|
139
140
|
LOGGER.info("Hook %s excluded by declaration.", definition.hook.id)
|
|
140
141
|
continue
|
|
@@ -142,6 +143,46 @@ def run_prepare_hooks(hooks: Iterable[HookDefinition], environment: str | None)
|
|
|
142
143
|
return ModuleSelections(update=_deduplicate(context._update), install=_deduplicate(context._install))
|
|
143
144
|
|
|
144
145
|
|
|
146
|
+
def _completion_key(hook: Hook, environment: str | None) -> str:
|
|
147
|
+
scope = environment if hook.environments is not None else None
|
|
148
|
+
identity = json.dumps([hook.id, scope], separators=(",", ":"))
|
|
149
|
+
return "godoo.hooks.once." + hashlib.sha256(identity.encode()).hexdigest()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def pending_prepare_hooks(
|
|
153
|
+
config: GodooConfig, hooks: Iterable[HookDefinition], environment: str | None
|
|
154
|
+
) -> tuple[list[HookDefinition], list[str]]:
|
|
155
|
+
"""Select eligible one-time preparation hooks not yet completed in this database."""
|
|
156
|
+
selected = [
|
|
157
|
+
definition
|
|
158
|
+
for definition in hooks
|
|
159
|
+
if definition.hook.once and _is_eligible(definition.hook, "prepare", None, environment)
|
|
160
|
+
]
|
|
161
|
+
if not selected:
|
|
162
|
+
return [], []
|
|
163
|
+
keys = [_completion_key(definition.hook, environment) for definition in selected]
|
|
164
|
+
with config.db_connection.with_db(config.db_name, readonly=True).connect() as cursor:
|
|
165
|
+
cursor.execute(
|
|
166
|
+
"SELECT key FROM ir_config_parameter WHERE key = ANY(%s) AND value IS NOT NULL AND value != ''", [keys]
|
|
167
|
+
)
|
|
168
|
+
completed = {row[0] for row in cursor.fetchall()}
|
|
169
|
+
pending = [(definition, key) for definition, key in zip(selected, keys, strict=True) if key not in completed]
|
|
170
|
+
return [definition for definition, _key in pending], [key for _definition, key in pending]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def complete_prepare_hooks(config: GodooConfig, keys: list[str]) -> None:
|
|
174
|
+
"""Record verified one-time preparation hooks after native reconciliation."""
|
|
175
|
+
if not keys:
|
|
176
|
+
return
|
|
177
|
+
with config.db_connection.connect() as cursor:
|
|
178
|
+
for key in keys:
|
|
179
|
+
cursor.execute(
|
|
180
|
+
"INSERT INTO ir_config_parameter (key, value) VALUES (%s, %s) "
|
|
181
|
+
"ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value",
|
|
182
|
+
[key, "1"],
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
145
186
|
def run_hook_phase(
|
|
146
187
|
config: GodooConfig,
|
|
147
188
|
hooks: Iterable[HookDefinition],
|
|
@@ -188,9 +229,7 @@ def _run_database_hook_batch(shell_env: Any, sources: Sequence[str], *, outcome:
|
|
|
188
229
|
completion_key = None
|
|
189
230
|
parameters = None
|
|
190
231
|
if definition.hook.once:
|
|
191
|
-
|
|
192
|
-
identity = json.dumps([definition.hook.id, scope], separators=(",", ":"))
|
|
193
|
-
completion_key = "godoo.hooks.once." + hashlib.sha256(identity.encode()).hexdigest()
|
|
232
|
+
completion_key = _completion_key(definition.hook, environment)
|
|
194
233
|
parameters = context.env["ir.config_parameter"].sudo()
|
|
195
234
|
if parameters.get_param(completion_key):
|
|
196
235
|
cursor.rollback()
|
|
@@ -119,6 +119,17 @@ def test_pending_states_are_not_hidden_by_matching_hashes(runtime: tuple[GodooCo
|
|
|
119
119
|
checksums.plan_module_changes(config, [], [])
|
|
120
120
|
|
|
121
121
|
|
|
122
|
+
def test_explicit_installs_are_verified_even_when_already_installed_at_planning(
|
|
123
|
+
runtime: tuple[GodooConfig, Database, Path],
|
|
124
|
+
) -> None:
|
|
125
|
+
config, database, _addons = runtime
|
|
126
|
+
plan = checksums.plan_module_changes(config, [], ["custom"])
|
|
127
|
+
assert plan.installs == []
|
|
128
|
+
database.states["custom"] = ModuleState("uninstalled")
|
|
129
|
+
with pytest.raises(RuntimeError, match="Requested modules were not installed: custom"):
|
|
130
|
+
plan.finish(config)
|
|
131
|
+
|
|
132
|
+
|
|
122
133
|
@pytest.mark.parametrize("cleanup_removes_missing", [False, True])
|
|
123
134
|
def test_missing_sources_require_real_cleanup_and_strict_rescan(
|
|
124
135
|
runtime: tuple[GodooConfig, Database, Path], cleanup_removes_missing: bool
|
|
@@ -356,6 +356,48 @@ def test_once_failure_rolls_back_action_and_receipt(
|
|
|
356
356
|
assert values == {}
|
|
357
357
|
|
|
358
358
|
|
|
359
|
-
def
|
|
360
|
-
|
|
361
|
-
|
|
359
|
+
def test_once_prepare_uses_database_completion_and_environment_scope(
|
|
360
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
361
|
+
) -> None:
|
|
362
|
+
from contextlib import contextmanager
|
|
363
|
+
|
|
364
|
+
values: dict[str, str] = {}
|
|
365
|
+
|
|
366
|
+
class Database:
|
|
367
|
+
def with_db(self, *_args: Any, **_kwargs: Any) -> Self:
|
|
368
|
+
return self
|
|
369
|
+
|
|
370
|
+
@contextmanager
|
|
371
|
+
def connect(self):
|
|
372
|
+
yield self
|
|
373
|
+
|
|
374
|
+
def execute(self, query: str, args: list[Any]) -> None:
|
|
375
|
+
if query.startswith("SELECT"):
|
|
376
|
+
self.rows = [(key,) for key in args[0] if values.get(key)]
|
|
377
|
+
else:
|
|
378
|
+
values[args[0]] = args[1]
|
|
379
|
+
|
|
380
|
+
def fetchall(self):
|
|
381
|
+
return self.rows
|
|
382
|
+
|
|
383
|
+
monkeypatch.setattr(GodooConfig, "db_connection", property(lambda _self: Database()))
|
|
384
|
+
decisions: list[str] = []
|
|
385
|
+
definition = hooks.HookDefinition(
|
|
386
|
+
hook=hooks.Hook(id="install.feature", phase="prepare", once=True, environments=("development", "review")),
|
|
387
|
+
source=tmp_path / "hook.py",
|
|
388
|
+
run=lambda ctx: ctx.request_modules(install=["sale"]),
|
|
389
|
+
needs_run=lambda _ctx: decisions.append("checked") or True,
|
|
390
|
+
)
|
|
391
|
+
config = _config(tmp_path)
|
|
392
|
+
selected, keys = hooks.pending_prepare_hooks(config, [definition], "development")
|
|
393
|
+
assert selected == [definition]
|
|
394
|
+
assert hooks.run_prepare_hooks(selected, "development", once=True).install == ("sale",)
|
|
395
|
+
hooks.complete_prepare_hooks(config, keys)
|
|
396
|
+
assert hooks.pending_prepare_hooks(config, [definition], "development") == ([], [])
|
|
397
|
+
hooks.run_prepare_hooks(
|
|
398
|
+
hooks.pending_prepare_hooks(config, [definition], "development")[0], "development", once=True
|
|
399
|
+
)
|
|
400
|
+
assert decisions == ["checked"]
|
|
401
|
+
assert hooks.pending_prepare_hooks(config, [definition], "review")[0] == [definition]
|
|
402
|
+
assert hooks.pending_prepare_hooks(config, [definition], "production") == ([], [])
|
|
403
|
+
assert keys[0] == hooks._completion_key(definition.hook, "development")
|
|
@@ -162,6 +162,7 @@ def test_prepare_hooks_merge_cli_and_environment_requests_on_every_init(
|
|
|
162
162
|
"reconcile_modules",
|
|
163
163
|
lambda _config, update, install, **_kwargs: reconciled.append((update, install)) or 0,
|
|
164
164
|
)
|
|
165
|
+
monkeypatch.setattr(runtime_init, "verify_installed_modules", lambda *_args: None)
|
|
165
166
|
|
|
166
167
|
def deployment(current: GodooConfig, **kwargs: object) -> tuple[LifecycleOutcome, int]:
|
|
167
168
|
kwargs["preflight"](current) # type: ignore[operator]
|
|
@@ -271,3 +272,151 @@ def test_checksum_checkpoint_precedes_post_hooks_and_is_not_saved_on_native_fail
|
|
|
271
272
|
result = runtime_init.initialize_odoo_runtime(lambda: config, runtime_init.InitOptions(update_changed=True))
|
|
272
273
|
assert result == (native_status or 9)
|
|
273
274
|
assert events == ([] if native_status else ["checkpoint", "failing-post-hook"])
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def test_native_install_success_is_verified_without_update_changed(
|
|
278
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
279
|
+
) -> None:
|
|
280
|
+
from godoo_cli.models import OdooVersion
|
|
281
|
+
|
|
282
|
+
config = _config(tmp_path)
|
|
283
|
+
monkeypatch.setattr(runtime_init, "resolve_command_config", lambda **_kwargs: config)
|
|
284
|
+
monkeypatch.setattr(runtime_init, "require_odoo_version", lambda *_args: OdooVersion("Odoo", 19, 0))
|
|
285
|
+
monkeypatch.setattr(runtime_init, "x_sendfile_enabled", lambda *_args: False)
|
|
286
|
+
monkeypatch.setattr(runtime_init, "module_states", lambda _config: {})
|
|
287
|
+
monkeypatch.setattr(runtime_init, "reconcile_modules", lambda *_args, **_kwargs: 0)
|
|
288
|
+
monkeypatch.setattr(runtime_init, "reconcile_runtime", lambda conf, *, reconciler, **_kwargs: reconciler(conf))
|
|
289
|
+
verifications: list[list[str]] = []
|
|
290
|
+
|
|
291
|
+
def verify(_config: GodooConfig, requested: list[str]) -> None:
|
|
292
|
+
verifications.append(requested)
|
|
293
|
+
message = "Requested modules were not installed: helpdesk"
|
|
294
|
+
raise RuntimeError(message)
|
|
295
|
+
|
|
296
|
+
monkeypatch.setattr(runtime_init, "verify_installed_modules", verify)
|
|
297
|
+
|
|
298
|
+
def deploy(conf: GodooConfig, **callbacks: Any) -> tuple[LifecycleOutcome, int]:
|
|
299
|
+
callbacks["reconcile_preflight"](conf)
|
|
300
|
+
return LifecycleOutcome.READY, callbacks["reconciler"](conf)
|
|
301
|
+
|
|
302
|
+
monkeypatch.setattr(runtime_init, "deployment_init", deploy)
|
|
303
|
+
result = runtime_init.initialize_odoo_runtime(
|
|
304
|
+
lambda: config,
|
|
305
|
+
runtime_init.InitOptions(install_modules=["helpdesk"]),
|
|
306
|
+
)
|
|
307
|
+
assert result == 1
|
|
308
|
+
assert verifications == [["helpdesk"]]
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def test_once_prepare_retries_then_remains_complete_after_uninstall(
|
|
312
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
313
|
+
) -> None:
|
|
314
|
+
from godoo_cli.runtime import checksums
|
|
315
|
+
|
|
316
|
+
config = _config(tmp_path)
|
|
317
|
+
hook_dir = tmp_path / "hooks"
|
|
318
|
+
hook_dir.mkdir()
|
|
319
|
+
(hook_dir / "install.py").write_text(
|
|
320
|
+
"from godoo_cli.runtime.hooks import Hook\n"
|
|
321
|
+
"HOOK = Hook(id='install.helpdesk', phase='prepare', once=True)\n"
|
|
322
|
+
"def run(ctx): ctx.request_modules(install=['helpdesk'])\n",
|
|
323
|
+
encoding="utf-8",
|
|
324
|
+
)
|
|
325
|
+
completed: list[str] = []
|
|
326
|
+
calls: list[list[str]] = []
|
|
327
|
+
installed = False
|
|
328
|
+
monkeypatch.setattr(runtime_init, "resolve_command_config", lambda **_kwargs: config)
|
|
329
|
+
monkeypatch.setattr(runtime_init, "require_odoo_version", lambda *_args: SimpleNamespace(major=19, raw="19.0"))
|
|
330
|
+
monkeypatch.setattr(runtime_init, "x_sendfile_enabled", lambda *_args: False)
|
|
331
|
+
monkeypatch.setattr(runtime_init, "preflight_reconcile_dependencies", lambda *_args, **_kwargs: None)
|
|
332
|
+
monkeypatch.setattr(
|
|
333
|
+
runtime_init,
|
|
334
|
+
"pending_prepare_hooks",
|
|
335
|
+
lambda _conf, definitions, _environment: ([], []) if completed else (definitions, ["receipt"]),
|
|
336
|
+
)
|
|
337
|
+
monkeypatch.setattr(runtime_init, "complete_prepare_hooks", lambda _conf, keys: completed.extend(keys))
|
|
338
|
+
monkeypatch.setattr(runtime_init, "module_states", lambda _conf: {})
|
|
339
|
+
monkeypatch.setattr(
|
|
340
|
+
checksums,
|
|
341
|
+
"module_states",
|
|
342
|
+
lambda _conf: {"helpdesk": checksums.ModuleState("installed")} if installed else {},
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
def native(_conf: GodooConfig, _updates: list[str], installs: list[str], **_kwargs: Any) -> int:
|
|
346
|
+
nonlocal installed
|
|
347
|
+
calls.append(installs)
|
|
348
|
+
if len(calls) == 1:
|
|
349
|
+
return 1
|
|
350
|
+
if len(calls) == 2:
|
|
351
|
+
return 0
|
|
352
|
+
installed = True
|
|
353
|
+
return 0
|
|
354
|
+
|
|
355
|
+
monkeypatch.setattr(runtime_init, "reconcile_modules", native)
|
|
356
|
+
monkeypatch.setattr(runtime_init, "reconcile_runtime", lambda conf, *, reconciler, **_kwargs: reconciler(conf))
|
|
357
|
+
|
|
358
|
+
fail_post_hook = True
|
|
359
|
+
|
|
360
|
+
def deploy(conf: GodooConfig, **callbacks: Any) -> tuple[LifecycleOutcome, int]:
|
|
361
|
+
nonlocal fail_post_hook
|
|
362
|
+
callbacks["reconcile_preflight"](conf)
|
|
363
|
+
result = callbacks["reconciler"](conf)
|
|
364
|
+
if result == 0 and fail_post_hook:
|
|
365
|
+
fail_post_hook = False
|
|
366
|
+
return LifecycleOutcome.READY, 9
|
|
367
|
+
return LifecycleOutcome.READY, result
|
|
368
|
+
|
|
369
|
+
monkeypatch.setattr(runtime_init, "deployment_init", deploy)
|
|
370
|
+
request = runtime_init.InitOptions(hooks_dir=[hook_dir])
|
|
371
|
+
assert runtime_init.initialize_odoo_runtime(lambda: config, request) == 1
|
|
372
|
+
assert completed == []
|
|
373
|
+
assert runtime_init.initialize_odoo_runtime(lambda: config, request) == 1
|
|
374
|
+
assert completed == []
|
|
375
|
+
assert runtime_init.initialize_odoo_runtime(lambda: config, request) == 9
|
|
376
|
+
assert completed == ["receipt"]
|
|
377
|
+
installed = False
|
|
378
|
+
assert runtime_init.initialize_odoo_runtime(lambda: config, request) == 0
|
|
379
|
+
assert calls == [["helpdesk"], ["helpdesk"], ["helpdesk"], []]
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def test_once_prepare_update_cannot_start_restore_hook_before_pre_upgrade(
|
|
383
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
384
|
+
) -> None:
|
|
385
|
+
config = _config(tmp_path)
|
|
386
|
+
hook_dir = tmp_path / "hooks"
|
|
387
|
+
hook_dir.mkdir()
|
|
388
|
+
(hook_dir / "prepare.py").write_text(
|
|
389
|
+
"from godoo_cli.runtime.hooks import Hook\n"
|
|
390
|
+
"HOOK = Hook(id='update', phase='prepare', once=True)\n"
|
|
391
|
+
"def run(ctx): ctx.request_modules(update=['base'])\n",
|
|
392
|
+
encoding="utf-8",
|
|
393
|
+
)
|
|
394
|
+
(hook_dir / "restore.py").write_text(
|
|
395
|
+
"from godoo_cli.runtime.hooks import Hook\n"
|
|
396
|
+
"HOOK = Hook(id='restore', phase='after_restore', on=('restored',))\n"
|
|
397
|
+
"def run(ctx): pass\n",
|
|
398
|
+
encoding="utf-8",
|
|
399
|
+
)
|
|
400
|
+
script = tmp_path / "pre.py"
|
|
401
|
+
script.write_text("def migrate(cr, version): pass\n", encoding="utf-8")
|
|
402
|
+
write_runtime_lifecycle(
|
|
403
|
+
runtime_readiness_marker(config.data_dir, config.db_name), config.db_name, outcome="restored"
|
|
404
|
+
)
|
|
405
|
+
monkeypatch.setattr(runtime_init, "resolve_command_config", lambda **_kwargs: config)
|
|
406
|
+
monkeypatch.setattr(runtime_init, "require_odoo_version", lambda *_args: SimpleNamespace(major=19, raw="19.0"))
|
|
407
|
+
monkeypatch.setattr(runtime_init, "x_sendfile_enabled", lambda *_args: False)
|
|
408
|
+
monkeypatch.setattr(
|
|
409
|
+
runtime_init,
|
|
410
|
+
"pending_prepare_hooks",
|
|
411
|
+
lambda _conf, definitions, _environment: ([item for item in definitions if item.hook.once], ["receipt"]),
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
def deploy(conf: GodooConfig, **callbacks: Any) -> tuple[LifecycleOutcome, int]:
|
|
415
|
+
callbacks["reconcile_preflight"](conf)
|
|
416
|
+
pytest.fail("pre-upgrade conflict must fail before database hooks")
|
|
417
|
+
|
|
418
|
+
monkeypatch.setattr(runtime_init, "deployment_init", deploy)
|
|
419
|
+
with pytest.raises(typer.BadParameter, match="after_restore hook"):
|
|
420
|
+
runtime_init.initialize_odoo_runtime(
|
|
421
|
+
lambda: config, runtime_init.InitOptions(hooks_dir=[hook_dir], pre_upgrade_scripts=[script])
|
|
422
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|