Home

02 Code-Qualität in der Praxis: Automatisierung und CI/CD - Qualität in jeden Commit einbauen

lecture ci-cd github-actions automation workflow devops quality testing

1. Einführung: Von manuellen Prüfungen zu automatisierten Workflows

In Kapitel 02 (Code-Qualität in der Praxis) haben Sie Code-Qualitätswerkzeuge wie Ruff und Pyright kennengelernt. In Kapitel 02 (Feature-Branch-Entwicklung) haben Sie Feature Branch Workflows und Pull Requests gelernt. Jetzt können Sie:

# Feature Branch erstellen
uv run ruff check .
uv run ruff format .
uv run pyright

# Pull Request erstellen

Aber hier ist das Problem: Was passiert, wenn:

Die Antwort: Automatisierung. Genauer gesagt: Continuous Integration und Continuous Deployment (CI/CD).

Diese Vorlesung handelt davon, von “bitte denken Sie daran, die Prüfungen vor dem Erstellen eines PR auszuführen” zu “Prüfungen laufen automatisch bei jedem PR, und Code, der die Qualitätsprüfungen nicht besteht, kann nicht gemergt werden” zu kommen.

Wir bauen auf dem Feature Branch Workflow auf, den Sie in Teil 1 gelernt haben, und fügen automatisierte Qualitätsgates hinzu, die sicherstellen, dass jede Änderung Ihre Standards erfüllt, bevor sie den main Branch erreicht.


2. Lernziele

Am Ende dieser Vorlesung werden Sie:

  1. CI/CD-Konzepte verstehen und wissen, warum sie grundlegend für moderne Softwareentwicklung sind
  2. GitHub Actions lernen - GitHubs integrierte Automatisierungsplattform
  3. Automatisierte Qualitätsprüfungen erstellen, die bei jedem Push und Pull Request laufen
  4. Entwicklungsworkflows erstellen, die Qualitätsstandards automatisch durchsetzen
  5. Pre-commit-Hooks implementieren für lokale Validierung, bevor Code GitHub erreicht
  6. Eine vollständige Qualitätspipeline entwerfen von der lokalen Entwicklung bis zur Produktion

3. Teil 1: Was ist CI/CD?

3.1 Das Problem, das CI/CD löst

Traditionelle Softwareentwicklung (Vor-CI/CD):

Entwickler A schreibt Code → Funktioniert auf seinem Rechner ✅
Entwickler B schreibt Code → Funktioniert auf seinem Rechner ✅
Beide mergen → Integrationshölle 🔥
                ↓
- Konfligierende Abhängigkeiten
- Unterschiedliche Python-Versionen
- Ungetestete Integrationspunkte
- Manuelles Testen dauert Tage
- Deployment ist ein Wochenendereignis

Reales Beispiel:

# Rechner von Entwickler A (Python 3.12)
def process_data(items: list[str]) -> list[str]:
    return [item.removeprefix("data_") for item in items]  # Python 3.9+ Feature

# Rechner von Entwickler B (Python 3.8)
# Dieser Code stürzt mit AttributeError ab: 'str' object has no attribute 'removeprefix'

Beide Entwickler denken, ihr Code funktioniert. Erst bei der Integration tritt das Problem auf.

3.2 Was ist Continuous Integration (CI)?

Continuous Integration (CI) ist die Praxis, Code-Änderungen von mehreren Mitwirkenden mehrmals täglich automatisch in ein gemeinsames Repository zu integrieren.

Kernprinzipien:

  1. Häufige Integration - Code-Änderungen täglich (oder öfter) mergen
  2. Automatisierter Build - Jede Integration löst einen automatisierten Build aus
  3. Automatisiertes Testen - Build beinhaltet das Ausführen von Tests
  4. Schnelles Feedback - Entwickler wissen innerhalb von Minuten, ob die Integration etwas kaputt macht
  5. Gemeinsame Verantwortung - Jeder verpflichtet sich, kaputte Builds sofort zu reparieren

Der CI-Workflow:

Entwickler schreibt Code
        ↓
Push zu GitHub
        ↓
Automatisierte Prüfungen laufen (CI)
    - Abhängigkeiten installieren
    - Linter ausführen
    - Tests ausführen
    - Formatierung prüfen
    - Typ-Prüfung
        ↓
    Pass ✅ → Merge erlauben
    Fail ❌ → Merge blockieren, Entwickler benachrichtigen

3.3 Was ist Continuous Deployment (CD)?

Continuous Deployment (CD) erweitert CI, indem jede Änderung, die alle Tests besteht, automatisch in die Produktion deployed wird.

Zwei Varianten:

Continuous Delivery:

Continuous Deployment:

Der CD-Workflow:

CI-Pipeline besteht ✅
        ↓
Automatisiertes Deployment
    - Produktionsartefakte erstellen
    - Integrationstests ausführen
    - Auf Staging deployen
    - Smoke-Tests ausführen
    - In Produktion deployen
        ↓
    Erfolg ✅ → Code ist live
    Fehler ❌ → Automatisches Rollback

3.4 Vorteile von CI/CD

1. Probleme früh erkennen

2. Schnellere Entwicklung

3. Höhere Qualität

4. Reduziertes Risiko

5. Bessere Zusammenarbeit

Reale Auswirkungen:

Metrik Ohne CI/CD Mit CI/CD
Zeit bis Bugs erkannt werden Tage bis Wochen Minuten
Deployment-Häufigkeit Wöchentlich/monatlich Mehrmals täglich
Fehlgeschlagene Deployments 20-30% <5%
Zeit zur Behebung von Produktionsproblemen Stunden bis Tage Minuten
Entwicklerzufriedenheit Niedriger (manuelle Arbeit) Höher (Automatisierung)

4. Teil 2: Einführung in GitHub Actions

4.1 Was ist GitHub Actions?

