From 380aa4a37b0cb6a6db45e79206b0a147edcfba72 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Tue, 17 Jun 2025 15:08:42 +0200 Subject: [PATCH] Optimized code, so that 'docker compose up' can run until setup is finished without any interruptions --- roles/docker-akaunting/tasks/main.yml | 2 +- roles/docker-bluesky/tasks/main.yml | 2 +- roles/docker-central-database/tasks/main.yml | 2 +- roles/docker-compose/handlers/main.yml | 22 ++--- roles/docker-compose/tasks/create-files.yml | 10 +-- .../templates/services/base.yml.j2 | 3 +- roles/docker-discourse/tasks/main.yml | 2 +- roles/docker-keycloak/tasks/main.yml | 2 +- roles/docker-listmonk/tasks/main.yml | 2 +- roles/docker-matrix/tasks/main.yml | 12 +-- roles/docker-mediawiki/tasks/main.yml | 2 +- roles/docker-mybb/tasks/main.yml | 4 +- roles/docker-nextcloud/tasks/main.yml | 2 +- roles/docker-oauth2-proxy/tasks/main.yml | 2 +- roles/docker-openproject/tasks/main.yml | 2 +- roles/docker-pgadmin/tasks/configuration.yml | 4 +- roles/docker-portfolio/tasks/main.yml | 6 +- roles/docker-presentation/tasks/main.yml | 2 +- roles/docker-repository-setup/tasks/main.yml | 2 +- roles/docker-roulette-wheel/tasks/main.yml | 2 +- roles/docker-sphinx/tasks/main.yml | 2 +- roles/docker-sphinx/vars/main.yml | 2 +- roles/docker-syncope/tasks/main.yml | 2 +- roles/docker-taiga/tasks/main.yml | 4 +- roles/docker-wordpress/tasks/main.yml | 4 +- .../files/__init__.py | 0 .../files/system-storage-optimizer.py | 88 +++++++++++++------ tasks/utils/update-repository-with-files.yml | 4 +- tests/unit/test_storage_optimizer.py | 56 ++++++++++++ 29 files changed, 165 insertions(+), 84 deletions(-) create mode 100644 roles/system-storage-optimizer/files/__init__.py create mode 100644 tests/unit/test_storage_optimizer.py diff --git a/roles/docker-akaunting/tasks/main.yml b/roles/docker-akaunting/tasks/main.yml index f45db1d5..13de79d6 100644 --- a/roles/docker-akaunting/tasks/main.yml +++ b/roles/docker-akaunting/tasks/main.yml @@ -22,4 +22,4 @@ dest: "{{docker_compose.files.env}}" mode: '770' force: yes - notify: docker compose project setup + notify: docker compose up diff --git a/roles/docker-bluesky/tasks/main.yml b/roles/docker-bluesky/tasks/main.yml index c0e10f19..15c6fe56 100644 --- a/roles/docker-bluesky/tasks/main.yml +++ b/roles/docker-bluesky/tasks/main.yml @@ -45,6 +45,6 @@ repo: "https://github.com/bluesky-social/social-app.git" dest: "{{social_app_path}}" version: "main" - notify: docker compose project build and setup + notify: docker compose up - include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/create-files.yml" diff --git a/roles/docker-central-database/tasks/main.yml b/roles/docker-central-database/tasks/main.yml index 675a0f69..a012a952 100644 --- a/roles/docker-central-database/tasks/main.yml +++ b/roles/docker-central-database/tasks/main.yml @@ -19,7 +19,7 @@ template: src: "env/{{database_type}}.env.j2" dest: "{{database_env}}" - notify: docker compose project build and setup + notify: docker compose up when: not applications | is_feature_enabled('central_database',application_id) - name: "Create central database" diff --git a/roles/docker-compose/handlers/main.yml b/roles/docker-compose/handlers/main.yml index f005d902..61b33246 100644 --- a/roles/docker-compose/handlers/main.yml +++ b/roles/docker-compose/handlers/main.yml @@ -5,35 +5,25 @@ #- name: shut down docker compose project # command: # cmd: docker-compose -p "{{ application_id }}" down -# listen: docker compose project setup +# listen: docker compose up # when: mode_reset | bool # default setup for docker compose files -- name: docker compose project setup +- name: docker compose up shell: > docker-compose -p {{ application_id }} up -d --force-recreate --remove-orphans 2>&1 | tee >(systemd-cat -t docker-compose-{{ application_id }}) - args: - chdir: "{{ docker_compose.directories.instance }}" - executable: /bin/bash - environment: - COMPOSE_HTTP_TIMEOUT: 600 - DOCKER_CLIENT_TIMEOUT: 600 - listen: docker compose project setup - -# it's necessary to rebuild when a build in the docker compose files is defined -# for performance reasons it's not recommended to use this if there is no build tag specified -- name: docker compose project build and setup shell: > - docker-compose -p {{ application_id }} up -d --force-recreate --build --remove-orphans - 2>&1 | tee >(systemd-cat -t docker-compose-{{ application_id }}) + docker-compose -p {{ application_id }} up -d + --force-recreate --remove-orphans --build + 2>&1 | tee >(systemd-cat -t docker-compose-{{ application_id }}) args: chdir: "{{ docker_compose.directories.instance }}" executable: /bin/bash environment: COMPOSE_HTTP_TIMEOUT: 600 DOCKER_CLIENT_TIMEOUT: 600 - listen: docker compose project build and setup + listen: docker compose up - name: docker compose restart command: diff --git a/roles/docker-compose/tasks/create-files.yml b/roles/docker-compose/tasks/create-files.yml index 58acc5a3..0496d163 100644 --- a/roles/docker-compose/tasks/create-files.yml +++ b/roles/docker-compose/tasks/create-files.yml @@ -2,7 +2,7 @@ template: src: "{{ playbook_dir }}/roles/{{ role_name }}/templates/Dockerfile.j2" dest: "{{ docker_compose.files.dockerfile }}" - notify: docker compose project build and setup + notify: docker compose up ignore_errors: false register: create_dockerfile_result failed_when: @@ -15,7 +15,7 @@ dest: "{{ docker_compose.files.env }}" mode: '770' force: yes - notify: docker compose project setup + notify: docker compose up register: env_template ignore_errors: false failed_when: @@ -26,7 +26,7 @@ template: src: "docker-compose.yml.j2" dest: "{{ docker_compose.files.docker_compose }}" - notify: docker compose project setup + notify: docker compose up register: docker_compose_template - name: "Check if any container is running in {{ docker_compose.directories.instance }}" @@ -35,8 +35,8 @@ chdir: "{{ docker_compose.directories.instance }}" register: docker_ps changed_when: (docker_ps.stdout | trim) == "" - notify: docker compose project setup + notify: docker compose up when: not (docker_compose_template.changed or env_template.changed) -- name: flush docker compose project setup +- name: flush docker compose up meta: flush_handlers diff --git a/roles/docker-compose/templates/services/base.yml.j2 b/roles/docker-compose/templates/services/base.yml.j2 index 0fe88ce8..e9164393 100644 --- a/roles/docker-compose/templates/services/base.yml.j2 +++ b/roles/docker-compose/templates/services/base.yml.j2 @@ -1,4 +1,5 @@ -# Base for docker services +{# Base for docker services #} + restart: {{docker_restart_policy}} env_file: - "{{docker_compose.files.env}}" diff --git a/roles/docker-discourse/tasks/main.yml b/roles/docker-discourse/tasks/main.yml index 84041525..dcf66f31 100644 --- a/roles/docker-discourse/tasks/main.yml +++ b/roles/docker-discourse/tasks/main.yml @@ -33,7 +33,7 @@ src: docker-compose.yml.j2 dest: "{{docker_compose.directories.instance}}docker-compose.yml" notify: - - docker compose project setup + - docker compose up when: run_once_docker_discourse is not defined - name: flush, to recreate discourse docker compose diff --git a/roles/docker-keycloak/tasks/main.yml b/roles/docker-keycloak/tasks/main.yml index 8f1f293d..5f0ed425 100644 --- a/roles/docker-keycloak/tasks/main.yml +++ b/roles/docker-keycloak/tasks/main.yml @@ -24,4 +24,4 @@ dest: "{{ import_directory_host }}/{{ item | basename | regex_replace('\\.j2$', '') }}" mode: '770' loop: "{{ lookup('fileglob', '{{ role_path }}/templates/import/*.j2', wantlist=True) }}" - notify: docker compose project setup \ No newline at end of file + notify: docker compose up \ No newline at end of file diff --git a/roles/docker-listmonk/tasks/main.yml b/roles/docker-listmonk/tasks/main.yml index 4033a53d..2b8198e7 100644 --- a/roles/docker-listmonk/tasks/main.yml +++ b/roles/docker-listmonk/tasks/main.yml @@ -23,7 +23,7 @@ template: src: "config.toml.j2" dest: "{{docker_compose.directories.config}}config.toml" - notify: docker compose project setup + notify: docker compose up - include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/create-files.yml" diff --git a/roles/docker-matrix/tasks/main.yml b/roles/docker-matrix/tasks/main.yml index 70391bcf..041a1d9c 100644 --- a/roles/docker-matrix/tasks/main.yml +++ b/roles/docker-matrix/tasks/main.yml @@ -77,25 +77,25 @@ src: "mautrix/{{item.bridge_name}}.config.yml.j2" dest: "{{docker_compose.directories.instance}}mautrix/{{item.bridge_name}}/config.yaml" loop: "{{ bridges }}" - notify: docker compose project setup + notify: docker compose up - name: add element configuration template: src: "element.config.json.j2" dest: "{{docker_compose.directories.instance}}element-config.json" - notify: docker compose project setup + notify: docker compose up - name: add synapse homeserver configuration template: src: "synapse/homeserver.yaml.j2" dest: "{{docker_compose.directories.instance}}homeserver.yaml" - notify: docker compose project setup + notify: docker compose up - name: add synapse log configuration template: src: "synapse/log.config.j2" dest: "{{docker_compose.directories.instance}}{{domains.matrix.synapse}}.log.config" - notify: docker compose project setup + notify: docker compose up # https://github.com/matrix-org/synapse/issues/6303 - name: set correct folder permissions @@ -106,7 +106,7 @@ template: src: "docker-compose.yml.j2" dest: "{{docker_compose.directories.instance}}docker-compose.yml" - notify: docker compose project setup + notify: docker compose up # Pull image when update is wished. # @todo This should be moved to update-docker @@ -116,7 +116,7 @@ chdir: "{{docker_compose.directories.instance}}" when: mode_update | bool -- name: docker compose project setup +- name: docker compose up command: cmd: "docker-compose -p {{application_id}} up -d --remove-orphans" chdir: "{{docker_compose.directories.instance}}" diff --git a/roles/docker-mediawiki/tasks/main.yml b/roles/docker-mediawiki/tasks/main.yml index c96f3482..9e8748be 100644 --- a/roles/docker-mediawiki/tasks/main.yml +++ b/roles/docker-mediawiki/tasks/main.yml @@ -12,4 +12,4 @@ - name: add docker-compose.yml template: src=docker-compose.yml.j2 dest={{docker_compose.directories.instance}}docker-compose.yml - notify: docker compose project setup + notify: docker compose up diff --git a/roles/docker-mybb/tasks/main.yml b/roles/docker-mybb/tasks/main.yml index 9d53d22c..fc090955 100644 --- a/roles/docker-mybb/tasks/main.yml +++ b/roles/docker-mybb/tasks/main.yml @@ -20,10 +20,10 @@ template: src: "default.conf" dest: "{{docker_compose_instance_confd_defaultconf_file}}" - notify: docker compose project setup + notify: docker compose up - name: add docker-compose.yml template: src: "docker-compose.yml.j2" dest: "{{docker_compose.directories.instance}}docker-compose.yml" - notify: docker compose project setup + notify: docker compose up diff --git a/roles/docker-nextcloud/tasks/main.yml b/roles/docker-nextcloud/tasks/main.yml index ca3c5553..9294e97c 100644 --- a/roles/docker-nextcloud/tasks/main.yml +++ b/roles/docker-nextcloud/tasks/main.yml @@ -17,7 +17,7 @@ group: "{{nextcloud_docker_user_id}}" loop: "{{ lookup('fileglob', role_path ~ '/templates/config/*.j2', wantlist=True) }}" # Not all type of changes take instantly place. Due to this reason a rebuild is required. - notify: docker compose project setup + notify: docker compose up - name: "include role for {{application_id}} to receive certs & do modification routines" include_role: diff --git a/roles/docker-oauth2-proxy/tasks/main.yml b/roles/docker-oauth2-proxy/tasks/main.yml index aeb4581b..15156e59 100644 --- a/roles/docker-oauth2-proxy/tasks/main.yml +++ b/roles/docker-oauth2-proxy/tasks/main.yml @@ -3,4 +3,4 @@ src: oauth2-proxy-keycloak.cfg.j2 dest: "{{(path_docker_compose_instances | get_docker_compose(oauth2_proxy_application_id)).directories.volumes}}{{applications[application_id].configuration_file}}" notify: - - docker compose project setup \ No newline at end of file + - docker compose up \ No newline at end of file diff --git a/roles/docker-openproject/tasks/main.yml b/roles/docker-openproject/tasks/main.yml index 67415b54..0e239c19 100644 --- a/roles/docker-openproject/tasks/main.yml +++ b/roles/docker-openproject/tasks/main.yml @@ -21,7 +21,7 @@ src: Gemfile.plugins dest: "{{openproject_plugins_folder}}Gemfile.plugins" notify: - - docker compose project build and setup + - docker compose up - name: "include role docker-repository-setup for {{application_id}}" include_role: diff --git a/roles/docker-pgadmin/tasks/configuration.yml b/roles/docker-pgadmin/tasks/configuration.yml index d10c1e02..eeb386ff 100644 --- a/roles/docker-pgadmin/tasks/configuration.yml +++ b/roles/docker-pgadmin/tasks/configuration.yml @@ -10,7 +10,7 @@ src: servers.json.j2 dest: "{{ pgadmin_host_server_file }}" mode: "0644" - notify: docker compose project setup + notify: docker compose up - name: "Render .pgpass file" template: @@ -19,4 +19,4 @@ owner: "{{ pgadmin_user }}" group: "{{ pgadmin_group }}" mode: "0600" - notify: docker compose project setup + notify: docker compose up diff --git a/roles/docker-portfolio/tasks/main.yml b/roles/docker-portfolio/tasks/main.yml index a2a18222..5fbc611e 100644 --- a/roles/docker-portfolio/tasks/main.yml +++ b/roles/docker-portfolio/tasks/main.yml @@ -54,7 +54,7 @@ template: src: "{{ config_inventory_path }}" dest: "{{docker_repository_path}}/app/config.yaml" - notify: docker compose project setup + notify: docker compose up when: - run_once_docker_portfolio is not defined - config_file.stat.exists @@ -63,7 +63,7 @@ template: src: "config.yaml.j2" dest: "{{docker_repository_path}}/app/config.yaml" - notify: docker compose project setup + notify: docker compose up when: - run_once_docker_portfolio is not defined - not config_file.stat.exists @@ -72,7 +72,7 @@ template: src: docker-compose.yml.j2 dest: "{docker_compose.directories.instance}}docker-compose.yml" - notify: docker compose project setup + notify: docker compose up when: run_once_docker_portfolio is not defined - name: run the portfolio tasks once diff --git a/roles/docker-presentation/tasks/main.yml b/roles/docker-presentation/tasks/main.yml index b49e0382..92699953 100644 --- a/roles/docker-presentation/tasks/main.yml +++ b/roles/docker-presentation/tasks/main.yml @@ -9,7 +9,7 @@ name: pkgmgr-install vars: package_name: cymais-presentation - package_notify: docker compose project build and setup + package_notify: docker compose up - name: Get path of cymais-presentation using pkgmgr command: pkgmgr path cymais-presentation diff --git a/roles/docker-repository-setup/tasks/main.yml b/roles/docker-repository-setup/tasks/main.yml index 2c6c7dd3..f850a370 100644 --- a/roles/docker-repository-setup/tasks/main.yml +++ b/roles/docker-repository-setup/tasks/main.yml @@ -8,6 +8,6 @@ dest: "{{ docker_repository_path }}" update: yes notify: - - docker compose project setup + - docker compose up - rebuild docker repository become: true \ No newline at end of file diff --git a/roles/docker-roulette-wheel/tasks/main.yml b/roles/docker-roulette-wheel/tasks/main.yml index 8d15ad07..ccd3499a 100644 --- a/roles/docker-roulette-wheel/tasks/main.yml +++ b/roles/docker-roulette-wheel/tasks/main.yml @@ -8,7 +8,7 @@ repo: "https://github.com/kevinveenbirkenbach/roulette-wheel.git" dest: "{{app_path}}" update: yes - notify: docker compose project setup + notify: docker compose up become: true ignore_errors: true diff --git a/roles/docker-sphinx/tasks/main.yml b/roles/docker-sphinx/tasks/main.yml index 7c4aba0e..f2d161a2 100644 --- a/roles/docker-sphinx/tasks/main.yml +++ b/roles/docker-sphinx/tasks/main.yml @@ -9,7 +9,7 @@ name: pkgmgr-install vars: package_name: cymais-sphinx - package_notify: docker compose project build and setup + package_notify: docker compose up - name: Get path of cymais-sphinx using pkgmgr command: pkgmgr path cymais-sphinx diff --git a/roles/docker-sphinx/vars/main.yml b/roles/docker-sphinx/vars/main.yml index 7151792a..0ec382fa 100644 --- a/roles/docker-sphinx/vars/main.yml +++ b/roles/docker-sphinx/vars/main.yml @@ -1 +1 @@ -application_id: "sphinx" \ No newline at end of file +application_id: "sphinx" \ No newline at end of file diff --git a/roles/docker-syncope/tasks/main.yml b/roles/docker-syncope/tasks/main.yml index 78b36327..3b6e46fb 100644 --- a/roles/docker-syncope/tasks/main.yml +++ b/roles/docker-syncope/tasks/main.yml @@ -25,6 +25,6 @@ dest: "{{docker_compose.files.env}}" mode: '770' force: yes - notify: docker compose project setup + notify: docker compose up - include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/create-files.yml" diff --git a/roles/docker-taiga/tasks/main.yml b/roles/docker-taiga/tasks/main.yml index b296a552..b8096f60 100644 --- a/roles/docker-taiga/tasks/main.yml +++ b/roles/docker-taiga/tasks/main.yml @@ -19,13 +19,13 @@ src: "taiga/{{item}}.py.j2" dest: "{{ docker_compose.directories.config }}taiga-{{item}}.py" when: applications[application_id].features.oidc and applications[application_id].oidc.flavor == 'taigaio' - notify: docker compose project build and setup + notify: docker compose up loop: "{{ settings_files }}" - name: "create {{docker_compose_init}}" template: src: "docker-compose-inits.yml.j2" dest: "{{docker_compose_init}}" - notify: docker compose project build and setup + notify: docker compose up - include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/create-files.yml" diff --git a/roles/docker-wordpress/tasks/main.yml b/roles/docker-wordpress/tasks/main.yml index 15e7f803..7765eed0 100644 --- a/roles/docker-wordpress/tasks/main.yml +++ b/roles/docker-wordpress/tasks/main.yml @@ -17,13 +17,13 @@ template: src: upload.ini.j2 dest: "{{ docker_compose.directories.instance }}upload.ini" - notify: docker compose project build and setup + notify: docker compose up - name: "Transfering msmtprc to {{ host_msmtp_conf }}" template: src: "{{ playbook_dir }}/roles/msmtp/templates/msmtprc.conf.j2" dest: "{{ host_msmtp_conf }}" - notify: docker compose project build and setup + notify: docker compose up - include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/create-files.yml" diff --git a/roles/system-storage-optimizer/files/__init__.py b/roles/system-storage-optimizer/files/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/roles/system-storage-optimizer/files/system-storage-optimizer.py b/roles/system-storage-optimizer/files/system-storage-optimizer.py index 9b5d32a9..53f7b300 100644 --- a/roles/system-storage-optimizer/files/system-storage-optimizer.py +++ b/roles/system-storage-optimizer/files/system-storage-optimizer.py @@ -5,6 +5,7 @@ import sys import shutil import argparse + def run_command(command): """ Run a shell command and return its output """ print(command) @@ -12,37 +13,59 @@ def run_command(command): print(output) return output + def stop_containers(containers): """Stop a list of containers.""" container_list = ' '.join(containers) print(f"Stopping containers {container_list}...") run_command(f"docker stop {container_list}") + def start_containers(containers): """Start a list of containers.""" container_list = ' '.join(containers) print(f"Starting containers {container_list}...") run_command(f"docker start {container_list}") + def is_database(image): databases = {"postgres", "mariadb", "redis", "memcached", "mongo"} - # Split the string at the colon and take the first part prefix = image.split(':')[0] - # Check if the prefix is in the database names return prefix in databases + def is_symbolic_link(file_path): return os.path.islink(file_path) + def get_volume_path(volume): return run_command(f"docker volume inspect --format '{{{{ .Mountpoint }}}}' {volume}") + def get_image(container): return run_command(f"docker inspect --format='{{{{.Config.Image}}}}' {container}") + +def has_healthcheck(container): + """Check if a container has a HEALTHCHECK defined.""" + result = run_command( + f"docker inspect --format='{{{{json .State.Health}}}}' {container}" + ) + return result not in ("null", "") + + +def get_health_status(container): + """Return the health status.""" + status = run_command( + f"docker inspect --format='{{{{.State.Health.Status}}}}' {container}" + ) + return status + + def run_rsync(src, dest): run_command(f"rsync -aP --remove-source-files {src} {dest}") + def delete_directory(path): """Deletes a directory and all its contents.""" try: @@ -51,56 +74,68 @@ def delete_directory(path): except OSError as e: print(f"Error deleting directory {path}: {e}") + def pause_and_move(storage_path, volume, volume_path, containers): stop_containers(containers) - # Create a new directory on the Storage storage_volume_path = os.path.join(storage_path, 'data', 'docker', 'volumes', volume) - os.makedirs(storage_volume_path,exist_ok=False) - - # Move the data - run_rsync(f"{volume_path}/",f"{storage_volume_path}/") - - # Delete the source directory + os.makedirs(storage_volume_path, exist_ok=False) + run_rsync(f"{volume_path}/", f"{storage_volume_path}/") delete_directory(volume_path) - - # Create a symbolic link os.symlink(storage_volume_path, volume_path) - start_containers(containers) + def has_container_with_database(containers): for container in containers: - # Get the image of the container image = get_image(container) if is_database(image): return True return False - + if __name__ == "__main__": - # Argument parser setup - parser = argparse.ArgumentParser(description='Migrate Docker volumes to SSD or HDD based on container image.') - parser.add_argument('--rapid-storage-path', type=str, required=True, help='Path to the SSD storage') - parser.add_argument('--mass-storage-path', type=str, required=True, help='Path to the HDD storage') - - # Parse arguments + parser = argparse.ArgumentParser( + description='Migrate Docker volumes to SSD or HDD based on container image.' + ) + parser.add_argument( + '--rapid-storage-path', type=str, required=True, + help='Path to the SSD storage' + ) + parser.add_argument( + '--mass-storage-path', type=str, required=True, + help='Path to the HDD storage' + ) args = parser.parse_args() - # Set paths from arguments rapid_storage_path = args.rapid_storage_path mass_storage_path = args.mass_storage_path - # List all Docker volumes volumes = run_command("docker volume ls -q").splitlines() - + for volume in volumes: volume_path = get_volume_path(volume) - containers = run_command(f"docker ps -q --filter volume={volume}").splitlines() + containers = run_command( + f"docker ps -q --filter volume={volume}" + ).splitlines() + if not containers: print(f"Skipped Volume {volume}. It does not belong to a running container.") - elif is_symbolic_link(volume_path): + continue + if is_symbolic_link(volume_path): print(f"Skipped Volume {volume}. The storage path {volume_path} is a symbolic link.") - elif has_container_with_database(containers): + continue + + # Wait until containers with a healthcheck are healthy (not starting or unhealthy) + for container in containers: + if has_healthcheck(container): + status = get_health_status(container) + while status != 'healthy': + print(f"Warte auf Container {container}, Status '{status}'...") + time.sleep(1) + status = get_health_status(container) + + # Proceed with migration + if has_container_with_database(containers): print(f"Safing volume {volume} on SSD.") pause_and_move(rapid_storage_path, volume, volume_path, containers) else: @@ -108,4 +143,3 @@ if __name__ == "__main__": pause_and_move(mass_storage_path, volume, volume_path, containers) print("Operation completed.") - \ No newline at end of file diff --git a/tasks/utils/update-repository-with-files.yml b/tasks/utils/update-repository-with-files.yml index be4af438..c69479f1 100644 --- a/tasks/utils/update-repository-with-files.yml +++ b/tasks/utils/update-repository-with-files.yml @@ -29,7 +29,7 @@ repo: "{{ docker_repository_address }}" dest: "{{ docker_repository_directory | default(docker_compose.directories.instance) }}" update: yes - notify: docker compose project setup + notify: docker compose up become: true - name: "restore detached files" @@ -45,4 +45,4 @@ src: "{{ item }}.j2" dest: "{{docker_compose.directories.instance}}{{ item }}" loop: "{{ detached_files }}" - notify: docker compose project setup + notify: docker compose up diff --git a/tests/unit/test_storage_optimizer.py b/tests/unit/test_storage_optimizer.py new file mode 100644 index 00000000..863a58d6 --- /dev/null +++ b/tests/unit/test_storage_optimizer.py @@ -0,0 +1,56 @@ +import unittest +from unittest.mock import patch +import os +import importlib.util +import sys + +# Dynamically load the module under test from the Ansible role's files directory +def load_optimizer_module(): + module_path = os.path.abspath(os.path.join( + os.path.dirname(__file__), + '..', "..", 'roles', 'system-storage-optimizer', 'files', 'system-storage-optimizer.py' + )) + spec = importlib.util.spec_from_file_location('storage_optimizer', module_path) + optimizer = importlib.util.module_from_spec(spec) + # Register module so patch('storage_optimizer...') works + sys.modules[spec.name] = optimizer + spec.loader.exec_module(optimizer) + return optimizer + +storage_optimizer = load_optimizer_module() + +class TestHealthCheckLogic(unittest.TestCase): + + def setUp(self): + # Prevent sleeping delays in inline loops + patcher = patch('storage_optimizer.time.sleep', return_value=None) + self.sleep_patcher = patcher.start() + self.addCleanup(self.sleep_patcher.stop) + + @patch('storage_optimizer.run_command') + def test_has_healthcheck_true(self, mock_run): + mock_run.return_value = '{"Status":"starting","FailingStreak":0}' + self.assertTrue(storage_optimizer.has_healthcheck('container-id')) + + @patch('storage_optimizer.run_command') + def test_has_healthcheck_false(self, mock_run): + mock_run.return_value = 'null' + self.assertFalse(storage_optimizer.has_healthcheck('container-id')) + + @patch('storage_optimizer.get_image') + def test_has_container_with_database(self, mock_get_image): + test_cases = [ + ('postgres:13', True), + ('mariadb:latest', True), + ('redis:6', True), + ('mongo:4', True), + ('nginx:alpine', False), + ] + for image_name, expected in test_cases: + mock_get_image.return_value = image_name + result = storage_optimizer.has_container_with_database(['any']) + self.assertEqual(result, expected, + f"Image {image_name} expected {expected}") + +if __name__ == '__main__': + unittest.main()