<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Opinion on The Nested Lab</title>
    <link>https://thenestedlab.com/tags/opinion/</link>
    <description>Recent content in Opinion on The Nested Lab</description>
    <generator>Hugo</generator>
    <language>en-gb</language>
    <lastBuildDate>Wed, 16 Sep 2026 06:30:00 +0100</lastBuildDate>
    <atom:link href="https://thenestedlab.com/tags/opinion/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>validateOnly everywhere: plan mode for infrastructure</title>
      <link>https://thenestedlab.com/posts/validateonly-everywhere/</link>
      <pubDate>Wed, 16 Sep 2026 06:30:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/validateonly-everywhere/</guid>
      <description>Terraform has plan. Kubernetes has &amp;ndash;dry-run. Your vRO workflows have nothing — unless you give them a validateOnly input and make the wrapper cascade it. A short argument for the single most valuable checkbox in the lab factory, with the failures it caught.</description>
      <content:encoded><![CDATA[<p>Terraform has <code>plan</code>. Kubernetes has <code>--dry-run=server</code>. Ansible has
<code>--check</code>. Every mature infrastructure tool grew a way to say &ldquo;tell me what
you&rsquo;d do, then don&rsquo;t&rdquo; — because the alternative is finding out at 2am, two
hours into a bringup, that a hostname doesn&rsquo;t resolve.</p>
<p>vRO workflows don&rsquo;t come with one. This is the case for adding it to every
single one you write, and cascading it through every wrapper.</p>
<h2 id="the-shape">The shape</h2>
<p>Every catalog item in <a href="/posts/one-catalog-item-one-vcf-instance/">the lab factory</a>
has a boolean input, <code>validateOnly</code>, default false. When true the workflow
does <em>everything it can without changing anything</em>:</p>
<ul>
<li>authenticate to every endpoint it would touch</li>
<li>resolve every name it would use, and fail on the ones that don&rsquo;t</li>
<li>generate every spec it would submit, and run the target&rsquo;s own validation
API on it where one exists (the VCF Installer has one; use it)</li>
<li>check for collisions — names, IPs, existing objects</li>
<li>report what it <em>would</em> have created, then return <code>CREATE_SUCCESSFUL</code></li>
</ul>
<p>The wrapper — the one form that chains hosts, bringup, supervisor, fleet
components, identity — has the same checkbox, and <strong>cascades</strong> it to every
component. Tick everything, tick validateOnly, request. Thirty seconds to
a few minutes later you have a full-stack plan against the <em>live</em>
environment, and nothing has moved.</p>
<h2 id="what-it-caught">What it caught</h2>
<p>Not hypothetically. On a built environment, the cascaded dry run of the
whole stack reported:</p>
<ul>
<li>edge cluster: <strong>already exists</strong> — correctly recorded, wrapper carried on</li>
<li>Ops for Logs: <strong>IP_IN_USE</strong> on the planned address — right, it&rsquo;s deployed</li>
<li>Ops for Networks: same</li>
<li>supervisor: the existing one would be reused; the per-service plan
listed which services were already active</li>
<li>identity: bind succeeded, group resolved, no changes needed</li>
</ul>
<p>That&rsquo;s a plan output. On a <em>fresh</em> environment the same run has caught, at
various times: a DNS record missing for one of ~40 required names (the
installer&rsquo;s own pre-flight found it, in seconds, instead of bringup
finding it in hour two); a stale content-library image ID; a form field
arriving <code>null</code> because a custom form hadn&rsquo;t finished re-importing — which
is a <em>publishing</em> bug the dry run surfaced before anyone requested
anything real.</p>
<h2 id="the-argument-against-answered">The argument against, answered</h2>
<p>&ldquo;It doubles the code.&rdquo; It doesn&rsquo;t — it moves the <code>if (!validateOnly)</code> guard
around the mutating call, and the validation logic is code you should have
had anyway. What it <em>does</em> force is separating &ldquo;compute what to do&rdquo; from
&ldquo;do it&rdquo;, which is how the workflows should have been structured in the
first place.</p>
<p>&ldquo;Some things can&rsquo;t be validated without doing them.&rdquo; True. Say so in the
result summary — &ldquo;would deploy X; no pre-validation available&rdquo; — rather
than skipping the item. Partial plans are still plans.</p>
<p>&ldquo;We have a test environment.&rdquo; You have <em>a</em> test environment. A dry run
against the <em>target</em> is what catches the collision with the thing that&rsquo;s
already there.</p>
<h2 id="make-it-the-smoke-test">Make it the smoke test</h2>
<p>The best consequence: a validateOnly request against a known environment
is a <strong>regression test for the automation itself</strong>, runnable on every
change. The factory&rsquo;s smoke runner does exactly this — request every item
with <code>validateOnly: true</code>, assert <code>CREATE_SUCCESSFUL</code>, diff the plan
summary against the last run. It takes minutes and it has caught more
bugs in the workflows than any amount of code review.</p>
<p><img alt="Deploy VCF Stack request form: one checkbox per component, and validateOnly" loading="lazy" src="/images/ui/f2-f00-stack-form-validateonly.jpg">
<em>The same form, real or dry-run. One checkbox decides.</em></p>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>For anyone who has sat through a failed change window, the value is
obvious: a full dry run against the <em>real</em> estate before anything moves.
Fewer failed changes, shorter windows, and a plan output that answers the
change board&rsquo;s questions before they&rsquo;re asked. It also gives auditors
something they rarely get from infrastructure automation — evidence of what
was going to happen, produced by the same tooling that then did it.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li>Add <code>validateOnly</code> to <strong>every</strong> workflow. Default false. Wrappers
cascade it.</li>
<li>Dry-run does everything but mutate: auth, resolve, generate, call the
target&rsquo;s validator, check collisions, report.</li>
<li>Where a step truly can&rsquo;t be pre-validated, <em>say so</em> in the summary.
Never skip it silently.</li>
<li>A dry run against the real target is a plan. A dry run on every change
is a smoke test. Same checkbox.</li>
<li>The refactor it forces — compute, <em>then</em> act — is the one you wanted.</li>
</ul>
<p><em>Part of <a href="/series/the-lab-factory/">The Lab Factory</a>.</em></p>
<hr>
<p><em>Lab environment; opinions my own.</em></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
