#!/usr/bin/python3 import os import argparse import logging from datetime import datetime from functions import getSettings from includes.airwatchAPI import * from includes.GLPIAPI import * parser = argparse.ArgumentParser() parser.add_argument("-sF", "--searchFilter", dest="searchfilter", type=str, choices=["Id", "SerialNumber", "Imei", "UserName"]) parser.add_argument("-sV","--searchValue", dest="searchvalue", type=str) parser.add_argument("-c", "--configPath", dest="configpath", type=str) parser.add_argument("-f", "--force", dest="force", action="store_true") parser.add_argument("-s", "--silent", dest="silent", action="store_true") parser.add_argument("-v", "--verbose", dest="debug", action="store_true") args = parser.parse_args() # Récupération des informations du fichier de configuration if(args.configpath != None and args.configpath != ''): settings = getSettings(args.configpath) else: settings = getSettings("{os.path.realpath(os.path.dirname(__file__))}/conf/settings.conf") #=========== Configuration des logs ===========# # handler pour les logs de base logger = logging.getLogger(__name__) # handler pour log les doublons dans GLPI loggerDouble = logging.getLogger('doubleGLPI') # hander pour log les appareils manquants dans GLPI loggerMissing = logging.getLogger('missingGLPI') if(args.debug or settings["LOGS"]["Debug"]): logginglevel = logging.DEBUG else: logginglevel = logging.INFO logger.setLevel(logginglevel) loggerDouble.setLevel(logginglevel) loggerMissing.setLevel(logginglevel) formatter = logging.Formatter(fmt='%(asctime)s | %(levelname)s: %(message)s', datefmt='%Y/%m/%d %H:%M:%S') # handler pour log dans un fichier if(settings["LOGS"]["Enabled"]): # File Handler if(settings["LOGS"].get("Path") and settings["LOGS"].get("Path") != ""): fileHandler = logging.FileHandler(f"{settings['LOGS'].get('Path')}syncGLPI.log") fileErrorHandler = logging.FileHandler(f"{settings['LOGS'].get('Path')}syncGLPI-errors.log") fileDoubleHandler = logging.FileHandler(f"{settings['LOGS'].get('Path')}syncGLPI-double.log") fileMissingHandler = logging.FileHandler(f"{settings['LOGS'].get('Path')}syncGLPI-missing.log") else: fileHandler = logging.FileHandler('{os.path.realpath(os.path.dirname(__file__))}/logs/syncGLPI.log') fileErrorHandler = logging.FileHandler("{os.path.realpath(os.path.dirname(__file__))}/logs/syncGLPI-errors.log") fileDoubleHandler = logging.FileHandler("{os.path.realpath(os.path.dirname(__file__))}/logs/syncGLPI-double.log") fileMissingHandler = logging.FileHandler("{os.path.realpath(os.path.dirname(__file__))}/logs/syncGLPI-missing.log") # Set Logging Level to files handler fileHandler.setLevel(logginglevel) fileErrorHandler.setLevel(logging.ERROR) fileDoubleHandler.setLevel(logging.ERROR) fileMissingHandler.setLevel(logging.ERROR) # Set Formatter to file handler fileHandler.setFormatter(formatter) fileErrorHandler.setFormatter(formatter) fileDoubleHandler.setFormatter(formatter) fileMissingHandler.setFormatter(formatter) # Add Handler to loggers logger.addHandler(fileHandler) logger.addHandler(fileErrorHandler) loggerDouble.addHandler(fileDoubleHandler) loggerMissing.addHandler(fileMissingHandler) # handler pour log dans la console if(not args.silent): consoleHandler = logging.StreamHandler() consoleHandler.setLevel(logginglevel) consoleHandler.setFormatter(formatter) logger.addHandler(consoleHandler) #======== Paramètres du script ========# # Emplacement du verrou nameForLockFile = settings["GLPI"]["UserAgent"].replace(' ', '-') lockFile = f'{os.path.realpath(os.path.dirname(__file__))}/{nameForLockFile}_SyncGLPI.lock' logger.debug(f"============ Settings ============") logger.debug(f"Airwatch server: {settings['AIRWATCH']['Server']}") logger.debug(f"Authentication method : {settings['AIRWATCH']['AuthenticationMethod']}") logger.debug(f"Staging user: {settings['AIRWATCH']['StagingUser']}") logger.debug(f"GLPI server: {settings['GLPI']['Server']}") logger.debug(f"UserAgent: {settings['GLPI']['UserAgent']}") # Filtres searchFilter = args.searchfilter searchValue = args.searchvalue # Platform exclusion (12 = computer) platformFilterEnabled = True platformFilterOut = [12] # ====================================== # # Vérification de la présence du verrou avant de continuer if(os.path.isfile(lockFile) and not args.force): logger.debug('Lock file exists, exiting...') exit(0) else: open(lockFile, "w").close() logger.info("========= Synchronization started =========") try: airwatch = AirwatchAPI(settings) # recherche des appareils devices = airwatch.GetDevices() logger.info("Airwatch server connection succeeded") except FileNotFoundError as F: logger.critical(f"Certificate file not found for CMSURL authentication : {F}") os.remove(lockFile) exit(1) except Exception as error: logger.critical(f"Connection to Airwatch server failed : {error}") os.remove(lockFile) exit(1) # Initialisation de l'api GLPI try: glpiapi = GLPIAPI(settings) logger.info("GLPI server connection succeeded") except requests.exceptions.ConnectionError as error: logger.critical(f"Connection to GLPI server failed : {error}") os.remove(lockFile) exit(1) logger.info(f"Number of devices found in Airwatch : {len(devices)}") # ====================== Début suppression des doublons ================================= # # On récupére les numéros de série serials = [device.SerialNumber for device in devices] # On garde ceux qui sont présent plus d'une fois et qui n'ont pas HUBNOSERIAL (BYOD) comme numéro de série serials = {serial for serial in serials if serials.count(serial) > 1 and serial != 'HUBNOSERIAL'} # Récupération des devices et de la dernière date d'enrolement devicesDouble = {} for serial in serials: # on fait une liste des appareils avec le même numéro de série # que l'on stocke dans un dictionnaire avec le numéro de série en clé devicesDouble[serial] = [[device,datetime.strptime(device.LastEnrolledOn, "%Y-%m-%dT%H:%M:%S.%f")] for device in devices if serial == device.SerialNumber] logger.info(f"Duplicates detected : {len(devicesDouble)}") # On supprime les doublons qui ne se sont pas enrôlés en dernier devicesToDelete = [] for k,v in devicesDouble.items(): latest = None for d in v: if(latest == None): latest = d else: if(latest[1] < d[1]): devicesToDelete += [latest[0]] latest = d else: devicesToDelete += [d[0]] # envoi de la requête de suppression des appareils sur airwatch for device in devicesToDelete: logger.info(f"Deleting {device.Id} - {device.FriendlyName} in Airwatch") airwatch.DeleteDevice(device) devices = airwatch.GetDevices() # ====================== Fin suppression des doublons ================================= # if(searchFilter != None): logger.debug(f"SearchFilter set to {searchFilter}") logger.debug(f"SearchValue set to {searchValue}") if(searchFilter == 'Id'): devices = [device for device in devices if getattr(device, "Id") == searchValue] else: devices = [device for device in devices if getattr(device, "searchFilter") == searchValue] for device in devices: if(device.EnrollmentStatus != 'Enrolled'): logger.warning(f"Device with Airwatch id {device.Id} not enrolled, skipping this device...") continue if(device.SerialNumber == 'HUBNOSERIAL'): logger.info(f"Device with Airwatch id {device.Id} is using work profile, skipping...") continue logger.info(f"Searching device {device.FriendlyName} (Airwatch id={device.Id}) on GLPI") deviceID, data, count = glpiapi.GetDevice(device) apps = airwatch.GetDeviceApps(device) if(count > 1): loggerDouble.error(f"{count} devices matching airwatch device {device.FriendlyName} (Airwatch id={device.Id}) in GLPI (GLPI ids = {', '.join(deviceID)}), skipping this device...") continue if(count == 0): loggerMissing.error(f"Device {device.FriendlyName} (id={device.Id}) not found in GLPI, is it in the trash bin ? Skipping device...") continue inventory = glpiapi.CreateInventoryForAirwatchDevice(device, data["1"], apps) # Mise à jour du friendly name sur Airwatch platformName = inventory.operatingsystem["name"] if(device.FriendlyName != f"{data['1']} {platformName} {device.OS} - {device.User}"): newFriendlyName = f"{data['1']} {platformName} {device.OS} - {device.User}" logger.info(f"Updating device friendlyname to {newFriendlyName}") airwatch.SetDeviceFriendlyName(device, newFriendlyName) # filtre des plateformes if(platformFilterEnabled): if device.PlatformId in platformFilterOut: logger.info(f"Device platform ({device.PlatformId}) is filtered out, not updating GLPI") continue logger.info(f"Updating {deviceID} on GLPI") glpiapi.UpdateInventory(inventory.Json()) #if(data['70'] == None and device.User != settings["AIRWATCH"]["StagingUser"]): #logger.info(f"Updating user from {data['70']} to {device.User} in GLPI (id={deviceID})") #glpiapi.UpdateUser(deviceID, device.User) if(data['5'] != device.SerialNumber): logger.info(f"Updating serial number from {data['5']} to {device.SerialNumber} in GLPI (id={deviceID})") glpiapi.UpdateSerialNumber(deviceID, device.SerialNumber) logger.info("========= End of synchronization =========") logger.debug('Removing lock file') os.remove(lockFile)