diff --git a/.ansible-lint b/.ansible-lint index 8d40d067c46c42adf88f5922bc7fc9c6407ce7bb..b22a3e652f8085a7a1390743c216b2f5778a1a08 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 fbebdb8c3eabfec06b071490a73331e2e80152fe..b66ea0c470037dfed0827d2b1345f7fcc7a49a06 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 0b0524378a07183546bd932a702ce4e086d1a650..79dd9656a6ae2cedd2d5083b5f18b10e1c3331a4 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 c73def4266cbcc567d9686ad555ab2e76f882fd8..babc4a5b26059af4fbfec2fb28c9829dfec7c19c 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 67f632a9f5816c925765741d929acfff5a6359ee..c79f3d65ec82044ba3a5f0653736a5e94e4cf23a 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 4c87319927f352e15c6917f39a1e0474453d5359..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..225bc98204db025ed60fdb3979b9065cadbbcbab --- /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 430b6ce113510ac7579cc98a71e702de75016d7a..c8a003a27e1f8b9873ef12535be597dd2b2cda2a 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 2704bb56f2d8e5667e08b7ccd3416649041869a1..7160e96b38a9d499988608211d3fb1d8c54e573d 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 596b72a0b022bfe67a0b261e8692120312465381..60f43b790b7aef2deccfa19e19aae08057444c3e 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 0f97dbdec2eb0f806d6f151f47891ef03940e9ea..1f42b28bc06bb9fcd33dae26d692c17cd14bf936 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 79f37408ea7329efb78dd10b045aea029e3be374..2487725d61196a4e7761ea56be06356516f23d5f 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 8b3eee55ddf965b56fe02d15ee46ea21138d3994..afbd0140146f46dbba0318e9b98b8c15ed7e89e3 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 e63c123bdc594404dfd1c17a438070d0bc12e9fe..a540a7fd0df066cd2d7cdfb88c3850f4eca04c46 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 9c79073b75e21887103a540b0faa3917c80d93ec..c9882d6eddd0402d550dad5c45f76c92853673d0 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 c4121f07b73e409acbcce196c15381e0cf51e0e7..20b6a2069ff8ad3a717fc073e48d74916c470b95 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 8dbc06a1b0b8feaaf18e4c76a7791740e105bf98..8b676bc49271b2a778d04cea2b0460338548fb6e 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 f872f6790895ba03f382d2fe8be2558aaf45985d..c12b35f102f9d9a9db6757f07542db6f628ebb49 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 0000000000000000000000000000000000000000..87976262906661ee5ba5ac4f609dbaa82cd726ea --- /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 }}"