263 lines
11 KiB
Python
Executable File
263 lines
11 KiB
Python
Executable File
#!/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)
|