import os.path import docker import restic import yaml import json import configparser bindingIncludePattern = None bindingExcludePattern = None remoteHost = None remoteUser = None repository = None passwordFile = None def read_config_file(filepath: str = "configuration.ini"): parser = configparser.ConfigParser() parser.read(filepath) global bindingIncludePattern global bindingExcludePattern global remoteHost global remoteUser global repository global passwordFile bindingIncludePattern = parser["backandup"]["bindincludepattern"] bindingExcludePattern = parser["backandup"]["bindexcludepattern"] remoteHost = parser["restic"]["remotehost"] remoteUser = parser["restic"]["remoteuser"] repository = parser["restic"]["repository"] passwordFile = parser["restic"]["passwordfile"] def do_backup(dirpath: str, tags: str = None): restic.repository = f"sftp:{remoteUser}@{remoteHost}:/{repository}" restic.password_file = passwordFile if tags is None: restic.backup(paths=[dirpath]) else: restic.backup(paths=[dirpath], tags=[]) def backup_ct_binds(ct: docker.models.containers.Container, includepattern: str | list = None, excludepattern: str | list = None): if ct.attrs['HostConfig']['Binds'] is None: print(" Nothing to backup") return 0 if type(includepattern) is str: includepattern = [includepattern] if type(excludepattern) is str: excludepattern = [excludepattern] if includepattern is None: includepattern = ["NOPATTERNTOINCLUDEXXXXXX"] if excludepattern is None: excludepattern = ["NOPATTERNTOEXCLUDEXXXXXX"] for BindMount in ct.attrs['HostConfig']['Binds']: BindPath = BindMount.split(":")[0] print(f" {BindPath}") # Si on matche au moins 1 pattern d'inclusion ET aucun pattern d'exclusion if any(x in BindPath for x in includepattern) and not any(x in BindPath for x in excludepattern): do_backup(BindPath) else: print(" not a directory to backup") return 0 def stop_backup_restart_container(ct: docker.models.containers.Container): print(ct.name) dorestart = False if ct.attrs['State']['Status'] == 'running': print(" stop the container") dorestart = True ct.stop() backup_ct_binds(ct, bindingIncludePattern, bindingExcludePattern) if dorestart: print(" start the container") ct.start() return 0 def yaml_to_services_list(filepath: str) -> dict|None: ''' Retourne la liste des services définis dans un fichier docker-compose.yaml sous forme de liste :param filepath: str :return: list|None ''' print(f"sorting services from {filepath}") serviceslist = dict() if not os.path.isfile(filepath): raise OSError(2, "No such file", filepath) ycontent = None with open(filepath, 'r') as yfile: try: ycontent = yaml.safe_load(yfile) except yaml.YAMLError as yerror: print(f"{filepath} doesn't seems to be a yaml file.") raise yerror if ycontent is None: return None if "services" not in ycontent: raise Exception("The file doesn't seems to be a correct docker-compose yaml") # on liste les services et ceux dont ils dépendent for servicename in ycontent["services"]: print(f"appending {servicename}") mustbefore = [] if "depends_on" in ycontent["services"][servicename]: for dependencie in ycontent["services"][servicename]["depends_on"]: mustbefore.append(dependencie) serviceslist[servicename] = {"must_before": mustbefore, "must_after": [], "backupdone": False} # on reparcourt une seconde fois pour définir de quels services ilssont les dépendances for service in serviceslist: if len(serviceslist[service]["must_before"]): for dependencie in serviceslist[service]["must_before"]: if service not in serviceslist[dependencie]["must_after"]: serviceslist[dependencie]["must_after"].append(service) return serviceslist def order_services_by_dependencies(servicesdict: dict) -> list: ''' Génère une liste avec les noms de services pour ordonner leurs coupures par dépendances. Les containers étant requis seront placés avant ceux les requérant. :param servicesdict: dict :return: list ''' serviceslist = [] for service in servicesdict: if len(servicesdict[service]["must_after"]) and len(serviceslist): depindex = len(serviceslist) -1 for dependencieof in servicesdict[service]["must_after"]: if dependencieof in serviceslist and serviceslist.index(dependencieof) < depindex: depindex = serviceslist.index(dependencieof) serviceslist.insert(service, depindex) else: serviceslist.append(service) return serviceslist def yaml_file_to_container_list(filepath: str) -> list|None: ''' Retourne le contenu d'un fichier yaml sous forme de dictionnaire :param filepath: str :return: dict|None ''' pass # dependances = [] # if "depends_on" in ycontent["services"][servicename]: # dependances = ycontent["services"][servicename]["depends_on"] # # bindslist = [] # volumeslist = [] # for mount in ycontent["services"][servicename]["volumes"]: # localpart = mount.split(":") # # TODO: adapter avec pathlib pour gérer Windows # # si un /, signifie qu'il s'agit d'un chemin et donc d'un bind mount, pas volume. # if localpart.find("/") != -1: # bindslist.append(localpart) # else: # volumeslist.append(localpart) # # containersid = os.popen(f"docker-compose --file {filepath} ps --quiet {servicename}").readlines() # containersnames = os.popen(f"docker-compose --file {filepath} ps {servicename} | tail -n +3").readlines() # # containerslist = [] # for i in range(0, len(containersid)): # containerslist.append((containersnames[i], containersid[i])) # # for containerfound in containerslist: # container = {"service": servicename, # "name": containerfound[0], # "id": containerfound[1], # "binds": bindslist, # "depends_on": dependances} # Press the green button in the gutter to run the script. if __name__ == '__main__': dockerhost = docker.from_env() bindcludepattern = None bindexcludepattern = None composefile = "/root/compose/core/docker-compose.yml" remotehost = None remoteuser = None repository = None passwordfile = None #read_config_file() servicesDict = yaml_to_services_list(composefile) servicesList = order_services_by_dependencies(servicesDict) print(servicesList)