GitHub Actions ist GitHubs integrierte Automatisierungsplattform, mit der Sie Workflows definieren können, die durch Ereignisse in Ihrem Repository ausgelöst werden.

Kernkonzepte:

Workflow:

Event:

Job:

Step:

Action:

Runner:

4.2 Workflow-Syntax

Hier ist ein minimaler GitHub Actions Workflow:

name: My First Workflow                    # Workflow-Name (erscheint in GitHub UI)

on: [push, pull_request]                   # Events, die diesen Workflow auslösen

jobs:                                       # Jobs definieren
  my-job:                                   # Job-ID
    runs-on: ubuntu-latest                  # Zu verwendender Runner
    
    steps:                                  # Schritte in diesem Job
      - name: Checkout code                 # Schritt 1: Code holen
        uses: actions/checkout@v4           # Eine vordefinierte Action verwenden
      
      - name: Run a command                 # Schritt 2: Shell-Befehl ausführen
        run: echo "Hello, World!"           # Der auszuführende Befehl

Aufschlüsselung:

name: My First Workflow
on: [push, pull_request]
jobs:
  my-job:
    runs-on: ubuntu-latest
steps:
  - name: Checkout code
    uses: actions/checkout@v4
  - name: Run a command
    run: echo "Hello, World!"

4.3 Die richtigen Actions finden und auswählen

Eine der mächtigsten Funktionen von GitHub Actions ist der GitHub Actions Marketplace - ein umfangreiches Ökosystem wiederverwendbarer Actions, die von GitHub und der Community erstellt wurden. Aber wie finden Sie die richtige Action und wissen, welche Version Sie verwenden sollten?

4.3.1 Der GitHub Actions Marketplace

Wo man Actions findet:

🔗 GitHub Marketplace - Der offizielle Marketplace für GitHub Actions

Wie man sucht:

  1. Zum Marketplace gehen: Besuchen Sie github.com/marketplace
  2. Nach “Actions” filtern: Verwenden Sie die linke Sidebar, um nur Actions anzuzeigen
  3. Nach Zweck suchen: Schlüsselwörter wie “Python”, “deploy”, “test”, “Docker”, etc.
  4. Nach Beliebtheit sortieren: Wählen Sie “Most installed”, um erprobte Actions zu sehen

Beispiel-Suchablauf:

Suche: "Python setup" → Finden: actions/setup-python
Suche: "Docker build" → Finden: docker/build-push-action
Suche: "AWS deploy" → Finden: aws-actions/configure-aws-credentials

4.3.2 Action-Qualität bewerten

Nicht alle Actions sind gleich. So bewerten Sie, ob eine Action vertrauenswürdig ist:

✅ Qualitätsindikatoren:

1. Offizielle Actions (Höchstes Vertrauen)

2. Verifizierte Ersteller

3. Community-Metriken

4. Dokumentationsqualität

❌ Warnsignale:

4.3.3 Action-Versionen verstehen

Actions verwenden Git-Tags für die Versionierung. Sie haben mehrere Optionen:

Versionsformate:

# ✅ EMPFOHLEN: Hauptversion (verfolgt neueste kompatible)
uses: actions/checkout@v4

# ⚠️ SPEZIFISCH: Exakte Version (am sichersten, verpasst aber Updates)
uses: actions/checkout@v4.1.1

# ⚠️ BRANCH: Bleeding Edge (kann ohne Warnung brechen)
uses: actions/checkout@main

# ❌ NIE: Nur SHA (schwer zu warten)
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608

Best-Practice-Empfehlungen:

Versionstyp Syntax Updates Stabilität Wann verwenden
Hauptversion @v4 Auto-Updates auf v4.x.x ✅ Hoch Standardwahl - Erhält Patches & Features, vermeidet Breaking Changes
Exakte Version @v4.1.1 Ändert sich nie ✅ Höchste Kritische Workflows, Compliance-Anforderungen, Reproduzierbarkeit
Branch-Name @main Jeder Commit ❌ Niedrig Testen unveröffentlichter Features (nicht in Produktion!)
Commit-SHA @8ade135 Ändert sich nie ✅ Höchste Sicherheitskritische Workflows (aber schwer zu warten)

Warum Hauptversionen (@v4) verwenden?

Beispiel einer Versionsentwicklung:

# Sie schreiben dies 2025:
uses: actions/checkout@v4

# GitHub verwendet automatisch neueste v4.x.x:
# Jan 2025: v4.1.0 ✓
# Mär 2025: v4.1.5 ✓ (Sicherheitspatch - automatisch angewendet!)
# Jun 2025: v4.2.0 ✓ (neues Feature - automatisch verfügbar!)
# Jan 2026: v5.0.0 ✗ (Breaking Change - Sie müssen manuell aktualisieren)

4.3.4 Wie man die neueste Version findet

Methode 1: Marketplace-Seite

  1. Gehen Sie zur Marketplace-Seite der Action
  2. Suchen Sie nach dem “Latest version” Badge in der Sidebar
  3. Beispiel: actions/checkout

Methode 2: GitHub Releases

  1. Besuchen Sie das GitHub-Repository der Action
  2. Klicken Sie auf “Releases” in der rechten Sidebar
  3. Das oberste Release ist die neueste Version
  4. Beispiel: github.com/actions/checkout/releases

Methode 3: Repository-Tags

  1. Gehen Sie zum Repository der Action
  2. Klicken Sie auf das Branch/Tag-Dropdown
  3. Wählen Sie “Tags”-Tab
  4. Sehen Sie alle verfügbaren Versionen sortiert nach Datum

Pro-Tipp: README prüfen Die meisten gut gewarteten Actions zeigen Verwendungsbeispiele mit der neuesten Version in ihrer README!

4.3.5 Häufig verwendete Actions

Hier sind die beliebtesten Actions, die Sie in diesem Kurs und darüber hinaus antreffen werden:

Essenzielle Actions für Python-Projekte:

# Ihr Repository klonen
- uses: actions/checkout@v4

