You've already forked configurator
mirror of
https://github.com/armbian/configurator.git
synced 2026-01-06 10:36:02 -08:00
When using directories, the docker images will start generaties files inside these directories, with root privileges. These files cannot be removed by the user, even though he owned the directory. So I decided to use volumes instead, since this alleviate the issue. I'll add a volume management API letter. For the time being, you'll have to rely on docker-compose volume commands to manage them. Signed-off-by: Myy Miouyouyou <myy@miouyouyou.fr>
254 lines
9.5 KiB
Python
Executable File
254 lines
9.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
from abc import ABC, abstractmethod
|
|
from argparse import ArgumentParser, RawDescriptionHelpFormatter
|
|
import logging
|
|
from pathlib import Path
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
main_logger = logging.getLogger('main')
|
|
main_logger.setLevel(logging.DEBUG)
|
|
main_logger.addHandler(logging.StreamHandler())
|
|
|
|
# TODO Move to library
|
|
def list_dirs(from_dir=".", containing_files=[]) -> list:
|
|
entries = None
|
|
print(f'Check {from_dir} with files {containing_files}')
|
|
try:
|
|
entries = os.scandir(from_dir)
|
|
except Exception as e:
|
|
print(e)
|
|
return []
|
|
dirs = []
|
|
for entry in entries:
|
|
if entry.is_dir():
|
|
directory = entry.name
|
|
all_files_present = True
|
|
for file_to_search in containing_files:
|
|
checked_file_path = os.path.join(os.path.join(from_dir,directory), file_to_search)
|
|
file_exist = os.path.exists(checked_file_path)
|
|
main_logger.debug(f'Does {checked_file_path} exist ? {file_exist}')
|
|
all_files_present &= file_exist
|
|
|
|
if all_files_present:
|
|
dirs.append(directory)
|
|
return dirs
|
|
|
|
class SystemServicesHandler(ABC):
|
|
@abstractmethod
|
|
def start(self, service_name:str):
|
|
raise NotImplementedError
|
|
|
|
@abstractmethod
|
|
def stop(self, service_name:str):
|
|
raise NotImplementedError
|
|
|
|
@abstractmethod
|
|
def status(self, service_name:str):
|
|
raise NotImplementedError
|
|
|
|
@abstractmethod
|
|
def add_on_boot(self, service_name:str):
|
|
raise NotImplementedError
|
|
|
|
@abstractmethod
|
|
def remove_from_boot(self, service_name:str):
|
|
raise NotImplementedError
|
|
|
|
class SystemDServicesHandler(SystemServicesHandler):
|
|
|
|
def systemd(self, action_name:str, service_name:str):
|
|
print(subprocess.run(["systemctl", action_name, service_name]))
|
|
|
|
def start(self, service_name:str):
|
|
self.systemd("start", service_name)
|
|
|
|
def stop(self, service_name:str):
|
|
self.systemd("stop", service_name)
|
|
|
|
def status(self, service_name:str):
|
|
self.systemd("status", service_name)
|
|
|
|
def add_on_boot(self, service_name:str):
|
|
self.systemd("enable", service_name)
|
|
|
|
def remove_from_boot(self, service_name:str):
|
|
self.systemd("disable", service_name)
|
|
|
|
class DockerComposeModule:
|
|
MAIN_PATH=Path('/opt/armbian/docker')
|
|
DOCKER_SERVICE_NAME="docker"
|
|
|
|
# configuration :
|
|
# install_dir: Path where docker services should be installed
|
|
# templates_dir: Path where docker services are available from
|
|
|
|
def __init__(self, configuration):
|
|
self.configuration = configuration
|
|
configuration["install_dir"] = Path(configuration["install_dir"])
|
|
configuration["templates_dir"] = Path(configuration["templates_dir"])
|
|
self.logger = logging.getLogger('docker-compose module')
|
|
self.logger.setLevel(logging.DEBUG)
|
|
self.logger.addHandler(logging.StreamHandler())
|
|
self.services_handler = SystemDServicesHandler()
|
|
|
|
def installed_path(self, service_name:str):
|
|
return (self.configuration["install_dir"] / service_name)
|
|
|
|
@staticmethod
|
|
def list_installed(configuration) -> list:
|
|
return list_dirs(configuration["install_dir"], ["docker-compose.yml"])
|
|
|
|
@staticmethod
|
|
def list_available(configuration) -> list:
|
|
return list_dirs(configuration["templates_dir"], ["docker-compose.yml"])
|
|
|
|
# TODO Check if the service name is actually available ?
|
|
def docker_install(self, services_names:list):
|
|
if not services_names:
|
|
self.logger.debug('No docker to install')
|
|
return
|
|
|
|
install_path = self.configuration["install_dir"]
|
|
templates_path = self.configuration["templates_dir"]
|
|
|
|
try:
|
|
install_path.mkdir( mode=0o755, parents=True, exist_ok=True )
|
|
except Exception as e:
|
|
self.logger.error(f'mkdir -p {install_path} failed: ')
|
|
self.logger.error(e)
|
|
sys.exit(1)
|
|
|
|
for service_name in services_names:
|
|
shutil.copytree((templates_path / service_name), (install_path / service_name))
|
|
|
|
def docker_remove(self, services_names:list):
|
|
if not services_names:
|
|
self.logger.debug('No docker to remove')
|
|
return
|
|
|
|
for docker_compose_service in services_names:
|
|
remove_path = self.installed_path(docker_compose_service)
|
|
if remove_path.exists():
|
|
try:
|
|
shutil.rmtree(remove_path)
|
|
except Exception as e:
|
|
self.logger.error(f'Could not delete {remove_path}')
|
|
continue
|
|
else:
|
|
self.logger.error(f'{remove_path} does not exist. Skipping {docker_compose_service}')
|
|
continue
|
|
|
|
def service_path(self, service_name: str, specific_file_path: str) -> Path:
|
|
returned_path = Path(self.MAIN_PATH / service_name)
|
|
if specific_file_path:
|
|
returned_path = Path(returned_path / specific_file_path)
|
|
return returned_path
|
|
|
|
def output_script_result(self, tool_name: str, args: []):
|
|
self.logger.debug(args)
|
|
print(subprocess.run([f'tools/{tool_name}'] + args))
|
|
|
|
def docker_service_start(self):
|
|
self.services_handler.start(self.configuration['service_name'])
|
|
|
|
def docker_service_stop(self):
|
|
self.services_handler.stop(self.configuration['service_name'])
|
|
|
|
def docker_service_status(self):
|
|
self.services_handler.status(self.configuration['service_name'])
|
|
|
|
def docker_service_add_on_boot(self):
|
|
self.services_handler.add_on_boot(self.configuration['service_name'])
|
|
|
|
def docker_service_remove_from_boot(self):
|
|
self.services_handler.remove_from_boot(self.configuration['service_name'])
|
|
|
|
# TODO Factorize
|
|
def compose_status(self, services_names: list):
|
|
if not services_names:
|
|
self.logger.debug('No services provided for status')
|
|
return
|
|
|
|
for service_name in services_names:
|
|
self.logger.info(f'Showing {service_name} status')
|
|
self.output_script_result(
|
|
tool_name = "compose_status.sh",
|
|
args = [self.service_path(service_name, "docker-compose.yml")])
|
|
|
|
def compose_start(self, services_names: list):
|
|
if not services_names:
|
|
self.logger.debug('No services provided for start')
|
|
return
|
|
|
|
for service_name in services_names:
|
|
self.logger.info(f'Starting {service_name}')
|
|
self.output_script_result(
|
|
tool_name = "compose_start.sh",
|
|
args = [self.service_path(service_name, "docker-compose.yml")])
|
|
|
|
def compose_stop(self, services_names: list):
|
|
if not services_names:
|
|
self.logger.debug('No services provided for stop')
|
|
return
|
|
|
|
for service_name in services_names:
|
|
self.logger.info(f'Stopping {service_name}')
|
|
self.output_script_result(
|
|
tool_name = "compose_stop.sh",
|
|
args = [self.service_path(service_name, "docker-compose.yml")])
|
|
|
|
if __name__ == '__main__':
|
|
default_config = {
|
|
'templates_dir': '/usr/share/armbian/configurator/modules/docker/softwares',
|
|
'install_dir': '/opt/armbian/docker',
|
|
'service_name': 'docker',
|
|
}
|
|
config = default_config.copy()
|
|
available_dockers = DockerComposeModule.list_available(config)
|
|
installed_dockers = DockerComposeModule.list_installed(config)
|
|
|
|
parser = ArgumentParser(
|
|
description='Armbian docker installation module',
|
|
formatter_class=RawDescriptionHelpFormatter,
|
|
epilog=f'AVAILABLE_IMAGE : {available_dockers}\nINSTALLED_IMAGE : {installed_dockers}')
|
|
parser.add_argument('--install', nargs='+', metavar='AVAILABLE_IMAGE', choices=available_dockers, help='Add docker installations')
|
|
parser.add_argument('--remove', nargs='+', metavar='INSTALLED_IMAGE', choices=installed_dockers, help='Remove installed dockers')
|
|
parser.add_argument('--start', nargs='+', metavar='INSTALLED_IMAGE', choices=installed_dockers, help='Start selected installed docker-compose images')
|
|
parser.add_argument('--stop', nargs='+', metavar='INSTALLED_IMAGE', choices=installed_dockers, help='Stop selected installed docker-compose images')
|
|
parser.add_argument('--status', nargs='+', metavar='INSTALLED_IMAGE', choices=installed_dockers, help='Status of installed dockers')
|
|
#parser.add_argument('--edit', nargs=1, metavar='INSTALLED_IMAGE', choices=installed_dockers, help='Edit an installed docker-compose image YAML file')
|
|
parser.add_argument('--service', nargs=1, choices=['start', 'stop', 'status', 'enable', 'disable'], help='Manage the docker service')
|
|
|
|
args = parser.parse_args()
|
|
main_logger.debug(args.install)
|
|
module = DockerComposeModule(config)
|
|
if args.remove:
|
|
module.docker_remove(args.remove)
|
|
if args.install:
|
|
new_installs = [i for i in args.install if i not in module.list_installed(config)]
|
|
module.docker_install(new_installs)
|
|
#if args.edit:
|
|
# module.edit_configuration_files(args.edit)
|
|
if args.service:
|
|
actions = {
|
|
'start': module.docker_service_start,
|
|
'stop': module.docker_service_stop,
|
|
'status': module.docker_service_status,
|
|
'enable': module.docker_service_add_on_boot,
|
|
'disable': module.docker_service_remove_from_boot
|
|
}
|
|
actions[args.service[0]]()
|
|
if args.stop:
|
|
module.compose_stop(args.stop)
|
|
|
|
if args.start:
|
|
module.compose_start(args.start)
|
|
|
|
if args.status:
|
|
module.compose_status(args.status)
|
|
|