Upload files to "/"

This commit is contained in:
Luigi_Tortora 2025-11-10 13:09:12 +00:00
commit ec9ba4ab9a
Signed by: luigi.git.onstackit.cloud
GPG key ID: BC891C192B4785B3
5 changed files with 406 additions and 0 deletions

View file

@ -0,0 +1,91 @@
{
"info": {
"name": "STACKIT Coriolis Worker Image",
"_postman_id": "b9c2a757-0a0d-4ab5-b2b4-0e5b71e71f41",
"description": "Ablauf: OAuth2 Token holen, Snapshot erstellen, Status prüfen, optional Image-Metadaten anpassen.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "01 - OAuth2 Token",
"request": {
"method": "POST",
"header": [
{ "key": "Content-Type", "value": "application/x-www-form-urlencoded" }
],
"body": {
"mode": "urlencoded",
"urlencoded": [
{ "key": "grant_type", "value": "client_credentials", "type": "text" },
{ "key": "client_id", "value": "{{client_id}}", "type": "text" },
{ "key": "client_secret", "value": "{{client_secret}}", "type": "text" }
]
},
"url": {
"raw": "{{iam_base_url}}/oauth2/token"
}
},
"response": []
},
{
"name": "02 - Server Snapshot erstellen",
"request": {
"method": "POST",
"header": [
{ "key": "Content-Type", "value": "application/json" },
{ "key": "X-Auth-Token", "value": "{{access_token}}" }
],
"body": {
"mode": "raw",
"raw": "{\n \"createImage\": {\n \"name\": \"coriolis-worker-v1\",\n \"metadata\": {\n \"coriolis-worker\": \"true\",\n \"os_version\": \"ubuntu-22.04\"\n }\n }\n}"
},
"url": {
"raw": "{{iaas_base_url}}/v2.1/{{project_id}}/servers/{{server_id}}/action"
}
},
"response": []
},
{
"name": "03 - Image-Status prüfen",
"request": {
"method": "GET",
"header": [
{ "key": "X-Auth-Token", "value": "{{access_token}}" }
],
"url": {
"raw": "{{iaas_base_url}}/v2.1/{{project_id}}/images/{{image_id}}"
}
},
"response": []
},
{
"name": "04 - Image-Metadaten aktualisieren (optional)",
"request": {
"method": "PATCH",
"header": [
{ "key": "Content-Type", "value": "application/openstack-images-v2.1-json-patch" },
{ "key": "X-Auth-Token", "value": "{{access_token}}" }
],
"body": {
"mode": "raw",
"raw": "[\n { \"op\": \"replace\", \"path\": \"/name\", \"value\": \"coriolis-worker-v1\" },\n { \"op\": \"add\", \"path\": \"/protected\", \"value\": true }\n]"
},
"url": {
"raw": "{{image_base_url}}/v2/images/{{image_id}}"
}
},
"response": []
}
],
"variable": [
{ "key": "iam_base_url", "value": "https://iam.eu01.stackit.cloud" },
{ "key": "iaas_base_url", "value": "https://iaas.eu01.stackit.cloud" },
{ "key": "image_base_url", "value": "https://image.eu01.stackit.cloud" },
{ "key": "client_id", "value": "STACKIT_CLIENT_ID" },
{ "key": "client_secret", "value": "STACKIT_CLIENT_SECRET" },
{ "key": "project_id", "value": "PROJECT_ID" },
{ "key": "server_id", "value": "SERVER_ID" },
{ "key": "image_id", "value": "IMAGE_ID" },
{ "key": "access_token", "value": "" }
]
}

116
README.MD Normal file
View file