# uv einrichten (Python Package Manager)
- uses: astral-sh/setup-uv@v4

# Abhängigkeiten für Geschwindigkeit cachen
- uses: actions/cache@v4

# Artefakte hochladen (Build-Outputs, Logs, etc.)
- uses: actions/upload-artifact@v4

# Artefakte von vorherigen Jobs herunterladen
- uses: actions/download-artifact@v4

Warum astral-sh/setup-uv statt actions/setup-python?

In diesem Kurs verwenden wir uv als unseren Python Package Manager. Die astral-sh/setup-uv@v4 Action:

Für andere Sprachen:

# Node.js einrichten
- uses: actions/setup-node@v4

# Go einrichten
- uses: actions/setup-go@v5

# Java einrichten
- uses: actions/setup-java@v4

Wo man diese findet:

4.3.6 Sicherheits-Best-Practices

Überprüfen Sie Actions immer vor der Verwendung!

# ❌ NICHT: Verwenden Sie keine zufälligen Actions ohne zu prüfen
- uses: unknown-user/sketchy-action@v1

# ✅ TUN: Verwenden Sie offizielle oder verifizierte Actions
- uses: actions/checkout@v4  # GitHub offiziell
- uses: docker/build-push-action@v5  # Docker offiziell (verifiziert)

Sicherheits-Checkliste:

Dependabot für GitHub Actions aktivieren:

Erstellen Sie .github/dependabot.yml:

version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

Dies erstellt automatisch PRs, wenn Action-Versionen veraltet sind!

4.3.7 Praktisches Beispiel: Eine Action auswählen

Szenario: Sie müssen uv (Python Package Manager) in Ihrem Workflow einrichten.

Schritt 1: Suchen

Schritt 2: Ergebnisse bewerten

Schritt 3: Neueste Version prüfen

Schritt 4: Dokumentation lesen

Schritt 5: In Ihrem Workflow verwenden

steps:
  - uses: actions/checkout@v4
  
  - name: Set up uv
    uses: astral-sh/setup-uv@v4  # Hauptversion verwenden
  
  - name: Install dependencies
    run: uv sync --dev
  
  - name: Run checks
    run: uv run ruff check .

Warum das funktioniert:

4.3.8 Kurzreferenz: Action-Auswahl-Leitfaden

Wenn Sie… benötigen

Für Python-Projekte (Dieser Kurs):

Für andere Sprachen:

Für Deployment:

