mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-06-24 19:25:32 +02:00
Compare commits
2 Commits
fedaa02067
...
380aa4a37b
Author | SHA1 | Date | |
---|---|---|---|
380aa4a37b | |||
4fbf8f505c |
@ -22,4 +22,4 @@
|
||||
dest: "{{docker_compose.files.env}}"
|
||||
mode: '770'
|
||||
force: yes
|
||||
notify: docker compose project setup
|
||||
notify: docker compose up
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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:
|
||||
|
@ -1,8 +1,8 @@
|
||||
- name: "Create (optional) '{{ docker_compose.files.dockerfile }}'"
|
||||
template:
|
||||
src: "{{ playbook_dir }}/roles/{{ role_name }}/templates/Dockerfile"
|
||||
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
|
||||
|
@ -1,4 +1,5 @@
|
||||
# Base for docker services
|
||||
{# Base for docker services #}
|
||||
|
||||
restart: {{docker_restart_policy}}
|
||||
env_file:
|
||||
- "{{docker_compose.files.env}}"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
notify: docker compose up
|
@ -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"
|
||||
|
||||
|
44
roles/docker-mariadb/defaults/README.md
Normal file
44
roles/docker-mariadb/defaults/README.md
Normal file
@ -0,0 +1,44 @@
|
||||
# defaults/
|
||||
|
||||
This directory contains default variable definition files for the `docker-mariadb` Ansible role. It centralizes all configurable values related to MariaDB deployment and can be adjusted without modifying task logic.
|
||||
|
||||
---
|
||||
|
||||
## files and their purpose
|
||||
|
||||
### `main.yml`
|
||||
|
||||
Defines default values for how the MariaDB database should be created.
|
||||
|
||||
* **`database_encoding`** (string):
|
||||
|
||||
* **Default:** `"utf8mb4"`
|
||||
* **Reasoning:**
|
||||
|
||||
* **Full Unicode support**: `utf8mb4` is the only MySQL/MariaDB character set that fully implements 4‑byte UTF‑8, allowing storage of emojis, supplementary symbols, and all global scripts without data loss.
|
||||
* **Future‑proof:** Modern applications and standards have converged on UTF‑8; using `utf8mb4` avoids migration challenges later.
|
||||
* **Performance trade‑off:** While slightly more storage might be used compared to `latin1`, the universality of `utf8mb4` outweighs the cost for most deployments.
|
||||
|
||||
* **`database_collation`** (string):
|
||||
|
||||
* **Default:** `"utf8mb4_unicode_ci"`
|
||||
* **Reasoning:**
|
||||
|
||||
* **Accurate sorting & comparison:** This collation uses full Unicode algorithm rules, ensuring linguistically correct comparisons across many languages.
|
||||
* **Case‑insensitive (`ci`):** Most web apps expect case‑insensitive matching for usernames, emails, and search queries, improving usability.
|
||||
* **Neutral choice:** Unlike language‑specific collations, `unicode_ci` works robustly in multilingual contexts without bias.
|
||||
|
||||
> **Tip:** If you have a legacy application requiring a different charset or collation (e.g., for backward compatibility with existing data), simply override `database_encoding` and `database_collation` in your playbook-level variables.
|
||||
|
||||
## Overriding default variables
|
||||
|
||||
To customize any of these values without editing role defaults:
|
||||
|
||||
1. Create or update a playbook-level vars file (e.g. `group_vars/all/docker-mariadb.yml`).
|
||||
2. Set the desired values, for example:
|
||||
|
||||
```yaml
|
||||
database_encoding: "latin1"
|
||||
database_collation: "latin1_swedish_ci"
|
||||
```
|
||||
3. Run your playbook—Ansible’s variable precedence ensures your overrides take effect.
|
3
roles/docker-mariadb/defaults/main.yml
Normal file
3
roles/docker-mariadb/defaults/main.yml
Normal file
@ -0,0 +1,3 @@
|
||||
# Check out the README.md file for more information, why this encodings and collations are used
|
||||
database_encoding: "utf8mb4"
|
||||
database_collation: "utf8mb4_unicode_ci"
|
@ -53,12 +53,14 @@
|
||||
|
||||
- name: "Create database: {{ database_name }}"
|
||||
mysql_db:
|
||||
name: "{{ database_name }}"
|
||||
state: present
|
||||
login_user: root
|
||||
name: "{{ database_name }}"
|
||||
state: present
|
||||
login_user: root
|
||||
login_password: "{{ applications.mariadb.credentials.root_password }}"
|
||||
login_host: 127.0.0.1
|
||||
login_port: "{{database_port}}"
|
||||
login_host: 127.0.0.1
|
||||
login_port: "{{ database_port }}"
|
||||
encoding: "{{ database_encoding }}"
|
||||
collation: "{{ database_collation }}"
|
||||
|
||||
- name: "Create database user: {{ database_username }}"
|
||||
mysql_user:
|
||||
|
34
roles/docker-mariadb/vars/README.md
Normal file
34
roles/docker-mariadb/vars/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
# vars/
|
||||
|
||||
This directory contains variable definition files for the `docker-mariadb` Ansible role. It centralizes all configurable values related to MariaDB deployment and can be adjusted without modifying task logic.
|
||||
|
||||
---
|
||||
|
||||
## files and their purpose
|
||||
|
||||
### 1. `configuration.yml`
|
||||
|
||||
Contains configuration values that determine which Docker image version to use and what hostname the container will be registered under.
|
||||
|
||||
* **`version`** (string):
|
||||
|
||||
* Default: `"latest"`
|
||||
* The MariaDB image tag to pull (e.g. `10.6`, `10.11`, or `latest`).
|
||||
|
||||
* **`hostname`** (string):
|
||||
|
||||
* Default: `"central-mariadb"`
|
||||
* The container name and DNS alias within the `central_mariadb` network. Used by other services (like Moodle) to connect.
|
||||
|
||||
> **Tip:** Pin to a specific minor version (e.g., `10.6.12`) in production to avoid breaking changes on rebuilds.
|
||||
|
||||
---
|
||||
|
||||
### 2. `main.yml`
|
||||
|
||||
Minimal file defining the application identifier for the role.
|
||||
|
||||
* **`application_id`** (string):
|
||||
|
||||
* Default: `"mariadb"`
|
||||
* Logical name used in templates, notifications, or paths when multiple roles/services may coexist.
|
@ -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}}"
|
||||
|
@ -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
|
||||
|
28
roles/docker-moodle/Administration.md
Normal file
28
roles/docker-moodle/Administration.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Administration
|
||||
|
||||
# Radical Erase of Setup
|
||||
To manually erase the full moodle setup inkluding all data execute:
|
||||
|
||||
**CLI:**
|
||||
|
||||
```bash
|
||||
cd /opt/docker/moodle && \
|
||||
docker compose down -v || {
|
||||
echo "docker compose down failed, cleaning up manually"
|
||||
rm -rv /mnt/hdd/data/docker/volumes/moodle_*
|
||||
docker compose down -v
|
||||
} && \
|
||||
rm -rv /opt/docker/moodle
|
||||
```
|
||||
|
||||
Afterwards login to the database and execute
|
||||
|
||||
**MariaDB:**
|
||||
```sql
|
||||
DROP DATABASE IF EXISTS moodle;
|
||||
```
|
||||
|
||||
to delete all data in the database related to this role.
|
||||
|
||||
# Virgin Setup
|
||||
After the installation you can rerun this role to create a fresh setup of Moodle.
|
@ -1,3 +1,2 @@
|
||||
# Todo
|
||||
- Check if sendmail needs to be installed. See [Issue](https://github.com/bitnami/containers/issues/63311).
|
||||
- Solve [issue](https://github.com/bitnami/containers/issues/72747)
|
||||
- Check if sendmail needs to be installed. See [Issue](https://github.com/bitnami/containers/issues/63311).
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
- docker compose up
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -8,6 +8,6 @@
|
||||
dest: "{{ docker_repository_path }}"
|
||||
update: yes
|
||||
notify:
|
||||
- docker compose project setup
|
||||
- docker compose up
|
||||
- rebuild docker repository
|
||||
become: true
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -1 +1 @@
|
||||
application_id: "sphinx"
|
||||
application_id: "sphinx"
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
||||
|
0
roles/system-storage-optimizer/files/__init__.py
Normal file
0
roles/system-storage-optimizer/files/__init__.py
Normal file
@ -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.")
|
||||
|
@ -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
|
||||
|
56
tests/unit/test_storage_optimizer.py
Normal file
56
tests/unit/test_storage_optimizer.py
Normal file
@ -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()
|
Loading…
x
Reference in New Issue
Block a user