Verbessern (Improve) Phase
Die Improve-Phase ist der vierte Schritt in einem Six Sigma Projekt. In dieser Phase werden die in der Analyze-Phase identifizierten Hauptursachen für Prozessabweichungen adressiert und Lösungen entwickelt, um diese zu beheben. Ziel ist es, durch gezielte Verbesserungsmassnahmen die Prozessleistung zu optimieren und die identifizierten Probleme nachhaltig zu lösen. Dies umfasst die Anwendung von Kreativitätstechniken, statistischen Methoden und Pilotprojekten, um die Wirksamkeit der vorgeschlagenen Lösungen zu testen und zu validieren.
Was ist MSVC?
MSVC, oder Microservices, ist ein Architekturstil für die Entwicklung von Anwendungen, bei dem eine Anwendung in eine Sammlung von kleinen, unabhängigen, und lose gekoppelten Diensten aufgeteilt wird. Jeder dieser Dienste, oder Microservices, hat seine eigene Codebasis und kann unabhängig voneinander entwickelt, bereitgestellt und skaliert werden.
Was ist Flask API?
Flask API ist ein leichtgewichtiges REST-Framework auf Basis von Python, das zur Implementierung von stateless Webservices genutzt wird. Es unterstützt HTTP-Methoden, JSON-Serialisierung und Middleware-Integration. Als Microservice fungiert es als Endpoint zur Datenverarbeitung oder Systemintegration.
Umsetzung (Improve)
Wie in den vorherigen Schritten beschrieben, möchte ich einen Microservice erstellen, welcher von diversen Tenants den aktuellen Lizenzstand abfragt und im Falle dass keine Lizenz mehr übrig ist, sollte der Supporttechniker informiert werden, damit der Bestellprozess gestartet werden kann.
Eine Umsetzung ist mir gelungen, wie ich Sie beschrieben habe. Als Endprodukt habe ich einen Microservice, welcher mittels FlaskAPI und dessen Templates ein Frontend anzeigen. Zusätzlich habe ich eine Integration im SharePoint, in der ich die Daten aus der Abfrage, in eine SharePoint Liste schreibe. Wenn die freien Lizenzen bei 0 stehen, dann wird mittels PowerAutomate ein Flow gestartet, welcher dies dem Supporttechniker meldet.
Das Know-how habe ich mir durch meine aktive Teilnahme am MSVC-Unterricht bei Boris Langert sowie durch die YouTube-Tutorials Python for Beginners | Telusko von Telusko.
⚠️ Wichtig
Die gesammte Umsetzung wird nur in einer lokalen Dockerumgebung aufgebaut. Da diese Semesterarbeit später auch in einer Produktiven umgebung in den Einsatz kommen kann, soll diese zuerst lokal funktionieren. Zusätzlich, wäre die Produktivumgebung später auch auf einem Server und würde durch Dockerdesktop betrieben/gehostet werden. Dieser Server ist aber nur durch das interne Netzwerk der Firma erreichbar Somit ist das Szenario, lokal auf dem eigenen Notebook realistisch und fast 1:1 das gleiche.Ein weiterer Punkt ist der Datenschutz. Da wir diverse Daten zu den jeweiligen Tenants in diesem Service haben, darf das ganze nicht in die Cloud. (genaueres folgt später)
Als erster Aufbau werden zwei Testtenants der ISE AG verwendet. Diese Simulieren dann alle Tenants, welche später ggf. gemonitort werden.
Abschnitt | Beschreibung | GitHub-Issue |
---|---|---|
Sichere Verbindung zur Graph API | Aufbau einer zertifikatsbasierten, sicheren Verbindung zu Microsoft Graph | #13 Establish secure connection |
Lizenzdaten automatisch abrufen | Microservice ruft die aktuellen Lizenzstände via Graph API automatisiert ab | #16 Implement license fetch via Graph |
Flask Microservice Architektur | Aufbau des Services mit Flask, Docker, Blueprints und SQLite | #12 Set up Flask microservice architecture |
Lizenzdaten in SharePoint speichern | Lizenzstatus wird pro Tenant in einer SharePoint-Liste persistiert | #14 Store license data in SharePoint list |
Automatische Benachrichtigung via PowerAutomate | Wenn keine Lizenzen mehr verfügbar sind, wird der Support automatisch informiert | #15 Create PowerAutomate-Flow for alerting |
Frontend zur Lizenzanzeige | Darstellung aller Lizenzdaten in einer übersichtlichen Weboberfläche | #17 Create frontend to visualize license data |
REST API für Frontend-Integration | Bereitstellung von API-Endpunkten für das Frontend | #18 Develop REST API for frontend |
Benutzerauthentifizierung via Azure | Zugriff nur nach Login mit Firmen-Microsoft-Konto möglich | #19 Implement authentication and access control |
SharePoint als zentrale Steuerung | Tenant-Aktivität und Monitoring-Status steuerbar über SharePoint | #20 Create SharePoint List for License Data Storage |
Monitoring im Frontend steuerbar | Möglichkeit, Monitoring pro Tenant direkt über das UI zu aktivieren/deaktivieren | #22 Control PowerAutomate Monitoring via Frontend |
Zentrales Logging | Logfile für Fehler, API-Aufrufe und Systemzustände mit Rotation | #23 Logging |
Wie soll der MSVC ablauffen?
Folgendes Diagramm zeigt auf, wie der MSVC ablaufen soll.
Ablaufdiagramm der App
- Blauer Zyklus: Der Scheduled Task ruft periodisch den Lizenzstatus ab.
- Grüner Pfad: Lizenzen verfügbar – Daten werden dokumentiert.
- Roter Pfad: Lizenzen = 0 – PowerAutomate wird getriggert.
⚠️ Wichtig
Nachfolgend werden die einzelnen Implementierungsschritte aufgezeigt, wie der MSVC aufgebaut wurde. Über die obere Auflistung, kann zu den Sektionen oder zu den Issues mit Userstories gesprungen werden.
Grundgerüst des Microservices
Zu beginn habe ich mit der App begonnen, dort habe ich mit der Vorlage aus dem Unterricht begonnen und auf dieser Aufgebaut. Da wir im Unterricht immer wieder ergänzugen gemacht haben, habe ich eigentlich von 0 begonnen und bis zum schritt alles vorbereitet, bis ich dort angelangt bin, bis dahin, wo ich auf der App aufbauen möchte.
Die Struktur war schlicht und nur gerade das nötigste.
- Docker / Compose Files
- Blueprint implementiert
- SQLite DB
Angaben zum Microsoervice
Technologie: FlaskAPI
Scriptsprache: Python
Endpunkt: http://localhost:5000/api/v1
Swagger-UI: http://localhost:5000/api/v1/docs
Zusätzlich wurde auch ein Swagger eingerichtet, um die einzelnen Routen zu dokumentieren. Der Swagger ist unter folgender URL zu erreichen (Nur wenn der Docker-Container aktiv ist) Lizenztool-Swagger-UI
Grundgerüst des Microservices
Die Entwicklung des Lizenzüberwachungstools begann mit einem einfachen Grundgerüst, basierend auf der im Unterricht vermittelten Vorlage. Im Verlauf des Semesters wurde die Vorlage stetig erweitert. Da die Unterrichtseinheiten immer wieder neue Bausteine ergänzten, entschloss ich mich dazu, die App vollständig neu aufzubauen – modular, testbar und Docker-kompatibel.
Das initiale Setup konzentrierte sich auf eine schlanke, aber funktionale Struktur:
- Docker-/Compose-Files zur Containerisierung und einfachen Bereitstellung
- Blueprints zur sauberen Trennung von Funktionen und Routen
- SQLite als leichtgewichtiges Datenbanksystem für die Entwicklungsphase
Projektstruktur
licensetool
├── app
│ ├── licenses
│ │ ├── __init.py
│ │ └── routes.py
│ ├── main
│ │ ├── __init.py
│ │ └── routes.py
│ ├── models
│ │ └── license.py
│ ├── __init__.py
│ └── extensions.py
├── app.db
├── compose.test.yaml
├── compose.yaml
├── config.py
├── dockerfile
├── dockerfile.test
└── requirements.txt
Die Struktur wurde so gewählt, dass spätere Erweiterungen (z. B. neue Blueprints oder externe Services) problemlos integriert werden können.
Bereits mit diesem Setup war es möglich, erste simulative API-Calls durchzuführen. In der Anfangsphase wurden Testdaten manuell in die Datenbank eingetragen, um die korrekte Funktion der API-Endpunkte zu validieren.
ℹ️ Information
Die SQLite-Datenbank dient in der Entwicklungsphase primär zu Testzwecken.
Technische Eckdaten des Microservices
Komponente | Beschreibung |
---|---|
Technologie | Flask (Flask-RESTful) |
Module | FlaskAPI |
Sprache | Python |
API-Endpunkt | http://localhost:5000/api/v1 |
Swagger UI | http://localhost:5000/api/v1/docs |
Zusätzlich wurde ein Swagger-Dokumentationsinterface eingerichtet, um alle API-Routen übersichtlich darzustellen. Dies erleichtert nicht nur die Entwicklung, sondern auch die spätere Integration in andere Systeme.
👉 Lizenztool Swagger UI (lokal) (nur aktiv bei laufendem Docker-Container)
Implementierung: Lizenzabfrage bei anderen Tenants (via Microsoft Graph)
Nachdem das Grundgerüst des Microservices steht und die ersten API-Tests erfolgreich durchgeführt wurden, ging es im nächsten Schritt darum, die Lizenzdaten automatisiert für verschiedene Microsoft-Tenants abzufragen und für die spätere Weiterverarbeitung (z. B. Speicherung oder Eskalation) bereitzustellen.
Da es sich bei den zu überwachenden Tenants um Microsoft-365-Umgebungen handelt, bot sich die Microsoft Graph API als zentrale Schnittstelle an. Ich konnte hierfür auf bestehende Erfahrungen zurückgreifen, da ich eine ähnliche Funktion bereits in einem anderen Projekt implementiert hatte.
Sicherheit durch Config-Profile
In der ersten Version waren die Authentifizierungsdaten fest im Code hinterlegt – das war aus Sicherheits- und Wartungsgründen jedoch nicht ideal. Für die produktionsnahe Umsetzung habe ich mich deshalb für dynamisch ladbare JSON-Konfigurationsprofile entschieden. Diese enthalten alle nötigen Angaben (z. B. tenant_id
, Zertifikatspfad, Ablaufdatum) und lassen sich bei Zertifikatserneuerung einfach austauschen.
ℹ️ Information
Diese Abstraktion erlaubt eine saubere Trennung von Code und Konfiguration. Neue Tenants können künftig mit minimalem Aufwand eingebunden werden – es reicht ein neues Config-File und Zertifikat im jeweiligen Ordner.
Beispiel eines Config-Files:
{
"tenant_id": "<tenantid>",
"tenant_name": "tenantname",
"client_id": "<clientid>",
"thumbprint": "<thumbprint>",
"cert_path": "certs/<tenantname>/mycert_<tenantname>.pem",
"expires": "2026-05-19"
}
Erweiterung der Struktur
Im Projekt wurden folgende Ordner ergänzt:
licensetool
├── app
│ ├── modules
│ │ └── mggraph.py # Graph-Modul zur Lizenzabfrage
├── certs
│ ├── *certfolder foreach tenant*
│ ├── *info folder foreach tenant*
│ └── certcreation.sh
├── config-profiles
│ └── *config-profile foreach tenant*
│...
Lizenzabfrage via Microsoft Graph API
Die eigentliche Abfrage der Lizenzinformationen (subscribedSkus
) erfolgt über das Modul mggraph.py
. Dort übernimmt die Klasse GraphLicenseClient
die Authentifizierung sowie die API-Kommunikation.
class GraphLicenseClient:
def __init__(self, tenant_name: str):
self.tenant_name = tenant_name
self.config = self._load_config()
self.token = self._authenticate()
def _load_config(self):
config_file = f"config-profiles/config-{self.tenant_name}-profile.json"
with open(config_file, "r") as f:
return json.load(f)
def _authenticate(self):
authority = f"https://login.microsoftonline.com/{self.config['tenant_id']}"
app = ConfidentialClientApplication(
client_id=self.config['client_id'],
authority=authority,
client_credential={
"private_key": open(self.config['cert_path'], "r").read(),
"thumbprint": self.config['thumbprint']
}
)
result = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
if "access_token" not in result:
raise Exception(f"Token acquisition failed: {result.get('error_description')}")
return result["access_token"]
def get_license_status(self):
headers = {"Authorization": f"Bearer {self.token}"}
response = requests.get(
"https://graph.microsoft.com/v1.0/subscribedSkus",
headers=headers,
timeout=10
)
if response.status_code != 200:
raise Exception(f"Graph API error: {response.status_code} - {response.text}")
return response.json()
Beispielhafte API-Antwort
Die get_license_status()
-Methode liefert eine strukturierte JSON-Antwort mit allen abonnierten Lizenzen des Tenants:
[
{
"available_units": 20,
"consumed_units": 19,
"free_units": 1,
"skuid": "94763226-9b3c-4e75-a931-5c89701abe66",
"skupartnumber": "STANDARDWOFFPACK_FACULTY"
},
{
"available_units": 1,
"consumed_units": 1,
"free_units": 0,
"skuid": "0e142028-345e-45da-8d92-8bfd4093bbb9",
"skupartnumber": "PHONESYSTEM_VIRTUALUSER_FACULTY"
},
{
"available_units": 12,
"consumed_units": 10,
"free_units": 2,
"skuid": "d979703c-028d-4de5-acbf-7955566b69b9",
"skupartnumber": "MCOEV_FACULTY"
},
{
"available_units": 2000,
"consumed_units": 1500,
"free_units": 500,
"skuid": "314c4481-f395-4525-be8b-2ec4bb1e9d91",
"skupartnumber": "STANDARDWOFFPACK_STUDENT"
},
{
"available_units": 100000,
"consumed_units": 7,
"free_units": 99930,
"skuid": "f30db892-07e9-47e9-837c-80727f46fd3d",
"skupartnumber": "FLOW_FREE"
}
]
ℹ️ Hinweis zu Daten und Datenschutz
Die angezeigten Lizenzzahlen wurden zu Test- und Demonstrationszwecken angepasst und entsprechen nicht den realen Werten produktiver Microsoft-Tenants.
Zudem wurden sämtliche darstellbaren Informationen im Sinne des Datenschutzes anonymisiert oder verfremdet, um Rückschlüsse auf reale Kundendaten auszuschliessen.
Somit haben wir bereits einen wichtigen Schritt gemacht, indem wir die Lizenzen als JSON zurück erhalten. Als nächstes, müssen wir die Daten aufwerten und bereitmachen für das Frontend.
Implementierung: Frontend zur Visualisierung der Lizenzdaten
Nachdem die Lizenzdaten erfolgreich über die Microsoft Graph API abgerufen und als JSON verarbeitet werden konnten, wurde im nächsten Schritt ein benutzerfreundliches Frontend entwickelt. Dieses dient allen Mitarbeitenden – unabhängig vom technischen Hintergrund – als zentrale Übersicht, um den aktuellen Lizenzstatus jederzeit auf einen Blick einsehen zu können.
Ziel war es, eine intuitive und optisch ansprechende Oberfläche bereitzustellen, die den aktuellen Zustand der Lizenzen klar darstellt, Filtermöglichkeiten bietet und potenzielle Engpässe direkt ersichtlich macht – ohne dass die Nutzer mit technischen Details wie API-Calls oder Datenbanken konfrontiert werden.
Verfügbare Ansichten im Frontend
Es wurden mehrere HTML-Seiten (Templates) implementiert, jeweils mit eigener CSS-Datei zur Gestaltung:
Template-Datei | Beschreibung |
---|---|
statusall.html | Übersicht aller Lizenzen aus allen Tenants in einer zentralen Tabelle |
tenant.html | Einzelabfrage eines spezifischen Tenants (z. B. Detailansicht) |
mainpage.html | Startseite / Einstiegsseite ins Tool |
monitoring.html | Verwaltungsansicht zur Steuerung ob ein Tenant aktiv ist oder ob Mitteilungen zu diesem Tenant versendet werden sollen. |
├── app
│ ├── static
│ │ ├── images
│ │ │ └── frontend.css
│ │ ├── mainpage.css
│ │ ├── monitoring.css
│ │ ├── statusall.css
│ │ └── tenant.css
│ ├── templates
│ │ ├── mainpage.html
│ │ ├── monitoring.html
│ │ ├── statusall.html
│ │ └── tenant.html
│...
Routenbindung der Templates
Die Templates werden mit dem Flask-Modul render_template()
in den jeweiligen Blueprints geladen.
# Beispiel einer Template-Route
@bp.get('/status/tenant')
def show_tenant():
return render_template("tenant.html")
Ausschnitt aus app/licenses/routes.py
Funktionen im Frontend
- Tabellarische Darstellung aller Lizenzdaten
- Farbliche Hervorhebung bei kritischem Lizenzstand
- Such- und Filterfunktionen über JavaScript
- Anbindung an API-Endpoint über
fetch()
zur Anzeige der aktuellen Daten - Trennung von HTML, CSS und Logik (JavaScript) für bessere Wartbarkeit
Beispielhafte HTML-/JS-Integration (statusall.html
)
<input type="text" id="filterInput" placeholder="z. B. ISE School">
...
<table id="licenseTable">
<thead>
<tr>
<th>Tenant</th>
<th>SKU Part Number</th>
<th>SKU ID</th>
<th>Verfügbar</th>
<th>Verbraucht</th>
<th>Frei</th>
</tr>
</thead>
<tbody id="licenseBody">
<!-- Dynamischer Inhalt -->
</tbody>
</table>
<script>
let fullData = [];
function renderTable(data) {
const tbody = document.getElementById('licenseBody');
tbody.innerHTML = '';
data.forEach(item => {
const row = document.createElement('tr');
if (item.free_units <= 0) row.classList.add('low-license');
row.innerHTML = `
<td>${item.tenant}</td>
<td>${item.skupartnumber}</td>
<td>${item.skuid}</td>
<td>${item.available_units}</td>
<td>${item.consumed_units}</td>
<td>${item.free_units}</td>
`;
tbody.appendChild(row);
});
}
fetch('/api/v1/licenses/status')
.then(response => response.json())
.then(data => {
fullData = data;
renderTable(fullData);
});
document.getElementById("filterInput").addEventListener("keyup", () => {
const query = document.getElementById("filterInput").value.toLowerCase();
const filtered = fullData.filter(item =>
item.tenant.toLowerCase().includes(query) ||
item.skupartnumber.toLowerCase().includes(query)
);
renderTable(filtered);
});
</script>
Ziel des Frontends
Das Frontend schafft eine klare Benutzeroberfläche, in der Lizenzdaten:
- tabellarisch dargestellt werden
- durch Farben oder Filter visuell hervorgehoben sind
- gezielt nach Tenants oder Lizenztypen gefiltert werden können
- aktuell bleiben dank direkter API-Anbindung
Damit kann jede Person schnell erfassen, ob Handlungsbedarf besteht – z. B. vollständigem Verbrauch.
Implementierung: SharePoint-Einbindung
Da in unserem Unternehmen intensiv mit SharePoint gearbeitet wird, war von Beginn an vorgesehen, die Lizenzdaten und Konfigurationen dort zentral zu verwalten. Der Microservice kommuniziert über die Microsoft Graph API mit SharePoint – sowohl zur Datenablage als auch zur Steuerung der Lizenzüberwachung.
Ein zusätzlicher Grund für die SharePoint-Einbindung liegt in der geplanten Alarmierung bei Lizenzengpässen über PowerAutomate, die auf Felder in den SharePoint-Listen reagiert. PowerAutomate wird an anderer Stelle genauer erklärt – an dieser Stelle reicht es zu wissen, dass der SharePoint auch dafür als Trigger dient.
Für den Zugriff wurde eine eigene App-Registrierung erstellt, welche ausschliesslich die Berechtigungen für den SharePoint-Zugriff besitzt.
├── config-profiles
│ ├── sharepoint
│ │ └── sp-config-<name>-profile.json
Übersicht der SharePoint-Listen und Felder
ℹ️ Information
Der Test-Tenant, auf dem alle Listen & auch die spätere Authentifizierung stattfinden, ist der Tenant Iseschool2013, welcher auch der Testtenant der ISE AG ist. Erst wenn alles korrekt läuft und die Testphase überstanden hat, kann der MSVC in die Produktive umgebung implementiert werden. Die nachfolgenden SharePoint-Listen, werden auf der Site-Collection: /Sites/misch-sem3arbeit/ gespeichert.
Parameterliste – Systemweite Konfigurationswerte
Feldname | Typ | Beschreibung |
---|---|---|
Parameter | Textfeld | Der technische Name des Parameters (z. B. Mail-Adresse) |
Parameterwert | Textfeld | Der zugehörige Wert (z. B. support@iseag.ch) |
Wird verwendet für globale Konfigurationswerte wie Empfänger, Absender, Kommunikationskanal etc.
Tenantliste – Steuerung der zu überwachenden Tenants
Feldname | Typ | Beschreibung |
---|---|---|
Title | Textfeld | Anzeigename / Name des Tenants |
enabled | Ja/Nein | Ob der Tenant aktiv überwacht werden soll |
monitoring | Ja/Nein | Ob bei Lizenzmangel eine Alarmierung (PowerAutomate) ausgelöst werden soll |
cert_expires | Datum | Ablaufdatum des hinterlegten App-Zertifikats |
Diese Liste ist für das Aktivieren/Deaktivieren einzelner Tenants zuständig und wird bei jeder Abfrage vor der Datenverarbeitung geprüft.
Lizenzstatusliste – Aktuelle Lizenzwerte pro Tenant
Feldname | Typ | Beschreibung |
---|---|---|
Lizenzname | Textfeld | Name/Bezeichnung der Lizenz (z. B. STANDARDWOFFPACK_STUDENT) |
Verfügbar | Zahl | Anzahl insgesamt verfügbarer Lizenzen |
Gebraucht | Zahl | Anzahl aktuell verwendeter Lizenzen |
Frei | Zahl | Differenz zwischen Verfügbar und Gebraucht |
tenant | Textfeld | Name des zugehörigen Tenants |
trigger_inform_supporter | Ja/Nein | Wird bei 0 freien Lizenzen gesetzt, um den Flow via PowerAutomate zu starten |
technician_informed | Ja/Nein | Gibt an, ob der Support bereits informiert wurde |
Diese Liste ist der zentrale Datenspeicher des Lizenzstatus und dient zugleich als Triggerquelle für PowerAutomate.
Technische Umsetzung im Code
Die Aktualisierung bzw. Erstellung der SharePoint-Einträge erfolgt im Modul mggraph.py
innerhalb der Funktion push_license_status_to_sharepoint()
.
Für jede Lizenz wird geprüft, ob ein Eintrag bereits existiert. Falls ja, wird dieser aktualisiert – andernfalls neu erstellt. Die Entscheidung, ob das Feld trigger_inform_supporter
gesetzt wird, basiert auf folgender Logik:
if free == 0 and not technician_informed:
sp_fields[field_mapping["Infosup"]] = True
if free > 0 and technician_informed:
sp_fields["technician_informed"] = False
-
Erklärung der Triggerlogik:
-
Wenn keine freien Lizenzen mehr verfügbar sind (
free == 0
) und der Techniker noch nicht informiert wurde (technician_informed = false
), wirdtrigger_inform_supporter = true
gesetzt.
→ Dies löst den PowerAutomate-Flow zur Benachrichtigung aus. -
Sobald wieder freie Lizenzen verfügbar sind (
free > 0
) undtechnician_informed = true
, wird dieses Feld automatisch auffalse
zurückgesetzt, um zukünftige Trigger zu ermöglichen.
-
Vollständiger Ablauf zur Verarbeitung eines Lizenz-Datensatzes
Der Ablauf zur Speicherung und Aktualisierung einer Lizenz im SharePoint umfasst folgende Schritte:
# Schritt 1: Tenantprüfung – nur wenn aktiv & monitoring aktiv
tenant_list_url = f"https://graph.microsoft.com/v1.0/sites/{site_id}/lists/{tenant_list_id}/items?expand=fields"
tenant_list_resp = requests.get(tenant_list_url, headers=headers)
tenant_list_resp.raise_for_status()
tenant_items = tenant_list_resp.json().get("value", [])
matching_tenant = next((item for item in tenant_items if item["fields"].get("Title") == tenant_name), None)
if not matching_tenant:
logger.warning(f"Tenant '{tenant_name}' NICHT in Tenantliste gefunden – Abbruch.")
return
if not matching_tenant["fields"].get("enabled", True):
logger.info(f"Tenant '{tenant_name}' ist inaktiv – Abbruch.")
return
if not matching_tenant["fields"].get("monitoring", False):
logger.info(f"Monitoring für Tenant '{tenant_name}' ist deaktiviert – Abbruch.")
return
# Schritt 2: Abfrage bestehender Lizenz-Einträge aus SharePoint
license_list_url = f"https://graph.microsoft.com/v1.0/sites/{site_id}/lists/{license_list_id}/items?expand=fields"
license_list_resp = requests.get(license_list_url, headers=headers)
license_list_resp.raise_for_status()
existing_items = license_list_resp.json().get("value", [])
# Schritt 3: Verarbeitung jeder Lizenz
for lic in licenses:
sku = lic.get("skupartnumber", "UNKNOWN")
free = lic.get("free_units", 0)
used = lic.get("consumed_units", 0)
avail = lic.get("available_units", 0)
match = next(
(item for item in existing_items if
item["fields"].get("Title") == sku and
item["fields"].get(tenant_field) == tenant_name),
None
)
sp_fields = {
field_mapping["Frei"]: free,
field_mapping["Gebraucht"]: used,
field_mapping["Verfügbar"]: avail
}
if match:
item_id = match["id"]
technician_informed = match["fields"].get("technician_informed", False)
# Triggerlogik: Engpass und Rücksetzung
if free == 0 and not technician_informed:
sp_fields[field_mapping["Infosup"]] = True
if free > 0 and technician_informed:
sp_fields["technician_informed"] = False
# PATCH – Eintrag aktualisieren
url_update = f"https://graph.microsoft.com/v1.0/sites/{site_id}/lists/{license_list_id}/items/{item_id}/fields"
response = requests.patch(url_update, headers=headers, json=sp_fields)
response.raise_for_status()
logger.info(f"Lizenz '{sku}' für Tenant '{tenant_name}' wurde aktualisiert.")
else:
# POST – Neuer Eintrag
sp_fields.update({
field_mapping["Tenant"]: tenant_name,
field_mapping["Lizenzname"]: sku
})
url_create = f"https://graph.microsoft.com/v1.0/sites/{site_id}/lists/{license_list_id}/items"
response = requests.post(url_create, headers=headers, json={"fields": sp_fields})
response.raise_for_status()
logger.info(f"Neue Lizenz '{sku}' für Tenant '{tenant_name}' erstellt.")
Verwendete Microsoft Graph Endpunkte (SharePoint)
Aktion | HTTP-Methode | Graph-Endpunkt |
---|---|---|
Tenantliste abrufen | GET | /sites/{site_id}/lists/{tenant_list_id}/items?expand=fields |
Lizenzstatus abrufen | GET | /sites/{site_id}/lists/{license_list_id}/items?expand=fields |
Lizenzstatus aktualisieren | PATCH | /sites/{site_id}/lists/{license_list_id}/items/{item_id}/fields |
Lizenzstatus neu erstellen | POST | /sites/{site_id}/lists/{license_list_id}/items |
Implementierung: PowerAutomate Flow
Damit bei einem Lizenzengpass nicht manuell geprüft werden muss, ob Handlungsbedarf besteht, wurde ein PowerAutomate-Flow eingerichtet, der bei bestimmten Bedingungen automatisch eine Benachrichtigung an den Support sendet.
Lizenzüberwachung – Trigger bei Engpass
Der Flow wird jedes Mal ausgelöst, wenn in der Lizenzstatusliste ein Eintrag geändert wird. Dabei prüft PowerAutomate, ob das Feld trigger_inform_supporter
auf true
gesetzt wurde.
Die Logik im Lizenz-Microservice sieht wie folgt aus:
-
Wenn
free_units = 0
(also keine Lizenzen mehr verfügbar sind)
und der Techniker noch nicht informiert wurde (technician_informed = false
),
wirdtrigger_inform_supporter = true
gesetzt → Flow wird getriggert. -
Ist
technician_informed = true
, wird kein neuer Trigger gesetzt, um Mehrfachbenachrichtigungen zu vermeiden.
Der Flow sendet bei Auslösung eine E-Mail mit den relevanten Informationen an das Support-Team.
Ablauf des PowerAutomate-Flows bei Lizenzengpass
ℹ️ Information
Der MSVC setzt automatisch das Feldtechnician_informed
zurück auffalse
, sobald bei einem Lizenzprodukt wieder freie Lizenzen verfügbar sind (d. h.free_units > 0
).
Dies stellt sicher, dass beim nächsten Engpass erneut eine Benachrichtigung über den PowerAutomate-Flow ausgelöst werden kann.
Das Rücksetzen erfolgt nur, wenn zuvortechnician_informed = true
war. Die gesamte Logik wird serverseitig im MSVC beim Schreiben in den SharePoint gesteuert.
Zertifikatsüberwachung – Ablaufwarnung
Ein zweiter Flow dient zur Überwachung der Gültigkeit von App-Zertifikaten, welche für die Authentifizierung via Microsoft Graph notwendig sind.
Er wird periodisch ausgeführt und überprüft das Ablaufdatum (cert_expires
) in der Tenantliste. Sobald ein Zertifikat in weniger als 7 Tagen abläuft, wird automatisch eine Benachrichtigung verschickt.
Ablauf des PowerAutomate-Flows zur Zertifikatsüberwachung
ℹ️ Hinweis:
Beide Flows greifen direkt auf die SharePoint-Listenstruktur zu, welche vom Microservice gepflegt wird. Die Automatisierung sorgt dafür, dass kritische Zustände (wie Lizenzmangel oder Zertifikatsablauf) nicht unbemerkt bleiben.
Implementierung: Authentifizierung
Damit nicht jede beliebige Person den Microservice nutzen kann, wurde eine Benutzerauthentifizierung via Microsoft eingebaut. Dabei kommt der OAuth 2.0 Authorization Code Flow zum Einsatz, welcher über Microsofts Azure Active Directory gesteuert wird. Ein Login ist Voraussetzung, um Zugriff auf API-Endpunkte oder das Frontend zu erhalten.
Ziel war es, keine eigene Benutzerdatenbank aufzubauen, sondern stattdessen bestehende Azure-Konten (Firmen-Accounts) zu nutzen.
Funktionsweise
Beim Aufruf geschützter Routen wird geprüft, ob ein gültiger Benutzer-Token vorhanden ist. Falls nicht, wird automatisch auf Microsofts Login-Seite weitergeleitet.
Nach erfolgreichem Login erhält der Microservice über einen Redirect den Access-Token sowie Benutzerinformationen zurück. Diese werden lokal in der Session gespeichert und für Folgeanfragen verwendet.
Technische Umsetzung
Datei | Funktion |
---|---|
routes.py | Regelt Login, Callback, Logout und optionalen Test-Login |
utils.py | Enthält den login_required -Decorator zum Absichern von Routen |
config-profiles/auth/ | Speichert Verbindungsdaten zur Azure App (Client ID, Secret, Tenant ID) |
Im Projekt wurden folgende Ordner ergänzt:
├── app
│ └── Auth
│ ├── __init__.py
│ ├── routes.py
│ └── utils.py
│
├── config-profiles
│ └── auth
│ └── *Config-profile for auth-module*
│...
🔐 Wichtig:
Ohne gültige Session wird der Zugriff verweigert – sowohl auf das Frontend als auch auf die API-Endpunkte.
Ausnahme: Diemainpage.html
bleibt öffentlich zugänglich und ist nicht geschützt.
Implementierung: Monitoring-Verwaltung
Falls es einmal Probleme mit einem Tenant gibt – oder ein neuer Tenant gerade erst erfasst wurde –, kann dessen Aktivierung sowie das Monitoring-Verhalten direkt über das Frontend gesteuert werden.
Dies erfolgt über den Bildschirm monitoring.html
, welcher eine Übersicht aller registrierten Tenants bietet.
Über diesen Screen lassen sich pro Tenant sowohl die Option Aktiv (enabled) als auch Monitoring aktiv (monitoring) ein- oder ausschalten.
Die Änderungen wirken sich direkt auf die SharePoint-Tenantliste aus und bestimmen, ob ein Tenant vom Microservice berücksichtigt wird und ob eine Alarmierung via PowerAutomate erfolgen soll.
Mit dem Monitoring werden folgende Strukturanpassungen gemacht:
licensetool
├── app
│ └── monitoring
│ ├── __init.py
│ └── routes.py
│...
Implementierung: Logging & Testing
Um im Fehlerfall gezielt analysieren zu können, wurde ein zentrales Logging sowie eine dedizierte Testumgebung eingerichtet. Beide Komponenten dienen der Qualitätssicherung und sorgen dafür, dass die Anwendung erst bei stabilem Zustand produktiv eingesetzt wird.
Testumgebung & Pytest
Vor jedem produktiven Rollout wird der Container in einer abgeschirmten Laborumgebung getestet. Dabei simulieren vorbereitete Datensätze typische Szenarien und prüfen die API auf korrekte Funktion.
Die Tests werden mit pytest
ausgeführt – einem flexiblen Framework für automatisiertes Testen in Python. Nur wenn ein definierter Prozentsatz der Tests erfolgreich ist, wird der Service live geschaltet.
Ergänzungen in der Projektstruktur
licensetool
├── app
│ └── modules
│ └── logging.py
├── logs
│ └── licensetool.log
├── test
│ ├── __init__.py
│ ├── conftest.py
│ ├── create_test_data.py
│ ├── test_auth.py
│ ├── test_license.py
│ ├── test_main.py
│ └── test_monitoring.py
├── compose.test.yaml
├── dockerfile.test
│...
Logging-Modul
Das Logging wurde über ein zentrales Modul logging.py
umgesetzt. Dieses initialisiert sowohl Datei-Logging als auch Konsolen-Ausgabe mit Rotation:
def setup_logging(log_file='logs/licensetool.log', level=logging.INFO):
...
file_handler = RotatingFileHandler(log_file, maxBytes=5*1024*1024, backupCount=3)
...
root_logger = logging.getLogger()
root_logger.setLevel(level)
...
Log-Ausgabe:
Alle Logs werden standardmäßig unter logs/licensetool.log
gespeichert und bei 5 MB automatisch rotiert.
Beispielauszug aus dem Log:
2025-06-19 11:22:03,699 [INFO] app.licenses.routes: Alle Lizenzstatus werden geladen (status/show)
2025-06-19 11:22:06,092 [INFO] app.licenses.routes: Lade Lizenzstatus für Tenant: ISE School 2013
2025-06-19 11:22:07,093 [INFO] werkzeug: 172.22.0.1 - - [19/Jun/2025 11:22:07] "GET /api/v1/licenses/status/show HTTP/1.1" 200 -
ℹ️ Hinweis:
Überlogger.info()
undlogger.error()
im gesamten Code lassen sich zielgerichtet Debug-Informationen schreiben – etwa bei Authentifizierungsproblemen oder SharePoint-Fehlern.
Erweiterung: Lizenz-Produktnamen
Aktuell werden die Lizenzen technisch anhand ihrer SKU Part Number identifiziert – zum Beispiel STANDARDWOFFPACK_STUDENT
oder FLOW_FREE
. Diese Systemnamen sind jedoch nicht für alle Benutzer direkt verständlich.
Um die Lesbarkeit und Benutzerfreundlichkeit zu verbessern, wurde ein zusätzliches Dictionary eingeführt, das die SKU Part Numbers den entsprechenden Display Names (also Klartextnamen) zuordnet.
Die SKU-Nummer bleibt weiterhin erhalten und wird im Datensatz mitgeführt – der Displayname dient ausschließlich zur besseren Darstellung im Frontend.
Beispielhafte Zuordnung im Dictionary:
PRODUCT_DISPLAY_NAMES = {
"STANDARDWOFFPACK_STUDENT": "Office 365 A1 for Students",
"FLOW_FREE": "Power Automate Free",
...
}
Das gesamte Mapping-File findet ihr unter
sku_mappings.json
Optimierung / Erweiterung: Frontend-Datenbank
Wie zu Beginn erwähnt, war die SQLite-Datenbank ursprünglich nur als temporärer Speicher für Testzwecke vorgesehen. Während der Entwicklung zeigte sich jedoch, dass der Microsoft Graph API-Aufruf – insbesondere in Kombination mit der SharePoint-Synchronisation – zu spürbaren Wartezeiten im Frontend führte.
Performanceproblem durch Live-Abfrage
Die Verzögerung trat vor allem dann auf, wenn Lizenzdaten live über Graph geladen und anschließend in SharePoint geschrieben wurden. Da dieser Prozess je nach Tenant und Anzahl der Lizenzen mehrere Sekunden dauern kann, wirkte das Frontend träge und unresponsive.
Lösung: Beibehalten der SQLite-Datenbank
Um dem entgegenzuwirken, wurde entschieden, die lokale SQLite-Datenbank weiterhin im System zu belassen – nicht als primärer Datenspeicher, sondern als Cache für das Frontend.
Diese Optimierung bringt mehrere Vorteile:
-
Frontend-Zugriffe auf Lizenzdaten erfolgen schnell und ohne API- oder Netzwerkaufruf
-
Benutzerinteraktionen (z. B. Filterung, Monitoring-Umschaltung) bleiben performant
-
Die Live-Daten via Microsoft Graph stehen weiterhin bei Bedarf zur Verfügung
Zwei Betriebsmodi im Frontend
Das System unterscheidet nun zwei Zugriffsarten:
Modus | Beschreibung |
---|---|
Nur Ansehen (Default) | Zeigt die Lizenzdaten aus der SQLite-Datenbank (schnell, reiner Lesezugriff) |
Aktualisieren in SP | Führt einen Live-API-Call durch, speichert die Lizenzdaten zuerst lokal in die SQLite-DB und überträgt sie danach in den SharePoint. Dabei kann auch der trigger_inform_supporter gesetzt werden |
📌 Hinweis:
Die zweite Option sollte nur bei Bedarf genutzt werden – z. B. zur manuell angestoßenen Aktualisierung oder zur Prüfung, ob eine Alarmierung nötig ist.
Zielsetzung
Die SQLite-Datenbank dient in dieser Architektur als lokaler Zwischenspeicher, um die Performance und Reaktionszeit des Frontends deutlich zu verbessern – insbesondere bei umfangreichen Lizenzdaten oder mehreren Tenants.
Während alle geschäftskritischen Prozesse wie Monitoring, Benachrichtigungen oder die langfristige Datenspeicherung weiterhin über SharePoint und Microsoft Graph abgewickelt werden, sorgt die lokale DB dafür, dass das Frontend auch bei hohen Abfragefrequenzen stabil und schnell bleibt.
Dadurch bleibt die Anwendung auch bei wachsender Tenant-Anzahl und parallelen Zugriffen leistungsfähig und nutzerfreundlich.
Die Datenbankstruktur ist wie folgt aufgebaut:
classDiagram
class LicenseModel {
+Integer id
+String name
+Integer count
}
class LicenseIn {
+String name
+Integer count
}
class LicenseOut {
+Integer id
+String name
+Integer count
}
class LicenseStatusOut {
+String skuid
+String skupartnumber
+Integer consumed_units
+Integer available_units
+Integer free_units
}
class LicenseStatusAllOut {
+String skuid
+String skupartnumber
+Integer consumed_units
+Integer available_units
+Integer free_units
+String tenant
}
%% Beziehungen
LicenseIn --|> LicenseModel
LicenseOut --|> LicenseModel
LicenseStatusAllOut --|> LicenseStatusOut
Was wäre wenn: Cloud-Implementierung (CI/CD mit GitLab & AWS)
Theoretisch sollte ein solcher Microservice in einer Cloud-Umgebung gehostet werden – beispielsweise für Hochverfügbarkeit, Skalierbarkeit und zentrale Zugriffe.
In diesem Projekt wurde jedoch bewusst auf eine lokale Lösung gesetzt, da Lizenzdaten sensible Informationen enthalten, deren Verarbeitung in externen Clouds nicht DSGVO-konform wäre.
(Detaillierte Infos: Datenschutz in diesem Microservice)
Ziel dieser Sektion
Trotz der lokalen Umsetzung soll hier aufgezeigt werden, wie ein Deployment in der Cloud aussehen würde – inklusive automatisiertem Build und Deployment mittels CI/CD-Pipeline (am Beispiel GitLab + AWS).
Voraussetzungen & Komponenten
Komponente | Zweck |
---|---|
AWS EC2 | Virtuelle Linux-Maschine als Cloud-Host |
Elastic IP | Statische IP für externen Zugriff auf den Microservice |
GitLab Repo | Source-Code-Management & CI/CD Pipeline |
Docker Desktop | Für lokale Tests vor Deployment (nicht produktiv verwendet) |
Infrastruktur & CI/CD-Ablauf
-
Code-Push auf GitLab
Triggert die CI/CD-Pipeline automatisch. -
GitLab CI/CD Pipeline
-
Führt Tests mit
pytest
durch -
Erstellt ein Docker-Image
-
Pusht das Image in die GitLab Container Registry
-
-
Deployment auf AWS EC2
Das Docker-Image wird auf einer EC2-Instanz (z. B. Ubuntu) gestartet – zusammen mit einer MySQL-Datenbank.
Gunicorn fungiert als produktionsfähiger WSGI-Server.
ℹ️ Hinweis:
Die nachfolgenden Konfigurationsdateien sind nicht Teil der aktuellen Projektstruktur, da der Microservice bislang nur lokal betrieben wird. Sie zeigen exemplarisch, wie eine Cloud-Integration mittels CI/CD technisch umgesetzt werden könnte.
CI/CD-Pipeline-Konfiguration (GitLab)
.gitlab-ci.yml
– Definiert das Build- und Testverhalten:
stages:
- test
test:
stage: test
image: python:3.10
before_script:
- pip install -r requirements.txt
- pip install pytest
script:
- pytest app/test
- pytest --cov=app app/test
Warum GitLab Container Registry?
Ein zentrales Element dieser CI/CD-Pipeline ist die Nutzung der GitLab Container Registry.
Sie ermöglicht es, Docker-Images direkt beim Commit automatisiert zu bauen, zu versionieren und zentral im GitLab-Projekt zu speichern.
Vorteile im Überblick:
Vorteil | Beschreibung |
---|---|
Nahtlose GitLab-Integration | Kein externer Registry-Anbieter notwendig – alles innerhalb von GitLab verwaltet |
Automatisierter Build & Tagging | Images werden bei jedem Commit mit :latest und :<commit> getaggt – ideal für Rollbacks |
Zentraler Zugriffspunkt | EC2-Instanzen, Staging-Server oder andere Microservices können direkt darauf zugreifen |
Sichere Authentifizierung | Kein manuelles Passworthandling – Zugriff über CI_JOB_TOKEN |
Skalierbar & übersichtlich | Jedes Projekt verwaltet seine Images isoliert und nachvollziehbar |
Produktionsumgebung (Docker Compose)
docker-compose.prod.yaml
– Setzt API & DB auf:
services:
prod-hf-itcne24-semarbeit3-msvc-lizenztool-api:
image: ${FLASK_BLUEPRINT_IMAGE}
build:
context: .
dockerfile: 'Dockerfile.prod'
container_name: prod-hf-itcne24-semarbeit3-msvc-lizenztool
environment:
- DATABASE_URI=mysql+mysqlconnector://root:root@prod-hf-itcne24-semarbeit3-msvc-lizenztool-db:3306/msvc-prod
ports:
- 5000:5000
depends_on:
prod-hf-itcne24-semarbeit3-msvc-lizenztool-db:
condition: service_healthy
msvc-bp-prod-db:
image: mysql:8.4.4
container_name: prod-hf-itcne24-semarbeit3-msvc-lizenztool-db
restart: always
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: msvc-prod
healthcheck:
test: mysqladmin ping -h localhost -uroot --password=$$MYSQL_ROOT_PASSWORD
start_period: 2s
interval: 5s
timeout: 5s
retries: 55
ports:
- 3306:3306
volumes:
- msvc-prod-db:/var/lib/mysql
volumes:
prod-hf-itcne24-semarbeit3-msvc-lizenztool-db:
Produktions-Image mit Gunicorn
Dockerfile.prod
– Für den produktiven Build:
FROM python:alpine3.21
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
RUN pip install gunicorn
EXPOSE 5000
ADD . /app
CMD gunicorn -b 0.0.0.0:5000 wsgi:app
Zusammenfassung: Vorteile der CI/CD-Cloud-Pipeline
Schritt | Beschreibung |
---|---|
Automatisierter Build | GitLab erzeugt Image bei jedem Commit |
Testdurchläufe | Pytest validiert Code vor Deployment |
Cloud-Deployment | EC2-Instanz erhält aktuelle Version automatisch |
Zugriff via IP | Externe Anfragen via http://<elastic-ip>: |
Datenschutz in diesem Microservice
Wie bereits in der Hinweisbox zu Beginn erwähnt, wird dieser Microservice lokal in einem Docker-Container auf Docker Desktop betrieben. Der Grund dafür ist der Schutz von Personendaten gemäss dem revidierten Datenschutzgesetz (revDSG, SR 235.1). Eine Cloud-Verarbeitung wird vermieden, da die bearbeiteten Daten potenziell besonders schützenswert sein können und Risiken durch externe Verarbeitung reduziert werden sollen.
Gemäss Artikel 7 revDSG (Datenschutz durch Technik und datenschutzfreundliche Voreinstellungen) gilt:
„Der Verantwortliche trifft bereits bei der Planung der Bearbeitung sowie bei der Bearbeitung selbst geeignete technische und organisatorische Massnahmen, um die Datenschutzvorschriften einzuhalten, insbesondere die Grundsätze nach Artikel 6.“
Obwohl der Zugriff über eine Microsoft-Authentifizierung abgesichert ist, besteht dennoch ein Restrisiko, dass Benutzerkonten kompromittiert werden könnten. Dies betrifft die Anforderungen zur Datensicherheit nach Artikel 8 revDSG (Datensicherheit), wo es heisst:
„Personendaten müssen durch geeignete technische und organisatorische Massnahmen gegen unbefugtes Bearbeiten geschützt werden.“
Sensitive Daten
Im Tool ist ersichtlich, welcher Tenant über welche und wie viele Microsoft-Lizenzen verfügt. Anhand dieser Lizenzinformationen – z. B. Lehrer- und Schülerlizenzen an einer Schule – lassen sich Rückschlüsse auf die Anzahl und Zusammensetzung der Benutzergruppen ziehen. Gemäss Artikel 5 lit. a revDSG sind Personendaten definiert als:
„alle Angaben, die sich auf eine bestimmte oder bestimmbare natürliche Person beziehen“.
Da bei Schul- oder KMU-Installationen oft klar ist, welche Gruppen (Lehrpersonen, Lernende, Mitarbeitende) mit welchen Lizenzen arbeiten, können diese Angaben als bestimmbare Personendaten gelten.
Zudem kann durch Premiumlizenzen indirekt erkannt werden, welche Tools oder Dienste verwendet werden. Diese Informationen erlauben möglicherweise Rückschlüsse auf interne Organisation oder Geschäftsstrategien. Je nach Kontext könnten solche Angaben unter die besonders schützenswerten Personendaten gemäss Artikel 5 lit. c revDSG fallen, insbesondere wenn sie Rückschlüsse auf berufliche Tätigkeiten, Gruppenzugehörigkeit oder Verhaltensmuster erlauben.
Kurzgesagt:
Aus Datenschutzgründen wird der Microservice lokal im Docker-Container betrieben und nicht in der Cloud gehostet. Obwohl Microsoft Authentication verwendet wird, besteht bei kompromittierten Konten ein Restrisiko. Das Tool zeigt sensible Informationen wie Tenant-Daten, Lizenztypen und -anzahl. Daraus lassen sich Rückschlüsse auf Nutzergruppen (z. B. Schüler, Lehrpersonen) und eingesetzte Dienste ziehen – was datenschutzrechtlich heikel sein kann.