Compare commits

...

29 Commits

Author SHA1 Message Date
00a4652e0b Ajout du droit d'exécution sur les scripts 2025-09-12 12:48:03 +02:00
f004382573 Modification de la récupération d'un utilisateur GLPI 2025-09-12 12:40:01 +02:00
d52e0b7a2a Merge branch 'main' into dev 2025-09-12 12:06:23 +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
ad634c7ec3 Correction de l'image docker 2025-07-10 23:09:30 +02:00
84c52ae234 Updated docker files 2025-07-10 23:05:00 +02:00
5eb7b6b778 Correction du dockerfile 2025-07-10 21:51:26 +02:00
e0e5ea37ce Correction de l'indentation 2025-07-10 21:50:25 +02:00
70211dc9ac Ajout du fichier compose.yml 2025-07-10 21:37:32 +02:00
43c4ba4639 Ajout des fichiers pour la création de l'image docker 2025-07-10 21:30:49 +02:00
b63ff9d788 Modification des chemins pour les fichiers pour qu'ils soient relatifs à l'emplacement du script 2025-07-10 20:12:16 +02:00
05a0b50741 Ajout d'une vérification dans la corbeille de GLPI 2025-07-10 20:07:53 +02:00
c46e426282 Vérification des appareils dans la corbeille du serveur GLPI 2025-07-10 19:57:29 +02:00
1228e2ac2a Correction du bug lorsque GLPI a plusieurs fois le même appareil 2025-07-10 19:45:04 +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
15 changed files with 308 additions and 52 deletions

108
README.md
View File

@ -3,28 +3,50 @@
## Explication de l'usage des scripts et de la configuration :
### 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.
Voici une liste des paramètres du fichier de configuration et les valeurs attendues (chaque valeur doit être entre " ") :
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.
- **"airwatchServer"**: l'adresse du serveur Airwatch
- **"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.
Voici un exemple du fichier de configuration :
```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
Le script syncGLPI.py permet de synchroniser les données des appareils présents dans Airwatch avec un inventaire GLPI.
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.
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
- 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
Les éléments synchronisés de Airwatch vers GLPI :
@ -33,6 +55,7 @@ Les éléments synchronisés de Airwatch vers GLPI :
- UUID
- le nom du système d'exploitation et sa version
- les logiciels présents sur la machine
- le numéro de série
Les éléments synchronisés de GLPI vers Airwatch :
- Le nom d'inventaire de la machine qui est mis pour le friendlyname
@ -40,23 +63,31 @@ Les éléments synchronisés de GLPI vers Airwatch :
#### Paramètres
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
- **-searchFilter** : permet de filtrer la recherche des appareils dans airwatch sur un attribut spécifique parmi la liste suivante : "Id", "SerialNumber", "Imei", "UserName"
- **-searchValue** : la valeur pour la recherche lorsque -searchFilter est utilisé
- **-force** : permet d'outrepasser la vérification du verrou posé par le script lors de son exécution
- **-sF / --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é
- **-f / --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
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
- **-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
- **-force** : permet d'outrepasser la vérification du verrou posé par le script lors de son exécution
- **-staginguser** : permet de préciser l'utilisateur de staging pour la recherche des appareils à modifier (override le fichier de paramètres)
- **-serialnumber** : permet de filtrer sur un numéro de série précis
- **-u / --staginguser** : permet de préciser l'utilisateur de staging pour la recherche des appareils à modifier (override le fichier de paramètres)
- **-sn / --serialnumber** : permet de filtrer sur un numéro de série précis
- **-f / --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
## Installation avec docker
## Dépendances
Installation des paquets linux :
@ -71,9 +102,28 @@ apt-get install -y python3 python3-pip git
yum install -y python3 python3-pip git
```
**Dépendances python**
## Modules python nécessaires
Voici la liste des modules python nécessaires au fonctionnement des scripts :
- cryptography
- requests
- toml
Un fichier requirements.txt contenant les modules dont dépendent les scripts est présent dans le répertoire scripts.
Les modules peuvent être installés de plusieurs façons :
**Avec le gestionnaire de paquet python Pip**
```
python3 -m pip install cryptography requests
python -m pip install -r requirements.txt
```
**Avec le gestionnaire de paquet apt (debian/ubuntu)**
```
apt-get install -y python3-cryptography python3-requests python3-toml
```
**Avec le gestionnaire de paquet yum (redhat/centos)**
```
yum install -y python3-cryptography python3-requests python3-toml
```
## Installation des scripts
@ -145,7 +195,7 @@ Description=Script qui assigne les appareils en staging aux utilisateurs en se b
[Timer]
OnUnitInactiveSec=1m
AccuracySec=1us
Unit=airwatchSync.service
Unit=airwatchStaging.service
[Install]
WantedBy=timers.target

