42 Plugins - Grundlagen

42.1 Warum reichen Module nicht aus?

Problem: Standard-Module arbeiten nur auf Zielsystemen. Sie können keine Daten direkt auf dem Control-Node verarbeiten, z. B. Passwörter laden oder IP-Adressen berechnen.

Lösung: Plugins erweitern Ansible um Funktionen, die auf dem Control-Node laufen und Daten flexibel bereitstellen oder transformieren.

42.2 Module vs. Plugins

Aspekt Module Plugins
Ausführungsort Zielsystem (Remote Host) Control-Node (Ansible-Engine)
Zweck Aktionen ausführen Daten verarbeiten/bereitstellen
Beispiele package, copy, service lookup('file'), selectattr(), yaml callback
Timing während Task-Ausführung vor/während/nach Tasks

Merksatz: Module sind die „Hände“, Plugins das „Gehirn“ von Ansible.

42.3 Plugin-Typen

42.3.1 Lookup-Plugins: Daten laden

ansible localhost -m debug -a "msg={{ lookup('env', 'HOME') }}"

42.3.2 Filter-Plugins: Daten transformieren

ansible localhost -m debug -a "msg={{ ['web01','db01','web02'] | select('match','web.*') | list }}"

42.3.3 Callback-Plugins: Ausgabe ändern

ansible localhost -m debug -a "msg=Hello"
ansible localhost -m debug -a "msg=Hello" -e ansible_stdout_callback=yaml

42.4 Beispiel: Lookup + Filter + Callback

test-plugins.yml

- name: Plugin-Grundlagen verstehen
  hosts: localhost
  gather_facts: false
  vars:
    servers:
      - {name: "web01", role: "web", active: true}
      - {name: "web02", role: "web", active: false}
      - {name: "db01", role: "database", active: true}

  tasks:
    - name: Aktueller Benutzer
      debug:
        msg: "Playbook läuft als: {{ lookup('env', 'USER') }}"

    - name: Nur aktive Web-Server
      debug:
        msg: "Aktive: {{ servers | selectattr('role','equalto','web') | selectattr('active') | map(attribute='name') | list }}"

    - name: Home-Verzeichnis analysieren
      debug:
        msg: "Home hat {{ lookup('env','HOME') | length }} Zeichen"
ansible-playbook test-plugins.yml
ansible-playbook test-plugins.yml -e ansible_stdout_callback=yaml

42.5 Plugin-Discovery

Reihenfolge der Suche: 1. Built-in (file, env, default) 2. Collections (z. B. community.general.dig) 3. Projekt-lokal (./plugins/lookup/) 4. Global (~/.ansible/plugins/)

ansible-doc -t lookup -l | head -10

42.6 Mini-Übung

Aufgabe: 1. Hostname über Lookup laden 2. Liste von Zahlen filtern (gerade Zahlen) 3. Ausgabe im Standard- und JSON-Format

uebung.yml

- name: Plugin-Übung
  hosts: localhost
  vars:
    numbers: [1,2,3,4,5,6,7,8,9,10]

  tasks:
    - name: Hostname laden
      debug:
        msg: "Host: {{ lookup('env','HOSTNAME') | default('unbekannt') }}"

    - name: Gerade Zahlen
      debug:
        msg: "Gerade Zahlen: {{ numbers | select('even') | list }}"
ansible-playbook uebung.yml
ansible-playbook uebung.yml -e ansible_stdout_callback=json

# Plugins nutzen

## Leitfrage: Wie nutze ich Plugins in realen Projekten?

Dieser Teil zeigt die praktische Anwendung in drei Stufen: **Einfach****Mittel****Fortgeschritten**

---

## Beispiel: Einfache Grundoperationen

### Ziel: Externe Dateien und Umgebungsvariablen nutzen

**Szenario**: SSH-Keys deployen und umgebungsabhängig konfigurieren

