Compare commits

..

22 Commits

Author SHA1 Message Date
1168cf62f3 Ajout du fichier requirements pour les dépendances python 2025-11-20 17:38:13 +01:00
73a94c3c50 Ajout d'une suppression du verrou si la synchro dure plus de 3h 2025-11-20 17:26:16 +01:00
8e698cfc49 Ajout du lien vers la page Airwatch de l'appareil sur la fiche GLPI 2025-10-23 17:50:03 +02:00
e25463ff89 Correction erreur de frappe pour le nom de la variable 2025-10-16 09:32:36 +02:00
064ecfad43 Modification du nom de plateforme en Windows Desktop en cas de version non énumérée 2025-10-16 09:14:42 +02:00
13bc1f46d7 Ajout d'une traduction des noms de version Windows dans le friendlyname 2025-10-15 14:10:26 +02:00
a89a17020a Désactivation de la vérif utilisateur pour maj glpi 2025-09-12 10:22:38 +02:00
078d4f0923 Modification de la vérification de l'utilisateur non présent 2025-09-12 10:00:33 +02:00
20f28b71db Ajout d'une fonction pour mettre à jour l'utilisateur GLPI lors de la synschro s'il est vide 2025-09-12 09:50:58 +02:00
2bb3eec219 Correction d'une erreur si l'utilisateur n'a pas d'appareils 2025-09-03 16:24:55 +02:00
2fa704f381 Correctifs 2025-08-26 09:35:02 +02:00
0a5e780546 Correction de la gestion des différents fichiers de logs et utilisation d'une variable qui récupère l'emplacement du script 2025-07-10 16:45:18 +02:00
ce003e206a Modification de la gestion des logs pour gérer les doublons et les manquants dans un fichier séparé 2025-07-10 14:31:34 +02:00
3e19b4d3e6 Modification des informations de log pour que l'identification de l'appareil soit présent pour les erreurs 2025-07-09 18:36:23 +02:00
074a1e5313 Création d'un fichier de log séparé pour les erreurs 2025-07-09 18:32:46 +02:00
f697d2fcb4 Changement de l'envoi pour l'inventaire en chaine de caractère au lieu de format json (json -> data) 2025-07-09 17:57:26 +02:00
eb1bc4cc3b Modification du fichier de configuration 2025-07-05 14:10:16 +02:00
92a6259f04 Création du répertoire certs pour les certificats 2025-07-05 14:00:47 +02:00
570b89e50c Mise à jour des informations concernant les scripts 2025-07-05 13:59:26 +02:00
15a34ba66c Created logs folder 2025-07-05 13:37:52 +02:00
68a0d7d6ab added more explicit error when certificate file is missing 2025-07-05 13:28:44 +02:00
ecbd48cbc0 added comment when fetching devices 2025-07-05 13:28:08 +02:00
10 changed files with 235 additions and 56 deletions

View File

