diff --git a/odoo/17.0/Dockerfile b/odoo/17.0/Dockerfile new file mode 100644 index 000000000..7159635b2 --- /dev/null +++ b/odoo/17.0/Dockerfile @@ -0,0 +1,80 @@ +FROM ursa/ubuntu-22.04:latest +MAINTAINER Open Source Integrators + +# Install some deps, lessc and less-plugin-clean-css, and wkhtmltopdf +RUN apt update \ + && apt install -y --no-install-recommends \ + ca-certificates \ + dirmngr \ + fonts-noto-cjk \ + gnupg \ + gosu \ + gsfonts \ + libssl-dev \ + node-less \ + npm \ + postgresql-client \ + python3-num2words \ + python3-pip \ + python3-phonenumbers \ + python3-pyldap \ + python3-qrcode \ + python3-renderpm \ + python3-setuptools \ + python3-slugify \ + python3-vobject \ + python3-watchdog \ + python3-wheel \ + python3-xlrd \ + python3-xlwt \ + xz-utils \ + xfonts-75dpi \ + libx11-6 \ + fontconfig \ + xfonts-base \ + libxrender1 \ + libxext6 \ + && rm -rf /var/lib/apt/lists/* + +RUN curl -o wkhtmltox.deb -sL0 "https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.jammy_amd64.deb" \ + && apt update \ + && apt -y install --no-install-recommends ./wkhtmltox.deb \ + && rm -rf ./wkhtmltox.deb + +# Install Rclone +RUN curl https://rclone.org/install.sh | bash + +# Install Odoo +ENV ODOO_VERSION 17.0 +ARG ODOO_RELEASE=latest +RUN curl -o odoo.deb -sSL http://nightly.odoo.com/${ODOO_VERSION}/nightly/deb/odoo_${ODOO_VERSION}.${ODOO_RELEASE}_all.deb \ + && apt update \ + && apt -y install --no-install-recommends ./odoo.deb \ + && rm -rf /var/lib/apt/lists/* odoo.deb + +RUN mkdir -p /odoo/addons /odoo/data + +# Copy entrypoint script and Odoo configuration file +COPY entrypoint.sh / +COPY templates /odoo/templates +COPY requirements.txt /odoo/ +COPY wait-for-psql.py /usr/local/bin/wait-for-psql.py + +# Copy Odoo addons +# COPY src/odoo-cloud-platform/monitoring_status /odoo/addons/monitoring_status +# COPY src/odoo-cloud-platform/session_redis /odoo/addons/session_redis + +# Expose Odoo services +EXPOSE 8069 8072 + +RUN pip3 install -r /odoo/requirements.txt + +RUN chown -R odoo /odoo + +# Set the default config file +ENV ODOO_RC /odoo/odoo.conf + +VOLUME /odoo/data + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["odoo"] diff --git a/odoo/17.0/entrypoint.sh b/odoo/17.0/entrypoint.sh new file mode 100755 index 000000000..ed1830476 --- /dev/null +++ b/odoo/17.0/entrypoint.sh @@ -0,0 +1,254 @@ +#!/bin/bash +# Usage: +# $0 odoo +# $0 odoo scaffold +# $0 odoo --test-enable --workers=0 --stop-after-init -d testodoo -i base + +set -e + +# Set default value to environment variables +: ${PLATFORM:='aws'} +: ${RUNNING_ENV:='dev'} +: ${APP_IMAGE_VERSION:='latest'} +: ${MIGRATE:='true'} +# AWS +: ${AWS_HOST:='false'} +# Azure +: ${AZURE_STORAGE_CONNECTION_STRING:='false'} +: ${AZURE_STORAGE_ACCOUNT_KEY:='false'} +# Odoo +: ${ODOO_DATA_DIR:='/odoo/data'} +# PostgreSQL +: ${PGHOST:='db'} +: ${PGPORT:=5432} +: ${PGUSER:='odoo'} +: ${PGPASSWORD:='odoo'} +: ${PGDATABASE:='false'} +: ${DEFAULTDB:='postgres'} +: ${PGSSLMODE:='prefer'} +# MARABUNTA +: ${MARABUNTA_MODE:='base'} +: ${MARABUNTA_ALLOW_SERIE:='false'} + +# Configuration functions +function config_rclone() { + echo "Configure Rclone" + mkdir -p $HOME/.config/rclone + dockerize -template $TEMPLATES/rclone.conf.tmpl:$HOME/.config/rclone/rclone.conf +} + +function config_odoo() { + echo "Configure Odoo" + dockerize -template $TEMPLATES/odoo.conf.tmpl:$ODOO_RC + chown -R odoo $ODOO_DATA_DIR $ODOO_RC +} + +# Main functions +function migrate() { + OLD="" + export DB_NAME=$1 + DB_EXIST=$(psql -X -A -t -d $DEFAULTDB -c "SELECT 1 AS result FROM pg_database WHERE datname = '$DB_NAME'";) + if [ "$DB_EXIST" = "1" ]; then + TABLE_EXIST=$(psql -X -A -t -d $DB_NAME -c " + SELECT EXISTS( + SELECT * + FROM information_schema.tables + WHERE table_schema='public' AND + table_catalog='$DB_NAME' AND + table_name='ir_config_parameter' + )";) + if [ "$TABLE_EXIST" = "1" ]; then + OLD=$(psql -X -A -t -v ON_ERROR_STOP=1 -d $DB_NAME -c "SELECT value FROM ir_config_parameter WHERE key = 'database.version'" 2> /dev/null ;) + fi + fi + NEW=$(grep version= /odoo/setup.py | sed -e 's/^ *version="//' -e 's/",$//') + if [ "$OLD" != "$NEW" ]; then + sed -i -e "s/db_name =.*/db_name = $DB_NAME/" "$ODOO_RC" + OLD_PGDATABASE=$PGDATABASE + export MARABUNTA_DATABASE=$DB_NAME + export PGDATABASE=$DB_NAME + [ "$OLD" != "" ] && export MARABUNTA_FORCE_VERSION=$NEW + echo "Migrating $DB_NAME" + gosu odoo marabunta + sed -i -e "s/db_name =.*/db_name = $OLD_PGDATABASE/" "$ODOO_RC" + export PGDATABASE=$OLD_PGDATABASE + fi +} + +function duplicate() { + BACKUP=$(psql -X -A -t $DEFAULTDB -c "SELECT 1 AS result FROM pg_database WHERE datname = 'backup';") + if [ "$BACKUP" == "1" ]; then + # If backup database exists, copy it and upgrade it + EXISTS=$(psql -X -A -t $DEFAULTDB -c "SELECT 1 AS result FROM pg_database WHERE datname = '$1';") + if [ "$EXISTS" != "1" ]; then + echo "Duplicating backup to $1" + psql $DEFAULTDB -c "CREATE DATABASE \"$1\" WITH TEMPLATE \"backup\""; + case "$PLATFORM" in + "aws") + BUCKET=`echo $AWS_BUCKETNAME | sed -e "s/{db}/$1/g"` + BACKUP_BUCKET=`echo $AWS_BUCKETNAME | sed -e "s/{db}/backup/g"` + # Internal Field Separator: Split string by dashes in to an array + IFS='-' read -r -a bucket_name_array <<< "$AWS_BUCKETNAME" + # Put together production bucket string + PRODUCTION_BUCKET_NAME=`echo production-master-${bucket_name_array[2]}` + echo "Sync $BACKUP_BUCKET to $BUCKET" + rclone sync filestore:/$BACKUP_BUCKET/ filestore:/$BUCKET/ + echo "Replace attachment paths for database $1" + psql -d $1 -c " + UPDATE ir_attachment AS t SET store_fname = s.store_fname FROM ( + SELECT id,REPLACE(store_fname, '/$PRODUCTION_BUCKET_NAME/', '/$BUCKET/') + AS store_fname FROM ir_attachment WHERE store_fname IS NOT NULL) + AS s(id,store_fname) where t.id = s.id;" + ;; + "azure") + rclone sync filestore:/$RUNNING_ENV-backup/ filestore:/$RUNNING_ENV-$1/ + ;; + "do") + rclone sync filestore:/$RUNNING_ENV-backup/ filestore:/$RUNNING_ENV-$1/ + psql -d $1 -c " + UPDATE ir_attachment AS t SET store_fname = s.store_fname FROM ( + SELECT id,REPLACE(store_fname, '/production-master/', '$RUNNING_ENV-$1') + AS store_fname FROM ir_attachment WHERE db_datas is NULL) + AS s(id,store_fname) where t.id = s.id;" + ;; + *) + cp -R $ODOO_DATA_DIR/filestore/backup $ODOO_DATA_DIR/filestore/$1 + ;; + esac + migrate $1 + fi + else + # If backup database does not exist, create the database and upgrade it + create $1 + migrate $1 + fi +} + +function create() { + EXIST=$(psql -X -A -t $DEFAULTDB -c "SELECT 1 AS result FROM pg_database WHERE datname = '$1'";) + if [ "$EXIST" != "1" ]; then + echo "Creating $1" + createdb --maintenance-db=$DEFAULTDB $1 + case "$PLATFORM" in + "aws") + export BUCKET=`echo $AWS_BUCKETNAME | sed -e "s/{db}/$1/g"` + rclone mkdir filestore:/$BUCKET/ + ;; + *) + ;; + esac + fi +} + +function drop() { + echo "Dropping $1" + dropdb --if-exists --maintenance-db=$DEFAULTDB $1 + case "$PLATFORM" in + "aws") + export BUCKET=`echo $AWS_BUCKETNAME | sed -e "s/{db}/$1/g"` + ! rclone purge filestore:/$BUCKET/ + ;; + "azure") + ! rclone purge filestore:/$RUNNING_ENV-$1/ + ;; + "do") + export BUCKET=`echo $AWS_BUCKETNAME | sed -e "s/{db}/$1/g"` + ! rclone purge filestore:/$BUCKET/ + ;; + *) + rm -Rf $ODOO_DATA_DIR/filestore/$1 + ;; + esac +} + +function upgrade_existing() { + echo "Upgrade existing databases" + DATABASES=$(psql -X -A -t $DEFAULTDB -c " + SELECT datname + FROM pg_database + WHERE datname not in ('master', 'backup', 'latest', 'postgres', 'azure_maintenance', 'azure_sys', '_dodb', 'defaultdb', 'rdsadmin', 'template0', 'template1')";) + for DB_NAME in $DATABASES; do + echo "Upgrading $DB_NAME" + migrate $DB_NAME + done +} + +# For dockerize +export TEMPLATES=/odoo/templates +# For Marabunta +export MARABUNTA_MIGRATION_FILE=/odoo/migration.yml +export MARABUNTA_DB_USER=$PGUSER +export MARABUNTA_DB_PASSWORD=$PGPASSWORD +export MARABUNTA_DB_PORT=$PGPORT +export MARABUNTA_DB_HOST=$PGHOST +# For anthem +export ODOO_DATA_PATH=/odoo/songs/data + +[ "$DEBUG" == "1" ] && env | sort +config_rclone +[ "$DEBUG" == "1" ] && env | sort +config_odoo +[ "$DEBUG" == "1" ] && env | sort + +# shellcheck disable=SC2068 +wait-for-psql.py --db_host=$PGHOST --db_port=$PGPORT --db_user=$PGUSER --db_password=$PGPASSWORD --timeout=30 --db_name=${DEFAULTDB} + +cd /odoo + +# Check if the 2nd command line argument is --test-enable +if [ "$1" == "--test-enable" ] ; then + # Change configuration of demo data + sed -i -e 's/without_demo = all/without_demo = False/g' $ODOO_RC + # Run odoo with all command line arguments + # shellcheck disable=SC2145 + echo "Running Odoo with the following commands: odoo $@" + exec gosu odoo odoo "$@" + exit 0 +# RUNNING_ENV must be set and MIGRATE must be true to perform migration. +elif [ ${MIGRATE,,} == "true" ]; then + case "$RUNNING_ENV" in + "production") + create master + migrate master + ;; + "qa") + upgrade_existing + duplicate $APP_IMAGE_VERSION + ;; + "test") + upgrade_existing + duplicate $(date -u +'%Y%m%d') + ;; + "dev") + drop latest + create latest + migrate latest + ;; + *) + echo Running environment is not set. + ;; + esac +else + echo Migration has been turned off. +fi + + + # Start Odoo + case "$1" in + -- | odoo) + shift + if [[ "$1" == "scaffold" ]] ; then + exec gosu odoo odoo "$@" + else + exec gosu odoo odoo "$@" + fi + ;; + -*) + exec gosu odoo odoo "$@" + ;; + *) + exec gosu odoo "$@" + ;; + esac + +exit 1 diff --git a/odoo/17.0/requirements.txt b/odoo/17.0/requirements.txt new file mode 100644 index 000000000..347be2fef --- /dev/null +++ b/odoo/17.0/requirements.txt @@ -0,0 +1,8 @@ +anthem +boto3 +marabunta +numpy +odoo-addon-web-environment-ribbon +openupgradelib +redis +urllib3 diff --git a/odoo/17.0/templates/odoo.conf.tmpl b/odoo/17.0/templates/odoo.conf.tmpl new file mode 100644 index 000000000..b9602c0c0 --- /dev/null +++ b/odoo/17.0/templates/odoo.conf.tmpl @@ -0,0 +1,60 @@ +[options] +addons_path = {{ default "/odoo/addons" .Env.ODOO_ADDONS_PATH }} +admin_passwd = {{ default "admin" .Env.ODOO_ADMIN_PASSWD }} +csv_internal_sep = {{ default "," .Env.ODOO_CSV_INTERNAL_SEP }} +data_dir = {{ default "/odoo/data" .Env.ODOO_DATA_DIR }} +dbfilter = {{ default "^[^backup|defaultdb].*$" .Env.ODOO_DBFILTER }} +db_host = {{ default "db" .Env.PGHOST }} +db_maxconn = {{ default "64" .Env.ODOO_DB_MAXCONN }} +db_name = {{ default "False" .Env.PGDATABASE }} +db_password = {{ default "odoo" .Env.PGPASSWORD }} +db_port = {{ default "5432" .Env.PGPORT }} +db_sslmode = {{ default "prefer" .Env.PGSSLMODE }} +db_template = {{ default "template0" .Env.ODOO_DB_TEMPLATE }} +db_user = {{ default "odoo" .Env.PGUSER }} +demo = {{ default "{}" .Env.ODOO_DEMO }} +email_from = {{ default "False" .Env.ODOO_EMAIL_FROM }} +geoip_database = {{ default "/usr/share/GeoIP/GeoLite2-City.mmdb" .Env.ODOO_GEOIP_DATABASE }} +http_enable = {{ default "True" .Env.ODOO_HTTP_ENABLE }} +http_interface = {{ default "" .Env.ODOO_HTTP_INTERFACE }} +http_port = {{ default "8069" .Env.ODOO_HTTP_PORT }} +import_partial = {{ default "" .Env.ODOO_IMPORT_PARTIAL }} +limit_memory_hard = {{ default "4294967296" .Env.ODOO_LIMIT_MEMORY_HARD }} +limit_memory_soft = {{ default "2147483648" .Env.ODOO_LIMIT_MEMORY_SOFT }} +limit_request = {{ default "8192" .Env.ODOO_LIMIT_REQUEST }} +limit_time_cpu = {{ default "1800" .Env.ODOO_LIMIT_TIME_CPU }} +limit_time_real_cron = {{ default "120" .Env.ODOO_LIMIT_TIME_REAL_CRON }} +limit_time_real = {{ default "1800" .Env.ODOO_LIMIT_TIME_REAL }} +list_db = {{ default "False" .Env.ODOO_LIST_DB }} +log_db = {{ default "False" .Env.ODOO_LOG_DB }} +log_db_level = {{ default "warning" .Env.ODOO_LOG_DB_LEVEL }} +logfile = {{ default "None" .Env.ODOO_LOGFILE }} +log_handler = {{ default ":INFO" .Env.ODOO_LOG_HANDLER }} +log_level = {{ default "info" .Env.ODOO_LOG_LEVEL }} +logrotate = {{ default "False" .Env.ODOO_LOGROTATE }} +gevent_port = {{ default "8072" .Env.ODOO_GEVENT_PORT }} +max_cron_threads = {{ default "1" .Env.ODOO_MAX_CRON_THREADS }} +osv_memory_count_limit = {{ default "False" .Env.ODOO_OSV_MEMORY_COUNT_LIMIT }} +pg_path = {{ default "" .Env.ODOO_PG_PATH }} +pidfile = {{ default "" .Env.ODOO_PIDFILE }} +proxy_mode = {{ default "True" .Env.ODOO_PROXY_MODE }} +reportgz = {{ default "False" .Env.ODOO_REPORTGZ }} +screencasts = {{ default "False" .Env.ODOO_SCREENCASTS }} +screenshots = {{ default "/tmp/odoo_tests" .Env.ODOO_SCREENSHOTS }} +server_wide_modules = {{ default "web,monitoring_status" .Env.ODOO_SERVER_WIDE_MODULES }} +smtp_password = {{ default "False" .Env.ODOO_SMTP_PASSWORD }} +smtp_port = {{ default "25" .Env.ODOO_SMTP_PORT }} +smtp_server = {{ default "localhost" .Env.ODOO_SMTP_SERVER }} +smtp_ssl = {{ default "False" .Env.ODOO_SMTP_SSL }} +smtp_user = {{ default "False" .Env.ODOO_SMTP_USER }} +syslog = {{ default "False" .Env.ODOO_SYSLOG }} +test_enable = {{ default "False" .Env.ODOO_TEST_ENABLE }} +test_file = {{ default "" .Env.ODOO_TEST_FILE }} +test_tags = {{ default "None" .Env.ODOO_TEST_TAGS }} +translate_modules = {{ default "['all']" .Env.ODOO_TRANSLATE_MODULES }} +transient_age_limit = {{ default "1.0" .Env.ODOO_TRANSIENT_AGE_LIMIT }} +unaccent = {{ default "False" .Env.ODOO_UNACCENT }} +upgrade_path = {{ default "" .Env.ODOO_UPGRADE_PATH }} +without_demo = {{ default "all" .Env.ODOO_WITHOUT_DEMO }} +workers = {{ default "3" .Env.ODOO_WORKERS }} +xmlrpc_interface = {{ default "" .Env.ODOO_XMLRPC_INTERFACE }} diff --git a/odoo/17.0/templates/rclone.conf.tmpl b/odoo/17.0/templates/rclone.conf.tmpl new file mode 100644 index 000000000..4163169e9 --- /dev/null +++ b/odoo/17.0/templates/rclone.conf.tmpl @@ -0,0 +1,26 @@ +{{ if contains "aws" .Env.PLATFORM }} +[filestore] +type = s3 +provider = AWS +env_auth = false +access_key_id = {{ default "" .Env.AWS_ACCESS_KEY_ID }} +secret_access_key = {{ default "" .Env.AWS_SECRET_ACCESS_KEY }} +region = {{ default "" .Env.AWS_REGION }} +location_constraint = {{ default "" .Env.AWS_REGION }} +acl = private +{{ end }} +{{ if contains "azure" .Env.PLATFORM }} +[filestore] +type = azureblob +sas_url = {{ default "" .Env.AZURE_STORAGE_ACCOUNT_URL }} +{{ end }} +{{ if contains "do" .Env.PLATFORM }} +[filestore] +type = s3 +provider = DigitalOcean +env_auth = true +access_key_id = {{ default "" .Env.AWS_ACCESS_KEY_ID }} +secret_access_key = {{ default "" .Env.AWS_SECRET_ACCESS_KEY }} +endpoint = {{ default "" .Env.AWS_HOST }} +acl = private +{{ end }} diff --git a/odoo/17.0/wait-for-psql.py b/odoo/17.0/wait-for-psql.py new file mode 100755 index 000000000..f6da0356d --- /dev/null +++ b/odoo/17.0/wait-for-psql.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +import argparse +import logging +import sys +import time + +import psycopg2 + +if __name__ == "__main__": + logging.info("Wait for database") + + arg_parser = argparse.ArgumentParser() + arg_parser.add_argument("--db_host", required=True) + arg_parser.add_argument("--db_port", required=True) + arg_parser.add_argument("--db_user", required=True) + arg_parser.add_argument("--db_password", required=True) + arg_parser.add_argument("--db_name", required=True) + arg_parser.add_argument("--timeout", type=int, default=5) + + args = arg_parser.parse_args() + + start_time = time.time() + while (time.time() - start_time) < args.timeout: + try: + conn = psycopg2.connect( + user=args.db_user, + host=args.db_host, + port=args.db_port, + password=args.db_password, + dbname=args.db_name, + ) + error = "" + break + except psycopg2.OperationalError as e: + error = e + else: + conn.close() + time.sleep(1) + if error: + logging.error("Database connection failure: %s" % error) + sys.exit(1)