**Code** (speichern als `stufe1-basics.yml`):
```yaml
---
- name: Stufe 1 - Basis-Lookups
  hosts: localhost
  gather_facts: false
  
  tasks:
    - name: SSH-Key aus Datei laden (falls vorhanden)
      debug:
        msg: "SSH-Key Länge: {{ ssh_key | length }} Zeichen"
      vars:
        ssh_key: "{{ lookup('file', ansible_env.HOME + '/.ssh/id_rsa.pub') | default('') }}"
      when: ssh_key | length > 0

    - name: Umgebung bestimmen  
      debug:
        msg: "Deploying in {{ environment }} Umgebung"
      vars:
        environment: "{{ lookup('env', 'ANSIBLE_ENV') | default('development') }}"

    - name: Passwort generieren oder laden
      debug:
        msg: "Passwort wurde {{ 'generiert' if password_file.stat.exists else 'neu erstellt' }}"
      vars:
        password: "{{ lookup('password', '/tmp/demo_password chars=ascii_letters,digits length=12') }}"
        password_file: "{{ ansible_env }}"

Ausführung:

# Test in verschiedenen Umgebungen
ansible-playbook stufe1-basics.yml
ANSIBLE_ENV=production ansible-playbook stufe1-basics.yml

Lernergebnis: Lookup-Plugins für Dateien, Umgebungsvariablen und Passwort-Generierung


42.7 Beispiel: Datenverarbeitung und -filterung

42.7.1 Ziel: Server-Listen analysieren und aufbereiten

Szenario: Aus einer Server-Liste die richtigen Ziele für Deployment ermitteln

Code (speichern als stufe2-filter.yml):

---
- name: Stufe 2 - Datenverarbeitung
  hosts: localhost
  gather_facts: false
  vars:
    servers:
      - {name: "web01", env: "prod", role: "frontend", cpu: 4, active: true}
      - {name: "web02", env: "prod", role: "frontend", cpu: 2, active: false}
      - {name: "api01", env: "prod", role: "backend", cpu: 8, active: true}
      - {name: "web03", env: "staging", role: "frontend", cpu: 2, active: true}
      - {name: "db01", env: "prod", role: "database", cpu: 16, active: true}

  tasks:
    - name: Produktions-Server ermitteln
      debug:
        msg: "Prod-Server: {{ prod_servers | join(', ') }}"
      vars:
        prod_servers: "{{ servers | selectattr('env', 'equalto', 'prod') | selectattr('active') | map(attribute='name') | list }}"

    - name: High-Performance Server finden  
      debug:
        msg: "Leistungsstarke Server: {{ high_perf | join(', ') }}"
      vars:
        high_perf: "{{ servers | selectattr('cpu', 'gt', 4) | map(attribute='name') | list }}"

    - name: Server nach Rollen gruppieren
      debug:
        msg: "{{ role }}: {{ count }} Server"
      vars:
        roles_count: "{{ servers | selectattr('active') | groupby('role') | items2dict(key_name='role', value_name='servers') }}"
      loop: "{{ roles_count.keys() | list }}"
      loop_control:
        loop_var: role
      vars:
        count: "{{ roles_count[role] | length }}"

    - name: Deployment-Ziele bestimmen
      set_fact:
        deploy_targets: |
          {{ servers | 
             selectattr('env', 'equalto', target_env | default('prod')) |
             selectattr('role', 'in', ['frontend', 'backend']) |
             selectattr('active') |
             map(attribute='name') | list }}

    - name: Deployment-Plan anzeigen
      debug:
        msg: "Deploying to: {{ deploy_targets | join(', ') }}"

Test mit verschiedenen Umgebungen:

ansible-playbook stufe2-filter.yml
ansible-playbook stufe2-filter.yml -e target_env=staging

Lernergebnis: Filter-Kombinationen für komplexe Datenanalysen


42.8 Beispiel: API-Integration und Callbacks

42.8.1 Ziel: Externe APIs nutzen und Ausgabe optimieren

Szenario: Service-Discovery über API mit strukturierter Ausgabe

Code (speichern als stufe3-advanced.yml):

---
- name: Stufe 3 - API-Integration
  hosts: localhost
  gather_facts: false
  
  tasks:
    - name: Service-Discovery via DNS
      debug:
        msg: "Mail-Server: {{ mail_servers | join(', ') }}"
      vars:
        mail_servers: "{{ lookup('dig', 'google.com', 'qtype=MX', wantlist=True) | default(['keine gefunden']) }}"

    - name: Externes API simulieren (httpbin.org)
      set_fact:
        api_response: "{{ lookup('url', 'https://httpbin.org/json') | from_json }}"
      
    - name: API-Daten verarbeiten
      debug:
        msg: "Slideshow hat {{ slide_count }} Folien"
      vars:
        slide_count: "{{ api_response | json_query('slideshow.slides | length') }}"

    - name: Konfiguration aus mehreren Quellen
      set_fact:
        final_config:
          environment: "{{ lookup('env', 'NODE_ENV') | default('development') }}"
          debug_mode: "{{ lookup('env', 'DEBUG') | default('false') | bool }}"
          api_endpoint: "{{ api_response.slideshow.author | default('localhost') }}"
          timestamp: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"

    - name: Finale Konfiguration
      debug:
        var: final_config

Callback-Test (verschiedene Ausgabeformate):

# Standard-Format
ansible-playbook stufe3-advanced.yml

# Strukturiertes YAML
ansible-playbook stufe3-advanced.yml -e ansible_stdout_callback=yaml

# Kompaktes JSON (für Scripts)
ansible-playbook stufe3-advanced.yml -e ansible_stdout_callback=json

# Minimal (nur Ergebnisse)
ansible-playbook stufe3-advanced.yml -e ansible_stdout_callback=minimal

Lernergebnis: Integration externer APIs, JSON-Query-Filter, Callback-Plugins für verschiedene Ausgabeformate


42.9 Integriertes Beispiel: Alle Stufen kombiniert

42.9.1 Ziel: Vollständiger Deployment-Workflow mit allen Plugin-Typen

Code (speichern als komplett-workflow.yml):

---
- name: Vollständiger Plugin-Workflow
  hosts: localhost
  gather_facts: false
  vars:
    app_name: "{{ lookup('env', 'APP_NAME') | default('demo-app') }}"
    
  tasks:
    - name: 1. Deployment-Umgebung laden
      set_fact:
        deploy_config:
          environment: "{{ lookup('env', 'DEPLOY_ENV') | default('staging') }}"
          version: "{{ lookup('pipe', 'git rev-parse --short HEAD') | default('unknown') }}"
          timestamp: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"

    - name: 2. Server-Liste aus Datei (simuliert)
      set_fact:
        available_servers:
          - {name: "web01", env: "{{ deploy_config.environment }}", health: "ok"}
          - {name: "web02", env: "{{ deploy_config.environment }}", health: "warning"}
          - {name: "api01", env: "{{ deploy_config.environment }}", health: "ok"}

    - name: 3. Deployment-Ziele filtern
      set_fact:
        healthy_servers: "{{ available_servers | selectattr('health', 'equalto', 'ok') | map(attribute='name') | list }}"

    - name: 4. Deployment-Summary
      debug:
        msg: |
          Deployment Summary:
          App: {{ app_name }}
          Environment: {{ deploy_config.environment }}
          Version: {{ deploy_config.version }}
          Targets: {{ healthy_servers | join(', ') }}
          Time: {{ deploy_config.timestamp }}

Ausführung mit verschiedenen Konfigurationen:

# Staging-Deployment
APP_NAME=myapp DEPLOY_ENV=staging ansible-playbook komplett-workflow.yml -e ansible_stdout_callback=yaml

# Production-Deployment  
APP_NAME=myapp DEPLOY_ENV=production ansible-playbook komplett-workflow.yml -e ansible_stdout_callback=json

42.10 Übung: Eigene Plugin-Kombination

Aufgabe: Erstellen Sie ein Playbook, das: 1. Ihren Benutzernamen und das aktuelle Datum lädt 2. Eine Liste von Services filtert (nur die mit Status “running”) 3. Ein Deployment-Log in /tmp/deployment.log schreibt

Vorlage:

---
- name: Plugin-Übung fortgeschritten
  hosts: localhost
  vars:
    services:
      - {name: "nginx", status: "running", port: 80}
      - {name: "apache", status: "stopped", port: 8080}  
      - {name: "redis", status: "running", port: 6379}

  tasks:
    - name: Aktuelle Info sammeln
      set_fact:
        deploy_info:
          user: "{{ lookup('env', 'USER') }}"
          date: "{{ lookup('pipe', 'date') }}"
          running_services: "{{ services | selectattr('status', 'equalto', 'running') | map(attribute='name') | list }}"

    - name: Log-Eintrag schreiben
      copy:
        content: "{{ deploy_info | to_nice_json }}"
        dest: /tmp/deployment.log

    - name: Ergebnis anzeigen
      debug:
        var: deploy_info

42.11 Erkenntnisse