Files
finistdev-configuration-as-…/index.html

1151 lines
71 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Configuration as Code — Puppet vs Ansible vs Terraform</title>
<!-- Reveal.js (vendored) -->
<link rel="stylesheet" href="vendor/reveal.js/dist/reset.css">
<link rel="stylesheet" href="vendor/reveal.js/dist/reveal.css">
<link rel="stylesheet" href="vendor/reveal.js/dist/white.css">
<!-- Highlight.js for code blocks -->
<link rel="stylesheet" href="vendor/reveal.js/plugin/highlight/github.css">
<style>
:root {
/* OVHcloud Design System palette (ovh/design-system) */
--ods-blue-500: #0050d7;
--ods-blue-800: #00185e;
--ods-text: #4d5592;
--ods-neutral-600: #666666;
/* Tool brand colors */
--puppet-color: #A06010;
--ansible-color: #CC0000;
--terraform-color: #7B42BC;
--strip-color: var(--ods-blue-500);
}
/* Blue accent strip — fixed outside Reveal.js, always visible */
.top-strip {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 6px;
background: var(--strip-color);
z-index: 50;
pointer-events: none;
}
.reveal .slides section {
text-align: left;
}
.reveal .slides section svg,
.reveal .slides section > img {
display: block;
margin-left: auto;
margin-right: auto;
}
.reveal h1, .reveal h2 { text-align: left; }
.reveal h1 { font-size: 1.8em; }
.reveal h2 { font-size: 1.3em; }
.title-slide, .title-slide h1 { text-align: center !important; }
.title-slide .subtitle { color: var(--ods-text); }
.title-slide .meta { font-size: 0.6em; color: var(--ods-text); margin-top: 1em; }
.title-slide .meta a { color: var(--strip-color); }
.puppet-col { color: var(--puppet-color); }
.ansible-col { color: var(--ansible-color); }
.tf-col { color: var(--terraform-color); }
.filename {
font-family: monospace;
font-size: 0.65em;
color: var(--ods-text);
margin-bottom: 0.2em;
}
/* Code blocks: fill the slide without scrollbars */
.reveal pre {
width: 100%;
max-height: none;
overflow: visible;
margin: 0;
}
.reveal pre code {
font-size: 0.65em;
line-height: 1.6;
max-height: none;
overflow: visible;
padding: 1em;
}
.filename { margin-top: 0; margin-bottom: 0.1em; }
/* Tool logos — fixed like OVHcloud logo, toggled by JS per slide */
.tool-logo-global {
position: fixed;
top: 16px;
left: 16px;
width: 40px;
height: 40px;
opacity: 0.8;
z-index: 50;
pointer-events: none;
display: none;
}
/* Persistent logo — outside Reveal container, fixed to viewport */
.ovh-logo-global {
position: fixed;
bottom: 16px;
left: 16px;
height: 28px;
width: auto;
opacity: 0.7;
z-index: 50;
pointer-events: none;
}
</style>
</head>
<body>
<div class="top-strip"></div>
<img src="vendor/ovhcloud-logo.svg" alt="OVHcloud" class="ovh-logo-global">
<div class="reveal">
<div class="slides">
<!-- ─── SLIDE 1 : Title ─────────────────────────────────────────── -->
<section class="title-slide">
<h1>Configuration as Code</h1>
<p class="subtitle">
<img src="https://cdn.simpleicons.org/puppet/C17F00" alt="Puppet" style="height:0.9em; vertical-align:middle;"> Puppet ·
<img src="https://cdn.simpleicons.org/ansible" alt="Ansible" style="height:0.9em; vertical-align:middle;"> Ansible ·
<img src="https://cdn.simpleicons.org/terraform" alt="Terraform" style="height:0.9em; vertical-align:middle;"> Terraform
<br>— What's the difference and when to use what? —</p>
<p class="meta">FinistDevs · 2026</p>
</section>
<!-- ─── SLIDE 2 : Speaker intro ─────────────────────────────────── -->
<section>
<h2>Arnaud Prémel-Cabic</h2>
<p>Tech Lead @ OVHCloud</p>
<p><small style="color:var(--ods-text);">arnaud.premel-cabic@ovhcloud.com</small></p>
</section>
<!-- ─── SLIDE 3 ─────────────────────────────────────────────────── -->
<section>
<h2>"It works on my server."</h2>
<p><em>Or: why doesn't it work here, when it works everywhere else?</em></p>
</section>
<!-- ─── SLIDE 4 ─────────────────────────────────────────────────── -->
<section>
<h2>You have a server. It works.</h2>
<p><em>Great.</em></p>
<!-- SVG1 — single server, green check -->
<svg width="700" height="250" viewBox="0 0 700 250" style="margin-top:0.5em;" xmlns="http://www.w3.org/2000/svg">
<!-- Server body -->
<rect x="270" y="30" width="160" height="140" rx="12" ry="12" fill="#f0f2f8" stroke="#00185e" stroke-width="2.5"/>
<!-- Rack lines -->
<line x1="290" y1="65" x2="410" y2="65" stroke="#4d5592" stroke-width="1.5" stroke-linecap="round"/>
<line x1="290" y1="100" x2="410" y2="100" stroke="#4d5592" stroke-width="1.5" stroke-linecap="round"/>
<line x1="290" y1="135" x2="410" y2="135" stroke="#4d5592" stroke-width="1.5" stroke-linecap="round"/>
<!-- Drive bays / LEDs -->
<circle cx="298" cy="50" r="4" fill="#0050d7"/>
<circle cx="298" cy="85" r="4" fill="#0050d7"/>
<circle cx="298" cy="120" r="4" fill="#0050d7"/>
<!-- Power button -->
<circle cx="402" cy="155" r="5" fill="#00185e"/>
<!-- Server legs -->
<rect x="290" y="170" width="14" height="10" rx="2" fill="#666666"/>
<rect x="396" y="170" width="14" height="10" rx="2" fill="#666666"/>
<!-- Green checkmark circle -->
<circle cx="350" cy="215" r="22" fill="#2ecc71"/>
<polyline points="339,215 347,224 363,207" fill="none" stroke="#fff" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</section>
<!-- ─── SLIDE 5 ─────────────────────────────────────────────────── -->
<section>
<h2>You have 10 servers.</h2>
<ul>
<li>10 SSH sessions</li>
<li>10 manual edits</li>
<li>10 chances to make a mistake</li>
</ul>
<!-- SVG2 — laptop + 10 servers fan-out -->
<svg width="720" height="260" viewBox="0 0 720 260" style="margin-top:0.5em;" xmlns="http://www.w3.org/2000/svg">
<!-- Laptop body -->
<rect x="20" y="60" width="120" height="80" rx="8" ry="8" fill="#f0f2f8" stroke="#00185e" stroke-width="2"/>
<!-- Screen inner -->
<rect x="30" y="68" width="100" height="56" rx="3" fill="#00185e"/>
<!-- Terminal prompt on screen -->
<text x="38" y="88" font-family="monospace" font-size="10" fill="#2ecc71">$</text>
<text x="48" y="88" font-family="monospace" font-size="9" fill="#aab4d5">ssh root@…</text>
<text x="38" y="103" font-family="monospace" font-size="10" fill="#2ecc71">$</text>
<text x="48" y="103" font-family="monospace" font-size="9" fill="#aab4d5">vim /etc/…</text>
<!-- Laptop base -->
<path d="M10,140 L150,140 L140,155 L20,155 Z" fill="#d8dce8" stroke="#00185e" stroke-width="1.5"/>
<!-- Keyboard hinge -->
<rect x="40" y="140" width="80" height="3" rx="1" fill="#666666"/>
<!-- 10 server icons on right, 2 columns of 5 -->
<!-- Dashed lines from laptop to each server -->
<!-- Column 1 (x=460) -->
<line x1="150" y1="100" x2="460" y2="18" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
<line x1="150" y1="100" x2="460" y2="66" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
<line x1="150" y1="100" x2="460" y2="114" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
<line x1="150" y1="100" x2="460" y2="162" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
<line x1="150" y1="100" x2="460" y2="210" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
<!-- Column 2 (x=590) -->
<line x1="150" y1="100" x2="590" y2="18" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
<line x1="150" y1="100" x2="590" y2="66" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
<line x1="150" y1="100" x2="590" y2="114" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
<line x1="150" y1="100" x2="590" y2="162" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
<line x1="150" y1="100" x2="590" y2="210" stroke="#0050d7" stroke-width="1" stroke-dasharray="5,3" opacity="0.6"/>
<!-- Arrow tips (small triangles) on server end -->
<polygon points="458,16 458,20 463,18" fill="#0050d7" opacity="0.7"/>
<polygon points="458,64 458,68 463,66" fill="#0050d7" opacity="0.7"/>
<polygon points="458,112 458,116 463,114" fill="#0050d7" opacity="0.7"/>
<polygon points="458,160 458,164 463,162" fill="#0050d7" opacity="0.7"/>
<polygon points="458,208 458,212 463,210" fill="#0050d7" opacity="0.7"/>
<polygon points="588,16 588,20 593,18" fill="#0050d7" opacity="0.7"/>
<polygon points="588,64 588,68 593,66" fill="#0050d7" opacity="0.7"/>
<polygon points="588,112 588,116 593,114" fill="#0050d7" opacity="0.7"/>
<polygon points="588,160 588,164 593,162" fill="#0050d7" opacity="0.7"/>
<polygon points="588,208 588,212 593,210" fill="#0050d7" opacity="0.7"/>
<!-- Column 1 servers -->
<rect x="465" y="5" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
<rect x="465" y="53" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
<rect x="465" y="101" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
<rect x="465" y="149" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
<rect x="465" y="197" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
<!-- Column 1 rack lines -->
<line x1="475" y1="18" x2="555" y2="18" stroke="#4d5592" stroke-width="1"/>
<line x1="475" y1="66" x2="555" y2="66" stroke="#4d5592" stroke-width="1"/>
<line x1="475" y1="114" x2="555" y2="114" stroke="#4d5592" stroke-width="1"/>
<line x1="475" y1="162" x2="555" y2="162" stroke="#4d5592" stroke-width="1"/>
<line x1="475" y1="210" x2="555" y2="210" stroke="#4d5592" stroke-width="1"/>
<!-- Column 1 LEDs -->
<circle cx="473" cy="12" r="2.5" fill="#0050d7"/>
<circle cx="473" cy="60" r="2.5" fill="#0050d7"/>
<circle cx="473" cy="108" r="2.5" fill="#0050d7"/>
<circle cx="473" cy="156" r="2.5" fill="#0050d7"/>
<circle cx="473" cy="204" r="2.5" fill="#0050d7"/>
<!-- Column 2 servers -->
<rect x="595" y="5" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
<rect x="595" y="53" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
<rect x="595" y="101" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
<rect x="595" y="149" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
<rect x="595" y="197" width="100" height="26" rx="5" fill="#f0f2f8" stroke="#00185e" stroke-width="1.5"/>
<!-- Column 2 rack lines -->
<line x1="605" y1="18" x2="685" y2="18" stroke="#4d5592" stroke-width="1"/>
<line x1="605" y1="66" x2="685" y2="66" stroke="#4d5592" stroke-width="1"/>
<line x1="605" y1="114" x2="685" y2="114" stroke="#4d5592" stroke-width="1"/>
<line x1="605" y1="162" x2="685" y2="162" stroke="#4d5592" stroke-width="1"/>
<line x1="605" y1="210" x2="685" y2="210" stroke="#4d5592" stroke-width="1"/>
<!-- Column 2 LEDs -->
<circle cx="603" cy="12" r="2.5" fill="#0050d7"/>
<circle cx="603" cy="60" r="2.5" fill="#0050d7"/>
<circle cx="603" cy="108" r="2.5" fill="#0050d7"/>
<circle cx="603" cy="156" r="2.5" fill="#0050d7"/>
<circle cx="603" cy="204" r="2.5" fill="#0050d7"/>
<!-- "ssh" labels floating near lines -->
<text x="260" y="65" font-family="monospace" font-size="9" fill="#0050d7" opacity="0.7" transform="rotate(-8,260,65)">ssh</text>
<text x="280" y="115" font-family="monospace" font-size="9" fill="#0050d7" opacity="0.7">ssh</text>
<text x="260" y="160" font-family="monospace" font-size="9" fill="#0050d7" opacity="0.7" transform="rotate(6,260,160)">ssh</text>
</svg>
</section>
<!-- ─── SLIDE 6 ─────────────────────────────────────────────────── -->
<section>
<h2>Now you have 100 servers.</h2>
<p><em>Some 2 years old. Some a couple of months. Some freshly provisioned.<br>None of them are exactly alike.</em></p>
<!-- SVG3 — 100-server chaos grid with drift indicators -->
<svg width="700" height="260" viewBox="0 0 700 260" style="margin-top:0.5em;" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- Warning triangle symbol -->
<symbol id="warn" viewBox="0 0 16 16">
<polygon points="8,1 15,15 1,15" fill="#e00034" stroke="#fff" stroke-width="0.5"/>
<text x="8" y="13" text-anchor="middle" font-size="10" font-weight="bold" fill="#fff">!</text>
</symbol>
<!-- Snowflake symbol -->
<symbol id="snow" viewBox="0 0 16 16">
<text x="8" y="14" text-anchor="middle" font-size="14" fill="#0050d7"></text>
</symbol>
</defs>
<!-- Generate a 10×6 grid of servers (60 shown, suggests ~100) -->
<!-- Row 0 -->
<rect x="10" y="5" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="78" y="5" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="146" y="5" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
<rect x="214" y="5" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="282" y="5" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
<rect x="350" y="5" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="418" y="5" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
<rect x="486" y="5" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
<rect x="554" y="5" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="622" y="5" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
<!-- Row 1 -->
<rect x="10" y="45" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
<rect x="78" y="45" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="146" y="45" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="214" y="45" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
<rect x="282" y="45" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
<rect x="350" y="45" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="418" y="45" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="486" y="45" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
<rect x="554" y="45" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="622" y="45" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
<!-- Row 2 -->
<rect x="10" y="85" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="78" y="85" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
<rect x="146" y="85" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
<rect x="214" y="85" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="282" y="85" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="350" y="85" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
<rect x="418" y="85" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
<rect x="486" y="85" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="554" y="85" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="622" y="85" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
<!-- Row 3 -->
<rect x="10" y="125" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
<rect x="78" y="125" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="146" y="125" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
<rect x="214" y="125" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
<rect x="282" y="125" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="350" y="125" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="418" y="125" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="486" y="125" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
<rect x="554" y="125" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
<rect x="622" y="125" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<!-- Row 4 -->
<rect x="10" y="165" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="78" y="165" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
<rect x="146" y="165" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="214" y="165" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="282" y="165" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
<rect x="350" y="165" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
<rect x="418" y="165" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="486" y="165" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
<rect x="554" y="165" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="622" y="165" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
<!-- Row 5 (partial — fades into "...more") -->
<rect x="10" y="205" width="58" height="30" rx="4" fill="#e8edf8" stroke="#0050d7" stroke-width="1.2"/>
<rect x="78" y="205" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="146" y="205" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#666666" stroke-width="1.2"/>
<rect x="214" y="205" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="282" y="205" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<rect x="350" y="205" width="58" height="30" rx="4" fill="#fce8ec" stroke="#e00034" stroke-width="1.2"/>
<rect x="418" y="205" width="58" height="30" rx="4" fill="#f0f2f8" stroke="#00185e" stroke-width="1.2"/>
<!-- Ellipsis to suggest more -->
<text x="510" y="226" font-family="sans-serif" font-size="22" fill="#666666" letter-spacing="4">· · ·</text>
<!-- Rack lines on every server (subtle) -->
<g stroke="#4d5592" stroke-width="0.6" opacity="0.4">
<!-- Just add a mid-line to each server for texture -->
<line x1="18" y1="20" x2="60" y2="20"/> <line x1="86" y1="20" x2="128" y2="20"/>
<line x1="18" y1="60" x2="60" y2="60"/> <line x1="86" y1="60" x2="128" y2="60"/>
<line x1="18" y1="100" x2="60" y2="100"/> <line x1="86" y1="100" x2="128" y2="100"/>
<line x1="18" y1="140" x2="60" y2="140"/> <line x1="86" y1="140" x2="128" y2="140"/>
<line x1="18" y1="180" x2="60" y2="180"/> <line x1="86" y1="180" x2="128" y2="180"/>
<line x1="18" y1="220" x2="60" y2="220"/> <line x1="86" y1="220" x2="128" y2="220"/>
</g>
<!-- Warning triangles on red-tinted servers -->
<use href="#warn" x="50" y="7" width="14" height="14"/>
<use href="#warn" x="528" y="7" width="14" height="14"/>
<use href="#warn" x="324" y="47" width="14" height="14"/>
<use href="#warn" x="120" y="87" width="14" height="14"/>
<use href="#warn" x="460" y="87" width="14" height="14"/>
<use href="#warn" x="256" y="127" width="14" height="14"/>
<use href="#warn" x="324" y="167" width="14" height="14"/>
<use href="#warn" x="528" y="167" width="14" height="14"/>
<use href="#warn" x="392" y="207" width="14" height="14"/>
<!-- Snowflakes on blue-tinted servers (drift / unique configs) -->
<use href="#snow" x="324" y="6" width="14" height="14"/>
<use href="#snow" x="664" y="6" width="14" height="14"/>
<use href="#snow" x="52" y="46" width="14" height="14"/>
<use href="#snow" x="528" y="46" width="14" height="14"/>
<use href="#snow" x="392" y="86" width="14" height="14"/>
<use href="#snow" x="188" y="126" width="14" height="14"/>
<use href="#snow" x="596" y="126" width="14" height="14"/>
<use href="#snow" x="120" y="166" width="14" height="14"/>
<use href="#snow" x="664" y="166" width="14" height="14"/>
<use href="#snow" x="52" y="206" width="14" height="14"/>
<!-- Small status dots (mixed: green OK, amber, red) scattered -->
<circle cx="18" cy="12" r="2.5" fill="#2ecc71"/> <circle cx="86" cy="12" r="2.5" fill="#2ecc71"/>
<circle cx="154" cy="12" r="2.5" fill="#e00034"/> <circle cx="222" cy="12" r="2.5" fill="#2ecc71"/>
<circle cx="494" cy="12" r="2.5" fill="#e00034"/> <circle cx="562" cy="12" r="2.5" fill="#2ecc71"/>
<circle cx="18" cy="52" r="2.5" fill="#f39c12"/> <circle cx="86" cy="52" r="2.5" fill="#2ecc71"/>
<circle cx="154" cy="52" r="2.5" fill="#2ecc71"/> <circle cx="290" cy="52" r="2.5" fill="#e00034"/>
<circle cx="358" cy="52" r="2.5" fill="#2ecc71"/> <circle cx="426" cy="52" r="2.5" fill="#2ecc71"/>
<circle cx="18" cy="92" r="2.5" fill="#2ecc71"/> <circle cx="86" cy="92" r="2.5" fill="#e00034"/>
<circle cx="222" cy="92" r="2.5" fill="#2ecc71"/> <circle cx="290" cy="92" r="2.5" fill="#f39c12"/>
<circle cx="426" cy="92" r="2.5" fill="#e00034"/> <circle cx="494" cy="92" r="2.5" fill="#2ecc71"/>
<circle cx="18" cy="132" r="2.5" fill="#f39c12"/> <circle cx="86" cy="132" r="2.5" fill="#2ecc71"/>
<circle cx="222" cy="132" r="2.5" fill="#e00034"/> <circle cx="290" cy="132" r="2.5" fill="#2ecc71"/>
<circle cx="494" cy="132" r="2.5" fill="#f39c12"/>
<circle cx="18" cy="172" r="2.5" fill="#2ecc71"/> <circle cx="290" cy="172" r="2.5" fill="#e00034"/>
<circle cx="358" cy="172" r="2.5" fill="#f39c12"/> <circle cx="494" cy="172" r="2.5" fill="#e00034"/>
<circle cx="562" cy="172" r="2.5" fill="#2ecc71"/>
<circle cx="86" cy="212" r="2.5" fill="#2ecc71"/> <circle cx="222" cy="212" r="2.5" fill="#f39c12"/>
<circle cx="290" cy="212" r="2.5" fill="#2ecc71"/> <circle cx="358" cy="212" r="2.5" fill="#e00034"/>
</svg>
</section>
<!-- ─── SLIDE 7 ─────────────────────────────────────────────────── -->
<section>
<h2>Unique. Unreproducible. Undocumented.</h2>
<p><em>Welcome to Snowflake Hell.</em></p>
<img src="https://media.giphy.com/media/QMHoU66sBXqqLqYvGO/giphy.gif"
alt="This is fine"
style="height:220px; margin-top:0.5em; border-radius:6px;">
</section>
<!-- ─── SLIDE 8 ─────────────────────────────────────────────────── -->
<section>
<h2>Configuration drift is silent…</h2>
<p><em>Prod breaks on a Tuesday. You can't reproduce the bug locally. You can't scale reliably. You can't onboard a new server without fear.</em></p>
<svg width="750" height="220" style="margin-top:0.5em;" viewBox="0 0 750 220" xmlns="http://www.w3.org/2000/svg">
<line x1="60" y1="180" x2="700" y2="180" stroke="#666666" stroke-width="2.5"/>
<polygon points="700,174 720,180 700,186" fill="#666666"/>
<text x="140" y="210" text-anchor="middle" font-family="system-ui,sans-serif" font-size="14" fill="#4d5592" font-weight="600">T0</text>
<text x="390" y="210" text-anchor="middle" font-family="system-ui,sans-serif" font-size="14" fill="#4d5592" font-weight="600">T1</text>
<text x="630" y="210" text-anchor="middle" font-family="system-ui,sans-serif" font-size="14" fill="#4d5592" font-weight="600">T2</text>
<line x1="140" y1="175" x2="140" y2="185" stroke="#666666" stroke-width="2"/>
<line x1="390" y1="175" x2="390" y2="185" stroke="#666666" stroke-width="2"/>
<line x1="630" y1="175" x2="630" y2="185" stroke="#666666" stroke-width="2"/>
<!-- T0: identical servers -->
<rect x="90" y="70" width="28" height="36" rx="4" fill="#0050d7" opacity="0.9"/>
<rect x="122" y="70" width="28" height="36" rx="4" fill="#0050d7" opacity="0.9"/>
<rect x="154" y="70" width="28" height="36" rx="4" fill="#0050d7" opacity="0.9"/>
<rect x="186" y="70" width="28" height="36" rx="4" fill="#0050d7" opacity="0.9"/>
<text x="140" y="55" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#00a344" font-weight="600">ALL IDENTICAL</text>
<!-- T1: drifting -->
<rect x="340" y="70" width="28" height="36" rx="4" fill="#0050d7" opacity="0.9"/>
<rect x="372" y="68" width="28" height="36" rx="4" fill="#0050d7" opacity="0.7"/>
<rect x="404" y="72" width="28" height="36" rx="4" fill="#4d5592" opacity="0.85"/>
<rect x="436" y="70" width="28" height="36" rx="4" fill="#0050d7" opacity="0.9"/>
<text x="390" y="55" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#4d5592" font-weight="600">DRIFTING…</text>
<!-- T2: chaos -->
<rect x="580" y="74" width="28" height="36" rx="4" fill="#e00034" opacity="0.85"/>
<rect x="612" y="66" width="28" height="36" rx="4" fill="#4d5592" opacity="0.8"/>
<rect x="644" y="78" width="28" height="36" rx="4" fill="#00185e" opacity="0.9"/>
<rect x="676" y="70" width="28" height="36" rx="4" fill="#e00034" opacity="0.7"/>
<polygon points="594,68 600,58 606,68" fill="#e00034" stroke="white" stroke-width="1"/>
<text x="600" y="67" text-anchor="middle" font-family="system-ui,sans-serif" font-size="8" fill="white" font-weight="bold">!</text>
<polygon points="654,72 660,62 666,72" fill="#e00034" stroke="white" stroke-width="1"/>
<text x="660" y="71" text-anchor="middle" font-family="system-ui,sans-serif" font-size="8" fill="white" font-weight="bold">!</text>
<polygon points="684,64 690,54 696,64" fill="#e00034" stroke="white" stroke-width="1"/>
<text x="690" y="63" text-anchor="middle" font-family="system-ui,sans-serif" font-size="8" fill="white" font-weight="bold">!</text>
<text x="630" y="48" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#e00034" font-weight="700">CHAOS</text>
</svg>
</section>
<!-- ─── SLIDE 9 ─────────────────────────────────────────────────── -->
<section style="height:100%;">
<h2>What if your infrastructure was just… code?</h2>
<p><em>Yes, even your AI assistant can write it*. But you still need to understand what it deploys.</em></p>
<svg width="750" height="220" style="margin-top:0.5em;" viewBox="0 0 750 220" xmlns="http://www.w3.org/2000/svg">
<!-- Code file icon -->
<rect x="80" y="35" width="120" height="150" rx="6" fill="none" stroke="#0050d7" stroke-width="2.5"/>
<path d="M165,35 L200,35 L200,70 L165,70 Z" fill="white" stroke="white" stroke-width="3"/>
<path d="M165,35 L200,70" stroke="#0050d7" stroke-width="2.5" fill="none"/>
<line x1="165" y1="35" x2="165" y2="70" stroke="#0050d7" stroke-width="2.5"/>
<line x1="165" y1="70" x2="200" y2="70" stroke="#0050d7" stroke-width="2.5"/>
<text x="140" y="108" text-anchor="middle" font-family="monospace" font-size="30" fill="#0050d7" font-weight="700">{ }</text>
<line x1="105" y1="128" x2="175" y2="128" stroke="#4d5592" stroke-width="2" opacity="0.4"/>
<line x1="112" y1="140" x2="168" y2="140" stroke="#4d5592" stroke-width="2" opacity="0.3"/>
<line x1="108" y1="152" x2="172" y2="152" stroke="#4d5592" stroke-width="2" opacity="0.4"/>
<!-- Arrow -->
<line x1="260" y1="110" x2="420" y2="110" stroke="#0050d7" stroke-width="4" stroke-linecap="round"/>
<polygon points="420,98 448,110 420,122" fill="#0050d7"/>
<text x="340" y="95" text-anchor="middle" font-family="system-ui,sans-serif" font-size="13" fill="#4d5592" font-weight="600">deploy</text>
<!-- Cloud with servers -->
<path d="M530,160 Q470,160 480,120 Q470,80 510,75 Q525,45 570,50 Q610,40 630,65 Q680,60 685,95 Q710,105 700,135 Q705,165 670,160 Z" fill="none" stroke="#0050d7" stroke-width="2.5" opacity="0.8"/>
<rect x="520" y="90" width="36" height="44" rx="5" fill="#0050d7" opacity="0.15" stroke="#0050d7" stroke-width="1.5"/>
<circle cx="548" cy="124" r="3" fill="#00a344"/>
<rect x="572" y="90" width="36" height="44" rx="5" fill="#0050d7" opacity="0.15" stroke="#0050d7" stroke-width="1.5"/>
<circle cx="600" cy="124" r="3" fill="#00a344"/>
<rect x="624" y="90" width="36" height="44" rx="5" fill="#0050d7" opacity="0.15" stroke="#0050d7" stroke-width="1.5"/>
<circle cx="652" cy="124" r="3" fill="#00a344"/>
</svg>
<small style="position:absolute; bottom:40px; right:0; color:var(--ods-neutral-600); font-size:0.45em;">*Like this presentation 🤖</small>
</section>
<!-- ─── SLIDE 10 ────────────────────────────────────────────────── -->
<section>
<h2>Configuration as Code</h2>
<p>Managing infrastructure through machine-readable files, stored in version control.</p>
<svg width="750" height="220" style="margin-top:0.5em;" viewBox="0 0 750 220" xmlns="http://www.w3.org/2000/svg">
<!-- Reproducible -->
<g transform="translate(125,80)">
<path d="M0,-32 A32,32 0 1,1 -22,22" fill="none" stroke="#0050d7" stroke-width="3" stroke-linecap="round"/>
<polygon points="-28,14 -22,26 -14,16" fill="#0050d7"/>
<path d="M0,32 A32,32 0 1,1 22,-22" fill="none" stroke="#0050d7" stroke-width="3" stroke-linecap="round"/>
<polygon points="28,-14 22,-26 14,-16" fill="#0050d7"/>
</g>
<text x="125" y="145" text-anchor="middle" font-family="system-ui,sans-serif" font-size="16" fill="#00185e" font-weight="700">Reproducible</text>
<text x="125" y="165" text-anchor="middle" font-family="system-ui,sans-serif" font-size="12" fill="#4d5592">Same input → same result</text>
<line x1="250" y1="40" x2="250" y2="180" stroke="#4d5592" stroke-width="0.5" opacity="0.3"/>
<!-- Versionable -->
<g transform="translate(375,80)">
<line x1="0" y1="-35" x2="0" y2="35" stroke="#0050d7" stroke-width="3" stroke-linecap="round"/>
<path d="M0,-5 Q20,-5 25,-25" fill="none" stroke="#0050d7" stroke-width="3" stroke-linecap="round"/>
<circle cx="0" cy="-30" r="5" fill="#0050d7"/>
<circle cx="0" cy="-5" r="5" fill="#0050d7"/>
<circle cx="0" cy="20" r="5" fill="#0050d7"/>
<circle cx="25" cy="-25" r="5" fill="#0050d7" opacity="0.7"/>
<path d="M25,-25 Q30,-10 0,20" fill="none" stroke="#0050d7" stroke-width="2.5" stroke-linecap="round" stroke-dasharray="4,3"/>
</g>
<text x="375" y="145" text-anchor="middle" font-family="system-ui,sans-serif" font-size="16" fill="#00185e" font-weight="700">Versionable</text>
<text x="375" y="165" text-anchor="middle" font-family="system-ui,sans-serif" font-size="12" fill="#4d5592">Track every change</text>
<line x1="500" y1="40" x2="500" y2="180" stroke="#4d5592" stroke-width="0.5" opacity="0.3"/>
<!-- Auditable -->
<g transform="translate(625,72)">
<circle cx="-8" cy="-8" r="22" fill="none" stroke="#0050d7" stroke-width="3"/>
<line x1="8" y1="8" x2="24" y2="24" stroke="#0050d7" stroke-width="4" stroke-linecap="round"/>
<polyline points="-18,-14 -14,-10 -6,-18" fill="none" stroke="#0050d7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<line x1="-2" y1="-14" x2="8" y2="-14" stroke="#0050d7" stroke-width="1.5" opacity="0.5"/>
<polyline points="-18,-2 -14,2 -6,-6" fill="none" stroke="#0050d7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<line x1="-2" y1="-2" x2="8" y2="-2" stroke="#0050d7" stroke-width="1.5" opacity="0.5"/>
</g>
<text x="625" y="145" text-anchor="middle" font-family="system-ui,sans-serif" font-size="16" fill="#00185e" font-weight="700">Auditable</text>
<text x="625" y="165" text-anchor="middle" font-family="system-ui,sans-serif" font-size="12" fill="#4d5592">Who changed what &amp; when</text>
</svg>
</section>
<!-- ─── SLIDE 11 ────────────────────────────────────────────────── -->
<section>
<h2>Meet the three musketeers of infrastructure.</h2>
<p><span class="puppet-col">Puppet</span> &nbsp;·&nbsp; <span class="ansible-col">Ansible</span> &nbsp;·&nbsp; <span class="tf-col">Terraform</span> — each fights a different battle.</p>
</section>
<!-- ─── SLIDE 12 : Terraform intro ──────────────────────────────── -->
<section class="s-tf">
<h2><span class="tf-col">Terraform</span></h2>
<p>Start here. Before you configure a server, you need to have one.</p>
</section>
<!-- ─── SLIDE 13 : Terraform concepts ───────────────────────────── -->
<section class="s-tf">
<h2>HCL: HashiCorp Configuration Language</h2>
<p>Declarative, human-readable — pure JSON works too.</p>
<ul>
<li><code>terraform plan</code> — preview what will change</li>
<li><strong>Review</strong> — validate the plan before proceeding</li>
<li><code>terraform apply</code> — create or update resources</li>
<li><code>terraform destroy</code> — tear everything down</li>
</ul>
<svg width="900" height="150" style="margin-top:0.5em;" viewBox="0 0 900 150" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="wf-arrow" viewBox="0 0 10 7" refX="10" refY="3.5" markerWidth="10" markerHeight="7" orient="auto-start-auto">
<path d="M0,0 L10,3.5 L0,7z" fill="#7B42BC"/>
</marker>
</defs>
<!-- Box 1: HCL Code -->
<rect x="10" y="15" width="140" height="100" rx="12" fill="none" stroke="#7B42BC" stroke-width="2.2"/>
<g transform="translate(50,28)">
<path d="M0,4 L0,30 Q0,33 3,33 L27,33 Q30,33 30,30 L30,10 L20,0 L3,0 Q0,0 0,4z" fill="none" stroke="#7B42BC" stroke-width="1.6"/>
<path d="M20,0 L20,7 Q20,10 23,10 L30,10" fill="none" stroke="#7B42BC" stroke-width="1.4"/>
</g>
<text x="80" y="92" text-anchor="middle" font-family="system-ui,sans-serif" font-size="14" font-weight="600" fill="#00185e">HCL Code</text>
<!-- Arrow 1→2 -->
<line x1="158" y1="65" x2="195" y2="65" stroke="#7B42BC" stroke-width="2" marker-end="url(#wf-arrow)"/>
<!-- Box 2: plan -->
<rect x="203" y="15" width="140" height="100" rx="12" fill="none" stroke="#7B42BC" stroke-width="2.2"/>
<g transform="translate(248,30)">
<ellipse cx="15" cy="15" rx="18" ry="12" fill="none" stroke="#7B42BC" stroke-width="1.6"/>
<circle cx="15" cy="15" r="6" fill="none" stroke="#7B42BC" stroke-width="1.6"/>
<circle cx="15" cy="15" r="2.5" fill="#7B42BC"/>
</g>
<text x="273" y="92" text-anchor="middle" font-family="monospace" font-size="14" font-weight="600" fill="#00185e">plan</text>
<!-- Arrow 2→3 -->
<line x1="351" y1="65" x2="388" y2="65" stroke="#7B42BC" stroke-width="2" marker-end="url(#wf-arrow)"/>
<!-- Box 3: review (new) -->
<rect x="396" y="15" width="140" height="100" rx="12" fill="#f6f0ff" stroke="#7B42BC" stroke-width="2.2" stroke-dasharray="6,3"/>
<g transform="translate(446,28)">
<polyline points="3,18 10,25 25,8" fill="none" stroke="#7B42BC" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<text x="466" y="92" text-anchor="middle" font-family="system-ui,sans-serif" font-size="14" font-weight="600" fill="#00185e">review</text>
<!-- Arrow 3→4 -->
<line x1="544" y1="65" x2="581" y2="65" stroke="#7B42BC" stroke-width="2" marker-end="url(#wf-arrow)"/>
<!-- Box 4: apply -->
<rect x="589" y="15" width="140" height="100" rx="12" fill="none" stroke="#7B42BC" stroke-width="2.2"/>
<g transform="translate(634,30)">
<polygon points="5,2 30,15 5,28" fill="none" stroke="#7B42BC" stroke-width="1.8" stroke-linejoin="round"/>
</g>
<text x="659" y="92" text-anchor="middle" font-family="monospace" font-size="14" font-weight="600" fill="#00185e">apply</text>
<!-- Arrow 4→5 -->
<line x1="737" y1="65" x2="774" y2="65" stroke="#7B42BC" stroke-width="2" marker-end="url(#wf-arrow)"/>
<!-- Box 5: Resources -->
<rect x="782" y="15" width="108" height="100" rx="12" fill="none" stroke="#7B42BC" stroke-width="2.2"/>
<g transform="translate(810,28)">
<path d="M12,28 Q0,28 0,20 Q0,14 6,12 Q6,4 15,2 Q24,0 28,6 Q29,5 32,5 Q38,5 38,11 Q44,12 44,18 Q44,24 38,25 Q38,28 34,28z" fill="none" stroke="#7B42BC" stroke-width="1.6"/>
</g>
<text x="836" y="92" text-anchor="middle" font-family="system-ui,sans-serif" font-size="13" font-weight="600" fill="#00185e">Resources</text>
</svg>
</section>
<!-- ─── SLIDE 14 : Terraform state ──────────────────────────────── -->
<section class="s-tf">
<h2>Terraform remembers what it built.</h2>
<ul>
<li>The <code>.tfstate</code> file maps code to real-world resources</li>
<li>Store it <strong>remotely</strong> — never commit it to Git</li>
<li>May contain sensitive values: credentials, tokens, secrets</li>
</ul>
<p><em>Handle it with care. Don't feed it to your LLM.</em></p>
<svg width="750" height="220" style="margin-top:0.5em;" viewBox="0 0 750 220" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="st-arrow" viewBox="0 0 8 6" refX="8" refY="3" markerWidth="8" markerHeight="6" orient="auto-start-auto">
<path d="M0,0 L8,3 L0,6z" fill="#7B42BC"/>
</marker>
</defs>
<!-- Left: HCL files -->
<g transform="translate(30,18)">
<rect width="80" height="48" rx="6" fill="#f6f0ff" stroke="#7B42BC" stroke-width="1.5"/>
<text x="40" y="44" text-anchor="middle" font-family="monospace" font-size="9" fill="#4d5592">main.tf</text>
</g>
<g transform="translate(30,85)">
<rect width="80" height="48" rx="6" fill="#f6f0ff" stroke="#7B42BC" stroke-width="1.5"/>
<text x="40" y="44" text-anchor="middle" font-family="monospace" font-size="9" fill="#4d5592">network.tf</text>
</g>
<g transform="translate(30,152)">
<rect width="80" height="48" rx="6" fill="#f6f0ff" stroke="#7B42BC" stroke-width="1.5"/>
<text x="40" y="44" text-anchor="middle" font-family="monospace" font-size="9" fill="#4d5592">dns.tf</text>
</g>
<text x="70" y="215" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#666666">Configuration</text>
<!-- Lines to center -->
<line x1="115" y1="42" x2="280" y2="88" stroke="#7B42BC" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#st-arrow)"/>
<line x1="115" y1="109" x2="280" y2="108" stroke="#7B42BC" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#st-arrow)"/>
<line x1="115" y1="176" x2="280" y2="128" stroke="#7B42BC" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#st-arrow)"/>
<!-- Center: .tfstate cylinder -->
<g transform="translate(285,42)">
<rect y="18" width="180" height="100" fill="#f6f0ff" stroke="#7B42BC" stroke-width="2"/>
<ellipse cx="90" cy="18" rx="90" ry="18" fill="#f6f0ff" stroke="#7B42BC" stroke-width="2"/>
<path d="M0,118 Q0,136 90,136 Q180,136 180,118" fill="#f6f0ff" stroke="#7B42BC" stroke-width="2"/>
<rect x="1" y="100" width="178" height="19" fill="#f6f0ff" stroke="none"/>
<line x1="0" y1="18" x2="0" y2="118" stroke="#7B42BC" stroke-width="2"/>
<line x1="180" y1="18" x2="180" y2="118" stroke="#7B42BC" stroke-width="2"/>
<text x="90" y="78" text-anchor="middle" font-family="monospace" font-size="20" font-weight="700" fill="#00185e">.tfstate</text>
<text x="90" y="98" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#4d5592">state mapping</text>
<g transform="translate(72,-8)">
<rect x="8" y="12" width="20" height="15" rx="3" fill="#FFD54F" stroke="#E65100" stroke-width="1.3"/>
<path d="M12,12 L12,7 Q12,1 18,1 Q24,1 24,7 L24,12" fill="none" stroke="#E65100" stroke-width="1.5"/>
<circle cx="18" cy="19" r="2" fill="#E65100"/>
</g>
</g>
<text x="375" y="215" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#666666">Source of Truth</text>
<!-- Lines to right -->
<line x1="470" y1="88" x2="595" y2="42" stroke="#00a344" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#st-arrow)"/>
<line x1="470" y1="108" x2="595" y2="109" stroke="#00a344" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#st-arrow)"/>
<line x1="470" y1="128" x2="595" y2="176" stroke="#00a344" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#st-arrow)"/>
<!-- Right: cloud resources -->
<g transform="translate(600,18)">
<rect width="80" height="48" rx="6" fill="#e8f5e9" stroke="#00a344" stroke-width="1.5"/>
<text x="40" y="42" text-anchor="middle" font-family="system-ui,sans-serif" font-size="10" font-weight="600" fill="#00185e">VM</text>
</g>
<g transform="translate(600,85)">
<rect width="80" height="48" rx="6" fill="#e8f5e9" stroke="#00a344" stroke-width="1.5"/>
<text x="40" y="42" text-anchor="middle" font-family="system-ui,sans-serif" font-size="10" font-weight="600" fill="#00185e">VNet</text>
</g>
<g transform="translate(600,152)">
<rect width="80" height="48" rx="6" fill="#e8f5e9" stroke="#00a344" stroke-width="1.5"/>
<text x="40" y="42" text-anchor="middle" font-family="system-ui,sans-serif" font-size="10" font-weight="600" fill="#00185e">DNS Zone</text>
</g>
<text x="640" y="215" text-anchor="middle" font-family="system-ui,sans-serif" font-size="11" fill="#666666">Real Resources</text>
</svg>
</section>
<!-- ─── SLIDE 15 : Terraform providers ──────────────────────────── -->
<section class="s-tf">
<h2>One tool. Every API.</h2>
<ul>
<li><strong>1000+ providers</strong> — AWS, GCP, Azure, Cloudflare, GitHub, Kubernetes…</li>
<li>Not just cloud — GitHub teams, Datadog monitors, PagerDuty schedules, DNS records</li>
</ul>
<p><em>If it has an API, there's a Terraform provider for it.</em></p>
</section>
<!-- ─── SLIDE 16 : Terraform code ───────────────────────────────── -->
<section class="s-tf">
<p class="filename"># main.tf</p>
<pre><code class="language-hcl" data-trim>
terraform {
required_providers {
openstack = { source = "terraform-provider-openstack/openstack", version = "~> 3.0" }
ovh = { source = "ovh/ovh", version = "~> 1.0" }
}
}
resource "openstack_compute_instance_v2" "web" {
name = "finistdevs-web"
image_name = "Debian 12"
flavor_name = "b3-8"
network { name = "Ext-Net" }
}
resource "ovh_domain_zone_record" "web" {
zone = "example.com"
subdomain = "finistdevs"
fieldtype = "A"
target = openstack_compute_instance_v2.web.access_ip_v4
}
</code></pre>
</section>
<!-- ─── SLIDE 17 : Terraform CLI ────────────────────────────────── -->
<section class="s-tf">
<p class="filename">$ terminal</p>
<pre><code class="language-bash" data-trim data-noescape>
$ terraform init
Initializing provider plugins...
- Finding terraform-provider-openstack/openstack versions matching "~> 3.0"...
- Finding ovh/ovh versions matching "~> 1.0"...
- Installing terraform-provider-openstack/openstack v3.0.0...
- Installing ovh/ovh v1.3.0...
Terraform has been successfully initialized!
$ terraform plan
openstack_compute_instance_v2.web: Refreshing state...
Plan: 2 to add, 0 to change, 0 to destroy.
$ terraform apply
openstack_compute_instance_v2.web: Creating...
openstack_compute_instance_v2.web: Creation complete after 45s [id=abc-123]
ovh_domain_zone_record.web: Creating...
ovh_domain_zone_record.web: Creation complete after 3s [id=456]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
</code></pre>
</section>
<!-- ─── SLIDE 18 : OpenTofu ─────────────────────────────────────── -->
<section class="s-tf">
<h2>HashiCorp changed Terraform's license.</h2>
<ul>
<li><strong>BSL</strong> instead of MPL — no longer truly open-source</li>
<li>The community responded: <strong>OpenTofu</strong>, by the OpenTF Foundation</li>
</ul>
<p><em>Drop-in replacement. Fully compatible. Community-driven.</em></p>
</section>
<!-- ─── SLIDE 19 : Terraform platforms ──────────────────────────── -->
<section class="s-tf">
<h2>Terraform at scale needs a platform.</h2>
<p>
<strong>Terraform Enterprise / HCP Terraform</strong> — HashiCorp's commercial offering: remote state, RBAC, audit logs<br>
<strong>Spacelift</strong> — GitOps-first CI/CD for Terraform (and OpenTofu)<br>
<strong>Atlantis</strong> — open-source: plan &amp; apply triggered by pull requests<br>
<strong>env0, Scalr</strong> — SaaS alternatives with policy &amp; cost management
</p>
</section>
<!-- ─── SLIDE 20 : Ansible intro ────────────────────────────────── -->
<section class="s-ansible">
<h2><span class="ansible-col">Ansible</span></h2>
<p>Your servers are provisioned. Now make them do something.</p>
</section>
<!-- ─── SLIDE 21 : Ansible concepts ─────────────────────────────── -->
<section class="s-ansible">
<h2>Push, not pull. SSH, not agents.</h2>
<ul>
<li>YAML playbooks run tasks in order, across any number of hosts</li>
<li>No daemon. No certificate authority. Just <strong>Python + SSH</strong></li>
<li>Idempotent modules — same playbook runs safely again and again</li>
</ul>
<svg width="750" height="250" viewBox="0 0 750 250" xmlns="http://www.w3.org/2000/svg" style="margin-top:0.5em;">
<defs>
<marker id="a-push" viewBox="0 0 10 7" refX="9" refY="3.5" markerWidth="9" markerHeight="7" orient="auto">
<polygon points="0 0,10 3.5,0 7" fill="#CC0000"/>
</marker>
</defs>
<!-- Laptop -->
<rect x="50" y="68" width="100" height="65" rx="6" fill="#2b2b3a"/>
<rect x="56" y="73" width="88" height="48" rx="3" fill="#14142a"/>
<text x="100" y="103" text-anchor="middle" fill="#5cf" font-family="monospace" font-size="13">&gt;_</text>
<rect x="38" y="135" width="124" height="9" rx="4" fill="#444"/>
<text x="100" y="164" text-anchor="middle" fill="#4d5592" font-size="13" font-weight="bold" font-family="sans-serif">Control Node</text>
<!-- PUSH badge -->
<rect x="310" y="10" width="80" height="28" rx="14" fill="#CC0000"/>
<text x="350" y="30" text-anchor="middle" fill="#fff" font-size="14" font-weight="bold" font-family="sans-serif">PUSH</text>
<!-- Arrows -->
<line x1="170" y1="100" x2="548" y2="30" stroke="#CC0000" stroke-width="2" marker-end="url(#a-push)"/>
<line x1="170" y1="100" x2="548" y2="72" stroke="#CC0000" stroke-width="2" marker-end="url(#a-push)"/>
<line x1="170" y1="100" x2="548" y2="117" stroke="#CC0000" stroke-width="2" marker-end="url(#a-push)"/>
<line x1="170" y1="100" x2="548" y2="162" stroke="#CC0000" stroke-width="2" marker-end="url(#a-push)"/>
<line x1="170" y1="100" x2="548" y2="207" stroke="#CC0000" stroke-width="2" marker-end="url(#a-push)"/>
<!-- SSH label -->
<rect x="310" y="46" width="42" height="18" rx="3" fill="#fff" fill-opacity="0.85"/>
<text x="331" y="59" text-anchor="middle" fill="#CC0000" font-size="11" font-weight="bold" font-family="monospace">SSH</text>
<!-- Servers -->
<g fill="#f2f2f8" stroke="#aaa">
<g transform="translate(555,16)"><rect width="120" height="28" rx="4"/><circle cx="105" cy="19" r="3" fill="#5cf"/></g>
<g transform="translate(555,58)"><rect width="120" height="28" rx="4"/><circle cx="105" cy="19" r="3" fill="#5cf"/></g>
<g transform="translate(555,103)"><rect width="120" height="28" rx="4"/><circle cx="105" cy="19" r="3" fill="#5cf"/></g>
<g transform="translate(555,148)"><rect width="120" height="28" rx="4"/><circle cx="105" cy="19" r="3" fill="#5cf"/></g>
<g transform="translate(555,193)"><rect width="120" height="28" rx="4"/><circle cx="105" cy="19" r="3" fill="#5cf"/></g>
</g>
<text x="615" y="245" text-anchor="middle" fill="#4d5592" font-size="12" font-family="sans-serif">Managed Hosts</text>
<text x="615" y="12" text-anchor="middle" fill="#999" font-size="10" font-style="italic" font-family="sans-serif">no agent required</text>
</svg>
</section>
<!-- ─── SLIDE 22 : Ansible code ─────────────────────────────────── -->
<section class="s-ansible">
<p class="filename"># playbook/webserver.yml</p>
<pre><code class="language-yaml" data-trim>
- name: Configure web server
hosts: webservers
become: true
tasks:
- name: Install nginx
ansible.builtin.package:
name: nginx
state: present
- name: Deploy nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Restart nginx
handlers:
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted
</code></pre>
</section>
<!-- ─── SLIDE 23 : Ansible CLI ──────────────────────────────────── -->
<section class="s-ansible">
<p class="filename">$ terminal</p>
<pre><code class="language-bash" data-trim data-noescape>
$ ansible-playbook -i inventory playbook/webserver.yml
PLAY [Configure web server] ***************************************************
TASK [Gathering Facts] ********************************************************
ok: [finistdevs-web]
TASK [Install nginx] **********************************************************
changed: [finistdevs-web]
TASK [Deploy nginx config] ****************************************************
changed: [finistdevs-web]
RUNNING HANDLER [Restart nginx] ***********************************************
changed: [finistdevs-web]
PLAY RECAP ********************************************************************
finistdevs-web : ok=4 changed=3 unreachable=0 failed=0 skipped=0
</code></pre>
</section>
<!-- ─── SLIDE 24 : Ansible operations ───────────────────────────── -->
<section class="s-ansible">
<h2>Not just configuration. Operations.</h2>
<ul>
<li>Patch 200 servers tonight</li>
<li>Roll out a kernel upgrade with a canary strategy</li>
<li>Run a compliance audit across your whole fleet</li>
</ul>
<p><em>Ansible is the tool you reach for when you need to do something — once, or every week.</em></p>
</section>
<!-- ─── SLIDE 25 : Ansible Galaxy ───────────────────────────────── -->
<section class="s-ansible">
<h2>The community does the heavy lifting.</h2>
<ul>
<li><strong>Ansible Galaxy</strong> — 10,000+ ready-made roles and collections</li>
<li>Don't write a playbook to install Docker from scratch — someone already did</li>
</ul>
<p><em>Just <code>ansible-galaxy install geerlingguy.docker</code>.</em></p>
</section>
<!-- ─── SLIDE 26 : Ansible platforms ────────────────────────────── -->
<section class="s-ansible">
<h2>Ansible at scale: open-source vs enterprise.</h2>
<p>
<strong>AWX</strong> — open-source web UI, API, and scheduler for Ansible<br>
<strong>Ansible Automation Platform</strong> (Red Hat) — enterprise version of AWX, with support &amp; integrations<br>
<strong>Semaphore</strong> — lightweight open-source alternative to AWX<br>
<em>The core Ansible engine remains Apache 2.0 — truly open-source.</em>
</p>
</section>
<!-- ─── SLIDE 27 : Puppet intro ─────────────────────────────────── -->
<section class="s-puppet">
<h2><span class="puppet-col">Puppet</span></h2>
<p>Your servers are configured. Now keep them that way.</p>
</section>
<!-- ─── SLIDE 28 : Puppet concepts ──────────────────────────────── -->
<section class="s-puppet">
<h2>Pull, not push. Agents, not SSH.</h2>
<ul>
<li>Every 30 minutes, each <code>puppet-agent</code> polls the Puppet Server</li>
<li>Compiles a catalog and enforces it locally</li>
</ul>
<p><em>Drift is corrected automatically — without anyone lifting a finger.</em></p>
<svg width="750" height="250" viewBox="0 0 750 250" xmlns="http://www.w3.org/2000/svg" style="margin-top:0.5em;">
<defs>
<marker id="a-pull" viewBox="0 0 10 7" refX="9" refY="3.5" markerWidth="9" markerHeight="7" orient="auto">
<polygon points="0 0,10 3.5,0 7" fill="#A06010"/>
</marker>
</defs>
<!-- Puppet Server -->
<rect x="290" y="10" width="170" height="60" rx="8" fill="#fdf4e8" stroke="#A06010" stroke-width="2"/>
<rect x="300" y="22" width="36" height="36" rx="4" fill="#f5e6d0" stroke="#A06010"/>
<line x1="306" y1="32" x2="324" y2="32" stroke="#c08030"/><line x1="306" y1="39" x2="324" y2="39" stroke="#c08030"/><line x1="306" y1="46" x2="324" y2="46" stroke="#c08030"/>
<rect x="346" y="24" width="24" height="30" rx="2" fill="#fff" stroke="#A06010"/>
<text x="380" y="35" fill="#A06010" font-size="9" font-family="sans-serif">catalog</text>
<text x="375" y="88" text-anchor="middle" fill="#4d5592" font-size="13" font-weight="bold" font-family="sans-serif">Puppet Server</text>
<!-- Clock -->
<circle cx="540" cy="40" r="18" fill="none" stroke="#A06010" stroke-width="1.5"/>
<line x1="540" y1="40" x2="540" y2="28" stroke="#A06010" stroke-width="1.5"/>
<line x1="540" y1="40" x2="550" y2="40" stroke="#A06010" stroke-width="1.5"/>
<text x="540" y="72" text-anchor="middle" fill="#A06010" font-size="10" font-family="sans-serif">every 30 min</text>
<!-- Pull arrows -->
<line x1="95" y1="160" x2="330" y2="74" stroke="#A06010" stroke-width="1.8" stroke-dasharray="6,4" marker-end="url(#a-pull)"/>
<line x1="225" y1="160" x2="350" y2="74" stroke="#A06010" stroke-width="1.8" stroke-dasharray="6,4" marker-end="url(#a-pull)"/>
<line x1="375" y1="160" x2="375" y2="74" stroke="#A06010" stroke-width="1.8" stroke-dasharray="6,4" marker-end="url(#a-pull)"/>
<line x1="525" y1="160" x2="400" y2="74" stroke="#A06010" stroke-width="1.8" stroke-dasharray="6,4" marker-end="url(#a-pull)"/>
<line x1="655" y1="160" x2="420" y2="74" stroke="#A06010" stroke-width="1.8" stroke-dasharray="6,4" marker-end="url(#a-pull)"/>
<rect x="420" y="112" width="38" height="17" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="439" y="124" text-anchor="middle" fill="#A06010" font-size="11" font-weight="bold" font-style="italic" font-family="sans-serif">pull</text>
<!-- Agent nodes -->
<g transform="translate(50,160)"><rect width="90" height="34" rx="4" fill="#f2f2f8" stroke="#aaa"/><circle cx="76" cy="22" r="3" fill="#5cf"/><rect x="26" y="36" width="38" height="14" rx="3" fill="#A06010"/><text x="45" y="47" text-anchor="middle" fill="#fff" font-size="8" font-family="sans-serif">agent</text></g>
<g transform="translate(180,160)"><rect width="90" height="34" rx="4" fill="#f2f2f8" stroke="#aaa"/><circle cx="76" cy="22" r="3" fill="#5cf"/><rect x="26" y="36" width="38" height="14" rx="3" fill="#A06010"/><text x="45" y="47" text-anchor="middle" fill="#fff" font-size="8" font-family="sans-serif">agent</text></g>
<g transform="translate(330,160)"><rect width="90" height="34" rx="4" fill="#f2f2f8" stroke="#aaa"/><circle cx="76" cy="22" r="3" fill="#5cf"/><rect x="26" y="36" width="38" height="14" rx="3" fill="#A06010"/><text x="45" y="47" text-anchor="middle" fill="#fff" font-size="8" font-family="sans-serif">agent</text></g>
<g transform="translate(480,160)"><rect width="90" height="34" rx="4" fill="#f2f2f8" stroke="#aaa"/><circle cx="76" cy="22" r="3" fill="#5cf"/><rect x="26" y="36" width="38" height="14" rx="3" fill="#A06010"/><text x="45" y="47" text-anchor="middle" fill="#fff" font-size="8" font-family="sans-serif">agent</text></g>
<g transform="translate(610,160)"><rect width="90" height="34" rx="4" fill="#f2f2f8" stroke="#aaa"/><circle cx="76" cy="22" r="3" fill="#5cf"/><rect x="26" y="36" width="38" height="14" rx="3" fill="#A06010"/><text x="45" y="47" text-anchor="middle" fill="#fff" font-size="8" font-family="sans-serif">agent</text></g>
<text x="375" y="240" text-anchor="middle" fill="#4d5592" font-size="12" font-family="sans-serif">Managed Nodes</text>
</svg>
</section>
<!-- ─── SLIDE 29 : Puppet code ──────────────────────────────────── -->
<section class="s-puppet">
<p class="filename"># manifests/webserver.pp</p>
<pre><code class="language-puppet" data-trim>
class webserver {
package { 'nginx':
ensure => installed,
}
file { '/etc/nginx/nginx.conf':
ensure => file,
content => template('webserver/nginx.conf.erb'),
notify => Service['nginx'],
}
service { 'nginx':
ensure => running,
enable => true,
}
}
</code></pre>
</section>
<!-- ─── SLIDE 30 : Puppet CLI ───────────────────────────────────── -->
<section class="s-puppet">
<p class="filename">$ terminal</p>
<pre><code class="language-bash" data-trim data-noescape>
$ puppet agent -t
Info: Using environment 'production'
Info: Retrieving pluginfacts
Info: Caching catalog for finistdevs-web.example.com
Info: Applying configuration version '1713052408'
Notice: /Stage[main]/Webserver/Package[nginx]/ensure: created
Notice: /Stage[main]/Webserver/File[/etc/nginx/nginx.conf]/content:
--- /etc/nginx/nginx.conf
+++ /tmp/puppet-file20260413
Notice: /Stage[main]/Webserver/Service[nginx]/ensure: started
Notice: Applied catalog in 12.34 seconds
</code></pre>
</section>
<!-- ─── SLIDE 31 : Puppet drift detection ───────────────────────── -->
<section class="s-puppet">
<h2>Someone SSH'd in and changed something.</h2>
<ul>
<li>Puppet noticed. Puppet fixed it.</li>
<li>Continuous compliance — not just at deploy time. <strong>Every. 30. Minutes.</strong></li>
<li>No more gardening your servers by hand</li>
</ul>
<svg width="750" height="200" viewBox="0 0 750 200" xmlns="http://www.w3.org/2000/svg" style="margin-top:0.5em;">
<defs>
<marker id="a-cycle" viewBox="0 0 10 7" refX="9" refY="3.5" markerWidth="9" markerHeight="7" orient="auto">
<polygon points="0 0,10 3.5,0 7" fill="#4d5592"/>
</marker>
</defs>
<!-- 1: Drift detected -->
<rect x="30" y="50" width="190" height="80" rx="12" fill="#fde8ec" stroke="#e00034" stroke-width="1.5"/>
<polygon points="70,68 80,88 60,88" fill="none" stroke="#e00034" stroke-width="2" stroke-linejoin="round"/>
<text x="70" y="84" text-anchor="middle" fill="#e00034" font-size="11" font-weight="bold" font-family="sans-serif">!</text>
<text x="135" y="84" text-anchor="middle" fill="#e00034" font-size="13" font-weight="bold" font-family="sans-serif">Drift</text>
<text x="135" y="100" text-anchor="middle" fill="#e00034" font-size="13" font-weight="bold" font-family="sans-serif">detected</text>
<circle cx="42" cy="56" r="10" fill="#e00034"/><text x="42" y="60" text-anchor="middle" fill="#fff" font-size="10" font-weight="bold" font-family="sans-serif">1</text>
<!-- 2: Agent applies catalog -->
<rect x="520" y="50" width="200" height="80" rx="12" fill="#fdf4e8" stroke="#A06010" stroke-width="1.5"/>
<circle cx="565" cy="85" r="13" fill="none" stroke="#A06010" stroke-width="2"/>
<circle cx="565" cy="85" r="5" fill="#A06010"/>
<text x="632" y="84" text-anchor="middle" fill="#A06010" font-size="13" font-weight="bold" font-family="sans-serif">Agent</text>
<text x="632" y="100" text-anchor="middle" fill="#A06010" font-size="13" font-weight="bold" font-family="sans-serif">applies catalog</text>
<circle cx="532" cy="56" r="10" fill="#A06010"/><text x="532" y="60" text-anchor="middle" fill="#fff" font-size="10" font-weight="bold" font-family="sans-serif">2</text>
<!-- 3: Compliant -->
<rect x="275" y="140" width="200" height="50" rx="12" fill="#e6f7ee" stroke="#00a344" stroke-width="1.5"/>
<polyline points="310,164 318,174 332,156" fill="none" stroke="#00a344" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<text x="395" y="171" text-anchor="middle" fill="#00a344" font-size="15" font-weight="bold" font-family="sans-serif">Compliant</text>
<circle cx="287" cy="146" r="10" fill="#00a344"/><text x="287" y="150" text-anchor="middle" fill="#fff" font-size="10" font-weight="bold" font-family="sans-serif">3</text>
<!-- Curved arrows -->
<path d="M 220,72 C 330,20 420,20 518,72" fill="none" stroke="#4d5592" stroke-width="2" marker-end="url(#a-cycle)"/>
<path d="M 600,132 C 580,155 530,168 477,165" fill="none" stroke="#4d5592" stroke-width="2" marker-end="url(#a-cycle)"/>
<path d="M 275,165 C 210,168 140,155 120,132" fill="none" stroke="#4d5592" stroke-width="2" marker-end="url(#a-cycle)"/>
<text x="375" y="42" text-anchor="middle" fill="#4d5592" font-size="10" font-family="sans-serif">continuous enforcement loop</text>
</svg>
</section>
<!-- ─── SLIDE 32 : Puppet platforms ─────────────────────────────── -->
<section class="s-puppet">
<h2>Puppet: large fleets, zero drift.</h2>
<ul>
<li>Continuous compliance, auditability, and guaranteed state — at scale</li>
<li>Best suited for enterprises with hundreds or thousands of long-lived servers</li>
<li>Fewer SaaS options than Terraform or Ansible</li>
</ul>
<p><em>Puppet Enterprise and Foreman are self-hosted. No managed cloud offering.</em></p>
</section>
<!-- ─── SLIDE 33 : Puppet community ─────────────────────────────── -->
<section class="s-puppet">
<h2>The ecosystem outlives the company.</h2>
<ul>
<li><strong>Vox Pupuli</strong> — 100+ open-source Puppet modules, community-maintained</li>
<li><strong>OpenVox</strong> — an emerging open-source fork of the Puppet core</li>
</ul>
<p><em>The community is strong, with or without Puppet Inc.</em></p>
</section>
<!-- ─── SLIDE 34 : They're complementary ────────────────────────── -->
<section>
<h2>They're not competing. They're complementary.</h2>
<p>Each one solves a different layer of the same problem.</p>
<svg width="800" height="280" viewBox="0 0 800 280" xmlns="http://www.w3.org/2000/svg" style="margin-top:0.5em;">
<defs>
<marker id="a-down" viewBox="0 0 10 10" refX="5" refY="10" markerWidth="10" markerHeight="10" orient="auto">
<polygon points="0 0,10 0,5 10" fill="#4d5592"/>
</marker>
</defs>
<!-- Terraform layer -->
<rect x="120" y="10" width="560" height="64" rx="10" fill="#7B42BC" fill-opacity="0.12" stroke="#7B42BC" stroke-width="2"/>
<text x="220" y="38" fill="#7B42BC" font-size="18" font-weight="bold" font-family="sans-serif">Terraform</text>
<text x="338" y="38" fill="#5a2e8c" font-size="16" font-family="sans-serif">— Provision</text>
<text x="220" y="56" fill="#8855cc" font-size="11" font-style="italic" font-family="sans-serif">VMs, networks, cloud resources</text>
<text x="100" y="42" text-anchor="end" fill="#7B42BC" font-size="11" font-weight="bold" font-family="sans-serif">Day 0</text>
<!-- Arrow -->
<line x1="400" y1="76" x2="400" y2="94" stroke="#4d5592" stroke-width="2.5" marker-end="url(#a-down)"/>
<!-- Ansible layer -->
<rect x="120" y="104" width="560" height="64" rx="10" fill="#CC0000" fill-opacity="0.1" stroke="#CC0000" stroke-width="2"/>
<text x="220" y="132" fill="#CC0000" font-size="18" font-weight="bold" font-family="sans-serif">Ansible</text>
<text x="306" y="132" fill="#a00" font-size="16" font-family="sans-serif">— Configure</text>
<text x="220" y="150" fill="#cc3333" font-size="11" font-style="italic" font-family="sans-serif">packages, services, app deployment</text>
<text x="100" y="136" text-anchor="end" fill="#CC0000" font-size="11" font-weight="bold" font-family="sans-serif">Day 1</text>
<!-- Arrow -->
<line x1="400" y1="170" x2="400" y2="188" stroke="#4d5592" stroke-width="2.5" marker-end="url(#a-down)"/>
<!-- Puppet layer -->
<rect x="120" y="198" width="560" height="64" rx="10" fill="#A06010" fill-opacity="0.1" stroke="#A06010" stroke-width="2"/>
<path d="M172,210 L188,216 L188,234 C188,244 172,250 172,250 C172,250 156,244 156,234 L156,216 Z" fill="none" stroke="#A06010" stroke-width="2"/>
<polyline points="164,232 170,238 180,224" fill="none" stroke="#A06010" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<text x="220" y="228" fill="#A06010" font-size="18" font-weight="bold" font-family="sans-serif">Puppet</text>
<text x="298" y="228" fill="#8a5010" font-size="16" font-family="sans-serif">— Enforce</text>
<text x="220" y="246" fill="#b07020" font-size="11" font-style="italic" font-family="sans-serif">continuous compliance, drift correction</text>
<text x="100" y="230" text-anchor="end" fill="#A06010" font-size="11" font-weight="bold" font-family="sans-serif">Day 2+</text>
</svg>
</section>
<!-- ─── SLIDE 35 : Real-world stack ─────────────────────────────── -->
<section>
<h2>A common production setup:</h2>
<ol>
<li class="fragment"><span class="tf-col">Terraform</span> provisions the VM</li>
<li class="fragment"><span class="ansible-col">Ansible</span> configures it and deploys the app</li>
<li class="fragment"><span class="puppet-col">Puppet</span> continuously enforces compliance</li>
</ol>
</section>
<!-- ─── SLIDE 36 : Closing ──────────────────────────────────────── -->
<section class="title-slide">
<h1>Questions?</h1>
<p class="subtitle">Thank you!</p>
<p class="meta" style="margin-top:2em;">
Arnaud Prémel-Cabic &nbsp;·&nbsp; arnaud.premel-cabic@ovhcloud.com<br>
Slides: <a href="https://ministicraft.pages.git.cloud.arnaud-pc.fr/finistdev-configuration-as-code/" target="_blank">ministicraft.pages.git.cloud.arnaud-pc.fr/finistdev-configuration-as-code/</a>
</p>
</section>
</div>
</div>
<script src="vendor/reveal.js/dist/reveal.js"></script>
<script src="vendor/reveal.js/plugin/notes/notes.js"></script>
<script src="vendor/reveal.js/plugin/highlight/highlight.js"></script>
<script>
const toolLogos = {
's-tf': 'https://cdn.simpleicons.org/terraform',
's-ansible': 'https://cdn.simpleicons.org/ansible',
's-puppet': 'https://cdn.simpleicons.org/puppet/C17F00'
};
const altNames = {'s-tf': 'Terraform', 's-ansible': 'Ansible', 's-puppet': 'Puppet'};
// Create fixed logo elements outside Reveal (like OVHcloud logo)
const logoElements = {};
Object.entries(toolLogos).forEach(([cls, src]) => {
const img = document.createElement('img');
img.src = src;
img.alt = altNames[cls];
img.className = 'tool-logo-global';
document.body.appendChild(img);
logoElements[cls] = img;
});
// Show/hide the correct tool logo based on current slide's class
function updateToolLogo() {
const slide = Reveal.getCurrentSlide();
Object.entries(logoElements).forEach(([cls, img]) => {
img.style.display = slide && slide.classList.contains(cls) ? 'block' : 'none';
});
}
Reveal.initialize({
width: 1280,
height: 720,
margin: 0.04,
minScale: 0.2,
maxScale: 2.0,
hash: true,
slideNumber: 'c/t',
transition: 'slide',
backgroundTransition: 'fade',
controls: true,
progress: true,
center: true,
plugins: [ RevealNotes, RevealHighlight ]
}).then(updateToolLogo);
Reveal.on('slidechanged', updateToolLogo);
</script>
</body>
</html>