Files
ansible_pr-sentation/create_presentation.py

1158 lines
47 KiB
Python

#!/usr/bin/env python3
"""
Moderne Ansible-Präsentation — vollständiger Generator (v3).
23 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)
- Ansible Vault (verschlüsselte Variablen & Dateien)
- 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__))
DOCS = os.path.join(BASE, "Dokumentation")
ASSETS = os.path.join(BASE, "assets")
os.makedirs(ASSETS, exist_ok=True)
os.makedirs(DOCS, 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(0xF8, 0xF5, 0xF5) # warmgetöntes Off-White (Rot-Stich)
REDTINT = RGBColor(0xFB, 0xE9, 0xE9) # dezente Rot-Tinktur für Akzentboxen
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 grad_rect(slide, x, y, w, h, c1, c2, angle=0.0, rounded=False, radius=0.08):
"""Rechteck mit linearem Farbverlauf (modern, in Markenfarben)."""
shp = rect(slide, x, y, w, h, None, rounded=rounded, radius=radius)
f = shp.fill
f.gradient()
stops = f.gradient_stops
stops[0].color.rgb = c1
stops[1].color.rgb = c2
try:
f.gradient_angle = angle
except Exception:
pass
shp.line.fill.background()
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)
# item: str | tuple | dict (ein Run) ODER list (mehrere Runs inline)
subs = item if isinstance(item, list) else [item]
for s in subs:
if isinstance(s, str):
s = {"t": s}
elif isinstance(s, tuple):
s = {"t": s[0], **s[1]}
r = p.add_run(); r.text = s["t"]
r.font.size = Pt(s.get("size", size))
r.font.bold = s.get("bold", bold)
r.font.italic = s.get("italic", False)
r.font.name = s.get("font", font)
r.font.color.rgb = s.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)
grad_rect(slide, 0.57, 1.42, 1.9, 0.045, DARKRED, RED, angle=0.0)
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))
_page_no = 1 # Titel (Folie 1) zeigt keine sichtbare Nummer
def footer(slide):
global _page_no
_page_no += 1
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(_page_no), 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)
grad_rect(s, 9.0, 0, 4.333, 7.5, RED, DARKRED, angle=90.0)
rect(s, 8.94, 0, 0.06, 7.5, BLACK) # weicher Übergang Anthrazit -> Rot-Panel
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, 0.95, "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. Ansible Vault (Secrets)",
"7. FQCN & Collections",
"8. 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?")
grad_rect(s, 0.55, 1.55, 8.0, 1.15, BLACK, DARKRED, angle=0.0, 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)
# ===========================================================================
# 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)
# ===========================================================================
# 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)
# ===========================================================================
# FOLIE 5 — ansible.cfg (Konfiguration)
# ===========================================================================
s = new_slide(WHITE)
header(s, "Konfiguration", "ansible.cfg — die Konfigurationsdatei")
text(s, 0.55, 1.55, 12.2, 0.7,
"ansible.cfg steuert das Verhalten von Ansible global. INI-Format mit "
"Sektionen wie [defaults], [privilege_escalation] und [ssh_connection].",
size=13, color=BLACK, leading=1.15)
# links: Suchreihenfolge (erste gefundene Datei gewinnt)
text(s, 0.55, 2.4, 5.5, 0.3, "SUCHREIHENFOLGE — WER GEWINNT?", size=11, bold=True, color=RED)
order = [
("ANSIBLE_CONFIG", "Umgebungsvariable"),
("./ansible.cfg", "Projektverzeichnis"),
("~/.ansible.cfg", "Home-Verzeichnis"),
("/etc/ansible/ansible.cfg", "systemweit"),
]
yy = 2.9
for i, (p, d) in enumerate(order, 1):
rect(s, 0.55, yy, 0.4, 0.4, RED, rounded=True, radius=0.3)
text(s, 0.55, yy + 0.03, 0.4, 0.34, str(i), size=13, bold=True,
color=WHITE, align=PP_ALIGN.CENTER)
text(s, 1.15, yy + 0.04, 2.55, 0.32, p, size=11.5, bold=True,
color=DARKRED, font=FONT_MONO)
text(s, 3.85, yy + 0.06, 2.2, 0.5, d, size=10, color=GRAY, leading=1.0)
yy += 0.62
text(s, 0.55, yy + 0.15, 5.5, 0.6,
[[("Erste gefundene Datei gewinnt — ", {"bold": True}),
("kein Zusammenführen!", {"bold": True, "color": RED})]],
size=11.5, color=BLACK)
# rechts: Beispiel mit den wichtigsten Werten
code_block(s, 6.4, 2.4, 6.4, [
"# ansible.cfg (INI-Format)",
"[defaults]",
"inventory = ./inventory.yml",
"remote_user = admin",
"forks = 20",
"host_key_checking = False",
"gather_facts = True",
"roles_path = ./roles",
"",
"[privilege_escalation]",
"become = True",
"become_method = sudo",
"",
"[ssh_connection]",
"pipelining = True",
], font_size=10.5, title="Beispiel")
footer(s)
# ===========================================================================
# FOLIE 6 — ansible.cfg Parameter
# ===========================================================================
s = new_slide(WHITE)
header(s, "Konfiguration", "ansible.cfg — wichtige Parameter")
text(s, 0.55, 1.6, 3.9, 0.35, "[defaults]", size=13, bold=True, color=RED, font=FONT_MONO)
kv_rows(s, 0.55, 2.15, 3.9, [
("inventory", "Pfad zur Inventory-Datei"),
("remote_user", "Standard-SSH-Benutzer"),
("forks", "parallele Prozesse"),
("host_key_checking", "SSH-Key-Prüfung"),
("gather_facts", "Facts sammeln"),
("roles_path", "Suchpfad für Rollen"),
], key_w=1.65, name_size=10.5, desc_size=10, gap=0.46)
text(s, 4.7, 1.6, 3.9, 0.35, "[privilege_escalation]", size=13, bold=True, color=RED, font=FONT_MONO)
kv_rows(s, 4.7, 2.15, 3.9, [
("become", "sudo aktivieren"),
("become_method", "sudo / su"),
("become_user", "Ziel-Benutzer (root)"),
("become_ask_pass", "Passwort abfragen"),
], key_w=1.7, name_size=10.5, desc_size=10, gap=0.46)
text(s, 8.85, 1.6, 3.9, 0.35, "[ssh_connection]", size=13, bold=True, color=RED, font=FONT_MONO)
kv_rows(s, 8.85, 2.15, 3.9, [
("pipelining", "weniger SSH-Roundtrips"),
("ssh_args", "zusätzl. SSH-Argumente"),
("control_path", "ControlMaster-Socket"),
], key_w=1.5, name_size=10.5, desc_size=10, gap=0.46)
footer(s)
# ===========================================================================
# FOLIE 7 — 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)
# ===========================================================================
# FOLIE 8 — 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)
# ===========================================================================
# FOLIE 9 — 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)
# ===========================================================================
# FOLIE 10 — 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 <hosts> -m <modul>",
" -a \"<argumente>\"",
], 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 -b -m apt",
" -a \"name=nginx state=present\"",
"",
"# Dienst neu starten",
"ansible webserver -b -m service",
" -a \"name=nginx state=restarted\"",
], font_size=10, title="Beispiele")
footer(s)
# ===========================================================================
# FOLIE 11 — 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)
# ===========================================================================
# FOLIE 12 — 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}),
(". Zugriff neu in ", {}),
("ansible_facts['…']", {"font": FONT_MONO, "color": DARKRED, "bold": True}),
(" — ohne ", {}),
("ansible_", {"font": FONT_MONO, "color": DARKRED}),
("-Präfix.", {})]],
size=13, color=BLACK, leading=1.15)
# Links: Facts als einzelne Spalte (breite Schlüssel-Spalte -> kein Umbruch)
text(s, 0.55, 2.4, 6.8, 0.3, "WAS MAN AUSLESEN KANN", size=11, bold=True, color=RED)
kv_rows(s, 0.55, 2.9, 6.8, [
("ansible_facts['os_family']", "Betriebssystem-Familie"),
("ansible_facts['distribution']", "z.B. Ubuntu, Debian"),
("ansible_facts['hostname']", "Hostname des Systems"),
("ansible_facts['default_ipv4']", ".['address'] = IP-Adresse"),
("ansible_facts['memtotal_mb']", "Arbeitsspeicher (MB)"),
("ansible_facts['processor_vcpus']", "CPU-Kerne"),
], key_w=3.35, name_size=11.5, desc_size=11, gap=0.6)
# Rechts: ein einzelner, klarer Code-Block (Zugriff + Custom Facts)
code_block(s, 7.55, 2.4, 5.2, [
"# Zugriff im Playbook (neu, empfohlen)",
"- name: OS anzeigen",
" ansible.builtin.debug:",
" msg: \"{{ ansible_facts['distribution'] }}\"",
"",
"# Fakten deaktivieren (schneller)",
"gather_facts: false",
"",
"# Eigene Facts setzen:",
"# Datei: /etc/ansible/facts.d/app.fact",
"# Abruf: {{ ansible_facts['ansible_local'] }}",
"# ['app']['version']",
], font_size=10.5, title="Zugriff & Custom Facts")
footer(s)
# ===========================================================================
# FOLIE 13 — 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)
# ===========================================================================
# FOLIE 14 — Ansible Vault (Konzept)
# ===========================================================================
s = new_slide(WHITE)
header(s, "Vault", "Ansible Vault — Secrets verschlüsseln")
grad_rect(s, 0.55, 1.55, 12.2, 1.0, BLACK, DARKRED, angle=0.0, rounded=True, radius=0.08)
text(s, 0.85, 1.72, 11.6, 0.7,
"Ansible Vault verschlüsselt sensible Daten — Passwörter, API-Keys, ganze "
"Dateien — mit AES-256. Verschlüsseltes kann gefahrlos im Git-Repository "
"liegen; ohne Vault-Passwort bleibt der Inhalt unlesbar.",
size=13.5, color=WHITE, leading=1.15)
vault_cards = [
("Ganze Dateien", "Mit ansible-vault create / encrypt / edit wird eine "
"komplette YAML-Datei verschlüsselt — z.B. group_vars/all.yml."),
("Einzelne Variablen", "Mit ansible-vault encrypt_string entsteht ein "
"!vault-Block im Klartext-File — Ansible löst ihn automatisch auf."),
("AES-256 + Passwort", "Der Schlüssel ist dein Vault-Passwort. Ohne es "
"ist der Chiffre-Text wertlos — auch für Git-Reader."),
("Sicher im Git", "Chiffrierte Dateien sind committbar. Nur das "
"Passwort (bzw. Passwort-Datei) hältst du aus dem Repo fern."),
]
for i, (t, d) in enumerate(vault_cards):
col = i % 2; row = i // 2
card(s, 0.55 + col * 6.2, 2.95 + row * 1.75, 5.95, 1.55, "•", t, d)
text(s, 0.55, 6.5, 12.2, 0.35,
[[("Erkennungs-Merkmal: ", {"bold": True}),
("$ANSIBLE_VAULT;1.1;AES256", {"font": FONT_MONO, "color": DARKRED, "bold": True}),
(" — dieser Header in der ersten Zeile markiert jede verschlüsselte Datei.", {})]],
size=11.5, color=GRAY)
footer(s)
# ===========================================================================
# FOLIE 15 — Ansible Vault (Praxis: Variable & Datei)
# ===========================================================================
s = new_slide(WHITE)
header(s, "Vault", "Vault in der Praxis — Variable & Datei")
text(s, 0.55, 1.55, 12.2, 0.4,
"Zwei typische Fälle: eine einzelne Variable als !vault-Block — oder eine "
"ganze Datei komplett verschlüsselt.",
size=12.5, color=BLACK, leading=1.15)
code_block(s, 0.55, 2.15, 6.0, [
"# Variable verschlüsseln",
"ansible-vault encrypt_string \\",
" 'S3kret!' --name db_password",
"",
"# Ergebnis — in group_vars/all.yml:",
"db_password: !vault |",
" $ANSIBLE_VAULT;1.1;AES256",
" 36623063653163316238...",
"",
"# Nutzung wie jede Variable:",
"msg: \"PW: {{ db_password }}\"",
], font_size=10, title="Einzelne Variable")
code_block(s, 6.85, 2.15, 5.95, [
"# Neue verschlüsselte Datei anlegen",
"ansible-vault create secrets.yml",
"",
"# Bestehende Datei verschlüsseln",
"ansible-vault encrypt secrets.yml",
"",
"# Ansehen / bearbeiten / entschlüsseln",
"ansible-vault view secrets.yml",
"ansible-vault edit secrets.yml",
"ansible-vault decrypt secrets.yml",
], font_size=10, title="Ganze Datei")
code_block(s, 0.55, 5.35, 12.25, [
"ansible-playbook site.yml --ask-vault-pass",
"ansible-playbook site.yml --vault-password-file ~/.vault_pass",
], font_size=11, title="Playbook ausführen — Vault-Passwort nötig")
footer(s)
# ===========================================================================
# FOLIE 16 — FQCN (Definition)
# ===========================================================================
s = new_slide(WHITE)
header(s, "FQCN", "FQCN — Fully Qualified Collection Name")
rect(s, 0.55, 1.55, 12.2, 0.95, REDTINT, 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)
# ===========================================================================
# FOLIE 17 — 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, REDTINT, 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.", {})],
[("Der FQCN macht eindeutig, ", {}),
("welches Modul", {"bold": True}),
(" gemeint ist.", {})],
[("Er vermeidet ", {}),
("Namenskollisionen", {"bold": True}),
(" — mehrere Collections können ein gleichnamiges Modul anbieten.", {})],
[("Faustregel: ", {"bold": True, "color": RED}),
("immer den FQCN verwenden, nie die Kurzform.", {})],
], size=12.5, color=BLACK, leading=1.25, space_after=8)
footer(s)
# ===========================================================================
# FOLIE 18 — Collections (Definition)
# ===========================================================================
s = new_slide(LIGHT)
header(s, "Collections", "Was sind Collections?")
grad_rect(s, 0.55, 1.6, 12.2, 1.1, BLACK, DARKRED, angle=0.0, 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)
# ===========================================================================
# FOLIE 19 — 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)
# ===========================================================================
# FOLIE 20 — Rollen (Konzept)
# ===========================================================================
s = new_slide(LIGHT)
header(s, "Rollen", "Wie funktionieren Rollen?")
grad_rect(s, 0.55, 1.6, 12.2, 1.05, BLACK, DARKRED, angle=0.0, 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)
# ===========================================================================
# FOLIE 21 — 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.6,
"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)
# ===========================================================================
# FOLIE 22 — 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.", {})],
[("1.", {"bold": True, "color": RED}), (" Rolle wird geladen.", {})],
[("2.", {"bold": True, "color": RED}), (" Aufgaben & Variablen aus der Rolle gelten.", {})],
[("3.", {"bold": True, "color": RED}), (" Idempotenz verhindert doppelte Arbeit.", {})],
], size=12, color=BLACK, leading=1.25, space_after=6)
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)
# ===========================================================================
# FOLIE 23 — 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 <hosts> -m <modul> für schnelle Einzelaktionen."),
("Facts", "Systeminfos werden automatisch gesammelt (gather_facts)."),
("Variablen", "Auflösung nach Präzedenz — Extra-Vars gewinnen, Defaults verlieren."),
("Vault", "Sensible Daten mit AES-256 verschlüsseln — Variable (!vault) oder ganze Datei."),
("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, REDTINT, 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)
# ---------------------------------------------------------------------------
# Speichern
# ---------------------------------------------------------------------------
OUT = os.path.join(DOCS, "ansible_praesentation.pptx")
prs.save(OUT)
print(f"OK — gespeichert: {OUT}")
print(f"Folien: {len(prs.slides)}")