<?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>Blueprint on The Nested Lab</title>
    <link>https://thenestedlab.com/tags/blueprint/</link>
    <description>Recent content in Blueprint on The Nested Lab</description>
    <generator>Hugo</generator>
    <language>en-gb</language>
    <lastBuildDate>Wed, 16 Sep 2026 07:40:00 +0100</lastBuildDate>
    <atom:link href="https://thenestedlab.com/tags/blueprint/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>A datacenter in a catalog tile: nested ESXi pods via VCF Automation All Apps</title>
      <link>https://thenestedlab.com/posts/nested-esxi-via-vcfa-all-apps/</link>
      <pubDate>Wed, 16 Sep 2026 07:40:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/nested-esxi-via-vcfa-all-apps/</guid>
      <description>The whole isolated pod — namespace, trunk subnets, binding maps, two dual-NIC nested ESXi hosts with an ISO attached, SSH/HTTPS VIPs — as one VCF Automation blueprint, published to the catalog. Anatomy of the blueprint, the ordering it enforces, and the three things it can&amp;rsquo;t express.</description>
      <content:encoded><![CDATA[<p>Everything in this series so far was built with <code>kubectl</code> and API calls.
That proves the platform. It doesn&rsquo;t make a <em>product</em>. This post turns the
pod into a <strong>catalog item</strong>: fill in a name, pick a VPC, click Request, and
a few minutes later there&rsquo;s a datacenter-in-miniature with two SSH prompts
waiting.</p>
<p><img alt="VCFA catalog: the nested-esxi-pod tile" loading="lazy" src="/images/ui/u1-catalog-tile.jpg"></p>
<h2 id="all-apps-in-one-paragraph">All Apps in one paragraph</h2>
<p>VCF Automation 9.1 has two provisioning models side by side. <strong>VM Apps</strong> is
the classic Aria Automation path — cloud templates through an IaaS engine
that drives vCenter. <strong>All Apps</strong> is the supervisor-native path: the
blueprint composes Kubernetes objects (a Supervisor Namespace, VM Service
VMs, NSX subnets, VKS clusters) and the vSphere Supervisor&rsquo;s controllers
reconcile them. A blueprint is <code>formatVersion: 2</code>; its resources are
<code>CCI.Supervisor.Namespace</code> and <code>CCI.Supervisor.Resource</code> — the latter is
literally &ldquo;here&rsquo;s a manifest, apply it in that namespace.&rdquo;</p>
<p>That makes the blueprint a <em>composition</em> of the manifests from the earlier
posts, with two additions: inputs, and <code>dependsOn</code>.</p>
<h2 id="the-blueprint-section-by-section">The blueprint, section by section</h2>
<h3 id="inputs--the-form">Inputs — the form</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">inputs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">podName</span><span class="p">:</span><span class="w">  </span>{<span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="nt">string, default</span><span class="p">:</span><span class="w"> </span><span class="nt">nested-pod, pattern</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;^[a-z0-9]([-a-z0-9]*[a-z0-9])?$&#39;</span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">vpcName</span><span class="p">:</span><span class="w">  </span>{<span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="nt">string, description</span><span class="p">:</span><span class="w"> </span><span class="l">Must exist and be Realized before deploying.}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">esxOva</span><span class="p">:</span><span class="w">   </span>{<span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="nt">string, default</span><span class="p">:</span><span class="w"> </span><span class="l">vmi-61bb062ddfc506b79}  </span><span class="w"> </span><span class="c"># Nested ESXi 9.1 appliance</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">isoImage</span><span class="p">:</span><span class="w"> </span>{<span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="nt">string, default</span><span class="p">:</span><span class="w"> </span><span class="l">vmi-39f562e2ae9e9c501}  </span><span class="w"> </span><span class="c"># the ISO to attach</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">vmClass</span><span class="p">:</span><span class="w">  </span>{<span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="nt">string, default</span><span class="p">:</span><span class="w"> </span><span class="nt">best-effort-large, enum</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">best-effort-large, best-effort-xlarge, best-effort-2xlarge]}</span><span class="w">
</span></span></span></code></pre></div><p><img alt="The request form" loading="lazy" src="/images/ui/u2-request-form.jpg"></p>
<h3 id="the-namespace--with-libraries-attached">The namespace — with libraries attached</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">namespace</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">CCI.Supervisor.Namespace</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">generateName</span><span class="p">:</span><span class="w"> </span><span class="l">${input.podName}-       </span><span class="w"> </span><span class="c"># NOT name — new namespaces get a suffix</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">className</span><span class="p">:</span><span class="w"> </span><span class="l">large</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">regionName</span><span class="p">:</span><span class="w"> </span><span class="l">f06</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">vpcName</span><span class="p">:</span><span class="w"> </span><span class="l">${input.vpcName}             </span><span class="w"> </span><span class="c"># pins the namespace to the pod&#39;s VPC</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">storageClasses</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>{<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="nt">vSAN Default Storage Policy, limit</span><span class="p">:</span><span class="w"> </span><span class="l">400000Mi}]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">zones</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>{<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="nt">domain-c9, cpuLimit</span><span class="p">:</span><span class="w"> </span><span class="nt">40000M, memoryLimit</span><span class="p">:</span><span class="w"> </span><span class="l">64000Mi, ...}]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">contentSources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="nt">ISO, type</span><span class="p">:</span><span class="w"> </span><span class="l">ContentLibrary}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="nt">f06-vks-lib01, type</span><span class="p">:</span><span class="w"> </span><span class="l">ContentLibrary}</span><span class="w">
</span></span></span></code></pre></div><p><code>contentSources</code> is the line that closes the gap a lot of first attempts
hit: a VCFA-created namespace has <strong>no content library</strong>, so there are no
<code>VirtualMachineImage</code>s and nothing can be deployed. Declaring the libraries
here attaches them at creation.</p>
<h3 id="the-topology--ordered-on-purpose">The topology — ordered on purpose</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">snTrunk</span><span class="p">:</span><span class="w">   </span>{<span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="nt">CCI.Supervisor.Resource, properties</span><span class="p">:</span><span class="w"> </span>{<span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">${resource.namespace.id}, manifest: &lt;Subnet sn-trunk&gt;}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">snMgmt</span><span class="p">:</span><span class="w">    </span>{<span class="nt">dependsOn</span><span class="p">:</span><span class="w"> </span><span class="nt">[snTrunk], ...  manifest</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;Subnet sn-mgmt&gt;}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">snVmotion</span><span class="p">:</span><span class="w"> </span>{<span class="nt">dependsOn</span><span class="p">:</span><span class="w"> </span><span class="nt">[snMgmt],  ...  manifest</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;Subnet sn-vmotion&gt;}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">bmMgmt</span><span class="p">:</span><span class="w">    </span>{<span class="nt">... manifest</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;SubnetConnectionBindingMap sn-mgmt -&gt; sn-trunk, vlan 1610&gt;}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">bmVmotion</span><span class="p">:</span><span class="w"> </span>{<span class="nt">... manifest</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;SubnetConnectionBindingMap sn-vmotion -&gt; sn-trunk, vlan 1611&gt;}</span><span class="w">
</span></span></span></code></pre></div><p>The <code>dependsOn</code> chain is the whole reason <a href="/posts/three-datacenters-one-ip-plan/">every pod has identical
CIDRs</a>: fresh VPCs realize subnets
in creation order, and the blueprint fixes that order.</p>
<p><img alt="Blueprint canvas and YAML side by side" loading="lazy" src="/images/ui/u3-blueprint-canvas-yaml.jpg"></p>
<h3 id="the-hosts--dual-nic-iso-attached-bootstrapped-by-ovf">The hosts — dual-NIC, ISO attached, bootstrapped by OVF</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">esx01</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">CCI.Supervisor.Resource</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">dependsOn</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">bmMgmt]                   </span><span class="w"> </span><span class="c"># no point booting before VLAN 1610 exists</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">manifest</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">VirtualMachine               </span><span class="w"> </span><span class="c"># vmoperator.vmware.com/v1alpha5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">hardware</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">cdrom</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="l">... the ISO, declared, connected ... ]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">network</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">interfaces</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="l">eth0 -&gt; sn-trunk, eth1 -&gt; sn-trunk ]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">bootstrap</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">vAppConfig</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="l">guestinfo.hostname / ipaddress / vlan / ... ]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">wait</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">fields</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>{<span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="nt">status.powerState, value</span><span class="p">:</span><span class="w"> </span><span class="l">PoweredOn}]</span><span class="w">
</span></span></span></code></pre></div><p>(Abridged — the full resource carries the image references, VM class,
guest ID and the complete <code>guestinfo</code> set.)</p>
<p>Two vNICs, both on the trunk — <a href="/series/the-vpc-pod-papers/">the nested equivalent of a VCF host&rsquo;s two
pNICs</a>. The ISO rides along as a declarative
CD-ROM. And the <code>wait</code> block makes the deployment&rsquo;s <em>completion</em> mean
something: the request doesn&rsquo;t finish until the host is powered on.</p>
<h3 id="the-doors--one-vip-per-host">The doors — one VIP per host</h3>
<p>A <code>VirtualMachineService</code> of type <code>LoadBalancer</code> per host, selecting it by
label and publishing 22 and 443, and a blueprint <strong>output</strong> that reads the
VIP back out of the service&rsquo;s status:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">outputs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">esx01Ssh</span><span class="p">:</span><span class="w"> </span>{<span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;ssh root@${resource.esx01Access.object.status.loadBalancer.ingress[0].ip}&#34;</span>}<span class="w">
</span></span></span></code></pre></div><p>The outputs surface in the deployment view — the requester gets the SSH
command, not a scavenger hunt.</p>
<p><img alt="Deployment topology after a successful request" loading="lazy" src="/images/ui/u4-deployment-topology.jpg"></p>
<p><img alt="Request → deployment in progress → complete" loading="lazy" src="/images/u7-catalog-request-flow.gif">
<em>The request flow, end to end.</em></p>
<h2 id="what-the-blueprint-cannot-express-yet">What the blueprint cannot express (yet)</h2>
<p>Three cluster-scoped objects have <strong>no blueprint resource type</strong>, and they
must exist <em>before</em> the request — <a href="/posts/the-lb-that-must-exist-first/">in this order</a>:</p>
<ol>
<li><code>VPC</code> — <code>privateIPs: 172.30.0.0/16</code>, same in every pod</li>
<li><code>VPCAttachment</code> — connectivity profile with the service gateway; the LB
creation fails loudly without it</li>
<li><code>LoadBalancer</code> — silently, permanently required before the namespace</li>
</ol>
<p>Today that&rsquo;s a short script or a runbook step per pod. The honest framing:
the blueprint is the <em>pod</em>; the VPC is the <em>tenancy</em>, and tenancy is
still created one layer up. I&rsquo;d expect that layer to become blueprintable;
until then, keep the three calls next to the blueprint in version control.</p>
<h2 id="publishing-one-version-at-a-time">Publishing: one version at a time</h2>
<p>Blueprint → <code>BlueprintVersion</code> → release. Validation happens at <em>version</em>
time, not create time, and the result lives in <code>status.validationMessages</code>
rather than the HTTP code — a 200 with <code>ContentValid: False</code> is a thing.
And only <strong>one</strong> version can be published: unrelease 1.0.0 before releasing
1.1.0, or you get a 409. (The full list of sharp edges is
<a href="/series/the-vpc-pod-papers/">its own post</a>.)</p>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>This is where platform engineering turns into a service. The difference
between &ldquo;we can build you an environment&rdquo; and &ldquo;request one from the
catalog&rdquo; is the difference between days and minutes — and between a
bespoke build and one that is consistent, quota-controlled and recorded
every time. For an organisation that means:</p>
<ul>
<li><strong>Time-to-environment</strong> measured in minutes, requested by the people who
need it, without a queue.</li>
<li><strong>Consistency by construction</strong> — every environment comes from the same
definition, so support, training material and runbooks all match.</li>
<li><strong>Governance built in</strong> — quotas, ownership, history and clean teardown
are properties of the deployment record, not a spreadsheet.</li>
</ul>
<p>The nested-ESXi pod is one catalog item. The same approach delivers any
environment shape: application stacks for developers, sandboxes for a
proof of concept, demo kits for a sales team, isolated builds for a
partner.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li>All Apps blueprints are <strong>compositions of manifests</strong>: <code>CCI.Supervisor.Namespace</code>
plus <code>CCI.Supervisor.Resource</code> per object. If it works with <code>kubectl</code>, it
works in a blueprint.</li>
<li><code>generateName</code>, not <code>name</code>, for the namespace; <code>contentSources</code> to attach
libraries at creation; <code>zones</code>/<code>storageClasses</code> flat, not wrapped.</li>
<li><code>dependsOn</code> is how you get <strong>deterministic CIDRs</strong> — order the subnets.</li>
<li><code>wait.fields</code> turns &ldquo;request complete&rdquo; into &ldquo;host is powered on&rdquo;.</li>
<li>VPC / VPCAttachment / LoadBalancer are <strong>prerequisites outside the
blueprint</strong>, in that order, before every request.</li>
<li>One published version per blueprint; validation in <code>status</code>, not the
HTTP response.</li>
</ul>
<p><em>Previously: <a href="/posts/shared-services-for-isolated-tenants/">shared services for isolated tenants</a>.
This closes the Pod Papers&rsquo; core arc — the companion posts on
<a href="/series/the-vpc-pod-papers/">dual-NIC</a>, <a href="/series/the-vpc-pod-papers/">no-DHCP bootstrap</a>
and <a href="/series/the-vpc-pod-papers/">blueprint gotchas</a> fill in the details.</em></p>
<hr>
<p><em>Lab environment; opinions my own. Blueprint <code>nested-esxi-pod</code> 1.1.0 is
live in the lab catalog; YAML above trimmed for length.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Blueprinting the supervisor: seven CCI blueprint gotchas</title>
      <link>https://thenestedlab.com/posts/cci-blueprint-gotchas/</link>
      <pubDate>Wed, 16 Sep 2026 07:10:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/cci-blueprint-gotchas/</guid>
      <description>Everything that made the nested-esxi-pod blueprint fail validation before it worked: ${input} inside flow mappings, name vs generateName, flat zones, contentSources, one-published-version, validation-in-status, and the image-sync race. Short, specific, and each one cost me a cycle.</description>
      <content:encoded><![CDATA[<p>The <a href="/posts/nested-esxi-via-vcfa-all-apps/">nested-esxi-pod blueprint</a>
works. Getting there took seven distinct &ldquo;ContentValid: False&rdquo; (or worse:
a 200 that quietly did nothing). None of them are in the docs I could
find; all of them are five-minute fixes once you know. Here they are, in
the order they bit.</p>
<h2 id="1-inputx-is-illegal-inside-a-flow-mapping">1. <code>${input.x}</code> is illegal inside a flow mapping</h2>
<p>This looks like valid YAML and valid blueprint syntax:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- {<span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="nt">guestinfo.hostname, value</span><span class="p">:</span><span class="w"> </span>{<span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;esx01.${input.podName}.res.lab&#34;</span>}}<span class="w">
</span></span></span></code></pre></div><p>It fails content validation. The expression parser doesn&rsquo;t reach into
flow-style (<code>{...}</code>) mappings. Block style is fine:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">guestinfo.hostname</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">value</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="l">esx01.${input.podName}.res.lab</span><span class="w">
</span></span></span></code></pre></div><p>Mixed style in the same list is fine too — only the entries that carry an
expression need to be block-style. (This is why the blueprint&rsquo;s
<code>vAppConfig</code> list looks inconsistent; it&rsquo;s deliberate.)</p>
<h2 id="2-name-vs-generatename-for-a-new-namespace">2. <code>name</code> vs <code>generateName</code> for a new namespace</h2>
<p>A <code>CCI.Supervisor.Namespace</code> you&rsquo;re <em>creating</em> must use <code>generateName</code>.
<code>metadata.name</code> is rejected by the CCI API — the platform appends a random
suffix, so <code>pod-a-</code> becomes <code>pod-a-dgf5p</code>. Everything downstream should
reference <code>${resource.namespace.id}</code>, never a literal name.</p>
<h2 id="3-zones-and-storage-classes-are-flat">3. Zones and storage classes are flat</h2>
<p>Early attempts wrapped them the way the raw CCI API does:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">initialClassConfigOverrides</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">zones</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">...]</span><span class="w">
</span></span></span></code></pre></div><p>In a blueprint they&rsquo;re top-level properties of the namespace resource:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">zones</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">domain-c9</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cpuLimit</span><span class="p">:</span><span class="w"> </span><span class="l">40000M</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">memoryLimit</span><span class="p">:</span><span class="w"> </span><span class="l">64000Mi</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">storageClasses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">vSAN Default Storage Policy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">limit</span><span class="p">:</span><span class="w"> </span><span class="l">400000Mi</span><span class="w">
</span></span></span></code></pre></div><p>And zones are <strong>required</strong> — omit them and the API says &ldquo;Zone should be
specified&rdquo;, which at least is a clear message.</p>
<h2 id="4-a-new-namespace-has-no-content-library">4. A new namespace has no content library</h2>
<p>Deploy the namespace, deploy a VM, and get: no <code>VirtualMachineImage</code>
found. A VCFA-created namespace attaches <strong>no</strong> content libraries by
default. The fix is one block:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">contentSources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="nt">ISO, type</span><span class="p">:</span><span class="w"> </span><span class="l">ContentLibrary}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="nt">f06-vks-lib01, type</span><span class="p">:</span><span class="w"> </span><span class="l">ContentLibrary}</span><span class="w">
</span></span></span></code></pre></div><p>Without it you&rsquo;re in the vSphere Client attaching libraries to a namespace
by hand, which rather defeats the catalog.</p>
<h2 id="5-images-sync-after-attach--wait-for-statusdisks">5. Images sync <em>after</em> attach — wait for <code>status.disks</code></h2>
<p>Even with libraries attached at creation, the first VM create in a fresh
namespace can be rejected:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">no disks found in image ... status.disks
</span></span></code></pre></div><p>The image objects appear immediately; their disk metadata syncs over the
next 1–3 minutes. The quota webhook checks <code>status.disks</code> and refuses
until it&rsquo;s populated. In a blueprint, put the hosts <code>dependsOn</code> something
that takes a couple of minutes (the binding maps did the job here), or add
an explicit wait. In a script, poll:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">kubectl get virtualmachineimage -n &lt;ns&gt; &lt;vmi&gt; -o jsonpath=&#39;{.status.disks}&#39;
</span></span></code></pre></div><h2 id="6-validation-lives-in-status-not-the-http-code">6. Validation lives in <code>status</code>, not the HTTP code</h2>
<p>Creating a <code>BlueprintVersion</code> returns 200 whether or not the content is
valid. Read the object back:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">status:
</span></span><span class="line"><span class="cl">  contentValid: false
</span></span><span class="line"><span class="cl">  validationMessages:
</span></span><span class="line"><span class="cl">    - &#34;... unexpected token ...&#34;
</span></span></code></pre></div><p>If your pipeline checks the response code, it will happily publish a
broken blueprint. Check <code>status.contentValid</code> and print the messages.</p>
<h2 id="7-only-one-published-version--409-on-the-second">7. Only one published version — 409 on the second</h2>
<p>Release 1.1.0 while 1.0.0 is released and you get a 409. It isn&rsquo;t a
transient conflict; it&rsquo;s the rule. <strong>Unrelease</strong> the current version, then
release the new one. Practically that means a publish step is
<code>unrelease old → release new</code>, and there&rsquo;s a short window where the
catalog item has no released version. Do it when nobody&rsquo;s requesting.</p>
<h2 id="bonus-the-things-that-arent-blueprint-problems">Bonus: the things that aren&rsquo;t blueprint problems</h2>
<p>Three prerequisites have <strong>no blueprint resource type</strong> and have to exist
before the request — VPC, VPCAttachment, LoadBalancer, <a href="/posts/the-lb-that-must-exist-first/">in that
order</a>. The blueprint&rsquo;s <code>vpcName</code>
input says &ldquo;must exist and be Realized&rdquo;, and it means it. Nothing in the
blueprint fails if they&rsquo;re missing; the deployment just never gets a VIP.</p>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>VCF Automation&rsquo;s All Apps model is new, and new platforms have edges. None
of these seven are documented; all of them stall a first project by days if
you meet them cold. The value of a delivery partner who has already built
on the platform isn&rsquo;t the YAML — it&rsquo;s that a customer&rsquo;s first blueprint
publishes on day one instead of week two, and that the sharp edges are
encoded into templates and provisioning scripts where users never meet
them.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li>Expressions need <strong>block-style YAML</strong>; flow mappings don&rsquo;t get parsed.</li>
<li><code>generateName</code>, and reference the namespace by <code>${resource.x.id}</code>.</li>
<li><code>zones</code> and <code>storageClasses</code> are <strong>flat</strong> and zones are required.</li>
<li><code>contentSources</code> on the namespace, or nothing can be deployed.</li>
<li>Wait for image <code>status.disks</code> before the first VM (1–3 min).</li>
<li>Check <code>status.contentValid</code> — the HTTP code lies by omission.</li>
<li>One released version per blueprint: unrelease, then release.</li>
</ul>
<p><em>Companion to <a href="/posts/nested-esxi-via-vcfa-all-apps/">a datacenter in a catalog tile</a>.</em></p>
<hr>
<p><em>Lab environment; opinions my own. Error text captured live.</em></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