@ -3,28 +3,50 @@
## Explication de l'usage des scripts et de la configuration : ## Explication de l'usage des scripts et de la configuration :
### Fichier de configuration global settings.json ### Fichier de configuration global settings.json
Les scripts prennent les informations de configuration du fichier settings.json, si celui-ci n'existe pas au lancement d'un script, il est automatiquement créé avec des valeurs d'exemples. Les scripts prennent les informations de configuration du fichier de configuration présent dans le répertoire conf, si celui-ci n'existe pas au lancement d'un script, il est automatiquement créé avec des valeurs d'exemples. Un chemin personnalisé vers un fichier de configuration peut être renseigné avec le paramètre -c.
Voici une liste des paramètres du fichier de configuration et les valeurs attendues (chaque valeur doit être entre " ") :
- **"airwatchServer"**: l'adresse du serveur Airwatch Voici un exemple du fichier de configuration :
- **"airwatchAPIKey"**: la clé API récupéré dans Airwatch
- **"airwatchAuthMethod"**: défini la méthode d'authentification, peut prendre la valeur ***"CMSURL"*** pour l'authentification par certificat ou ***"password"*** pour l'authentification par mot de passe.
- **"airwatchCertPath"**: le chemin vers le certificat si la méthode d'authentification est par certificat.
- **"airwatchCertPass"**: le mot de passe du certificat si celui-ci en possède un, laissez une chaîne vide si vous n'avez pas mis de mot de passe.
- **"airwatchAPIUser"**: le nom de l'utilisateur pour l'authentification API dans Airwatch.
- **"airwatchAPIPassword"**: le mot de passe de l'utilisateur si l'authentification par mot de passe est choisie.
- **"glpiServer"**: l'adresse du serveur GLPI avec un / à la fin.
- **"glpiAppToken"**: le token d'application GLPI.
- **"glpiUserToken"**: le token de l'utilisateur GLPI utilisé pour les requêtes API.
- **"stagingUser"**: le nom du compte de staging présent dans Airwatch.
- **"userAgent"**: Un nom pour l'user agent tel qu'il sera visible dans GLPI, cela permet d'identifier l'instance Airwatch (Prod / Pré-prod) par exemple.
```toml
[AIRWATCH]
Server = "https://airwatchServer"
APIKey = "APIKEY"
# Méthode d'authentification (CMSURL or PASSWORD)
# CMSURL permet l'authentification avec un certificat utilisateur (CertificatePath, CertificatePassword)
# PASSWORD permet l'authentification avec un nom d'utilisateur et un mot de passe (APIUser, APIPassword)
AuthenticationMethod = "CMSURL"
CertificatePath = "/path/to/cert"
CertificatePassword = "12345"
APIUser = "UserAPI"
APIPassword = "PasswordUserAPI"
# Utilisateur de staging que l'on va remplacer par l'utilisateur trouvé dans GLPI
StagingUser = "staging-pr"
[GLPI]
Server = "http://127.0.0.1/glpi"
AppToken = "GLPIAppToken"
UserToken = "GLPIUserToken"
# User agent qui sera visible sur GLPI lors de la synchronisation
UserAgent = "Airwatch Synchronizer"
[LOGS]
Enabled = true
# Chemin où seront créé les fichiers de log
Path = "./logs/"
# Mode debug pour avoir plus d'informations
Debug = false
```
--- ---
### syncGLPI.py ### syncGLPI.py
Le script syncGLPI.py permet de synchroniser les données des appareils présents dans Airwatch avec un inventaire GLPI. Le script syncGLPI.py permet de synchroniser les données des appareils présents dans Airwatch avec un inventaire GLPI. Les actions du script :
- vérification et suppression des doublons en fonction du numéro de série et de la date de dernier enrôlement
Au début, le script va vérifier la présence de doublons en fonction du numéro de série et garder seulement le dernier à s'être enrôlé, puis il va procéder à la vérification de la présence des appareils dans l'inventaire GLPI pour procéder à la synchronisation des données. - vérification de la présence des appareils dans l'inventaire GLPI et envoi d'un inventaire à partir des données d'Airwatch pour mettre à jours les informations
- modification du numéro de série de l'appareil sur GLPI si celui-ci n'est pas identique à celui d'Airwatch
- modification du friendlyname de l'appareil sur Airwatch à partir du nom d'inventaire de l'appareil sur GLPI
#### Synchronisation #### Synchronisation
Les éléments synchronisés de Airwatch vers GLPI : Les éléments synchronisés de Airwatch vers GLPI :
@ -33,6 +55,7 @@ Les éléments synchronisés de Airwatch vers GLPI :
- UUID - UUID
- le nom du système d'exploitation et sa version - le nom du système d'exploitation et sa version
- les logiciels présents sur la machine - les logiciels présents sur la machine
- le numéro de série
Les éléments synchronisés de GLPI vers Airwatch : Les éléments synchronisés de GLPI vers Airwatch :
- Le nom d'inventaire de la machine qui est mis pour le friendlyname - Le nom d'inventaire de la machine qui est mis pour le friendlyname
@ -40,23 +63,27 @@ Les éléments synchronisés de GLPI vers Airwatch :
#### Paramètres #### Paramètres
Ce script possède les paramètres suivants qui sont optionnels pour son exécution : Ce script possède les paramètres suivants qui sont optionnels pour son exécution :
- **-debug** : affiche des informations lors de son exécution, utile pour résoudre des problèmes liés à des droits d'accès API ou des problèmes d'ouvertures réseaux - **-sF / --searchFilter** : permet de filtrer la recherche des appareils dans airwatch sur un attribut spécifique parmi la liste suivante : "Id", "SerialNumber", "Imei", "UserName"
- **-searchFilter** : permet de filtrer la recherche des appareils dans airwatch sur un attribut spécifique parmi la liste suivante : "Id", "SerialNumber", "Imei", "UserName" - **-sV / --searchValue** : la valeur pour la recherche lorsque -searchFilter est utilisé
- **-searchValue** : la valeur pour la recherche lorsque -searchFilter est utilisé - **-f / --force** : permet d'outrepasser la vérification du verrou posé par le script lors de son exécution
- **-force** : permet d'outrepasser la vérification du verrou posé par le script lors de son exécution - **-c / --configPath** : permet de définir un chemin vers un fichier de configuration a utilisé pour l'exécution du script
- **-s / --silent** : exécute le script sans faire de retour dans la console, les informations seront toujours présentes dans les fichiers de log
- **-v / --verbose** : affiche des informations lors de son exécution, utile pour résoudre des problèmes liés à des droits d'accès API ou des problèmes d'ouvertures réseaux
--- ---
### StagingUserAssignation.py ### StagingUserAssignation.py
Le script StagingUserAssignation.py permet d'assigner les appareils en staging qui sont assignés à un utilisateur de staging à l'utilisateur renseigné dans l'inventaire GLPI. Le script StagingUserAssignation.py permet d'assigner les appareils en staging qui sont assignés à un utilisateur de staging à l'utilisateur renseigné dans l'inventaire GLPI.
Il récupère le nom de l'utilisateur de staging dans le fichier settings.json. Il récupère le nom de l'utilisateur de staging dans le fichier de configuration.
#### Paramètres #### Paramètres
- **-debug** : affiche des informations lors de son exécution, utile pour résoudre des problèmes liés à des droits d'accès API ou des problèmes d'ouvertures réseaux - **-u / --staginguser** : permet de préciser l'utilisateur de staging pour la recherche des appareils à modifier (override le fichier de paramètres)
- **-force** : permet d'outrepasser la vérification du verrou posé par le script lors de son exécution - **-sn / --serialnumber** : permet de filtrer sur un numéro de série précis
- **-staginguser** : permet de préciser l'utilisateur de staging pour la recherche des appareils à modifier (override le fichier de paramètres) - **-f / --force** : permet d'outrepasser la vérification du verrou posé par le script lors de son exécution
- **-serialnumber** : permet de filtrer sur un numéro de série précis - **-c / --configPath** : permet de définir un chemin vers un fichier de configuration a utilisé pour l'exécution du script
- **-s / --silent** : exécute le script sans faire de retour dans la console, les informations seront toujours présentes dans les fichiers de log
- **-v / --verbose** : affiche des informations lors de son exécution, utile pour résoudre des problèmes liés à des droits d'accès API ou des problèmes d'ouvertures réseaux
## Dépendances ## Dépendances
Installation des paquets linux : Installation des paquets linux :
@ -73,7 +100,7 @@ yum install -y python3 python3-pip git
**Dépendances python** **Dépendances python**
``` ```
python3 -m pip install cryptography requests python3 -m pip install cryptography requests toml
``` ```
## Installation des scripts ## Installation des scripts

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
cryptography
requests
toml

