Files
Odoo-Build/task
T
Janik von Rotz 78a127e598 feat(config): replace ODOO_INIT_DEMO_DATA with WITHOUT_DEMO and refactor ir.config_parameter handling
* **BREAKING**: Replaced `ODOO_INIT_DEMO_DATA` with `WITHOUT_DEMO` to align with Odoo's native config option
* Migrate SSH key and other parameters from individual env vars to `IR_CONFIG_PARAMETER`
* Update template-odoo-rc to dynamically build `IR_CONFIG_PARAMETER` from env vars
* Adjust task script logic to use `WITHOUT_DEMO` consistently
* Update README and CHANGELOG to reflect breaking change and new usage patterns
2025-10-21 17:31:49 +02:00

2832 lines
97 KiB
Bash
Executable File

#!/bin/bash
cd "$(dirname "$0")" || exit
# Commands such as git pull return exit code if there are no changes
# set -e
if [[ -a ".env" ]]; then
source .env
fi
function help-table() {
CMD_WIDTH=26
OPT_WIDTH=20
DESC_WIDTH=78
COLUMN="| %-${CMD_WIDTH}s | %-${OPT_WIDTH}s | %-${DESC_WIDTH}s |\n"
printf "$COLUMN" "Command" "Option" "Description"
echo "|$(printf '%*s' $((CMD_WIDTH + 2)) '' | tr ' ' '-')|$(printf '%*s' $((OPT_WIDTH + 2)) '' | tr ' ' '-')|$(printf '%*s' $((DESC_WIDTH + 2)) '' | tr ' ' '-')|"
# Env
printf "$COLUMN" "backup-env-files" "[path]" "Archive and copy env files to target location."
printf "$COLUMN" "copy-env" "[env][env]" "Copy env file."
printf "$COLUMN" "create-nextcloud-env" "[env]" "Create env file for Nextcloud instance."
printf "$COLUMN" "create-odoo-env" "[env]" "Create env file for Odoo instance."
printf "$COLUMN" "edit-env" "[env]" "Open env file in default editor."
printf "$COLUMN" "list-env" "" "List env files."
printf "$COLUMN" "load-dotenv" "" "Restore content of .env from pass entry."
printf "$COLUMN" "load-env" "[env]" "Load and export env file."
printf "$COLUMN" "remove-env" "[env]" "Remove environment config."
printf "$COLUMN" "remove-venv" "" "Remove virtualenv."
printf "$COLUMN" "rename-env" "[env][env]" "Rename env file."
printf "$COLUMN" "restore-env-files" "[path]" "Extract and copy env files from backup file."
printf "$COLUMN" "save-dotenv" "" "Store content of .env in pass entry."
printf "$COLUMN" "show-env" "[env]" "Output content of the env file."
# Git
printf "$COLUMN" "add-git-submodule" "[url] [path]" "Add git submodule."
printf "$COLUMN" "checkout" "[version]" "Checkout Odoo version."
printf "$COLUMN" "checkout-git-folder" "" "Checkout git commit."
printf "$COLUMN" "commit-git-folder" "[message][path]" "Commit all changes in path."
printf "$COLUMN" "clean-git-folder" "" "Clean git folder."
printf "$COLUMN" "clone-git-folder" "[submodule][version]" "Clone git folder listed in the .gitmodules file."
printf "$COLUMN" "create-git-feature-branch" "[path]" "Create feature branch for Odoo module."
printf "$COLUMN" "create-git-mig-branch" "[path]" "Create migration branch for Odoo module."
printf "$COLUMN" "download-git-folder" "[grep]" "Download git folder listed in the .gitmodules file."
printf "$COLUMN" "status-git-folder" "[path]" "Show status for git folder in path."
printf "$COLUMN" "list-git-folder" "[grep]" "List path and url of git folders."
printf "$COLUMN" "pull-git-folder" "" "Pull all git folders listed in the .gitmodules file."
printf "$COLUMN" "push-git-folder" "" "Push all git folders in path."
printf "$COLUMN" "remove-git-folders" "" "Delete all git folders."
printf "$COLUMN" "remove-git-submodule" "[path]" "Remove a git submodule."
printf "$COLUMN" "ls-git-folder" "[grep]" "List git folders path space separated."
printf "$COLUMN" "reset-git-folder" "" "Abort rebase and reset submodules listed in the .gitmodules file."
printf "$COLUMN" "stage-git-folder" "[path]" "Stage all files in git folders in path."
printf "$COLUMN" "switch-git-folder" "[version]" "Switch branch for all git folders listed in the .gitmodules file."
printf "$COLUMN" "sync-git-folder" "" "Switch, stash and pull all git folders."
# Docker
printf "$COLUMN" "archive-docker-tags" "" "Archive Docker image tags on hub older than 1 year."
printf "$COLUMN" "build" "[folder][--push]" "Build Docker image from target folder."
printf "$COLUMN" "login-docker" "[user][token]" "Setup Docker Hub login credentials."
printf "$COLUMN" "login-podman" "[user][token]" "Login into registry with podman."
printf "$COLUMN" "container-ps" "" "List container processes."
printf "$COLUMN" "push" "[folder]" "Publish Docker image from target folder."
printf "$COLUMN" "remove" "[name]" "Remove containers and volumes."
printf "$COLUMN" "test-image" "[folder]" "Test Docker image from target folder."
# Vuepress
printf "$COLUMN" "build-vuepress" "" "Create Vuepress build."
printf "$COLUMN" "install-vuepress" "" "Install Node build dependencies."
printf "$COLUMN" "dev-vuepress" "" "Start Vuepress development server."
printf "$COLUMN" "serve-vuepress" "" "Serve Vuepress build."
# Database
printf "$COLUMN" "change-uuid" "[env]" "Change database uuid via xmlrpc."
printf "$COLUMN" "clear-assets" "[db]" "Clear all assets of Odoo database."
printf "$COLUMN" "clear-filestore" "[db]" "Clear local filestore folder. No param will clear all filestores."
printf "$COLUMN" "clear-views" "[db]" "Clear all views of Odoo database."
printf "$COLUMN" "cloc-odoo" "[db]" "Count custom line of codes."
printf "$COLUMN" "drop-db" "[db]" "Drop target Odoo database."
printf "$COLUMN" "init-db" "[db]" "Initialize the Odoo database."
printf "$COLUMN" "load-language" "[db][lang]" "Install language package in Odoo db."
printf "$COLUMN" "patch-database" "[db][path]" "Apply sql file to database."
printf "$COLUMN" "reset-views" "[db][key]" "Execute hard reset on views matching keys."
printf "$COLUMN" "set-admin" "[db]" "Sets the password for the first user in database."
printf "$COLUMN" "setup-mail" "[db]" "Setup mail config in Odoo db."
printf "$COLUMN" "update-module" "[db][name,path]" "Update target Odoo module."
printf "$COLUMN" "update-module-list" "[db]" "Update module list of Odoo database."
# Revision
printf "$COLUMN" "checkout-latest-revision" "[version]" "Checkout the latest revision of the Odoo version."
printf "$COLUMN" "checkout-revision" "[revision]" "Load Odoo revision env var and checkout git folders."
printf "$COLUMN" "commit-and-push-revision" "" "Commit all changes and tag with current revision"
printf "$COLUMN" "create-revision" "[revision]" "Create new Odoo revision."
printf "$COLUMN" "list-revision" "" "List available Odoo revisions."
printf "$COLUMN" "load-latest-revision" "[version]" "Load the latest revision of the Odoo version."
printf "$COLUMN" "load-revision" "[revision]" "Load env var from specified revision."
printf "$COLUMN" "show-revision" "[revision]" "Show references of Odoo revision."
# LLM
printf "$COLUMN" "commit-with-llm" "" "Commit with llm generated commit message."
printf "$COLUMN" "show-odoo-mcp-config" "" "Show the Odoo MCP server config."
printf "$COLUMN" "update-with-llm" "[glob][prompt]" "Feed module files with prompt to LLM and apply file changes."
# Snippets
printf "$COLUMN" "create-snippet" "[id]" "Create snippet from template."
printf "$COLUMN" "install-snippet" "[env][path]" "Install snippet definition."
printf "$COLUMN" "disable-snippet" "[env][path]" "Disable snippet definition."
printf "$COLUMN" "lint-snippets" "" "Run checks for all snippets."
printf "$COLUMN" "remove-snippet" "[env][path]" "Remove snippet definition."
printf "$COLUMN" "update-snippet" "[env][path]" "Update snippet definition."
# Setup
printf "$COLUMN" "activate-venv" "" "Activate virtualenv."
printf "$COLUMN" "init-venv" "" "Initialize python virtual env."
printf "$COLUMN" "disable-mailserver" "[env]" "Disable mail server settings via xmlrpc."
printf "$COLUMN" "install" "" "Install Odoo requirements in source folder."
printf "$COLUMN" "install-odoo-scripts" "" "Install Odoo scripts."
printf "$COLUMN" "install-requirements" "[db][path]" "Install python packages from requirements.txt."
printf "$COLUMN" "init-module" "[db][path,module]" "Initialize Odoo module."
printf "$COLUMN" "set-addons-path" "" "Set Odoo addons path env variable."
# Migration
printf "$COLUMN" "export-website-data" "[env]" "Export website data from Odoo database."
printf "$COLUMN" "import-csv" "[db][path]" "Import data from csv. Filename must match PostgreSQL table name."
printf "$COLUMN" "import-website-data" "[env]" "Import website data to Odoo database."
# Generate
printf "$COLUMN" "generate-module-docs" "[path]" "Generate readme file for module."
printf "$COLUMN" "generate-oca-module-docs" "[path]" "Generate readme file for module with OCA tools."
printf "$COLUMN" "generate-module-model" "[path][model]" "Generate model in module folder."
printf "$COLUMN" "generate-module-inherit" "[path][model]" "Generate inherited model in module folder."
printf "$COLUMN" "generate-module-views" "[path][model]" "Generate model views in module folder."
printf "$COLUMN" "generate-module-security" "[path][model]" "Generate model access file."
printf "$COLUMN" "generate-module-snippet" "[path][model][ref]" "Generate snippet for referefenced view."
printf "$COLUMN" "generate-module-wizard" "[path][model]" "Generate wizard for a model in module folder."
printf "$COLUMN" "generate-module" "[path]" "Create new Odoo module from template."
printf "$COLUMN" "generate-module-repo" "[path]" "Initialize Odoo module repo from template."
# Module
printf "$COLUMN" "lint-module" "[path]" "Run pylint odoo for module."
printf "$COLUMN" "lint-module-repo" "[path]" "Run pylint odoo for modules in repo folder."
printf "$COLUMN" "list-modules" "[path]" "Get modules in path as bash array."
printf "$COLUMN" "pytest-module" "[db][name,path]" "Run module tests with pytest."
printf "$COLUMN" "release-module" "[path]" "Create GitHub release for a module."
printf "$COLUMN" "remove-module" "[db][name]" "Remove target Odoo module."
printf "$COLUMN" "test-module" "[db][name,path]" "Test target Odoo module."
printf "$COLUMN" "test-module-repo" "[db][path]" "Test target Odoo modules in repo folder."
printf "$COLUMN" "translate-module" "[path][lang][db]" "Generate translation for Odoo module."
printf "$COLUMN" "upload-module" "[env][path]" "Zip and upload Odoo module."
printf "$COLUMN" "visualize-dependencies" "[path]" "Generate visualizations of module dependencies."
printf "$COLUMN" "zip-module" "[path]" "Create zip file for module."
# Helper
printf "$COLUMN" "generate-admin-passwd" "[pass]" "Generate hash for Odoo master password."
printf "$COLUMN" "generate-ssh-keys" "" "Generate ssh key pair."
printf "$COLUMN" "generate-pg-ssl-keys" "" "Generate PostgreSQL SSL key material."
printf "$COLUMN" "get-addons-path" "" "Output addons path."
printf "$COLUMN" "get-modules" "[path][option]" "Get list of modules in path. Option is 'basename' or 'dirname'."
printf "$COLUMN" "get-module-version" "[path]" "Get module version from manifest."
printf "$COLUMN" "help" "[grep]" "Show help for commands."
printf "$COLUMN" "info" "" "Show values of project env vars."
printf "$COLUMN" "load-ssh-key" "" "Load SSH private key from env var."
printf "$COLUMN" "update-module-license" "[grep]" "Update LICENSE file for each matching module."
# Run
printf "$COLUMN" "debug" "[name]" "Debugg application. Options: native."
printf "$COLUMN" "exec" "[name][cmd]" "Run command in container."
printf "$COLUMN" "logs" "[name]" "Tail container logs. Default is 'odoo'."
printf "$COLUMN" "odoocli" "[param]" "Execute odoocli cli."
printf "$COLUMN" "psql" "[db]" "Start interactive psql shell."
printf "$COLUMN" "restart" "[name]" "Restart container."
printf "$COLUMN" "run" "[name][cmd]" "Run container with command."
printf "$COLUMN" "shell" "[db][code]" "Start interactive odoo shell or run code."
printf "$COLUMN" "source" "" "Source the Python virtual env."
printf "$COLUMN" "start" "[name][db]" "Start application. Options: none, admin, db, mailgate, mailpit, native, odoo."
printf "$COLUMN" "stop" "[name]" "Stop containers."
printf "$COLUMN" "test-xmlrpc" "[env]" "Test json rpc connection."
# Project
printf "$COLUMN" "lint" "" "Run precommit for this project."
printf "$COLUMN" "list-versions" "" "List available Odoo versions."
printf "$COLUMN" "load-version" "[version]" "Load git refs from version folder."
printf "$COLUMN" "save-version" "" "Save git folder refs to version folder."
printf "$COLUMN" "template-compose" "" "Template the Docker compose file."
printf "$COLUMN" "template-odoo-rc" "" "Template the Odoo config file."
printf "$COLUMN" "template-repo" "[path]" "Update the repo folder from template."
printf "$COLUMN" "test-project" "[clean]" "Run tests for this project."
printf "$COLUMN" "version" "" "Show version of required tools."
# Performance
printf "$COLUMN" "record-with-memray" "[name]" "Record application memory usage with memray. Options: native."
printf "$COLUMN" "record-with-py-spy" "[pid]" "Record and create flamechart for a process."
# Docs
printf "$COLUMN" "update-docs" "" "Update all project docs."
printf "$COLUMN" "update-help-doc" "" "Write help table to task.md file."
printf "$COLUMN" "update-modules-doc" "" "Update modules docs file."
printf "$COLUMN" "update-revisions-doc" "" "Update revisions doc file."
printf "$COLUMN" "update-snippets-doc" "" "Update snippets doc file."
# Upgrade
printf "$COLUMN" "migrate-module" "[path]" "Migrate module code from to target Odoo version."
printf "$COLUMN" "upgrade-odoo" "[db][version][mode]" "Run the Odoo upgrade scripts. Default mode ist 'test'."
}
function help() {
echo
if [ -n "$1" ]; then
help-table | grep -i "$1" | column -t -s'|'
else
echo "task <command> [options]"
echo
echo "commands:"
echo
help-table
fi
echo
}
# Static env vars
ORG_NAME=Mint-System
PASS_ENTRY=$(echo "env/${PWD##*/}" | tr '[:upper:]' '[:lower:]')
ODOO_RC="odoo.conf"
TASK_CONFIG_DIR="$HOME/.config/odoo-build"
PGSSLROOTCERT="tmp/postgres-odoo/ca.crt"
export PGSSLROOTCERT
export PYTHONPATH="$PWD/odoo"
# Dynamic env vars
ODOO_VERSION=$(echo "$ODOO_REVISION" | cut -d'.' -f1-2)
ODOO_DATABASE=${ODOO_DATABASE:="$ODOO_VERSION"}
WITHOUT_DEMO=${WITHOUT_DEMO:="False"}
ODOO_LANGUAGE=${ODOO_LANGUAGE:="de_CH"}
ODOO_PORT=${ODOO_PORT:="8069"}
POSTGRES_IMAGE=${POSTGRES_IMAGE:="pgvector/pgvector:pg16-trixie"}
POSTGRES_PORT=${POSTGRES_PORT:="5432"}
POSTGRES_SSL=${POSTGRES_SSL:="on"}
PGSSLMODE=${PGSSLMODE:="prefer"}
LOG_LEVEL=${LOG_LEVEL:="info"}
CONTAINER_REGISTRY=${CONTAINER_REGISTRY:="mintsystem"}
CONTAINER_CONFIG="$HOME/.docker/$CONTAINER_REGISTRY"
BROWSER_OPEN=${BROWSER_OPEN:=true}
CONTAINER_ENGINE=${CONTAINER_ENGINE:="docker"}
COMPOSE_COMMAND=${COMPOSE_COMMAND:="docker compose"}
LLM_MODEL=${LLM_MODEL:="llama"}
SMTP_SERVER=${SMTP_SERVER:="mailpit"}
SMTP_PORT=${SMTP_PORT:=1025}
SMTP_SSL=${SMTP_SSL:="False"}
EMAIL_FROM=${EMAIL_FROM:="info@yourcompany.com"}
MAIL_ALIAS_DOMAIN=${MAIL_ALIAS_DOMAIN:="yourcompany.com"}
# Conditional env vars
if [[ "$CONTAINER_ENGINE" == "podman" ]]; then
COMPOSE_COMMAND="podman-compose"
fi
if [[ "$(uname)" == "Darwin" ]]; then
OS_RELEASE="Darwin"
PYTHON_NOTIFY=""
OS_OPEN="open"
else
OS_RELEASE=$(awk -F= '/^NAME/{print $2}' /etc/os-release | tr -d '"')
PYTHON_NOTIFY="inotify"
OS_OPEN="xdg-open"
fi
if [ "$WITHOUT_DEMO" = "True" ]; then
ODOO_PARAM="--without-demo=all"
fi
# Check commands
if ! (command -v grep &> /dev/null && grep --version | grep -q GNU) && [[ "$OS_RELEASE" =~ ^(Darwin)$ ]]; then
echo "Error: On Darwin systems, GNU grep must be installed."
exit 1
fi
if ! command -v envsubst &> /dev/null; then
echo "Error: 'envsubst' is not available. Install 'gettext' package."
exit 1
fi
# Import commands
function clone-taskfile(){
if [ ! -d "$HOME/taskfile.build" ]; then
echo -e "\033[38;5;214mGit\033[0m: Clone taskfile repo"
git clone https://git.taskfile.build "$HOME/taskfile.build"
else
echo -e "\033[38;5;214mGit\033[0m: Pull taskfile repo"
git -C "$HOME/taskfile.build" pull
fi
}
if [ -d "$HOME/taskfile.build/bin" ]; then
for file in "$HOME/taskfile.build/bin/"*; do
if [ -f "$file" ]; then
source "$file"
fi
done
fi
# Help Commands
function info() {
set-addons-path
echo "OS Release: $OS_RELEASE"
echo "Pass Entry: $PASS_ENTRY"
echo "Odoo Revision: $ODOO_REVISION"
echo "Odoo Version: $ODOO_VERSION"
echo "Odoo Port: $ODOO_PORT"
echo "Postgres Port: $POSTGRES_PORT"
echo "Odoo Language: $ODOO_LANGUAGE"
echo "Container Registry: $CONTAINER_REGISTRY"
echo "Container Config: $CONTAINER_CONFIG"
echo "Addons Path: $ADDONS_PATH"
}
function version() {
activate-venv
uv --version
wkhtmltopdf -V
if [ "$CONTAINER_ENGINE" == "docker" ]; then
docker -v
docker compose version
else
podman --version
podman-compose --version
fi
}
function generate-admin-passwd() {
activate-venv
if test -z "$1"; then echo "\$1 is empty."; exit; fi
ODOO_PASSWORD="$1"
bin/hash_password
}
function show-odoo-mcp-config() {
echo "Add this config to your IDE: "
cat << EOF
{
"mcpServers": {
"odoo": {
"command": "$PWD/venv$ODOO_VERSION/bin/python",
"args": ["$PWD/images/odoo-mcp/bin/server"],
"env": {
"ODOO_URL": "http://localhost:8069",
"ODOO_DATABASE": "$ODOO_DATABASE",
"ODOO_USERNAME": "admin",
"ODOO_PASSWORD": "admin"
}
}
}
}
EOF
}
# Env Commands
function init-venv() {
echo "Ensure Python version $(cat .python-version) is installed."
uv python install
if [ ! -d "venv$ODOO_VERSION" ]; then
echo "Init venv$ODOO_VERSION with $(uv --version)."
uv venv "venv$ODOO_VERSION"
fi
}
function activate-venv() {
echo "Source virtualenv venv$ODOO_VERSION."
source "venv$ODOO_VERSION/bin/activate"
echo "$(python --version) is active."
}
function remove-venv() {
echo "Remove virtualenv venv$ODOO_VERSION."
rm -rf "venv$ODOO_VERSION"
}
function get-addons-path() {
set-addons-path
echo "$ADDONS_PATH"
}
function template-odoo-rc() {
echo "Template $ODOO_RC"
# Load and export vars defined in odoo conf template
for VAR in $(grep -oE '\$([A-Z_][A-Z0-9_]*)|\$\{([A-Z_][A-Z0-9_]*)\}' odoo.conf.template); do
VAR=${VAR/\$/}
VAR=${VAR//\{\//}
export $VAR
done
IR_CONFIG_PARAMETER=${IR_CONFIG_PARAMETER:-""}
if [ -n "$GIT_SSH_PRIVATE_KEY" ]; then
GIT_SSH_PRIVATE_KEY=$(echo -e "$GIT_SSH_PRIVATE_KEY" | base64 -w0)
IR_CONFIG_PARAMETER+=$'\n'"git.ssh_private_key = \"$GIT_SSH_PRIVATE_KEY\""
fi
if [ -n "$GIT_SSH_PUBLIC_KEY" ]; then
IR_CONFIG_PARAMETER+=$'\n'"git.ssh_public_key = \"$GIT_SSH_PUBLIC_KEY\""
fi
if [ -n "$BASE_URL_WEBSITE" ]; then
IR_CONFIG_PARAMETER+=$'\n'"base_url_website = \"$BASE_URL_WEBSITE\""
fi
if [ -n "$BASE_URL_APP" ]; then
IR_CONFIG_PARAMETER+=$'\n'"base_url_app = \"$BASE_URL_APP\""
fi
if [ -n "$UPLOADCARE_PUBLIC_KEY" ]; then
IR_CONFIG_PARAMETER+=$'\n'"uploadcare.public_key = \"$UPLOADCARE_PUBLIC_KEY\""
fi
if [ -n "$UPLOADCARE_SECRET_KEY" ]; then
IR_CONFIG_PARAMETER+=$'\n'"uploadcare.secret_key = \"$UPLOADCARE_SECRET_KEY\""
fi
if [ -n "$KEYCLOAK_BASE_URL" ]; then
IR_CONFIG_PARAMETER+=$'\n'"keycloak.base_url = \"$KEYCLOAK_BASE_URL\""
fi
if [ -n "$KEYCLOAK_CLIENT_ID" ]; then
IR_CONFIG_PARAMETER+=$'\n'"keycloak.client_id = \"$KEYCLOAK_CLIENT_ID\""
fi
if [ -n "$KEYCLOAK_CLIENT_SECRET" ]; then
IR_CONFIG_PARAMETER+=$'\n'"keycloak.client_secret = \"$KEYCLOAK_CLIENT_SECRET\""
fi
if [ -n "$KEYCLOAK_REALM" ]; then
IR_CONFIG_PARAMETER+=$'\n'"keycloak.realm = \"$KEYCLOAK_REALM\""
fi
export IR_CONFIG_PARAMETER
envsubst < "odoo.conf.template" > "$ODOO_RC"
}
function set-addons-path() {
# Read addons path from git module
GIT_FOLDERS_PATH="$(git config --file .gitmodules --get-regexp path | awk '{ print $2 }' | sort | tr '\n' ',' | sed 's/,*$//g')"
# Remove excluded addons
EXCLUDES="addons/theme_mint_system addons/company addons/web"
for EXCLUDE in $EXCLUDES; do
GIT_FOLDERS_PATH=$(echo "$GIT_FOLDERS_PATH" | sed "s|${EXCLUDE},||g")
done
# Check if addons dir exist
for ADDON in $GIT_FOLDERS_PATH; do
if [ -d "$ADDON" ]; then
echo "Path does not exist: $ADDON"
exit 1
fi
done
# Convert to native paths
ADDONS_PATH="$(echo $GIT_FOLDERS_PATH | sed 's|,odoo|,odoo/addons|g')"
# Append paths from config env var
if [ -n "$ODOO_ADDONS_PATH" ]; then
ADDONS_PATH="${ODOO_ADDONS_PATH},${ADDONS_PATH}"
fi
}
function load-ssh-key() {
if [ -n "$GIT_SSH_PRIVATE_KEY" ]; then
echo "Setup SSH key from env var."
DECODED_GIT_SSH_PRIVATE_KEY=$(echo -e "$GIT_SSH_PRIVATE_KEY" | base64 -d)
mkdir -p ~/.ssh
echo "$DECODED_GIT_SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519 || (echo "Dumping ~/.ssh/id_ed25519 content:" && cat ~/.ssh/id_ed25519)
else
echo "The SSH key env var is empty."
exit 1
fi
}
function list-versions() {
ls -1 versions
}
# Docs Commands
function lint() {
activate-venv
echo "Run pre-commit in $PWD"
pre-commit run
}
function update-help-doc() {
echo "Pipe help table into task.md."
help-table > task.md
}
function update-snippets-doc() {
./bin/update-snippets-doc
}
function update-revisions-doc() {
echo "Update revisions doc file."
rm -f "revisions.md"
echo -e "# Odoo Revisions \n\
A Odoo revision is a snapshot of git references of the Odoo source and modules at a specific date.\n\
For each major release there are multiple revisions.\n" > "revisions.md"
declare -A REVISIONS
PATH_URL="https://github.com/${ORG_NAME}/Odoo-Build/tree/main"
for FILE in $(ls revisions/ | sort -rV); do
REVISION="$FILE"
VERSION=$(echo "$REVISION" | cut -d'.' -f1-2)
if [[ -z "${REVISIONS[$VERSION]}" ]]; then
REVISIONS[$VERSION]="## $VERSION"
fi
REVISIONS[$VERSION]+=$'\n\n'
REVISIONS[$VERSION]+="#### $REVISION"
REVISIONS[$VERSION]+=$'\n\n```bash\n'
REVISIONS[$VERSION]+=$(cat "revisions/$FILE")
REVISIONS[$VERSION]+=$'\n```'
done
for REVISION in $(echo "${!REVISIONS[@]}" | tr ' ' '\n' | sort -rV); do
echo -e "${REVISIONS[$REVISION]}\n" >> "revisions.md"
done
}
function update-modules-doc() {
echo "Update module doc file."
activate-venv
./bin/update-modules-doc
}
function update-docs() {
update-modules-doc
update-revisions-doc
update-snippets-doc
update-help-doc
}
function update-module-license() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
GIT_FOLDERS=$(list-module "$1")
for GIT_FOLDER in $GIT_FOLDERS; do
echo "Update license file for $GIT_FOLDER."
cp templates/module/LICENSE "$GIT_FOLDER/"
done
}
function template-repo(){
if test -z "$1"; then echo "\$1 is empty."; exit; fi
REPO_PATH="$1"
echo "Copy task file to $REPO_PATH."
cp "templates/task" "$REPO_PATH"
echo "Template README.md to $REPO_PATH."
REPO_FOLDER=$(basename "$REPO_PATH")
REPO_MODEL=${REPO_FOLDER%%_*}
REPO_TITLE=$(echo "$REPO_FOLDER" | sed 's/_/ /g' | sed 's/\b\(.\)/\u\1/g')
REPO_NAME=$(echo "$REPO_FOLDER" | sed 's/_/-/g' | sed 's/\b\(.\)/\u\1/g')
export REPO_FOLDER REPO_MODEL REPO_TITLE REPO_NAME
envsubst < "templates/README.md" > "$REPO_PATH/README.md"
echo "Copy $ODOO_VERSION template files to $REPO_PATH."
cp -r "templates/$ODOO_VERSION/." "$REPO_PATH"
echo "Remove deprecated repo files."
rm "$REPO_PATH/.flake8"
rm "$REPO_PATH/.isort.cfg"
rm "$REPO_PATH/.pylintrc-mandatory"
}
# Module Commands
function get-modules() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
OPTION="$2"
if [ -z "$OPTION" ]; then
OPTION="dirname"
fi
if [[ "$OPTION" == "dirname" ]]; then
MODULES=$(echo "$1" | tr "," "\n" | xargs -I {} find {} -type f -name "__manifest__.py" | xargs grep -l -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" | xargs -r dirname | sort -u | tr "\n" "," | sed 's/,$//')
fi
if [[ "$OPTION" == "basename" ]]; then
MODULES=$(echo "$1" | tr "," "\n" | xargs -I {} find {} -type f -name "__manifest__.py" | xargs grep -l -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" | xargs -r dirname | xargs -r -I {} basename {} | sort -u | tr "\n" "," | sed 's/,$//')
fi
echo "$MODULES"
}
function get-module-version() {
if test -z "$1"
then
echo "\$1 is empty."
else
# Get version of module
VERSION=$(grep -m 1 \"version "$1/__manifest__.py" | sed "s;';\";g" | sed "s/,//g" | sed 's/#.*//g')
VERSION=$(echo "{ $VERSION }" | jq .version | sed 's/"//g' | sed 's/null//g')
# Set default version
[ -z "$VERSION" ] && VERSION=0.0
COUNT_DOTS=$(echo "$VERSION" | grep -o "\." | wc -l)
# Check if oca version or enterprise version
if [[ $COUNT_DOTS == 2 ]]; then
VERSION="$ODOO_VERSION.$VERSION"
fi
if [[ $COUNT_DOTS == 1 ]]; then
VERSION="$ODOO_VERSION.$VERSION"
fi
echo "$VERSION"
fi
}
function list-modules() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
MODULES=$(get-modules "$1" dirname)
echo "$MODULES" | tr ',' '\n'
}
# Container Commands
function archive-docker-tags() {
activate-venv
export CONTAINER_CONFIG
python bin/archive-docker-tags
}
function template-compose() {
echo "Template compose.yml"
# Load and export vars defined in compose template
for VAR in $(grep -oE '\$\{?[A-Z_][A-Z0-9_]*\}?' compose.yml.template); do
# Strip $, ${, }
VAR=${VAR#\$\{} # Remove leading ${
VAR=${VAR%\}} # Remove trailing }
VAR=${VAR#\$} # Remove leading $
export "$VAR"
done
CONTAINER_TAG="odoo:$ODOO_REVISION"
envsubst < "compose.yml.template" > "compose.yml"
}
function container-ps() {
$CONTAINER_ENGINE ps
}
function build() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
# Set container tag and platform based on Dockerfile
PLATFORM="linux/amd64"
if [ "$1" = "images/odoo" ]; then
PLATFORM="linux/amd64,linux/arm64"
MANIFEST_FILE="images/odoo/Dockerfile"
CONTAINER_TAG="odoo:$ODOO_REVISION"
LATEST_TAG="odoo:$ODOO_VERSION"
fi
if [ "$1" = "images/odoo-mailgate" ]; then
MANIFEST_FILE="images/odoo-mailgate/Dockerfile"
CONTAINER_TAG="odoo-mailgate"
LATEST_TAG="odoo-mailgate:latest"
elif [ "$1" = "images/odoo-upgrade" ]; then
MANIFEST_FILE="images/odoo-upgrade/Dockerfile"
CONTAINER_TAG="odoo-upgrade"
LATEST_TAG="odoo-upgrade:latest"
elif [ "$1" = "images/odoocli" ]; then
MANIFEST_FILE="images/odoocli/Dockerfile"
CONTAINER_TAG="odoocli"
LATEST_TAG="odoocli:latest"
elif [ "$1" = "images/odoo-mcp" ]; then
MANIFEST_FILE="images/odoo-mcp/Dockerfile"
CONTAINER_TAG="odoo-mcp"
LATEST_TAG="odoo-mcp:latest"
fi
# Load Odoo revision
ODOO_DATE=$(echo "$ODOO_REVISION" | cut -d'.' -f3-)
source "revisions/$ODOO_REVISION"
# Select Python version
case "$ODOO_VERSION" in
"15.0")
PYTHON_VERSION="3.9"
PLATFORM="linux/amd64"
;;
"16.0")
PYTHON_VERSION="3.11"
;;
"17.0")
PYTHON_VERSION="3.11"
;;
"18.0")
PYTHON_VERSION="3.12"
;;
"19.0")
PYTHON_VERSION="3.13"
;;
*)
PYTHON_VERSION="3.12"
;;
esac
# Set platform
DOCKER_OUTPUT="--load"
if [ "$2" = "--push" ]; then
DOCKER_OUTPUT="--push"
fi
# Output configuration
echo "Python Version: $PYTHON_VERSION"
echo "Platform: $PLATFORM"
echo "Docker Output: $DOCKER_OUTPUT"
echo "Remove container image with tag ${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
docker image rm -f "${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
if ! docker buildx inspect odoo_builder >/dev/null 2>&1; then
docker buildx create --name odoo_builder --use
docker buildx inspect --bootstrap
else
docker buildx use odoo_builder
fi
echo "Run Docker build ${CONTAINER_REGISTRY}/${CONTAINER_TAG} for ${PLATFORM}"
SOURCE_DATE_EPOCH=$(git -C odoo log -1 --pretty=%ct) docker buildx build --platform "$PLATFORM" . \
--file "$MANIFEST_FILE" \
--build-arg PYTHON_VERSION="$PYTHON_VERSION" \
--build-arg ODOO_VERSION="$ODOO_VERSION" \
--build-arg ODOO_DATE="$ODOO_DATE" \
--build-arg ODOO_ENTERPRISE_REF="$ODOO_ENTERPRISE_REF" \
--tag "${CONTAINER_REGISTRY}/${CONTAINER_TAG}" \
--tag "${CONTAINER_REGISTRY}/${LATEST_TAG}" \
"$DOCKER_OUTPUT"
}
function load-latest-revision() {
if [ -n "$1" ] ; then
ODOO_VERSION="$1"
fi
ODOO_REVISION=$(list-revision | grep "$ODOO_VERSION" | tail -n1)
load-revision "$ODOO_REVISION"
}
function checkout-latest-revision() {
if [ -n "$1" ] ; then
ODOO_VERSION="$1"
fi
ODOO_REVISION=$(list-revision | grep "$ODOO_VERSION" | tail -n1)
checkout-revision "$ODOO_REVISION"
}
function container-login() {
if [ -n "$1" ]; then
CONTAINER_REGISTRY_USERNAME="$1"
fi
if [ -n "$2" ]; then
CONTAINER_REGISTRY_PASSWORD="$2"
fi
echo "Docker login with username $CONTAINER_REGISTRY_USERNAME."
echo "$CONTAINER_REGISTRY_PASSWORD" | docker --config "$CONTAINER_CONFIG" login --username "$CONTAINER_REGISTRY_USERNAME" --password-stdin
}
function login-podman() {
if [ -n "$1" ]; then
CONTAINER_REGISTRY_USERNAME="$1"
fi
if [ -n "$2" ]; then
CONTAINER_REGISTRY_PASSWORD="$2"
fi
echo "Podman login with username $CONTAINER_REGISTRY_USERNAME."
podman login --username "$CONTAINER_REGISTRY_USERNAME" --password "$CONTAINER_REGISTRY_PASSWORD" docker.io
}
function test-image() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if [ "$1" == "images/odo" ]; then
CONTAINER_TAG="odoo:$ODOO_REVISION"
fi
if [ "$1" == "images/odoo-upgrade" ]; then
CONTAINER_TAG="odoo-upgrade"
fi
if [ "$1" == "images/odoocli" ]; then
CONTAINER_TAG="odoocli"
fi
if [[ "$CONTAINER_TAG" =~ ^odoo: ]]; then
echo -e "\033[38;5;214mTEST\033[0m: Run git clone addons"
generate-ssh-keys
export GIT_SSH_PRIVATE_KEY
export GIT_SSH_PUBLIC_KEY
export LOCAL_PATH="./tmp"
SERVER_TOOLS_GIT_REF=$(curl -s "https://api.github.com/repos/OCA/server-tools/branches/$ODOO_VERSION" | grep '"sha":' | head -n 1 | awk -F '"' '{print $4}')
export ADDONS_GIT_REPOS="https://github.com/OCA/server-tools.git#${ODOO_VERSION}#${SERVER_TOOLS_GIT_REF}"
mkdir -p "$LOCAL_PATH"
export PATH=./images/odoo/bin/:$PATH
./images/odoo/bin/clone-git-addons
rm -rf "$LOCAL_PATH/github.com"
echo -e "\033[38;5;214mTEST\033[0m: Check addons path regex with version: $ODOO_VERSION"
echo "'version': '1.0'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})"
echo "'version': '1.1'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})"
echo "'version': '2.5'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})"
echo "'version': '13.0.1.0.0'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" || echo "Must not match"
echo "'version': '14.0.2.1.1'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" || echo "Must not match"
echo "'version': '${ODOO_VERSION}.0.0.0'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})"
echo '"Version": "1.0"' | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})"
echo -e "\033[38;5;214mTEST\033[0m: Install python package."
export PYTHON_INSTALL="cowsay"
activate-venv
./images/odoo/bin/install-python-packages
fi
if [[ "$CONTAINER_TAG" =~ ^odoo-upgrade ]]; then
echo -e "\033[38;5;214mTEST\033[0m: Show help of odoo-upgrade."
docker run -it mintsystem/odoo-upgrade --help
fi
if [[ "$CONTAINER_TAG" =~ ^odoocli ]]; then
echo -e "\033[38;5;214mTEST\033[0m: Show help of odoocli."
docker_odoocli() {
docker run -it \
-e ODOO_URL="$ODOO_URL" -e ODOO_DATABASE="$ODOO_DATABASE" -e ODOO_USERNAME="$ODOO_USERNAME" -e ODOO_PASSWORD="$ODOO_PASSWORD" \
mintsystem/odoocli "$@"
}
docker_odoocli --help
fi
}
function push() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if [ "$1" == "images/odoo" ]; then
CONTAINER_TAG="odoo:$ODOO_REVISION"
LATEST_TAG="odoo:$ODOO_VERSION"
elif [ "$1" == "images/odoo-mailgate" ]; then
CONTAINER_TAG="odoo-mailgate"
LATEST_TAG="odoo-mailgate:latest"
elif [ "$1" == "images/odoo-upgrade" ]; then
CONTAINER_TAG="odoo-upgrade"
LATEST_TAG="odoo-upgrade:latest"
elif [ "$1" == "images/odoocli" ]; then
CONTAINER_TAG="odoocli"
LATEST_TAG="odoocli:latest"
elif [ "$1" == "images/odoo-mcp" ]; then
CONTAINER_TAG="odoo-mcp"
LATEST_TAG="odoocli:latest"
fi
echo "Tag container image ${CONTAINER_REGISTRY}/${CONTAINER_TAG} with ${CONTAINER_REGISTRY}/${LATEST_TAG}"
$CONTAINER_ENGINE tag "${CONTAINER_REGISTRY}/${CONTAINER_TAG}" "${CONTAINER_REGISTRY}/${LATEST_TAG}"
if [ "$CONTAINER_ENGINE" == "docker" ]; then
echo "Push container image to ${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
docker --config "$CONTAINER_CONFIG" push "${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
docker --config "$CONTAINER_CONFIG" push "${CONTAINER_REGISTRY}/${LATEST_TAG}"
fi
if [ "$CONTAINER_ENGINE" == "podman" ]; then
echo "Push container image to ${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
podman push "${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
podman push "${CONTAINER_REGISTRY}/${LATEST_TAG}"
fi
}
function upload-module() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if test -z "$2"; then echo "\$2 is empty."; exit; fi
# Check if path is root folder
MODULES="$2"
if [ ! -f "$2/__manifest__.py" ]; then
echo "Identified as root folder"
MODULES="$MODULES/*"
fi
for MODULE in $MODULES; do
if [ -f "$MODULE/__manifest__.py" ]; then
# Create module zip
zip-module "$MODULE"
# Get path to zip file and upload url
FILE_PATH="tmp/$MODULE_NAME.zip"
load-env "$1"
NEXTCLOUD_UPLOAD_URL="$NEXTCLOUD_URL/remote.php/dav/files/$NEXTCLOUD_USERNAME/Odoo-Apps/"
# Upload zip file
curl -u "$NEXTCLOUD_USERNAME:$NEXTCLOUD_PASSWORD" -T "$FILE_PATH" "$NEXTCLOUD_UPLOAD_URL"
echo "File $FILE_PATH uploaded to $NEXTCLOUD_UPLOAD_URL."
UPLOADS+=" - name: $MODULE\n"
UPLOADS+=" version: $VERSION\n"
fi
done
echo "odoo_apps:"
echo -e "$UPLOADS"
}
function logs() {
CONTAINER="$1"
CONTAINER=${CONTAINER:="odoo"}
$CONTAINER_ENGINE logs --follow "$CONTAINER"
}
# Script Commands
function install-odoo-scripts() {
echo "Installing Odoo scripts with user flag."
curl -L https://ansible.build/odoo_scripts/install | bash -s -- --user
}
function upgrade-odoo() {
if test -z "$1"; then echo "\$1/database is empty."; exit; fi
if test -z "$2"; then echo "\$2/version is empty."; exit; fi
DATABASE="$1"
ODOO_TARGET_VERSION="$2"
MODE="test"
if [ -n "$3" ]; then
# Options: test, production
MODE="$3"
fi
NEW_DATABASE="${DATABASE}_${ODOO_TARGET_VERSION}"
echo "Execute Odoo upgrade script in $MODE mode for $DATABASE with version $ODOO_TARGET_VERSION."
echo "The database will be restored as $NEW_DATABASE."
export PGHOST=localhost
export PGUSER=odoo
export PGPASSWORD=odoo
python <(curl -s https://upgrade.odoo.com/upgrade) "$MODE" -d "$DATABASE" -t "$ODOO_TARGET_VERSION" -r "$NEW_DATABASE"
echo "Upgrade finished successfully."
}
# Install Commands
function install() {
init-venv
activate-venv
if [[ "$OS_RELEASE" =~ ^(Debian|Ubuntu|Pop!_OS)$ ]]; then
echo -e "\033[38;5;214mApt\033[0m: Update apt repo"
sudo apt update
echo -e "\033[38;5;214mApt\033[0m: Install python-ldap and psycopg2 build dependencies"
sudo apt-get install -y libsasl2-dev libldap2-dev libssl-dev libpq-dev python3-dev
echo -e "\033[38;5;214mApt\033[0m: Install xmllint and gettext"
sudo apt install -y libxml2-utils gettext
elif [[ "$OS_RELEASE" =~ ^(Fedora Linux)$ ]]; then
echo -e "\033[38;5;214mDnf\033[0m: Update dnf repo"
sudo dnf update
echo -e "\033[38;5;214mDnf\033[0m: Install psycopg2 build dependencies"
sudo dnf install -y postgresql-devel
echo -e "\033[38;5;214mPython\033[0m: Install psycopg2-binary"
uv pip install psycopg2-binary
echo -e "\033[38;5;214mPython\033[0m: Remove psycopg2 from Odoo requirements"
sed -i '/^psycopg2/d' odoo/requirements.txt
elif [[ "$OS_RELEASE" =~ ^(Darwin)$ ]]; then
echo "Welcome daring Mac user. Let's try our best to get this working."
echo -e "\033[38;5;214mBrew\033[0m: Install brew "
brew install gettext libsasl2 openldap openssl libxml2 grep
echo -e "\033[38;5;214mPython\033[0m: Install the binary version of psycopg2"
uv pip install psycopg2-binary
echo -e "\033[38;5;214mPython\033[0m: Remove psycopg2 from Odoo requirements"
sed -i '/^psycopg2/d' odoo/requirements.txt
elif [[ "$OS_RELEASE" =~ ^(Arch Linux|SteamOS)$ ]]; then
echo "Arch is the best"
echo -e "\033[38;5;214mPacman\033[0m: Install python-ldap and psycopg2 build dependencies"
sudo pacman -S --noconfirm base-devel postgresql
echo -e "\033[38;5;214mPython\033[0m: Install the binary version of psycopg2"
uv pip install psycopg2-binary
echo -e "\033[38;5;214mPython\033[0m: Remove psycopg2 from Odoo requirements"
sed -i '/^psycopg2/d' odoo/requirements.txt
echo -e "\033[38;5;214mPython\033[0m: Remove python-ldap from Odoo requirements"
sed -i '/^python-ldap/d' odoo/requirements.txt
else
echo "The operating system $OS_RELEASE is not supported."
exit 1
fi
if [[ "$OS_RELEASE" =~ ^(Arch Linux|Darwin|Debian|Fedora Linux|Ubuntu|Pop!_OS|SteamOS)$ ]]; then
# Source: https://gist.github.com/faniska/37f896d5e9de5fee925925d7caf3cb9e
INSTALLED_VERSION=$(wkhtmltopdf --version 2>&1 | sed -E 's/^wkhtmltopdf +([0-9.]+).*/\1/')
if [ "$INSTALLED_VERSION" != "0.12.4" ]; then
echo -e "\033[38;5;214mCurl\033[0m: Install wkhtmltopdf"
curl -L -O https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.4/wkhtmltox-0.12.4_linux-generic-amd64.tar.xz
tar xvf wkhtmltox-0.12.4_linux-generic-amd64.tar.xz
sudo mv wkhtmltox/bin/wkhtmlto* /usr/bin/
rm -rf wkhtmltox wkhtmltox-0.12.4_linux-generic-amd64.tar.xz
else
echo "wkhtmltopdf version is already 0.12.4"
fi
fi
if [ ! -d "$HOME/taskfile.build" ]; then
echo -e "\033[38;5;214mGit\033[0m: Clone taskfile repo"
git clone https://github.com/janikvonrotz/taskfile.build.git ~/taskfile.build
else
echo -e "\033[38;5;214mGit\033[0m: Pull taskfile repo"
git -C ~/taskfile.build pull
fi
echo -e "\033[38;5;214mPython\033[0m: Install Odoo development tools"
if [ -n "$PYTHON_NOTIFY" ]; then
uv pip install "$PYTHON_NOTIFY"
fi
uv pip install -r requirements.txt
if [ -f "odoo/requirements.txt" ]; then
echo -e "\033[38;5;214mPython\033[0m: Install Odoo dependencies"
uv pip install -r odoo/requirements.txt
if [ "$ODOO_VERSION" = "17.0" ]; then
uv pip install lxml_html_clean Werkzeug==2.2.2
fi
echo -e "\033[38;5;214mPython\033[0m: Configure Odoo as python package"
uv pip install -e odoo/ --no-deps
fi
if [[ "$OS_RELEASE" =~ ^(Fedora Linux)$ ]]; then
echo -e "\033[38;5;214mGit\033[0m: Reset Odoo requirements"
git -C odoo checkout requirements.txt
fi
if [ "$PYTHON_NOTIFY" == "inotify" ] && ! sudo grep -q "fs.inotify.max_user_watches=524288" /etc/sysctl.d/99-sysctl.conf; then
sudo bash -c 'echo "fs.inotify.max_user_watches=524288" >> /etc/sysctl.d/99-sysctl.conf'
sudo sysctl -p /etc/sysctl.d/99-sysctl.conf
echo -e "\033[38;5;214mSystem\033[0m: Updated fs.inotify.max_user_watches to 524288"
fi
}
# Process Commands
function open-url-with-delay(){
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if $BROWSER_OPEN; then
sleep 2
$OS_OPEN "$1"
fi
}
function start() {
set-addons-path
if [ "$POSTGRES_SSL" = "on" ]; then
generate-pg-ssl-keys
fi
template-compose
# Use default database if second param is not given
DATABASE="$2"
if [ -z "$2" ]; then
DATABASE="$ODOO_DATABASE"
fi
CONTAINER_TAG="odoo:$ODOO_REVISION"
if [ -z "$1" ] ; then
echo "Start all container ${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
echo "Open http://localhost:8069 url in your browser."
$COMPOSE_COMMAND up -d
fi
if [[ "$1" =~ "admin" ]]; then
echo "Open http://localhost:8000 url in your browser."
open-url-with-delay "http://localhost:8000" & $COMPOSE_COMMAND up -d admin
fi
if [[ "$1" =~ "db" ]]; then
$COMPOSE_COMMAND up -d db
fi
if [[ "$1" =~ "mailgate" ]]; then
echo "Send emails to: smtp://localhost:587"
$COMPOSE_COMMAND up -d mailgate
fi
if [[ "$1" =~ "mailpit" ]]; then
echo "Open http://localhost:8025 url in your browser."
open-url-with-delay "http://localhost:8025" & $COMPOSE_COMMAND up -d mailpit
fi
if [[ "$1" =~ "native" ]]; then
activate-venv
export SMTP_SERVER=localhost
template-odoo-rc
PORT=8069
while [ $PORT -le 8079 ]; do
if ! lsof -i:$PORT >/dev/null 2>&1; then
break
fi
PORT=$((PORT + 1))
done
echo "Open http://localhost:$PORT url in your browser."
open-url-with-delay "http://localhost:$PORT/web?debug=1&db=$DATABASE" &
odoo --database="$DATABASE" --config "$ODOO_RC" \
--http-port="$PORT" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL"
fi
if [[ "$1" =~ "odoo" ]]; then
echo "Start Odoo container ${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
echo "Open http://localhost:8069 url in your browser."
open-url-with-delay "http://localhost:8069" & $COMPOSE_COMMAND up -d odoo
fi
}
function debug() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
activate-venv
set-addons-path
PORT=8069
if [[ "$1" =~ "native" ]]; then
# Disable file validation to avoid frozen modules warning
export PYDEVD_DISABLE_FILE_VALIDATION=1
echo "Starting Odoo with the debugger on port 5678."
echo "Attach a debugpy debugger and open http://localhost:$PORT url in your browser."
python -m debugpy --listen 5678 --wait-for-client $(which odoo) --database="$DATABASE" --config "$ODOO_RC" \
--http-port="$PORT" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL"
fi
}
function run() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if test -z "$2"; then echo "\$2 is empty."; exit; fi
activate-venv
template-compose
echo "Run '$1' container with command '$2'."
$COMPOSE_COMMAND run --rm "$1" "$2" "$3"
}
function exec() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
local container="$1"
shift
local cmd="$@"
echo "Execute command '$cmd' with $CONTAINER_EXEC_PARAM in container '$container'."
$CONTAINER_ENGINE exec $CONTAINER_EXEC_PARAM "$container" $cmd
}
function record-with-py-spy() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
PID="$1"
OUTPUT_FILE="tmp/speedscope-capture.json"
SPEEDSCOPE_URL="https://www.speedscope.app/"
mkdir -p tmp
echo "source task source; py-spy record --pid $PID --output $OUTPUT_FILE --format speedscope" | sudo bash --
$OS_OPEN "$SPEEDSCOPE_URL"
}
function record-with-memray() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
activate-venv
set-addons-path
PORT=8069
OUTPUT_FILE="tmp/memray-capture.bin"
if [[ "$1" =~ "native" ]]; then
echo "Starting Odoo with memray."
echo "Open http://localhost:$PORT url in your browser."
mkdir -p tmp
rm "$OUTPUT_FILE"
python3 -m memray run -o "$OUTPUT_FILE" $(which odoo) --database="$DATABASE" --config "$ODOO_RC" \
--http-port="$PORT" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL"
python3 -m memray flamegraph "$OUTPUT_FILE"
$OS_OPEN "tmp/memray-flamegraph-output.html"
fi
}
function remove() {
template-compose
if test -z "$1"; then
$COMPOSE_COMMAND down -v
fi
if [[ "$1" =~ "db" ]]; then
$COMPOSE_COMMAND rm -f -s -v db
fi
if [[ "$1" =~ "admin" ]]; then
$COMPOSE_COMMAND rm -f -s -v admin
fi
if [[ "$1" =~ "odoo" ]]; then
$COMPOSE_COMMAND rm -f -s -v odoo
fi
if [[ "$1" =~ "mailpit" ]]; then
$COMPOSE_COMMAND rm -f -s -v mail
fi
if [[ "$1" =~ "mailgate" ]]; then
$COMPOSE_COMMAND rm -f -s -v mailgate
fi
}
function restart() {
template-compose
if test -z "$1"; then
$COMPOSE_COMMAND restart
fi
if [[ "db|mail|odoo" =~ $1 ]]; then
$COMPOSE_COMMAND restart "$1"
fi
}
function stop() {
template-compose
if test -z "$1"; then
$COMPOSE_COMMAND stop
fi
$COMPOSE_COMMAND stop "$1"
}
function generate-ssh-keys() {
if [ -z "$GIT_SSH_PRIVATE_KEY" ]; then
echo "Generate SSH private and public keys."
mkdir -p tmp
if [ ! -f "tmp/ed25519" ]; then
ssh-keygen -t ed25519 -C admin@example.com -f tmp/ed25519 -N ""
fi
GIT_SSH_PRIVATE_KEY=$(cat tmp/ed25519 | base64 -w0)
GIT_SSH_PUBLIC_KEY=$(cat tmp/ed25519.pub)
fi
}
function generate-pg-ssl-keys() {
KEYMATERIAL_PATH="tmp/postgres-odoo"
if [ -z "$PG_SSL_CA_CRT" ] || [ -z "$PG_SSL_SERVER_CRT" ] || [ -z "$PG_SSL_SERVER_KEY" ]; then
echo "Generate PostgreSQL SSL/TLS certificates."
mkdir -p "$KEYMATERIAL_PATH"
if [ ! -f "$KEYMATERIAL_PATH/ca.crt" ]; then
openssl genrsa -out "$KEYMATERIAL_PATH/ca.key" 4096
chmod 600 "$KEYMATERIAL_PATH/ca.key"
openssl req -x509 -new -nodes -key "$KEYMATERIAL_PATH/ca.key" -sha256 -days 3650 \
-out "$KEYMATERIAL_PATH/ca.crt" -subj "/C=CH/ST=State/L=Locality/O=Organization/CN=Root CA"
fi
if [ ! -f "$KEYMATERIAL_PATH/server.crt" ]; then
openssl genrsa -out "$KEYMATERIAL_PATH/server.key" 2048
openssl req -new -key "$KEYMATERIAL_PATH/server.key" -out "$KEYMATERIAL_PATH/server.csr" \
-subj "/C=CH/ST=State/L=Locality/O=Organization/CN=localhost"
openssl x509 -req -in "$KEYMATERIAL_PATH/server.csr" -CA "$KEYMATERIAL_PATH/ca.crt" \
-CAkey "$KEYMATERIAL_PATH/ca.key" -CAcreateserial -out "$KEYMATERIAL_PATH/server.crt" \
-days 365 -sha256
chmod 600 "$KEYMATERIAL_PATH/server.key"
chmod 644 "$KEYMATERIAL_PATH/server.crt"
sudo chown 999:999 "$KEYMATERIAL_PATH/server.key"
sudo chown 999:999 "$KEYMATERIAL_PATH/server.crt"
fi
fi
}
function test-project() {
if [ "$1" = "clean" ]; then
echo -e "\033[38;5;214mTEST\033[0m: Cleanup test environment."
drop-db test
remove odoo
git -C oca/partner-contact clean -df
git -C oca/partner-contact checkout .
remove-env test
else
activate-venv
version
info
set -e
echo -e "\033[38;5;214mTEST\033[0m: Clone module repo with https"
(git clone -b "$ODOO_VERSION" https://github.com/OCA/partner-contact.git oca/partner-contact) || true
echo -e "\033[38;5;214mTEST\033[0m: Start container environment."
export BROWSER_OPEN=false
start db,mail
echo -e "\033[38;5;214mTEST\033[0m: Clone Odoo addons."
generate-ssh-keys
export GIT_SSH_PRIVATE_KEY
export GIT_SSH_PUBLIC_KEY
export ADDONS_GIT_REPOS="https://github.com/OCA/server-env.git#${ODOO_VERSION},https://github.com/OCA/server-tools.git#${ODOO_VERSION},https://github.com/Mint-System/Odoo-Apps-Server-Tools.git#${ODOO_VERSION}"
run odoo clone-git-addons
echo -e "\033[38;5;214mTEST\033[0m: Init Odoo database."
export ODOO_ADDONS_PATH="/mnt/oca/partner-contact"
export ODOO_DATABASE=test
export WITHOUT_DEMO=False
export ODOO_INIT_LOGIN=test
export ODOO_INIT_PASSWORD=test
export ODOO_INIT_LANG=de_CH
export ODOO_INIT_ADDONS=server_environment_ir_config_parameter,module_auto_update,prometheus_exporter,mail_server_filter,contacts,web_enterprise
export PYTHON_INSTALL=prometheus-client,manifestoo
run odoo init-db
echo -e "\033[38;5;214mTEST\033[0m: Start Odoo container."
export ODOO_MAIL_SMTP_HOST=mail
export ODOO_MAIL_SMTP_PORT=1025
export ODOO_MAIL_SMTP_ENCRYPTION=none
export SERVER_WIDE_MODULES=module_change_auto_install
# export SESSION_DB_URI=postgres://odoo:odoo@db/test
export MODULE_AUTO_INSTALL_DISABLED=odoo_test_xmlrunner
export AUTO_UPDATE_MODULES=True
export AUTO_UPDATE_TRANSLATIONS=True
export AUTO_UPDATE_MODULES_LIST=True
export MAIL_DEFAULT_FROM="test"
export BROWSER_OPEN=false
export TEST_ADDONS_DIR=/mnt/oca/partner-contact
export TEST_INCLUDE=partner_firstname
start odoo
TIMEOUT=60
ELAPSED=0
until curl --silent --fail "http://localhost:8069/web/login" &> /dev/null; do
if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
echo "Odoo did not start within $TIMEOUT seconds."
docker logs odoo
exit 1
fi
echo "Waiting for Odoo to be ready..."
sleep 3
ELAPSED=$((ELAPSED + 3))
done
echo -e "\033[38;5;214mTEST\033[0m: Init repos.yml file."
cat << EOF > tmp/repos.yml
github.com/Mint-System/Odoo-Apps-Partner-Contact:
remotes:
mint_system: https://github.com/Mint-System/Odoo-Apps-Partner-Contact.git
merges:
- mint_system ${ODOO_VERSION}
target: mint_system ${ODOO_VERSION}
EOF
docker cp tmp/repos.yml odoo:/var/lib/odoo/git/repos.yml
echo -e "\033[38;5;214mTEST\033[0m: Init OCA module."
run odoo init-module partner_firstname
echo -e "\033[38;5;214mTEST\033[0m: Setup and check RPC credentials."
export ODOO_URL="http://localhost:8069"
export ODOO_USERNAME=test
export ODOO_PASSWORD=test
if ! show-env test; then
create-odoo-env test
fi
show-env test
test-xmlrpc test
echo -e "\033[38;5;214mTEST\033[0m: Install, update, disable and remove snippet."
install-snippet test snippets/base.view_partner_form.show_type.xml
update-snippet test snippets/base.view_partner_form.show_type.xml
disable-snippet test snippets/base.view_partner_form.show_type.xml
remove-snippet test snippets/base.view_partner_form.show_type.xml
echo -e "\033[38;5;214mTEST\033[0m: Check module auto update."
sed -i 's/First name/Firstname/g' oca/partner-contact/partner_firstname/models/res_partner.py
restart odoo
until curl --silent --fail "http://localhost:8069/web/login" &> /dev/null; do
echo "Waiting for Odoo to be ready..."
sleep 3
done
docker logs odoo
docker logs odoo 2>&1 | grep "Run click-odoo-update."
echo -e "\033[38;5;214mTEST\033[0m: Check translations auto update."
sed -i 's/Nachname/Familienname/g' oca/partner-contact/partner_firstname/i18n/de.po
restart odoo
echo -e "\033[38;5;214mTEST\033[0m: Drop the test database."
drop-db test
echo -e "\033[38;5;214mTEST\033[0m: Prepare container for testing."
exec odoo setup-tests
exec odoo ls /var/lib/odoo/git/github.com/Mint-System/Odoo-Apps-Partner-Contact
echo -e "\033[38;5;214mTEST\033[0m: Run module tests."
CONTAINER_EXEC_PARAM="-w /mnt/oca/partner-contact"
exec odoo run-tests
echo -e "\033[38;5;214mTEST\033[0m: Run module upgrade."
mkdir -p oca/partner-contact/partner_firstname/migrations/${ODOO_VERSION}.2.0.0
cat << EOF > oca/partner-contact/partner_firstname/migrations/${ODOO_VERSION}.2.0.0/post-migration.py
import logging
from odoo.upgrade import util
_logger = logging.getLogger(__name__)
def migrate(cr, version):
env = util.env(cr)
partners = env["res.partner"].search([])
for partner in partners:
partner.name += "!"
_logger.info("Updated %s partners", len(partners))
EOF
sed -i "/\"version\":/c\ \"version\": \"${ODOO_VERSION}.2.0.0\"," oca/partner-contact/partner_firstname/__manifest__.py
exec odoo update-module partner_firstname 2>&1 | grep "module partner_firstname: Running migration"
fi
}
# Interactive Shell Commands
function shell() {
DATABASE="$1"
if [ -z "$DATABASE" ]; then
DATABASE="$ODOO_DATABASE"
fi
CODE="$2"
activate-venv
set-addons-path
if [ -n "$CODE" ]; then
echo "$CODE" | odoo shell --database "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
else
odoo shell --database "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
fi
}
function psql() {
DATABASE="$1"
if [ -z "$DATABASE" ]; then
DATABASE="$ODOO_DATABASE"
fi
$CONTAINER_ENGINE exec -it db psql "postgres://odoo:odoo@localhost:5432/$DATABASE"
}
# Database Commands
function init-db() {
ODOO_INIT_ADDONS="${ODOO_INIT_ADDONS:-"web"}"
DATABASE="$1"
if [ -z "$DATABASE" ]; then
DATABASE="$ODOO_DATABASE"
fi
activate-venv
set-addons-path
template-odoo-rc
echo "Initialize database $DATABASE with $ODOO_INIT_ADDONS."
if [[ -n "$ODOO_PARAM" ]]; then
odoo -d "$DATABASE" -i "$ODOO_INIT_ADDONS" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE" "$ODOO_PARAM"
else
odoo -d "$DATABASE" -i "$ODOO_INIT_ADDONS" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE"
fi
}
function import-csv() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
DATABASE="$1"
FILE_PATH="$2"
# Use default database if second param is not given
if test -z "$2"; then
DATABASE="$ODOO_DATABASE"
FILE_PATH="$1"
fi
FILE_NAME=$(basename "$FILE_PATH")
echo "Importing $FILE_NAME to ${DATABASE}..."
$CONTAINER_ENGINE cp "$FILE_PATH" db:/tmp/
$CONTAINER_ENGINE exec db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -c "\copy $FILE_NAME FROM '/tmp/$FILE_NAME' DELIMITER ',' CSV HEADER;"
}
function drop-db() {
DATABASE="$1"
if [ -z "$DATABASE" ]; then
DATABASE="$ODOO_DATABASE"
fi
$CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/postgres" -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$DATABASE';"
$CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/postgres" -c "DROP DATABASE \"$DATABASE\";"
}
function setup-mail() {
DATABASE="$1"
if [ -z "$DATABASE" ]; then
DATABASE="$ODOO_DATABASE"
fi
activate-venv
set-addons-path
export MAIL_BOUNCE_ALIAS
export MAIL_CATCHALL_ALIAS
export MAIL_CATCHALL_DOMAIN
export MAIL_DEFAULT_FROM
export MAIL_ALIAS_DOMAIN
echo "Setup mail server configuation for database $DATABASE."
odoo shell --database "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http < bin/setup-mail
}
function load-language() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
DATABASE="$1"
LANGUAGE="$2"
# Use default database if second param is not given
if test -z "$2"; then
DATABASE="$ODOO_DATABASE"
LANGUAGE="$1"
fi
activate-venv
set-addons-path
echo "Setup language $LANGUAGE for database $DATABASE:"
odoo -d "$DATABASE" -u all --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$LANGUAGE"
}
function set-admin() {
DATABASE="$1"
if [ -z "$DATABASE" ]; then
DATABASE="$ODOO_DATABASE"
fi
$CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -c "UPDATE res_users SET active=true,password='\$pbkdf2-sha512\$25000\$JuScEwIg5JzTGqNUivFeqw\$yWTOcix2Afr3XGP2NPY7w4w49e9vpsu14NRndDYXAkbtMF4zkrmx6inVsoLl0zZY30xI/0GzhwonWsK9TUmjWA' WHERE id=2;"
$CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$DATABASE" -c "SELECT login, password FROM res_users WHERE id=2;"
}
function clear-assets() {
DATABASE="$1"
if [ -z "$DATABASE" ]; then
DATABASE="$ODOO_DATABASE"
fi
echo "Delete these assets for $DATABASE:"
$CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "select id,name from ir_attachment where res_model='ir.ui.view' and name like '%assets_%';"
$CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "delete from ir_attachment where res_model='ir.ui.view' and name like '%assets_%';"
}
function clear-views() {
DATABASE="$1"
if [ -z "$DATABASE" ]; then
DATABASE="$ODOO_DATABASE"
fi
echo "Archive views with prefix 'mint_system'."
$CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "SELECT id,name from ir_ui_view WHERE name like '%mint_system%';"
$CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "UPDATE ir_ui_view SET active=false WHERE name like '%mint_system%';"
echo "Archive views with prefix 'Odoo Studio:'."
$CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "SELECT id,name from ir_ui_view WHERE name like '%Odoo Studio:%';"
$CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "UPDATE ir_ui_view SET active=false WHERE name like '%Odoo Studio:%';"
echo "Delete all active views for $DATABASE in $CONTAINER :"
$CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "DELETE FROM report_layout WHERE view_id IN (SELECT id FROM ir_ui_view WHERE active = TRUE);"
$CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$DATABASE" -c "WITH RECURSIVE views_to_delete AS (SELECT id, inherit_id FROM ir_ui_view WHERE active = TRUE AND inherit_id IS NULL UNION ALL SELECT v.id, v.inherit_id FROM ir_ui_view v JOIN views_to_delete p ON v.inherit_id = p.id) DELETE FROM ir_ui_view WHERE id IN (SELECT id FROM views_to_delete);"
}
function clear-filestore() {
echo "Remove directory $HOME/.local/share/Odoo/filestore/$1"
rm -rf "$HOME/.local/share/Odoo/filestore/$1"
}
function disable-mailserver() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
activate-venv
load-env "$1"
images/odoocli/bin/odoocli --method 'write' --model 'ir.mail_server' --field 'active' --value False --domain "[]"
images/odoocli/bin/odoocli --method 'write' --model 'fetchmail.server' --field 'active' --value False --domain "[]"
}
function change-uuid() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
activate-venv
load-env "$1"
UUID=$(uuidgen)
echo "Set uuid $UUID for $1."
images/odoocli/bin/odoocli --method 'write' --model 'ir.config_parameter' --domain "[('key', '=', 'database.uuid')]" --field 'value' --value "'$UUID'"
}
function update-module-list() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
activate-venv
set-addons-path
echo "env['ir.module.module'].update_list()" | odoo shell --database "$1" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
}
function patch-database() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
DATABASE="$1"
FILENAME="$2"
# Use default database if second param is not given
if test -z "$2"; then
DATABASE="$ODOO_DATABASE"
FILENAME="$1"
fi
echo "Apply patch $FILENAME to Database $DATABASE."
cat "$FILENAME" | $CONTAINER_ENGINE exec -i db psql "postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost/$DATABASE"
echo "Patching database succeeded"
}
# Report Commands
function cloc-odoo() {
DATABASE="$1"
if [ -z "$DATABASE" ]; then
DATABASE="$ODOO_DATABASE"
fi
activate-venv
set-addons-path
odoo cloc -d "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http
}
function visualize-dependencies() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
mkdir -p tmp
./bin/odoo-module-dependencies "$1"
$OS_OPEN tmp/odoo-module-dependencies.html
}
# Repo Commands
function install-requirements() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
DATABASE="$1"
FILE_PATH="$2"
# Use default database if second param is not given
if test -z "$2"; then
DATABASE="$ODOO_DATABASE"
FILE_PATH="$1"
fi
FILE_NAME=$(basename "$FILE_PATH")
if [[ "$FILE_NAME" != "requirements.txt" ]]; then echo "\$1 must point to requirements.txt file."; exit; fi
CONTAINER_RUNNING=$($CONTAINER_ENGINE container inspect -f '{{.State.Running}}' odoo)
if [ "$CONTAINER_RUNNING" == "true" ]; then
echo "Installing python packages in container..."
$CONTAINER_ENGINE exec -u root odoo python -m pip install -r "/mnt/$FILE_PATH"
else
activate-venv
echo "Installing python packages in virutal env..."
uv pip install -r "$FILE_PATH"
fi
}
function generate-module-repo() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
REPO_PATH="$1"
REPO_NAME=$(basename "$REPO_PATH")
# If repo already exists, create new branch
if [ -d "$REPO_PATH/.git" ]; then
echo "Create new branch $REPO_NAME in $REPO_PATH"
cd "$REPO_PATH" || exit
git switch --orphan -c "$ODOO_VERSION"
echo "Remove all files from $REPO_PATH"
rm -rf "$REPO_PATH/*"
fi
echo "Copy template files from templates/$ODOO_VERSION to $REPO_PATH"
cp "templates/$ODOO_VERSION/*" "$REPO_PATH"
}
function lint-module-repo() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
MODULE_PATHS=$(get-modules "$MODULE_PATH")
for MODULE_PATH in ${MODULE_PATHS//,/ }; do
lint-module "$MODULE_PATH"
done
}
function test-module-repo() {
DATABASE="$1"
MODULE_PATH="$2"
# Use default database if second param is not given
if test -z "$2"; then
DATABASE="$ODOO_DATABASE"
MODULE_PATH="$1"
fi
MODULES=$(get-modules "$MODULE_PATH" basename)
for MODULE in ${MODULES//,/ }; do
echo "Testing module ${MODULE}..."
test-module "$DATABASE" "$MODULE"
done
}
# Module Commands
function init-module() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
DATABASE="$1"
MODULE_PATH="$2"
# Use default database if second param is not given
if test -z "$2"; then
DATABASE="$ODOO_DATABASE"
MODULE_PATH="$1"
fi
# Check if module path is a module repo
if [ -f "$MODULE_PATH/README.md" ]; then
# Look for manifest files in repo folder
# Get parent folder name of manifest files
# Create a comma separated string of folder names
MODULES=$(get-modules "$MODULE_PATH" basename)
else
MODULES=$(basename "$MODULE_PATH")
fi
activate-venv
set-addons-path
echo "Initialize module $MODULES on ${DATABASE}..."
odoo -d "$DATABASE" -i "$MODULES" --config "$ODOO_RC" \
--addons-path="$ADDONS_PATH" --stop-after-init --no-http
}
function migrate-module() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
MODULE_PATH="$1"
activate-venv
set-addons-path
if [ "$ODOO_VERSION" = "17.0" ]; then
echo "Migrate views of module $MODULE_PATH to ${ODOO_VERSION}..."
odoo --database "$DATABASE" -i "$MODULE_PATH" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --load=base,web,views_migration_17 --stop-after-init
fi
SOURCE_ODOO_VERSION=$(get-module-version $MODULE_PATH | cut -d'.' -f1,2)
# if [ "$ODOO_VERSION" = "18.0" ]; then
# echo "Run Odoo upgrade_code command for $MODULE_PATH from ${SOURCE_ODOO_VERSION}..."
# odoo upgrade_code --addons-path "$MODULE_PATH" --from "$SOURCE_ODOO_VERSION"
# fi
# echo "Replace manifest version $SOURCE_ODOO_VERSION with $ODOO_VERSION."
# sed -i -E "s/$SOURCE_ODOO_VERSION/$ODOO_VERSION/g" "$MODULE/__manifest__.py"
DIRECTORY=$(dirname "$MODULE_PATH")
MODULES=$(basename "$MODULE_PATH")
echo "Run odoo-module-migrator for $MODULES with source $SOURCE_ODOO_VERSION and target ${ODOO_VERSION}..."
odoo-module-migrate --directory $(dirname "$MODULE_PATH") --modules $(basename "$MODULE_PATH") \
--init-version-name $SOURCE_ODOO_VERSION --target-version-name $ODOO_VERSION --no-commit
}
function update-module() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
DATABASE="$1"
MODULE_NAME=$(basename "$2")
# Use default database if second param is not given
if test -z "$2"; then
DATABASE="$ODOO_DATABASE"
MODULE_NAME=$(basename "$1")
fi
activate-venv
set-addons-path
echo "Updating module $MODULE_NAME on ${DATABASE}..."
if [[ -n "$ODOO_PARAM" ]] ; then
odoo --database "$DATABASE" --update "$MODULE_NAME" \
--config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http "$ODOO_PARAM"
else
odoo --database "$DATABASE" --update "$MODULE_NAME" \
--config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http
fi
}
function translate-module() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
MODULE_PATH="$1"
MODULE_NAME=$(basename "$1")
LANGUAGE="$2"
DATABASE="$3"
if test -z "$2"; then
LANGUAGE="$ODOO_LANGUAGE"
fi
if test -z "$3"; then
DATABASE="$ODOO_DATABASE"
fi
activate-venv
set-addons-path
echo "Generate $LANGUAGE tranlsation file for $MODULE_NAME on ${DATABASE}..."
mkdir -p "$MODULE_PATH/i18n/"
odoo -d "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --modules "$MODULE_NAME" -l "$LANGUAGE" --i18n-export "$MODULE_PATH/i18n/$LANGUAGE.po"
# If lang is de_CH then rename the file to de.po.
if [[ "$LANGUAGE" == "de_CH" ]]; then
mv "$MODULE_PATH/i18n/$LANGUAGE.po" "$MODULE_PATH/i18n/de.po"
fi
}
function lint-module() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
activate-venv
echo "Set file and folder permissions on $1"
find "$1" -type d -exec chmod u=rwx,go=rx {} \;
find "$1" -type f -exec chmod u=rw,go=r {} \;
echo "Update index.html"
rst2html5 "$1/README.rst" "$1/static/description/index.html"
pushd .
cd "$1"
echo "Run pre-commit in $PWD"
pre-commit run --all-files # --show-diff-on-failure --color=always
echo "Stage changes"
git add .
popd
}
function test-module() {
DATABASE="$1"
MODULE_NAME=$(basename "$2")
# Use default database if second param is not given
if test -z "$2"; then
DATABASE="$ODOO_DATABASE"
MODULE_NAME=$(basename "$1")
fi
activate-venv
set-addons-path
echo "Testing module $MODULE_NAME on ${DATABASE}..."
odoo -d "$DATABASE" -i "$MODULE_NAME" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --test-tags /"$MODULE_NAME"
}
function pytest-module() {
DATABASE="$1"
MODULE_PATH="$2"
# Use default database if second param is not given
if test -z "$2"; then
DATABASE="$ODOO_DATABASE"
MODULE_PATH="$1"
fi
activate-venv
set-addons-path
ODOO_CONFIG="tmp/odoo.conf"
echo "Write $ODOO_CONFIG"
echo "[options]" > "$ODOO_CONFIG"
echo "addons_path=$ADDONS_PATH" >> "$ODOO_CONFIG"
echo "db_host = localhost" >> "$ODOO_CONFIG"
echo "db_password = odoo" >> "$ODOO_CONFIG"
echo "db_user = odoo" >> "$ODOO_CONFIG"
pytest -s --odoo-http --odoo-config="$ODOO_CONFIG" --odoo-database="$DATABASE" "$MODULE_PATH"
}
function remove-module() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
DATABASE="$1"
MODULE_NAME=$(basename "$2")
# Use default database if second param is not given
if test -z "$2"; then
DATABASE="$ODOO_DATABASE"
MODULE_NAME=$(basename "$1")
fi
activate-venv
set-addons-path
echo "Remove module $MODULE_NAME"
echo "self.env['ir.module.module'].search([('name', '=', '$MODULE_NAME')]).button_immediate_uninstall()" |
odoo shell --database "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
}
function generate-module() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
MODULE_NAME=$(basename "$1")
PARENT_DIR=$(dirname "$1")
activate-venv
echo "Scaffolding module $MODULE_NAME in ${PARENT_DIR}..."
odoo scaffold "$MODULE_NAME" "$PARENT_DIR" -t templates/module
}
function generate-oca-module-docs() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
MODULE_PATH="$1"
MANIFEST_PATH="$MODULE_PATH/__manifest__.py"
activate-venv
GIT_USER_NAME="$(git config user.name)"
GIT_USER_EMAIL="$(git config user.email)"
echo "Update CONTRIBUTORS.rst"
export GIT_USER_NAME GIT_USER_EMAIL
envsubst < templates/module/readme/CONTRIBUTORS.rst > "$MODULE_PATH/readme/CONTRIBUTORS.rst"
echo "Copy icon.png"
cp templates/module/static/description/icon.png "$MODULE_PATH/static/description/icon.png"
echo "Generator OCA readme."
REPO_URL=$(git -C "$(dirname $MODULE_PATH)" remote get-url origin)
REPO_NAME=$(basename "${REPO_URL%.git}")
oca-gen-addon-readme --repo-name=$REPO_NAME --branch=$ODOO_VERSION --org-name=$ORG_NAME --addon-dir=$MODULE_PATH
echo "Copy LICENSE"
cp templates/module/LICENSE "$MODULE_PATH/"
}
function generate-module-docs() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
MODULE_PATH="$1"
MANIFEST_PATH="$MODULE_PATH/__manifest__.py"
MODULE_TITLE=$(python3 -c "import ast; d=ast.literal_eval(open('$MANIFEST_PATH').read()); print(d.get('name', ''))")
MODULE_UNDERLINE=$(python3 -c "import ast; d=ast.literal_eval(open('$MANIFEST_PATH').read()); print('=' * len(d.get('name', '')))")
MODULE_SUMMARY=$(python3 -c "import ast; d=ast.literal_eval(open('$MANIFEST_PATH').read()); print(d.get('summary', '').replace('\n', ' ').strip())")
MODULE_WIKI_PATH=$(basename "$MODULE_PATH" | sed 's/_/-/g')
echo "Update $MODULE_PATH/README.rst"
export MODULE_TITLE MODULE_UNDERLINE MODULE_SUMMARY MODULE_WIKI_PATH
envsubst '$MODULE_TITLE $MODULE_UNDERLINE $MODULE_SUMMARY $MODULE_WIKI_PATH' < templates/README.rst > "$MODULE_PATH/README.rst"
echo "Copy icon.png"
cp templates/module/static/description/icon.png "$MODULE_PATH/static/description/icon.png"
echo "Copy LICENSE"
cp templates/module/LICENSE "$MODULE_PATH/"
}
function generate-module-security() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if test -z "$2"; then echo "\$2 is empty."; exit; fi
MODULE_NAME=$(basename "$1")
PARENT_DIR=$(dirname "$1")
MODEL_DOT_NAME="$2"
MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_')
ACCESS_FILE="$PARENT_DIR/$MODULE_NAME/security/ir.model.access.csv"
mkdir -p "$PARENT_DIR/$MODULE_NAME/security"
if [ ! -f "$ACCESS_FILE" ]; then
echo "Init security file: $ACCESS_FILE"
echo "id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink" > "$ACCESS_FILE"
fi
# id
# name
# model_id:id
# group_id:id
# perm_read
# perm_write
# perm_create
# perm_unlink
echo "Add model access to $ACCESS_FILE"
echo "access_${MODEL_NAME}_user,access.$MODEL_DOT_NAME.user,model_${MODEL_NAME},base.group_user,1,0,0,0" >> "$ACCESS_FILE"
echo "access_${MODEL_NAME}_manager,access.$MODEL_DOT_NAME.manager,model_${MODEL_NAME},base.group_erp_manager,1,1,1,1" >> "$ACCESS_FILE"
}
function generate-module-model() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if test -z "$2"; then echo "\$2 is empty."; exit; fi
MODULE_PATH="$1"
MODULE_NAME=$(basename "$1")
# Generate template values
MODEL_DOT_NAME="$2" # res.partner
MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') # res_partner
NAME=$(echo "$MODEL_DOT_NAME" | cut -d"." -f2 | sed 's/[^_]\+/\L\u&/g') # Partners
MODEL_DESCRIPTION=$(echo "$MODEL_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g') # Res Partner
MODEL_CAMEL_NAME=$(echo "$MODEL_DESCRIPTION" | sed 's/ //g') # ResPartner
export NAME MODULE_NAME MODEL_DOT_NAME MODEL_NAME MODEL_DESCRIPTION MODEL_CAMEL_NAME
echo "Substitute model template"
envsubst '$NAME $MODULE_NAME $MODEL_DOT_NAME $MODEL_NAME $MODEL_DESCRIPTION $MODEL_CAMEL_NAME' < templates/model.py > "$MODULE_PATH/models/$MODEL_NAME.py"
echo "from . import $MODEL_NAME" >> "$MODULE_PATH/models/__init__.py"
echo "File "$MODULE_PATH/models/$MODEL_NAME.py created.
}
function generate-module-inherit() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if test -z "$2"; then echo "\$2 is empty."; exit; fi
MODULE_PATH="$1"
MODULE_NAME=$(basename "$1")
# Generate template values
MODEL_DOT_NAME="$2" # res.partner
MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') # res_partner
NAME=$(echo "$MODEL_DOT_NAME" | cut -d"." -f2 | sed 's/[^_]\+/\L\u&/g') # Partners
MODEL_DESCRIPTION=$(echo "$MODEL_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g') # Res Partner
MODEL_CAMEL_NAME=$(echo "$MODEL_DESCRIPTION" | sed 's/ //g') # ResPartner
export NAME MODULE_NAME MODEL_DOT_NAME MODEL_NAME MODEL_DESCRIPTION MODEL_CAMEL_NAME
echo "Substitute model template"
envsubst '$NAME $MODULE_NAME $MODEL_DOT_NAME $MODEL_NAME $MODEL_DESCRIPTION $MODEL_CAMEL_NAME' < templates/inherit.py > "$MODULE_PATH/models/$MODEL_NAME.py"
echo "from . import $MODEL_NAME" >> "$MODULE_PATH/models/__init__.py"
echo "File "$MODULE_PATH/models/$MODEL_NAME.py created.
}
function generate-module-views() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if test -z "$2"; then echo "\$2 is empty."; exit; fi
MODULE_PATH="$1"
MODULE_NAME=$(basename "$1")
# Generate template values
MODEL_DOT_NAME="$2" # res.partner
MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') # res_partner
NAME=$(echo "$MODEL_DOT_NAME" | cut -d"." -f2 | sed 's/[^_]\+/\L\u&/g') # Partners
MODEL_DESCRIPTION=$(echo "$MODEL_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g') # Res Partner
MODEL_CAMEL_NAME=$(echo "$MODEL_DESCRIPTION" | sed 's/ //g') # ResPartner
export NAME MODULE_NAME MODEL_DOT_NAME MODEL_NAME MODEL_DESCRIPTION MODEL_CAMEL_NAME
echo "Substitute view template"
mkdir -p "$MODULE_PATH/views"
envsubst '$NAME $MODULE_NAME $MODEL_DOT_NAME $MODEL_NAME $MODEL_DESCRIPTION $MODEL_CAMEL_NAME' < templates/model_views.xml > "$MODULE_PATH/views/${MODEL_NAME}_views.xml"
echo "Update file $MODULE_NAME/__manifest__.py with:"
echo -e "\t\"views/${MODEL_NAME}_views.xml\","
}
function generate-module-snippet() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if test -z "$2"; then echo "\$2 is empty."; exit; fi
if test -z "$2"; then echo "\$2 is empty."; exit; fi
MODULE_PATH="$1" # addons/sale_workflow/sale_helm
MODEL_DOT_NAME="$2" # sale.order
VIEW_REF="$3" # sale.view_order_form
# Generate template values
MODEL_NAME=${MODEL_DOT_NAME//\./_}
MODULE_NAME=$(basename "$1")
SRC_MODULE_NAME=$(echo "$VIEW_REF" | cut -d"." -f1)
XML_ID=$(echo "$VIEW_REF" | cut -d"." -f2)
export MODULE_NAME MODEL_DOT_NAME SRC_MODULE_NAME VIEW_REF XML_ID
echo "Substitute snippet template"
mkdir -p "$MODULE_PATH/views"
envsubst '$MODULE_NAME $MODEL_DOT_NAME $SRC_MODULE_NAME $VIEW_REF $XML_ID' < templates/model_snippet.xml > "$MODULE_PATH/views/${MODEL_NAME}_views.xml"
echo "Update file $MODULE_NAME/__manifest__.py with: \"views/${MODEL_NAME}_views.xml\""
}
function release-module() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
VERSION=$(get-module-version "$1")
cd "$1" || exit
LAST_TAG=$(git describe --tags)
RELEASE_NOTES=$(git log "$LAST_TAG"..HEAD --oneline)
echo "Tag with v${VERSION}..."
git tag -a "v$VERSION" -m "v$VERSION"
echo "Release notes:"
echo "$RELEASE_NOTES"
}
function zip-module() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
# Get version and module name
VERSION=$(get-module-version "$1")
MODULE=$(basename "$1")
# Copy module to tmp folder
echo "Copy module $MODULE version $VERSION to tmp folder"
MODULE_NAME="$MODULE-$VERSION"
MODULE_PATH="tmp/$MODULE_NAME"
rm -rf "$MODULE_PATH"
mkdir -p "$MODULE_PATH"
cp -r "$1/." "$MODULE_PATH"
find "$MODULE_PATH" -type d -exec chmod u=rwx,go=rx {} \;
find "$MODULE_PATH" -type f -exec chmod u=rw,go=r {} \;
# Configure and zip module
echo "Remove auto install option"
sed -i'' -e "s/'auto_install': True,/'auto_install': False,/" "$MODULE_PATH/__manifest__.py"
echo "Remove pycache and hidden folders"
find "$MODULE_PATH" | grep -E "(\.git|__pycache__|\.pyc|\.pyo$)" | xargs rm -rf
echo "Zip module $MODULE"
cd tmp || exit
rm -f "$MODULE_NAME".zip
zip -q -r "$MODULE_NAME".zip "$MODULE_NAME"
cd .. || exit
}
# Snippet Commands
function reset-views() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
DATABASE="$1"
KEY=$(basename "$2")
# Use default database if second param is not given
if test -z "$2"; then
DATABASE="$ODOO_DATABASE"
KEY=$(basename "$1")
fi
activate-venv
set-addons-path
echo "Reset views for key $KEY"
echo "self.env['ir.ui.view'].search([('key', 'ilike', '$KEY')]).reset_arch(mode='hard')" |
odoo shell --database "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
}
function create-snippet() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
export INHERIT_ID=$(echo "$1" | cut -d. -f1-2)
FILE="snippets/$1.xml"
echo "Create snippet from template: $FILE"
envsubst '$INHERIT_ID' < templates/snippet.xml > "$FILE"
}
function install-snippet() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if test -z "$2"; then echo "\$2 is empty."; exit; fi
# Generate view name from filename
FILE=$(basename "$2")
NAME="mint_system.${FILE%.xml}"
INHERIT_ID=$(echo 'cat //data/@inherit_id' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
TYPE=$(echo 'cat //data/@type' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
MODEL=$(echo 'cat //data/@model' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
PRIORITY=$(echo 'cat //data/@priority' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
MODULE_NAME=$(echo "$INHERIT_ID" | cut -d. -f 1 )
XML_ID=$(echo "$INHERIT_ID" | cut -d. -f 2 )
# Set default values
: ${TYPE:='qweb'}
activate-venv
load-env "$1"
echo "Search for view with name $MODULE_NAME.$XML_ID"
INHERIT_ID=$(images/odoocli/bin/odoocli --method "read" --model "ir.model.data" --field "res_id" --domain "[('name', '=', '$XML_ID'), ('module', '=', '$MODULE_NAME')]")
echo "Found view id $INHERIT_ID"
echo "Apply view $NAME"
images/odoocli/bin/odoocli --method 'create' --model 'ir.ui.view' --value "{
'name': '$NAME',
'type': '$TYPE',
'model': '$MODEL',
'mode': 'extension',
'priority': $PRIORITY,
'inherit_id': $INHERIT_ID,
'arch_base': '''$(cat "$2")'''
}"
}
function update-snippet() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if test -z "$2"; then echo "\$2 is empty."; exit; fi
# Generate view name from filename
FILE=$(basename "$2")
NAME="mint_system.${FILE%.xml}"
activate-venv
load-env "$1"
echo "Update view with name $NAME"
images/odoocli/bin/odoocli --method 'write' --model 'ir.ui.view' --domain "[('name', '=', '$NAME')]" --field 'arch_base' --value "'''$(cat "$2")'''"
}
function disable-snippet() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if test -z "$2"; then echo "\$2 is empty."; exit; fi
# Generate view name from filename
FILE=$(basename "$2")
NAME="mint_system.${FILE%.xml}"
activate-venv
load-env "$1"
echo "Disable view with name $NAME"
images/odoocli/bin/odoocli --method 'write' --model "ir.ui.view" --domain "[('name', '=', '$NAME')]" --field "active" --value "False"
}
function remove-snippet() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if test -z "$2"; then echo "\$2 is empty."; exit; fi
# Generate view name from filename
FILE=$(basename "$2")
NAME="mint_system.${FILE%.xml}"
activate-venv
load-env "$1"
echo "Remove view with name $NAME"
images/odoocli/bin/odoocli --method "unlink" --model "ir.ui.view" --domain "[('name', '=', '$NAME')]"
}
function lint-snippets() {
echo "Lint snippets files..."
FILES="snippets/*.xml"
for FILE in $FILES; do
# Access various snippet parameters
FILENAME=$(basename "$FILE")
SNIPPET_MODULE=$(echo "$FILENAME" | cut -d. -f1)
REPORT=$(echo "$FILENAME" | cut -d. -f2)
EDIT=$(echo "$FILENAME" | cut -d. -f3)
INHERIT_ID=$(echo 'cat //data/@inherit_id' | xmllint --shell "$FILE" | awk -F'[="]' '!/>/{print $(NF-1)}')
TYPE=$(echo 'cat //data/@type' | xmllint --shell "$FILE" | awk -F'[="]' '!/>/{print $(NF-1)}')
MODEL=$(echo 'cat //data/@model' | xmllint --shell "$FILE" | awk -F'[="]' '!/>/{print $(NF-1)}')
PRIORITY=$(echo 'cat //data/@priority' | xmllint --shell "$FILE" | awk -F'[="]' '!/>/{print $(NF-1)}')
MODULE_NAME=$(echo "$INHERIT_ID" | cut -d. -f 1 )
XML_ID=$(echo "$INHERIT_ID" | cut -d. -f 2 )
# Check filename
COUNT_DOTS=$(echo "$FILENAME" | grep -o "\." | wc -l)
if [ "$COUNT_DOTS" -ge 4 ]; then
echo "Linting failed for $FILE"
echo "Dots count: $COUNT_DOTS"
echo " Filename: $SNIPPET_MODULE.$REPORT"
exit 1
fi
# Check if inherit id matches the filename
if [ -n "$INHERIT_ID" ]; then
if [[ "$INHERIT_ID" != "$SNIPPET_MODULE.$REPORT" ]]; then
echo "Linting failed for $FILE"
echo " Inherit: $INHERIT_ID"
echo "Filename: $SNIPPET_MODULE.$REPORT"
exit 1
fi
fi
# Check if priority is set
if [ -z "$PRIORITY" ] && [ -n "$INHERIT_ID" ]; then
echo "Linting failed for $FILE"
echo "No priority is set."
exit 1
fi
done
echo "No problems with snippets found."
}
# Git Commands
function add-git-submodule() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if test -z "$2"; then echo "\$2 is empty."; exit; fi
# Add to .gitmodules
git submodule add -f -b "$ODOO_VERSION" "$1" "$2"
# Ensure branch is Odoo version
git -C "$2" switch "$ODOO_VERSION"
# Save version
save-version
# Restore staged submodule files
git restore --staged .gitmodules "$2"
# Remove from working tree
# rm -rf .git/modules/"$2" || true
# git rm --cached "$2" || true
}
function ls-git-folder() {
if [ -n "$1" ]; then
echo $(git config --file .gitmodules --get-regexp url | sed -e 's/submodule.//g' -e 's/.url.*//g' | grep "$1" | sort)
else
echo $(git config --file .gitmodules --get-regexp url | sed -e 's/submodule.//g' -e 's/.url.*//g' | sort)
fi
}
function list-git-folder() {
# Define column widths
path_width=35
url_width=50
branch_width=8
# Print table header
printf "| %-${path_width}s | %-${url_width}s | %-${branch_width}s |\n" "PATH" "URL" "BRANCH"
echo "|$(printf '%*s' $((path_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((url_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((branch_width + 2)) '' | tr ' ' '-')|"
GIT_FOLDERS=$(ls-git-folder "$1")
for GIT_FOLDER in $GIT_FOLDERS; do
URL=$(git config --file .gitmodules --get "submodule.$GIT_FOLDER.url")
URL=${URL#git@github.com:}
URL=${URL%.git}
BRANCH=$(git -C "$GIT_FOLDER" branch --show-current 2>/dev/null || echo "detached")
printf "| %-${path_width}s | %-${url_width}s | %-${branch_width}s |\n" "$GIT_FOLDER" "$URL" "${BRANCH:-detached}"
done
}
function remove-git-folders() {
GIT_FOLDERS=$(ls-git-folder)
echo "Delete: $GIT_FOLDERS"
read -p "Are you sure you want to delete these git folders? (yes/N): " CONFIRM
if [[ "${CONFIRM,,}" != "yes" ]]; then
echo "Aborted."
return 1
fi
for GIT_FOLDER in $GIT_FOLDERS; do
echo "Delete git folder $GIT_FOLDER."
rm -rf "$GIT_FOLDER"
done
}
function remove-git-submodule() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
echo "Deinit submodule"
git submodule deinit -f "$1"
echo "Remove submodule from work tree"
git rm -f "$1"
echo "Remove submodule from .git folder"
rm -rf ".git/modules/$1"
echo "Remove submodule from .gitmodules file"
git config --file .gitmodules --remove-section submodule.addons/website
save-version
}
function clone-git-folder() {
GIT_FOLDERS=$(ls-git-folder "$1")
for GIT_FOLDER in $GIT_FOLDERS; do
GIT_FOLDER_PATH=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".path)
GIT_FOLDER_URL=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".url)
SHALLOW=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".shallow)
GIT_FOLDER_BRANCH=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".branch)
if [ "$SHALLOW" = "true" ]; then
echo "Clone git folder $GIT_FOLDER (shallow) with $GIT_FOLDER_BRANCH branch"
git -C "$GIT_FOLDER" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git clone --depth 1 --no-single-branch "$GIT_FOLDER_URL" "$GIT_FOLDER_PATH"
else
echo "Clone git folder $GIT_FOLDER with $GIT_FOLDER_BRANCH branch"
git clone --branch="$GIT_FOLDER_BRANCH" "$GIT_FOLDER_URL" "$GIT_FOLDER_PATH"
fi
done
}
function download-git-folder() {
GIT_FOLDERS=$(ls-git-folder "$1")
for GIT_FOLDER in $GIT_FOLDERS; do
GIT_FOLDER_PATH=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".path)
GIT_FOLDER_URL=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".url)
GIT_FOLDER_BRANCH=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".branch)
# Convert git url to http url.
GIT_FOLDER_URL="$(echo $GIT_FOLDER_URL | sed 's|git@|https://|g' | sed 's|m:|m/|g' | sed 's|\.git||g')"
# Get commit ref from .gitmodule.csv
COMMIT_REF=$(grep "$GIT_FOLDER_PATH" "versions/$ODOO_VERSION/.gitmodules.csv" | cut -d',' -f2)
echo "Download $GIT_FOLDER_URL to $GIT_FOLDER_PATH."
mkdir -p "$GIT_FOLDER_PATH"
curl -L -o "$GIT_FOLDER_PATH/archive.tar.gz" \
"$GIT_FOLDER_URL/archive/$COMMIT_REF.tar.gz"
echo "Extract and delete $GIT_FOLDER_PATH/archive.tar.gz"
tar -xzf "$GIT_FOLDER_PATH/archive.tar.gz" --strip-components=1 -C "$GIT_FOLDER_PATH"
rm "$GIT_FOLDER_PATH/archive.tar.gz"
done
}
function pull-git-folder() {
GIT_FOLDERS=$(ls-git-folder)
for GIT_FOLDER in $GIT_FOLDERS; do
echo "Pull git folder $GIT_FOLDER."
git -C "$GIT_FOLDER" stash
UPDATE=$(git config submodule."$GIT_FOLDER".update)
if [ -z "$UPDATE" ]; then
git -C "$GIT_FOLDER" pull origin "$ODOO_VERSION"
fi;
done
}
function reset-git-folder() {
GIT_FOLDERS=$(ls-git-folder)
for GIT_FOLDER in $GIT_FOLDERS; do
echo "Abort rebase and reset git folder $GIT_FOLDER."
git -C "$GIT_FOLDER" rebase --abort
git -C "$GIT_FOLDER" reset --hard
done
}
function clean-git-folder() {
GIT_FOLDERS=$(ls-git-folder)
for GIT_FOLDER in $GIT_FOLDERS; do
echo "Clean git folder $GIT_FOLDER."
git -C "$GIT_FOLDER" clean -df
done
}
function checkout-git-folder() {
echo "Checkout submodule commits."
GIT_FOLDERS=$(ls-git-folder)
for GIT_FOLDER in $GIT_FOLDERS; do
SHALLOW=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".shallow)
COMMIT_REF=$(grep "$GIT_FOLDER" "versions/$ODOO_VERSION/.gitmodules.csv" | cut -d',' -f2)
if [ "$SHALLOW" = "true" ]; then
echo "Checkout git folder $GIT_FOLDER (shallow) to $COMMIT_REF"
git -C "$GIT_FOLDER" stash
git -C "$GIT_FOLDER" git fetch origin "$ODOO_VERSION:$ODOO_VERSION"
git -C "$GIT_FOLDER" checkout "$COMMIT_REF"
else
echo "Checkout git folder $GIT_FOLDER to $COMMIT_REF"
git -C "$GIT_FOLDER" stash
git -C "$GIT_FOLDER" checkout "$COMMIT_REF"
fi
done
}
function switch-git-folder() {
if [ -n "$1" ]; then
ODOO_VERSION="$1"
fi
GIT_FOLDERS=$(ls-git-folder)
for GIT_FOLDER in $GIT_FOLDERS; do
SHALLOW=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".shallow)
if [ "$SHALLOW" = "true" ]; then
echo "Fetch branch $ODOO_VERSION (shallow) from origin and then switch."
git -C "$GIT_FOLDER" fetch origin "$ODOO_VERSION:$ODOO_VERSION"
git -C "$GIT_FOLDER" switch "$ODOO_VERSION"
else
echo "Switch git folder branch to $ODOO_VERSION: $GIT_FOLDER"
git -C "$GIT_FOLDER" stash
git -C "$GIT_FOLDER" switch "$ODOO_VERSION"
fi
done
}
function status-git-folder() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
GIT_FOLDERS=$(ls-git-folder "$1")
echo "Show status in git folders."
for GIT_FOLDER in $GIT_FOLDERS; do
git -C "$GIT_FOLDER" status
done
}
function stage-git-folder() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
GIT_FOLDERS=$(ls-git-folder "$1")
for GIT_FOLDER in $GIT_FOLDERS; do
echo "Stage all files in $GIT_FOLDER."
git -C "$GIT_FOLDER" add --all
done
}
function commit-git-folder() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
if test -z "$2"; then echo "\$2 is empty."; exit; fi
GIT_FOLDERS=$(ls-git-folder "$1")
for GIT_FOLDER in $GIT_FOLDERS; do
echo "Commit in $GIT_FOLDER."
git -C "$GIT_FOLDER" commit --message "$2"
done
}
function push-git-folder() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
GIT_FOLDERS=$(ls-git-folder "$1")
for GIT_FOLDER in $GIT_FOLDERS; do
echo "Push in $GIT_FOLDER."
git -C "$GIT_FOLDER" push
done
}
function sync-git-folder() {
echo "Switch and pull submodules."
switch-git-folder
pull-git-folder
}
function create-git-feature-branch(){
if test -z "$1"; then echo "\$1 is empty."; exit; fi
MODULE_NAME=$(basename "$1")
MODULE_PATH=$(dirname "$1")
echo "Create feature branch for $MODULE_NAME in ${MODULE_PATH}..."
cd ./"$MODULE_PATH" || exit
pwd
git switch -c "feature-$MODULE_NAME"
}
function create-git-mig-branch(){
if test -z "$1"; then echo "\$1 is empty."; exit; fi
MODULE_NAME=$(basename "$1")
MODULE_PATH=$(dirname "$1")
echo "Create mig branch for $MODULE_NAME in ${MODULE_PATH}..."
cd ./"$MODULE_PATH" || exit
pwd
git switch -c "$ODOO_VERSION-mig-$MODULE_NAME"
}
function load-version() {
if [ -n "$1" ]; then
ODOO_VERSION="$1"
fi
echo "Copy .gitmodules and .python-version from 'versions/$ODOO_VERSION'."
cp "versions/$ODOO_VERSION/.gitmodules" .
cp "versions/$ODOO_VERSION/.python-version" .
}
function checkout() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
ODOO_VERSION="$1"
# Load .gitmodules and .python-version
load-version "$ODOO_VERSION"
# Set .env
load-revision "$ODOO_VERSION"
# Clone the addons
clone-git-folder
# Checkout commit ref
checkout-git-folder
# Upate branch
switch-git-folder
}
function save-version() {
GIT_FOLDERS_FILE="versions/$ODOO_VERSION/.gitmodules.csv"
echo "Save submodule refs to $GIT_FOLDERS_FILE."
GIT_FOLDERS=$(ls-git-folder)
echo "path,rev" > "$GIT_FOLDERS_FILE"
for GIT_FOLDER in $GIT_FOLDERS; do
COMMIT_REF=$(git -C "$GIT_FOLDER" rev-parse HEAD)
echo "$GIT_FOLDER,$COMMIT_REF" >> "$GIT_FOLDERS_FILE"
done
echo "Copy .gitmodules and .python-version to 'versions/$ODOO_VERSION'"
cp ".gitmodules" "versions/$ODOO_VERSION/"
cp ".python-version" "versions/$ODOO_VERSION/"
}
# Revision Commands
function list-revision() {
ls -1 revisions
}
function show-revision() {
if [ -n "$1" ]; then
ODOO_REVISION="$1"
fi
echo "Odoo Revision: $ODOO_REVISION"
source "revisions/$ODOO_REVISION"
echo "Odoo Ref: $ODOO_REF"
echo "Odoo Enterprise Ref: $ODOO_ENTERPRISE_REF"
echo "Odoo Themes Ref: $ODOO_THEME_REF"
}
function load-revision() {
if [ -n "$1" ]; then
ODOO_REVISION="$1"
fi
echo "Update revision var in .env file."
if grep -q "ODOO_REVISION" .env; then
sed -i -e "s/^ODOO_REVISION=.*/ODOO_REVISION=${ODOO_REVISION}/" .env
else
echo "ODOO_REVISION=${ODOO_REVISION}" >> .env
fi
echo "Load refs for revision: $ODOO_REVISION"
export $(cat "revisions/$ODOO_REVISION" | sed 's/#.*//g' | xargs)
}
function checkout-revision() {
if [ -n "$1" ]; then
ODOO_REVISION="$1"
fi
load-revision "$ODOO_REVISION"
echo "Checkout and tag git repos..."
git -C odoo checkout $ODOO_REF
git -C odoo tag -a $ODOO_REVISION -m "$ODOO_REVISION"
git -C enterprise checkout $ODOO_ENTERPRISE_REF
git -C enterprise tag -a $ODOO_REVISION -m "$ODOO_REVISION"
git -C themes checkout $ODOO_THEME_REF
git -C themes tag -a $ODOO_REVISION -m "$ODOO_REVISION"
}
function create-revision() {
if test -z "$1"; then echo "\$1 is empty."; exit; fi
REVISION="$1"
ODOO_DATE=$(echo "$REVISION" | cut -d'.' -f3-)
BEFORE_DATE=$(date -d "$ODOO_DATE -1 day" +"%Y-%m-%d")
echo "Checkout Odoo repos on $BEFORE_DATE."
# Get git commit by date
cd odoo
git fetch
echo -e "\nShow last 3 commits from $BEFORE_DATE:"
git --no-pager log -n 3 --pretty=format:"%h %ad %s" --date=short --before="$BEFORE_DATE" origin/$ODOO_VERSION
REF=$(git rev-list -n 1 --before="$BEFORE_DATE" origin/$ODOO_VERSION)
git checkout "$REF"
cd ..
cd enterprise
git fetch
echo -e "\nShow last 3 commits from $BEFORE_DATE:"
git --no-pager log -n 3 --pretty=format:"%h %ad %s" --date=short --before="$BEFORE_DATE" origin/$ODOO_VERSION
ENTERPRISE_REF=$(git rev-list -n 1 --before="$BEFORE_DATE" origin/$ODOO_VERSION)
git checkout "$ENTERPRISE_REF"
cd ..
cd themes
git fetch
echo -e "\nShow last 3 commits from $BEFORE_DATE:"
git --no-pager log -n 3 --pretty=format:"%h %ad %s" --date=short --before="$BEFORE_DATE" origin/$ODOO_VERSION
THEME_REF=$(git rev-list -n 1 --before="$BEFORE_DATE" origin/$ODOO_VERSION)
git checkout "$THEME_REF"
cd ..
echo -e "\nCreate revision: $REVISION"
export REVISION REF ENTERPRISE_REF THEME_REF
envsubst '$REVISION $REF $ENTERPRISE_REF $THEME_REF' < templates/odoo_revision > "revisions/$REVISION"
chmod +x "revisions/$REVISION"
echo "Checkout revision: $REVISION"
checkout-revision "$REVISION"
}
function commit-and-push-revision() {
echo "Remove existing tag..."
git tag -d "$ODOO_REVISION"
echo "Commit with revision $ODOO_REVISION tag..."
git commit --all --message "feat(revision): release $ODOO_REVISION"
git tag "$ODOO_REVISION"
echo "Push tags..."
git push
git push -f --tags
}
# RPC Commands
function test-xmlrpc() {
activate-venv
load-env "$1"
echo "Connect to database and count res.partner records: $(images/odoocli/bin/odoocli --model "res.partner" --method "search_count" --domain '[]')"
}
function odoocli(){
activate-venv
images/odoocli/bin/odoocli "$@"
}
function export-website-data() {
activate-venv
if [ -n "$1" ]; then
load-env $1
fi
export ODOO_URL
export ODOO_DATABASE
export ODOO_USERNAME
export ODOO_PASSWORD
export ODOO_WEBSITE_ID
echo -e "\nPython Output:\n\n---"
python "bin/export-website-data"
EXIT_CODE=$?
echo -e "---\n"
echo -e "Exit code: $EXIT_CODE\n"
}
function import-website-data() {
activate-venv
if [ -n "$1" ]; then
load-env $1
fi
export ODOO_URL
export ODOO_DATABASE
export ODOO_USERNAME
export ODOO_PASSWORD
export ODOO_WEBSITE_ID
echo -e "\nPython Output:\n\n---"
python "bin/import-website-data"
EXIT_CODE=$?
echo -e "---\n"
echo -e "Exit code: $EXIT_CODE\n"
}
# Vuepress Commands
function install-vuepress() {
echo "Install Node."
pnpm env use --global lts
echo "Install npm packages."
pnpm install
}
function dev-vuepress() {
pnpm run dev
}
function build-vuepress() {
pnpm run build
}
function serve-vuepress() {
cd .vuepress/dist
pnpx serve
}
if declare -f "$1" > /dev/null; then
"$1" "${@:2}"
else
case "$1" in
source)
activate-venv
;;
*)
echo "Unknown command: $1"
help
exit 1
;;
esac
fi