@ -0,0 +1,116 @@
# ANLEITUNG: Installation des STACKIT Provider Plugins
Dies ist eine Schritt-für-Schritt-Anleitung, um Ihr benutzerdefiniertes STACKIT v2 Provider-Plugin auf einer laufenden Coriolis Appliance zu installieren.
## Schritt 1: Code-Struktur vorbereiten
Stellen Sie sicher, dass Ihre Dateien exakt diese Struktur aufweisen. Sie benötigen einen Projekt-Ordner (z.B. `coriolis_stackit_plugin`) und darin das eigentliche Python-Paket.
```
coriolis_stackit_plugin/
├── setup.py
└── coriolis_stackit/
├── __init__.py
└── provider.py
```
* `setup.py`: Registriert den Provider bei Coriolis.
* `coriolis_stackit/provider.py`: Enthält die `StackitProvider`-Klasse inkl. aller Worker- und Datentransfer-Funktionen.
> **Abhängigkeiten:**
> Das Plugin benötigt mindestens `requests>=2.20.0` und `boto3>=1.24.0` (siehe `setup.py`), damit sowohl die STACKIT REST-APIs als auch optionale S3-Validierungen genutzt werden können.
## Schritt 2: Plugin-Code auf die Appliance kopieren
1. **Verbinden:** Stellen Sie per SSH eine Verbindung zu Ihrer Coriolis Appliance her.
```bash
ssh [ihr_benutzer]@[ip_ihrer_coriolis_appliance]
```
2. **Code kopieren:** Kopieren Sie den gesamten Ordner `coriolis_stackit_plugin` (z.B. mit `scp` oder `rsync`) in ein temporäres Verzeichnis auf der Appliance, z.B. `/tmp/`.
*Beispiel mit `scp` (von Ihrem lokalen Rechner aus):*
```bash
scp -r /pfad/zu/ihrem/coriolis_stackit_plugin [ihr_benutzer]@[ip_ihrer_coriolis_appliance]:/tmp/
```
## Schritt 3: Plugin in Coriolis installieren
1. **In das Verzeichnis wechseln:**
Gehen Sie auf der Appliance in das Verzeichnis, das Sie gerade kopiert haben.
```bash
cd /tmp/coriolis_stackit_plugin
```
2. **Python-Umgebung aktivieren:**
Coriolis verwendet eine eigene virtuelle Python-Umgebung (virtualenv). Der Pfad kann variieren, aber er ist oft unter `/opt/coriolis/` oder `/usr/share/coriolis/`. Sie müssen diese Umgebung aktivieren.
*(Suchen Sie nach `activate` in `/opt/coriolis/` oder `/usr/share/coriolis/`)*
*Beispiel:*
```bash
source /opt/coriolis/coriolis-env/bin/activate
```
*(Ihr Konsolen-Prompt sollte sich jetzt ändern und `(coriolis-env)` anzeigen)*
3. **Plugin installieren:**
Installieren Sie Ihr Plugin mit `pip` im "editable" Modus. Dies ist der wichtigste Befehl.
```bash
pip install -e .
```
*(Das `-e .` bedeutet, dass das Plugin von diesem Verzeichnis aus installiert wird. Pip liest die `setup.py` und führt die Installation durch.)*
4. **Dienste neu starten:**
Damit Coriolis das neu installierte Plugin erkennt, müssen Sie die Coriolis-Dienste neu starten.
```bash
sudo systemctl restart coriolis-api
sudo systemctl restart coriolis-worker
```
*(Die Dienstnamen können leicht variieren, z.B. `coriolis-conductor`)*
## Schritt 4: Konfiguration des Endpoints (in der Coriolis UI)
Nachdem das Plugin installiert und der Dienst neu gestartet wurde, ist Ihr Provider `"stackit_v2"` für Coriolis bekannt.
1. **`.endpoint`-Datei erstellen:**
Erstellen Sie auf Ihrem *lokalen* Rechner eine Datei, z.B. `stackit_v2.endpoint`.
2. **Inhalt einfügen:**
Fügen Sie den folgenden JSON-Inhalt ein und ersetzen Sie die Platzhalter.
Sie finden eine ausführliche Vorlage in `stackit_v2.endpoint`. Die wichtigsten Felder sind:
* `project_id`, `region`, `api_url`, `object_storage_api_url`, `auth_url`
* Object-Storage-Einstellungen (`object_storage_s3_endpoint_url`, `migration_bucket_name`)
* Worker-VM-Parameter (`worker_image_id`, `worker_flavor_id`, `worker_network_id`, `worker_availability_zone`, `worker_security_group_ids`, `worker_keypair_name`)
* Run-Command Zugang (`run_command_api_url`, optional eigenes `run_command_template`, Timeout)
* Optional: `os_morphing_assets` Verweise auf S3-Objekte, die Skripte bzw. Treiber-Pakete für Linux- und Windows-OS-Morphing enthalten (siehe unten).
* Optionale Worker-Metadaten und Steuerflags (`worker_user`, `worker_auto_cleanup`, `worker_boot_volume_size`)
Diese Angaben werden von den Methoden `export_snapshot_to_url()` und `create_volume_from_url()` genutzt, um automatisch Buckets anzulegen, temporäre Access Keys zu erzeugen, Worker-VMs zu starten und anschließend über die STACKIT **Run-Command** API die eigentlichen Datenbefehle (Upload/Download) auf der Worker-VM auszuführen. Stellen Sie sicher, dass Ihr Worker-Image den STACKIT Agent aktiviert hat, damit Run-Command die Shell-Skripte ausführen darf.
> **OS-Morphing:**
> Zusätzlich stehen `prepare_instance_for_os_morphing()` und `inject_drivers()` zur Verfügung. Beide Funktionen erstellen ebenfalls eine Worker-VM, hängen das Ziel-Volume an und führen die Morphing-Schritte direkt auf dem Block-Device aus.
> * Für **Linux** existiert eine eingebaute Standard-Logik (DHCP, Cloud-Init, VirtIO-Module). Optional kann über `os_morphing_assets.linux.script` ein eigenes Skript bereitgestellt werden; dieses wird mit den Umgebungsvariablen `CORIOLIS_DEVICE` und `CORIOLIS_JOB_JSON` auf dem Worker ausgeführt.
> * Für **Windows** wird zwingend ein Skript (z.B. Shell/Python) über `os_morphing_assets.windows.script` erwartet, das die notwendigen Registry-/Treiber-Anpassungen vornimmt (z.B. durch Nutzung von `ntfs-3g`, `chntpw`, `virtio`-Treiberpaketen). Das Skript wird analog mit `CORIOLIS_DEVICE` und `CORIOLIS_JOB_JSON` aufgerufen und kann benötigte Pakete ebenfalls aus S3 laden.
* **WICHTIG:**
Der `"provider"`-Name (`"stackit_v2"`) muss exakt mit dem Namen übereinstimmen, den Sie in der `setup.py` unter `entry_points` definiert haben.
3. **In Coriolis hochladen:**
* Gehen Sie in Ihrer Coriolis Weboberfläche auf "Cloud Endpoints".
* Klicken Sie auf "New Cloud Endpoint".
* Klicken Sie unten auf den "upload"-Link (wie in Ihrem ersten Screenshot).
* Laden Sie Ihre `stackit_v2.endpoint`-Datei hoch.
4. **Validieren:**
Coriolis wird die Datei lesen, Ihren `stackit_v2`-Provider laden und die `authenticate`-Methode aufrufen. Wenn die Anmeldeinformationen korrekt sind, wird der Endpoint als "Valid" gespeichert.
Sie können nun mit dem Testen der `list_...`-Funktionen beginnen.

