diff --git a/cli/build/inventory/__init__.py b/cli/build/inventory/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/roles/docker-compose/handlers/main.yml b/roles/docker-compose/handlers/main.yml index d9e1d6ad..33d61931 100644 --- a/roles/docker-compose/handlers/main.yml +++ b/roles/docker-compose/handlers/main.yml @@ -9,6 +9,7 @@ listen: - docker compose up - docker compose restart + - docker compose just up - name: Build docker command: @@ -19,21 +20,26 @@ DOCKER_CLIENT_TIMEOUT: 600 listen: - docker compose build - - docker compose up # This is just here because I didn't took the time to refactor - # @todo go over all docker compose up implementations and check where it makes sense to user docker compose build and where docker compose up - name: docker compose up - shell: docker-compose -p {{ application_id | get_entity_name }} up -d --force-recreate --remove-orphans + shell: | + if [ -f "{{ docker_compose.files.env }}" ]; then + docker compose -p {{ application_id | get_entity_name }} --env-file "{{ docker_compose.files.env }} up -d --force-recreate --remove-orphans" + else + docker compose -p {{ application_id | get_entity_name }} up -d --force-recreate --remove-orphans + fi args: chdir: "{{ docker_compose.directories.instance }}" executable: /bin/bash environment: COMPOSE_HTTP_TIMEOUT: 600 DOCKER_CLIENT_TIMEOUT: 600 - listen: docker compose up + listen: + - docker compose up + - docker compose just up # @todo replace later just up by up when code is refactored, build atm is also listening to up - name: docker compose restart command: - cmd: "docker compose restart" + cmd: 'docker compose restart' chdir: "{{docker_compose.directories.instance}}" listen: docker compose restart diff --git a/roles/web-app-bigbluebutton/filter_plugins/compose_mods.py b/roles/web-app-bigbluebutton/filter_plugins/compose_mods.py new file mode 100644 index 00000000..e8607e72 --- /dev/null +++ b/roles/web-app-bigbluebutton/filter_plugins/compose_mods.py @@ -0,0 +1,72 @@ +import re +import yaml + +def compose_mods(yml_text, docker_repository_path, env_file): + yml_text = re.sub(r'\./data/postgres:/var/lib/postgresql/data', 'database:/var/lib/postgresql/data', yml_text) + yml_text = re.sub(r'\./data/bigbluebutton:/var/bigbluebutton', 'bigbluebutton:/var/bigbluebutton', yml_text) + yml_text = re.sub(r'\./data/freeswitch-meetings:/var/freeswitch/meetings', 'freeswitch:/var/freeswitch/meetings', yml_text) + yml_text = re.sub(r'\./data/greenlight:/usr/src/app/storage', 'greenlight:/usr/src/app/storage', yml_text) + yml_text = re.sub(r'\./data/mediasoup:/var/mediasoup', 'mediasoup:/var/mediasoup', yml_text) + yml_text = re.sub(r'\./', docker_repository_path + '/', yml_text) + yml_text = re.sub( + r'(^\s*context:\s*)' + re.escape(docker_repository_path) + r'/mod/(.*)', + r'\1' + docker_repository_path + r'/mod/\2', + yml_text, flags=re.MULTILINE + ) + yml_text = re.sub( + r'(^\s*context:\s*)mod/(.*)', + r'\1' + docker_repository_path + r'/mod/\2', + yml_text, flags=re.MULTILINE + ) + + try: + data = yaml.safe_load(yml_text) + services = data.get('services', {}) + for name, svc in services.items(): + svc['env_file'] = [env_file] + if name == 'redis': + vols = svc.get('volumes') + if not vols or not isinstance(vols, list): + svc['volumes'] = ['redis:/data'] + elif 'redis:/data' not in vols: + svc['volumes'].append('redis:/data') + if name == 'coturn': + vols = svc.get('volumes') + if not vols or not isinstance(vols, list): + svc['volumes'] = ['coturn:/var/lib/coturn'] + elif 'coturn:/var/lib/coturn' not in vols: + svc['volumes'].append('coturn:/var/lib/coturn') + if name == 'bbb-graphql-server': + svc['healthcheck'] = { + 'test': ['CMD', 'curl', '-f', 'http://localhost:8085/healthz'], + 'interval': '30s', + 'timeout': '10s', + 'retries': 5, + 'start_period': '10s' + } + data['services'] = services + + # **ADD THIS BLOCK:** + # Only add volumes block if not present + if 'volumes' not in data: + data['volumes'] = { + 'database': None, + 'greenlight': None, + 'redis': None, + 'coturn': None, + 'freeswitch': None, + 'bigbluebutton': None, + 'mediasoup': None + } + + yml_text = yaml.dump(data, default_flow_style=False, sort_keys=False) + except Exception: + pass + + return yml_text + +class FilterModule(object): + def filters(self): + return { + 'compose_mods': compose_mods, + } diff --git a/roles/web-app-bigbluebutton/tasks/docker-compose.yml b/roles/web-app-bigbluebutton/tasks/docker-compose.yml index d3ca7880..65767968 100644 --- a/roles/web-app-bigbluebutton/tasks/docker-compose.yml +++ b/roles/web-app-bigbluebutton/tasks/docker-compose.yml @@ -6,107 +6,14 @@ COMPOSE_HTTP_TIMEOUT: 600 DOCKER_CLIENT_TIMEOUT: 600 -- name: Copy docker-compose.yml from origin to final location +- name: Slurp docker-compose.yml from remote host + slurp: + src: "{{ docker_compose_file_origine }}" + register: compose_slurp + +- name: Transform docker-compose.yml with compose_mods copy: - src: "{{ docker_compose_file_origine }}" + content: "{{ compose_slurp.content | b64decode | compose_mods(docker_repository_path, docker_compose.files.env) }}" dest: "{{ docker_compose_file_final }}" - remote_src: yes - -- name: Replace bind mounts by named volume mounts - replace: - path: "{{ docker_compose_file_final }}" - regexp: "{{ item.regexp }}" - replace: "{{ item.replace }}" - loop: - - { regexp: '\./data/postgres:/var/lib/postgresql/data', replace: 'database:/var/lib/postgresql/data' } - - { regexp: '\./data/bigbluebutton:/var/bigbluebutton', replace: 'bigbluebutton:/var/bigbluebutton' } - - { regexp: '\./data/freeswitch-meetings:/var/freeswitch/meetings', replace: 'freeswitch:/var/freeswitch/meetings' } - - { regexp: '\./data/greenlight:/usr/src/app/storage', replace: 'greenlight:/usr/src/app/storage' } - - { regexp: '\./data/mediasoup:/var/mediasoup', replace: 'mediasoup:/var/mediasoup' } - -- name: add volume to redis - lineinfile: - path: "{{ docker_compose_file_final }}" - insertafter: "^\\s*redis:" - line: " volumes:\n - redis:/data" - firstmatch: yes - -- name: add volume to coturn - lineinfile: - path: "{{ docker_compose_file_final }}" - insertafter: "- ./mod/coturn/turnserver.conf:/etc/coturn/turnserver.conf" - line: " - coturn:/var/lib/coturn" - -# Implemented due to etherpad health bug. -# @todo Remove when health check is working fine -# @see https://chatgpt.com/c/67a0fc7e-5104-800f-bb6b-3731e2f83b7b -#- name: "Update docker-compose.yml for Etherpad health check" -# lineinfile: -# line: " healthcheck:\n test: [\"CMD\", \"curl\", \"-f\", \"http://127.0.0.1:9001\"]\n interval: 30s\n timeout: 10s\n retries: 5\n start_period: 10s" -# path: "{{docker_compose_file_final}}" -# insertafter: "etherpad:" -# listen: setup bigbluebutton - -- name: Add volumes block after services in docker compose - blockinfile: - path: "{{ docker_compose_file_final }}" - block: | - volumes: - database: - greenlight: - redis: - coturn: - freeswitch: - bigbluebutton: - mediasoup: - marker: "# {mark} ANSIBLE MANAGED BLOCK FOR VOLUMES" - insertbefore: "^services:" - -- name: Replace all './' with '/services/' in docker-compose.yml - ansible.builtin.replace: - path: "{{ docker_compose_file_final }}" - regexp: '\./' - replace: '{{ docker_repository_path }}/' - -- name: Prefix build context with docker_repository_path - ansible.builtin.replace: - path: "{{ docker_compose_file_final }}" - regexp: '(^\s*context:\s*)mod/(.*)' - replace: '\1{{ docker_repository_path }}/mod/\2' - -- name: "Update healthcheck for bbb-graphql-server" - # This is neccessary because the healthcheck doesn't listen to the correct port - lineinfile: - line: " healthcheck:\n test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:8085/healthz\"]\n interval: 30s\n timeout: 10s\n retries: 5\n start_period: 10s" - path: "{{docker_compose_file_final}}" - insertafter: "bbb-graphql-server:" - -- name: Add env_file to each service in docker-compose.yml - blockinfile: - path: "{{ docker_compose_file_final }}" - insertafter: '^ {{ service }}:$' - marker: "# {mark} ANSIBLE MANAGED BLOCK FOR ENV_FILE" - block: | - env_file: - - "{{ docker_compose.files.env }}" - loop: - - bbb-web - - freeswitch - - nginx - - etherpad - - bbb-pads - - bbb-export-annotations - - redis - - webrtc-sfu - - fsesl-akka - - apps-akka - - bbb-graphql-server - - bbb-graphql-actions - - bbb-graphql-middleware - - collabora - - periodic - - coturn - - greenlight - - postgres - loop_control: - loop_var: service + notify: + - docker compose just up diff --git a/roles/web-app-bigbluebutton/tasks/main.yml b/roles/web-app-bigbluebutton/tasks/main.yml index c588a74c..3a216848 100644 --- a/roles/web-app-bigbluebutton/tasks/main.yml +++ b/roles/web-app-bigbluebutton/tasks/main.yml @@ -35,15 +35,29 @@ - name: "Setup docker-compose.yml file" include_tasks: "docker-compose.yml" +- name: Ensure all containers in instance are running + include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/04_ensure_up.yml" + - name: flush docker service meta: flush_handlers -- name: Wait for BigBlueButton - wait_for: - host: "{{ domains | get_domain('web-app-bigbluebutton') }}" - port: 80 - delay: 5 - timeout: 300 +- name: "Get greenlight container name" + shell: | + docker compose ps -q greenlight + args: + chdir: "{{ docker_compose.directories.instance }}" + register: greenlight_id + +- name: "Wait until BigBlueButton (greenlight) is running" + shell: | + docker inspect --format='{{'{{'}}.State.Status{{'}}'}}' {{ greenlight_id.stdout }} + args: + chdir: "{{ docker_compose.directories.instance }}" + register: bbb_state + until: bbb_state.stdout.strip() == "running" + retries: 30 + delay: 5 + changed_when: false - name: create admin command: diff --git a/roles/web-app-matomo/config/main.yml b/roles/web-app-matomo/config/main.yml index fcc4f30d..6b72f6c1 100644 --- a/roles/web-app-matomo/config/main.yml +++ b/roles/web-app-matomo/config/main.yml @@ -5,7 +5,7 @@ features: # itself wouldn't be possible matomo: false css: false - port-ui-desktop: true + port-ui-desktop: false # Didn't work in frame didn't have high priority @todo figure out pcause and solve it central_database: true oauth2: false csp: diff --git a/roles/web-app-openproject/tasks/main.yml b/roles/web-app-openproject/tasks/main.yml index 3962134c..d3861cd6 100644 --- a/roles/web-app-openproject/tasks/main.yml +++ b/roles/web-app-openproject/tasks/main.yml @@ -15,6 +15,7 @@ dest: "{{openproject_plugins_folder}}Gemfile.plugins" notify: - docker compose up + - docker compose build - name: "create {{dummy_volume}}" file: diff --git a/tests/unit/roles/web-app-bigbluebutton/__init__.py b/tests/unit/roles/web-app-bigbluebutton/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/roles/web-app-bigbluebutton/filter_plugins/__init__.py b/tests/unit/roles/web-app-bigbluebutton/filter_plugins/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/roles/web-app-bigbluebutton/filter_plugins/test_compose_mods.py b/tests/unit/roles/web-app-bigbluebutton/filter_plugins/test_compose_mods.py new file mode 100644 index 00000000..d79b9c2a --- /dev/null +++ b/tests/unit/roles/web-app-bigbluebutton/filter_plugins/test_compose_mods.py @@ -0,0 +1,145 @@ +import os +import unittest +import sys +import yaml + +sys.path.insert( + 0, + os.path.abspath( + os.path.join( + os.path.dirname(__file__), + '../../../../../roles/web-app-bigbluebutton/filter_plugins' + ) + ) +) + +from compose_mods import compose_mods + +def sort_dict(obj): + if isinstance(obj, dict): + return {k: sort_dict(obj[k]) for k in sorted(obj)} + elif isinstance(obj, list): + return [sort_dict(v) for v in obj] + else: + return obj + +class TestComposeModsFullFile(unittest.TestCase): + def setUp(self): + self.docker_repository_path = "/opt/docker/bigbluebutton/services/repository" + self.env_file = "/opt/docker/bigbluebutton/.env/env" + self.original = """# auto generated by ./scripts/generate-compose +# don't edit this directly. + +services: + bbb-web: + build: + context: mod/bbb-web + additional_contexts: + - src-web=./repos/bigbluebutton/bigbluebutton-web + volumes: + - ./data/bigbluebutton:/var/bigbluebutton + - ./data/freeswitch-meetings:/var/freeswitch/meetings + + freeswitch: + build: + context: mod/freeswitch + additional_contexts: + - freeswitch=./repos/freeswitch/ + volumes: + - ./data/freeswitch-meetings:/var/freeswitch/meetings + + nginx: + build: + context: mod/nginx + + redis: + image: redis:7.2-alpine + + coturn: + image: coturn/coturn:4.6-alpine + volumes: + - ./mod/coturn/turnserver.conf:/etc/coturn/turnserver.conf + + bbb-graphql-server: + build: + context: mod/bbb-graphql-server +""" + + self.expected = """services: + bbb-web: + build: + context: /opt/docker/bigbluebutton/services/repository/mod/bbb-web + additional_contexts: + - src-web=/opt/docker/bigbluebutton/services/repository/repos/bigbluebutton/bigbluebutton-web + volumes: + - bigbluebutton:/var/bigbluebutton + - freeswitch:/var/freeswitch/meetings + env_file: + - /opt/docker/bigbluebutton/.env/env + + freeswitch: + build: + context: /opt/docker/bigbluebutton/services/repository/mod/freeswitch + additional_contexts: + - freeswitch=/opt/docker/bigbluebutton/services/repository/repos/freeswitch/ + volumes: + - freeswitch:/var/freeswitch/meetings + env_file: + - /opt/docker/bigbluebutton/.env/env + + nginx: + build: + context: /opt/docker/bigbluebutton/services/repository/mod/nginx + env_file: + - /opt/docker/bigbluebutton/.env/env + + redis: + image: redis:7.2-alpine + volumes: + - redis:/data + env_file: + - /opt/docker/bigbluebutton/.env/env + + coturn: + image: coturn/coturn:4.6-alpine + volumes: + - /opt/docker/bigbluebutton/services/repository/mod/coturn/turnserver.conf:/etc/coturn/turnserver.conf + - coturn:/var/lib/coturn + env_file: + - /opt/docker/bigbluebutton/.env/env + + bbb-graphql-server: + build: + context: /opt/docker/bigbluebutton/services/repository/mod/bbb-graphql-server + healthcheck: + test: + - CMD + - curl + - -f + - http://localhost:8085/healthz + interval: 30s + timeout: 10s + retries: 5 + start_period: 10s + env_file: + - /opt/docker/bigbluebutton/.env/env + +volumes: + database: + greenlight: + redis: + coturn: + freeswitch: + bigbluebutton: + mediasoup: +""" + + def test_full_file_semantic_match(self): + actual_data = yaml.safe_load(compose_mods(self.original, self.docker_repository_path, self.env_file)) + expected_data = yaml.safe_load(self.expected) + for key in expected_data: + self.assertIn(key, actual_data) + self.assertEqual(sort_dict(actual_data[key]), sort_dict(expected_data[key])) + +if __name__ == "__main__": + unittest.main()