Goldene Regel: Beginnen Sie mit offiziellen actions/* Actions, schauen Sie dann nach verifizierten Erstellern (wie astral-sh für uv), und bewerten Sie dann sorgfältig Community-Actions.


4.4 Eigene Actions erstellen

Obwohl wir in diesem Kurs hauptsächlich bestehende Actions verwenden, ist es wert zu wissen, dass Sie Ihre eigenen benutzerdefinierten GitHub Actions erstellen können für spezialisierte Workflows.

4.4.1 Wann eigene Actions erstellen

Erwägen Sie die Erstellung einer benutzerdefinierten Action, wenn:

Erstellen Sie keine benutzerdefinierte Action, wenn:

4.4.2 Arten von benutzerdefinierten Actions

1. JavaScript Actions

2. Docker Container Actions

3. Composite Actions

Beispiel: Einfache Composite Action

Erstellen Sie .github/actions/setup-project/action.yml:

name: 'Setup Project'
description: 'Install uv and project dependencies'

runs:
  using: 'composite'
  steps:
    - name: Set up uv
      uses: astral-sh/setup-uv@v4
      shell: bash
    
    - name: Install dependencies
      run: uv sync --dev
      shell: bash
    
    - name: Show environment info
      run: |
        uv --version
        uv run python --version
      shell: bash

In Workflows verwenden:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-project  # Lokale Action verwenden
      - run: uv run ruff check .

4.4.3 Benutzerdefinierte Actions veröffentlichen

Für organisatorische Nutzung:

Für öffentliches Teilen:

4.4.4 Ressourcen zum Weiterlernen

Das Erstellen benutzerdefinierter Actions geht über den Umfang dieses Kurses hinaus, aber wenn Sie interessiert sind:

Für diesen Kurs: Konzentrieren Sie sich auf die effektive Nutzung bestehender Actions. Benutzerdefinierte Actions sind ein fortgeschrittenes Thema für den Fall, dass Sie die Workflow-Grundlagen beherrschen und spezifische Bedürfnisse haben, die bestehende Actions nicht erfüllen.


4.5 Häufige Workflow-Trigger

# Bei Push auf bestimmte Branches auslösen
on:
  push:
    branches:
      - main
      - develop

# Bei Pull Requests auf bestimmte Branches auslösen
on:
  pull_request:
    branches:
      - main

# Bei mehreren Events auslösen
on: [push, pull_request, workflow_dispatch]

# Geplante Workflows (Cron-Syntax)
on:
  schedule:
    - cron: '0 0 * * *'  # Täglich um Mitternacht UTC ausführen

# Manueller Trigger (workflow_dispatch)
on:
  workflow_dispatch:    # Fügt "Run workflow" Button in GitHub UI hinzu
    inputs:
      environment:
        description: 'Environment to deploy to'
        required: true
        type: choice
        options:
          - staging
          - production

4.6 GitHub Actions Preise

Für öffentliche Repositories:

Für private Repositories:

Für diesen Kurs:


5. Teil 3: Ihren ersten GitHub Actions Workflow erstellen

Lassen Sie uns einen echten Workflow für das Road Profile Viewer Projekt erstellen, der Qualitätsprüfungen ausführt.

5.1 Schritt 1: Die Workflow-Datei erstellen

Erstellen Sie .github/workflows/quality.yml in Ihrem Repository:

name: Code Quality

on:
  pull_request:
    branches: [ main ]

jobs:
  quality:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up uv
        uses: astral-sh/setup-uv@v4

      - name: Cache dependencies
        uses: actions/cache@v4
        with:
          path: ~/.cache/uv
          key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}

      - name: Install dependencies
        run: uv sync --dev

      - name: Run Ruff linter
        run: uv run ruff check .

      - name: Check formatting
        run: uv run ruff format --check .

      - name: Run Pyright
        run: uv run pyright

5.2 Jeden Schritt verstehen

Schritt 1: Repository auschecken

- name: Checkout repository
  uses: actions/checkout@v4

Schritt 2: uv einrichten

- name: Set up uv
  uses: astral-sh/setup-uv@v4

Schritt 3: Abhängigkeiten cachen (Geschwindigkeitsoptimierung!)

- name: Cache dependencies
  uses: actions/cache@v4
  with:
    path: ~/.cache/uv
    key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}

Dieser Schritt ist ein Game-Changer für die CI-Geschwindigkeit. Das passiert:

Beim ersten Workflow-Durchlauf:

Abhängigkeiten installieren → Dauert ~30-45 Sekunden → Cache gespeichert ✅

Bei nachfolgenden Workflow-Durchläufen (wenn uv.lock unverändert):

Aus Cache wiederherstellen → Dauert ~3-5 Sekunden → Weiter! 🚀

Warum das funktioniert:

Erinnern Sie sich an die uv.lock-Datei, die immer in Ihrem Repository erstellt wird? Sie ist nicht nur Dokumentation – sie ist ein präziser Snapshot jedes Pakets und jeder Version, die Ihr Projekt benötigt. Der Cache verwendet diese Datei als seinen “Fingerabdruck”:

Der Aha-Moment: “Moment mal, uv.lock ist also kein Ballast, sondern spart mir 30 Sekunden bei jedem CI-Durchlauf?” Genau! Deshalb erstellt uv sie immer. Die Lock-Datei sorgt für:

  1. Reproduzierbare Builds (alle bekommen die gleichen Versionen)
  2. Schnelles CI (Cache weiß genau, was wiederhergestellt werden soll)
  3. Abhängigkeits-Tracking (sehen Sie, was sich zwischen Versionen geändert hat)

Pro-Tipp: Löschen Sie niemals uv.lock oder fügen Sie sie zu .gitignore hinzu. Sie ist ein kritischer Teil Ihrer Projektinfrastruktur!

Schritt 4: Abhängigkeiten installieren

- name: Install dependencies
  run: uv sync --dev

Schritte 5-7: Qualitätsprüfungen ausführen

- name: Run Ruff linter
  run: uv run ruff check .

- name: Check formatting
  run: uv run ruff format --check .

- name: Run Pyright
  run: uv run pyright

5.3 Den Workflow committen und pushen

# Verzeichnisstruktur erstellen
mkdir -p .github/workflows

# Workflow-Datei erstellen
# (YAML-Inhalt von oben einfügen)

# Committen und pushen
git add .github/workflows/quality.yml
git commit -m "Add GitHub Actions workflow for code quality"
git push origin main

Was als Nächstes passiert:

  1. GitHub erkennt die neue Workflow-Datei
  2. Workflow wird bei diesem Push ausgelöst
  3. Sie können das Ausführen im “Actions”-Tab beobachten
  4. Ergebnisse erscheinen neben Ihrem Commit

5.4 Workflow-Ergebnisse anzeigen

Auf GitHub:

  1. Gehen Sie zu Ihrem Repository
  2. Klicken Sie auf “Actions”-Tab
  3. Sehen Sie alle Workflow-Läufe
  4. Klicken Sie auf einen Lauf, um detaillierte Logs zu sehen
  5. Jeder Schritt zeigt seine Ausgabe

Visuelle Indikatoren:

5.5 Wenn eine Prüfung fehlschlägt

Beispiel: Ruff findet Style-Probleme

Run uv run ruff check .
src/main.py:16:1: E401 [*] Multiple imports on one line
src/main.py:29:5: E225 [*] Missing whitespace around operator
Found 2 errors.
Error: Process completed with exit code 1.

GitHubs Reaktion:

Reaktion des Entwicklers:

  1. Probleme lokal beheben
  2. Prüfungen lokal ausführen, um Fix zu verifizieren
  3. Fix pushen
  4. Workflow läuft automatisch beim neuen Commit
  5. Wenn er besteht: ✅ Bereit zum Mergen

6. Teil 4: Erweiterte Workflow-Muster

6.1 Mehrere Jobs parallel ausführen

Beschleunigen Sie Ihren Workflow, indem Sie unabhängige Prüfungen parallel ausführen:

name: Code Quality

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv sync --dev
      - run: uv run ruff check .
  
  format:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv sync --dev
      - run: uv run ruff format --check .
  
  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv sync --dev
      - run: uv run pyright

Vorteile:

Nachteil:

6.2 Matrix-Testing (Mehrere Python-Versionen)

Testen Sie Ihren Code gegen mehrere Python-Versionen mit uv’s eingebautem Python-Management:

name: Test Multiple Python Versions

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.12', '3.13']
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: astral-sh/setup-uv@v4
      
      - name: Set Python version
        run: uv python pin ${{ matrix.python-version }}
      
      - name: Install dependencies
        run: uv sync --dev
      
      - name: Run checks
        run: |
          uv run ruff check .
          uv run pyright

Was passiert:

Warum uv für Python-Versionsverwaltung verwenden?

6.3 Abhängigkeiten für Geschwindigkeit cachen

Beschleunigen Sie Workflows durch Caching von Abhängigkeiten:

jobs:
  quality:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: astral-sh/setup-uv@v4
      
      - name: Cache uv dependencies
        uses: actions/cache@v4
        with:
          path: ~/.cache/uv
          key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}
          restore-keys: |
            ${{ runner.os }}-uv-
      
      - run: uv sync --dev
      - run: uv run ruff check .

Wie es funktioniert:

6.4 Bedingte Schritte

Schritte nur unter bestimmten Bedingungen ausführen:

jobs:
  quality:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv sync --dev
      
      - name: Run linter
        run: uv run ruff check .
      
      - name: Auto-fix and commit (only on main branch)
        if: github.ref == 'refs/heads/main'
        run: |
          uv run ruff check --fix .
          git config user.name "GitHub Actions"
          git config user.email "actions@github.com"
          git add -A
          git diff --quiet && git diff --staged --quiet || git commit -m "Auto-fix: Apply Ruff suggestions"
          git push

Anwendungsfälle:

6.5 Secrets verwenden

Sensible Daten sicher speichern:

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to production
        env:
          API_KEY: ${{ secrets.API_KEY }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: |
          echo "Deploying with API key: $API_KEY"
          # Tatsächliche Deployment-Befehle

Secrets verwalten:

  1. Gehen Sie zu Repository Settings → Secrets and variables → Actions
  2. Klicken Sie auf “New repository secret”
  3. Namen und Wert hinzufügen
  4. Im Workflow mit ${{ secrets.SECRET_NAME }} referenzieren
  5. Secret-Werte werden niemals in Logs angezeigt

7. Teil 5: GitHub Copilot zum Erstellen von Workflows verwenden

7.1 Warum sowohl manuelle als auch KI-unterstützte Workflow-Erstellung lernen?

Sie haben die Grundlagen gelernt—jetzt ist es Zeit, KI zu nutzen.

In Teil 3 und 4 haben Sie gelernt, wie man GitHub Actions Workflows manuell schreibt. Dieses grundlegende Wissen ist kritisch, weil:

Verständnis ermöglicht Verifikation - Sie können überprüfen und validieren, was Copilot generiert
Bessere Prompts = bessere Ergebnisse - YAML-Syntax-Kenntnisse helfen Ihnen, präzise Prompts zu schreiben
Debugging erfordert Wissen - Wenn Workflows fehlschlagen, müssen Sie verstehen, warum
KI ist ein Werkzeug, keine Magie - Sie leiten die KI mit Ihrem Fachwissen

Aber wir sind im Zeitalter der LLMs. Jetzt, da Sie Workflows verstehen, nutzen wir GitHub Copilot, um Ihre Arbeit zu beschleunigen.

7.2 GitHub Copilot in VS Code

Wir verwenden GitHub Copilot durchgehend in diesem Kurs als Ihren KI-Programmierparter. Für die Workflow-Erstellung kann Copilot:

Zwei Wege, Copilot für Workflows zu verwenden:

  1. Inline-Vorschläge - Während Sie YAML tippen, schlägt Copilot Vervollständigungen vor
  2. Copilot Chat - Beschreiben Sie, was Sie wollen, erhalten Sie vollständige Workflows

7.3 Praktisches Beispiel: Einen Workflow mit Copilot Chat erstellen

Lassen Sie uns denselben Quality-Workflow aus Teil 3 erstellen, aber mit Copilot Chat.

Schritt 1: Copilot Chat öffnen

In VS Code:

  1. Drücken Sie Ctrl+Alt+I (Windows/Linux) oder Cmd+Alt+I (Mac)
  2. Oder klicken Sie auf das Chat-Symbol in der Sidebar
  3. Oder verwenden Sie die Command Palette: “GitHub Copilot: Open Chat”

Schritt 2: Einen klaren Prompt schreiben

Je besser Ihr Prompt, desto besser das Ergebnis. Nutzen Sie Ihr Workflow-Wissen, um präzise Prompts zu schreiben.

❌ Vager Prompt:

Erstelle einen GitHub Actions Workflow für mein Python-Projekt

✅ Spezifischer Prompt:

Erstelle einen GitHub Actions Workflow für ein Python-Projekt, der:
- Bei Push auf main und Pull Requests auslöst
- astral-sh/setup-uv@v4 verwendet, um uv einzurichten
- Abhängigkeiten mit uv sync --dev installiert
- ruff check und ruff format --check ausführt
- pyright Type Checker ausführt
- Ubuntu Runner verwendet
- uv-Abhängigkeiten für schnellere Läufe cached

Speichere es als .github/workflows/quality.yml

Schritt 3: Überprüfen und verfeinern

Copilot generiert:

name: Code Quality

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  quality:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Set up uv
        uses: astral-sh/setup-uv@v4
      
      - name: Cache uv dependencies
        uses: actions/cache@v4
        with:
          path: ~/.cache/uv
          key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}
          restore-keys: |
            ${{ runner.os }}-uv-
      
      - name: Install dependencies
        run: uv sync --dev
      
      - name: Run Ruff linter
        run: uv run ruff check .
      
      - name: Check formatting
        run: uv run ruff format --check .
      
      - name: Run Pyright
        run: uv run pyright

✅ Das ist exzellent! Aber immer überprüfen:

Wenn etwas angepasst werden muss:

Können Sie diesen Workflow auch bei Pushes auf den develop-Branch auslösen?

Copilot aktualisiert den Workflow:

on:
  push:
    branches: [ main, develop ]  # ✅ develop hinzugefügt
  pull_request:
    branches: [ main ]

Schritt 4: Die Datei erstellen

Copilot bitten, die Datei zu erstellen:

Erstelle die Datei .github/workflows/quality.yml mit diesem Inhalt

Copilot wird:

  1. Die Verzeichnisstruktur erstellen, falls nötig
  2. Die Datei generieren
  3. Den Workflow-Inhalt hinzufügen

Oder manuell erstellen:

  1. Verzeichnis .github/workflows/ erstellen
  2. Datei quality.yml erstellen
  3. Generierten Inhalt einfügen

7.4 Fortgeschrittene Prompting-Techniken

Nutzen Sie Ihr Workflow-Wissen für bessere Prompts.

Technik 1: Spezifische Actions referenzieren

Statt:

Füge Python-Setup zu meinem Workflow hinzu

Verwenden Sie Ihr Wissen:

Füge astral-sh/setup-uv@v4 hinzu, um uv zu installieren, dann verwende uv python pin, um Python 3.12 zu setzen

Technik 2: Matrix-Testing spezifizieren

Erstelle einen Workflow, der mein Python-Projekt gegen Python 3.12 und 3.13 
testet, mit einer Matrix-Strategie. Verwende uv python pin, um Versionen zu wechseln.

Copilot generiert:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.12', '3.13']
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - name: Set Python version
        run: uv python pin $
      - run: uv sync --dev
      - run: uv run pytest

Technik 3: Bedingte Logik anfordern

Füge einen Deployment-Schritt hinzu, der nur auf dem main-Branch nach bestandenen Tests läuft. 
Verwende das "needs"-Schlüsselwort, um eine Abhängigkeit zu erstellen.

Technik 4: Um Erklärungen bitten

Erkläre, was dieser Workflow macht und warum wir actions/cache@v4 verwenden

Copilot erklärt den Workflow, hilft Ihnen beim Lernen während Sie bauen.

7.5 Workflows mit Copilot debuggen

Wenn ein Workflow fehlschlägt, verwenden Sie Copilot zum Debuggen.

Szenario: Workflow-Fehler

Sie sehen diesen Fehler in GitHub Actions:

Error: Unable to resolve action `astral-sh/setup-uv@v3`, 
unable to find version `v3`

Fragen Sie Copilot:

Mein Workflow schlägt fehl mit "unable to find version v3" für astral-sh/setup-uv. 
Was ist falsch und wie behebe ich es?

Copilot antwortet:

Der Fehler zeigt an, dass v3 für astral-sh/setup-uv nicht existiert. 
Die neueste Version ist @v4. Aktualisieren Sie Ihren Workflow:

- uses: astral-sh/setup-uv@v4  # Geändert von @v3

Sie können die neueste Version überprüfen unter:
https://github.com/astral-sh/setup-uv/releases

Um Optimierung bitten

Mein Workflow dauert 5 Minuten. Wie kann ich ihn schneller machen?

Copilot schlägt vor:

7.6 Best Practices: Manuelles Wissen mit KI kombinieren

Tun:

Zuerst die Grundlagen lernen (das haben Sie in Teil 3-4 getan!)
Spezifische Prompts schreiben mit korrekter Terminologie
Allen generierten Code überprüfen - Sie sind verantwortlich für das, was läuft
Action-Versionen verifizieren im Marketplace
Workflows testen, bevor Sie zu main mergen
Copilot um Erklärungen bitten für unbekannte Muster
Mit Copilot iterieren - Prompts basierend auf Ergebnissen verfeinern

Nicht tun:

Generiertem Code blind vertrauen ohne Überprüfung
Grundlagen überspringen und nur KI verwenden
Veraltete Actions verwenden, die Copilot vorschlägt (Versionen verifizieren!)
Sicherheitsimplikationen ignorieren von generierten Workflows
Ersten Vorschlag akzeptieren - für bessere Ergebnisse iterieren

7.7 Praktische Übung: Erstellen Sie Ihren eigenen Workflow

Jetzt sind Sie dran! Verwenden Sie Copilot, um einen Workflow für Ihr Road Profile Viewer Projekt zu erstellen.

Ihre Aufgabe:

  1. Öffnen Sie Copilot Chat in VS Code
  2. Schreiben Sie einen Prompt für einen Workflow, der:
    • Bei Pull Requests auf main läuft
    • uv einrichtet
    • Abhängigkeiten installiert
    • ruff, pyright und pytest ausführt
    • Test-Coverage als Artefakt hochlädt
  3. Überprüfen Sie den generierten Workflow
  4. Bitten Sie Copilot, ihn zu verbessern (Caching hinzufügen, bessere Benennung, etc.)
  5. Erstellen Sie die Datei und testen Sie sie

Beispiel-Prompt zum Starten:

Erstelle einen GitHub Actions Workflow für mein Python-Projekt, der Qualitätsprüfungen 
und Tests bei Pull Requests ausführt. Verwende uv für Dependency Management, führe ruff für 
Linting- und Formatierungsprüfungen aus, pyright für Type Checking und pytest für Tests. 
Lade Test-Coverage-Ergebnisse als Artefakt hoch. Füge Caching für schnellere Läufe hinzu.

7.8 Der moderne Entwicklungsworkflow

So funktioniert moderne Softwareentwicklung:

1. Grundlagen verstehen ✅ (Teil 3-4)
        ↓
2. KI zur Beschleunigung nutzen ⚡ (Dieser Teil!)
        ↓
3. Mit Expertise überprüfen 🧠 (Ihr Wissen)
        ↓
4. Iterieren und verbessern 🔄 (Prompts verfeinern)
        ↓
5. Qualitätscode ausliefern 🚀

Schlüsselerkenntnis: KI verstärkt Ihre Fähigkeiten. Je besser Sie Workflows verstehen, desto besser können Sie Copilot verwenden, um sie zu erstellen.

7.9 Wann manuelle vs. KI-unterstützte Erstellung verwenden

Manuelle Erstellung verwenden, wenn:

KI-unterstützte Erstellung verwenden, wenn:

Bester Ansatz: Kombinieren Sie beide! Nutzen Sie Ihr Wissen, um KI zu leiten, und nutzen Sie KI, um Ihre Arbeit zu beschleunigen.


8. Teil 6: Pre-commit-Hooks - Lokale Qualitätsgates

8.1 Warum Pre-commit-Hooks?

Problem: Auf das Fehlschlagen von CI zu warten ist langsam:

Code schreiben → Committen → Pushen → CI läuft (2 Minuten) → CI schlägt fehl ❌
    ↓
Probleme beheben → Committen → Pushen → CI läuft (2 Minuten) → CI besteht ✅

Gesamtverschwendete Zeit: 4+ Minuten

Lösung: Probleme vor GitHub erkennen mit Pre-commit-Hooks.

8.2 Was sind Pre-commit-Hooks?

Pre-commit-Hooks sind Skripte, die automatisch laufen, wenn Sie versuchen, Code zu committen.

Workflow:

git commit
    ↓
Pre-commit-Hook läuft
    - Prüft Code-Qualität
    - Führt Linter aus
    - Prüft Formatierung
    ↓
Pass ✅ → Commit läuft weiter
Fail ❌ → Commit blockiert, Probleme beheben

Vorteile:

8.3 Pre-commit installieren

# Pre-commit als Tool installieren (nicht als Projektabhängigkeit)
uv tool install pre-commit

# Installation verifizieren
pre-commit --version

8.4 Pre-commit konfigurieren

Erstellen Sie .pre-commit-config.yaml im Projekt-Root:

repos:
  # Ruff Linter und Formatter
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.8.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format
  
  # Pyright Type Checker
  - repo: https://github.com/RobertCraigie/pyright-python
    rev: v1.1.388
    hooks:
      - id: pyright
  
  # Allgemeine Prüfungen
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files

Hooks installieren:

pre-commit install

Dies erstellt ein .git/hooks/pre-commit Skript, das automatisch läuft.

8.5 Pre-commit verwenden

Automatisch beim Committen:

git add .
git commit -m "Add new feature"

# Pre-commit läuft automatisch:
# - Ruff Linter prüft Code
# - Ruff Formatter prüft Formatierung
# - Pyright prüft Typen
# - Allgemeine Prüfungen laufen
#
# Wenn alle bestehen: Commit erfolgreich
# Wenn einer fehlschlägt: Commit blockiert

Manueller Lauf:

# Auf allen Dateien ausführen
pre-commit run --all-files

# Spezifischen Hook ausführen
pre-commit run ruff --all-files

Hooks überspringen (nur im Notfall):

git commit --no-verify -m "Emergency hotfix"

8.6 Pre-commit Best Practices

TUN:

NICHT TUN:


9. Teil 7: Eine vollständige Qualitätspipeline aufbauen

Lassen Sie uns alles zu einer umfassenden Qualitätspipeline kombinieren.

9.1 Das vollständige Setup

Datei: pyproject.toml

[project]
name = "road-profile-viewer"
version = "0.1.0"
description = "Interactive road profile visualization"
requires-python = ">=3.12"

dependencies = [
    "numpy>=2.0.0",
    "dash>=2.18.0",
    "plotly>=5.24.0",
]

[build-system]
requires = ["uv>=0.4.0"]
build-backend = "uv"

[tool.uv.dev-dependencies]
ruff = ">=0.8.0"
pyright = ">=1.1.388"
pre-commit = ">=4.0.0"

[tool.ruff]
line-length = 88
target-version = "py312"

[tool.ruff.lint]
select = [
    "E",   # pycodestyle errors
    "W",   # pycodestyle warnings
    "F",   # Pyflakes
    "I",   # isort
    "N",   # pep8-naming
    "B",   # flake8-bugbear
    "C4",  # flake8-comprehensions
    "UP",  # pyupgrade
]

[tool.pyright]
typeCheckingMode = "standard"
pythonVersion = "3.12"

Datei: .pre-commit-config.yaml

repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.8.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format
  
  - repo: https://github.com/RobertCraigie/pyright-python
    rev: v1.1.388
    hooks:
      - id: pyright

Datei: .github/workflows/quality.yml

name: Code Quality

on:
  pull_request:
    branches: [ main ]

jobs:
  quality:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up uv
        uses: astral-sh/setup-uv@v4

      - name: Cache dependencies
        uses: actions/cache@v4
        with:
          path: ~/.cache/uv
          key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}

      - name: Install dependencies
        run: uv sync --dev

      - name: Run Ruff linter
        run: uv run ruff check .

      - name: Check formatting
        run: uv run ruff format --check .

      - name: Run Pyright
        run: uv run pyright

9.2 Der vollständige Entwickler-Workflow

1. Initiales Setup (einmal pro Entwickler):

# Repository klonen
git clone https://github.com/your-username/road-profile-viewer.git
cd road-profile-viewer

# Umgebung einrichten
uv sync --dev

# Pre-commit-Hooks installieren
pre-commit install

2. Tägliche Entwicklung:

# Feature-Branch erstellen
git checkout -b feature/new-visualization

# Code schreiben mit IDE-Feedback
# - VS Code + Pylance zeigt Typfehler in Echtzeit
# - Ruff-Extension hebt Style-Probleme hervor

# Prüfungen manuell ausführen (optional, Pre-commit macht das)
uv run ruff check --fix .
uv run ruff format .
uv run pyright

# Code committen
git add .
git commit -m "Add new visualization"

# Pre-commit-Hooks laufen automatisch:
# ✅ Ruff Linter - bestanden
# ✅ Ruff Format - bestanden
# ✅ Pyright - bestanden
# 
# Commit erfolgreich!

# Zu GitHub pushen
git push origin feature/new-visualization

3. GitHub antwortet:

GitHub Actions Workflow ausgelöst:
  - Abhängigkeiten installieren... ✅
  - Ruff Linter ausführen... ✅
  - Formatierung prüfen... ✅
  - Pyright ausführen... ✅

Alle Prüfungen bestanden! ✅

4. Code-Review:

Reviewer sieht:
- ✅ Alle automatisierten Prüfungen bestanden
- Kann sich fokussieren auf:
  - Geschäftslogik
  - Algorithmuskorrektheit
  - Architekturentscheidungen
  - NICHT Style/Formatierung (bereits validiert)

5. Merge:

# Pull Request auf GitHub mergen
# Oder via Kommandozeile:
git checkout main
git merge feature/new-visualization
git push origin main

# CI läuft erneut auf main-Branch
# Bestätigt, dass nach dem Merge alles noch funktioniert

9.3 Qualitätsgates auf jeder Ebene

Ebene Tool Wann Geschwindigkeit Zweck
IDE Pylance, Ruff Extension Echtzeit beim Tippen Sofort Fehler beim Schreiben erkennen
Pre-commit pre-commit hooks Vor jedem Commit <10s Schlechte Commits verhindern
CI GitHub Actions Nach jedem Push 1-3 min In sauberer Umgebung verifizieren
Code Review Menschliche Reviewer Vor Merge Stunden Logik und Design prüfen

Der Trichtereffekt:


10. Teil 8: Branch Protection und Merge-Anforderungen

10.1 Was ist Branch Protection?

Branch-Protection-Regeln verhindern direkte Pushes auf wichtige Branches und setzen Qualitätsanforderungen vor dem Mergen durch.

Häufige Schutzmaßnahmen:

10.2 Branch Protection einrichten

Auf GitHub:

  1. Gehen Sie zu Repository Settings
  2. Klicken Sie auf “Branches” in der Sidebar
  3. Klicken Sie auf “Add branch protection rule”
  4. Konfigurieren:
Branch name pattern: main

☑ Require a pull request before merging
  ☑ Require approvals: 1
  ☑ Dismiss stale pull request approvals when new commits are pushed

☑ Require status checks to pass before merging
  ☑ Require branches to be up to date before merging
  Status checks required:
    - quality (Ihr GitHub Actions Workflow-Name)

☑ Require conversation resolution before merging

☐ Require signed commits (optional, fortgeschrittener)

☐ Require linear history (optional, verhindert Merge-Commits)

☐ Include administrators (empfohlen: ja)

Klicken Sie auf “Create” zum Aktivieren.

10.3 Was passiert jetzt?

Vor Branch Protection:

# Jeder kann direkt auf main pushen
git checkout main
git commit -m "Quick fix"
git push origin main  # ✅ Funktioniert (aber riskant!)

Nach Branch Protection:

git push origin main  # ❌ Blockiert!
# Error: Protected branch 'main' cannot be pushed to

Erforderlicher Workflow:

# 1. Feature-Branch erstellen
git checkout -b feature/fix

# 2. Änderungen machen und committen
git commit -m "Fix bug"

# 3. Feature-Branch pushen
git push origin feature/fix

# 4. Pull Request auf GitHub öffnen

# 5. Auf bestandene CI warten

# 6. Code-Review anfordern

# 7. Genehmigung erhalten

# 8. Via GitHub UI mergen (jetzt erlaubt)

10.4 Vorteile von Branch Protection

Qualitätsdurchsetzung:

Zusammenarbeit:

Sicherheit:


11. Teil 9: Erweiterte CI/CD-Muster

11.1 Deployment-Pipeline

Erweitern Sie Ihre CI-Pipeline um Deployment:

name: CI/CD Pipeline

on:
  push:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv sync --dev
      - run: uv run ruff check .
      - run: uv run pyright
      - run: uv run pytest  # Tests ausführen
  
  deploy:
    needs: test  # Nur ausführen, wenn test-Job erfolgreich ist
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to production
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
        run: |
          echo "Deploying to production..."
          # Ihre Deployment-Befehle hier

11.2 Benachrichtigungen

Benachrichtigt werden, wenn Workflows fehlschlagen:

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # ... Ihre Prüfungen ...
      
      - name: Notify on failure
        if: failure()
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: 'Quality checks failed!'
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}

11.3 Workflow-Badges

Build-Status in Ihrer README anzeigen:

# Road Profile Viewer

![CI Status](https://github.com/username/repo/workflows/Code%20Quality/badge.svg)

Interaktives Road-Profile-Visualisierungstool.

Das Badge aktualisiert sich automatisch:


12. Zusammenfassung: Wichtige Erkenntnisse

12.1 CI/CD ist nicht optional

In moderner Softwareentwicklung ist CI/CD grundlegend:

12.2 Qualitätsgates schichten

Bauen Sie einen Qualitätstrichter:

  1. IDE - Echtzeit-Feedback beim Schreiben
  2. Pre-commit - Schlechte Commits verhindern
  3. CI - In sauberer Umgebung verifizieren
  4. Code Review - Menschliche Validierung
  5. Produktions-Monitoring - Fangen, was entkommen ist

12.3 Alles automatisieren

Wenn Menschen sich erinnern müssen, es zu tun, wird es nicht konsistent getan:

12.4 GitHub Actions ist mächtig und kostenlos

Für öffentliche Repositories (wie Studentenprojekte):

12.5 KI verstärkt Ihre Fähigkeiten

Im Zeitalter der LLMs:

Schlüsselerkenntnis: Je besser Sie CI/CD verstehen, desto besser können Sie KI-Tools wie GitHub Copilot nutzen, um anspruchsvolle Workflows zu erstellen.


13. Zusätzliche Ressourcen

13.1 Offizielle Dokumentation

13.2 Lernressourcen

13.3 Tools und Extensions


14. Referenzen

[1] GitHub Actions Documentation. https://docs.github.com/en/actions

[2] pre-commit Framework Documentation. https://pre-commit.com/

[3] Martin Fowler - Continuous Integration. https://martinfowler.com/articles/continuousIntegration.html

[4] Astral - uv CI/CD Integration. https://docs.astral.sh/uv/guides/integration/github/

[5] Kim, Gene et al. “The DevOps Handbook.” IT Revolution Press, 2016.

[6] Humble, Jez and David Farley. “Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation.” Addison-Wesley, 2010.

[7] GitHub Branch Protection Documentation. https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches

[8] YAML Syntax Guide. https://yaml.org/spec/1.2/spec.html


Hinweis: Diese Vorlesung basiert auf GitHub Actions und Tools, die ab Oktober 2025 verfügbar sind. Plattform-Features und spezifische Actions können sich im Laufe der Zeit weiterentwickeln.

© 2026 Dominik Mueller   •  Powered by Soopr   •  Theme  Moonwalk