106
provider_funktionsstatus.md Normal file
View file

@ -0,0 +1,106 @@
Hier ist eine detaillierte Aufschlüsselung, welche Funktionen unser STACKIT Provider-Plugin (theoretisch) abdeckt, nachdem es erfolgreich installiert und die `.endpoint`-Datei hochgeladen wurde.
**Voraussetzung:** Alle `Stackit...`-Wrapper-Klassen (z.B. `StackitInstance`, `StackitVolume`) in der `provider.py` wurden korrekt an die JSON-Antworten der STACKIT API angepasst.
---
## ✅ Teil 1: Basis-Funktionen (implementiert)
Diese Funktionen bilden das Fundament. Coriolis kann Ihre STACKIT-Umgebung jetzt "sehen" und als Quelle (Source) oder Ziel (Destination) für Migrations-Jobs auswählen.
### 1. Authentifizierung & Konnektivität
* **Status:** **Implementiert**
* **Funktion:** Coriolis kann die `.endpoint`-Datei lesen, sich über OAuth 2.0 bei der STACKIT IAM API authentifizieren und Bearer Tokens abrufen.
* **Test:** Die `authenticate()`-Methode testet erfolgreich die Verbindung zur **IaaS API** (`/flavors`) UND zur **Object Storage API** (`/buckets`). Der Endpoint wird in der UI als "Valid" angezeigt.
### 2. Inventarisierung (Ressourcen-Erkennung)
* **Status:** **Implementiert**
* **Funktion:** Coriolis kann die Listen-Funktionen aufrufen, um alle relevanten Ressourcen in Ihrem STACKIT-Projekt zu sehen.
* **Abgedeckte Methoden:**
* `list_instances()`: Sie sehen alle Ihre VMs inklusive zugehöriger Labels (STACKIT "labels").
* `list_volumes()`: Sie sehen alle Ihre Volumes inklusive Labels.
* `list_snapshots()`: Sie sehen alle Volume-Snapshots; vererbte Labels werden ausgewiesen.
* `list_networks()`: Sie sehen Ihre Netzwerke inklusive Labels.
* `list_subnets()`: Zeigt "Pseudo-Subnetze", die aus den Netzwerk-CIDRs abgeleitet werden.
* `list_images()`: Sie sehen alle verfügbaren OS-Images.
* `list_flavors()`: Sie sehen alle verfügbaren VM-Größen.
* **Nutzen:** Unverzichtbar, um im Migrations-Assistenten Quell-VMs auszuwählen oder Ziel-Netzwerke/Flavors festzulegen. Die Labels erlauben jetzt detailliertes Tagging für UI-Filtern und Projekt-Metadaten.
### 3. Basis-Lebenszyklus-Management (VMs)
* **Status:** **Implementiert**
* **Funktion:** Grundlegende Steuerung von VMs.
* **Abgedeckte Methoden:**
* `power_on_instance()`
* `power_off_instance()`
* `get_instance_status()`
* **Nutzen:** Coriolis kann VMs vor einer Snapshot-Erstellung sicher herunterfahren.
### 4. Basis-Storage-Management (Volumes & Snapshots)
* **Status:** **Implementiert**
* **Funktion:** Erstellen und Löschen von Storage-Ressourcen.
* **Abgedeckte Methoden:**
* `create_snapshot()`: **Sehr wichtig.** Dies ist der erste Schritt einer jeden Migration von STACKIT weg.
* `delete_snapshot()`: Wichtig für das Aufräumen.
* `create_volume()`: Erstellt ein neues, leeres Volume. Wird als Basis für den Daten-Import benötigt.
* `delete_volume()`: Wichtig für das Aufräumen.
* `attach_volume()` / `detach_volume()`: Wichtig, um Volumes an temporäre Worker-VMs anzuhängen.
### 5. Basis-Object-Storage-Management (Control Plane)
* **Status:** **Implementiert** (als interne Helfer-Methoden)
* **Funktion:** Vorbereitung des Datentransfers.
* **Abgedeckte Methoden:**
* `_get_or_create_migration_bucket()`: Stellt sicher, dass ein S3-Bucket für die Migration existiert.
* `_create_temp_s3_credentials()`: Erstellt S3 Access/Secret Keys für die Worker-VMs.
* `_delete_temp_s3_credentials()`: Räumt die S3-Schlüssel nach dem Transfer wieder auf.
* **Nutzen:** Essenzielle Vorbereitung für die noch fehlenden Datentransfer-Funktionen.
## ✅ Teil 2: Erweiterte Migration-Funktionen (implementiert)
Diese Bausteine bilden den Kern des Worker-gestützten Migrations-Workflows, bei dem Coriolis temporäre VMs startet, um Daten physisch zu übertragen.
### 1. Datentransfer
* **Status:** **Implementiert (Worker-gestützt)**
`export_snapshot_to_url()` und `create_volume_from_url()` orchestrieren:
* Region-aware IaaS-Aufrufe über den aktualisierten `StackitAPIClient`.
* Bucket- und Access-Key-Lifecycle der Object-Storage-API.
* Worker-VMs (Image/Flavor/Netzwerk frei konfigurierbar), die via cloud-init ein JSON-Job-Manifest erhalten **und** anschließend über die STACKIT **Run-Command v2** API Skripte zur eigentlichen Datenübertragung ausführen (Python/Boto3 + `lsblk`-basierte Device-Erkennung).
* Polling für Volume- und Instanz-Status sowie optionales Verifizieren des hochgeladenen Objekts via S3 (`boto3`).
* **Voraussetzungen:** Neue `.endpoint`-Felder (Region, Worker-Image/-Flavor/-Netzwerk, Bucket-Name, optionaler S3-Endpoint, Run-Command-API-URL). Ohne korrektes Worker-Image oder aktivierten STACKIT-Agent lassen sich die Run-Command-Aufrufe nicht ausführen.
* **Cleanup/Output:** Worker-Instanzen und temporäre Clone-Volumes werden nach Abschluss aufgeräumt (`worker_auto_cleanup`). Die erzeugten S3-Zugangsdaten werden im Resultat zurückgegeben, damit nachfolgende Schritte (z.B. Ziel-Import) zugreifen können; die Löschung erfolgt nach dem eigentlichen Migrations-Workflow.
### 2. OS-Morphing
* **Status:** **Implementiert (Linux, Script-basiert für Windows)**
Die neuen Provider-Methoden `prepare_instance_for_os_morphing()` und `inject_drivers()` orchestrieren eine Worker-VM, hängen das Ziel-Volume an und führen über die STACKIT Run-Command API ein Python-Skript aus, das:
* (Linux) Persistent-NIC-Regeln entfernt, `cloud-init`/Netplan/ifcfg-Dateien auf DHCP setzt, GRUB-Parameter (`net.ifnames=0 biosdevname=0`) injiziert und VirtIO-Module + `initramfs` neu baut. Optional kann ein eigenes Skript aus `os_morphing_assets.linux.script` eingebunden werden, welches die Standardlogik ersetzt.
* (Windows) Ein benutzerdefiniertes Skript aus `os_morphing_assets.windows.script` ausführt. Dieses Script erhält Zugriff auf das Block-Device (`$CORIOLIS_DEVICE`) und die Job-Beschreibung und kann Treiberpakete/Cloudbase-init aus dem gleichen S3-Bucket laden. Damit lässt sich der bestehende Coriolis-Workflow (VirtIO-Treiber aktivieren, Registry anpassen, DHCP setzen) 1:1 portieren.
* **Voraussetzungen:**
* Linux-Gäste: unterstützte Dateisysteme (ext4/ext3/xfs/btrfs), `update-initramfs` oder `dracut` im Image.
* Windows-Gäste: ein eigenes Morphing-Skript und ggf. Treiberpakete im S3-Bucket; der Worker muss über `ntfs-3g`, `chntpw`, `zip/unzip` verfügen (siehe Worker-VM-Spezifikation).
* **Hinweis:** Ohne Windows-Skript bricht der Provider den Morphing-Schritt ab. Damit ist klar ersichtlich, welche Assets noch gepflegt werden müssen.
### 3. Instanz-Erstellung
* **Status:** **Implementiert**
* **Funktion:** `create_instance()` nutzt jetzt den STACKIT Server-Endpoint (`bootVolume` Payload). Wenn ein bereits importiertes Volume übergeben wird, setzt der Provider automatisch `bootVolume.source = {"type": "volume", "id": ...}` und verzichtet auf `imageId`. Damit lassen sich Ziel-VMs direkt von einem zuvor transferierten Boot-Volume starten.
* **Hinweis:** Optional können weiterhin zusätzliche Volumes via `volumeIds` angehängt werden; `root_volume_size` greift nur, wenn kein eigenes Boot-Volume angegeben ist.
---
## 🟡 Teil 3: Nice-to-Have & Ausstehende Verbesserungen
Diese Funktionen sind nicht kritisch für eine Migration, würden den Provider aber robuster und benutzerfreundlicher machen.
### 1. Asynchrones Status-Polling
* **Status:** **Weitgehend Implementiert**
* **Funktion:** Power- (`start/stop`), Volume- (`create`) **und Snapshot-Operationen** (`create/delete`) blocken nun, bis der jeweilige Ressourcenstatus über die regulären GET-/LIST-Endpunkte den Zielzustand meldet. `_wait_for_snapshot_status()` nutzt `GET /snapshots/{id}` als Workaround, sodass kein separater `/tasks/{id}`-Workflow nötig ist.
* **Noch offen:** Für seltene Aktionen mit separaten Task-IDs (z.B. Image-Importe) könnten wir optional noch dediziertes Task-Polling ergänzen, ist aber aktuell nicht blocker.
### 2. Detailliertes Ressourcen-Mapping
* **Status:** **Implementiert (Labels)**
* **Funktion:** Die Wrapper-Klassen (`StackitInstance`, `StackitVolume`, `StackitNetwork`) mappen jetzt die STACKIT-Labels (`labels`), sodass Tagging-Informationen in Coriolis sichtbar und filterbar sind.
* **Grund (Nice-to-have):** Optional könnten künftig weitere Metadaten (Erstellungsdatum, detaillierte Netzwerk-IPs, Sicherheitsgruppen) ergänzt werden, die über die Labels hinausgehen.
### 3. UI-Kosmetik (Logo)
* **Status:** **NICHT Implementiert**
* **Funktion:** Ein STACKIT-Logo in der "New Cloud Endpoint"-Auswahl anzeigen.
* **Grund (Nice-to-have):** Rein kosmetisch. Wie besprochen, erfordert dies eine manuelle und riskante Bearbeitung der Coriolis-Frontend-Dateien auf dem Server.

