#!/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("./conf/settings.conf") #=========== Configuration des logs ===========# logger = logging.getLogger(__name__) if(args.debug or settings["LOGS"]["Debug"]): logginglevel = logging.DEBUG else: logginglevel = logging.INFO logger.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"]): if(settings["LOGS"].get("Path") and settings["LOGS"].get("Path") != ""): fileHandler = logging.FileHandler(f"{settings['LOGS'].get('Path')}syncGLPI.log") else: fileHandler = logging.FileHandler('./logs/syncGLPI.log') fileHandler.setLevel(logginglevel) fileHandler.setFormatter(formatter) logger.addHandler(fileHandler) # 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 lockFile = './airwatchSyncGLPI.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 id {device.Id} not enrolled, skipping this device...") continue logger.info(f"Searching device {device.FriendlyName} (id={device.Id}) on GLPI") deviceID, data, count = glpiapi.GetDevice(device) apps = airwatch.GetDeviceApps(device) if(count > 1): logger.error(f"{count} devices matching airwatch device in GLPI (GLPI ids = {', '.join(deviceID)}), skipping this device...") continue if(count == 0): logger.error(f"Device 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['5'] != device.SerialNumber): logger.info(f"Updating serial number from {data['5']} to {device.SerialNumber} in GLPI") glpiapi.UpdateSerialNumber(deviceID, device.SerialNumber) logger.info("========= End of synchronization =========") logger.debug('Removing lock file') os.remove(lockFile)