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

1195 lines
72 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>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>
<ul>
<li>Some 2 years old. Some a few months. Some brand new.</li>
<li>None of them are exactly alike.</li>
</ul>
<!-- 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>
<ul>
<li>Prod breaks on a Tuesday</li>
<li>Can't reproduce the bug locally</li>
<li>Can't scale reliably</li>
<li>Can't onboard a new server without fear</li>
</ul>
<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>Your AI assistant can write it*. 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>Machine-readable files. Version-controlled. Automated.</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 style="text-align:center;">
<h2 style="text-align:center;">Meet the three musketeers of infrastructure.</h2>
<p style="font-size:1.2em; margin-top:1em;">
<img src="https://cdn.simpleicons.org/puppet/C17F00" alt="Puppet" style="height:0.9em; vertical-align:middle;"> <span class="puppet-col">Puppet</span> &nbsp;·&nbsp;
<img src="https://cdn.simpleicons.org/ansible" alt="Ansible" style="height:0.9em; vertical-align:middle;"> <span class="ansible-col">Ansible</span> &nbsp;·&nbsp;
<img src="https://cdn.simpleicons.org/terraform" alt="Terraform" style="height:0.9em; vertical-align:middle;"> <span class="tf-col">Terraform</span>
</p>
<p><em>Each solves a different problem.</em></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 : What is Terraform ────────────────────────────── -->
<section class="s-tf">
<h2>What is Terraform?</h2>
<ul>
<li><strong>Infrastructure as Code</strong> tool for provisioning cloud resources</li>
<li>Created by <strong>HashiCorp</strong> in 2014</li>
<li>Written in <strong>Go</strong></li>
<li>BSL license since 2023 (was MPL)</li>
</ul>
</section>
<!-- ─── SLIDE 14 : 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 15 : 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 with care.</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 16 : 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 — DNS, monitoring, CI/CD, anything with an API</li>
</ul>
<p><em>If it has an API, there's a Terraform provider for it.</em></p>
</section>
<!-- ─── SLIDE 17 : 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 18 : 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 19 : 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 20 : Terraform platforms ──────────────────────────── -->
<section class="s-tf">
<h2>Terraform at scale needs a platform.</h2>
<ul>
<li><strong>Terraform Enterprise / HCP Terraform</strong> — remote state, RBAC, audit logs</li>
<li><strong>Spacelift</strong> — GitOps-first CI/CD for Terraform and OpenTofu</li>
<li><strong>Atlantis</strong> — open-source, plan &amp; apply from pull requests</li>
<li><strong>env0, Scalr</strong> — SaaS with policy &amp; cost management</li>
</ul>
</section>
<!-- ─── SLIDE 21 : 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 22 : What is Ansible ──────────────────────────────── -->
<section class="s-ansible">
<h2>What is Ansible?</h2>
<ul>
<li><strong>Agentless automation</strong> tool for configuration and orchestration</li>
<li>Created by <strong>Michael DeHaan</strong> in 2012</li>
<li>Acquired by <strong>Red Hat</strong> in 2015</li>
<li>Written in <strong>Python</strong> — Apache 2.0 license</li>
</ul>
</section>
<!-- ─── SLIDE 23 : Ansible concepts ─────────────────────────────── -->
<section class="s-ansible">
<h2>Push-based. Runs over SSH.</h2>
<ul>
<li>YAML playbooks run tasks in order, across any number of hosts</li>
<li>Nothing to install on target servers — 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 24 : 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 25 : 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 26 : 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>The go-to tool for one-off tasks and recurring operations.</em></p>
</section>
<!-- ─── SLIDE 27 : 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 28 : Ansible platforms ────────────────────────────── -->
<section class="s-ansible">
<h2>Ansible at scale: open-source vs enterprise.</h2>
<ul>
<li><strong>AWX</strong> — open-source web UI, API, and scheduler</li>
<li><strong>Ansible Automation Platform</strong> (Red Hat) — enterprise AWX with support</li>
<li><strong>Semaphore</strong> — lightweight open-source alternative</li>
</ul>
<p><em>Core engine remains Apache 2.0 — truly open-source.</em></p>
</section>
<!-- ─── SLIDE 29 : 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 30 : What is Puppet ───────────────────────────────── -->
<section class="s-puppet">
<h2>What is Puppet?</h2>
<ul>
<li><strong>Configuration management</strong> tool for enforcing system state</li>
<li>Created by <strong>Luke Kanies</strong> in 2005</li>
<li>Puppet Inc. acquired by <strong>Perforce</strong> in 2022</li>
<li>Written in <strong>Ruby</strong> and <strong>Clojure</strong></li>
</ul>
</section>
<!-- ─── SLIDE 31 : 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.</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 32 : 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 33 : 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 34 : 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 manual remediation</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 35 : 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 36 : 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 37 : They're complementary ────────────────────────── -->
<section>
<h2>They're not competing. They're complementary.</h2>
<p>Each 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>
<!-- Arrow between Terraform and Ansible -->
<line x1="400" y1="78" x2="400" y2="100" 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>
<!-- Arrow between Ansible and Puppet -->
<line x1="400" y1="172" x2="400" y2="194" 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"/>
<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>
</svg>
</section>
<!-- ─── SLIDE 38 : 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 39 : 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>