View File

@ -2,6 +2,7 @@
import os import os
import argparse import argparse
import logging import logging
import time
from functions import getSettings from functions import getSettings
from includes.airwatchAPI import * from includes.airwatchAPI import *
from includes.GLPIAPI import * from includes.GLPIAPI import *
@ -67,26 +68,37 @@ if(args.staginguser != None):
# ====================================== # # ====================================== #
# Vérification de la présence du verrou avant de continuer # Vérification de la présence du verrou avant de continuer
if(os.path.isfile(lockFile) and not args.force): if(os.path.isfile(lockFile) and not args.force):
logger.debug('Lock file exists, exiting...') # Récupération du temps de création de verrou en minutes
exit(0) lockTime = (time.time() - os.path.getmtime(lockFile)) // 60
# Recréation du verrou s'il existe depuis plus de 3 heures (crash)
# sinon on quitte, une synchro est déjà en cours
if(lockTime > 180):
os.remove(lockFile)
open(lockFile, "w").close()
else:
logger.debug('Lock file exists, exiting...')
exit(0)
else: else:
# Création du verrou s'il n'existe pas
open(lockFile, "w").close() open(lockFile, "w").close()
# Initialisation de l'api Airwatch # Initialisation de l'api Airwatch
try: try:
airwatch = AirwatchAPI(settings) airwatch = AirwatchAPI(settings)
# Recherche des appareils filtré sur l'utilisateur de staging
devices = airwatch.GetDevices(stagingUser)
logger.info("Airwatch server connection succeeded") logger.info("Airwatch server connection succeeded")
except requests.exceptions.ConnectionError as error: 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}") logger.critical(f"Connection to Airwatch server failed : {error}")
os.remove(lockFile) os.remove(lockFile)
exit(1) exit(1)
# Recherche des appareils filtré sur l'utilisateur de staging
devices = airwatch.GetDevices(stagingUser)
if(devices == None): if(devices == None):
logger.info(f"No device found with staging user ({stagingUser}), exiting...") logger.info(f"No device found with staging user ({stagingUser}), exiting...")
os.remove(lockFile) os.remove(lockFile)