38
setup.py Normal file
View file

@ -0,0 +1,38 @@
from setuptools import setup, find_packages
setup(
name="coriolis-stackit-provider",
version="0.1.0",
# Findet automatisch das 'coriolis_stackit'-Paket
packages=find_packages(),
# Abhängigkeiten:
# Coriolis selbst wird bereits in der Umgebung vorhanden sein.
# Wir müssen nur 'requests' hinzufügen, das unser Client verwendet.
install_requires=[
"requests>=2.20.0",
"boto3>=1.24.0",
# Wenn Sie die echten Coriolis-Basisklassen importieren,
# müssen Sie hier eventuell 'coriolis-core' oder
# ähnliches als Abhängigkeit deklarieren.
],
# === DER WICHTIGSTE TEIL ===
# Hier registrieren wir unser Plugin bei Coriolis.
entry_points={
'coriolis.providers': [
# 'name_fuer_endpoint_file = paket_ordner.datei_name:Klassenname'
# Dieser Name ('stackit_v2') ist der "provider"-String,
# den Sie in Ihrer .endpoint-Datei verwenden werden.
'stackit_v2 = coriolis_stackit.provider:StackitProvider'
]
},
# Metadaten
author="Luigi Tortora",
author_email="luigi.tortora@stackit.cloud",
description="Coriolis Provider für die STACKIT CLoud.",
url="https://luigi.git.onstackit.cloud/luigi.tortora/stackit_coriolis_plugin"
)