28
compose.yml Normal file
View File

@ -0,0 +1,28 @@
services:
sync:
image: emitlinks/airwatchConnector
restart: unless-stopped
volumes:
- ./conf:/airwatchConnector/conf
- ./certs:/airwatchConnector/certs
- ./logs:/airwatchConnector/logs
healthcheck:
test: if [ -f $(ls /airwatchConnector/*_SyncGLPI.lock) ]; then exit 0; else exit 1; fi
interval: 5m
start_period: 3h
environment:
TASK: "syncGLPI"
stagingAssignment:
image: emitlinks/airwatchConnector
restart: unless-stopped
volumes:
- ./conf:/airwatchConnector/conf
- ./certs:/airwatchConnector/certs
- ./logs:/airwatchConnector/logs
healthcheck:
test: if [ -f $(ls /airwatchConnector/*_StagingUserAssignation.lock) ]; then exit 0; else exit 1; fi
interval: 15s
start_period: 5m
environment:
TASK: "stagingAssignment"

10
dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM python
RUN <<-EOF
apt-get update
apt-get install -y python3-cryptography python3-requests python3-toml
EOF
ADD scripts\* /airwatchConnector
ADD pre-start.py /
ADD start.sh /
RUN chmod u+x /start.sh
ENTRYPOINT ["/start.sh"]

26
pre-start.py Normal file
View File

@ -0,0 +1,26 @@
import os
import toml
baseDir = "/airwatchConnector"
logDir = f"{baseDir}/logs"
confDir = f"{baseDir}/conf"
confFiles = os.listdir(confDir)
# On récupère que les fichiers .conf
confFiles = [conf for conf in confFiles if os.path.isfile(f"{confDir}/{conf}") and not conf.endswith(".conf")]
for conf in confFiles:
# On forme un nom à partir du nom du fichier de conf sans l'extension
# et on enlève les espaces
confName = conf[:5].replace(' ', '')
with open(f"{confDir}/{conf}", "r") as f:
settings = toml.load(f)
# Create log folder
if(settings["LOGS"]["Enabled"]):
logPath = settings["LOGS"].get(Path)
if not os.path.exists(f"{logDir}/{logPath}"):
os.makedirs(f"{logDir}/{logPath}")

28
scripts/StagingUserAssignation.py Normal file → Executable file
View File

@ -20,7 +20,7 @@ args = parser.parse_args()
if(args.configpath != None and args.configpath != ''):
settings = getSettings(args.configpath)
else:
settings = getSettings("./conf/settings.conf")
settings = getSettings(f"{os.path.realpath(os.path.dirname(__file__))}/conf/settings.conf")
#=========== Configuration des logs ===========#
@ -41,7 +41,7 @@ if(settings["LOGS"]["Enabled"]):
if(settings["LOGS"].get("Path") and settings["LOGS"].get("Path") != ""):
fileHandler = logging.FileHandler(f"{settings['LOGS'].get('Path')}stagingUserAssignation.log")
else:
fileHandler = logging.FileHandler('./logs/stagingUserAssignation.log')
fileHandler = logging.FileHandler(f'{os.path.realpath(os.path.dirname(__file__))}/logs/stagingUserAssignation.log')
fileHandler.setLevel(logginglevel)
fileHandler.setFormatter(formatter)
logger.addHandler(fileHandler)
@ -56,7 +56,8 @@ if(not args.silent):
#======== Paramètres du script ========#
# Emplacement du verrou
lockFile = './airwatchStagingUserAssignation.lock'
nameForLockFile = settings["GLPI"]["UserAgent"].replace(' ', '-')
lockFile = f'{os.path.realpath(os.path.dirname(__file__))}/{nameForLockFile}_StagingUserAssignation.lock'
debug=args.debug
@ -78,15 +79,18 @@ else:
# Initialisation de l'api Airwatch
try:
airwatch = AirwatchAPI(settings)
# Recherche des appareils filtré sur l'utilisateur de staging
devices = airwatch.GetDevices(stagingUser)
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}")
os.remove(lockFile)
exit(1)
# Recherche des appareils filtré sur l'utilisateur de staging
devices = airwatch.GetDevices(stagingUser)
if(devices == None):
logger.info(f"No device found with staging user ({stagingUser}), exiting...")
os.remove(lockFile)
@ -139,9 +143,15 @@ for device in devices:
else:
logger.warning(f"Device with id {device.Id} is not assigned to any user in GLPI, skipping the device")
elif(deviceCount > 1):
logger.info(f"More than one entry found in GLPI for device with id {device.Id}")
logger.error(f"{count} devices matching airwatch device {device.FriendlyName} (Airwatch id={device.Id}) in GLPI trashbin (GLPI ids = {', '.join(deviceID)}), skipping this device...")
else:
logger.error(f"Device {device.Id} with serialnumber {device.SerialNumber} not found in GLPI (in trash bin ?)")
deviceIDTrash, dataTrash, deviceCountTrash = glpiapi.GetDevice(device, trashbin=True)
if(countTrash > 1):
logger.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:
logger.error(f"Device {device.FriendlyName} (Airwatch id={device.Id}) not found in GLPI.")
# Suppression du verrou

0
scripts/certs/empty Normal file
View File

View File

@ -1,4 +1,3 @@
[AIRWATCH]
Server = "https://airwatchServer"
APIKey = "APIKEY"
@ -19,10 +18,13 @@ StagingUser = "staging-pr"
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

3
scripts/functions.py Normal file → Executable file
View File

@ -23,12 +23,15 @@ StagingUser = "staging-pr"
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
"""

35
scripts/includes/GLPIAPI.py Normal file → Executable file
View File

@ -55,17 +55,39 @@ class GLPIAPI:
return deviceID, data, search["totalcount"]
elif(search["totalcount"] > 1):
deviceID = list(search["data"].keys())
return deviceID, search["data"], search["totalcount"]
else:
return None, None, 0
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):
headers = {
"Content-Type":"Application/x-compress",
"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):
@ -77,6 +99,17 @@ class GLPIAPI:
}
uri = f"{self.Server}/apirest.php/Computer/"
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 CreateInventoryForAirwatchDevice(self, device, deviceName, apps=None):
platforms = {

0
scripts/includes/__init__.py Normal file → Executable file
View File

13
scripts/includes/airwatchAPI.py Normal file → Executable file
View File

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

0
scripts/logs/empty Normal file
View File

3
scripts/requirements.txt Normal file
View File

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

63
scripts/syncGLPI.py Normal file → Executable file
View File

@ -22,30 +22,57 @@ args = parser.parse_args()
if(args.configpath != None and args.configpath != ''):
settings = getSettings(args.configpath)
else:
settings = getSettings("./conf/settings.conf")
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('./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)
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):
@ -57,7 +84,8 @@ if(not args.silent):
#======== Paramètres du script ========#
# 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"Airwatch server: {settings['AIRWATCH']['Server']}")
@ -88,6 +116,7 @@ logger.info("========= Synchronization started =========")
try:
airwatch = AirwatchAPI(settings)
# recherche des appareils
devices = airwatch.GetDevices()
logger.info("Airwatch server connection succeeded")
except FileNotFoundError as F:
@ -160,18 +189,28 @@ if(searchFilter != None):
for device in devices:
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
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} (id={device.Id}) on GLPI")
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):
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
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
inventory = glpiapi.CreateInventoryForAirwatchDevice(device, data["1"], apps)
@ -191,8 +230,14 @@ for device in devices:
logger.info(f"Updating {deviceID} on GLPI")
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):
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)
logger.info("========= End of synchronization =========")

39
start.sh Normal file
View File

@ -0,0 +1,39 @@
#!/bin/bash
python /pre-start.py
if [ $CONF != '' ]; then
case $TASK in
syncGLPI)
python3 /airwatchConnector/sync.py -c $CONF -f
;;
stagingAssignment)
python3 /airwatchConnector/stagingUserAssignation.py -c $CONF -f
;;
*)
exit 0
;;
esac
else
confFiles = $(ls /airwatchConnector/conf/*.conf)
if [ $confFiles = '' ]; then exit 1; do
for CONF in $confFiles; do
case $TASK in
syncGLPI)
python3 /airwatchConnector/sync.py -c $CONF -for
;;
stagingAssignment)
python3 /airwatchConnector/stagingUserAssignation.py -c $CONF -f
;;
*)
exit 0
;;
esac
done
fi