From fd0134b3618840b8b3350bf20c56c499575af8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20-=20Le=20Filament?= <theo@le-filament.com> Date: Wed, 7 Aug 2024 15:09:50 +0200 Subject: [PATCH] feat: allow tasks parallelization and use specifics variables in template --- .ansible-lint | 10 +- .yamllint | 68 ++-- README.md | 135 +++---- defaults/main.yml | 3 + handlers/main.yml | 100 ++--- tasks/instance.yml | 281 ------------- tasks/instance_images.yml | 22 ++ tasks/main.yml | 546 +++++++++++++++++++++++--- templates/Dockerfile.j2 | 65 ++- templates/backup.yaml.j2 | 46 +-- templates/docker-compose.yaml.j2 | 134 ++++--- templates/fetch_repos | 4 +- templates/fetch_repos_addons | 4 +- templates/odoo.conf.j2 | 30 +- templates/pre_restore-odootest.sql.j2 | 6 +- templates/repos-addons.yaml.j2 | 4 +- templates/repos.yaml.j2 | 4 +- templates/restore-odootest.yaml.j2 | 46 +-- vars/main.yml | 28 ++ 19 files changed, 876 insertions(+), 660 deletions(-) delete mode 100644 tasks/instance.yml create mode 100644 tasks/instance_images.yml create mode 100644 vars/main.yml diff --git a/.ansible-lint b/.ansible-lint index 8d40d06..b22a3e6 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -1,7 +1,7 @@ --- warn_list: # or 'skip_list' to silence them completely - - git-latest # Git checkouts must contain explicit version - - ignore-errors # Use failed_when and specify error conditions instead of using ignore_errors - - no-changed-when # Commands should not change things if nothing needs doing - - no-handler # Tasks that run when changed should likely be handlers - - package-latest # Package installs should not use latest + - git-latest # Git checkouts must contain explicit version + - ignore-errors # Use failed_when and specify error conditions instead of using ignore_errors + - no-changed-when # Commands should not change things if nothing needs doing + - no-handler # Tasks that run when changed should likely be handlers + - package-latest # Package installs should not use latest diff --git a/.yamllint b/.yamllint index fbebdb8..b66ea0c 100644 --- a/.yamllint +++ b/.yamllint @@ -3,37 +3,37 @@ extends: default rules: - braces: - max-spaces-inside: 1 - level: error - brackets: - max-spaces-inside: 1 - level: error - colons: - max-spaces-after: -1 - level: error - commas: - max-spaces-after: -1 - level: error - # comments enable - comments: enable - comments-indentation: enable - document-start: enable - empty-lines: - max: 3 - level: error - hyphens: - level: error - indentation: - level: warning - indent-sequences: consistent - spaces: 4 - check-multi-line-strings: true - key-duplicates: enable - line-length: disable - new-line-at-end-of-file: enable - new-lines: - type: unix - # trailing-spaces enable - trailing-spaces: enable - truthy: enable + braces: + max-spaces-inside: 1 + level: error + brackets: + max-spaces-inside: 1 + level: error + colons: + max-spaces-after: -1 + level: error + commas: + max-spaces-after: -1 + level: error + # comments enable + comments: enable + comments-indentation: enable + document-start: enable + empty-lines: + max: 3 + level: error + hyphens: + level: error + indentation: + level: warning + indent-sequences: consistent + spaces: 2 + check-multi-line-strings: true + key-duplicates: enable + line-length: disable + new-line-at-end-of-file: enable + new-lines: + type: unix + # trailing-spaces enable + trailing-spaces: enable + truthy: enable diff --git a/README.md b/README.md index 0b05243..79dd965 100644 --- a/README.md +++ b/README.md @@ -36,70 +36,71 @@ The variable structure for instances is defined below : # Odoo instances definition. odoo_instances: - # Production instance - odoo: - # Name of a production instance. Itself if it's a production instance (like here). - prod_instance: odoo - # Name of an instance from which take image (like here). Itself if image build is done on this instance. - image_instance: odootest - # Name of an instance from which take backups. Itself if backups are made for this instance (like here). - backup_instance: odoo - # Odoo version to be deployed - odoo_setup_version: 16 - ## URL (only sub.domain without https:// in front) - url: "{{ SERVER_odoo_url }}" - master_pass: "{{ SERVER_odoo_master_pass }}" - ## Database identifiers user and password - db_user: "{{ SERVER_odoo_db_user }}" - db_pass: "{{ SERVER_odoo_db_pass }}" - ## Database name - db: "{{ SERVER_odoo_db_name }}" - ## OPTIONAL - For maintenance only - Backup password - # odoo_backup_pass: "{{ SERVER_odoo_backup_pass }}" - - # Test instance - odootest: - # This test instance rely on previous production instance. - prod_instance: odoo - # This instance build it's image. - image_instance: odootest - # This instance need to restore backups from production instance. - backup_instance: odoo - # Odoo version to be deployed - odoo_setup_version: 16 - ## URL (only sub.domain without https:// in front) - url: "{{ SERVER_odoo_test_url }}" - ## Database Name - db: "{{ SERVER_odoo_db_name_test }}" - ## Custom modules Le Filament (one module per repo and specify branch if differ from default odoo version) - # custom_modules: - # - repo: lefilament_account - # - repo: lefilament_export_journal - # branch: "14.0" - ## OCA modules - these should be limited to the ones not already defined - ## in groups_vars/docker_odoo in default_odoo_custom_modules_oca (since these - ## are already part of lefilament/odoo:10.0 and 12.0 dockers) - # custom_modules_oca: - # - repo: server-tools - # modules: - # - auto_backup - ## Other Odoo modules where git repo is the module - # other_repos: - # - repo: filament - # url: git@github.com:lefilament/link_sale_project_tasks.git - ## Other Odoo modules where git repo contains various modules - # other_modules: - # - repo: filament - # url: https://github.com/lefilament/bank-statement-import.git - # branch: 12.0-mig-account_bank_statement_import_ofx - # modules: - # - account_bank_statement_import_ofx - ## OPTIONAL - Extra pip packages to be installed in image - # odoo_pip_packages: unidecode - ## OPTIONAL - Odoo multilingual - Will install Odoo with all languages (English and French only if set to no - by default) - uncomment and set to yes if needed - # odoo_multilingual: false - ## OPTIONAL - Force usage of Python 3.6 for Odoo 12.0 - # odoo_python36: false + # Production instance + odoo: + # Name of a production instance. Itself if it's a production instance (like here). + prod_instance: odoo + # Name of an instance from which take image (like here). Itself if image build is done on this instance. + image_instance: odootest + # Name of an instance from which take backups. Itself if backups are made for this instance (like here). + backup_instance: odoo + # Odoo version to be deployed + odoo_setup_version: 16 + ## List of domains + domains: "{{ SERVER_odoo_domains }}" + master_pass: "{{ SERVER_odoo_master_pass }}" + ## Database identifiers user and password + db_user: "{{ SERVER_odoo_db_user }}" + db_pass: "{{ SERVER_odoo_db_pass }}" + ## Database name + db: "{{ SERVER_odoo_db_name }}" + ## OPTIONAL - For maintenance only - Backup password + # odoo_backup_pass: "{{ SERVER_odoo_backup_pass }}" + + # Test instance + odootest: + # This test instance rely on previous production instance. + prod_instance: odoo + # This instance build it's image. + image_instance: odootest + # This instance need to restore backups from production instance. + backup_instance: odoo + # Odoo version to be deployed + odoo_setup_version: 16 + ## List of domains + domains: "{{ SERVER_odoo_test_domains }}" + ## Database Name + db: "{{ SERVER_odoo_db_name_test }}" + ## Custom modules Le Filament (one module per repo and specify branch if differ from default odoo version) + # custom_modules: + # - repo: lefilament_account + # - repo: lefilament_export_journal + # branch: "14.0" + ## OCA modules - these should be limited to the ones not already defined + ## in groups_vars/docker_odoo in default_odoo_custom_modules_oca (since these + ## are already part of lefilament/odoo:10.0 and 12.0 dockers) + # custom_modules_oca: + # - repo: server-tools + # modules: + # - auto_backup + ## Other Odoo modules where git repo is the module + # other_repos: + # - repo: filament + # url: git@github.com:lefilament/link_sale_project_tasks.git + ## Other Odoo modules where git repo contains various modules + # other_modules: + # - repo: filament + # url: https://github.com/lefilament/bank-statement-import.git + # branch: 12.0-mig-account_bank_statement_import_ofx + # modules: + # - account_bank_statement_import_ofx + ## OPTIONAL - Extra pip packages to be installed in image + # odoo_pip_packages: + # - "unidecode" + ## OPTIONAL - Odoo multilingual - Will install Odoo with all languages (English and French only if set to no - by default) - uncomment and set to yes if needed + # odoo_multilingual: false + ## OPTIONAL - Force usage of Python 3.6 for Odoo 12.0 + # odoo_python36: false ### OPTIONAL - Mail server configuration - for Odoo - uncomment to add mail server ## Mail domain @@ -118,8 +119,8 @@ odoo_instances: ## OPTIONAL - Whitelisted URLs allowed to be reached from Odoo SERVER # whitelisted_urls: -# - github.com -# - sources.le-filament.com +# - "github.com" +# - "sources.le-filament.com" ``` * Extra modules : although Odoo comes with a number of apps, the real added value comes from community modules. A number of variables allow to retrieve modules from various sources : @@ -139,7 +140,7 @@ odoo_instances: * imap_mailserver : if defined a whitelist is created to allow connecting to IMAP server on port 993 (see also the previous section related to Internet Access for explaining why these whitelists might be required) * Backups (for backups to be deployed, host needs to be in maintenance_contract group) : the backups make use of [Tecnativa Duplicity Docker](https://github.com/Tecnativa/docker-duplicity) - * swift_accounts dict parameters for object storage instances where backups should be pushed daily + * swift_accounts dict parameters (where keys are integers) for object storage instances where backups should be pushed daily * odoo_backup_pass : Passphrase for encryption of backups * Metabase : This role allows for deployment of [Metabase](https://www.metabase.com/), a Business Intelligence (BI) Open Source tool that would be connected with readonly used on prod database in order to extract metrics and indicators from Odoo database and display those in dashboards. diff --git a/defaults/main.yml b/defaults/main.yml index c73def4..babc4a5 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -48,6 +48,9 @@ odoo_setup_conf: ## Custom modules default URL for retrieving custom modules : custom_modules_base_url: "https://sources.le-filament.com/lefilament" +## Default Traefik entrypoints. +default_traefik_entrypoints: 'websecure' + ## OPTIONAL - Default configuration to use namespaces (set to false to not use namespace) docker_userns_remap: true diff --git a/handlers/main.yml b/handlers/main.yml index 67f632a..c79f3d6 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -1,86 +1,30 @@ --- -- name: Start odoo whitelists +- name: "Restart whitelist containers" block: - - name: Remove whitelists container - community.docker.docker_compose_v2: - project_src: /home/docker - files: whitelists.yaml - project_name: whitelists - remove_orphans: true - state: absent - listen: restart odoo whitelists - - - name: Start whitelists container - community.docker.docker_compose_v2: - project_src: /home/docker - files: whitelists.yaml - project_name: whitelists - recreate: always - remove_orphans: true - state: present - listen: restart odoo whitelists - -- name: Pull odoo ML image - community.docker.docker_image: - name: lefilament/odoo:{{ instance_odoo_version }}_ml - source: pull - force_source: true - when: not ansible_check_mode and instance.value.odoo_multilingual | default(false) - -- name: Pull odoo Python3.6 image - community.docker.docker_image: - name: lefilament/odoo:{{ instance_odoo_version }}_py3.6 - source: pull - force_source: true - when: not ansible_check_mode and instance.value.odoo_python36 | default(false) - -- name: Pull odoo image - community.docker.docker_image: - name: lefilament/odoo:{{ instance_odoo_version }} - source: pull - force_source: true - when: not ansible_check_mode and not (instance.value.odoo_multilingual | default(false) or instance.value.odoo_python36 | default(false)) - -- name: Build odoo image - community.docker.docker_compose_v2: - project_src: /home/docker/{{ instance.key }}/ - build: always - # TODO: ensure build without cache. - recreate: always - remove_orphans: true - state: restarted - async: 600 - poll: 10 - when: not ansible_check_mode - -- name: Restart odoo container - block: - - name: Remove Odoo containers + - name: "Remove whitelist containers" community.docker.docker_compose_v2: - project_src: /home/docker/{{ instance.key }}/ - remove_orphans: true - state: absent - listen: restart odoo container - - - name: Start Odoo containers + project_src: "/home/docker" + files: "whitelists.yaml" + project_name: "whitelists" + remove_orphans: true + state: absent + listen: "restart whitelists containers" + + - name: "Start whitelist containers" community.docker.docker_compose_v2: - project_src: /home/docker/{{ instance.key }}/ - recreate: always - remove_orphans: true - state: present - listen: restart odoo container - when: not ansible_check_mode - -- name: Remove intermediate image + project_src: "/home/docker" + files: "whitelists.yaml" + project_name: "whitelists" + recreate: always + remove_orphans: true + state: present + listen: "restart whitelist containers" + +- name: "Remove intermediate images" community.docker.docker_prune: builder_cache: true images: true images_filters: - label: stage=builder - when: not ansible_check_mode and inventory_hostname not in groups['maintenance_contract'] - -- name: Remove ssh private keys - ansible.builtin.file: - path: "/home/docker/{{ instance.key }}/odoo/id_ed25519.sources" - state: absent - when: not ansible_check_mode and inventory_hostname not in groups['maintenance_contract'] + label: "stage=builder" + listen: "remove intermediate images" + when: inventory_hostname not in groups['maintenance_contract'] diff --git a/tasks/instance.yml b/tasks/instance.yml deleted file mode 100644 index 4c87319..0000000 --- a/tasks/instance.yml +++ /dev/null @@ -1,281 +0,0 @@ ---- -- name: Create Odoo docker structure on server in /home/docker/{{ item.key }} - ansible.builtin.file: - name: "/home/docker/{{ item.key }}" - state: directory - owner: root - group: root - mode: '0755' - -- name: Container building requirements - tags: - - "odoo" - when: item.value.image_instance | default(false) == item.key - block: - - name: Create Odoo private docker structure on server in /home/docker/{{ item.key }} - ansible.builtin.file: - name: "/home/docker/{{ item.key }}/odoo/private" - state: directory - owner: root - group: root - mode: '0755' - - - name: Copy update scripts to be run during build - ansible.builtin.template: - src: "{{ file }}" - dest: "/home/docker/{{ item.key }}/odoo/private/" - owner: root - group: root - mode: '0750' - with_items: - - fetch_repos - - fetch_repos_addons - loop_control: - loop_var: file - notify: - - Pull odoo ML image - - Pull odoo Python3.6 image - - Pull odoo image - - Build odoo image - - - name: Set repos variables from template - ansible.builtin.template: - src: "{{ file }}.j2" - dest: "/home/docker/{{ item.key }}/odoo/private/{{ file }}" - owner: root - group: root - mode: '0644' - with_items: - - "repos.yaml" - - "repos-addons.yaml" - loop_control: - loop_var: file - notify: - - Pull odoo ML image - - Pull odoo Python3.6 image - - Pull odoo image - - Build odoo image - - - name: Copy odoo.conf file - ansible.builtin.template: - src: odoo.conf.j2 - dest: "/home/docker/{{ item.key }}/odoo/odoo.conf" - owner: root - group: root - mode: '0600' - notify: Build odoo image - - - name: Copy private GitLab ssh keys file - ansible.builtin.copy: - content: "{{ git_modules_privkey | default('') }}" - dest: "/home/docker/{{ item.key }}/odoo/id_ed25519.sources" - owner: root - group: root - mode: '0400' - notify: - - Build odoo image - - Remove ssh private keys - - Remove intermediate image - - - name: Copy ssh config for connecting to LF Gitlab - ansible.builtin.copy: - src: ssh_config - dest: "/home/docker/{{ item.key }}/odoo/ssh_config" - owner: root - group: root - mode: '0444' - notify: Build odoo image - - - name: Copy private Git ssh keys file - when: git_private_keys | default(false) - ansible.builtin.copy: - content: "{{ git_private_keys }}" - dest: "/home/docker/{{ item.key }}/odoo/id_rsa" - owner: root - group: root - mode: '0400' - notify: Build odoo image - - - name: Copy Dockerfile to retrieve private repos and extra OCA ones - ansible.builtin.template: - src: Dockerfile.j2 - dest: "/home/docker/{{ item.key }}/odoo/Dockerfile" - owner: root - group: root - mode: '0644' - notify: - - Pull odoo ML image - - Pull odoo Python3.6 image - - Pull odoo image - - Build odoo image - -- name: Get image from another instance - tags: - - "odoo" - when: item.value.image_instance | default(false) != item.key - block: - - name: Check if destination instance image exists - community.docker.docker_image_info: - name: "filament/{{ item.key }}:{{ instance_odoo_setup.odoo_version }}" - register: dst_image - - - name: Check if source instance image exists - community.docker.docker_image_info: - name: "filament/{{ item.value.image_instance }}:{{ instance_odoo_setup.odoo_version }}" - register: src_image - - - name: Copy image from {{ item.value.image_instance }} to {{ item.key }} may be needed - when: dst_image.images | length != 0 and dst_image.images[0]['Config']['Image'] != src_image.images[0]['Config']['Image'] - ansible.builtin.debug: - msg: "Images differ between {{ item.value.image_instance }} to {{ item.key }}. You may need to run:\n$ ansible-playbook docker_odoo_operations_playbook.yml -l '{{ inventory_hostname }}' -t 'restart_prod_on_test_image' -e 'instance={{ item.key }}'" - - - name: Copy image from image_instance if it does not exist - when: dst_image.images | length == 0 - community.docker.docker_image: - name: "filament/{{ item.value.image_instance }}:{{ instance_odoo_setup.odoo_version }}" - repository: "filament/{{ item.key }}:{{ instance_odoo_setup.odoo_version }}" - source: local - -- name: Copy docker compose service - tags: - - "docker_proxy" - - "metabase" - - "odoo" - ansible.builtin.template: - src: docker-compose.yaml.j2 - dest: "/home/docker/{{ item.key }}/docker-compose.yml" - owner: root - group: root - mode: '0400' - notify: "restart odoo container" - -# -------------------------------------------------- -# non-prod restore section -# -------------------------------------------------- -- name: Restore backups from another instance - when: (item.value.backup_instance | default(item.key) != item.key or item.value.backup_host | default(inventory_hostname) != inventory_hostname) and inventory_hostname in groups.maintenance_contract - tags: - - "backup_odoo" - block: - - name: Copy sql script to be run before restoring db from backup_instance - ansible.builtin.template: - src: pre_restore-odootest.sql.j2 - dest: "/home/docker/backups/pre_restore-{{ item.key }}.sql" - owner: root - group: root - mode: '0444' - - - name: Copy sql script to be run after restoring db from backup_instance - ansible.builtin.template: - src: post_restore-odootest.sql.j2 - dest: "/home/docker/backups/post_restore-{{ item.key }}.sql" - owner: root - group: root - mode: '0444' - - - name: Copy compose file to restore db from backup_instance - ansible.builtin.template: - src: restore-odootest.yaml.j2 - dest: "/home/docker/backups/restore-{{ item.key }}{{ account_index + 1 }}.yaml" - owner: root - group: root - mode: '0400' - loop: "{{ swift_accounts }}" - loop_control: - label: "account {{ account_index + 1 }}" - index_var: account_index - loop_var: account - -# -------------------------------------------------- -# prod backup section -# -------------------------------------------------- -- name: Backup - tags: - - "backup_odoo" - when: item.value.backup_instance | default(false) == item.key and item.value.backup_host | default(inventory_hostname) == inventory_hostname and inventory_hostname in groups.maintenance_contract - block: - - name: "Copy docker compose for backup account {{ account_index + 1 }}" - ansible.builtin.template: - src: backup.yaml.j2 - dest: "/home/docker/backups/backup-{{ item.key }}{{ account_index + 1 }}.yaml" - owner: root - group: root - mode: '0400' - loop: "{{ swift_accounts }}" - loop_control: - label: "account {{ account_index + 1 }}" - index_var: account_index - loop_var: account - - # TODO: duplicate with swift_accounts - - name: "Add cron job to run every day backup account {{ account_index + 1 }}" - ansible.builtin.cron: - name: "backup {{ item.key }}{{ account_index + 1 }}" - minute: "{{ '%H' | strftime((('1970-01-01 ' + backup_time_start) | to_datetime).timestamp() + (swift_accounts | length - (account_index + 1)) * ((backup_time_slot_duration | community.general.to_seconds - swift_accounts | length * backup_time_max_duration | community.general.to_seconds) / (swift_accounts | length - 1) + backup_time_max_duration | community.general.to_seconds) | int) }}" - hour: "{{ '%H' | strftime((('1970-01-01 ' + backup_time_start) | to_datetime).timestamp() + (swift_accounts | length - (account_index + 1)) * ((backup_time_slot_duration | community.general.to_seconds - swift_accounts | length * backup_time_max_duration | community.general.to_seconds) / (swift_accounts | length - 1) + backup_time_max_duration | community.general.to_seconds) | int) }}" - job: "/usr/bin/docker-compose -f /home/docker/backups/backup-{{ item.key }}{{ account_index + 1 }}.yaml run --rm backup_odoo" - loop: "{{ swift_accounts }}" - loop_control: - label: "account {{ account_index + 1 }}" - index_var: account_index - loop_var: account - -# Flush handlers. -- name: Set facts - tags: - - "docker_proxy" - - "metabase" - - "odoo" - ansible.builtin.set_fact: - instance: "{{ {'key': item.key, 'value': item.value} }}" - instance_odoo_version: "{{ instance_odoo_setup.odoo_version }}" - cacheable: false - -- name: Flush handlers - tags: - - "docker_proxy" - - "metabase" - - "odoo" - ansible.builtin.meta: flush_handlers - -# -------------------------------------------------- -# Postgres Readonly user -# -------------------------------------------------- -- name: Postgres Read-only user - tags: - - "db_remote_ro_user" - when: item.value.odoo_remote_db_access | default(false) - block: - - name: Allow readonly user connection to prod db (with userns_remap) - when: docker_userns_remap - ansible.builtin.blockinfile: - path: "/var/lib/docker/{{ dockremap_subuid }}.{{ dockremap_subgid }}/volumes/{{ item.key }}_db/_data/pg_hba.conf" - block: | - host {{ item.value.db }} {{ odoo_instances[item.value.prod_instance | default(item.key)].db_user }} 172.16.0.0/12 md5 - host postgres {{ odoo_instances[item.value.prod_instance | default(item.key)].db_user }} 172.16.0.0/12 md5 - host {{ item.value.db }} {{ item.value.odoo_db_rouser }} all md5 - - - name: PROD Allow readonly user connection to prod db (no userns_remap) - when: not docker_userns_remap - ansible.builtin.blockinfile: - path: /var/lib/docker/volumes/{{ item.key }}_db/_data/pg_hba.conf - block: | - host {{ item.value.db }} {{ odoo_instances[item.value.prod_instance | default(item.key)].db_user }} 172.16.0.0/12 md5 - host postgres {{ odoo_instances[item.value.prod_instance | default(item.key)].db_user }} 172.16.0.0/12 md5 - host {{ item.value.db }} {{ item.value.odoo_db_rouser }} all md5 - - - name: PROD Disable access all rights (with userns_remap) - when: docker_userns_remap - ansible.builtin.lineinfile: - name: "/var/lib/docker/{{ dockremap_subuid }}.{{ dockremap_subgid }}/volumes/{{ item.key }}_db/_data/pg_hba.conf" - regexp: "^host all all all md5" - line: "#host all all all md5" - - - name: PROD Disable access all rights (no userns_remap) - when: not docker_userns_remap - ansible.builtin.lineinfile: - name: /var/lib/docker/volumes/{{ item.key }}_db/_data/pg_hba.conf - regexp: "^host all all all md5" - line: "#host all all all md5" - -# TODO: add restart db container diff --git a/tasks/instance_images.yml b/tasks/instance_images.yml new file mode 100644 index 0000000..225bc98 --- /dev/null +++ b/tasks/instance_images.yml @@ -0,0 +1,22 @@ +--- +- name: Check if destination instance image exists + community.docker.docker_image_info: + name: "filament/{{ item.key }}:{{ odoo_instance_version }}" + register: dst_image + +- name: Check if source instance image exists + community.docker.docker_image_info: + name: "filament/{{ item.value.image_instance }}:{{ odoo_instance_version }}" + register: src_image + +- name: Copy image from {{ item.value.image_instance }} to {{ item.key }} may be needed + ansible.builtin.debug: + msg: "Images differ between {{ item.value.image_instance }} to {{ item.key }}. You may need to run:\n$ ansible-playbook docker_odoo_operations_playbook.yml -l '{{ inventory_hostname }}' -t 'restart_prod_on_test_image' -e 'instance={{ item.key }}'" + when: dst_image.images | length != 0 and dst_image.images[0]['Config']['Image'] != src_image.images[0]['Config']['Image'] + +- name: Copy image from image_instance if it does not exist + community.docker.docker_image: + name: "filament/{{ item.value.image_instance }}:{{ odoo_instance_version }}" + repository: "filament/{{ item.key }}:{{ odoo_instance_version }}" + source: local + when: dst_image.images | length == 0 diff --git a/tasks/main.yml b/tasks/main.yml index 430b6ce..c8a003a 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -3,42 +3,490 @@ # Whitelists section # -------------------------------------------------- -- name: Copy docker compose for whitelists - tags: "whitelists" +- name: "Copy docker compose for whitelists" + tags: + - "docker_whitelists" + ansible.builtin.template: + src: "whitelists.yaml.j2" + dest: "/home/docker/whitelists.yaml" + owner: "root" + group: "root" + mode: "0400" + notify: + - "restart whitelist containers" + when: > + restrict_internet_access + and whitelisted_urls is defined + +- name: "Set empty lists to trigger actions on instances" + tags: + - "docker_proxy" + - "metabase" + set_fact: + instances_to_pull: [] + instances_to_rebuild: [] + instances_to_remove_key: [] + instances_to_restart: [] + +- name: "Create Odoo docker directories on server in /home/docker/" + ansible.builtin.file: + name: "/home/docker/{{ odoo_instance.key }}" + state: directory + owner: "root" + group: "root" + mode: "0755" + loop: "{{ odoo_instances | dict2items }}" + loop_control: + label: "{{ odoo_instance.key }}" + when: test_instance_is_selected + +- name: "Create Odoo docker build directories on server in /home/docker/<instance>/odoo/" + ansible.builtin.file: + name: "/home/docker/{{ odoo_instance.key }}/odoo/" + state: directory + owner: "root" + group: "root" + mode: "0755" + loop: "{{ odoo_instances | dict2items }}" + loop_control: + label: "{{ odoo_instance.key }}" + when: > + test_instance_need_build + and test_instance_is_selected + +- name: "Copy odoo.conf file" + vars: + template_admin_passwd: "{{ odoo_source_instance.value.master_pass | pbkdf2_passwd(65534 | random(seed=inventory_hostname) | string) }}" + template_server_wide_modules: "{{ odoo_instance_setup.server_wide_modules | join(',') }}{% if odoo_instance.value.odoo_server_wide_modules is defined %},{{ odoo_instance.value.odoo_server_wide_modules | join(',') }}{% endif %}" + template_dbfilter: "^({{ odoo_source_instance.value.db }}|{{ odoo_instance.value.db }})$" + template_db_name: "{{ odoo_source_instance.value.db }}" + template_db_password: "{{ odoo_source_instance.value.db_pass }}" + template_db_user: "{{ odoo_source_instance.value.db_user }}" + template_db_maxconn: "{{ odoo_instance.value.odoo_db_maxconn }}" + template_limit_time_cpu: "{{ odoo_instance.value.odoo_limit_time_cpu }}" + template_limit_time_real: "{{ odoo_instance.value.odoo_limit_time_real }}" + template_force_workers: "{{odoo_instance.value.force_odoo_workers}}" + template_modules_auto_install_disabled: "{{ (odoo_instance_setup.modules_auto_install_disabled | default(['mail_bot'])) | join(',') }}{% if odoo_instance.value.modules_auto_install_disabled is defined %},{{ odoo_instance.value.modules_auto_install_disabled | join(',') }}{% endif %}" + template_modules_auto_install_enabled: "{{ (odoo_instance_setup.modules_auto_install_enabled | default(['web'])) | join(',') }}{% if odoo_instance.value.modules_auto_install_enabled is defined %},{{ odoo_instance.value.modules_auto_install_enabled | join(',') }}{% endif %}" + template_extra_conf: "{{ odoo_instance.value.odoo_extra_conf }}" ansible.builtin.template: - src: whitelists.yaml.j2 - dest: "/home/docker/whitelists.yaml" + src: "odoo.conf.j2" + dest: "/home/docker/{{ odoo_instance.key }}/odoo/odoo.conf" + owner: "root" + group: "root" + mode: "0600" + loop: "{{ odoo_instances | dict2items }}" + loop_control: + label: "{{ odoo_instance.key }}" + when: > + test_instance_need_build + and test_instance_is_selected + register: result + +# notify: +# - "rebuild odoo image" +- name: "Add instance to restart list if files was changed" + set_fact: + instances_to_rebuild: "{{ instances_to_rebuild + [item.item.key] }}" + loop: "{{ result.results | flatten(levels=1) }}" + loop_control: + label: "{{ item.item.key }}" + when: test_result_item_has_changed + +- name: Copy private GitLab ssh keys file + ansible.builtin.copy: + content: "{{ git_modules_privkey | default('') }}" + dest: "/home/docker/{{ odoo_instance.key }}/odoo/id_ed25519.sources" owner: root group: root mode: '0400' - notify: restart odoo whitelists - when: restrict_internet_access and whitelisted_urls | default([]) + loop: "{{ odoo_instances | dict2items }}" + loop_control: + label: "{{ odoo_instance.key }}" + when: > + test_instance_need_build + and test_instance_is_selected + register: result + notify: + - "remove intermediate images" + +# notify: +# - "rebuild odoo image" +# - "remove ssh private keys" +- name: "Add instance to rebuild and remove key lists if files was changed" + set_fact: + instances_to_rebuild: "{{ instances_to_rebuild + [item.item.key] }}" + instances_to_remove_key: "{{ instances_to_remove_key + [item.item.key] }}" + loop: "{{ result.results | flatten(levels=1) }}" + loop_control: + label: "{{ item.item.key }}" + when: test_result_item_has_changed + +- name: Copy ssh config for connecting to LF Gitlab + ansible.builtin.copy: + src: ssh_config + dest: "/home/docker/{{ odoo_instance.key }}/odoo/ssh_config" + owner: root + group: root + mode: '0444' + loop: "{{ odoo_instances | dict2items }}" + loop_control: + label: "{{ odoo_instance.key }}" + when: > + test_instance_need_build + and test_instance_is_selected + register: result + +# notify: +# - "rebuild odoo image" +- name: "Add instance to rebuild list if files was changed" + set_fact: + instances_to_rebuild: "{{ instances_to_rebuild + [item.item.key] }}" + loop: "{{ result.results | flatten(levels=1) }}" + loop_control: + label: "{{ item.item.key }}" + when: test_result_item_has_changed + +- name: Copy private Git ssh keys file + ansible.builtin.copy: + content: "{{ git_private_keys }}" + dest: "/home/docker/{{ odoo_instance.key }}/odoo/id_rsa" + owner: root + group: root + mode: '0400' + loop: "{{ odoo_instances | dict2items }}" + loop_control: + label: "{{ odoo_instance.key }}" + when: > + git_private_keys is defined + and test_instance_need_build + and test_instance_is_selected + register: result + +# notify: +# - "rebuild odoo image" +- name: "Add instance to rebuild list if files was changed" + set_fact: + instances_to_rebuild: "{{ instances_to_rebuild + [item.item.key] }}" + loop: "{{ result.results | flatten(levels=1) }}" + loop_control: + label: "{{ item.item.key }}" + when: test_result_item_has_changed + +- name: Copy Dockerfile to retrieve private repos and extra OCA ones + vars: + template_odoo_instance: "{{ odoo_instance }}" + template_odoo_instance_setup: "{{ odoo_instance_setup }}" + ansible.builtin.template: + src: Dockerfile.j2 + dest: "/home/docker/{{ odoo_instance.key }}/odoo/Dockerfile" + owner: root + group: root + mode: '0644' + loop: "{{ odoo_instances | dict2items }}" + loop_control: + label: "{{ odoo_instance.key }}" + when: > + test_instance_need_build + and test_instance_is_selected + register: result + +# notify: +# - "pull odoo image" +# - "rebuild odoo image" +- name: "Add instance to pull and rebuild lists if files was changed" + set_fact: + instances_to_pull: "{{ instances_to_pull + [item.item.key] }}" + instances_to_rebuild: "{{ instances_to_rebuild + [item.item.key] }}" + loop: "{{ result.results | flatten(levels=1) }}" + loop_control: + label: "{{ item.item.key }}" + when: test_result_item_has_changed + +- name: Copy docker compose service + tags: + - "docker_proxy" + - "metabase" + vars: + template_odoo_instance: "{{ odoo_instance }}" + template_odoo_instance_setup: "{{ odoo_instance_setup }}" + template_odoo_instance_domains: "{{ odoo_instance_domains }}" + template_odoo_source_instance: "{{ odoo_source_instance }}" + template_database_name: "{{ odoo_instance.value.db }}" + template_instance_is_prod: "{{ test_instance_is_prod }}" + template_instance_need_build: "{{ test_instance_need_build }}" + ansible.builtin.template: + src: docker-compose.yaml.j2 + dest: "/home/docker/{{ odoo_instance.key }}/docker-compose.yml" + owner: root + group: root + mode: '0400' + loop: "{{ odoo_instances | dict2items }}" + loop_control: + label: "{{ odoo_instance.key }}" + when: > + test_instance_is_selected + register: result + +# notify: +# - "restart odoo image" +- name: "Add instance to restart list if files was changed" + tags: + - "docker_proxy" + - "metabase" + set_fact: + instances_to_restart: "{{ instances_to_restart + [item.item.key] }}" + loop: "{{ result.results | flatten(levels=1) }}" + loop_control: + label: "{{ item.item.key }}" + when: test_result_item_has_changed + +# Flush handlers. +- name: "Pull Odoo image" + vars: + odoo_favor: "{{ '_ml' if (odoo_instance.value.odoo_multilingual | default(false)) else '_py3.6' if (odoo_instance.value.odoo_python36 | default(false)) else '' }}" + community.docker.docker_image: + name: "lefilament/odoo:{{ odoo_instance_version }}{{ odoo_favor }}" + source: pull + force_source: true + loop: "{{ instances_to_pull | unique | sort }}" + loop_control: + label: "{{ odoo_instance.key }}" + when: > + allow_pull is truthy(convert_bool=True) + and (instances_to_pull | length > 0) + async: 600 + poll: 10 + +- name: "Rebuild instance images" + ansible.builtin.command: + chdir: "/home/docker/{{ odoo_instance.key }}/" + cmd: "docker compose build" + loop: "{{ instances_to_rebuild | unique | sort }}" + loop_control: + label: "{{ odoo_instance.key }}" + when: > + allow_rebuild is truthy(convert_bool=True) + and instances_to_rebuild | length > 0 + async: 1800 + poll: 10 + +- name: "Remove instances private keys" + ansible.builtin.file: + path: "/home/docker/{{ odoo_instance.key }}/odoo/id_ed25519.sources" + state: absent + loop: "{{ instances_to_remove_key | unique | sort }}" + when: > + allow_remove_key is truthy(convert_bool=True) + and instances_to_remove_key | length > 0 + +- name: "Get image from another instance" + include_tasks: "instance_images.yml" + loop: "{{ odoo_instances | dict2items }}" + loop_control: + label: "{{ odoo_instance.key }}" + when: > + not test_instance_need_build + and test_instance_is_selected + +- name: "Flush handlers" + tags: + - "docker_proxy" + - "metabase" + - "odoo" + ansible.builtin.meta: flush_handlers -- name: NON-PROD instance setup +- name: "Restart instances: remove instance containers" tags: - - "always" - - "odoo_nonprod" - ansible.builtin.include_tasks: - file: instance.yml - with_dict: "{{ odoo_instances }}" + - "docker_proxy" + - "metabase" + community.docker.docker_compose_v2: + project_src: "/home/docker/{{ odoo_instance.key }}/" + remove_orphans: true + state: absent + loop: "{{ instances_to_restart | unique | sort }}" + when: > + allow_restart is truthy(convert_bool=True) + and instances_to_restart | length > 0 + +- name: "Restart instances: start instance containers" + tags: + - "docker_proxy" + - "metabase" + community.docker.docker_compose_v2: + project_src: /home/docker/{{ odoo_instance.key }}/ + recreate: always + remove_orphans: true + state: present + loop: "{{ instances_to_restart | unique | sort }}" + when: > + allow_restart is truthy(convert_bool=True) + and instances_to_restart | length > 0 + +# -------------------------------------------------- +# non-prod restore section +# -------------------------------------------------- +- name: "Copy sql script to be run before restoring db from backup_instance" + tags: + - "odoo_backup" + vars: + template_database_name: "{{ odoo_instance.value.db }}" + ansible.builtin.template: + src: "pre_restore-odootest.sql.j2" + dest: "/home/docker/backups/pre_restore-{{ odoo_instance.key }}.sql" + owner: "root" + group: "root" + mode: "0444" + loop: "{{ odoo_instances | dict2items }}" + loop_control: + label: "{{ odoo_instance.key }}" + when: > + (odoo_instance.value.backup_instance | default(odoo_instance.key) != odoo_instance.key + or odoo_instance.value.backup_host | default(inventory_hostname) != inventory_hostname) + and (inventory_hostname in groups.maintenance_contract) + and test_instance_is_prod is false + and test_instance_is_selected + +- name: "Copy sql script to be run after restoring db from backup_instance" + tags: + - "odoo_backup" + ansible.builtin.template: + src: "post_restore-odootest.sql.j2" + dest: "/home/docker/backups/post_restore-{{ odoo_instance.key }}.sql" + owner: "root" + group: "root" + mode: "0444" + loop: "{{ odoo_instances | dict2items }}" loop_control: - label: "{{ item.key }}" + label: "{{ odoo_instance.key }}" + when: > + (odoo_instance.value.backup_instance | default(odoo_instance.key) != odoo_instance.key + or odoo_instance.value.backup_host | default(inventory_hostname) != inventory_hostname) + and (inventory_hostname in groups.maintenance_contract) + and test_instance_is_prod is false + and test_instance_is_selected + +- name: "Copy compose file to restore db from backup_instance" + tags: + - "odoo_backup" vars: - instance_odoo_setup: "{{ odoo_setup_conf[item.value.odoo_setup_version | default(odoo_setup_version)] }}" - when: not (item.value.prod_instance | default(false) == item.key) and (odoo_instance is undefined or item.key == odoo_instance) + # Allow role vars to work with `item` variable. + item: "{{ item_account_instance.1 }}" + template_backup_account: "{{ item_account_instance.0 }}" + template_odoo_instance: "{{ odoo_instance }}" + template_database_name: "{{ template_odoo_instance.value.db }}" + template_odoo_source_instance: "{{ {'key': template_odoo_instance.value.prod_instance | default(template_odoo_instance.key ), 'value': odoo_instances[template_odoo_instance.value.prod_instance | default(template_odoo_instance.key )]} }}" + ansible.builtin.template: + src: "restore-odootest.yaml.j2" + dest: "/home/docker/backups/restore-{{ template_odoo_instance.key }}{{ template_backup_account.key }}.yaml" + owner: "root" + group: "root" + mode: "0400" + loop: "{{ swift_accounts | dict2items | product(odoo_instances | dict2items) }}" + loop_control: + label: "account {{ template_backup_account.key }} on {{ template_odoo_instance.key }}" + loop_var: item_account_instance + when: > + (odoo_instance.value.backup_instance | default(odoo_instance.key) != odoo_instance.key + or odoo_instance.value.backup_host | default(inventory_hostname) != inventory_hostname) + and (inventory_hostname in groups.maintenance_contract) + and test_instance_is_prod is false + and test_instance_is_selected -- name: PROD instance setup +# -------------------------------------------------- +# prod backup section +# -------------------------------------------------- +- name: "Copy docker compose for backup" tags: - - "always" - - "odoo_prod" - ansible.builtin.include_tasks: - file: instance.yml - with_dict: "{{ odoo_instances }}" + - "odoo_backup" + vars: + # Allow role vars to work with `item` variable. + item: "{{ item_account_instance.1 }}" + template_backup_account: "{{ item_account_instance.0 }}" + template_odoo_instance: "{{ odoo_instance }}" + ansible.builtin.template: + src: "backup.yaml.j2" + dest: "/home/docker/backups/backup-{{ template_odoo_instance.key }}{{ template_backup_account.key }}.yaml" + owner: "root" + group: "root" + mode: "0400" + loop: "{{ swift_accounts | dict2items | product(odoo_instances | dict2items) }}" loop_control: - label: "{{ item.key }}" + label: "account {{ template_backup_account.key }} on {{ template_odoo_instance.key }}" + loop_var: item_account_instance + when: > + (odoo_instance.value.backup_host | default(inventory_hostname) == inventory_hostname) + and (inventory_hostname in groups.maintenance_contract) + and test_instance_is_prod + and test_instance_is_selected + +- name: "Add cron job to backup instances every day" + tags: + - "odoo_backup" vars: - instance_odoo_setup: "{{ odoo_setup_conf[item.value.odoo_setup_version | default(odoo_setup_version)] }}" - when: item.value.prod_instance | default(false) == item.key and (odoo_instance is undefined or item.key == odoo_instance) + # Allow role vars to work with `item` variable. + item: "{{ item_account_instance.1 }}" + template_backup_account: "{{ item_account_instance.0 }}" + template_odoo_instance: "{{ odoo_instance }}" + ansible.builtin.cron: + name: "backup {{ template_odoo_instance.key }}{{ template_backup_account.key }}" + minute: "{{ '%H' | strftime((('1970-01-01 ' + backup_time_start) | to_datetime).timestamp() + (swift_accounts | length - template_backup_account.key) * ((backup_time_slot_duration | community.general.to_seconds - swift_accounts | length * backup_time_max_duration | community.general.to_seconds) / (swift_accounts | length - 1) + backup_time_max_duration | community.general.to_seconds) | int) }}" + hour: "{{ '%H' | strftime((('1970-01-01 ' + backup_time_start) | to_datetime).timestamp() + (swift_accounts | length - template_backup_account.key) * ((backup_time_slot_duration | community.general.to_seconds - swift_accounts | length * backup_time_max_duration | community.general.to_seconds) / (swift_accounts | length - 1) + backup_time_max_duration | community.general.to_seconds) | int) }}" + job: "/usr/bin/docker compose -f /home/docker/backups/backup-{{ template_odoo_instance.key }}{{ template_backup_account.key }}.yaml run --rm backup_odoo" + loop: "{{ swift_accounts | dict2items | product(odoo_instances | dict2items) }}" + loop_control: + label: "account {{ template_backup_account.key }} on {{ template_odoo_instance.key }}" + loop_var: item_account_instance + when: > + (odoo_instance.value.backup_host | default(inventory_hostname) == inventory_hostname) + and (inventory_hostname in groups.maintenance_contract) + and test_instance_is_prod + and test_instance_is_selected + +# -------------------------------------------------- +# Postgres Readonly user +# -------------------------------------------------- +# - name: Postgres Read-only user +# tags: +# - "db_remote_ro_user" +# when: item.value.odoo_remote_db_access | default(false) +# block: +# - name: Allow readonly user connection to prod db (with userns_remap) +# when: docker_userns_remap +# ansible.builtin.blockinfile: +# path: "/var/lib/docker/{{ dockremap_subuid }}.{{ dockremap_subgid }}/volumes/{{ item.key }}_db/_data/pg_hba.conf" +# block: | +# host {{ item.value.db }} {{ odoo_instances[item.value.prod_instance | default(item.key)].db_user }} 172.16.0.0/12 md5 +# host postgres {{ odoo_instances[item.value.prod_instance | default(item.key)].db_user }} 172.16.0.0/12 md5 +# host {{ item.value.db }} {{ item.value.odoo_db_rouser }} all md5 + +# - name: PROD Allow readonly user connection to prod db (no userns_remap) +# when: not docker_userns_remap +# ansible.builtin.blockinfile: +# path: /var/lib/docker/volumes/{{ item.key }}_db/_data/pg_hba.conf +# block: | +# host {{ item.value.db }} {{ odoo_instances[item.value.prod_instance | default(item.key)].db_user }} 172.16.0.0/12 md5 +# host postgres {{ odoo_instances[item.value.prod_instance | default(item.key)].db_user }} 172.16.0.0/12 md5 +# host {{ item.value.db }} {{ item.value.odoo_db_rouser }} all md5 + +# - name: PROD Disable access all rights (with userns_remap) +# when: docker_userns_remap +# ansible.builtin.lineinfile: +# name: "/var/lib/docker/{{ dockremap_subuid }}.{{ dockremap_subgid }}/volumes/{{ item.key }}_db/_data/pg_hba.conf" +# regexp: "^host all all all md5" +# line: "#host all all all md5" + +# - name: PROD Disable access all rights (no userns_remap) +# when: not docker_userns_remap +# ansible.builtin.lineinfile: +# name: /var/lib/docker/volumes/{{ item.key }}_db/_data/pg_hba.conf +# regexp: "^host all all all md5" +# line: "#host all all all md5" + +# TODO: add restart db container + + + # -------------------------------------------------- # Remote imports section @@ -46,28 +494,28 @@ - name: Remote Imports tags: "remote_imports" block: - - name: Push private keys for any external tool connection - when: private_keys is defined - ansible.builtin.copy: - content: "{{ private_keys }}" - dest: "/root/.ssh/id_rsa" - owner: root - group: root - mode: '0400' - - - name: PROD Copy script file for collecting remote files - when: private_pull is defined - ansible.builtin.template: - src: pull_remote_files.sh.j2 - dest: /root/pull_remote_files.sh - owner: root - group: root - mode: '0700' - - - name: PROD add cron job to pull files from remote server - when: private_pull is defined - ansible.builtin.cron: - name: pull remote server files - minute: "30" - hour: "23" - job: /root/pull_remote_files.sh + - name: Push private keys for any external tool connection + when: private_keys is defined + ansible.builtin.copy: + content: "{{ private_keys }}" + dest: "/root/.ssh/id_rsa" + owner: root + group: root + mode: '0400' + + - name: PROD Copy script file for collecting remote files + when: private_pull is defined + ansible.builtin.template: + src: pull_remote_files.sh.j2 + dest: /root/pull_remote_files.sh + owner: root + group: root + mode: '0700' + + - name: PROD add cron job to pull files from remote server + when: private_pull is defined + ansible.builtin.cron: + name: pull remote server files + minute: "30" + hour: "23" + job: /root/pull_remote_files.sh diff --git a/templates/Dockerfile.j2 b/templates/Dockerfile.j2 index 2704bb5..7160e96 100644 --- a/templates/Dockerfile.j2 +++ b/templates/Dockerfile.j2 @@ -1,8 +1,14 @@ ## Image to get git repos {# TODO: make base image a variable #} -FROM lefilament/odoo:{{ instance_odoo_setup.odoo_version }}{% if item.value.odoo_python36 | default(false) %}_py3.6{% endif %}{% if item.value.odoo_multilingual | default(false) %}_ml{% endif %} as odoo_addons +{% if template_odoo_instance.value.from_image_digest is defined %} +FROM lefilament/odoo@{{ template_odoo_instance.value.from_image_digest }} +{% else %} +FROM lefilament/odoo:{{ template_odoo_instance_setup.odoo_version }}{% if template_odoo_instance.value.odoo_python36 | default(false) %}_py3.6{% endif %}{% if template_odoo_instance.value.odoo_multilingual | default(false) %}_ml{% endif %} as odoo_addons +{% endif %} LABEL stage=builder + USER root + # Install GitLab private keys COPY ./id_ed25519.sources /root/.ssh/ COPY ./ssh_config /root/.ssh/config @@ -10,23 +16,62 @@ COPY ./ssh_config /root/.ssh/config # Install private keys COPY ./id_rsa /root/.ssh/ {% endif %} -# Install private repos -COPY ./private/* /opt/odoo/private/ -RUN chown -R root:root /opt/odoo/additional_addons /opt/odoo/private_addons -RUN /opt/odoo/private/fetch_repos_addons -RUN /opt/odoo/private/fetch_repos + +# Change files owner to solve a Docker error when copying (`failed to copy directory: Container ID <id> cannot be mapped to a host ID`). +RUN chown -R root:root /opt/odoo + +# Add addons. +ARG SAVE_COMMITS_FILENAME='custom_addons' +RUN echo 'info: getting Odoo modules...' \ +# Get OCA Odoo modules from custom_modules_oca variable. +{% for repo in template_odoo_instance.value.custom_modules_oca | default([]) | sort(attribute='repo') %} + && get_addons 'https://github.com/OCA/{{ repo.repo }}' '{{ repo.branch | default(template_odoo_instance_setup.odoo_version) }}' 'additional_addons' \ +{% for module in repo.modules | sort %} + '{{ module }}' \ +{% endfor %} +{% endfor %} +# Get other Odoo modules from other_modules variable. +{% for repo in template_odoo_instance.value.other_modules | default([]) | sort(attribute='url') %} + && get_addons '{{ repo.url }}' '{{ repo.branch | default(template_odoo_instance_setup.odoo_version) }}' 'additional_addons' \ +{% for module in repo.modules | sort %} + '{{ module }}' \ +{% endfor %} +{% endfor %} +# Get Le Filament Odoo modules from custom_modules variable. +{% for repo in template_odoo_instance.value.custom_modules | default([]) | sort(attribute='repo') %} + && get_addons '{{ custom_modules_base_url }}/{{ repo.repo }}.git' '{{ repo.branch | default(template_odoo_instance_setup.odoo_version) }}' 'private_addons/{{ repo.repo | basename }}' \ +{% endfor %} +# Get other Odoo modules from other_repos variable. +{% for repo in template_odoo_instance.value.other_repos | default([]) | sort(attribute='repo') %} + && get_addons '{{ repo.url }}' '{{ repo.branch | default(template_odoo_instance_setup.odoo_version) }}' 'private_addons/{{ repo.repo }}' \ +{% endfor %} + && echo "info: custom Odoo module commits:" \ + && touch $SAVE_COMMITS_DIR/$SAVE_COMMITS_FILENAME \ + && cat $SAVE_COMMITS_DIR/$SAVE_COMMITS_FILENAME + ## Final image {# TODO: make base image a variable #} -FROM lefilament/odoo:{{ instance_odoo_setup.odoo_version }}{% if item.value.odoo_python36 | default(false) %}_py3.6{% endif %}{% if item.value.odoo_multilingual | default(false) %}_ml{% endif %} as final +{% if template_odoo_instance.value.from_image_digest is defined %} +FROM lefilament/odoo@{{ template_odoo_instance.value.from_image_digest }} +{% else %} +FROM lefilament/odoo:{{ template_odoo_instance_setup.odoo_version }}{% if template_odoo_instance.value.odoo_python36 | default(false) %}_py3.6{% endif %}{% if template_odoo_instance.value.odoo_multilingual | default(false) %}_ml{% endif %} as final +{% endif %} + USER root COPY --from=odoo_addons --chown=odoo:odoo /opt/odoo/private_addons /opt/odoo/private_addons COPY --from=odoo_addons --chown=odoo:odoo /opt/odoo/additional_addons /opt/odoo/additional_addons -{% if item.value.odoo_pip_packages is defined %} +COPY --from=odoo_addons --chown=odoo:odoo $SAVE_COMMITS_DIR $SAVE_COMMITS_DIR +{% if template_odoo_instance.value.odoo_pip_packages is defined %} + # Install pip packages -RUN pip install {{ item.value.odoo_pip_packages }} +{% if template_odoo_instance.value.odoo_pip_packages is string %} +RUN pip install {{ template_odoo_instance.value.odoo_pip_packages }} +{% else %} +RUN pip install '{{ template_odoo_instance.value.odoo_pip_packages | sort | join("' '") }}' +{% endif %} {% endif %} -{{ item.value.odoo_specific_dockerfile_commands | default("") }} +{{ template_odoo_instance.value.odoo_specific_dockerfile_commands | default('') }} # Copy Odoo configuration file COPY --chown=odoo:odoo ./odoo.conf /opt/odoo/etc/odoo.conf USER odoo diff --git a/templates/backup.yaml.j2 b/templates/backup.yaml.j2 index 596b72a..60f43b7 100644 --- a/templates/backup.yaml.j2 +++ b/templates/backup.yaml.j2 @@ -2,48 +2,48 @@ version: "2.1" services: backup_odoo: image: ghcr.io/tecnativa/docker-duplicity-postgres:master - hostname: {{ inventory_hostname_short | lower | regex_replace('_','') }}-{{ item.key }} + hostname: {{ inventory_hostname_short | lower | regex_replace('_','') }}-{{ template_odoo_instance.key }} environment: - DST: "swift://{{ item.key }}_{{ inventory_hostname|lower }}" - PGDATABASE: "{{ item.value.db }}" - PGUSER: "{{ item.value.db_user }}" - PGPASSWORD: "{{ item.value.db_pass }}" - PASSPHRASE: "{{ item.value.odoo_backup_pass | default(item.value.master_pass) }}" - SWIFT_USERNAME: "{{ account.username }}" - SWIFT_PASSWORD: "{{ account.password }}" - SWIFT_AUTHURL: "{{ account.authurl }}" - SWIFT_AUTHVERSION: {{ account.authversion }} - SWIFT_TENANTNAME: "{{ account.tenantname }}" - SWIFT_TENANTID: "{{ account.tenantid }}" - SWIFT_REGIONNAME: "{{ account.regionname }}" + DST: "swift://{{ template_odoo_instance.key }}_{{ inventory_hostname | lower }}" + PGDATABASE: "{{ template_odoo_instance.value.db }}" + PGUSER: "{{ template_odoo_instance.value.db_user }}" + PGPASSWORD: "{{ template_odoo_instance.value.db_pass }}" + PASSPHRASE: "{{ template_odoo_instance.value.odoo_backup_pass | default(template_odoo_instance.value.master_pass) }}" + SWIFT_USERNAME: "{{ template_backup_account.value.username }}" + SWIFT_PASSWORD: "{{ template_backup_account.value.password }}" + SWIFT_AUTHURL: "{{ template_backup_account.value.authurl }}" + SWIFT_AUTHVERSION: {{ template_backup_account.value.authversion }} + SWIFT_TENANTNAME: "{{ template_backup_account.value.tenantname }}" + SWIFT_TENANTID: "{{ template_backup_account.value.tenantid }}" + SWIFT_REGIONNAME: "{{ template_backup_account.value.regionname }}" JOB_200_WHEN: "never" JOB_300_WHAT: "pg_dump --no-owner --format c --file $$SRC/$$PGDATABASE.pgdump && backup --full-if-older-than 6D" JOB_302_WHAT: "dup remove-all-but-n-full 5 --force $$DST $$@" JOB_302_WHEN: "daily" volumes: - - {{ item.key }}{{ account_index + 1 }}_backup_cache:/root/.cache/duplicity/:z - - {{ item.key }}_filestore:/mnt/backup/src/odoo:z -{% if item.value.metabase | default(false) %} - - {{ item.key }}_metabase:/mnt/backup/src/metabase:z + - {{ template_odoo_instance.key }}{{ template_backup_account.key }}_backup_cache:/root/.cache/duplicity/:z + - {{ template_odoo_instance.key }}_filestore:/mnt/backup/src/odoo:z +{% if template_odoo_instance.value.metabase | default(false) %} + - {{ template_odoo_instance.key }}_metabase:/mnt/backup/src/metabase:z {% endif %} networks: - - {{ item.key }}_default + - {{ template_odoo_instance.key }}_default - public command: - /etc/periodic/daily/jobrunner networks: - {{ item.key }}_default: + {{ template_odoo_instance.key }}_default: external: true public: driver_opts: encrypted: 1 volumes: - {{ item.key }}{{ account_index + 1 }}_backup_cache: - {{ item.key }}_filestore: + {{ template_odoo_instance.key }}{{ template_backup_account.key }}_backup_cache: + {{ template_odoo_instance.key }}_filestore: external: true -{% if item.value.metabase | default(false) %} - {{ item.key }}_metabase: +{% if template_odoo_instance.value.metabase | default(false) %} + {{ template_odoo_instance.key }}_metabase: external: true {% endif %} diff --git a/templates/docker-compose.yaml.j2 b/templates/docker-compose.yaml.j2 index 0f97dbd..1f42b28 100644 --- a/templates/docker-compose.yaml.j2 +++ b/templates/docker-compose.yaml.j2 @@ -2,26 +2,26 @@ version: "2.1" services: odoo: {# if instance is to be built #} -{% if item.value.image_instance | default(false) == item.key %} +{% if template_instance_need_build %} build: context: ./odoo {% endif %} - image: filament/{{ item.key }}:{{ instance_odoo_setup.odoo_version }} - container_name: {{ item.key }} + image: filament/{{ template_odoo_instance.key }}:{{ template_odoo_instance_setup.odoo_version }} + container_name: {{ template_odoo_instance.key }} depends_on: - db - smtp environment: - PGDATABASE: "{{ item.value.db }}" -{% if item.value.odoo_extra_host is defined %} + PGDATABASE: "{{ template_database_name }}" +{% if template_odoo_instance.value.odoo_extra_host is defined %} extra_hosts: - - "{{ item.value.odoo_extra_host }}" + - "{{ template_odoo_instance.value.odoo_extra_host }}" {% endif %} tty: true volumes: - filestore:/opt/odoo/data:z {# if prod and banking #} -{% if item.value.prod_instance | default(false) == item.key and banking | default(false) %} +{% if template_instance_is_prod and banking | default(false) %} - ./bank/:/ofx/:ro {% endif %} {% if private_pull is defined %} @@ -34,18 +34,18 @@ services: {% if whitelisted_urls is defined %} whitelists_proxy: {% endif %} -{% if item.value.extra_urls is defined %} +{% if template_odoo_instance.value.extra_urls is defined %} extrawhitelists_proxy: {% endif %} {% endif %} {# if prod and mailname #} -{% if item.value.prod_instance | default(false) == item.key and mailname | default(false) %} +{% if template_instance_is_prod and mailname | default(false) %} email_proxy: {% endif %} restart: unless-stopped labels: {# if prod #} -{% if item.value.prod_instance | default(false) == item.key %} +{% if template_instance_is_prod %} co.elastic.logs/multiline.pattern: '^[0-9]{2}' co.elastic.logs/multiline.negate: 'true' co.elastic.logs/multiline.match: "after" @@ -53,54 +53,60 @@ services: co.elastic.logs/enabled: "false" {% endif %} traefik.enable: "true" -{% if ansible_processor_vcpus > 2 or item.value.force_odoo_workers is defined %} - traefik.http.routers.{{ item.key }}-{{ instance_odoo_setup.websocket_uri }}.rule: "Host(`{{ item.value.url }}`{% if item.value.url2 is defined %}, `{{ item.value.url2 }}`{% endif %}) && PathPrefix(`/{{ instance_odoo_setup.websocket_uri }}{{ instance_odoo_setup.websocket_uri_trailing }}`)" - traefik.http.routers.{{ item.key }}-{{ instance_odoo_setup.websocket_uri }}.service: "{{ item.key }}-{{ instance_odoo_setup.websocket_uri }}" - traefik.http.services.{{ item.key }}-{{ instance_odoo_setup.websocket_uri }}.loadbalancer.server.port: "8072" -{% endif %} - traefik.http.routers.{{ item.key }}-restrict.middlewares: "auth@file" - traefik.http.routers.{{ item.key }}-restrict.rule: "Host(`{{ item.value.url }}`{% if item.value.url2 is defined %}, `{{ item.value.url2 }}`{% endif %}) && (Path(`/website/info`) || PathRegexp(`/web/database/(manager|create|duplicate|drop|backup|restore|change_password)`))" - traefik.http.routers.{{ item.key }}-restrict.service: "{{ item.key }}" - traefik.http.routers.{{ item.key }}.rule: "Host(`{{ item.value.url }}`{% if item.value.url2 is defined %}, `{{ item.value.url2 }}`{% endif %})" - traefik.http.routers.{{ item.key }}.service: "{{ item.key }}" - traefik.http.services.{{ item.key }}.loadbalancer.server.port: "8069" +{% if ansible_processor_vcpus > 2 or template_odoo_instance.value.force_odoo_workers is defined %} + traefik.http.services.{{ template_odoo_instance.key }}-{{ template_odoo_instance_setup.websocket_uri }}.loadbalancer.server.port: "8072" + traefik.http.routers.{{ template_odoo_instance.key }}-{{ template_odoo_instance_setup.websocket_uri }}.entrypoints: "{{ template_traefik_entrypoints | default(default_traefik_entrypoints) }}" + traefik.http.routers.{{ template_odoo_instance.key }}-{{ template_odoo_instance_setup.websocket_uri }}.rule: "(Host(`{{ template_odoo_instance_domains | join('`) || Host(`') }}`)) && PathPrefix(`/{{ template_odoo_instance_setup.websocket_uri }}{{ template_odoo_instance_setup.websocket_uri_trailing }}`)" + traefik.http.routers.{{ template_odoo_instance.key }}-{{ template_odoo_instance_setup.websocket_uri }}.service: "{{ template_odoo_instance.key }}-{{ template_odoo_instance_setup.websocket_uri }}" +{% endif %} + traefik.http.services.{{ template_odoo_instance.key }}.loadbalancer.server.port: "8069" + traefik.http.routers.{{ template_odoo_instance.key }}-restrict.entrypoints: "{{ template_traefik_entrypoints | default(default_traefik_entrypoints) }}" + traefik.http.routers.{{ template_odoo_instance.key }}-restrict.rule: "(Host(`{{ template_odoo_instance_domains | join('`) || Host(`') }}`)) && (Path(`/website/info`) || PathRegexp(`/web/database/(manager|create|duplicate|drop|backup|restore|change_password)`))" + traefik.http.routers.{{ template_odoo_instance.key }}-restrict.middlewares: "auth@file" + traefik.http.routers.{{ template_odoo_instance.key }}-restrict.service: "{{ template_odoo_instance.key }}" + traefik.http.routers.{{ template_odoo_instance.key }}.entrypoints: "{{ template_traefik_entrypoints | default(default_traefik_entrypoints) }}" + traefik.http.routers.{{ template_odoo_instance.key }}.rule: "Host(`{{ template_odoo_instance_domains | join('`) || Host(`') }}`)" + traefik.http.routers.{{ template_odoo_instance.key }}.middlewares: "norobot-headers@file" + traefik.http.routers.{{ template_odoo_instance.key }}.service: "{{ template_odoo_instance.key }}" {# if not prod #} -{% if item.value.prod_instance | default(false) != item.key %} - traefik.http.routers.{{ item.key }}.middlewares: "norobot-headers@file" +{% if template_instance_is_prod is false %} command: - odoo - --smtp-port=1025 - - --database={{ item.value.db }} + - --database={{ template_database_name }} +{% if template_db_filter is defined %} + - --db-filter={{ template_db_filter }} +{% endif %} - --init=web_environment_ribbon -{% if ansible_processor_vcpus > 2 or item.value.force_odoo_workers is defined %} +{% if ansible_processor_vcpus > 2 or template_odoo_instance.value.force_odoo_workers is defined %} - --workers=2 - --max-cron-threads=1 {% endif %} {% endif %} db: - image: postgres:{{ instance_odoo_setup.postgres_version }}-alpine - container_name: {{ item.key }}_db + image: postgres:{{ template_odoo_instance_setup.postgres_version }}-alpine + container_name: {{ template_odoo_instance.key }}_db environment: POSTGRES_DB: "postgres" - POSTGRES_USER: "{{ odoo_instances[item.value.prod_instance | default(item.key)].db_user }}" - POSTGRES_PASSWORD: "{{ odoo_instances[item.value.prod_instance | default(item.key)].db_pass }}" -{% if item.value.postgres_options is defined %} - command: {{ item.value.postgres_options }} + POSTGRES_USER: "{{ template_odoo_source_instance.value.db_user }}" + POSTGRES_PASSWORD: "{{ template_odoo_source_instance.value.db_pass }}" +{% if template_odoo_instance.value.postgres_options is defined %} + command: {{ template_odoo_instance.value.postgres_options }} {% endif %} labels: {# if prod #} -{% if item.value.prod_instance | default(false) == item.key %} +{% if template_instance_is_prod %} co.elastic.logs/module: postgresql {% else %} co.elastic.logs/enabled: "false" {% endif %} -{% if item.value.odoo_remote_db_access | default(false) %} +{% if template_odoo_instance.value.odoo_remote_db_access | default(false) %} networks: default: public: ports: - - "{{ item.value.odoo_remote_db_access_port }}:5432" + - "{{ template_odoo_instance.value.odoo_remote_db_access_port }}:5432" {% endif %} volumes: - db:/var/lib/postgresql/data:z @@ -108,9 +114,9 @@ services: smtp: {# if prod and mailname #} -{% if item.value.prod_instance | default(false) == item.key and mailname | default(false) %} +{% if template_instance_is_prod and mailname | default(false) %} image: tecnativa/postfix-relay - container_name: {{ item.key }}_smtp + container_name: {{ template_odoo_instance.key }}_smtp networks: email_proxy: volumes: @@ -129,7 +135,7 @@ services: {% if imap_mailserver is defined %} email_imap: image: tecnativa/whitelist - container_name: {{ item.key }}_imap + container_name: {{ template_odoo_instance.key }}_imap labels: co.elastic.logs/enabled: "false" networks: @@ -146,7 +152,7 @@ services: {% endif %} email: image: tecnativa/whitelist - container_name: {{ item.key }}_email + container_name: {{ template_odoo_instance.key }}_email labels: co.elastic.logs/enabled: "false" networks: @@ -163,29 +169,29 @@ services: {% endif %} {% else %} image: mailhog/mailhog - container_name: {{ item.key }}_smtp + container_name: {{ template_odoo_instance.key }}_smtp restart: unless-stopped labels: co.elastic.logs/enabled: "false" {# if not prod #} -{% if item.value.prod_instance | default(false) != item.key %} +{% if template_instance_is_prod is false %} traefik.docker.network: "inverseproxy_smtp" traefik.enable: "true" - traefik.http.routers.{{ item.key }}smtp.middlewares: "auth@file, smtp-stripprefix@file" - traefik.http.routers.{{ item.key }}smtp.rule: "Host(`{{ item.value.url }}`) && PathPrefix(`/smtp/`)" - traefik.http.routers.{{ item.key }}smtp.service: "{{ item.key }}smtp" - traefik.http.services.{{ item.key }}smtp.loadbalancer.server.port: "8025" + traefik.http.routers.{{ template_odoo_instance.key }}smtp.middlewares: "auth@file, smtp-stripprefix@file" + traefik.http.routers.{{ template_odoo_instance.key }}smtp.rule: "(Host(`{{ template_odoo_instance_domains | join('`) || Host(`') }}`)) && PathPrefix(`/smtp/`)" + traefik.http.routers.{{ template_odoo_instance.key }}smtp.service: "{{ template_odoo_instance.key }}smtp" + traefik.http.services.{{ template_odoo_instance.key }}smtp.loadbalancer.server.port: "8025" networks: default: inverseproxy_smtp: {% endif %} {% endif %} -{% if restrict_internet_access and item.value.extra_urls is defined %} -{% for server in item.value.extra_urls %} +{% if restrict_internet_access and template_odoo_instance.value.extra_urls is defined %} +{% for server in template_odoo_instance.value.extra_urls %} {{ server.url }}: image: tecnativa/whitelist - container_name: {{ item.key }}_{{ server.url }} + container_name: {{ template_odoo_instance.key }}_{{ server.url }} labels: co.elastic.logs/enabled: "false" networks: @@ -201,10 +207,10 @@ services: {% endfor %} {% endif %} -{% if item.value.metabase | default(false) %} +{% if template_odoo_instance.value.metabase | default(false) %} metabase: image: metabase/metabase - container_name: {{ item.key }}_metabase + container_name: {{ template_odoo_instance.key }}_metabase environment: MB_DB_FILE: "/metabase-data/metabase.db" networks: @@ -216,7 +222,7 @@ services: traefik.enable: "true" traefik.http.middlewares.metabase-stripprefix.stripprefix.prefixes: "/metabase" traefik.http.routers.metabase.middlewares: "metabase-stripprefix" - traefik.http.routers.metabase.rule: "Host(`{{ item.value.url }}`) && PathPrefix(`/metabase`)" + traefik.http.routers.metabase.rule: "(Host(`{{ template_odoo_instance_domains | join('`) || Host(`') }}`)) && PathPrefix(`/metabase`)" traefik.http.routers.metabase.service: "metabase" traefik.http.services.metabase.loadbalancer.server.port: "3000" restart: unless-stopped @@ -224,10 +230,10 @@ services: - metabase:/metabase-data:z {% endif %} -{% if item.value.extra_app is defined %} +{% if template_odoo_instance.value.extra_app is defined %} app: - image: {{ item.value.extra_app.image }} - container_name: {{ item.value.extra_app.name }} + image: {{ template_odoo_instance.value.extra_app.image }} + container_name: {{ template_odoo_instance.value.extra_app.name }} restart: unless-stopped networks: inverseproxy_app: @@ -235,9 +241,9 @@ services: co.elastic.logs/enabled: "false" traefik.docker.network: "inverseproxy_app" traefik.enable: "true" - traefik.http.routers.{{ item.value.extra_app.name | lower | regex_replace('_','') }}.rule: "Host(`{{ item.value.extra_app.url }}`)" - traefik.http.routers.{{ item.value.extra_app.name | lower | regex_replace('_','') }}.service: "{{ item.value.extra_app.name | lower | regex_replace('_','') }}" - traefik.http.services.{{ item.value.extra_app.name | lower | regex_replace('_','') }}.loadbalancer.server.port: "80" + traefik.http.routers.{{ template_odoo_instance.value.extra_app.name | lower | regex_replace('_','') }}.rule: "Host(`{{ template_odoo_instance.value.extra_app.url }}`)" + traefik.http.routers.{{ template_odoo_instance.value.extra_app.name | lower | regex_replace('_','') }}.service: "{{ template_odoo_instance.value.extra_app.name | lower | regex_replace('_','') }}" + traefik.http.services.{{ template_odoo_instance.value.extra_app.name | lower | regex_replace('_','') }}.loadbalancer.server.port: "80" {% endif %} networks: @@ -248,15 +254,15 @@ networks: inverseproxy_shared: external: true {# if not prod #} -{% if item.value.prod_instance | default(false) != item.key %} +{% if template_instance_is_prod is false %} inverseproxy_smtp: external: true {% endif %} -{% if item.value.extra_app is defined %} +{% if template_odoo_instance.value.extra_app is defined %} inverseproxy_app: external: true {% endif %} -{% if item.value.metabase | default(false) %} +{% if template_odoo_instance.value.metabase | default(false) %} inverseproxy_bi: external: true {% endif %} @@ -265,7 +271,7 @@ networks: whitelists_proxy: external: true {% endif %} -{% if item.value.extra_urls is defined %} +{% if template_odoo_instance.value.extra_urls is defined %} extrawhitelists_proxy: driver_opts: encrypted: 1 @@ -276,7 +282,7 @@ networks: {% endif %} {% endif %} {# if prod and mailname #} -{% if item.value.prod_instance | default(false) == item.key and mailname | default(false) %} +{% if template_instance_is_prod and mailname | default(false) %} email_proxy: driver_opts: encrypted: 1 @@ -287,7 +293,7 @@ networks: encrypted: 1 {% endif %} {% endif %} -{% if item.value.odoo_remote_db_access | default(false) %} +{% if template_odoo_instance.value.odoo_remote_db_access | default(false) %} public: {% endif %} @@ -295,9 +301,9 @@ volumes: filestore: db: {# if prod and mailname #} -{% if item.value.prod_instance | default(false) == item.key and mailname | default(false) %} +{% if template_instance_is_prod and mailname | default(false) %} smtp: {% endif %} -{% if item.value.metabase | default(false) %} +{% if template_odoo_instance.value.metabase | default(false) %} metabase: {% endif %} diff --git a/templates/fetch_repos b/templates/fetch_repos index 79f3740..2487725 100755 --- a/templates/fetch_repos +++ b/templates/fetch_repos @@ -1,4 +1,4 @@ -{% if instance_odoo_setup.odoo_version == '10.0' %} +{% if odoo_instance_setup.odoo_version == '10.0' %} #!/usr/bin/env python {% else %} #!/usr/bin/env python3 @@ -33,7 +33,7 @@ if os.path.isfile(REPO_FILE): ], check=True) -{% if instance_odoo_setup.odoo_version == '10.0' %} +{% if odoo_instance_setup.odoo_version == '10.0' %} if os.path.isdir("/opt/odoo/private_addons/lf_theme"): if not os.path.isfile("/opt/odoo/private_addons/lf_theme/static\ /src/less/lefilament_variable.less"): diff --git a/templates/fetch_repos_addons b/templates/fetch_repos_addons index 8b3eee5..afbd014 100755 --- a/templates/fetch_repos_addons +++ b/templates/fetch_repos_addons @@ -1,4 +1,4 @@ -{% if instance_odoo_setup.odoo_version == '10.0' %} +{% if odoo_instance_setup.odoo_version == '10.0' %} #!/usr/bin/env python {% else %} #!/usr/bin/env python3 @@ -10,7 +10,7 @@ import os import pwd import shutil import subprocess -{% if instance_odoo_setup.odoo_version == '10.0' %} +{% if odoo_instance_setup.odoo_version == '10.0' %} from urlparse import urlsplit {% else %} from urllib.parse import urlsplit diff --git a/templates/odoo.conf.j2 b/templates/odoo.conf.j2 index e63c123..a540a7f 100644 --- a/templates/odoo.conf.j2 +++ b/templates/odoo.conf.j2 @@ -1,9 +1,9 @@ [options] ;; COMMON OPTIONS addons_path = /opt/odoo/odoo/addons,/opt/odoo/additional_addons,/opt/odoo/private_addons -admin_passwd = {{ odoo_instances[item.value.prod_instance | default(item.key )].master_pass | pbkdf2_passwd(65534 | random(seed=inventory_hostname) | string) }} +admin_passwd = {{ template_admin_passwd }} data_dir = /opt/odoo/data -server_wide_modules = {{ odoo_setup_conf[item.value.odoo_setup_version | default(odoo_setup_version)].server_wide_modules }}{% if item.value.odoo_server_wide_modules is defined %},{{ item.value.odoo_server_wide_modules }}{% endif %} +server_wide_modules = {{ template_server_wide_modules }} without_demo = all @@ -16,7 +16,7 @@ proxy_mode = True ; x_sendfile = False ;; Web interface Configuration -; dbfilter = +dbfilter = {{ template_dbfilter }} ;; Logging Configuration ; log_db = False @@ -42,17 +42,17 @@ smtp_server = smtp ; smtp_user = False ;; Database related options -db_name = {{ odoo_instances[item.value.prod_instance | default(item.key )].db }} -db_password = {{ odoo_instances[item.value.prod_instance | default(item.key )].db_pass }} -db_user = {{ odoo_instances[item.value.prod_instance | default(item.key )].db_user }} +db_name = {{ template_db_name }} +db_password = {{ template_db_password }} +db_user = {{ template_db_user }} ; db_template = template0 db_host = db -db_maxconn = {{ item.value.odoo_db_maxconn | default(64) }} +db_maxconn = {{ template_db_maxconn | default(64) }} ; db_sslmode = prefer ; pg_path = ;; Security-related options -; list_db = True +list_db = False ;; Advanced options ; geoip_database = /usr/share/GeoIP/GeoLite2-City.mmdb @@ -64,10 +64,10 @@ unaccent = True ; limit_memory_hard = 2684354560 ; limit_memory_soft = 2147483648 ; limit_request = 65536 -limit_time_cpu = {{ item.value.odoo_limit_time_cpu | default(300) }} -limit_time_real = {{ item.value.odoo_limit_time_real | default(600) }} +limit_time_cpu = {{ template_limit_time_cpu | default(300) }} +limit_time_real = {{ template_limit_time_real | default(600) }} ; limit_time_real_cron = -1 -{% if ansible_processor_vcpus > 2 or item.value.force_odoo_workers is defined %} +{% if ansible_processor_vcpus > 2 or template_force_workers is defined %} max_cron_threads = {{ ansible_processor_vcpus | int }} workers = {{ (ansible_processor_vcpus * 2 - 1) | int | abs }} {% else %} @@ -76,12 +76,12 @@ workers = {{ (ansible_processor_vcpus * 2 - 1) | int | abs }} {% endif %} ;; Autoinstall options -modules_auto_install_disabled = {{ odoo_setup_conf[item.value.odoo_setup_version | default(odoo_setup_version)].modules_auto_install_disabled | default("mail_bot") }}{% if item.value.modules_auto_install_disabled is defined %},{{ item.value.modules_auto_install_disabled }}{% endif %} +modules_auto_install_disabled = {{ template_modules_auto_install_disabled }} -modules_auto_install_enabled = {{ odoo_setup_conf[item.value.odoo_setup_version | default(odoo_setup_version)].modules_auto_install_enabled | default("web") }}{% if item.value.modules_auto_install_enabled is defined %},{{ item.value.modules_auto_install_enabled }}{% endif %} +modules_auto_install_enabled = {{ template_modules_auto_install_enabled }} -{% if item.value.odoo_extra_conf is defined %} +{% if template_extra_conf is defined %} ;; Extra options -{{ item.value.odoo_extra_conf }} +{{ template_extra_conf }} {% endif %} diff --git a/templates/pre_restore-odootest.sql.j2 b/templates/pre_restore-odootest.sql.j2 index 9c79073..c9882d6 100644 --- a/templates/pre_restore-odootest.sql.j2 +++ b/templates/pre_restore-odootest.sql.j2 @@ -1,3 +1,3 @@ -REVOKE CONNECT ON DATABASE {{ item.value.db }} FROM public; -SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '{{ item.value.db }}'; -DROP DATABASE {{ item.value.db }}; +REVOKE CONNECT ON DATABASE {{ template_database_name }} FROM public; +SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '{{ template_database_name }}'; +DROP DATABASE {{ template_database_name }}; diff --git a/templates/repos-addons.yaml.j2 b/templates/repos-addons.yaml.j2 index c4121f0..20b6a20 100644 --- a/templates/repos-addons.yaml.j2 +++ b/templates/repos-addons.yaml.j2 @@ -3,7 +3,7 @@ {% if repo.branch is defined %} branch: "{{ repo.branch }}" {% else %} - branch: "{{ instance_odoo_setup.odoo_version }}" + branch: "{{ odoo_instance_setup.odoo_version }}" {% endif %} modules: {% for modules in repo.modules | sort %} @@ -15,7 +15,7 @@ https://github.com/OCA/{{ repo.repo }}.git: {% if repo.branch is defined %} branch: "{{ repo.branch }}" {% else %} - branch: "{{ instance_odoo_setup.odoo_version }}" + branch: "{{ odoo_instance_setup.odoo_version }}" {% endif %} modules: {% for modules in repo.modules | sort %} diff --git a/templates/repos.yaml.j2 b/templates/repos.yaml.j2 index 8dbc06a..8b676bc 100644 --- a/templates/repos.yaml.j2 +++ b/templates/repos.yaml.j2 @@ -4,7 +4,7 @@ {% if repo.branch is defined %} branch: "{{ repo.branch }}" {% else %} - branch: "{{ instance_odoo_setup.odoo_version }}" + branch: "{{ odoo_instance_setup.odoo_version }}" {% endif %} {% endfor %} {% for repo in item.value.other_repos | default([]) | sort(attribute='repo') %} @@ -13,6 +13,6 @@ {% if repo.branch is defined %} branch: "{{ repo.branch }}" {% else %} - branch: "{{ instance_odoo_setup.odoo_version }}" + branch: "{{ odoo_instance_setup.odoo_version }}" {% endif %} {% endfor %} diff --git a/templates/restore-odootest.yaml.j2 b/templates/restore-odootest.yaml.j2 index f872f67..c12b35f 100644 --- a/templates/restore-odootest.yaml.j2 +++ b/templates/restore-odootest.yaml.j2 @@ -2,42 +2,42 @@ version: "2.1" services: restore_test: image: ghcr.io/tecnativa/docker-duplicity-postgres:master - hostname: {{ item.value.backup_host | default(inventory_hostname_short) | lower | regex_replace('_','') }}-odoo + hostname: {{ template_odoo_instance.value.backup_host | default(inventory_hostname_short) | lower | regex_replace('_','') }}-odoo networks: - - {{ item.key }}_default + - {{ template_odoo_instance.key }}_default - public volumes: - - backups_{{ item.value.backup_instance }}{{ account_index + 1 }}_backup_cache:/root/.cache/duplicity/:z - - {{ item.key }}_filestore:/mnt/backup/src/odoo:z - - ./post_restore-{{ item.key }}.sql:/tmp/post-restore.sql:ro - - ./pre_restore-{{ item.key }}.sql:/tmp/pre-restore.sql:ro + - backups_{{ template_odoo_instance.value.backup_instance }}{{ template_backup_account.key }}_backup_cache:/root/.cache/duplicity/:z + - {{ template_odoo_instance.key }}_filestore:/mnt/backup/src/odoo:z + - ./post_restore-{{ template_odoo_instance.key }}.sql:/tmp/post-restore.sql:ro + - ./pre_restore-{{ template_odoo_instance.key }}.sql:/tmp/pre-restore.sql:ro environment: - PGDATABASE: "{{ item.value.db }}" - SWIFT_USERNAME: "{{ account.username }}" - SWIFT_PASSWORD: "{{ account.password }}" - SWIFT_AUTHURL: "{{ account.authurl }}" - SWIFT_AUTHVERSION: {{ account.authversion }} - SWIFT_TENANTNAME: "{{ account.tenantname }}" - SWIFT_TENANTID: "{{ account.tenantid }}" - SWIFT_REGIONNAME: "{{ account.regionname }}" + PGDATABASE: "{{ template_database_name }}" + SWIFT_USERNAME: "{{ template_backup_account.value.username }}" + SWIFT_PASSWORD: "{{ template_backup_account.value.password }}" + SWIFT_AUTHURL: "{{ template_backup_account.value.authurl }}" + SWIFT_AUTHVERSION: {{ template_backup_account.value.authversion }} + SWIFT_TENANTNAME: "{{ template_backup_account.value.tenantname }}" + SWIFT_TENANTID: "{{ template_backup_account.value.tenantid }}" + SWIFT_REGIONNAME: "{{ template_backup_account.value.regionname }}" OPTIONS: "--force" - DST: "swift://{{ item.value.backup_instance }}_{{ item.value.backup_host | default(inventory_hostname) | lower }}" - PGUSER: "{{ odoo_instances[item.value.prod_instance | default(item.key )].db_user }}" - PGPASSWORD: "{{ odoo_instances[item.value.prod_instance | default(item.key )].db_pass }}" - PASSPHRASE: "{{ hostvars[item.value.backup_host | default(inventory_hostname)]['odoo_instances'][item.value.backup_instance].odoo_backup_pass | default(hostvars[item.value.backup_host | default(inventory_hostname)]['odoo_instances'][item.value.backup_instance].master_pass) }}" - command: [sh, -c, "psql -a -f /tmp/pre-restore.sql postgres ; echo 'remove existing dir' && rm -rf /mnt/backup/src/odoo/filestore/$$PGDATABASE && restore && echo 'move repo to final dest' && mv /mnt/backup/src/odoo/filestore/{{ hostvars[item.value.backup_host | default(inventory_hostname)]['odoo_instances'][item.value.backup_instance].db }} /mnt/backup/src/odoo/filestore/$$PGDATABASE && echo 'create database' && createdb -T template0 $$PGDATABASE && echo 'restore database' && pg_restore --jobs $$(nproc) -d $$PGDATABASE $$SRC/{{ hostvars[item.value.backup_host | default(inventory_hostname)]['odoo_instances'][item.value.backup_instance].db }}.pgdump ; psql -a -f /tmp/post-restore.sql $$PGDATABASE"] + DST: "swift://{{ template_odoo_instance.value.backup_instance }}_{{ template_odoo_instance.value.backup_host | default(inventory_hostname) | lower }}" + PGUSER: "{{ template_odoo_source_instance.value.db_user }}" + PGPASSWORD: "{{ template_odoo_source_instance.value.db_pass }}" + PASSPHRASE: "{{ hostvars[template_odoo_instance.value.backup_host | default(inventory_hostname)]['odoo_instances'][template_odoo_instance.value.backup_instance].odoo_backup_pass | default(hostvars[template_odoo_instance.value.backup_host | default(inventory_hostname)]['odoo_instances'][template_odoo_instance.value.backup_instance].master_pass) }}" + command: [sh, -c, "psql -a -f /tmp/pre-restore.sql postgres ; echo 'remove existing dir' && rm -rf /mnt/backup/src/odoo/filestore/$$PGDATABASE && restore && echo 'move repo to final dest' && mv /mnt/backup/src/odoo/filestore/{{ hostvars[template_odoo_instance.value.backup_host | default(inventory_hostname)]['odoo_instances'][template_odoo_instance.value.backup_instance].db }} /mnt/backup/src/odoo/filestore/$$PGDATABASE && echo 'create database' && createdb -T template0 $$PGDATABASE && echo 'restore database' && pg_restore -d $$PGDATABASE $$SRC/{{ hostvars[template_odoo_instance.value.backup_host | default(inventory_hostname)]['odoo_instances'][template_odoo_instance.value.backup_instance].db }}.pgdump ; psql -a -f /tmp/post-restore.sql $$PGDATABASE"] networks: - {{ item.key }}_default: + {{ template_odoo_instance.key }}_default: external: true public: driver_opts: encrypted: 1 volumes: - backups_{{ item.value.backup_instance }}{{ account_index + 1 }}_backup_cache: -{% if item.value.backup_host | default(inventory_hostname) == inventory_hostname %} + backups_{{ template_odoo_instance.value.backup_instance }}{{ template_backup_account.key }}_backup_cache: +{% if template_odoo_instance.value.backup_host | default(inventory_hostname) == inventory_hostname %} external: true {% endif %} - {{ item.key }}_filestore: + {{ template_odoo_instance.key }}_filestore: external: true diff --git a/vars/main.yml b/vars/main.yml new file mode 100644 index 0000000..8797626 --- /dev/null +++ b/vars/main.yml @@ -0,0 +1,28 @@ +allow_pull: "{{ pull is undefined or pull is truthy(convert_bool=True) }}" +allow_rebuild: "{{ rebuild is undefined or rebuild is truthy(convert_bool=True) }}" +allow_remove_key: "{{ remove_key is undefined or remove_key is truthy(convert_bool=True) }}" +allow_restart: "{{ restart is undefined or restart is truthy(convert_bool=True) }}" +odoo_instance_noprod: "{{ odoo_instance_kind is undefined or odoo_instance_kind == 'all' or odoo_instance_kind == 'noprod' }}" +odoo_instance_prod: "{{ odoo_instance_kind is undefined or odoo_instance_kind == 'all' or odoo_instance_kind == 'prod' }}" + +odoo_instance: "{{ {'key': item, 'value': odoo_instances[item]} if item is string else item }}" +odoo_instance_setup: "{{ odoo_setup_conf[odoo_instance.value.odoo_setup_version | default(odoo_setup_version)] }}" +odoo_instance_version: "{{ odoo_instance_setup.odoo_version }}" +odoo_instance_domains: "{{ [odoo_instance.value.url] if odoo_instance.value.url is defined else [odoo_instance.value.domains] if odoo_instance.value.domains is string else odoo_instance.value.domains }}" +odoo_source_instance_name: "{{ odoo_instance.value.prod_instance | default(odoo_instance.key ) }}" +odoo_source_instance: "{{ {'key': odoo_source_instance_name, 'value': odoo_instances[odoo_source_instance_name]} }}" +odoo_source_instance_setup: "{{ odoo_setup_conf[odoo_source_instance.value.odoo_setup_version | default(odoo_setup_version)] }}" +odoo_image_instance_name: "{{ odoo_instance.value.image_instance | default(odoo_instance.key ) }}" +odoo_image_instance: "{{ {'key': odoo_image_instance_name, 'value': odoo_instances[odoo_image_instance_name]} }}" +odoo_image_instance_setup: "{{ odoo_setup_conf[odoo_image_instance.value.odoo_setup_version | default(odoo_setup_version)] }}" + +# Conditions +test_result_item_has_changed: "{{ item.item is defined and item.changed is defined and item.changed is true }}" + +test_instance_is_prod: "{{ odoo_instance.value.prod_instance | default(false) == odoo_instance.key }}" +test_instance_is_prod_selected: "{{ odoo_instance_prod and test_instance_is_prod }}" +test_instance_is_noprod_selected: "{{ odoo_instance_noprod and test_instance_is_prod is false }}" + +test_instance_is_selected: "{{ (test_instance_is_prod_selected or test_instance_is_noprod_selected) and (selected_odoo_instance is undefined or selected_odoo_instance == odoo_instance.key) and (selected_odoo_version is undefined or odoo_instance_version in selected_odoo_version) }}" + +test_instance_need_build: "{{ odoo_instance.value.image_instance | default(false) == odoo_instance.key }}" -- GitLab