#!/usr/bin/env python3 """ Moderne Ansible-Präsentation — vollständiger Generator (v3). 19 Folien (16:9) mit den Ansible-Brandfarben (Rot #EE0000, Anthrazit #1A1918). Fonts: Carlito (Body) + DejaVu Sans Mono (Code) — auf dem Zielsystem installiert und von OnlyOffice korrekt gerendert (kein Font-Substitution-Bruch). Behandelt: - Was ist Ansible? / Aufbau & Architektur - YAML-Syntax, Playbooks, Module, Ad-hoc-Befehle - Jinja2 (Templating) und Gather Facts - Variablen-Auflösung (Präzedenz) - FQCN (Fully Qualified Collection Name) - Collections - Rollen (Roles) - Praxisbeispiel + Quellen Benötigt: pip install python-pptx matplotlib Pillow Bilder: Ansible-Logo (assets/ansible_mark_*.png, Quelle: Wikimedia Commons) Architektur-Diagramm wird automatisch erzeugt (assets/architektur.png) """ import os import re import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from matplotlib.patches import FancyBboxPatch from pptx import Presentation from pptx.util import Inches, Pt from pptx.enum.text import PP_ALIGN, MSO_ANCHOR from pptx.enum.shapes import MSO_SHAPE from pptx.dml.color import RGBColor # --------------------------------------------------------------------------- # Pfade & Farben # --------------------------------------------------------------------------- BASE = os.path.dirname(os.path.abspath(__file__)) ASSETS = os.path.join(BASE, "assets") os.makedirs(ASSETS, exist_ok=True) LOGO_RED = os.path.join(ASSETS, "ansible_mark_red.png") LOGO_WHITE = os.path.join(ASSETS, "ansible_mark_white.png") ARCH = os.path.join(ASSETS, "architektur.png") BLACK = RGBColor(0x1A, 0x19, 0x18) RED = RGBColor(0xEE, 0x00, 0x00) DARKRED = RGBColor(0xB0, 0x00, 0x00) WHITE = RGBColor(0xFF, 0xFF, 0xFF) LIGHT = RGBColor(0xF7, 0xF7, 0xF7) CARD = RGBColor(0xFF, 0xFF, 0xFF) GRAY = RGBColor(0x55, 0x55, 0x55) # dunkler für bessere Lesbarkeit MIDGRAY = RGBColor(0x8A, 0x8A, 0x8A) BORDER = RGBColor(0xDD, 0xDD, 0xDD) CODE_BG = RGBColor(0x1E, 0x1E, 0x1E) CODE_CMT= RGBColor(0x8B, 0xC3, 0x7C) # grün für Kommentare CODE_KEY= RGBColor(0x7E, 0xC8, 0xFF) # hellblau für Schlüssel CODE_MOD= RGBColor(0xFF, 0x6B, 0x6B) # rot für Module/FQCN # Fonts: auf dem Zielsystem installiert (OnlyOffice-kompatibel) FONT = "Carlito" FONT_MONO = "DejaVu Sans Mono" # --------------------------------------------------------------------------- # Architektur-Diagramm (matplotlib) # --------------------------------------------------------------------------- def make_architecture_diagram(path): C_RED, C_BLACK, C_GRAY = "#EE0000", "#1A1918", "#6E6E6E" fig, ax = plt.subplots(figsize=(11, 5.0), dpi=170) ax.set_xlim(0, 11); ax.set_ylim(0, 5.0); ax.axis("off") def box(x, y, w, h, fc, ec, lw=1.5, r=0.12): p = FancyBboxPatch((x, y), w, h, boxstyle=f"round,pad=0.06,rounding_size={r}", fc=fc, ec=ec, lw=lw) ax.add_patch(p); return p box(0.5, 0.7, 4.3, 3.7, "#1A1918", C_RED, lw=2.5) ax.text(2.65, 3.95, "CONTROL NODE", ha="center", va="center", color="white", fontsize=15, fontweight="bold") ax.text(2.65, 3.45, "Ansible installiert", ha="center", va="center", color=C_RED, fontsize=10.5, fontweight="bold") items = ["Playbooks (YAML)", "Inventory", "Module & Plugins", "Rollen & Collections"] for i, it in enumerate(items): y = 2.85 - i * 0.5 box(0.95, y - 0.18, 3.4, 0.36, "#2B2B2B", "none") ax.text(2.65, y, it, ha="center", va="center", color="white", fontsize=10.5) ax.annotate("", xy=(5.75, 2.6), xytext=(4.9, 2.6), arrowprops=dict(arrowstyle="-|>", color=C_RED, lw=2.6)) ax.text(5.32, 3.05, "SSH", ha="center", va="center", color=C_RED, fontsize=12, fontweight="bold") ax.text(5.32, 2.1, "agentenlos", ha="center", va="center", color=C_GRAY, fontsize=8.5) box(5.95, 0.7, 4.6, 3.7, "#F7F7F7", C_BLACK, lw=1.5) ax.text(8.25, 3.95, "MANAGED NODES", ha="center", va="center", color=C_BLACK, fontsize=15, fontweight="bold") ax.text(8.25, 3.45, "Ziel-Systeme — kein Agent nötig", ha="center", va="center", color=C_GRAY, fontsize=10) nodes = [("Web-Server", "Linux"), ("Datenbank", "Linux"), ("App-Server", "Windows")] xs = [6.6, 8.25, 9.9] for (name, os_), x in zip(nodes, xs): box(x - 0.62, 1.0, 1.24, 1.5, "white", C_RED, lw=2, r=0.1) ax.text(x, 1.95, name, ha="center", va="center", color=C_BLACK, fontsize=10, fontweight="bold") ax.text(x, 1.4, os_, ha="center", va="center", color=C_GRAY, fontsize=9) fig.savefig(path, bbox_inches="tight", facecolor="white", transparent=False) plt.close(fig) return path # --------------------------------------------------------------------------- # pptx-Helfer # --------------------------------------------------------------------------- prs = Presentation() prs.slide_width = Inches(13.333) prs.slide_height = Inches(7.5) BLANK = prs.slide_layouts[6] def new_slide(bg=None): s = prs.slides.add_slide(BLANK) if bg is not None: r = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height) r.fill.solid(); r.fill.fore_color.rgb = bg r.line.fill.background(); r.shadow.inherit = False return s def rect(slide, x, y, w, h, fill, line=None, line_w=1.0, rounded=False, radius=0.08): shp_type = MSO_SHAPE.ROUNDED_RECTANGLE if rounded else MSO_SHAPE.RECTANGLE shp = slide.shapes.add_shape(shp_type, Inches(x), Inches(y), Inches(w), Inches(h)) if fill is None: shp.fill.background() else: shp.fill.solid(); shp.fill.fore_color.rgb = fill if line is None: shp.line.fill.background() else: shp.line.color.rgb = line; shp.line.width = Pt(line_w) shp.shadow.inherit = False if rounded: try: shp.adjustments[0] = radius except Exception: pass return shp def text(slide, x, y, w, h, runs, size=14, bold=False, color=BLACK, align=PP_ALIGN.LEFT, font=FONT, anchor=MSO_ANCHOR.TOP, leading=1.0, space_after=0): box = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)) tf = box.text_frame tf.word_wrap = True tf.vertical_anchor = anchor tf.margin_left = 0; tf.margin_right = 0; tf.margin_top = 0; tf.margin_bottom = 0 if isinstance(runs, str): runs = [runs] for i, item in enumerate(runs): p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() p.alignment = align p.line_spacing = leading if space_after: p.space_after = Pt(space_after) if isinstance(item, str): item = {"t": item} elif isinstance(item, tuple): item = {"t": item[0], **item[1]} r = p.add_run(); r.text = item["t"] r.font.size = Pt(item.get("size", size)) r.font.bold = item.get("bold", bold) r.font.italic = item.get("italic", False) r.font.name = item.get("font", font) r.font.color.rgb = item.get("color", color) return box def header(slide, kicker, title, dark=False): text(slide, 0.55, 0.42, 12.2, 0.3, kicker.upper(), size=11, bold=True, color=RED, font=FONT) text(slide, 0.55, 0.75, 12.2, 0.75, title, size=30, bold=True, color=WHITE if dark else BLACK) logo = LOGO_RED if not dark else LOGO_WHITE if os.path.exists(logo): slide.shapes.add_picture(logo, Inches(12.35), Inches(0.25), Inches(0.62), Inches(0.76)) def footer(slide, num): text(slide, 0.55, 7.08, 9.0, 0.3, "Ansible — moderne Automatisierung", size=8, color=MIDGRAY) text(slide, 12.2, 7.08, 0.6, 0.3, str(num), size=9, color=MIDGRAY, align=PP_ALIGN.RIGHT) def _set_run(r, text, font_size, color, bold=False): r.text = text r.font.size = Pt(font_size) r.font.name = FONT_MONO r.font.color.rgb = color r.font.bold = bold def _style_kv(p, text, font_size): if ":" in text: key, colon, rest = text.partition(":") is_mod = ("ansible." in key) or ("community." in key) _set_run(p.add_run(), key, font_size, CODE_MOD if is_mod else CODE_KEY, bold=is_mod) _set_run(p.add_run(), colon + rest, font_size, WHITE) else: _set_run(p.add_run(), text, font_size, WHITE) def _style_code_line(p, line, font_size): """Syntax-Einfärbung unter Erhalt der Einrückung.""" m = re.match(r'^(\s*)(.*)$', line) lead, rest = m.group(1), m.group(2) if lead: _set_run(p.add_run(), lead, font_size, WHITE) if rest.startswith("#"): _set_run(p.add_run(), rest, font_size, CODE_CMT) return # FQCN (namespace.collection.modul) -> rot, fett if re.match(r'^[A-Za-z0-9_]+\.[A-Za-z0-9_]+\.[A-Za-z0-9_]+$', rest): _set_run(p.add_run(), rest, font_size, CODE_MOD, bold=True) return if rest.startswith("- "): _set_run(p.add_run(), "- ", font_size, CODE_MOD) _style_kv(p, rest[2:], font_size) return _style_kv(p, rest, font_size) def code_block(slide, x, y, w, lines, font_size=11, title=None, title_color=RED): """Dunkler Code-Block mit automatischer Höhe (kein Überlauf).""" line_h = font_size * 1.32 / 72.0 pad_top = 0.18 pad_bot = 0.16 title_h = 0.38 if title else 0.0 n = len(lines) if lines else 1 h = title_h + n * line_h + pad_top + pad_bot rect(slide, x, y, w, h, CODE_BG, line=BORDER, line_w=0.75, rounded=True, radius=0.04) if title: rect(slide, x, y, w, 0.32, CODE_BG, line=BORDER, line_w=0.75, rounded=True, radius=0.14) text(slide, x + 0.25, y + 0.035, w - 0.5, 0.25, title, size=9.5, bold=True, color=title_color, font=FONT_MONO) y += title_h tb = slide.shapes.add_textbox(Inches(x + 0.22), Inches(y + pad_top), Inches(w - 0.44), Inches(h - pad_top - pad_bot)) tf = tb.text_frame; tf.word_wrap = True tf.margin_left = 0; tf.margin_right = 0; tf.margin_top = 0; tf.margin_bottom = 0 for i, line in enumerate(lines): p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() p.line_spacing = 1.05 if line.strip() == "": _set_run(p.add_run(), " ", font_size, WHITE) continue _style_code_line(p, line, font_size) return h def card(slide, x, y, w, h, icon, title, body, accent=RED): rect(slide, x, y, w, h, CARD, line=BORDER, line_w=1.0, rounded=True, radius=0.06) if icon: circ = slide.shapes.add_shape(MSO_SHAPE.OVAL, Inches(x + 0.2), Inches(y + 0.2), Inches(0.44), Inches(0.44)) circ.fill.solid(); circ.fill.fore_color.rgb = accent circ.line.fill.background(); circ.shadow.inherit = False text(slide, x + 0.2, y + 0.2, 0.44, 0.44, icon, size=14, bold=True, color=WHITE, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE) text(slide, x + 0.78, y + 0.2, w - 0.95, 0.4, title, size=15, bold=True, color=BLACK) else: text(slide, x + 0.22, y + 0.2, w - 0.44, 0.4, title, size=15, bold=True, color=BLACK) text(slide, x + 0.22, y + 0.72, w - 0.44, h - 0.85, body, size=11, color=GRAY, leading=1.05) def kv_rows(slide, x, y, w, rows, key_w, name_size=11, desc_size=10.5, gap=0.42): """Monospace-Schlüssel + Beschreibung in Zeilen. rows = [(key, desc), ...]""" yy = y for key, desc in rows: text(slide, x, yy, key_w, 0.3, key, size=name_size, bold=True, color=DARKRED, font=FONT_MONO) text(slide, x + key_w, yy, w - key_w, 0.4, desc, size=desc_size, color=GRAY, leading=1.0) yy += gap # --------------------------------------------------------------------------- # Diagramm erzeugen # --------------------------------------------------------------------------- make_architecture_diagram(ARCH) # =========================================================================== # FOLIE 1 — Titel (dunkel) # =========================================================================== s = new_slide(BLACK) rect(s, 9.0, 0, 4.333, 7.5, RED, rounded=False) if os.path.exists(LOGO_WHITE): s.shapes.add_picture(LOGO_WHITE, Inches(0.9), Inches(1.35), Inches(2.1), Inches(2.59)) text(s, 0.9, 4.15, 7.5, 0.4, "MODERNE IT-AUTOMATISIERUNG", size=14, bold=True, color=RED) text(s, 0.9, 4.55, 8.0, 1.3, "Ansible", size=60, bold=True, color=WHITE) text(s, 0.9, 5.75, 7.5, 0.4, "Agentenlos · Idempotent · Deklarativ · YAML", size=16, color=RGBColor(0xD8, 0xD8, 0xD8)) text(s, 0.9, 6.55, 7.5, 0.4, "Was es ist · Aufbau · Syntax · Ad-hoc · Jinja2 · Facts · FQCN · Collections · Rollen", size=12, color=RGBColor(0x9A, 0x9A, 0x9A)) text(s, 9.5, 1.6, 3.3, 0.3, "INHALT", size=12, bold=True, color=WHITE) agenda = [ "1. Was ist Ansible?", "2. Aufbau & Architektur", "3. YAML, Playbooks & Ad-hoc", "4. Module, Jinja2 & Facts", "5. Variablen & Auflösung", "6. FQCN & Collections", "7. Rollen & Praxis", ] text(s, 9.5, 2.15, 3.3, 4.0, agenda, size=14, color=WHITE, leading=1.5) # =========================================================================== # FOLIE 2 — Was ist Ansible? # =========================================================================== s = new_slide(LIGHT) header(s, "Grundlagen", "Was ist Ansible?") rect(s, 0.55, 1.55, 8.0, 1.15, BLACK, rounded=True, radius=0.08) text(s, 0.85, 1.75, 7.4, 0.75, "Ansible ist ein Open-Source-Tool zur Automatisierung von IT-Aufgaben — " "Provisionierung, Konfigurationsmanagement und Anwendungs-Deployment.", size=15, color=WHITE, leading=1.15) card(s, 0.55, 3.0, 4.0, 1.8, "A", "Agentenlos", "Kein Agent auf Zielsystemen nötig. Kommunikation über SSH (Linux) bzw. WinRM (Windows).") card(s, 4.75, 3.0, 4.0, 1.8, "P", "Push-basiert", "Der Control Node stößt Änderungen aktiv an. Kein Pull-Mechanismus, kein ständiger Dienst.") card(s, 0.55, 5.0, 4.0, 1.8, "D", "Deklarativ", "Du beschreibst den gewünschten Ziel-Zustand in YAML — nicht die einzelnen Schritte.") card(s, 4.75, 5.0, 4.0, 1.8, "I", "Idempotent", "Mehrfaches Ausführen führt zum selben Ergebnis — keine doppelten Änderungen.") if os.path.exists(LOGO_RED): s.shapes.add_picture(LOGO_RED, Inches(9.4), Inches(1.9), Inches(1.7), Inches(2.09)) text(s, 9.15, 4.1, 3.6, 0.3, "KEY FACTS", size=11, bold=True, color=RED) facts = [("Red Hat / IBM", "Open-Source-Projekt"), ("Python", "Implementiert in Python"), ("4000+", "Module verfügbar"), ("2012", "Erstveröffentlichung")] yy = 4.5 for big, small in facts: text(s, 9.15, yy, 1.8, 0.5, big, size=22, bold=True, color=BLACK) text(s, 10.95, yy + 0.08, 2.2, 0.5, small, size=11, color=GRAY, leading=1.0) yy += 0.62 footer(s, 2) # =========================================================================== # FOLIE 3 — Aufbau & Architektur # =========================================================================== s = new_slide(WHITE) header(s, "Architektur", "Wie ist Ansible aufgebaut?") if os.path.exists(ARCH): from PIL import Image as _Img _dw, _dh = _Img.open(ARCH).size _ar = _dw / _dh _w = 8.1 _h = _w / _ar s.shapes.add_picture(ARCH, Inches(0.55), Inches(1.55), Inches(_w), Inches(_h)) text(s, 8.95, 1.6, 3.9, 0.3, "KOMPONENTEN", size=11, bold=True, color=RED) comp = [ ("Control Node", "Rechner, auf dem Ansible installiert ist; enthält Playbooks & Inventory."), ("Managed Nodes", "Zielsysteme, die über SSH/WinRM erreicht werden."), ("Inventory", "Liste aller Hosts und Gruppen (INI oder YAML)."), ("Module", "Bausteine, die eine Aufgabe ausführen (z.B. copy, apt)."), ] yy = 2.0 for t, d in comp: text(s, 8.95, yy, 3.9, 0.3, t, size=13, bold=True, color=BLACK) text(s, 8.95, yy + 0.28, 3.9, 0.9, d, size=10.5, color=GRAY, leading=1.05) yy += 1.22 footer(s, 3) # =========================================================================== # FOLIE 4 — Kernkonzepte # =========================================================================== s = new_slide(LIGHT) header(s, "Architektur", "Vier Kernkonzepte") card(s, 0.55, 1.6, 6.0, 2.4, "1", "Playbooks", "YAML-Dateien, die beschreiben, was auf welchen Hosts passieren soll. " "Ein Playbook enthält ein oder mehrere Plays mit Tasks (Aufgaben).") card(s, 6.85, 1.6, 6.0, 2.4, "2", "Inventory", "Die Host-Liste. Definiert Server, Gruppen und Variablen. Formate: INI oder YAML. " "Auch dynamisch (Cloud, VMware) möglich.") card(s, 0.55, 4.3, 6.0, 2.4, "3", "Module", "Kleine Programme, die eine konkrete Aktion ausführen: Paket installieren, Datei kopieren, " "Dienst starten. Werden über FQCN referenziert.") card(s, 6.85, 4.3, 6.0, 2.4, "4", "Rollen & Collections", "Rollen bündeln Tasks/Templates/Variablen zu wiederverwendbaren Einheiten. " "Collections verteilen Module, Rollen & Plugins.") footer(s, 4) # =========================================================================== # FOLIE 5 — YAML-Syntax # =========================================================================== s = new_slide(WHITE) header(s, "Syntax", "YAML — die Sprache von Ansible") text(s, 0.55, 1.6, 5.6, 0.3, "GRUNDREGELN", size=11, bold=True, color=RED) rules = [ ("Schlüssel: Wert", "Einfache Zuweisungen, getrennt durch Doppelpunkt."), ("Einrückung", "Struktur über Leerzeichen (2 Spaces, KEINE Tabs)."), ("Listen", "Aufzählungen mit Bindestrich (-)."), ("Lesbar", "YAML ist menschenlesbar, kein Code-Zwang."), ] yy = 2.0 for t, d in rules: text(s, 0.55, yy, 5.6, 0.3, t, size=13, bold=True, color=BLACK) text(s, 0.55, yy + 0.28, 5.6, 0.7, d, size=11, color=GRAY, leading=1.05) yy += 1.05 code_block(s, 6.5, 1.6, 6.3, [ "# Inventory (hosts.yml)", "all:", " hosts:", " webserver:", " ansible_host: 192.168.1.10", " database:", " ansible_host: 192.168.1.20", "", "# Schlüssel-Wert", "name: \"Mein Webserver\"", "paket: nginx", "", "# Liste", "pakete:", " - nginx", " - git", " - curl", ], font_size=11) footer(s, 5) # =========================================================================== # FOLIE 6 — Playbook & Tasks # =========================================================================== s = new_slide(WHITE) header(s, "Syntax", "Ein Playbook in der Praxis") text(s, 0.55, 1.55, 5.6, 1.4, [("Ein Playbook ", {}), ("definiert, was passieren soll. ", {}), ("plays", {"bold": True, "color": RED}), (" wirken auf ", {}), ("hosts", {"bold": True, "color": RED}), (", jede ", {}), ("task", {"bold": True, "color": RED}), (" ruft genau ein ", {}), ("Modul", {"bold": True, "color": RED}), (" auf.", {})], size=12.5, color=BLACK, leading=1.2) code_block(s, 6.5, 1.55, 6.3, [ "---", "- name: Webserver konfigurieren", " hosts: webserver", " become: true", "", " tasks:", " - name: nginx installieren", " ansible.builtin.apt:", " name: nginx", " state: present", "", " - name: Dienst starten", " ansible.builtin.service:", " name: nginx", " state: started", " enabled: true", ], font_size=10.5, title="site.yml") footer(s, 6) # =========================================================================== # FOLIE 7 — Module # =========================================================================== s = new_slide(LIGHT) header(s, "Syntax", "Module — die Arbeitspferde") text(s, 0.55, 1.6, 12.2, 0.8, "Ein Modul ist ein kleines, eigenständiges Programm, das eine Aufgabe auf dem " "Zielsystem ausführt. Ansible bringt hunderte Module mit — von Paketverwaltung " "über Datei-Operationen bis Cloud-Anbindung.", size=13, color=BLACK, leading=1.2) mods = [ ("apt / dnf", "Pakete installieren (Debian/RedHat)"), ("copy / file", "Dateien kopieren & Rechte setzen"), ("service", "Dienste starten / stoppen / aktivieren"), ("template", "Jinja2-Templates rendern"), ("user / group", "Benutzer & Gruppen verwalten"), ("command / shell", "Beliebige Befehle ausführen"), ] for i, (t, d) in enumerate(mods): col = i % 3; row = i // 3 card(s, 0.55 + col * 4.2, 2.6 + row * 1.75, 3.95, 1.5, "•", t, d) text(s, 0.55, 6.15, 12.2, 0.6, [("Wichtig: Module werden heute über den ", {}), ("FQCN", {"bold": True, "color": RED}), (" referenziert (z.B. ", {}), ("ansible.builtin.apt", {"font": FONT_MONO, "color": DARKRED}), (").", {})], size=12, color=BLACK) footer(s, 7) # =========================================================================== # FOLIE 8 — Ad-hoc-Befehle # =========================================================================== s = new_slide(WHITE) header(s, "Syntax", "Ad-hoc-Befehle — Ansible ohne Playbook") text(s, 0.55, 1.55, 5.7, 1.25, [("Mit dem ", {}), ("ansible", {"font": FONT_MONO, "color": DARKRED, "bold": True}), ("-CLI lassen sich einzelne Module direkt ausführen — ohne Playbook. Ideal " "für schnelle Checks und einmalige Aktionen.", {})], size=12.5, color=BLACK, leading=1.2) text(s, 0.55, 2.85, 5.7, 0.3, "AUFBAU", size=11, bold=True, color=RED) code_block(s, 0.55, 3.2, 5.7, [ "ansible -m ", " -a \"\"", ], font_size=11, title="Kommando") text(s, 0.55, 4.55, 5.7, 0.3, "WICHTIGE OPTIONEN", size=11, bold=True, color=RED) kv_rows(s, 0.55, 4.95, 2.6, [ ("-m", "Modul"), ("-a", "Argumente"), ("-i", "Inventory"), ], key_w=0.55, gap=0.4) kv_rows(s, 3.4, 4.95, 2.6, [ ("-b", "become (sudo)"), ("-u", "Benutzer"), ], key_w=0.55, gap=0.4) code_block(s, 6.6, 1.55, 6.2, [ "# Erreichbarkeit testen", "ansible all -m ping", "", "# Facts anzeigen", "ansible webserver -m setup", "", "# Paket installieren", "ansible webserver -m apt", " -a \"name=nginx state=present\"", "", "# Dienst neu starten", "ansible webserver -m service", " -a \"name=nginx state=restarted\"", ], font_size=10, title="Beispiele") footer(s, 8) # =========================================================================== # FOLIE 9 — Jinja2 (Templating) # =========================================================================== s = new_slide(WHITE) header(s, "Syntax", "Jinja2 — dynamische Inhalte") text(s, 0.55, 1.55, 12.2, 0.7, "Jinja2 ist die Template-Engine von Ansible. Damit werden Variablen aufgelöst " "und Dateien dynamisch erzeugt — in Playbooks, aber vor allem in Templates (.j2).", size=13, color=BLACK, leading=1.15) card(s, 0.55, 2.4, 4.0, 1.7, "1", "Variable auflösen", [("{{ variable }}", {"font": FONT_MONO, "color": DARKRED, "bold": True}), (" setzt den Wert einer Variablen an die Stelle im Text ein.", {})]) card(s, 4.75, 2.4, 4.0, 1.7, "2", "Filter anwenden", [("{{ wert | filter }}", {"font": FONT_MONO, "color": DARKRED, "bold": True}), (" transformiert Werte (z.B. ", {}), ("upper", {"font": FONT_MONO}), (", ", {}), ("default", {"font": FONT_MONO}), (").", {})]) card(s, 8.95, 2.4, 3.85, 1.7, "3", "Template-Dateien", [("Dateien mit Endung ", {}), (".j2", {"font": FONT_MONO, "color": DARKRED, "bold": True}), (" werden vom Modul ", {}), ("template", {"font": FONT_MONO}), (" gerendert.", {})]) code_block(s, 0.55, 4.35, 6.0, [ "# Variablen auflösen", "msg: \"Hallo {{ benutzer }}\"", "", "# Filter anwenden", "name: \"{{ name | upper }}\"", "port: \"{{ port | default(80) }}\"", ], font_size=10.5, title="Variablen & Filter") code_block(s, 6.85, 4.35, 6.0, [ "# nginx.conf.j2", "server {", " listen {{ nginx_port }};", " server_name {{ domain }};", " root {{ web_root }};", "}", ], font_size=10.5, title="nginx.conf.j2") footer(s, 9) # =========================================================================== # FOLIE 10 — Gather Facts # =========================================================================== s = new_slide(WHITE) header(s, "Facts", "Gather Facts — Systeminformationen") text(s, 0.55, 1.5, 12.2, 0.7, [("Ansible sammelt beim Start automatisch ", {}), ("Facts", {"bold": True}), (" (Systeminfos) über jeden Host — über das Modul ", {}), ("setup", {"font": FONT_MONO, "color": DARKRED}), (", standardmäßig ", {}), ("gather_facts: true", {"font": FONT_MONO, "color": DARKRED}), (".", {})], size=13, color=BLACK, leading=1.15) # Links: Facts als 2-Spalten-Raster (Name oben, Beschreibung darunter) text(s, 0.55, 2.4, 6.6, 0.3, "WAS MAN AUSLESEN KANN", size=11, bold=True, color=RED) facts = [ ("ansible_os_family", "Betriebssystem-Familie"), ("ansible_distribution", "z.B. Ubuntu, Debian"), ("ansible_hostname", "Hostname des Systems"), ("ansible_default_ipv4.address", "IP-Adresse"), ("ansible_memtotal_mb", "Arbeitsspeicher (MB)"), ("ansible_processor_vcpus", "CPU-Kerne"), ] for i, (name, desc) in enumerate(facts): col = i % 2 row = i // 2 cx = 0.55 + col * 3.3 cy = 2.9 + row * 1.3 text(s, cx, cy, 3.2, 0.35, name, size=11.5, bold=True, color=DARKRED, font=FONT_MONO) text(s, cx, cy + 0.38, 3.2, 0.5, desc, size=11, color=GRAY, leading=1.0) # Rechts: ein einzelner, klarer Code-Block (Zugriff + Custom Facts) code_block(s, 7.25, 2.4, 5.55, [ "# Zugriff im Playbook", "- name: OS anzeigen", " ansible.builtin.debug:", " msg: \"{{ ansible_distribution }}\"", "", "# Fakten deaktivieren (schneller)", "gather_facts: false", "", "# Eigene Facts setzen:", "# Datei: /etc/ansible/facts.d/app.fact", "# Abruf: {{ ansible_local.app.version }}", ], font_size=11, title="Zugriff & Custom Facts") footer(s, 10) # =========================================================================== # FOLIE 11 — Variablen & Auflösung (Präzedenz) # =========================================================================== s = new_slide(LIGHT) header(s, "Variablen", "Variablen definieren & auflösen") text(s, 0.55, 1.55, 5.7, 0.3, "WO KOMMEN SIE HER?", size=11, bold=True, color=RED) sources = [ ("Inventory", "host_vars / group_vars"), ("Playbook", "vars: / vars_files:"), ("Rollen", "vars/ & defaults/"), ("Facts", "automatisch gesammelt"), ("Laufzeit", "register & set_fact"), ("CLI", "Extra-Variablen (-e)"), ] yy = 2.0 for t, d in sources: text(s, 0.55, yy, 2.2, 0.3, t, size=12.5, bold=True, color=BLACK) text(s, 2.9, yy, 3.3, 0.3, d, size=11, color=GRAY, font=FONT_MONO) yy += 0.55 text(s, 6.85, 1.55, 6.0, 0.3, "PRÄZEDENZ — WER GEWINNT?", size=11, bold=True, color=RED) text(s, 6.85, 1.9, 6.0, 0.3, "Oben gewinnt — von höchster zu niedrigster Priorität:", size=10, color=GRAY) prec = [ ("Extra-Variablen", "ansible-playbook ... -e key=wert"), ("Task-/Block-Variablen", "vars: direkt an der Aufgabe"), ("set_fact / register", "Werte aus der Laufzeit"), ("Rollen-Variablen", "roles/.../vars/main.yml"), ("Playbook-Variablen", "vars: im Play"), ("host_vars / group_vars", "aus dem Inventory"), ("Rollen-Defaults", "roles/.../defaults/main.yml"), ] yy = 2.25 for i, (t, d) in enumerate(prec, 1): rect(s, 6.85, yy, 0.4, 0.4, RED, rounded=True, radius=0.3) text(s, 6.85, yy + 0.03, 0.4, 0.34, str(i), size=13, bold=True, color=WHITE, align=PP_ALIGN.CENTER) text(s, 7.4, yy + 0.02, 3.6, 0.34, t, size=12, bold=True, color=BLACK) text(s, 11.0, yy + 0.02, 1.85, 0.34, d, size=9, color=GRAY, font=FONT_MONO) yy += 0.5 text(s, 6.85, yy + 0.1, 6.0, 0.6, [("Faustregel: ", {"bold": True, "color": RED}), ("je spezifischer die Quelle, desto höher die Priorität.", {})], size=11, color=GRAY) footer(s, 11) # =========================================================================== # FOLIE 12 — FQCN (Definition) # =========================================================================== s = new_slide(WHITE) header(s, "FQCN", "FQCN — Fully Qualified Collection Name") rect(s, 0.55, 1.55, 12.2, 0.95, LIGHT, line=BORDER, line_w=1.0, rounded=True, radius=0.1) text(s, 0.85, 1.77, 11.6, 0.55, [("FQCN = ", {"bold": True, "color": RED}), ("eindeutige, vollständige Referenz auf ein Modul oder Plugin.", {})], size=16, color=BLACK) text(s, 0.85, 2.75, 11.6, 0.6, "namespace · collection · modul", size=28, bold=True, color=BLACK) code_block(s, 0.55, 3.6, 12.2, [ "# FQCN = namespace.collection.modul", "", "ansible.builtin.copy", "", "# ansible -> namespace (Anbieter)", "# builtin -> collection (Paket)", "# copy -> modul (die Funktion)", ], font_size=12, title="Beispiel") text(s, 0.55, 6.15, 12.2, 0.5, [("NICHT verwechseln mit ", {"bold": True}), ("FQDN", {"font": FONT_MONO, "color": DARKRED, "bold": True}), (" (Fully Qualified Domain Name = Hostname)! Der FQCN macht eindeutig, " "aus welcher Collection ein Modul stammt.", {})], size=12.5, color=BLACK) footer(s, 12) # =========================================================================== # FOLIE 13 — FQCN (Beispiele & Warum) # =========================================================================== s = new_slide(WHITE) header(s, "FQCN", "FQCN in der Praxis") text(s, 0.55, 1.6, 6.5, 0.4, "GÄNGIGE FQCN-BEISPIELE", size=11, bold=True, color=RED) ex = [ ("ansible.builtin.copy", "Datei kopieren"), ("ansible.builtin.apt", "Paket installieren"), ("ansible.builtin.service", "Dienst verwalten"), ("ansible.builtin.template", "Template rendern"), ("ansible.posix.firewalld", "Firewall (Collection)"), ("community.general.git_config", "Git (Community)"), ] yy = 2.1 for fqcn, d in ex: rect(s, 0.55, yy, 3.7, 0.5, LIGHT, line=BORDER, line_w=0.75, rounded=True, radius=0.14) text(s, 0.8, yy + 0.1, 3.4, 0.3, fqcn, size=11, bold=True, color=DARKRED, font=FONT_MONO) text(s, 4.45, yy + 0.1, 2.6, 0.3, d, size=11.5, color=BLACK) yy += 0.62 text(s, 7.35, 1.6, 5.4, 0.4, "WARUM FQCN?", size=11, bold=True, color=RED) rect(s, 7.35, 2.1, 5.45, 4.35, LIGHT, line=BORDER, line_w=1.0, rounded=True, radius=0.05) text(s, 7.65, 2.35, 4.9, 3.9, [ ("Seit Ansible 2.10 sind Module in ", {}), ("Collections", {"bold": True}), (" gebündelt.\n\n", {}), ("Der FQCN macht eindeutig, ", {}), ("welches Modul", {"bold": True}), (" gemeint ist.\n\n", {}), ("Er vermeidet ", {}), ("Namenskollisionen", {"bold": True}), (" — mehrere Collections können ein gleichnamiges Modul anbieten.\n\n", {}), ("Faustregel: ", {"bold": True, "color": RED}), ("immer den FQCN verwenden, nie die Kurzform.", {}), ], size=12.5, color=BLACK, leading=1.25) footer(s, 13) # =========================================================================== # FOLIE 14 — Collections (Definition) # =========================================================================== s = new_slide(LIGHT) header(s, "Collections", "Was sind Collections?") rect(s, 0.55, 1.6, 12.2, 1.1, BLACK, rounded=True, radius=0.08) text(s, 0.85, 1.8, 11.6, 0.75, "Eine Collection ist das Verteilungsformat für Ansible-Inhalte. Sie bündelt " "Module, Plugins, Rollen, Playbooks und Dokumentation in einem Paket.", size=15, color=WHITE, leading=1.15) cols = [ ("Module", "Aktions-Bausteine (z.B. apt, firewalld)"), ("Plugins", "Erweiterungen: Inventory, Connection, Filter"), ("Rollen", "Wiederverwendbare Aufgaben-Sammlungen"), ("Playbooks", "Fertige Abläufe als Vorlage"), ("Dokumentation", "Beschreibung & Beispiele (docs/)"), ("Test", "Tests & CI für die Collection"), ] for i, (t, d) in enumerate(cols): col = i % 3; row = i // 3 card(s, 0.55 + col * 4.2, 3.0 + row * 1.6, 3.95, 1.35, "•", t, d) footer(s, 14) # =========================================================================== # FOLIE 15 — Collections (Struktur & Galaxy) # =========================================================================== s = new_slide(WHITE) header(s, "Collections", "Collections nutzen") text(s, 0.55, 1.55, 5.8, 0.3, "BEZUGSQUELLE", size=11, bold=True, color=RED) text(s, 0.55, 1.9, 5.8, 0.85, [("Ansible Galaxy", {"bold": True}), (" (galaxy.ansible.com) ist der zentrale Hub — vergleichbar mit einem " "Paket-Manager für Ansible-Inhalte.", {})], size=12, color=BLACK, leading=1.15) text(s, 0.55, 2.85, 5.8, 0.3, "INSTALLATION", size=11, bold=True, color=RED) code_block(s, 0.55, 3.25, 5.8, [ "# Installation", "ansible-galaxy collection", " install ansible.posix", "", "# requirements.yml", "collections:", " - name: ansible.posix", " version: '>=1.5.0'", ], font_size=11, title="galaxy") text(s, 6.85, 1.55, 5.9, 0.3, "NAMENSSCHEMA", size=11, bold=True, color=RED) text(s, 6.85, 1.9, 5.9, 0.45, [("namespace", {"font": FONT_MONO, "color": DARKRED, "bold": True}), (" . ", {"color": BLACK, "bold": True}), ("name", {"font": FONT_MONO, "color": DARKRED, "bold": True}), (" → z.B. ", {}), ("ansible.posix", {"font": FONT_MONO, "color": DARKRED, "bold": True})], size=14) text(s, 6.85, 2.5, 5.9, 0.3, "AUFBAU EINER COLLECTION", size=11, bold=True, color=RED) code_block(s, 6.85, 2.9, 5.9, [ "ansible-posix/", " plugins/modules/", " plugins/inventory/", " roles/", " playbooks/", " docs/", " galaxy.yml", ], font_size=11.5, title="ansible.posix") footer(s, 15) # =========================================================================== # FOLIE 16 — Rollen (Konzept) # =========================================================================== s = new_slide(LIGHT) header(s, "Rollen", "Wie funktionieren Rollen?") rect(s, 0.55, 1.6, 12.2, 1.05, BLACK, rounded=True, radius=0.08) text(s, 0.85, 1.78, 11.6, 0.75, "Eine Rolle ist eine wiederverwendbare, in sich geschlossene Einheit von " "Aufgaben. Sie kapselt Tasks, Variablen, Templates und Handler zu einer " "logischen Funktion — z.B. „installiere & konfiguriere einen Webserver“.", size=14.5, color=WHITE, leading=1.15) points = [ ("Wiederverwendbar", "Einmal schreiben, in vielen Playbooks nutzen."), ("Gekapselt", "Eigene Variablen, Templates & Dateien pro Rolle."), ("Teilbar", "Über Collections & Galaxy mit anderen teilen."), ("Strukturiert", "Klare Verzeichnis-Struktur (tasks, handlers, …)."), ] for i, (t, d) in enumerate(points): col = i % 2; row = i // 2 card(s, 0.55 + col * 6.2, 3.0 + row * 1.8, 5.95, 1.55, "✓", t, d) footer(s, 16) # =========================================================================== # FOLIE 17 — Rollen (Verzeichnisstruktur) # =========================================================================== s = new_slide(WHITE) header(s, "Rollen", "Die Verzeichnisstruktur einer Rolle") text(s, 0.55, 1.55, 5.5, 0.3, "ANATOMIE", size=11, bold=True, color=RED) text(s, 0.55, 1.9, 5.5, 0.9, "Eine Rolle folgt einem festen Aufbau. Jede Datei hat eine klar definierte " "Aufgabe — Ansible lädt sie automatisch.", size=12, color=BLACK, leading=1.15) expl = [ ("tasks/main.yml", "Hauptaufgaben der Rolle"), ("handlers/main.yml", "Neustart-/Trigger-Aktionen"), ("templates/", "Jinja2-Templates (.j2)"), ("files/", "Statische Dateien (Copy-Quelle)"), ("vars/main.yml", "Variablen (hohe Priorität)"), ("defaults/main.yml", "Standard-Variablen (niedrig)"), ("meta/main.yml", "Abhängigkeiten & Infos"), ] yy = 2.6 for f, d in expl: text(s, 0.55, yy, 3.0, 0.3, f, size=11, bold=True, color=DARKRED, font=FONT_MONO) text(s, 3.7, yy, 2.6, 0.5, d, size=10.5, color=GRAY, leading=1.0) yy += 0.55 code_block(s, 6.7, 1.55, 6.1, [ "roles/webserver/", " tasks/", " main.yml", " handlers/", " main.yml", " templates/", " files/", " vars/", " main.yml", " defaults/", " main.yml", " meta/", " main.yml", ], font_size=11.5, title="roles/webserver/") footer(s, 17) # =========================================================================== # FOLIE 18 — Praxisbeispiel # =========================================================================== s = new_slide(LIGHT) header(s, "Praxis", "Alles zusammen: Playbook mit Rolle") text(s, 0.55, 1.55, 5.3, 0.3, "ZUSAMMENSPIEL", size=11, bold=True, color=RED) text(s, 0.55, 1.9, 5.3, 2.8, [("Das Playbook referenziert die Rolle ", {}), ("webserver", {"font": FONT_MONO, "color": DARKRED, "bold": True}), (". FQCN-Module (", {}), ("ansible.builtin.*", {"font": FONT_MONO, "color": DARKRED}), (") arbeiten im Hintergrund.\n\n", {}), ("1.", {"bold": True, "color": RED}), (" Rolle wird geladen.\n", {}), ("2.", {"bold": True, "color": RED}), (" Aufgaben & Variablen aus der Rolle gelten.\n", {}), ("3.", {"bold": True, "color": RED}), (" Idempotenz verhindert doppelte Arbeit.", {})], size=12, color=BLACK, leading=1.25) code_block(s, 6.15, 1.55, 6.6, [ "---", "- name: Webserver ausrollen", " hosts: webserver", " become: true", "", " roles:", " - webserver", "", " vars:", " nginx_port: 8080", ], font_size=11, title="deploy.yml") footer(s, 18) # =========================================================================== # FOLIE 19 — Zusammenfassung & Quellen # =========================================================================== s = new_slide(LIGHT) header(s, "Fazit", "Zusammenfassung") takeaways = [ ("Was", "Agentenloses, deklaratives Automatisierungs-Tool (SSH, YAML)."), ("Aufbau", "Control Node, Managed Nodes, Inventory, Module, Playbooks."), ("Syntax", "YAML + Jinja2 ({{ variable }}) für dynamische Inhalte."), ("Ad-hoc", "ansible -m für schnelle Einzelaktionen."), ("Facts", "Systeminfos werden automatisch gesammelt (gather_facts)."), ("Variablen", "Auflösung nach Präzedenz — Extra-Vars gewinnen, Defaults verlieren."), ("FQCN", "Eindeutige Modul-Referenz: namespace.collection.modul."), ("Collections", "Pakete mit Modulen, Rollen & Plugins (Galaxy)."), ("Rollen", "Wiederverwendbare, gekapselte Aufgaben-Einheiten."), ] yy = 1.6 for t, d in takeaways: text(s, 0.75, yy, 2.0, 0.4, t, size=13, bold=True, color=RED) text(s, 2.85, yy + 0.02, 6.7, 0.5, d, size=11.5, color=BLACK, leading=1.05) yy += 0.52 rect(s, 9.8, 1.7, 2.95, 4.6, LIGHT, line=BORDER, line_w=1.0, rounded=True, radius=0.06) if os.path.exists(LOGO_RED): s.shapes.add_picture(LOGO_RED, Inches(10.35), Inches(1.95), Inches(1.2), Inches(1.48)) text(s, 10.05, 3.6, 2.4, 0.3, "QUELLEN", size=11, bold=True, color=RED, align=PP_ALIGN.CENTER) text(s, 10.05, 3.95, 2.5, 1.9, ["docs.ansible.com", "galaxy.ansible.com", "github.com/ansible", "Logo: Wikimedia Commons"], size=9.5, color=GRAY, leading=1.4) footer(s, 19) # --------------------------------------------------------------------------- # Speichern # --------------------------------------------------------------------------- OUT = os.path.join(BASE, "ansible_praesentation.pptx") prs.save(OUT) print(f"OK — gespeichert: {OUT}") print(f"Folien: {len(prs.slides)}")