#!/usr/bin/env python3 ################################################################################ # BSD 3-Clause License # # Copyright © 2020-2021 Aurélien Grimal - aurelien.grimal@tech-tips.fr # All rights reserved. # # Please see license file on root of this directory ################################################################################ # !! GIT FILE !! # https://framagit.org/zorval/scripts/check-nrpe ################################################################################ # Import des modules import sys try: import hashlib, platform, os, shutil, argparse, subprocess as sp, re except Exception as e: print("[CRITICAL] Impossible d'importer les modules", e) sys.exit(2) # Test si le script est bien lancé en root if os.geteuid() != 0: print("[CRITICAL] Il faut être root pour lancer cette commande") sys.exit(2) # Variables config_dir = '/srv/git/conf' common_config_dir = 'common' wildcard = '__' git_monitored_repos_file = '/etc/zorval/git-monitored-repos' try: with open('/etc/hostname', 'r') as etc_hostname: hostname = etc_hostname.read().rstrip() except Exception: print('[CRITICAL] Erreur lors de la lecture du fichier /etc/hostname') # Fonctions def hash_bytestr_iter(bytesiter, hasher, ashexstr=False): for block in bytesiter: hasher.update(block) return hasher.hexdigest() if ashexstr else hasher.digest() def file_as_blockiter(afile, blocksize=65536): with afile: block = afile.read(blocksize) while len(block) > 0: yield block block = afile.read(blocksize) def md5sum(file_path): return hash_bytestr_iter(file_as_blockiter(open(file_path, 'rb')), hashlib.md5()) def walk_git_conf(directory, level=1): directory = directory.rstrip(os.path.sep) if not os.path.isdir(directory): print("[CRITICAL] Le répertoire", directory, "n'existe pas") sys.exit(2) num_sep = directory.count(os.path.sep) # Au moment du tri, on remplace '__' par '###' car dans l'ordre ASCII, le caractère _ est placé après les lettres # Or pour l'application de la précédence, il doit se trouver avant for root_abs, dirs, files in sorted(os.walk(directory), key=lambda d: d[0].replace("__", "###")): root_abs = root_abs.replace("###", "__") num_sep_this = root_abs.count(os.path.sep) if num_sep_this >= num_sep + 2 and root_abs.split(os.path.sep)[num_sep + 2] == '.git': continue if num_sep_this == 6: root_rel = os.path.basename(root_abs) root_rel_ignore = re.sub('.ignore$', '', root_rel) #print("Analyse de", root_abs, "->", root_rel) if root_rel_ignore in [common_config_dir, hostname] or re.search('^' + root_rel_ignore.replace(wildcard, '.*') + '$', hostname): #print(" -> retenu") yield root_abs else: #print(" -> non retenu") pass if num_sep + level <= num_sep_this: del dirs[:] # Arguments du script parser = argparse.ArgumentParser(description='Script permettant de surveiller / copier des fichiers entre un dépôt git et la machine locale') exclusive_grp = parser.add_mutually_exclusive_group() exclusive_grp.add_argument('--copy-repo-to-local', action='store_true', help='Copie les fichiers manquants / différents depuis le dépôt git ' + config_dir + ' vers la machine locale') exclusive_grp.add_argument('--copy-local-to-repo', action='store_true', help='Copie les fichiers manquants / différents depuis la machine locale vers le dépôt git ' + config_dir) parser.add_argument('--force', action='store_true', help='Force la copie, mode non-interactif - /!\ Dangereux /!\\') args = parser.parse_args() # Liste des répertoires valides contenant les fichiers à analyser/comparer # Les répertoires doivent suivre ce modèle : # - {config_dir}/xxxxxxxx/conf/{common_config_dir} # - {config_dir}/xxxxxxxx/conf/{hostname} # - {config_dir}/xxxxxxxx/conf/{part_of_hostname_followed_by_wildcard_pattern} conf_dirs = [] for directory in walk_git_conf(config_dir, 3): conf_dirs.append(directory) # Stockage dans un dictionnaire de chaque fichier à comparer # Clef : chemin absolu du fichier local # Valeur : chemin absolu du fichier dans le dépôt git # Les fichiers déjà trouvés sont remplacés par les éventuels nouveaux, dans l'ordre de parcours (alphanumérique) de 'conf_dirs' git_conf_file_dict = {} for directory in conf_dirs: len_dir = len(directory) #print("\nRépertoire =", directory) for item in os.walk(directory): # Si le fichier contient des fichiers (donc est un répertoire non vide) if len(item[2]) > 0: for f in item[2]: local_f_path = item[0][len_dir:] + '/' + f git_f_path = item[0] + '/' + f if directory.endswith('.ignore'): #print(" -> on oublie", local_f_path) git_conf_file_dict.pop(local_f_path, None) else: #print(" -> on retient", local_f_path) git_conf_file_dict.update({ local_f_path : git_f_path }) # Génération des listes de fichiers manquants et différents different_files_dict = {} missing_files_dict = {} for f_local, f_repo in git_conf_file_dict.items(): try: local_md5 = md5sum(f_local) repo_md5 = md5sum(f_repo) if local_md5 != repo_md5: different_files_dict.update({ f_local : f_repo }) except FileNotFoundError: missing_files_dict.update({ f_local : f_repo }) # Récupération du statut des dépôts git monitored_git_repos = [] try: with open(git_monitored_repos_file, 'r') as monitored_repos: for line in monitored_repos: git_repo = line.rstrip() if not os.path.exists(git_repo): print('[CRITICAL]', git_repo, 'n\'existe pas, merci de vérifier le fichier', git_monitored_repos_file) sys.exit(2) if not os.path.isdir(git_repo): print('[CRITICAL]', git_repo, 'n\'est pas un répertoire, merci de vérifier le fichier', git_monitored_repos_file) sys.exit(2) monitored_git_repos.append(git_repo) except FileNotFoundError as e: print('[CRITICAL]', e) sys.exit(2) git_repos_locally_modified = [] for git_repo in monitored_git_repos: p = sp.Popen(['git', '--work-tree=' + git_repo, '--git-dir=' + git_repo + '/.git', 'diff', '--name-only'], stdout=sp.PIPE, stderr=sp.PIPE) stdout, stderr = p.communicate(2) rc = p.returncode stdout = stdout.decode('utf-8').replace('\n', ' ') stderr = stderr.decode('utf-8').replace('\n', ' ') if rc != 0: print('[CRITICAL]', git_repo, stderr, 'rc=' + str(rc)) sys.exit(2) if stdout != '': git_repos_locally_modified.append(git_repo) # Output len_diff = len(different_files_dict) len_miss = len(missing_files_dict) if len_diff + len_miss > 0 or len(git_repos_locally_modified) > 0: # If copy if args.copy_repo_to_local or args.copy_local_to_repo: # If there are missing files if len_miss > 0: for f_local, f_repo in missing_files_dict.items(): if args.copy_local_to_repo: print("[CRITICAL] Je ne peux pas copier un fichier qui n'existe pas (" + f_local + " vers " + f_repo + ")") else: if not args.force: print("\nMerci de confirmer l'exécution de cette commande :") print("cp", f_repo, f_local) answer = input("Votre réponse ([oui|non]) : ") if args.force or answer.lower() in ['y', 'yes', 'o', 'oui']: try: shutil.copy2(f_repo, f_local) except FileNotFoundError: os.makedirs(os.path.dirname(f_local), mode=0o755) shutil.copy2(f_repo, f_local) print("-", f_local, ": copie effectuée") else: print("-", f_local, ": on ne fait rien") # If there are different files if len_diff > 0: for f_local, f_repo in different_files_dict.items(): if not args.force: print("\nMerci de confirmer l'exécution de cette commande :") if args.copy_local_to_repo: print("cp", f_local, f_repo) else: print("cp", f_repo, f_local) answer = input("Votre réponse ([oui|non]) : ") if args.force or answer.lower() in ['y', 'yes', 'o', 'oui']: if args.copy_local_to_repo: shutil.copy2(f_local, f_repo) print("-", f_local, ": copie effectuée") else: shutil.copy2(f_repo, f_local) print("-", f_local, ": copie effectuée") else: print("-", f_local, ": on ne fait rien") # Else : just monitoring else: print('[WARNING] ', end='') if len_miss > 0: if len_miss == 1: print('- 1 fichier manquant ', end='') else: print('-', len_miss, 'fichiers manquants ', end='') if len_diff > 0: if len_diff == 1: print('- 1 fichier différent ', end='') else: print('-', len_diff, 'fichiers différents ', end='') if len(git_repos_locally_modified) > 0: if len(git_repos_locally_modified) == 1: print('- 1 dépôt git modifié') print("\nDépôt git modifié :") else: print('-', len(git_repos_locally_modified), 'dépôts git modifiés') print("\nDépôts git modifiés :") for git_repo in git_repos_locally_modified: print('-', git_repo) if len_miss > 0: if len_miss == 1: print("\nFichier manquant :") else: print("\nFichiers manquants :") for f_local, f_repo in missing_files_dict.items(): print("- dépôt :", f_repo) print(" local :", f_local) if len_diff > 0: if len_diff == 1: print("\nFichier différent :") else: print("\nFichiers différents :") for f_local, f_repo in different_files_dict.items(): print("- dépôt :", f_repo) print(" local :", f_local) sys.exit(1) else: print("[OK] Cette machine est conforme") sys.exit(0)