0
scripts/certs/empty Normal file
View File

View File

@ -1,4 +1,3 @@
[AIRWATCH] [AIRWATCH]
Server = "https://airwatchServer" Server = "https://airwatchServer"
APIKey = "APIKEY" APIKey = "APIKEY"
@ -19,10 +18,13 @@ StagingUser = "staging-pr"
Server = "http://127.0.0.1/glpi" Server = "http://127.0.0.1/glpi"
AppToken = "GLPIAppToken" AppToken = "GLPIAppToken"
UserToken = "GLPIUserToken" UserToken = "GLPIUserToken"
# User agent qui sera visible sur GLPI lors de la synchronisation # User agent qui sera visible sur GLPI lors de la synchronisation
UserAgent = "Airwatch Synchronizer" UserAgent = "Airwatch Synchronizer"
[LOGS] [LOGS]
Enabled = true Enabled = true
# Chemin o<> seront cr<63><72> les fichiers de log
Path = "./logs/" Path = "./logs/"
# Mode debug pour avoir plus d'informations
Debug = false Debug = false

View File

@ -5,6 +5,7 @@ def getSettings(settingsPath):
settingsDefault =""" settingsDefault ="""
[AIRWATCH] [AIRWATCH]
Server = "https://airwatchServer" Server = "https://airwatchServer"
ConsoleURI = "https://airwatchConsole"
APIKey = "APIKEY" APIKey = "APIKEY"
# Méthode d'authentification (CMSURL or PASSWORD) # Méthode d'authentification (CMSURL or PASSWORD)
@ -23,12 +24,15 @@ StagingUser = "staging-pr"
Server = "http://127.0.0.1/glpi" Server = "http://127.0.0.1/glpi"
AppToken = "GLPIAppToken" AppToken = "GLPIAppToken"
UserToken = "GLPIUserToken" UserToken = "GLPIUserToken"
# User agent qui sera visible sur GLPI lors de la synchronisation # User agent qui sera visible sur GLPI lors de la synchronisation
UserAgent = "Airwatch Synchronizer" UserAgent = "Airwatch Synchronizer"
[LOGS] [LOGS]
Enabled = true Enabled = true
# Chemin où seront créé les fichiers de log
Path = "./logs/" Path = "./logs/"
# Mode debug pour avoir plus d'informations
Debug = false Debug = false
""" """

