Files
Odoo-Build/task
T
Janik von Rotz bdd83120b7 fix(task): ensure migration script continues on update-module failure
- Modify `migrate` function to prevent exit on `update-module` command failure
- Adjust `remove-ssh-key` script to improve SSH agent detection and cleanup
- Maintain compatibility with existing SSH key handling behavior
2025-11-24 13:50:34 +01:00

3026 lines
100 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
help-table() {
local cmd_width=26
local opt_width=20
local desc_width=78
local 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" 'aggregate-git-folders' '[path]' 'Run gitaggregate for the provide repos.yaml.'
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][output]' '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" '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' '[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" 'list-packages' '' 'List installed python packages.'
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" 'create-module' '[path]' 'Create new Odoo module from template.'
printf "$column" 'generate-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-migration' '[path]' 'Generate .'
printf "$column" 'generate-module-snippet' '[path][ref][model]' 'Generate snippet for referefenced view.'
printf "$column" 'generate-module-wizard' '[path][model]' 'Generate wizard for a model in module folder.'
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' '[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' '[path]' 'Test target Odoo module.'
printf "$column" 'test-modules' '[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-password' '[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" 'get-module-checksum' '[path]' 'Get module checksum.'
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: source.'
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, source, odoo, hatch.'
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" 'check-version' '' 'Check if Odoo source is the correct version.'
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: source.'
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" 'run-enterprise-upgrade' '[db][version][mode]' 'Run the Odoo Enterprise upgrade scripts. Default mode ist '\''test'\''.'
printf "$column" 'run-open-upgrade' '[db][version]' 'Run the Odoo OpenUpgrade scripts.'
}
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
PASS_ENTRY=$(echo "env/${PWD##*/}" | tr '[:upper:]' '[:lower:]')
ODOO_RC="odoo.conf"
TASK_CONFIG_DIR="$HOME/.config/odoo-build"
export PYTHONPATH="$PWD/odoo" # Required so upgrade-util does not overwrite the odoo namespace
ODOO_VERSION=$(echo "$ODOO_REVISION" | cut -d'.' -f1-2)
CONTAINER_CONFIG="$HOME/.docker/$CONTAINER_REGISTRY"
# Updateable env vars
ORG_NAME=${ORG_NAME:=Mint-System}
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}
PLATFORM=${PLATFORM:=linux/amd64}
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}
LIST_DB=${LIST_DB:=True}
CONTAINER_TAG="odoo:$ODOO_REVISION"
ODOO_INIT_LOGIN=${ODOO_INIT_LOGIN:=admin}
ODOO_INIT_PASSWORD=${ODOO_INIT_PASSWORD:=admin}
# Conditional env vars
if [[ "$CONTAINER_ENGINE" == "podman" ]]; then
COMPOSE_COMMAND="podman-compose"
fi
if [[ "$(uname)" == "Darwin" ]]; then
OS_RELEASE="Darwin"
PYTHON_NOTIFY=""
else
OS_RELEASE=$(awk -F= '/^NAME/{print $2}' /etc/os-release | tr -d '"')
PYTHON_NOTIFY="inotify"
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
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
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"
}
version() {
uv --version
wkhtmltopdf -V
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
docker -v
docker compose version
else
podman --version
podman-compose --version
fi
}
generate-admin-password() {
if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi
export ODOO_PASSWORD="$1"
bin/hash-password
}
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
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
}
activate-venv() {
if [[ -f ".venv$ODOO_VERSION/bin/activate" ]]; then
source ".venv$ODOO_VERSION/bin/activate"
else
echo "Could not find .venv$ODOO_VERSION/bin/active. Use init-venv to initalize."
fi
}
remove-venv() {
echo "Remove virtualenv venv$ODOO_VERSION."
rm -rf "venv$ODOO_VERSION"
}
get-addons-path() {
set-addons-path
echo "$ADDONS_PATH"
}
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
local ir_config_parameter=${IR_CONFIG_PARAMETER:-""}
if [[ -n "$GIT_SSH_PRIVATE_KEY" ]]; then
local 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="$ir_config_parameter"
envsubst < "odoo.conf.template" > "$ODOO_RC"
}
set-addons-path() {
# Read addons path from git module
local git_folders_path="$(git config --file .gitmodules --get-regexp path | awk '{ print $2 }' | sort | tr '\n' ',' | sed 's/,*$//g')"
# Remove excluded addons
local 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 source paths
local 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
# Export the addons path to make it available to other functions
export ADDONS_PATH="$addons_path"
}
load-ssh-key() {
if [[ -n "$GIT_SSH_PRIVATE_KEY" ]]; then
echo 'Setup SSH key from env var.'
local 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
}
# Docs commands
lint() {
echo "Run pre-commit in $PWD"
pre-commit run
}
update-help-doc() {
echo 'Pipe help table into task.md.'
help-table > task.md
}
update-snippets-doc() {
./bin/update-snippets-doc
}
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
local path_url="https://github.com/${ORG_NAME}/Odoo-Build/tree/main"
for file in $(ls revisions/ | sort -rV); do
local revision="$file"
local 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
}
update-modules-doc() {
echo 'Update module doc file.'
./bin/update-modules-doc
}
update-docs() {
update-modules-doc
update-revisions-doc
update-snippets-doc
update-help-doc
}
update-module-license() {
if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi
local 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
}
template-repo(){
if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi
local repo_path="$1"
echo "Copy task file to $repo_path."
cp "templates/task" "$repo_path"
echo "Template README.md to $repo_path."
export REPO_FOLDER=$(basename "$repo_path")
export REPO_MODEL=${REPO_FOLDER%%_*}
export REPO_TITLE=$(echo "$REPO_FOLDER" | sed 's/_/ /g' | sed 's/\b\(.\)/\u\1/g')
export REPO_NAME=$(echo "$REPO_FOLDER" | sed 's/_/-/g' | sed 's/\b\(.\)/\u\1/g')
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 -f "$repo_path/.flake8"
rm -f "$repo_path/.isort.cfg"
rm -f "$repo_path/.pylintrc-mandatory"
}
# Module commands
get-modules() {
if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi
local option="$2"
if [[ -z "$option" ]]; then
option="dirname"
fi
local modules
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]+(\.[0-9]+)?|${ODOO_VERSION}(\.[0-9]+)*)" | 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]+(\.[0-9]+)?|${ODOO_VERSION}(\.[0-9]+)*)" | xargs -r dirname | xargs -r -I {} basename {} | sort -u | tr "\n" "," | sed 's/,$//')
fi
echo "$modules"
}
get-module-version() {
if [[ -z "$1" ]]; then
echo '\$1 is empty.'
else
# Get version of module
local 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
local 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
}
get-module-checksum() {
if [[ -z "$1" ]]; then
echo '\$1 is empty.'
else
bin/get-module-checksum "$1"
fi
}
list-modules() {
if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi
local modules=$(get-modules "$1" dirname)
echo "$modules" | tr ',' '\n'
}
# Container commands
archive-docker-tags() {
export CONTAINER_CONFIG
python bin/archive-docker-tags
}
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
envsubst < "compose.yml.template" > "compose.yml"
}
container-ps() {
$CONTAINER_ENGINE ps
}
build() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
# Set container tag based on Dockerfile
local manifest_file
local latest_tag
if [[ "$1" == "images/odoo" ]]; then
manifest_file="images/odoo/Dockerfile"
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
local odoo_date=$(echo "$ODOO_REVISION" | cut -d'.' -f3-)
source "revisions/$ODOO_REVISION"
# Select Python version
case "$ODOO_VERSION" in
"15.0")
local python_version="3.9"
PLATFORM="linux/amd64"
;;
"16.0")
local python_version="3.11"
;;
"17.0")
local python_version="3.11"
;;
"18.0")
local python_version="3.12"
;;
"19.0")
local python_version="3.13"
;;
*)
local python_version="3.12"
;;
esac
local docker_output="--load"
if [[ -n "$2" ]]; then
docker_output="$2"
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"
}
load-latest-revision() {
if [[ -n "$1" ]]; then
ODOO_VERSION="$1"
fi
ODOO_REVISION=$(list-revision | grep "$ODOO_VERSION" | tail -n1)
load-revision "$ODOO_REVISION"
}
checkout-latest-revision() {
if [[ -n "$1" ]]; then
ODOO_VERSION="$1"
fi
ODOO_REVISION=$(list-revision | grep "$ODOO_VERSION" | tail -n1)
checkout-revision "$ODOO_REVISION"
check-version
}
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
}
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
}
test-image() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
if [[ "$1" == "images/odoo-upgrade" ]]; then
CONTAINER_TAG="odoo-upgrade"
elif [[ "$1" == "images/odoocli" ]]; then
CONTAINER_TAG="odoocli"
fi
if [[ "$CONTAINER_TAG" =~ ^odoo: ]]; then
generate-ssh-keys
export GIT_SSH_PRIVATE_KEY
export GIT_SSH_PUBLIC_KEY
export PATH=./images/odoo/bin/:$PATH
export LOCAL_PATH="./tmp"
mkdir -p "$LOCAL_PATH"
echo -e "\033[38;5;214mTEST\033[0m: Run git clone addons"
local 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}"
# This will remove all SSH keys at the end
./images/odoo/bin/clone-git-addons
rm -rf "$LOCAL_PATH/github.com" # Cleanup for next run
ODOO_VERSION="18.0" # Example value
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]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✓ Matched" || echo "✗ Match missing"
echo "'version': '1.1'" | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✓ Matched" || echo "✗ Match missing"
echo "'version': '2.5'" | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✓ Matched" || echo "✗ Match missing"
echo "'version': '2.0.0'" | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✓ Matched" || echo "✗ Match missing"
echo "'version': '13.0.1.0.0'" | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✗ No match (expected)" || echo "✓ No match is ok"
echo "'version': '14.0.2.1.1'" | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✗ No match (expected)" || echo "✓ No match is ok"
echo "'version': '${ODOO_VERSION}.0.0.0'" | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✓ Matched" || echo "✗ Match missing"
echo '"Version": "1.0"' | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✓ Must match" || echo "✗ Match missing"
echo -e "\033[38;5;214mTEST\033[0m: Install python package."
export PYTHON_INSTALL="prometheus-client,pyjwt"
./images/odoo/bin/install-python-packages
echo -e "\033[38;5;214mTEST\033[0m: Sync python project."
cat > tmp/pyproject.toml << 'EOF'
[project]
name = "test-project"
version = "0.1.0"
description = "Test project for uv sync"
dependencies = [
"requests>=2.28.0",
"click>=8.0.0",
]
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[tool.uv]
dev-dependencies = [
"pytest>=7.0.0",
"black>=23.0.0",
]
EOF
./images/odoo/bin/sync-python-project
uv pip list
echo -e "\033[38;5;214mTEST\033[0m: Run set-addons-path"
export TEST_ADDONS_DIR="./templates,./themes,./enterprise,/gugus"
export ODOO_VERSION="18.0"
source ./images/odoo/bin/set-addons-path
echo $ADDONS_PATH | grep "./templates"
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
}
push() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local latest_tag
if [[ "$1" == "images/odoo" ]]; then
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
}
upload-module() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
if [[ -z "$2" ]]; then
echo '$2 is empty.'
exit
fi
# Check if path is root folder
local 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
local version=$(get-module-version "$module")
local module_name=$(basename "$module")
local file_path="tmp/$module_name-$version.zip"
load-env "$1"
local 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_name\n"
UPLOADS+=" version: $version\n"
fi
done
echo 'odoo_apps:'
echo -e "$UPLOADS"
}
logs() {
local container="$1"
container=${container:="odoo"}
$CONTAINER_ENGINE logs --follow "$container"
}
# Script commands
install-odoo-scripts() {
echo 'Installing Odoo scripts with user flag.'
curl -L https://ansible.build/odoo_scripts/install | bash -s -- --user
}
run-enterprise-upgrade() {
if [[ -z "$1" ]]; then
echo '$1/database is empty.'
exit
fi
if [[ -z "$2" ]]; then
echo '$2/version is empty.'
exit
fi
local database="$1"
local odoo_target_version="$2"
local mode="test"
if [[ -n "$3" ]]; then
# Options: test, production
mode="$3"
fi
local 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.'
}
run-open-upgrade() {
if [[ -z "$1" ]]; then
echo '$1/database is empty.'
exit
fi
if [[ -z "$2" ]]; then
echo '$2/version is empty.'
exit
fi
local database="$1"
local odoo_target_version="$2"
local new_database="${database}_${odoo_target_version}"
echo "Get the current version of the database."
echo "Checkout the Odoo version 15.0..."
echo "Run the upgrade script for 15.0..."
echo "Checkout the Odoo version 16.0..."
echo "Run the upgrade script for 16.0..."
echo "Checkout the Odoo version 17.0..."
echo "Run the upgrade script for 17.0..."
echo "Checkout the Odoo version 18.0..."
echo "Run the upgrade script for 18.0..."
}
# Install commands
install() {
init-venv
activate-venv
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
# OS specific installations
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, ripgrep and gettext"
sudo apt install -y libxml2-utils ripgrep 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;214mApt\033[0m: Install ripgrep and gettext"
sudo dnf install -y ripgrep gettext
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 required packages with brew."
brew install gettext libsasl2 openldap openssl libxml2 grep ripgrep
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;214mApt\033[0m: Install ripgrep"
sudo pacman -S --noconfirm ripgrep
else
echo "The operating system $OS_RELEASE is not supported."
exit 1
fi
# Install wkhtmltopdf
if [[ "$OS_RELEASE" =~ ^(Arch Linux|Darwin|Debian|Fedora Linux|Ubuntu|Pop!_OS|SteamOS)$ ]]; then
# Source: https://gist.github.com/faniska/37f896d5e9de5fee925925d7caf3cb9e
local 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
# Odoo Build requirements
echo -e "\033[38;5;214mPython\033[0m: Install Odoo Build development tools"
uv pip install -r requirements.txt
# Odoo requirements
echo -e "\033[38;5;214mPython\033[0m: Install Odoo and dependencies."
cd odoo
uv pip install -e .
grep -v "python-ldap" requirements.txt | uv pip install -r -
cd ..
# Install inotify
echo -e "\033[38;5;214mPython\033[0m: Install $PYTHON_NOTIFY."
if [[ -n "$PYTHON_NOTIFY" ]]; then
uv pip install "$PYTHON_NOTIFY"
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
# Fixes for Odoo versions
if [[ "$ODOO_VERSION" == "14.0" ]] ; then
echo -e "\033[38;5;214mPython\033[0m: Fix shebang for odoo binary."
sed -i '1s|.*|#!'"$PWD"'/.venv14.0/bin/python|' .venv14.0/bin/odoo
fi
}
# Process commands
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
local database="$2"
if [[ -z "$2" ]]; then
database="$ODOO_DATABASE"
fi
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" 2 & $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" 2 & $COMPOSE_COMMAND up -d mailpit
fi
if [[ "$1" =~ "source" ]]; then
export SMTP_SERVER=localhost
template-odoo-rc
local 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" 2 &
odoo --database="$database" --config "$ODOO_RC" \
--http-port="$port" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL"
fi
if [[ "$1" =~ "hatch" ]]; then
export SMTP_SERVER=localhost
template-odoo-rc
local 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" 2 &
odoo --database="$database" --config "$ODOO_RC" \
--http-port="$port" --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" 2 & $COMPOSE_COMMAND up -d odoo
fi
}
debug() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
set-addons-path
local port=8069
if [[ "$1" =~ "source" ]]; 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
}
run() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
if [[ -z "$2" ]]; then
echo '$2 is empty.'
exit
fi
template-compose
echo "Run '$1' container with command '$2'."
$COMPOSE_COMMAND run --rm "$1" "$2" "$3"
}
exec() {
if [[ -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
}
record-with-py-spy() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local pid="$1"
local output_file="tmp/speedscope-capture.json"
local 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 --
open-with-os "$speedscope_url"
}
record-with-memray() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
set-addons-path
local port=8069
local output_file="tmp/memray-capture.bin"
if [[ "$1" =~ "source" ]]; 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"
open-with-os "tmp/memray-flamegraph-output.html"
fi
}
remove() {
template-compose
if [[ -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
}
restart() {
template-compose
if [[ -z "$1" ]]; then
$COMPOSE_COMMAND restart
fi
if [[ "db|mail|odoo" =~ $1 ]]; then
$COMPOSE_COMMAND restart "$1"
fi
}
stop() {
template-compose
if [[ -z "$1" ]]; then
$COMPOSE_COMMAND stop
fi
$COMPOSE_COMMAND stop "$1"
}
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
}
generate-pg-ssl-keys() {
local 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
}
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
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
odoo update-module partner_firstname 2>&1 | grep "module partner_firstname: Running migration" || true
fi
}
# Interactive Shell commands
shell() {
local database="$1"
if [[ -z "$database" ]]; then
database="$ODOO_DATABASE"
fi
local code="$2"
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
}
psql() {
local database="$1"
if [[ -z "$database" ]]; then
database="$ODOO_DATABASE"
fi
$CONTAINER_ENGINE exec -it db psql "postgres://odoo:odoo@localhost:5432/$database"
}
# Database commands
init-db() {
local odoo_init_addons="${ODOO_INIT_ADDONS:-"web"}"
local database="$1"
if [[ -z "$database" ]]; then
database="$ODOO_DATABASE"
fi
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
}
import-csv() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local database="$1"
local file_path="$2"
# Use default database if second param is not given
if [[ -z "$2" ]]; then
database="$ODOO_DATABASE"
file_path="$1"
fi
local 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;"
}
drop-db() {
local 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\";"
}
load-language() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local database="$1"
local language="$2"
# Use default database if second param is not given
if [[ -z "$2" ]]; then
database="$ODOO_DATABASE"
language="$1"
fi
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"
}
set-admin() {
local 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;"
}
clear-assets() {
local 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_%';"
}
clear-views() {
local 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);"
}
clear-filestore() {
echo "Remove directory $HOME/.local/share/Odoo/filestore/$1"
rm -rf "$HOME/.local/share/Odoo/filestore/$1"
}
disable-mailserver() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
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 "[]"
}
change-uuid() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
load-env "$1"
local 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'"
}
update-module-list() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
set-addons-path
echo "env['ir.module.module'].update_list()" | odoo shell --database "$1" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
}
patch-database() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local database="$1"
local filename="$2"
# Use default database if second param is not given
if [[ -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
cloc-odoo() {
local database="$1"
if [[ -z "$database" ]]; then
database="$ODOO_DATABASE"
fi
set-addons-path
odoo cloc -d "$database" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http
}
visualize-dependencies() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
mkdir -p tmp
./bin/odoo-module-dependencies "$1"
open-with-os tmp/odoo-module-dependencies.html
cat tmp/odoo-module-dependencies.mmd
}
# Setup commands
install-requirements() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local database="$1"
local file_path="$2"
# Use default database if second param is not given
if [[ -z "$2" ]]; then
database="$ODOO_DATABASE"
file_path="$1"
fi
local file_name=$(basename "$file_path")
if ! [[ "$file_name" =~ .*requirements\.txt$ ]]; then
echo '$1 must point to requirements.txt file.'
exit
fi
local 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
echo 'Installing python packages in virutal env...'
uv pip install -r "$file_path"
fi
}
list-packages() {
uv pip list
}
generate-module-repo() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local repo_path="$1"
local 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"
}
lint-module-repo() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local module_paths=$(get-modules "$MODULE_PATH")
for module_path in ${module_paths//,/ }; do
lint-module "$module_path"
done
}
# Module commands
init-module() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local database="$1"
local module_path="$2"
# Use default database if second param is not given
if [[ -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
local modules=$(get-modules "$module_path" basename)
else
local modules=$(basename "$module_path")
fi
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
}
migrate-module() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local module_path="$1"
local source_odoo_version=$(get-module-version $module_path | cut -d'.' -f1,2)
local directory=$(dirname "$module_path")
local module=$(basename "$module_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
# 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" --from "$source_odoo_version"
# echo "Replace manifest version $source_odoo_version with $ODOO_VERSION."
# sed -i -E "s/$source_odoo_version/$ODOO_VERSION/g" "$module_path/__manifest__.py"
# else
echo "Run odoo-module-migrator for $module 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
}
update-module() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local database="$1"
local module_name=$(basename "$2")
# Use default database if second param is not given
if [[ -z "$2" ]]; then
database="$ODOO_DATABASE"
module_name=$(basename "$1")
fi
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
}
translate-module() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local module_path="$1"
local module_name=$(basename "$1")
local language="$2"
local database="$3"
if [[ -z "$2" ]]; then
language="$ODOO_LANGUAGE"
fi
if [[ -z "$3" ]]; then
database="$ODOO_DATABASE"
fi
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
}
lint-module() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
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
}
test-module() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local module=$(basename "$1")
set-addons-path
echo "Testing module $module..."
odoo -d "$ODOO_DATABASE" -i "$module" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --test-tags /"$module_name"
}
test-modules() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local modules=$(get-modules "$1" basename)
set-addons-path
echo "Collect requirements.txt files in $1."
find "$1" -type f -name "requirements.txt" -exec uv pip install -r {} \;
echo "Collect test-requirements.txt files in $1."
find "$1" -type f -name "test-requirements.txt" -exec uv pip install -r {} \;
depends_modules=$(manifestoo --select-addons-dir "$1" list-depends --separator=,)
echo "Setup test database with "$ODOO_LANGUAGE" lang and depends modules: $depends_modules"
odoo -d "$ODOO_DATABASE" -i "$depends_modules" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE"
echo "Testing modules: $modules"
odoo -d "$ODOO_DATABASE" -i "$modules" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --test-enable
}
pytest-module() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local module_path="$1"
echo "Setup custom tmp/odoo.conf for pytest."
set-addons-path
local odoo_config="tmp/odoo.conf"
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"
echo "Run pytest with odoo-http option..."
pytest -s --odoo-http --odoo-config="$odoo_config" --odoo-database="$ODOO_DATABASE" "$module_path"
}
remove-module() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local database="$1"
local module_name=$(basename "$2")
# Use default database if second param is not given
if [[ -z "$2" ]]; then
database="$ODOO_DATABASE"
module_name=$(basename "$1")
fi
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
}
create-module() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local module_name=$(basename "$1")
local parent_dir=$(dirname "$1")
echo "Scaffolding module $module_name in ${parent_dir}..."
odoo scaffold "$module_name" "$parent_dir" -t templates/module
}
generate-module-docs() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local module_path="$1"
local manifest_path="$module_path/__manifest__.py"
echo 'Setup readme folder'
mkdir -p "$module_path/readme"
cp -n -r "templates/module/readme" "$module_path"
echo 'Update DESCRIPTION.rst'
export MODULE_SUMMARY=$(python3 -c "import ast; d=ast.literal_eval(open('$manifest_path').read()); print(d.get('summary', '').replace('\n', ' ').strip())")
envsubst < templates/module/readme/DESCRIPTION.md > "$module_path/readme/DESCRIPTION.md"
echo 'Update CONTRIBUTORS.rst'
export GIT_USER_NAME="$(git config user.name)"
export GIT_USER_EMAIL="$(git config user.email)"
envsubst < templates/module/readme/CONTRIBUTORS.md > "$module_path/readme/CONTRIBUTORS.md"
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/"
echo "Generate OCA readme for $ORG_NAME org."
local repo_url=$(git -C "$(dirname $module_path)" remote get-url origin)
local 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
}
generate-module-migration() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local module_path="$1"
local module_version=$(get-module-version $module_path)
echo "Create migrations folder for version $module_version."
mkdir -p "$module_path/migrations/$module_version"
echo "Copy pre-migrate template."
cp "templates/migrations/pre-migrate.py" "$module_path/migrations/$module_version"
}
generate-module-security() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
if [[ -z "$2" ]]; then
echo '$2 is empty.'
exit
fi
local module_name=$(basename "$1")
local parent_dir=$(dirname "$1")
local model_dot_name="$2"
local model_name=$(echo "$model_dot_name" | tr '.' '_')
local 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"
}
generate-module-model() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
if [[ -z "$2" ]]; then
echo '$2 is empty.'
exit
fi
local module_path="$1"
local module_name=$(basename "$1")
echo 'Substitute model template'
export MODEL_DOT_NAME="$2" # res.partner
export MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') # res_partner
export NAME=$(echo "$MODEL_DOT_NAME" | cut -d"." -f2 | sed 's/[^_]\+/\L\u&/g') # Partners
export MODEL_DESCRIPTION=$(echo "$MODEL_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g') # Res Partner
export MODEL_CAMEL_NAME=$(echo "$MODEL_DESCRIPTION" | sed 's/ //g') # ResPartner
envsubst < 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.
}
generate-module-inherit() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
if [[ -z "$2" ]]; then
echo '$2 is empty.'
exit
fi
local module_path="$1"
local module_name=$(basename "$1")
echo 'Substitute model template'
export MODEL_DOT_NAME="$2" # res.partner
export MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') # res_partner
export NAME=$(echo "$MODEL_DOT_NAME" | cut -d"." -f2 | sed 's/[^_]\+/\L\u&/g') # Partners
export MODEL_DESCRIPTION=$(echo "$MODEL_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g') # Res Partner
export MODEL_CAMEL_NAME=$(echo "$MODEL_DESCRIPTION" | sed 's/ //g') # ResPartner
envsubst < 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.
}
generate-module-views() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
if [[ -z "$2" ]]; then
echo '$2 is empty.'
exit
fi
local module_path="$1"
local module_name=$(basename "$1")
echo 'Substitute view template'
mkdir -p "$module_path/views"
export MODEL_DOT_NAME="$2" # res.partner
export MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') # res_partner
export NAME=$(echo "$MODEL_DOT_NAME" | cut -d"." -f2 | sed 's/[^_]\+/\L\u&/g') # Partners
export MODEL_DESCRIPTION=$(echo "$MODEL_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g') # Res Partner
export MODEL_CAMEL_NAME=$(echo "$MODEL_DESCRIPTION" | sed 's/ //g') # ResPartner
envsubst < 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\","
}
generate-module-snippet() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
if [[ -z "$2" ]]; then
echo '$2 is empty.'
exit
fi
if [[ -z "$3" ]]; then
echo '$3 is empty.'
exit
fi
local module_path="$1" # addons/sale_workflow/sale_helm
echo 'Substitute snippet template'
export MODEL_DOT_NAME="$3" # sale.order
export VIEW_REF="$2" # sale.view_order_form
export MODEL_NAME=${MODEL_DOT_NAME//\./_}
export MODULE_NAME=$(basename "$1")
export SRC_MODULE_NAME=$(echo "$VIEW_REF" | cut -d"." -f1)
export XML_ID=$(echo "$VIEW_REF" | cut -d"." -f2)
mkdir -p "$module_path/views"
envsubst < templates/model_snippet.xml > "$module_path/views/${MODEL_NAME}_views.xml"
echo "Update file $MODULE_NAME/__manifest__.py with: \"views/${MODEL_NAME}_views.xml\""
}
release-module() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local version=$(get-module-version "$1")
cd "$1" || exit
local last_tag=$(git describe --tags)
local 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"
}
zip-module() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
# Get version and module name
local version=$(get-module-version "$1")
local module=$(basename "$1")
# Copy module to tmp folder
echo "Copy module $module version $version to tmp folder"
local module_name="$module-$version"
local 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
reset-views() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
local database="$1"
local key=$(basename "$2")
# Use default database if second param is not given
if [[ -z "$2" ]]; then
database="$ODOO_DATABASE"
key=$(basename "$1")
fi
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
}
create-snippet() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
export INHERIT_ID=$(echo "$1" | cut -d. -f1-2)
local file="snippets/$1.xml"
echo "Create snippet from template: $file"
envsubst '$INHERIT_ID' < templates/snippet.xml > "$file"
}
install-snippet() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
if [[ -z "$2" ]]; then
echo '$2 is empty.'
exit
fi
# Generate view name from filename
local file=$(basename "$2")
local name="mint_system.${file%.xml}"
local inherit_id=$(echo 'cat //data/@inherit_id' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
local type=$(echo 'cat //data/@type' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
local model=$(echo 'cat //data/@model' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
local priority=$(echo 'cat //data/@priority' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
local module_name=$(echo "$inherit_id" | cut -d. -f 1 )
local xml_id=$(echo "$inherit_id" | cut -d. -f 2 )
# Set default values
: ${type:='qweb'}
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")'''
}"
}
update-snippet() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
if [[ -z "$2" ]]; then
echo '$2 is empty.'
exit
fi
# Generate view name from filename
local file=$(basename "$2")
local name="mint_system.${file%.xml}"
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")'''"
}
disable-snippet() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
if [[ -z "$2" ]]; then
echo '$2 is empty.'
exit
fi
# Generate view name from filename
local file=$(basename "$2")
local name="mint_system.${file%.xml}"
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"
}
remove-snippet() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
if [[ -z "$2" ]]; then
echo '$2 is empty.'
exit
fi
# Generate view name from filename
local file=$(basename "$2")
local name="mint_system.${file%.xml}"
load-env "$1"
echo "Remove view with name $name"
images/odoocli/bin/odoocli --method "unlink" --model "ir.ui.view" --domain "[('name', '=', '$name')]"
}
lint-snippets() {
echo 'Lint snippets files...'
local files="snippets/*.xml"
for file in $files; do
# Access various snippet parameters
local filename=$(basename "$file")
local snippet_module=$(echo "$filename" | cut -d. -f1)
local report=$(echo "$filename" | cut -d. -f2)
local edit=$(echo "$filename" | cut -d. -f3)
local inherit_id=$(echo 'cat //data/@inherit_id' | xmllint --shell "$file" | awk -F'[="]' '!/>/{print $(NF-1)}')
local type=$(echo 'cat //data/@type' | xmllint --shell "$file" | awk -F'[="]' '!/>/{print $(NF-1)}')
local model=$(echo 'cat //data/@model' | xmllint --shell "$file" | awk -F'[="]' '!/>/{print $(NF-1)}')
local priority=$(echo 'cat //data/@priority' | xmllint --shell "$file" | awk -F'[="]' '!/>/{print $(NF-1)}')
local module_name=$(echo "$inherit_id" | cut -d. -f 1 )
local xml_id=$(echo "$inherit_id" | cut -d. -f 2 )
# Check filename
local 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
add-git-submodule() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
if [[ -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
}
aggregate-git-folders() {
if [[ -z "$1" ]]; then
echo '$1 is empty.'
exit
fi
gitaggregate -c "$1" --expand-env
}
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
}
list-git-folder() {
# Define column widths
local path_width=35
local url_width=50
local 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 ' ' '-')|"
local git_folders=$(ls-git-folder "$1")
for git_folder in $git_folders; do
local url=$(git config --file .gitmodules --get "submodule.$git_folder.url")
url=${url#git@github.com:}
url=${url%.git}
local 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
}
remove-git-folders() {
local 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
}
remove-git-submodule() {
if [[ -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
}
clone-git-folder() {
local git_folders=$(ls-git-folder "$1")
for git_folder in $git_folders; do
local git_folder_path=$(git config --file .gitmodules --get submodule."$git_folder".path)
local git_folder_url=$(git config --file .gitmodules --get submodule."$git_folder".url)
local shallow=$(git config --file .gitmodules --get submodule."$git_folder".shallow)
local 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
}
download-git-folder() {
local git_folders=$(ls-git-folder "$1")
for git_folder in $git_folders; do
local git_folder_path=$(git config --file .gitmodules --get submodule."$git_folder".path)
local git_folder_url=$(git config --file .gitmodules --get submodule."$git_folder".url)
local 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
local 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
}
pull-git-folder() {
local git_folders=$(ls-git-folder)
for git_folder in $git_folders; do
echo "Pull git folder $git_folder."
git -C "$git_folder" stash
local update=$(git config submodule."$git_folder".update)
if [[ -z "$update" ]]; then
git -C "$git_folder" pull origin "$ODOO_VERSION"
fi;
done
}
reset-git-folder() {
local 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
}
clean-git-folder() {
local 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
}
checkout-git-folder() {
echo 'Checkout submodule commits.'
local git_folders=$(ls-git-folder)
for git_folder in $git_folders; do
local shallow=$(git config --file .gitmodules --get submodule."$git_folder".shallow)
local 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" 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
}
switch-git-folder() {
if [[ -n "$1" ]]; then
ODOO_VERSION="$1"
fi
local git_folders=$(ls-git-folder)
for git_folder in $git_folders; do
local 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
}
status-git-folder() {
if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi
local 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
}
stage-git-folder() {
if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi
local 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
}
commit-git-folder() {
if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi
if [[ -z "$2" ]]; then echo "\$2 is empty."; exit; fi
local 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
}
push-git-folder() {
if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi
local git_folders=$(ls-git-folder "$1")
for git_folder in $git_folders; do
echo "Push in $git_folder."
git -C "$git_folder" push
done
}
sync-git-folder() {
echo 'Switch and pull submodules.'
switch-git-folder
pull-git-folder
}
create-git-feature-branch(){
if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi
local module_name=$(basename "$1")
local 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"
}
create-git-mig-branch(){
if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi
local module_name=$(basename "$1")
local 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"
}
# Project commands
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" .
}
checkout() {
if [[ -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
}
save-version() {
local git_folders_file="versions/$ODOO_VERSION/.gitmodules.csv"
echo "Save submodule refs to $git_folders_file."
local git_folders=$(ls-git-folder)
echo "path,rev" > "$git_folders_file"
for git_folder in $git_folders; do
local 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/"
}
list-versions() {
ls -1 versions
}
check-version() {
echo 'Check if Odoo version matches the release file:'
local odoo_version=$(echo "$ODOO_VERSION" | cut -d'.' -f1)
grep -E "version_info.+$odoo_version" odoo/odoo/release.py
}
# Revision commands
list-revision() {
ls -1 revisions
}
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"
}
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)
}
checkout-revision() {
if [[ -n "$1" ]]; then
ODOO_REVISION="$1"
fi
load-revision "$ODOO_REVISION"
echo 'Checkout Odoo git repos...'
git -C odoo checkout $ODOO_REF
git -C enterprise checkout $ODOO_ENTERPRISE_REF
git -C themes checkout $ODOO_THEME_REF
}
create-revision() {
if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi
local revision="$1"
local odoo_date=$(echo "$revision" | cut -d'.' -f3-)
local 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
local 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
local 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
local theme_ref=$(git rev-list -n 1 --before="$before_date" origin/$ODOO_VERSION)
git checkout "$theme_ref"
cd ..
echo -e "\nCreate revision: $revision"
export ODOO_REVISION=$revision
export ODOO_REF=$ref
export ODOO_ENTERPRISE_REF=$enterprise_ref
export ODOO_THEME_REF=$theme_ref
envsubst < templates/odoo_revision > "revisions/$revision"
chmod +x "revisions/$revision"
echo "Checkout revision: $revision"
checkout-revision "$revision"
}
commit-and-push-revision() {
if [[ -n "$1" ]]; then
ODOO_REVISION="$1"
fi
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
test-xmlrpc() {
load-env "$1"
echo "Connect to database and count res.partner records: $(images/odoocli/bin/odoocli --model 'res.partner' --method 'search_count' --domain '[]')"
}
odoocli(){
images/odoocli/bin/odoocli "$@"
}
export-website-data() {
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"
local exit_code=$?
echo -e "---\n"
echo -e "Exit code: $exit_code\n"
}
import-website-data() {
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"
local exit_code=$?
echo -e "---\n"
echo -e "Exit code: $exit_code\n"
}
# Vuepress commands
install-vuepress() {
echo 'Install Node.'
pnpm env use --global lts
echo 'Install npm packages.'
pnpm install
}
dev-vuepress() {
pnpm run dev
}
build-vuepress() {
pnpm run build
}
serve-vuepress() {
cd .vuepress/dist
pnpx serve
}
if declare -f "$1" > /dev/null; then
activate-venv
"$1" "${@:2}"
else
case "$1" in
source)
activate-venv
;;
*)
echo "Unknown command: $1"
help
exit 1
;;
esac
fi