206 lines
7.7 KiB
Python
206 lines
7.7 KiB
Python
#!/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")
|
|
fileErrorHandler = logging.FileHandler(f"{settings['LOGS'].get('Path')}syncGLPI-errors.log")
|
|
else:
|
|
fileHandler = logging.FileHandler('./logs/syncGLPI.log')
|
|
fileHandler.setLevel(logginglevel)
|
|
fileHandler.setFormatter(formatter)
|
|
fileErrorHandler.setLevel(logging.ERROR)
|
|
fileErrorHandler.setFormatter(formatter)
|
|
logger.addHandler(fileHandler)
|
|
logger.addHandler(fileErrorHandler)
|
|
|
|
# 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 {device.FriendlyName} (id={device.Id}) in GLPI (GLPI ids = {', '.join(deviceID)}), skipping this device...")
|
|
continue
|
|
if(count == 0):
|
|
logger.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['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) |