View File

@ -55,17 +55,39 @@ class GLPIAPI:
return deviceID, data, search["totalcount"] return deviceID, data, search["totalcount"]
elif(search["totalcount"] > 1): elif(search["totalcount"] > 1):
deviceID = list(search["data"].keys())
return deviceID, search["data"], search["totalcount"] return deviceID, search["data"], search["totalcount"]
else: else:
return None, None, 0 return None, None, 0
return None, None, None return None, None, None
def GetUser(self, username=None, email=None):
if(username != None):
search_parameter = f'is_deleted=0&criteria[0][field]=1&withindexes=true&criteria[0][searchtype]=contains&criteria[0][value]=^{username}$'
elif(email != None):
search_parameter = f'is_deleted=0&criteria[0][field]=5&withindexes=true&criteria[0][searchtype]=contains&criteria[0][value]=^{email}$'
searchUri = f"{self.Server}/apirest.php/search/user?{search_parameter}"
search = requests.get(searchUri, headers=self.Headers)
if(search.status_code == 200):
search = search.json()
if(search["totalcount"] == 1):
userID = list(search["data"].keys())[0]
data = search["data"][userID]
return userID, data, search["totalcount"]
elif(search["totalcount"] > 1):
userID = list(search["data"].keys())
return userID, search["data"], search["totalcount"]
else:
return None, None, 0
def UpdateInventory(self, inventory): def UpdateInventory(self, inventory):
headers = { headers = {
"Content-Type":"Application/x-compress", "Content-Type":"Application/x-compress",
"user-agent":self.UserAgent "user-agent":self.UserAgent
} }
return requests.post(self.Server, headers=headers, json=inventory) return requests.post(self.Server, headers=headers, data=inventory)
def UpdateSerialNumber(self, deviceid, serialnumber): def UpdateSerialNumber(self, deviceid, serialnumber):
@ -78,11 +100,33 @@ class GLPIAPI:
uri = f"{self.Server}/apirest.php/Computer/" uri = f"{self.Server}/apirest.php/Computer/"
return requests.put(uri, headers=self.Headers, json=body) return requests.put(uri, headers=self.Headers, json=body)
def UpdateUser(self, deviceid, username):
body = {
"input" : {
"id" : deviceid,
"users_id" : username
}
}
uri = f"{self.Server}/apirest.php/Computer/"
return requests.put(uri, headers=self.Headers, json=body)
def UpdateAirwatchLink(self, deviceid, airwatchlink):
body = {
"input": {
"id": deviceid,
"appareilsurmagentafield": airwatchlink
}
}
uri = f"{self.Server}/apirest.php/Computer/"
return requests.put(uri, headers=self.Headers, json=body)
def CreateInventoryForAirwatchDevice(self, device, deviceName, apps=None): def CreateInventoryForAirwatchDevice(self, device, deviceName, apps=None):
platforms = { platforms = {
2:"Apple iOS", 2:"Apple iOS",
5:"Android", 5:"Android",
12:"Windows Desktop" 12:"Windows"
} }
if(device.PlatformId in platforms.keys()): if(device.PlatformId in platforms.keys()):
@ -107,10 +151,28 @@ class GLPIAPI:
osArch = "Unknown" osArch = "Unknown"
softwareArch = "Unknown" softwareArch = "Unknown"
windowsOSTranslation = {
"10.0.19043":"10 21H1",
"10.0.19044":"10 21H2",
"10.0.19045":"10 22H2",
"10.0.22000":"11 21H2",
"10.0.22621":"11 22H2",
"10.0.22631":"11 23H2",
"10.0.26100":"11 24H2",
"10.0.26200":"11 25H2"
}
logDate = datetime.strptime(device.LastSeen, "%Y-%m-%dT%H:%M:%S.%f").strftime("%Y-%m-%d %H:%M:%S") logDate = datetime.strptime(device.LastSeen, "%Y-%m-%dT%H:%M:%S.%f").strftime("%Y-%m-%d %H:%M:%S")
inventory = GLPIInventory(logdate=logDate, versionclient=self.UserAgent, tag=device.Group, deviceid=f"{deviceName} - {device.SerialNumber}", itemtype="Computer") inventory = GLPIInventory(logdate=logDate, versionclient=self.UserAgent, tag=device.Group, deviceid=f"{deviceName} - {device.SerialNumber}", itemtype="Computer")
inventory.SetOperatingSystem(platformName, device.OS, osArch) if(platformName == "Windows"):
if(device.OS in windowsOSTranslation.keys()):
inventory.SetOperatingSystem(platformName, windowsOSTranslation[str(device.OS)], osArch)
else:
platformName = "Windows Desktop"
inventory.SetOperatingSystem(platformName, device.OS, osArch)
else:
inventory.SetOperatingSystem(platformName, device.OS, osArch)
inventory.SetHardware(deviceName, device.Uuid, device.TotalMemory) inventory.SetHardware(deviceName, device.Uuid, device.TotalMemory)
inventory.AddUser(device.User) inventory.AddUser(device.User)

