diff --git a/docker/Dockerfile.backend b/docker/Dockerfile.backend index 75e893f5..58f06b37 100644 --- a/docker/Dockerfile.backend +++ b/docker/Dockerfile.backend @@ -72,8 +72,8 @@ RUN useradd awx -u 1000 -g 0 --home-dir /var/lib/awx -s /bin/bash RUN python3.12 -m venv /venv RUN /venv/bin/pip install --upgrade pip wheel RUN mkdir /code /code/src -COPY requirements.txt /code -RUN /venv/bin/pip install -r /code/requirements.txt +COPY requirements-build.txt /code +RUN /venv/bin/pip install -r /code/requirements-build.txt # source .env COPY src/backend /code/src/backend diff --git a/requirements-build.txt b/requirements-build.txt index a4d352a8..7bf8b35e 100644 --- a/requirements-build.txt +++ b/requirements-build.txt @@ -1258,6 +1258,9 @@ uvicorn==0.38.0 \ --hash=sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02 \ --hash=sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d # via -r requirements-pinned.txt +uwsgi==2.0.31 \ + --hash=sha256:e8f8b350ccc106ff93a65247b9136f529c14bf96b936ac5b264c6ff9d0c76257 + # via -r requirements-pinned.txt weasyprint==66.0 \ --hash=sha256:82b0783b726fcd318e2c977dcdddca76515b30044bc7a830cc4fbe717582a6d0 \ --hash=sha256:da71dc87dc129ac9cffdc65e5477e90365ab9dbae45c744014ec1d06303dde40 diff --git a/requirements-pinned.txt b/requirements-pinned.txt index 490d7e97..c088c2f5 100644 --- a/requirements-pinned.txt +++ b/requirements-pinned.txt @@ -23,9 +23,12 @@ split-settings==1.0.0 urllib3==2.5.0 uvicorn==0.38.0 +# Deployment dependencies +uwsgi==2.0.31 + # Development dependencies pytest-cov==7.0.0 pytest-django==4.11.1 pytest-mock==3.15.1 pytest==9.0.0 -time-machine==2.19.0 \ No newline at end of file +time-machine==2.19.0 diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/playbooks/dashboard_install.yml b/setup/collections/ansible_collections/ansible/containerized_installer/playbooks/dashboard_install.yml index c6e66561..d1792ea1 100644 --- a/setup/collections/ansible_collections/ansible/containerized_installer/playbooks/dashboard_install.yml +++ b/setup/collections/ansible_collections/ansible/containerized_installer/playbooks/dashboard_install.yml @@ -18,7 +18,7 @@ name: preflight - name: Install common container components - hosts: automationcontroller:automationeda:automationhub:database:execution_nodes:automationdashboard + hosts: automationcontroller:automationeda:automationhub:database:execution_nodes:automationdashboard:redis any_errors_fatal: true gather_facts: false become: false @@ -61,6 +61,45 @@ tasks_from: conf.yml when: not dashboard_pg_containerized +- name: Install redis cluster cache + hosts: redis + any_errors_fatal: true + gather_facts: false + become: false + tasks: + - name: Install and configure redis cluster tcp socket + ansible.builtin.include_role: + name: redis + vars: + redis_unix_socket: false + redis_cluster: true + when: redis_mode | default('cluster') == 'cluster' + +- name: Install the redis cache + hosts: automationcontroller:automationeda:automationgateway:automationhub:automationdashboard + any_errors_fatal: true + gather_facts: false + become: false + tasks: + - name: Install and configure redis unix socket + ansible.builtin.include_role: + name: redis + when: > + inventory_hostname in groups.get('automationcontroller', []) or + inventory_hostname in groups.get('automationhub', []) or + (inventory_hostname in groups.get('automationeda', []) and groups.get('automationeda', []) | length == 1) or + (inventory_hostname in groups.get('automationdashboard', []) and groups.get('automationdashboard', []) | length == 1) + + # group automationgateway in not defined when installing only automationdashboard + # - name: Install and configure redis tcp socket + # ansible.builtin.include_role: + # name: redis + # vars: + # redis_unix_socket: false + # when: + # - redis_mode | default('cluster') == 'standalone' + # - inventory_hostname == groups['automationgateway'] | first + - name: Install the Automation Dashboard any_errors_fatal: true hosts: automationdashboard diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/playbooks/dashboard_uninstall.yml b/setup/collections/ansible_collections/ansible/containerized_installer/playbooks/dashboard_uninstall.yml index 2ddf6cef..1cc28e68 100644 --- a/setup/collections/ansible_collections/ansible/containerized_installer/playbooks/dashboard_uninstall.yml +++ b/setup/collections/ansible_collections/ansible/containerized_installer/playbooks/dashboard_uninstall.yml @@ -1,6 +1,6 @@ --- - name: Collect services facts - hosts: automationcontroller:automationeda:automationhub:database:execution_nodes:automationdashboard + hosts: automationcontroller:automationeda:automationhub:database:execution_nodes:automationdashboard:redis gather_facts: false become: false tasks: @@ -24,6 +24,47 @@ name: automationdashboard tasks_from: uninstall.yml +- name: Uninstall redis cluster + hosts: redis + any_errors_fatal: true + gather_facts: false + become: false + tasks: + - name: Uninstall redis cluster tcp socket + ansible.builtin.include_role: + name: redis + tasks_from: uninstall.yml + vars: + redis_unix_socket: false + redis_cluster: true + when: redis_mode | default('cluster') == 'cluster' + +- name: Uninstall redis + hosts: automationcontroller:automationeda:automationgateway:automationhub:automationdashboard + any_errors_fatal: true + gather_facts: false + become: false + tasks: + - name: Uninstall redis unix socket + ansible.builtin.include_role: + name: redis + tasks_from: uninstall.yml + when: > + inventory_hostname in groups.get('automationcontroller', []) or + inventory_hostname in groups.get('automationhub', []) or + (inventory_hostname in groups.get('automationeda', []) and groups.get('automationeda', []) | length == 1) or + (inventory_hostname in groups.get('automationdashboard', []) and groups.get('automationdashboard', []) | length == 1) + + # - name: Uninstall redis tcp socket + # ansible.builtin.include_role: + # name: redis + # tasks_from: uninstall.yml + # vars: + # redis_unix_socket: false + # when: + # - redis_mode | default('cluster') == 'standalone' + # - inventory_hostname == groups['automationgateway'] | first + - name: Uninstall the database hosts: database gather_facts: false diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/automationdashboard/tasks/facts.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/automationdashboard/tasks/facts.yml index 4600f3cf..20787f15 100644 --- a/setup/collections/ansible_collections/ansible/containerized_installer/roles/automationdashboard/tasks/facts.yml +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/automationdashboard/tasks/facts.yml @@ -54,7 +54,7 @@ - '{{ dashboard_conf_dir }}/launch_dashboard_web.sh:/usr/bin/launch_dashboard_web.sh:ro,z' # - '{{ receptor_conf_dir }}/receptor.conf:/etc/receptor/receptor.conf:ro,z' # - 'receptor_run:/run/receptor:U' - # - 'redis_run:/run/redis:z' + - 'redis_run:/run/redis:z' # - '{{ rsyslog_run_dir }}:/run/awx-rsyslog:z' - '{{ supervisor_run_dir }}:/run/supervisor:z' - '{{ dispatcher_run_dir }}:/run/dispatcher:z' diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/automationdashboard/templates/django_dashboard_conf.py.j2 b/setup/collections/ansible_collections/ansible/containerized_installer/roles/automationdashboard/templates/django_dashboard_conf.py.j2 index d913cd53..fa457289 100644 --- a/setup/collections/ansible_collections/ansible/containerized_installer/roles/automationdashboard/templates/django_dashboard_conf.py.j2 +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/automationdashboard/templates/django_dashboard_conf.py.j2 @@ -1,3 +1,6 @@ +with open("/etc/dashboard/SECRET_KEY") as fin: + SECRET_KEY = fin.read() + ALLOWED_HOSTS=["*"] CORS_ALLOWED_ORIGINS = [ "http://localhost:8080", @@ -35,6 +38,8 @@ INITIAL_SYNC_SINCE = "{{ initial_sync_since }}" DISPATCHERD_DEBUGGING_SOCKFILE = "/run/dispatcher/automation-dashboard-dispatcher.sock" +BROKER_URL = 'unix:///run/redis/redis.sock' + SHOW_URLLIB3_INSECURE_REQUEST_WARNING = {{ show_urllib3_insecure_request_warning | default(True) }} # django DEBUG=0 DEBUG = {{ django_debug | default(False) }} diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/defaults/main.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/defaults/main.yml new file mode 100644 index 00000000..7fbe5ba8 --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/defaults/main.yml @@ -0,0 +1,20 @@ +--- +### container +container_log_driver: journald + +### common +archive_compressed_extension: "tar.gz" +archive_decompressed_extension: "tar" + +### backup/restore +redis_use_archive_compression: "{{ use_archive_compression | default(true) }}" + +redis_conf_dir: '{{ ansible_user_dir }}/aap/redis' +redis_disable_tls: false +redis_firewall_zone: public +redis_port: 6379 +redis_unix_socket: true +redis_cluster: false +redis_cluster_replicas: 1 +redis_cluster_port: 16379 +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/handlers/main.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/handlers/main.yml new file mode 100644 index 00000000..03febe9a --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Restart redis + ansible.builtin.systemd: + name: 'redis-{{ _redis_suffix }}.service' + scope: user + state: restarted +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/backup.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/backup.yml new file mode 100644 index 00000000..f42ac0ae --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/backup.yml @@ -0,0 +1,16 @@ +--- +- name: Archive the redis data + ansible.containerized_installer.archive: + path: + - '{{ ansible_user_dir }}/aap/redis' + - '{{ ansible_user_dir }}/aap/tls' + dest: '{{ ansible_user_dir }}/aap/backups/redis.{{ redis_archive_extension }}' + format: '{{ redis_use_archive_compression | bool | ternary(omit, archive_decompressed_extension) }}' + mode: '0640' + +- name: Download the redis tarball + ansible.builtin.fetch: + src: '{{ ansible_user_dir }}/aap/backups/redis.{{ redis_archive_extension }}' + dest: '{{ hostvars["localhost"]["_backup_dir"] }}/redis_{{ inventory_hostname }}.{{ redis_archive_extension }}' + flat: true +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/cluster.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/cluster.yml new file mode 100644 index 00000000..99986f6c --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/cluster.yml @@ -0,0 +1,59 @@ +--- +- name: Set options fact for tcp socket + ansible.builtin.set_fact: + _options: '-h {{ _redis_hostname }} -p {{ redis_port }}' + +- name: Add tls options for tcp socket + ansible.builtin.set_fact: + _options: '{{ _options }} --tls --cert /var/lib/redis/server.crt --key /var/lib/redis/server.key' + when: not redis_disable_tls | bool + +- name: Check if the cluster is already initialized + ansible.builtin.stat: + path: '{{ redis_conf_dir }}/cluster.init' + register: _cluster_init + +- name: Configure the redis cluster + when: not _cluster_init.stat.exists | bool + block: + - name: Create the redis cluster + containers.podman.podman_container: + name: redis-cluster-init + image: '{{ _redis_image }}' + command: 'redis-cli {{ _options }} --cluster create {{ _redis_cluster_ips }} --cluster-replicas {{ redis_cluster_replicas }} --cluster-yes' + detach: false + rm: true + log_driver: '{{ container_log_driver }}' + network: host + volume: '{{ _volumes }}' + uidmap: + - '{{ redis_uid }}:0:1' + - '0:1:{{ redis_uid }}' + - '{{ redis_uid + 1 }}:{{ redis_uid + 1 }}:{{ 65536 - redis_uid }}' + gidmap: + - '{{ redis_gid }}:0:1' + - '0:1:{{ redis_gid }}' + - '{{ redis_gid + 1 }}:{{ redis_gid + 1 }}:{{ 65536 - redis_gid }}' + timeout: 120 + run_once: true + register: _redis_cluster + changed_when: "'[OK] All 16384 slots covered' in _redis_cluster.stdout" + + rescue: + - name: Cleanup redis_nodes.conf content + ansible.builtin.copy: + content: '' + dest: '{{ redis_conf_dir }}/redis_nodes.conf' + mode: '0640' + + - name: Redis cluster created failed + ansible.builtin.fail: + msg: 'Please check the network and firewall configuration ({{ redis_port }}/{{ redis_cluster_port }})' + run_once: true + +- name: Create the initilized cluster file + ansible.builtin.file: + path: '{{ redis_conf_dir }}/cluster.init' + state: touch + mode: '0644' +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/config.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/config.yml new file mode 100644 index 00000000..81cc59f2 --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/config.yml @@ -0,0 +1,29 @@ +--- +- name: Create the redis config directory + ansible.builtin.file: + path: '{{ redis_conf_dir }}' + state: directory + mode: '0770' + +- name: Create the redis configuration file + ansible.builtin.template: + src: redis.conf.j2 + dest: '{{ redis_conf_dir }}/redis-{{ _redis_suffix }}.conf' + mode: '0640' + notify: Restart redis + +- name: Create the redis user ACL file + ansible.builtin.template: + src: redis-users.acl.j2 + dest: '{{ redis_conf_dir }}/redis-users.acl' + mode: '0640' + notify: Restart redis + when: not redis_unix_socket | bool + +- name: Create redis nodes file + ansible.builtin.file: + path: '{{ redis_conf_dir }}/redis_nodes.conf' + state: touch + mode: '0640' + when: redis_cluster | bool +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/containers.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/containers.yml new file mode 100644 index 00000000..5c065a47 --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/containers.yml @@ -0,0 +1,27 @@ +--- +- name: Create the redis container + containers.podman.podman_container: + name: 'redis-{{ _redis_suffix }}' + image: '{{ _redis_image }}' + generate_systemd: + container_prefix: '' + path: '{{ ansible_user_dir }}/.config/systemd/user' + separator: '' + log_driver: '{{ container_log_driver }}' + network: host + volume: '{{ _volumes }}' + env: + REDIS_CONF: /etc/redis.conf + uidmap: + - '{{ redis_uid }}:0:1' + - '0:1:{{ redis_uid }}' + - '{{ redis_uid + 1 }}:{{ redis_uid + 1 }}:{{ 65536 - redis_uid }}' + gidmap: + - '{{ redis_gid }}:0:1' + - '0:1:{{ redis_gid }}' + - '{{ redis_gid + 1 }}:{{ redis_gid + 1 }}:{{ 65536 - redis_gid }}' + label: '{{ _autoupdate_label }}' + state: created + recreate: '{{ __containers_recreate | default(false) }}' + notify: Restart redis +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/facts.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/facts.yml new file mode 100644 index 00000000..89ea6c88 --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/facts.yml @@ -0,0 +1,72 @@ +--- +- name: Set redis hostname + ansible.builtin.set_fact: + _redis_hostname: '{{ routable_hostname | default(ansible_host) }}' + _redis_suffix: '{{ redis_unix_socket | bool | ternary("unix", "tcp") }}' + _redis_cluster_ip: '{{ redis_cluster_ip | default(ansible_default_ipv4.address) | default(ansible_all_ipv4_addresses[0]) }}' + +- name: Set redis IPs + ansible.builtin.set_fact: + _redis_ips: '{{ _redis_ips | default([]) | union(["IP:" + item]) }}' + loop: '{{ ansible_all_ipv4_addresses + ansible_all_ipv6_addresses }}' + +- name: Set redis cluster IPs + ansible.builtin.set_fact: + _redis_cluster_ips: '{{ _ipv4_addresses | product([redis_port | string]) | map("join", ":") | join(" ") }}' + vars: + _ipv4_addresses: '{{ groups["redis"] | map("extract", hostvars) | map(attribute="_redis_cluster_ip") }}' + run_once: true + when: redis_cluster | bool + +- name: Set redis volumes + ansible.builtin.set_fact: + _volumes: + - '{{ redis_conf_dir }}/redis-{{ _redis_suffix }}.conf:/etc/redis.conf:ro,z' + - 'redis_data_{{ _redis_suffix }}:/var/lib/redis/data:Z' + +- name: Add redis nodes file when cluster + ansible.builtin.set_fact: + _volumes: '{{ _volumes + _nodes_file }}' + vars: + _nodes_file: + - '{{ redis_conf_dir }}/redis_nodes.conf:/etc/redis_nodes.conf:z' + when: redis_cluster | bool + +- name: Add runtime directory volume + ansible.builtin.set_fact: + _volumes: '{{ _volumes + _run_volumes }}' + vars: + _run_volumes: + - 'redis_run:/run/redis:U' + when: redis_unix_socket | bool + +- name: Add redis ACL file to volumes + ansible.builtin.set_fact: + _volumes: '{{ _volumes + _acl_volumes }}' + vars: + _acl_volumes: + - '{{ redis_conf_dir }}/redis-users.acl:/etc/redis-users.acl:ro,z' + when: not redis_unix_socket | bool + +- name: Add tls to volumes + ansible.builtin.set_fact: + _volumes: '{{ _volumes + _tls_volumes }}' + vars: + _tls_volumes: + - '{{ _ca_tls_dir }}/extracted:/etc/pki/ca-trust/extracted:z' + - '{{ redis_conf_dir }}/server.crt:/var/lib/redis/server.crt:ro,z' + - '{{ redis_conf_dir }}/server.key:/var/lib/redis/server.key:ro,z' + when: + - not redis_disable_tls | bool + - not redis_unix_socket | bool + +- name: Set redis user password facts + ansible.builtin.set_fact: + _eda_redis_password: '{{ eda_redis_password | default(lookup("ansible.builtin.password", "/dev/null chars=ascii_letters,digits length=128")) }}' + _gateway_redis_password: '{{ gateway_redis_password | default(lookup("ansible.builtin.password", "/dev/null chars=ascii_letters,digits length=128")) }}' + delegate_facts: true + delegate_to: localhost + no_log: true + run_once: true + when: not redis_unix_socket | bool +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/firewalld.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/firewalld.yml new file mode 100644 index 00000000..88deb207 --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/firewalld.yml @@ -0,0 +1,26 @@ +--- +- name: Manage redis firewalld port + ansible.posix.firewalld: + port: '{{ redis_port }}/tcp' + permanent: true + state: '{{ __firewalld_state }}' + immediate: true + zone: '{{ redis_firewall_zone }}' + become: true + when: + - ansible_facts.services.get('firewalld.service', {}) | length + - ansible_facts.services['firewalld.service']['status'] == 'enabled' + +- name: Manage redis firewalld cluster port + ansible.posix.firewalld: + port: '{{ redis_cluster_port }}/tcp' + permanent: true + state: '{{ __firewalld_state }}' + immediate: true + zone: '{{ redis_firewall_zone }}' + become: true + when: + - redis_cluster | bool + - ansible_facts.services.get('firewalld.service', {}) | length + - ansible_facts.services['firewalld.service']['status'] == 'enabled' +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/flush.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/flush.yml new file mode 100644 index 00000000..88b488af --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/flush.yml @@ -0,0 +1,47 @@ +--- +- name: Set options fact for tcp socket + ansible.builtin.set_fact: + _options: '-h {{ __redis_host }} -p {{ __redis_port }}' + when: not redis_unix_socket | bool + +- name: Add tls options for tcp socket + ansible.builtin.set_fact: + _options: '{{ _options }} --tls --cert /var/lib/redis/redis.cert --key /var/lib/redis/redis.key' + _volumes: + - '{{ ansible_user_dir }}/aap/tls/extracted:/etc/pki/ca-trust/extracted:z' + - '{{ __redis_cert }}:/var/lib/redis/redis.cert:ro,z' + - '{{ __redis_key }}:/var/lib/redis/redis.key:ro,z' + when: + - not redis_unix_socket | bool + - not redis_disable_tls | bool + +- name: Set facts for unix socket + ansible.builtin.set_fact: + _volumes: + - 'redis_run:/run/redis:U' + _options: '-s /run/redis/redis.sock' + when: redis_unix_socket | bool + +- name: Flush the redis database + containers.podman.podman_container: + name: redis-flush + image: '{{ _redis_image }}' + command: 'redis-cli {{ _options }} -n {{ item }} FLUSHDB' + detach: false + rm: true + log_driver: '{{ container_log_driver }}' + network: host + volume: '{{ _volumes | default(omit) }}' + uidmap: + - '{{ redis_uid }}:0:1' + - '0:1:{{ redis_uid }}' + - '{{ redis_uid + 1 }}:{{ redis_uid + 1 }}:{{ 65536 - redis_uid }}' + gidmap: + - '{{ redis_gid }}:0:1' + - '0:1:{{ redis_gid }}' + - '{{ redis_gid + 1 }}:{{ redis_gid + 1 }}:{{ 65536 - redis_gid }}' + run_once: '{{ not redis_unix_socket | bool }}' + register: _redis_flush + changed_when: "'OK' in _redis_flush.stdout" + loop: '{{ __redis_databases | default([]) }}' +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/main.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/main.yml new file mode 100644 index 00000000..428be721 --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/main.yml @@ -0,0 +1,40 @@ +--- +- name: Set redis facts + ansible.builtin.include_tasks: facts.yml + +- name: Create podman volumes + ansible.builtin.include_tasks: volumes.yml + vars: + __volume_state: present + +- name: Configure redis + ansible.builtin.include_tasks: config.yml + +- name: Configure TLS + ansible.builtin.include_tasks: tls.yml + when: + - not redis_disable_tls | bool + - not redis_unix_socket | bool + +- name: Handle container image update + ansible.builtin.include_tasks: update.yml + +- name: Configure containers + ansible.builtin.include_tasks: containers.yml + +- name: Ensure redis container is enabled and started + ansible.builtin.include_tasks: systemd.yml + vars: + __systemd_enabled: true + __systemd_state: started + +- name: Add firewalld rules + ansible.builtin.include_tasks: firewalld.yml + vars: + __firewalld_state: enabled + when: not redis_unix_socket | bool + +- name: Initialize the redis cluster + ansible.builtin.include_tasks: cluster.yml + when: redis_cluster | bool +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/restore.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/restore.yml new file mode 100644 index 00000000..4b2da0b9 --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/restore.yml @@ -0,0 +1,32 @@ +--- +- name: Set redis facts + ansible.builtin.include_tasks: facts.yml + +- name: Stop redis systemd service + ansible.builtin.include_tasks: systemd.yml + vars: + __systemd_state: stopped + +- name: Unarchive the redis data + ansible.builtin.unarchive: + src: '{{ hostvars["localhost"]["_backup_dir"] }}/redis_{{ inventory_hostname }}.{{ redis_archive_extension }}' + dest: '{{ ansible_user_dir }}/aap' + exclude: + - 'tls/' + - 'aap/tls' + - '.local/share/containers/storage/secrets/' + - 'redis/server.key' + - 'redis/server.crt' + - 'redis/redis-users.acl' + - 'redis/redis_nodes.conf' + +- name: Reconfigure containers + ansible.builtin.include_tasks: containers.yml + vars: + __containers_recreate: true + +- name: Start redis systemd service + ansible.builtin.include_tasks: systemd.yml + vars: + __systemd_state: started +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/systemd.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/systemd.yml new file mode 100644 index 00000000..3dca4675 --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/systemd.yml @@ -0,0 +1,19 @@ +--- +- name: Manage redis container via systemd - with deamon_reload + ansible.builtin.systemd: + name: 'redis-{{ _redis_suffix }}.service' + state: '{{ __systemd_state | default("started") }}' + enabled: '{{ __systemd_enabled | default(true) }}' + scope: user + daemon_reload: true + ignore_errors: 1 + +- name: Manage redis container via systemd - without deamon_reload + ansible.builtin.systemd: + name: 'redis-{{ _redis_suffix }}.service' + state: '{{ __systemd_state | default("started") }}' + enabled: '{{ __systemd_enabled | default(true) }}' + scope: user + retries: 5 + delay: 5 +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/tls.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/tls.yml new file mode 100644 index 00000000..533ca9d8 --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/tls.yml @@ -0,0 +1,75 @@ +--- +- name: Generate TLS certificate and key + when: + - redis_tls_cert is not defined + - redis_tls_key is not defined + block: + - name: Install python-cryptography + ansible.builtin.package: + name: python3-cryptography + become: true + when: not ostree | bool + + - name: Check the current redis private key + ansible.builtin.stat: + path: '{{ redis_conf_dir }}/server.key' + register: _redis_tls_key + + - name: Generate a private key + community.crypto.openssl_privatekey: + path: '{{ redis_conf_dir }}/server.key' + mode: '0400' + when: not _redis_tls_key.stat.exists | bool + + - name: Check the current redis certificate + ansible.builtin.stat: + path: '{{ redis_conf_dir }}/server.crt' + register: _redis_tls_cert + + - name: Create the redis TLS certificate + when: not _redis_tls_cert.stat.exists | bool + block: + - name: Generate a certificate signing request + community.crypto.openssl_csr_pipe: + common_name: '{{ _redis_hostname }}' + country_name: 'US' + locality_name: 'Raleigh' + organization_name: 'Red Hat' + organizational_unit_name: 'Ansible' + state_or_province_name: 'North Carolina' + subject_alt_name: '{{ [_redis_hostname | ansible.containerized_installer.subject_alt_name] | union(_redis_ips) }}' + privatekey_path: '{{ redis_conf_dir }}/server.key' + register: _redis_tls_csr + + - name: Generate a self signed x509 certificate + community.crypto.x509_certificate: + path: '{{ redis_conf_dir }}/server.crt' + csr_content: '{{ _redis_tls_csr.csr }}' + privatekey_path: '{{ redis_conf_dir }}/server.key' + provider: ownca + ownca_path: '{{ _ca_tls_dir }}/ca.cert' + ownca_privatekey_path: '{{ _ca_tls_dir }}/ca.key' + ownca_privatekey_passphrase: '{{ ca_tls_key_passphrase | default(omit) }}' + mode: '0640' + +- name: Import TLS certificate and key + when: + - redis_tls_cert is defined + - redis_tls_key is defined + block: + - name: Copy redis TLS certificate + ansible.builtin.copy: + src: '{{ redis_tls_cert }}' + dest: '{{ redis_conf_dir }}/server.crt' + mode: '0640' + remote_src: '{{ redis_tls_remote | default(false) }}' + notify: Restart redis + + - name: Copy redis TLS key + ansible.builtin.copy: + src: '{{ redis_tls_key }}' + dest: '{{ redis_conf_dir }}/server.key' + mode: '0400' + remote_src: '{{ redis_tls_remote | default(false) }}' + notify: Restart redis +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/uninstall.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/uninstall.yml new file mode 100644 index 00000000..58d74b13 --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/uninstall.yml @@ -0,0 +1,39 @@ +--- +- name: Set redis suffix + ansible.builtin.set_fact: + _redis_suffix: '{{ redis_unix_socket | bool | ternary("unix", "tcp") }}' + +- name: Ensure systemd unit are disabled and stopped + ansible.builtin.systemd: + name: 'redis-{{ _redis_suffix }}.service' + state: stopped + enabled: false + scope: user + failed_when: false + +- name: Delete the container + containers.podman.podman_container: + name: 'redis-{{ _redis_suffix }}' + state: absent + +- name: Delete the systemd unit files + ansible.builtin.file: + path: '{{ ansible_user_dir }}/.config/systemd/user/redis-{{ _redis_suffix }}.service' + state: absent + +- name: Delete podman volumes + ansible.builtin.include_tasks: volumes.yml + vars: + __volume_state: absent + +- name: Delete the redis config directory + ansible.builtin.file: + path: '{{ redis_conf_dir }}' + state: absent + +- name: Delete firewalld rules + ansible.builtin.include_tasks: firewalld.yml + vars: + __firewalld_state: disabled + when: not redis_unix_socket | bool +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/update.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/update.yml new file mode 100644 index 00000000..519827e4 --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/update.yml @@ -0,0 +1,24 @@ +--- +- name: Get redis container information + containers.podman.podman_container_info: + name: 'redis-{{ _redis_suffix }}' + register: _redis_container_info + +- name: Get redis image information + containers.podman.podman_image_info: + name: '{{ _redis_image }}' + register: _redis_image_info + +- name: Set redis update fact + ansible.builtin.set_fact: + _redis_image_update: '{{ _redis_before | difference(_redis_after) | length > 0 }}' + vars: + _redis_before: '{{ _redis_container_info.containers | map(attribute="Image") | unique }}' + _redis_after: '{{ _redis_image_info.images | map(attribute="Id") | unique }}' + +- name: Stop redis service on update + ansible.builtin.include_tasks: systemd.yml + vars: + __systemd_state: stopped + when: _redis_image_update | bool +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/volumes.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/volumes.yml new file mode 100644 index 00000000..0610bfe2 --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/tasks/volumes.yml @@ -0,0 +1,12 @@ +--- +- name: Manage the redis data volume + containers.podman.podman_volume: + name: 'redis_data_{{ _redis_suffix }}' + state: '{{ __volume_state }}' + +- name: Manage the redis run volume + containers.podman.podman_volume: + name: redis_run + state: '{{ __volume_state }}' + when: redis_unix_socket | bool +... diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/templates/redis-users.acl.j2 b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/templates/redis-users.acl.j2 new file mode 100644 index 00000000..9475a46e --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/templates/redis-users.acl.j2 @@ -0,0 +1,4 @@ +user {{ gateway_redis_username | default('gateway') }} on #{{ hostvars["localhost"]["_gateway_redis_password"] | hash('sha256') }} ~gateway:* resetchannels +@all +{% if groups.get('automationeda', []) | length > 1 %} +user {{ eda_redis_username | default('eda') }} on #{{ hostvars["localhost"]["_eda_redis_password"] | hash('sha256') }} ~eda-rq:* resetchannels &eda-rq:pubsub:* +@all +{% endif %} diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/templates/redis.conf.j2 b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/templates/redis.conf.j2 new file mode 100644 index 00000000..d92cc32a --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/templates/redis.conf.j2 @@ -0,0 +1,35 @@ +dir /var/lib/redis/data +{% if redis_unix_socket | bool %} +unixsocket /run/redis/redis.sock +unixsocketperm 777 +port 0 +bind 127.0.0.1 +{% else %} +{% if ansible_all_ipv6_addresses | length == 0 %} +bind 0.0.0.0 +{% endif %} +protected-mode no +aclfile /etc/redis-users.acl +{% if redis_disable_tls | bool %} +port {{ redis_port }} +{% else %} +port 0 +tls-port {{ redis_port }} +tls-ca-cert-file /etc/pki/tls/certs/ca-bundle.crt +tls-cert-file /var/lib/redis/server.crt +tls-key-file /var/lib/redis/server.key +tls-auth-clients yes +{% if redis_cluster | bool %} +tls-cluster yes +tls-replication yes +{% endif %} +{% endif %} +{% if redis_cluster | bool %} +cluster-enabled yes +cluster-require-full-coverage no +cluster-node-timeout 15000 +cluster-config-file /etc/redis_nodes.conf +cluster-migration-barrier 1 +appendonly yes +{% endif %} +{% endif %} diff --git a/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/vars/main.yml b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/vars/main.yml new file mode 100644 index 00000000..4a14c489 --- /dev/null +++ b/setup/collections/ansible_collections/ansible/containerized_installer/roles/redis/vars/main.yml @@ -0,0 +1,7 @@ +--- +redis_uid: 1001 +redis_gid: 996 + +archive_extension: "{{ use_archive_compression | bool | ternary(archive_compressed_extension, archive_decompressed_extension) }}" +redis_archive_extension: "{{ redis_use_archive_compression | bool | ternary(archive_compressed_extension, archive_decompressed_extension) }}" +... diff --git a/setup/inventory.example b/setup/inventory.example index 96d4b8f5..ed352933 100644 --- a/setup/inventory.example +++ b/setup/inventory.example @@ -50,7 +50,12 @@ initial_sync_days=1 [database] host.example.com ansible_connection=local +[redis] +host.example.com ansible_connection=local + [all:vars] +redis_mode=standalone + postgresql_admin_username=postgres postgresql_admin_password=TODO # registry_username=