55
stackit_v2.endpoint Normal file
View file

@ -0,0 +1,55 @@
{
"name": "Mein STACKIT v2 Endpoint",
"description": "STACKIT Provider (IaaS v2 + Object Storage)",
"provider": "stackit_v2",
"connection": {
"project_id": "IHRE_STACKIT_PROJEKT_ID_HIER",
"region": "eu01",
"client_id": "IHR_SERVICE_ACCOUNT_CLIENT_ID_HIER",
"client_secret": "IHR_SERVICE_ACCOUNT_CLIENT_SECRET_HIER",
"auth_url": "https://identity.api.stackit.cloud/v2.0/oauth/token",
"api_url": "https://iaas.api.stackit.cloud/v2",
"object_storage_api_url": "https://object-storage.api.stackit.cloud/v2",
"object_storage_s3_endpoint_url": "https://object.storage.eu01.onstackit.cloud",
"migration_bucket_name": "coriolis-migration-data",
"worker_image_id": "STACKIT_IMAGE_ID_DER_WORKER_VM",
"worker_flavor_id": "STACKIT_FLAVOR_FUER_WORKER",
"worker_network_id": "STACKIT_NETWORK_ID_FUER_WORKER",
"worker_availability_zone": "eu01-1",
"worker_security_group_ids": [
"STACKIT_SECURITY_GROUP_ID"
],
"worker_keypair_name": "coriolis-worker-key",
"worker_metadata": {
"owner": "coriolis"
},
"worker_user": "ubuntu",
"worker_boot_volume_size": 20,
"worker_auto_cleanup": true,
"run_command_api_url": "https://run-command.api.stackit.cloud/v2",
"run_command_template": "RunShellScript",
"run_command_timeout": 7200,
"os_morphing_assets": {
"linux": {
"script": {
"type": "s3",
"bucket": "my-morph-assets",
"object": "linux-morph.sh",
"endpoint_url": "https://object.storage.eu01.onstackit.cloud",
"access_key": "YOUR_ACCESS_KEY",
"secret_key": "YOUR_SECRET_KEY"
}
},
"windows": {
"script": {
"type": "s3",
"bucket": "my-morph-assets",
"object": "windows-morph.sh",
"endpoint_url": "https://object.storage.eu01.onstackit.cloud",
"access_key": "YOUR_ACCESS_KEY",
"secret_key": "YOUR_SECRET_KEY"
}
}
}
}
}