View File

@ -168,7 +168,10 @@ class AirwatchUser:
self.Group = user["Group"] self.Group = user["Group"]
self.GroupId = user["LocationGroupId"] self.GroupId = user["LocationGroupId"]
self.OrgUuid = user["OrganizationGroupUuid"] self.OrgUuid = user["OrganizationGroupUuid"]
self.DeviceCount = int(user["EnrolledDevicesCount"]) if(user["EnrolledDevicesCount"] != ''):
self.DeviceCount = int(user["EnrolledDevicesCount"])
else:
self.DeviceCount = 0
class AirwatchDevice: class AirwatchDevice:
@ -182,8 +185,12 @@ class AirwatchDevice:
self.GroupId = device["LocationGroupId"]["Id"]["Value"] self.GroupId = device["LocationGroupId"]["Id"]["Value"]
self.Group = device["LocationGroupName"] self.Group = device["LocationGroupName"]
self.GroupUuid = device["LocationGroupId"]["Uuid"] self.GroupUuid = device["LocationGroupId"]["Uuid"]
self.UserId = device["UserId"]["Id"]["Value"] if(device["UserId"].get("Id") != None):
self.User = device["UserName"] self.UserId = device["UserId"]["Id"]["Value"]
self.User = device["UserName"]
else:
self.UserId = None
self.User = None
self.UserEmail = device["UserEmailAddress"] self.UserEmail = device["UserEmailAddress"]
self.PlatformId = device["PlatformId"]["Id"]["Value"] self.PlatformId = device["PlatformId"]["Id"]["Value"]
self.Platform = device["Platform"] self.Platform = device["Platform"]

0
scripts/logs/empty Normal file
View File

View File

