2024-01-09 12:47:00 +01:00
|
|
|
import subprocess
|
|
|
|
import os
|
2024-01-10 15:51:59 +01:00
|
|
|
import time
|
|
|
|
import sys
|
2024-01-09 12:47:00 +01:00
|
|
|
import shutil
|
2024-01-09 18:31:44 +01:00
|
|
|
import argparse
|
2024-01-09 12:47:00 +01:00
|
|
|
|
|
|
|
def run_command(command):
|
|
|
|
""" Run a shell command and return its output """
|
2024-01-10 15:51:59 +01:00
|
|
|
print(command)
|
|
|
|
output = subprocess.check_output(command, shell=True).decode('utf-8').strip()
|
|
|
|
print(output)
|
|
|
|
return output
|
2024-01-09 12:47:00 +01:00
|
|
|
|
2024-01-09 14:20:30 +01:00
|
|
|
def stop_containers(containers):
|
|
|
|
"""Stop a list of containers."""
|
|
|
|
container_list = ' '.join(containers)
|
|
|
|
print(f"Stopping containers {container_list}...")
|
2024-01-09 18:31:44 +01:00
|
|
|
run_command(f"docker stop {container_list}")
|
|
|
|
|
2024-01-09 14:20:30 +01:00
|
|
|
def start_containers(containers):
|
|
|
|
"""Start a list of containers."""
|
|
|
|
container_list = ' '.join(containers)
|
2024-01-09 18:31:44 +01:00
|
|
|
print(f"Starting containers {container_list}...")
|
|
|
|
run_command(f"docker start {container_list}")
|
2024-01-09 14:20:30 +01:00
|
|
|
|
|
|
|
def is_database(image):
|
2024-01-13 16:52:36 +01:00
|
|
|
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
|
2024-01-09 14:20:30 +01:00
|
|
|
|
|
|
|
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}")
|
|
|
|
|
2024-01-09 18:31:44 +01:00
|
|
|
def get_image(container):
|
2024-01-09 14:20:30 +01:00
|
|
|
return run_command(f"docker inspect --format='{{{{.Config.Image}}}}' {container}")
|
|
|
|
|
2024-01-10 15:51:59 +01:00
|
|
|
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:
|
|
|
|
shutil.rmtree(path)
|
|
|
|
print(f"Directory {path} was successfully deleted.")
|
|
|
|
except OSError as e:
|
|
|
|
print(f"Error deleting directory {path}: {e}")
|
|
|
|
|
2024-01-09 18:31:44 +01:00
|
|
|
def pause_and_move(storage_path, volume, volume_path, containers):
|
2024-01-09 14:20:30 +01:00
|
|
|
stop_containers(containers)
|
|
|
|
# Create a new directory on the Storage
|
2024-01-10 12:19:19 +01:00
|
|
|
storage_volume_path = os.path.join(storage_path, 'data', 'docker', 'volumes', volume)
|
2024-01-10 14:00:43 +01:00
|
|
|
os.makedirs(storage_volume_path,exist_ok=False)
|
2024-01-09 14:20:30 +01:00
|
|
|
|
|
|
|
# Move the data
|
2024-01-10 20:42:41 +01:00
|
|
|
run_rsync(f"{volume_path}/",f"{storage_volume_path}/")
|
2024-01-10 14:00:43 +01:00
|
|
|
|
2024-01-10 15:51:59 +01:00
|
|
|
# Delete the source directory
|
|
|
|
delete_directory(volume_path)
|
2024-01-09 14:20:30 +01:00
|
|
|
|
|
|
|
# Create a symbolic link
|
|
|
|
os.symlink(storage_volume_path, volume_path)
|
|
|
|
|
|
|
|
start_containers(containers)
|
|
|
|
|
2024-01-09 18:44:52 +01:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2024-01-09 18:27:43 +01:00
|
|
|
if __name__ == "__main__":
|
|
|
|
# Argument parser setup
|
|
|
|
parser = argparse.ArgumentParser(description='Migrate Docker volumes to SSD or HDD based on container image.')
|
2024-01-10 10:33:39 +01:00
|
|
|
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')
|
2024-01-09 14:20:30 +01:00
|
|
|
|
2024-01-09 18:27:43 +01:00
|
|
|
# Parse arguments
|
|
|
|
args = parser.parse_args()
|
2024-01-09 12:47:00 +01:00
|
|
|
|
2024-01-09 18:27:43 +01:00
|
|
|
# Set paths from arguments
|
2024-01-10 10:33:39 +01:00
|
|
|
rapid_storage_path = args.rapid_storage_path
|
|
|
|
mass_storage_path = args.mass_storage_path
|
2024-01-09 12:47:00 +01:00
|
|
|
|
2024-01-09 18:27:43 +01:00
|
|
|
# List all Docker volumes
|
|
|
|
volumes = run_command("docker volume ls -q").splitlines()
|
|
|
|
|
|
|
|
for volume in volumes:
|
|
|
|
volume_path = get_volume_path(volume)
|
2024-01-10 14:00:43 +01:00
|
|
|
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):
|
2024-01-09 18:27:43 +01:00
|
|
|
print(f"Skipped Volume {volume}. The storage path {volume_path} is a symbolic link.")
|
2024-01-09 18:44:52 +01:00
|
|
|
elif has_container_with_database(containers):
|
2024-01-09 18:49:46 +01:00
|
|
|
print(f"Safing volume {volume} on SSD.")
|
2024-01-10 10:33:39 +01:00
|
|
|
pause_and_move(rapid_storage_path, volume, volume_path, containers)
|
2024-01-09 18:44:52 +01:00
|
|
|
else:
|
2024-01-09 18:49:46 +01:00
|
|
|
print(f"Safing volume {volume} on HDD.")
|
2024-01-10 10:33:39 +01:00
|
|
|
pause_and_move(mass_storage_path, volume, volume_path, containers)
|
2024-01-09 18:49:46 +01:00
|
|
|
|
2024-01-09 18:27:43 +01:00
|
|
|
print("Operation completed.")
|
|
|
|
|