<?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>Api on The Nested Lab</title>
    <link>https://thenestedlab.com/tags/api/</link>
    <description>Recent content in Api on The Nested Lab</description>
    <generator>Hugo</generator>
    <language>en-gb</language>
    <lastBuildDate>Wed, 16 Sep 2026 06:10:00 +0100</lastBuildDate>
    <atom:link href="https://thenestedlab.com/tags/api/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Driving the VCF Installer API from vRO: generate, validate, start, walk away</title>
      <link>https://thenestedlab.com/posts/driving-the-vcf-installer-api-from-vro/</link>
      <pubDate>Wed, 16 Sep 2026 06:10:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/driving-the-vcf-installer-api-from-vro/</guid>
      <description>Stage 2 of the lab factory: a vRO workflow that turns an environment number into a complete VCF 9.1 deployment spec, runs the installer&amp;rsquo;s own validation, starts bringup and hands back a task id — because the request dies long before the eight-hour build does. Plus how the wrapper slips the two-hour leash.</description>
      <content:encoded><![CDATA[<p>The <a href="/posts/porting-a-powershell-deploy-script/">nested hosts exist</a>. Now
they need to become a VCF instance: vCenter, NSX, SDDC Manager, the fleet
components. The VCF 9.1 Installer appliance does that from a deployment
spec — a few hundred lines of JSON — through an API. This post is the vRO
workflow that drives it, and the three design constraints that shaped it:
nobody edits the spec by hand, the request can&rsquo;t outlive two hours, and
nested hosts fail hardware validation.</p>
<p>Unlike stage 1 this isn&rsquo;t VM provisioning, so it&rsquo;s not a cloud template.
It&rsquo;s a <strong>vRO workflow published directly as a catalog item</strong> through an
Orchestrator content source.</p>
<h2 id="the-spec-is-generated-never-edited">The spec is generated, never edited</h2>
<p>A vRO action, <code>buildVcfDeploymentSpec(environment, hostFqdns, labPassword, …)</code>, returns the whole spec as a string. Its structure was reconciled
against a <em>validated</em> export from a real bringup — the installer UI lets
you export the spec it accepted — and everything variable derives from the
environment number X:</p>
<table>
	<thead>
			<tr>
					<th>Element</th>
					<th>Pattern</th>
					<th>f03</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Names</td>
					<td><code>f0X-m01-*</code></td>
					<td><code>f03-m01-vc01.res.lab</code></td>
			</tr>
			<tr>
					<td>Subnets</td>
					<td><code>10.(20+X).&lt;sub&gt;.0/24</code></td>
					<td><code>10.23.1.0/24</code> (mgmt)</td>
			</tr>
			<tr>
					<td>VLANs</td>
					<td><code>2X0&lt;sub&gt;</code></td>
					<td>2301 mgmt … 2306 TEP</td>
			</tr>
			<tr>
					<td>Gateways</td>
					<td><code>.254</code></td>
					<td><code>10.23.1.254</code></td>
			</tr>
			<tr>
					<td>vMotion / vSAN ranges</td>
					<td><code>.1–.16</code></td>
					<td><code>10.23.3.1-16</code></td>
			</tr>
			<tr>
					<td>NSX TEP pool</td>
					<td><code>.6.1–.6.32</code></td>
					<td><code>10.23.6.1-32</code></td>
			</tr>
			<tr>
					<td>SDDC Manager</td>
					<td><code>f0X-vcf01.res.lab</code></td>
					<td><code>f03-vcf01.res.lab</code></td>
			</tr>
	</tbody>
</table>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">n</span>   <span class="o">=</span> <span class="nb">parseInt</span><span class="p">(</span><span class="nx">environment</span><span class="p">.</span><span class="nx">substring</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="mi">10</span><span class="p">);</span>   <span class="c1">// &#34;f03&#34; -&gt; 3
</span></span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">pfx</span> <span class="o">=</span> <span class="nx">environment</span> <span class="o">+</span> <span class="s2">&#34;-m01&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">net</span> <span class="o">=</span> <span class="s2">&#34;10.&#34;</span> <span class="o">+</span> <span class="p">(</span><span class="mi">20</span> <span class="o">+</span> <span class="nx">n</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">vlan</span><span class="p">(</span><span class="nx">o</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">2000</span> <span class="o">+</span> <span class="p">(</span><span class="nx">n</span> <span class="o">*</span> <span class="mi">100</span><span class="p">)</span> <span class="o">+</span> <span class="nx">o</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">gw</span><span class="p">(</span><span class="nx">sub</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">net</span> <span class="o">+</span> <span class="s2">&#34;.&#34;</span> <span class="o">+</span> <span class="nx">sub</span> <span class="o">+</span> <span class="s2">&#34;.254&#34;</span><span class="p">;</span> <span class="p">}</span>
</span></span></code></pre></div><p>Static across environments: DNS, NTP, subdomain, component sizes, vSAN ESA
FTT=1, and the component build versions pinned to the installer binaries.
One lab password feeds every credential field (the UI export scrubs them;
the action puts them back per the API schema).</p>
<p>Two spec-level decisions worth stealing:</p>
<ul>
<li><code>skipEsxThumbprintValidation: true</code> instead of carrying per-host
<code>sslThumbprint</code>. Supported, and the right trade-off for a lab.</li>
<li>Ops and Automation are <strong>checkboxes</strong> that add their blocks to the spec
— and the installer only accepts a <code>licenseServerSpec</code> when Ops is
present, so the action adds them together or not at all.</li>
</ul>
<h2 id="the-workflow-authenticate--validate--start--return">The workflow: authenticate → validate → start → return</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">1. POST /v1/tokens                 installer login
</span></span><span class="line"><span class="cl">2. POST /v1/sddcs/validations      the installer&#39;s OWN pre-flight on the spec
</span></span><span class="line"><span class="cl">   (poll until COMPLETED; fail on any FAILED check)
</span></span><span class="line"><span class="cl">3. if validateOnly -&gt; return the validation report; touch nothing
</span></span><span class="line"><span class="cl">4. POST /v1/sddcs                  start bringup -&gt; sddcTaskId
</span></span><span class="line"><span class="cl">5. return { sddcTaskId, installerUrl }
</span></span></code></pre></div><p>Step 2 is the <a href="/posts/validateonly-everywhere/">validateOnly</a> story made
concrete: the installer will tell you, in seconds, that
<code>f03-m01-nsx01.res.lab</code> doesn&rsquo;t resolve, that an IP is in use, that a host
isn&rsquo;t reachable. Two hours into a bringup is a bad time to learn that.
Every one of the ~40 DNS records the pre-flight wants is created ahead of
time by a one-shot PowerShell script (<code>New-LabEnvDnsRecords.ps1</code>) — DNS is
a prerequisite, not a step.</p>
<h2 id="the-two-hour-leash-and-how-to-slip-it">The two-hour leash, and how to slip it</h2>
<p>A request from the catalog carries a token with a roughly <strong>two-hour</strong>
lifetime, and a bringup takes around <strong>eight</strong>. So by default the workflow
is fire-and-forget: <code>waitForCompletion=false</code>, return the task id, watch
progress in the installer UI. A <code>watchTaskId</code> input lets you re-attach
later and poll an already-running bringup from a new request.</p>
<p>The wrapper that chains <em>everything</em> — hosts, bringup, then the day-N
components that need bringup to be finished — has a neater trick. The
catalog-bound parent deploys the hosts, submits bringup, and then
<strong>re-executes itself as a plain vRO run</strong> (Orchestrator → Run, no catalog
token, no two-hour kill) carrying the hidden <code>bringupWatchTaskId</code>. That
continuation polls the installer task to completion — eight hours, fine —
and then runs certificates, fleet items, edge, supervisor and identity.
Watch it under <em>Orchestrator → Activity → Runs</em>. The catalog request
itself completes in under an hour — 47 minutes on the run pictured below — having
<em>started the work correctly and handed off</em>.</p>
<p><img alt="Orchestrator runs: the catalog-bound parent (13:12→14:00) and the continuation it spawned (14:00 → 01:56 next day)" loading="lazy" src="/images/ui/f5-f00-vro-runs-parent-continuation.jpg">
<em>Two rows, one build. The parent returns inside the catalog&rsquo;s window; the continuation waits out the bringup and does the day-N work.</em></p>
<p><img alt="The deployment&rsquo;s stackSummary output: hosts ready, bringup completed, continuation started — watch it in Orchestrator › Activity › Runs" loading="lazy" src="/images/ui/f4-f00-stack-outputs-continuation.jpg"></p>
<h2 id="nested-host-frictions">Nested-host frictions</h2>
<p>Three things a physical bringup never meets:</p>
<ul>
<li><strong>HCL validation vs virtual NVMe.</strong> The installer&rsquo;s hardware check
blocks the virtual NVMe controller. Fix at the vLCM layer:
<code>enforce_hcl_validation=false</code> on the image policy. The vSAN health test
<code>nvmeonhcl</code> also complains; silenced via the vSAN API, best-effort with
manual fallback.</li>
<li><strong>DVS compatibility appears late.</strong> After bringup, NSX takes 1–2 hours
to settle before the supervisor&rsquo;s zones endpoint stops returning 500.
If the supervisor stage fails &ldquo;No compatible DVS&rdquo; on a fresh instance,
wait and re-run just that item.</li>
<li><strong>TSM-SSH.</strong> Bringup wants SSH on the hosts; the wrapper enables it
host-direct via SOAP before submitting.</li>
</ul>
<h2 id="stale-schema-the-failure-that-looks-like-a-bug-and-isnt">Stale schema: the failure that looks like a bug and isn&rsquo;t</h2>
<p>Add an input to the vRO workflow after the catalog item exists and the
form will show the new field, the request will record its value, and the
workflow will receive <strong>null</strong> — Service Broker keeps the old request
schema until the content source re-imports. The workflow null-guards every
boolean and aborts with &ldquo;inputs not mapped&rdquo; rather than running with
silently-wrong options. Fix: re-import the content source, confirm the
schema, submit a <em>new</em> request (resubmitting an old one reuses the old
payload).</p>
<p>There are actually three async layers between &ldquo;publish&rdquo; and &ldquo;mappable
request&rdquo; — vRO processing the import, the catalog schema after re-import,
and the form service still enforcing the previous custom form for a minute
or two. Same symptom for all three. Check timing before assuming a bug.</p>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>Repeatable, generated VCF deployments matter well beyond a lab: a second
site, a disaster-recovery instance, a new business unit, an environment per
supported release. Generating the specification from a validated reference
removes the class of errors that comes from editing hundreds of lines of
JSON by hand, and running the installer&rsquo;s own validation first turns
&ldquo;find out in hour two&rdquo; into &ldquo;find out in minute one&rdquo;. It&rsquo;s the difference
between a VCF deployment being a project and being a procedure.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li><strong>Generate the spec</strong> from a validated export plus one number. Nobody
hand-edits JSON at 2am.</li>
<li>Run the <strong>installer&rsquo;s own validation</strong> first, and make it a mode you
can request on its own.</li>
<li>Pre-create DNS. Enable SSH. Disable HCL enforcement on virtual NVMe.</li>
<li>Respect the request lifetime: <strong>start, return a task id, re-attach</strong>.
For a long chain, have the workflow re-run itself outside the catalog.</li>
<li>Null-guard every input and fail loud; stale schemas are a fact of life
after adding inputs.</li>
<li>On a fresh instance, give NSX an hour before you expect DVS
compatibility.</li>
</ul>
<p><em>Part of <a href="/series/the-lab-factory/">The Lab Factory</a>. Previously:
<a href="/posts/porting-a-powershell-deploy-script/">porting the host script</a>.</em></p>
<hr>
<p><em>Lab environment; opinions my own. Bringup verified end-to-end on a
rebuilt environment: 305/305 tasks, <code>COMPLETED_WITH_SUCCESS</code>.</em></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