@ -2,6 +2,7 @@
import os import os
import argparse import argparse
import logging import logging
import time
from datetime import datetime from datetime import datetime
from functions import getSettings from functions import getSettings
from includes.airwatchAPI import * from includes.airwatchAPI import *
@ -22,11 +23,16 @@ args = parser.parse_args()
if(args.configpath != None and args.configpath != ''): if(args.configpath != None and args.configpath != ''):
settings = getSettings(args.configpath) settings = getSettings(args.configpath)
else: else:
settings = getSettings("./conf/settings.conf") settings = getSettings(f"{os.path.realpath(os.path.dirname(__file__))}/conf/settings.conf")
#=========== Configuration des logs ===========# #=========== Configuration des logs ===========#
# handler pour les logs de base
logger = logging.getLogger(__name__) 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"]): if(args.debug or settings["LOGS"]["Debug"]):
logginglevel = logging.DEBUG logginglevel = logging.DEBUG
@ -34,18 +40,40 @@ else:
logginglevel = logging.INFO logginglevel = logging.INFO
logger.setLevel(logginglevel) 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') formatter = logging.Formatter(fmt='%(asctime)s | %(levelname)s: %(message)s', datefmt='%Y/%m/%d %H:%M:%S')
# handler pour log dans un fichier # handler pour log dans un fichier
if(settings["LOGS"]["Enabled"]): if(settings["LOGS"]["Enabled"]):
# File Handler
if(settings["LOGS"].get("Path") and settings["LOGS"].get("Path") != ""): if(settings["LOGS"].get("Path") and settings["LOGS"].get("Path") != ""):
fileHandler = logging.FileHandler(f"{settings['LOGS'].get('Path')}syncGLPI.log") 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: else:
fileHandler = logging.FileHandler('./logs/syncGLPI.log') 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) fileHandler.setLevel(logginglevel)
fileErrorHandler.setLevel(logging.ERROR)
fileDoubleHandler.setLevel(logging.ERROR)
fileMissingHandler.setLevel(logging.ERROR)
# Set Formatter to file handler
fileHandler.setFormatter(formatter) fileHandler.setFormatter(formatter)
fileErrorHandler.setFormatter(formatter)
fileDoubleHandler.setFormatter(formatter)
fileMissingHandler.setFormatter(formatter)
# Add Handler to loggers
logger.addHandler(fileHandler) logger.addHandler(fileHandler)
logger.addHandler(fileErrorHandler)
loggerDouble.addHandler(fileDoubleHandler)
loggerMissing.addHandler(fileMissingHandler)
# handler pour log dans la console # handler pour log dans la console
if(not args.silent): if(not args.silent):
@ -57,7 +85,8 @@ if(not args.silent):
#======== Paramètres du script ========# #======== Paramètres du script ========#
# Emplacement du verrou # Emplacement du verrou
lockFile = './airwatchSyncGLPI.lock' nameForLockFile = settings["GLPI"]["UserAgent"].replace(' ', '-')
lockFile = f'{os.path.realpath(os.path.dirname(__file__))}/{nameForLockFile}_SyncGLPI.lock'
logger.debug(f"============ Settings ============") logger.debug(f"============ Settings ============")
logger.debug(f"Airwatch server: {settings['AIRWATCH']['Server']}") logger.debug(f"Airwatch server: {settings['AIRWATCH']['Server']}")
@ -77,17 +106,28 @@ platformFilterOut = [12]
# ====================================== # # ====================================== #
# Vérification de la présence du verrou avant de continuer # Vérification de la présence du verrou avant de continuer
if(os.path.isfile(lockFile) and not args.force): if(os.path.isfile(lockFile) and not args.force):
logger.debug('Lock file exists, exiting...') # Récupération du temps de création de verrou en minutes
exit(0) lockTime = (time.time() - os.path.getmtime(lockFile)) // 60
# Recréation du verrou s'il existe depuis plus de 3 heures (crash)
# sinon on quitte, une synchro est déjà en cours
if(lockTime > 180):
os.remove(lockFile)
open(lockFile, "w").close()
else:
logger.debug('Lock file exists, exiting...')
exit(0)
else: else:
# Création du verrou s'il n'existe pas
open(lockFile, "w").close() open(lockFile, "w").close()
logger.info("========= Synchronization started =========") logger.info("========= Synchronization started =========")
try: try:
airwatch = AirwatchAPI(settings) airwatch = AirwatchAPI(settings)
# recherche des appareils
devices = airwatch.GetDevices() devices = airwatch.GetDevices()
logger.info("Airwatch server connection succeeded") logger.info("Airwatch server connection succeeded")
except FileNotFoundError as F: except FileNotFoundError as F:
@ -160,28 +200,44 @@ if(searchFilter != None):
for device in devices: for device in devices:
if(device.EnrollmentStatus != 'Enrolled'): if(device.EnrollmentStatus != 'Enrolled'):
logger.warning(f"Device with id {device.Id} not enrolled, skipping this device...") logger.warning(f"Device with Airwatch id {device.Id} not enrolled, skipping this device...")
continue continue
logger.info(f"Searching device {device.FriendlyName} (id={device.Id}) on GLPI") 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) deviceID, data, count = glpiapi.GetDevice(device)
apps = airwatch.GetDeviceApps(device) apps = airwatch.GetDeviceApps(device)
if(count > 1): if(count > 1):
logger.error(f"{count} devices matching airwatch device in GLPI (GLPI ids = {', '.join(deviceID)}), skipping this device...") loggerDouble.error(f"{count} devices matching airwatch device {device.FriendlyName} (Airwatch id={device.Id}) in GLPI (GLPI ids = {', '.join(deviceID)}), skipping this device...")
continue continue
if(count == 0): if(count == 0):
logger.error(f"Device not found in GLPI, is it in the trash bin ? Skipping device...") deviceIDTrash, dataTrash, countTrash = glpiapi.GetDevice(device)
if(countTrash > 1):
loggerDouble.error(f"{countTrash} devices matching airwatch device {device.FriendlyName} (Airwatch id={device.Id}) in GLPI trashbin (GLPI ids = {', '.join(deviceIDTrash)}), skipping this device...")
elif(countTrash == 1):
logger.warning(f"Device {device.FriendlyName} (Airwatch id={device.Id}) in GLPI trashbin (GLPI id={deviceIDTrash}), skipping...")
else:
loggerMissing.error(f"Device {device.FriendlyName} (Airwatch id={device.Id}) not found in GLPI.")
continue continue
inventory = glpiapi.CreateInventoryForAirwatchDevice(device, data["1"], apps) inventory = glpiapi.CreateInventoryForAirwatchDevice(device, data["1"], apps)
# Mise à jour du friendly name sur Airwatch # Mise à jour du friendly name sur Airwatch
platformName = inventory.operatingsystem["name"] platformName = inventory.operatingsystem["name"]
if(device.FriendlyName != f"{data['1']} {platformName} {device.OS} - {device.User}"): osVersion = inventory.operatingsystem["version"]
newFriendlyName = f"{data['1']} {platformName} {device.OS} - {device.User}" if(device.FriendlyName != f"{data['1']} {platformName} {osVersion} - {device.User}"):
newFriendlyName = f"{data['1']} {platformName} {osVersion} - {device.User}"
logger.info(f"Updating device friendlyname to {newFriendlyName}") logger.info(f"Updating device friendlyname to {newFriendlyName}")
airwatch.SetDeviceFriendlyName(device, newFriendlyName) airwatch.SetDeviceFriendlyName(device, newFriendlyName)
# Mise à jour de l'url vers la page airwatch de l'appareil sur GLPI
airwatchlink = f"{settings['AIRWATCH']['ConsoleURI']}/AirWatch/#/AirWatch/Device/Details/Summary/{device.Id}"
if(data['76689'] != airwatchlink):
glpiapi.UpdateAirwatchLink(deviceID, airwatchlink)
# filtre des plateformes # filtre des plateformes
if(platformFilterEnabled): if(platformFilterEnabled):
if device.PlatformId in platformFilterOut: if device.PlatformId in platformFilterOut:
@ -191,8 +247,14 @@ for device in devices:
logger.info(f"Updating {deviceID} on GLPI") logger.info(f"Updating {deviceID} on GLPI")
glpiapi.UpdateInventory(inventory.Json()) glpiapi.UpdateInventory(inventory.Json())
if(data['70'] == None and device.User != settings["AIRWATCH"]["StagingUser"]):
userID, userData, userCount = glpiapi.GetUser(device.User)
if(userCount == 1):
logger.info(f"Updating user from {data['70']} to {device.User} in GLPI (id={deviceID})")
glpiapi.UpdateUser(deviceID, userID)
if(data['5'] != device.SerialNumber): if(data['5'] != device.SerialNumber):
logger.info(f"Updating serial number from {data['5']} to {device.SerialNumber} in GLPI") logger.info(f"Updating serial number from {data['5']} to {device.SerialNumber} in GLPI (id={deviceID})")
glpiapi.UpdateSerialNumber(deviceID, device.SerialNumber) glpiapi.UpdateSerialNumber(deviceID, device.SerialNumber)
logger.info("========= End of synchronization =========") logger.info("========= End of synchronization =========")